Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# 상품주문서비스 토이프로젝트

## DATE
2023.04.05 ~ 2023.04.09

<br/>

## STACKS

![Spring Boot](https://img.shields.io/badge/Spring_Boot-F2F4F9?style=for-the-badge&logo=spring-boot)
![JWT](https://img.shields.io/badge/JWT-000000?style=for-the-badge&logo=JSON%20web%20tokens&logoColor=white)
![Git](https://img.shields.io/badge/GIT-E44C30?style=for-the-badge&logo=git&logoColor=white)
![GitHub](https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white)

<br/>

### DEPENDENCIES
```
implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
```

<br/>

## 기능

### 회원가입

<br/>

### 로그인
- jwt 응답

<br/>

### 상품등록
- 인증필요, 판매자권한

<br/>

### 상풍목록보기
- 인증필요, 권한없음

<br/>

### 상품상세보기
- 인증필요, 권한없음

<br/>

### 상품수정하기
- 인증필요, 판매자권한

<br/>

### 주문하기
- 인증필요, 고객권한

<br/>

### 주문목록보기
- 인증필요, 고객권한(본인주문), 판매자권한(본인주문), 관리자권한(모든주문)

<br/>

### 주문취소하기
- CasCase 활용
- 인증필요, 고객권한, 판매자권한

<br/>

## ERD
![ERD](https://user-images.githubusercontent.com/107831692/230870048-a42ba4a0-a6d3-43ce-ab69-bdbd64121380.png)

<br/>

## API
| Page | Action | Method | URL | Request | Response |
|------|----------------------|--------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
| 회원가입 | signUp | POST | /api/user/signup | {<br/>"username":"username",<br/>"password":"password",<br/><br/>"password_check":"passsword",<br/>"email":"email@email.com",<br/>"role":"USER"<br/>} | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 로그인 | login | POST | /api/user/login | {<br/>"username":"username",<br/>"password":"password"<br/>} | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/> |
| 상품등록 | registerProduct | POST | /api/product | {<br/>"name":"name",<br/>"price":1000,<br/>"qty":1<br/>} | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>}|
| 상품목록 | getProducts | GET | /api/products | | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 상품상세 | getProductDetails | GET | /api/product/{id} | | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 상품수정 | updateProduct | PUT | /api/product/{id} | {<br/>"name":"name",<br/>"price":1000,<br/>"qty":1<br/>} | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 주문 | order | POST | /api/order | {<br/>"products":[<br/>{<br/>"count":1,<br/>"product_id":1<br/>},<br/>{<br/>"count":1,<br/>"product_id":2<br/>},...<br/>]<br/>} | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 주문목록 | getOrders | GET | /api/orders | | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 주문목록(관리자) | getAllOrders | GET | /api/admin/orders | | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
| 주문취소 | cancelOrder | DELETE | /api/order/{id} | | {<br/>"status":200,<br/>"msg":"성공",<br/>"data":{...}<br/>} |
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ dependencies {
implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
Expand Down
38 changes: 34 additions & 4 deletions src/main/java/shop/mtcoding/metamall/MetamallApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,56 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import shop.mtcoding.metamall.model.orderproduct.OrderProduct;
import shop.mtcoding.metamall.model.orderproduct.OrderProductRepository;
import shop.mtcoding.metamall.model.ordersheet.OrderSheet;
import shop.mtcoding.metamall.model.ordersheet.OrderSheetRepository;
import shop.mtcoding.metamall.model.product.Product;
import shop.mtcoding.metamall.model.product.ProductRepository;
import shop.mtcoding.metamall.model.user.User;
import shop.mtcoding.metamall.model.user.UserRepository;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@SpringBootApplication
public class MetamallApplication {

@Bean
CommandLineRunner initData(UserRepository userRepository, ProductRepository productRepository, OrderProductRepository orderProductRepository, OrderSheetRepository orderSheetRepository){
CommandLineRunner initData(UserRepository userRepository, ProductRepository productRepository, OrderSheetRepository orderSheetRepository){
return (args)->{
// 여기에서 save 하면 됨.
// bulk Collector는 saveAll 하면 됨.
User ssar = User.builder().username("ssar").password("1234").email("ssar@nate.com").role("USER").build();
userRepository.save(ssar);
User ssar = User.builder().username("ssar").password("1234").email("ssar@nate.com").role(User.Role.USER).build();
User tester = User.builder().username("tester").password("1234").email("tester@nate.com").role(User.Role.USER).build();
User seller1 = User.builder().username("seller1").password("5678").email("seller1@email.com").role(User.Role.SELLER).build();
User seller2 = User.builder().username("seller2").password("5678").email("seller2@email.com").role(User.Role.SELLER).build();
User admin = User.builder().username("admin").password("0000").email("admin@email.com").role(User.Role.ADMIN).build();
List<User> users = Stream.of(ssar,tester,seller1,seller2,admin).collect(Collectors.toList());
userRepository.saveAll(users);

Product product1 = Product.builder().user(seller1).name("제품1").qty(3).price(10000).build();
Product product2 = Product.builder().user(seller2).name("제품2").qty(3).price(1000).build();
List<Product> products = Stream.of(product1,product2).collect(Collectors.toList());
productRepository.saveAll(products);

orderSheetSave(product1,product2,ssar,productRepository,orderSheetRepository);
orderSheetSave(product1,product2,tester,productRepository,orderSheetRepository);
};
}

private void orderSheetSave(Product product1, Product product2, User user, ProductRepository productRepository, OrderSheetRepository orderSheetRepository){
product1.order(1);
product2.order(1);
productRepository.saveAll(Arrays.asList(product1,product2));
List<OrderProduct> orders = Arrays.asList(
OrderProduct.builder().product(product1).count(1).orderPrice(product1.getPrice()).build(),
OrderProduct.builder().product(product2).count(1).orderPrice(product2.getPrice()).build());
int totalPrice = orders.stream().mapToInt(OrderProduct::getOrderPrice).sum();
OrderSheet orderSheet = OrderSheet.builder().user(user).totalPrice(totalPrice).build();
orders.forEach(order -> order.syncOrderSheet(orderSheet));
orderSheetRepository.save(orderSheet);
}
public static void main(String[] args) {
SpringApplication.run(MetamallApplication.class, args);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ public class FilterRegisterConfig {
public FilterRegistrationBean<?> jwtVerifyFilterAdd() {
FilterRegistrationBean<JwtVerifyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new JwtVerifyFilter());
registration.addUrlPatterns("/user/*");
registration.setOrder(1);
registration.addUrlPatterns("/api/products");
registration.addUrlPatterns("/api/product/*");
registration.addUrlPatterns("/api/orders");
registration.addUrlPatterns("/api/order/*");
registration.addUrlPatterns("/api/admin/*");
// registration.setOrder(1);
return registration;
}
}
17 changes: 17 additions & 0 deletions src/main/java/shop/mtcoding/metamall/config/WebMvcConfig.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
package shop.mtcoding.metamall.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import shop.mtcoding.metamall.config.interceptor.LoginInterceptor;

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

private final LoginInterceptor loginInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/products")
.addPathPatterns("/api/product/*")
.addPathPatterns("/api/orders")
.addPathPatterns("/api/order/*")
.addPathPatterns("/api/admin/*");
}

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package shop.mtcoding.metamall.config.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import shop.mtcoding.metamall.core.exception.Exception401;
import shop.mtcoding.metamall.core.session.LoginUser;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Configuration
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
LoginUser loginUser = (LoginUser) session.getAttribute("loginUser");
if(loginUser == null) throw new Exception401("잘못된 접근입니다");
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package shop.mtcoding.metamall.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.metamall.core.annotation.Auth;
import shop.mtcoding.metamall.core.annotation.RoleCk;
import shop.mtcoding.metamall.core.exception.Exception404;
import shop.mtcoding.metamall.dto.ResponseDto;
import shop.mtcoding.metamall.dto.order.OrderRequest;
import shop.mtcoding.metamall.model.orderproduct.OrderProduct;
import shop.mtcoding.metamall.model.ordersheet.OrderSheet;
import shop.mtcoding.metamall.model.ordersheet.OrderSheetRepository;
import shop.mtcoding.metamall.model.product.Product;
import shop.mtcoding.metamall.model.product.ProductRepository;
import shop.mtcoding.metamall.model.user.User;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Validated
@Transactional
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class OrderController {

private final OrderSheetRepository orderSheetRepository;
private final ProductRepository productRepository;

/**
* 주문(고객)
*/
@RoleCk.User
@PostMapping("/order")
public ResponseEntity<?> order(@Auth User user, @RequestBody OrderRequest.OrderDto orderDto){
List<OrderProduct> orders = orderDto.getProducts().stream()
.map(dto -> {
Product product = productRepository.findById(dto.getProductId()).orElseThrow(
() -> new Exception404("상품을 찾을 수 없습니다"));
product.order(dto.getCount());
return OrderProduct.builder()
.product(product)
.count(dto.getCount())
.orderPrice(product.getPrice()*dto.getCount())
.build();
}).collect(Collectors.toList());
int totalPrice = orders.stream().mapToInt(OrderProduct::getOrderPrice).sum();
OrderSheet orderSheet = OrderSheet.builder()
.user(user)
.totalPrice(totalPrice)
.build();
orders.forEach(order -> order.syncOrderSheet(orderSheet));
orderSheetRepository.save(orderSheet);
return ResponseEntity.ok().body(new ResponseDto<>().data(orderSheet));
}

/**
* 주문목록
*/
@Transactional(readOnly = true)
@GetMapping("/orders")
public ResponseEntity<?> getOrders(@Auth User user){
List<OrderSheet> orderSheets = new ArrayList<>();
if(user.getRole() == User.Role.USER) orderSheets = orderSheetRepository.findByUser(user);
else if(user.getRole() == User.Role.SELLER) orderSheets = orderSheetRepository.findBySeller(user);
return ResponseEntity.ok().body(new ResponseDto<>().data(orderSheets));
}

/**
* 모든 주문목록(관리자)
*/
@RoleCk.Admin
@Transactional(readOnly = true)
@GetMapping("/admin/orders")
public ResponseEntity<?> getAllOrders(@Auth User user){
List<OrderSheet> orderSheets = orderSheetRepository.findAll();
return ResponseEntity.ok().body(new ResponseDto<>().data(orderSheets));
}

/**
* 주문취소
*/
@DeleteMapping("/order/{id}")
public ResponseEntity<?> cancelOrder(@Auth User user, @PathVariable Long id){
OrderSheet orderSheet = null;
if(user.getRole() == User.Role.USER) orderSheet = orderSheetRepository.findByIdAndUser(id,user).orElseThrow(
() -> new Exception404("주문이 없습니다"));
else if(user.getRole() == User.Role.SELLER) orderSheet = orderSheetRepository.findByIdAndSeller(id,user).orElseThrow(
() -> new Exception404("주문이 없습니다"));
orderSheet.getOrderProductList().forEach(op -> op.getProduct().cancelOrder(op.getCount()));
orderSheetRepository.delete(orderSheet);
return ResponseEntity.ok().body(new ResponseDto<>().data(orderSheet));
}
}
Loading