diff --git a/README.md b/README.md new file mode 100644 index 0000000..f42fce1 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# 상품주문서비스 토이프로젝트 + +## DATE +2023.04.05 ~ 2023.04.09 + +
+ +## 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) + +
+ +### 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' +``` + +
+ +## 기능 + +### 회원가입 + +
+ +### 로그인 +- jwt 응답 + +
+ +### 상품등록 +- 인증필요, 판매자권한 + +
+ +### 상풍목록보기 +- 인증필요, 권한없음 + +
+ +### 상품상세보기 +- 인증필요, 권한없음 + +
+ +### 상품수정하기 +- 인증필요, 판매자권한 + +
+ +### 주문하기 +- 인증필요, 고객권한 + +
+ +### 주문목록보기 +- 인증필요, 고객권한(본인주문), 판매자권한(본인주문), 관리자권한(모든주문) + +
+ +### 주문취소하기 +- CasCase 활용 +- 인증필요, 고객권한, 판매자권한 + +
+ +## ERD +![ERD](https://user-images.githubusercontent.com/107831692/230870048-a42ba4a0-a6d3-43ce-ab69-bdbd64121380.png) + +
+ +## API +| Page | Action | Method | URL | Request | Response | +|------|----------------------|--------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------| +| 회원가입 | signUp | POST | /api/user/signup | {
"username":"username",
"password":"password",

"password_check":"passsword",
"email":"email@email.com",
"role":"USER"
} | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 로그인 | login | POST | /api/user/login | {
"username":"username",
"password":"password"
} | {
"status":200,
"msg":"성공",
"data":{...}
| +| 상품등록 | registerProduct | POST | /api/product | {
"name":"name",
"price":1000,
"qty":1
} | {
"status":200,
"msg":"성공",
"data":{...}
}| +| 상품목록 | getProducts | GET | /api/products | | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 상품상세 | getProductDetails | GET | /api/product/{id} | | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 상품수정 | updateProduct | PUT | /api/product/{id} | {
"name":"name",
"price":1000,
"qty":1
} | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 주문 | order | POST | /api/order | {
"products":[
{
"count":1,
"product_id":1
},
{
"count":1,
"product_id":2
},...
]
} | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 주문목록 | getOrders | GET | /api/orders | | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 주문목록(관리자) | getAllOrders | GET | /api/admin/orders | | {
"status":200,
"msg":"성공",
"data":{...}
} | +| 주문취소 | cancelOrder | DELETE | /api/order/{id} | | {
"status":200,
"msg":"성공",
"data":{...}
} | diff --git a/build.gradle b/build.gradle index 4943187..cca989c 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/src/main/java/shop/mtcoding/metamall/MetamallApplication.java b/src/main/java/shop/mtcoding/metamall/MetamallApplication.java index 487bb62..62ae1d8 100644 --- a/src/main/java/shop/mtcoding/metamall/MetamallApplication.java +++ b/src/main/java/shop/mtcoding/metamall/MetamallApplication.java @@ -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 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 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 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); } diff --git a/src/main/java/shop/mtcoding/metamall/config/FilterRegisterConfig.java b/src/main/java/shop/mtcoding/metamall/config/FilterRegisterConfig.java index f5ea4db..152033f 100644 --- a/src/main/java/shop/mtcoding/metamall/config/FilterRegisterConfig.java +++ b/src/main/java/shop/mtcoding/metamall/config/FilterRegisterConfig.java @@ -12,8 +12,12 @@ public class FilterRegisterConfig { public FilterRegistrationBean jwtVerifyFilterAdd() { FilterRegistrationBean 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; } } diff --git a/src/main/java/shop/mtcoding/metamall/config/WebMvcConfig.java b/src/main/java/shop/mtcoding/metamall/config/WebMvcConfig.java index 64f5d9b..d5dea83 100644 --- a/src/main/java/shop/mtcoding/metamall/config/WebMvcConfig.java +++ b/src/main/java/shop/mtcoding/metamall/config/WebMvcConfig.java @@ -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("/**") diff --git a/src/main/java/shop/mtcoding/metamall/config/interceptor/LoginInterceptor.java b/src/main/java/shop/mtcoding/metamall/config/interceptor/LoginInterceptor.java new file mode 100644 index 0000000..bc70953 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/config/interceptor/LoginInterceptor.java @@ -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; + } +} diff --git a/src/main/java/shop/mtcoding/metamall/controller/OrderController.java b/src/main/java/shop/mtcoding/metamall/controller/OrderController.java new file mode 100644 index 0000000..06fd616 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/controller/OrderController.java @@ -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 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 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 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)); + } +} diff --git a/src/main/java/shop/mtcoding/metamall/controller/ProductController.java b/src/main/java/shop/mtcoding/metamall/controller/ProductController.java new file mode 100644 index 0000000..7732f1d --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/controller/ProductController.java @@ -0,0 +1,75 @@ +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.product.ProductRequest; +import shop.mtcoding.metamall.model.product.Product; +import shop.mtcoding.metamall.model.product.ProductRepository; +import shop.mtcoding.metamall.model.user.User; + +import java.util.List; + +@Validated +@Transactional +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class ProductController { + + private final ProductRepository productRepository; + + /** + * 상품등록(판매자) + */ + @RoleCk.Seller + @PostMapping("/product") + public ResponseEntity registerProduct(@Auth User seller, @RequestBody ProductRequest.ProductDto productDto){ + Product product = productRepository.save(Product.builder() + .name(productDto.getName()) + .price(productDto.getPrice()) + .qty(productDto.getQty()) + .user(seller) + .build()); + return ResponseEntity.ok().body(new ResponseDto<>().data(product)); + } + + /** + * 상품목록 + */ + @Transactional(readOnly = true) + @GetMapping("/products") + public ResponseEntity getProducts(@Auth User user){ + List products = productRepository.findAll(); + return ResponseEntity.ok().body(new ResponseDto<>().data(products)); + } + + /** + * 상품상세 + */ + @Transactional(readOnly = true) + @GetMapping("/product/{id}") + public ResponseEntity getProductDetails(@Auth User user, @PathVariable Long id){ + Product product = productRepository.findById(id).orElseThrow( + () -> new Exception404("상품을 찾을 수 없습니다")); + return ResponseEntity.ok().body(new ResponseDto<>().data(product)); + } + + /** + * 상품수정(판매자) + */ + @RoleCk.Seller + @PutMapping("/product/{id}") + public ResponseEntity updateProduct(@Auth User user, @PathVariable Long id, @RequestBody ProductRequest.ProductDto dto){ + Product product = productRepository.findByIdAndUser(id,user).orElseThrow( + () -> new Exception404("상품을 찾을 수 없습니다")); + product.update(dto); + return ResponseEntity.ok().body(new ResponseDto<>().data(product)); + } +} diff --git a/src/main/java/shop/mtcoding/metamall/controller/UserController.java b/src/main/java/shop/mtcoding/metamall/controller/UserController.java index ddfee94..bc4b2ea 100644 --- a/src/main/java/shop/mtcoding/metamall/controller/UserController.java +++ b/src/main/java/shop/mtcoding/metamall/controller/UserController.java @@ -2,33 +2,48 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import shop.mtcoding.metamall.core.annotation.PwdCk; import shop.mtcoding.metamall.core.exception.Exception400; import shop.mtcoding.metamall.core.exception.Exception401; import shop.mtcoding.metamall.core.jwt.JwtProvider; import shop.mtcoding.metamall.dto.ResponseDto; import shop.mtcoding.metamall.dto.user.UserRequest; -import shop.mtcoding.metamall.dto.user.UserResponse; -import shop.mtcoding.metamall.model.log.login.LoginLog; -import shop.mtcoding.metamall.model.log.login.LoginLogRepository; import shop.mtcoding.metamall.model.user.User; import shop.mtcoding.metamall.model.user.UserRepository; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import javax.validation.Valid; import java.time.LocalDateTime; import java.util.Optional; -@RequiredArgsConstructor +@Validated +@Transactional @RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor public class UserController { private final UserRepository userRepository; - private final LoginLogRepository loginLogRepository; - private final HttpSession session; + + @PostMapping("/signup") + public ResponseEntity signUp(@RequestBody @Valid @PwdCk UserRequest.SignUpDto signUpDto){ + User signUpUser = userRepository.save(User.builder() + .username(signUpDto.getUsername()) + .password(signUpDto.getPassword()) + .email(signUpDto.getEmail()) + .role(signUpDto.getRole()) + .build()); + return ResponseEntity.ok().body(new ResponseDto<>().data(signUpUser)); + } @PostMapping("/login") - public ResponseEntity login(@RequestBody UserRequest.LoginDto loginDto, HttpServletRequest request) { + public ResponseEntity login(@RequestBody @Valid UserRequest.LoginDto loginDto, HttpServletRequest request) { Optional userOP = userRepository.findByUsername(loginDto.getUsername()); if (userOP.isPresent()) { // 1. 유저 정보 꺼내기 @@ -43,15 +58,10 @@ public ResponseEntity login(@RequestBody UserRequest.LoginDto loginDto, HttpS String jwt = JwtProvider.create(userOP.get()); // 4. 최종 로그인 날짜 기록 (더티체킹 - update 쿼리 발생) - loginUser.setUpdatedAt(LocalDateTime.now()); + loginUser.updateLastLoginDate(LocalDateTime.now()); // 5. 로그 테이블 기록 - LoginLog loginLog = LoginLog.builder() - .userId(loginUser.getId()) - .userAgent(request.getHeader("User-Agent")) - .clientIP(request.getRemoteAddr()) - .build(); - loginLogRepository.save(loginLog); + //AOP로 // 6. 응답 DTO 생성 ResponseDto responseDto = new ResponseDto<>().data(loginUser); diff --git a/src/main/java/shop/mtcoding/metamall/core/advice/CheckAdvice.java b/src/main/java/shop/mtcoding/metamall/core/advice/CheckAdvice.java new file mode 100644 index 0000000..1c87721 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/core/advice/CheckAdvice.java @@ -0,0 +1,70 @@ +package shop.mtcoding.metamall.core.advice; + +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import shop.mtcoding.metamall.core.exception.Exception400; +import shop.mtcoding.metamall.core.exception.Exception403; +import shop.mtcoding.metamall.dto.user.UserRequest; +import shop.mtcoding.metamall.model.user.User; + +import javax.servlet.http.HttpSession; + +@Aspect +@Order(2) +@Component +@RequiredArgsConstructor +public class CheckAdvice { + + private final HttpSession session; + + @Pointcut("@annotation(shop.mtcoding.metamall.core.annotation.PwdCk)") + public void pwd(){} + + @Pointcut("@annotation(shop.mtcoding.metamall.core.annotation.RoleCk.User)") + public void roleUser(){} + + @Pointcut("@annotation(shop.mtcoding.metamall.core.annotation.RoleCk.Seller)") + public void roleSeller(){} + + @Pointcut("@annotation(shop.mtcoding.metamall.core.annotation.RoleCk.Admin)") + public void roleAdmin(){} + + @Before("pwd()") + public void pwdCheck(JoinPoint jp){ + boolean valid = true; + for(Object arg : jp.getArgs()){ + if(arg instanceof UserRequest.SignUpDto){ + UserRequest.SignUpDto dto = (UserRequest.SignUpDto) arg; + valid = dto.getPassword().equals(dto.getPasswordCheck()); + } + } + if(!valid) throw new Exception400("비밀번호가 일치하지 않습니다"); + } + + @Before("roleUser()") + public void roleUserCheck(JoinPoint jp){ + if(!roleCheck(jp, User.Role.USER)) throw new Exception403("권한이 없습니다"); + } + + @Before("roleSeller()") + public void roleSellerCheck(JoinPoint jp){ + if(!roleCheck(jp, User.Role.SELLER)) throw new Exception403("권한이 없습니다"); + } + + @Before("roleAdmin()") + public void roleAdminCheck(JoinPoint jp){ + if(!roleCheck(jp, User.Role.ADMIN)) throw new Exception403("권한이 없습니다"); + } + + private boolean roleCheck(JoinPoint jp, User.Role role){ + for(Object arg:jp.getArgs()){ + if(arg instanceof User) return ((User) arg).getRole() == role; + } + return true; + } +} diff --git a/src/main/java/shop/mtcoding/metamall/core/advice/LogAdvice.java b/src/main/java/shop/mtcoding/metamall/core/advice/LogAdvice.java new file mode 100644 index 0000000..ce27425 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/core/advice/LogAdvice.java @@ -0,0 +1,69 @@ +package shop.mtcoding.metamall.core.advice; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import shop.mtcoding.metamall.core.session.LoginUser; +import shop.mtcoding.metamall.dto.ResponseDto; +import shop.mtcoding.metamall.model.log.error.ErrorLog; +import shop.mtcoding.metamall.model.log.error.ErrorLogRepository; +import shop.mtcoding.metamall.model.log.login.LoginLog; +import shop.mtcoding.metamall.model.log.login.LoginLogRepository; +import shop.mtcoding.metamall.model.user.User; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class LogAdvice { + private final LoginLogRepository loginLogRepository; + private final ErrorLogRepository errorLogRepository; + private final HttpSession session; + + @AfterReturning(pointcut = "execution(public * login(..,*))", returning = "response") + public void loginLogAdvice(JoinPoint jp, ResponseEntity response) { + if (response.getStatusCode() == HttpStatus.OK) { + HttpServletRequest request = null; + for (Object arg : jp.getArgs()) { + if (arg instanceof HttpServletRequest) request = (HttpServletRequest) arg; + } + + ResponseDto responseDto = (ResponseDto) response.getBody(); + User loginUser = (User) responseDto.getData(); + + LoginLog loginLog = LoginLog.builder() + .userId(loginUser.getId()) + .userAgent(request.getHeader("User-Agent")) + .clientIP(request.getRemoteAddr()) + .build(); + loginLogRepository.save(loginLog); + } + } + + @After("@annotation(org.springframework.web.bind.annotation.ExceptionHandler)") + public void errorLogAdvice(JoinPoint jp) { + LoginUser loginSession = (LoginUser) session.getAttribute("loginUser"); + for (Object arg : jp.getArgs()) { + if (arg instanceof RuntimeException) { + if (loginSession != null) { + ErrorLog errorLog = ErrorLog.builder() + .userId(Long.valueOf(loginSession.getId())) + .msg(((RuntimeException) arg).getMessage()) + .build(); + errorLogRepository.save(errorLog); + return; + } + log.error(((RuntimeException) arg).getMessage()); + } + } + } +} diff --git a/src/main/java/shop/mtcoding/metamall/core/advice/LoginAdvice.java b/src/main/java/shop/mtcoding/metamall/core/advice/LoginAdvice.java new file mode 100644 index 0000000..9465266 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/core/advice/LoginAdvice.java @@ -0,0 +1,58 @@ +package shop.mtcoding.metamall.core.advice; + +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import shop.mtcoding.metamall.core.annotation.Auth; +import shop.mtcoding.metamall.core.exception.Exception401; +import shop.mtcoding.metamall.core.session.LoginUser; +import shop.mtcoding.metamall.model.user.User; +import shop.mtcoding.metamall.model.user.UserRepository; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.stream.IntStream; + +@Aspect +@Order(1) +@Component +@RequiredArgsConstructor +public class LoginAdvice { + private final UserRepository userRepository; + + @Around("execution(* shop.mtcoding.metamall.controller.*.*(..))") + public Object loginUserAdviceAround(ProceedingJoinPoint jp) throws Throwable { + MethodSignature signature = (MethodSignature) jp.getSignature(); + Method method = signature.getMethod(); + Object[] args = jp.getArgs(); + Parameter[] parameters = method.getParameters(); + + int loginUserAopIndex = IntStream.range(0, parameters.length) + .filter(i -> parameters[i].isAnnotationPresent(Auth.class)) + .findFirst() + .orElse(-1); + + if (loginUserAopIndex >= 0) { + HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + HttpSession session = req.getSession(); + LoginUser sessionUser = (LoginUser) session.getAttribute("loginUser"); + User principal = userRepository.findById(sessionUser.getId()).orElseThrow( + () -> new Exception401("인증되지 않았습니다") + ); + args[loginUserAopIndex] = principal; + + // 해당 메서드 실행 + return jp.proceed(args); + } + + return jp.proceed(); + } +} diff --git a/src/main/java/shop/mtcoding/metamall/core/advice/MyExceptionAdvice.java b/src/main/java/shop/mtcoding/metamall/core/advice/MyExceptionAdvice.java index 50ebee2..e139e55 100644 --- a/src/main/java/shop/mtcoding/metamall/core/advice/MyExceptionAdvice.java +++ b/src/main/java/shop/mtcoding/metamall/core/advice/MyExceptionAdvice.java @@ -1,20 +1,15 @@ package shop.mtcoding.metamall.core.advice; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import shop.mtcoding.metamall.core.exception.*; -import shop.mtcoding.metamall.model.log.error.ErrorLogRepository; -@Slf4j @RequiredArgsConstructor @RestControllerAdvice public class MyExceptionAdvice { - private final ErrorLogRepository errorLogRepository; - @ExceptionHandler(Exception400.class) public ResponseEntity badRequest(Exception400 e){ return new ResponseEntity<>(e.body(), e.status()); diff --git a/src/main/java/shop/mtcoding/metamall/core/annotation/Auth.java b/src/main/java/shop/mtcoding/metamall/core/annotation/Auth.java new file mode 100644 index 0000000..d38f150 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/core/annotation/Auth.java @@ -0,0 +1,11 @@ +package shop.mtcoding.metamall.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Auth { +} diff --git a/src/main/java/shop/mtcoding/metamall/core/annotation/PwdCk.java b/src/main/java/shop/mtcoding/metamall/core/annotation/PwdCk.java new file mode 100644 index 0000000..b100639 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/core/annotation/PwdCk.java @@ -0,0 +1,11 @@ +package shop.mtcoding.metamall.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface PwdCk { +} diff --git a/src/main/java/shop/mtcoding/metamall/core/annotation/RoleCk.java b/src/main/java/shop/mtcoding/metamall/core/annotation/RoleCk.java new file mode 100644 index 0000000..1de584b --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/core/annotation/RoleCk.java @@ -0,0 +1,21 @@ +package shop.mtcoding.metamall.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +public class RoleCk { + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface User{} + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Seller{} + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Admin{} +} diff --git a/src/main/java/shop/mtcoding/metamall/core/filter/JwtVerifyFilter.java b/src/main/java/shop/mtcoding/metamall/core/filter/JwtVerifyFilter.java index 870bf93..bcf1bc6 100644 --- a/src/main/java/shop/mtcoding/metamall/core/filter/JwtVerifyFilter.java +++ b/src/main/java/shop/mtcoding/metamall/core/filter/JwtVerifyFilter.java @@ -10,6 +10,7 @@ import shop.mtcoding.metamall.core.jwt.JwtProvider; import shop.mtcoding.metamall.core.session.LoginUser; import shop.mtcoding.metamall.dto.ResponseDto; +import shop.mtcoding.metamall.model.user.User; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; @@ -31,12 +32,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String jwt = prefixJwt.replace(JwtProvider.TOKEN_PREFIX, ""); try { DecodedJWT decodedJWT = JwtProvider.verify(jwt); - int id = decodedJWT.getClaim("id").asInt(); - String role = decodedJWT.getClaim("role").asString(); + Long id = decodedJWT.getClaim("id").asLong(); + int role = decodedJWT.getClaim("role").asInt(); // 세션을 사용하는 이유는 권한처리를 하기 위해서이다. HttpSession session = req.getSession(); - LoginUser loginUser = LoginUser.builder().id(id).role(role).build(); + LoginUser loginUser = LoginUser.builder().id(id).role(User.Role.values()[role]).build(); session.setAttribute("loginUser", loginUser); chain.doFilter(req, resp); }catch (SignatureVerificationException sve){ diff --git a/src/main/java/shop/mtcoding/metamall/core/jwt/JwtProvider.java b/src/main/java/shop/mtcoding/metamall/core/jwt/JwtProvider.java index 93a4bae..e40d4ca 100644 --- a/src/main/java/shop/mtcoding/metamall/core/jwt/JwtProvider.java +++ b/src/main/java/shop/mtcoding/metamall/core/jwt/JwtProvider.java @@ -24,7 +24,7 @@ public static String create(User user) { .withSubject(SUBJECT) .withExpiresAt(new Date(System.currentTimeMillis() + EXP)) .withClaim("id", user.getId()) - .withClaim("role", user.getRole()) + .withClaim("role", user.getRole().ordinal()) .sign(Algorithm.HMAC512(SECRET)); System.out.println("디버그 : 토큰 생성됨"); return TOKEN_PREFIX + jwt; diff --git a/src/main/java/shop/mtcoding/metamall/core/session/LoginUser.java b/src/main/java/shop/mtcoding/metamall/core/session/LoginUser.java index 59f402c..ffe18ff 100644 --- a/src/main/java/shop/mtcoding/metamall/core/session/LoginUser.java +++ b/src/main/java/shop/mtcoding/metamall/core/session/LoginUser.java @@ -2,14 +2,15 @@ import lombok.Builder; import lombok.Getter; +import shop.mtcoding.metamall.model.user.User; @Getter public class LoginUser { - private Integer id; - private String role; + private Long id; + private User.Role role; @Builder - public LoginUser(Integer id, String role) { + public LoginUser(Long id, User.Role role) { this.id = id; this.role = role; } diff --git a/src/main/java/shop/mtcoding/metamall/dto/ResponseDto.java b/src/main/java/shop/mtcoding/metamall/dto/ResponseDto.java index 7f190c6..7a661aa 100644 --- a/src/main/java/shop/mtcoding/metamall/dto/ResponseDto.java +++ b/src/main/java/shop/mtcoding/metamall/dto/ResponseDto.java @@ -1,6 +1,7 @@ package shop.mtcoding.metamall.dto; import lombok.Getter; +import lombok.NoArgsConstructor; import org.springframework.http.HttpStatus; @Getter diff --git a/src/main/java/shop/mtcoding/metamall/dto/order/OrderRequest.java b/src/main/java/shop/mtcoding/metamall/dto/order/OrderRequest.java new file mode 100644 index 0000000..4e31551 --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/dto/order/OrderRequest.java @@ -0,0 +1,32 @@ +package shop.mtcoding.metamall.dto.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class OrderRequest { + @Getter + @NoArgsConstructor + public static class OrderDto{ + private List products; + + public OrderDto(List products) { + this.products = products; + } + } + @Getter + public static class OrderProductDto{ + @JsonProperty("product_id") + private Long productId; + private int count; + + @Builder + public OrderProductDto(Long productId, int count) { + this.productId = productId; + this.count = count; + } + } +} diff --git a/src/main/java/shop/mtcoding/metamall/dto/product/ProductRequest.java b/src/main/java/shop/mtcoding/metamall/dto/product/ProductRequest.java new file mode 100644 index 0000000..a80e8ec --- /dev/null +++ b/src/main/java/shop/mtcoding/metamall/dto/product/ProductRequest.java @@ -0,0 +1,25 @@ +package shop.mtcoding.metamall.dto.product; + +import lombok.Builder; +import lombok.Getter; + +import javax.validation.constraints.NotNull; + +public class ProductRequest { + @Getter + public static class ProductDto{ + @NotNull + private String name; + @NotNull + private int price; + @NotNull + private int qty; + + @Builder + public ProductDto(String name, int price, int qty) { + this.name = name; + this.price = price; + this.qty = qty; + } + } +} diff --git a/src/main/java/shop/mtcoding/metamall/dto/user/UserRequest.java b/src/main/java/shop/mtcoding/metamall/dto/user/UserRequest.java index 80947db..27413e4 100644 --- a/src/main/java/shop/mtcoding/metamall/dto/user/UserRequest.java +++ b/src/main/java/shop/mtcoding/metamall/dto/user/UserRequest.java @@ -1,12 +1,51 @@ package shop.mtcoding.metamall.dto.user; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; import lombok.Getter; -import lombok.Setter; +import shop.mtcoding.metamall.model.user.User; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; public class UserRequest { - @Getter @Setter + @Getter public static class LoginDto { - private String username; - private String password; + @NotEmpty + private final String username; + @NotEmpty + private final String password; + + @Builder + public LoginDto(String username, String password) { + this.username = username; + this.password = password; + } + } + + @Getter + public static class SignUpDto{ + @NotEmpty + private final String username; + @NotEmpty + private final String password; + @NotEmpty + @JsonProperty("password_check") + private String passwordCheck; + @NotEmpty + @Pattern(regexp = "\\w+@\\w+\\.\\w{2,}") + private final String email; + @NotNull + private final User.Role role; + + @Builder + public SignUpDto(String username, String password, String passwordCheck, String email, User.Role role) { + this.username = username; + this.password = password; + this.passwordCheck = passwordCheck; + this.email = email; + this.role = role; + } } } diff --git a/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProduct.java b/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProduct.java index 165905e..afffccd 100644 --- a/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProduct.java +++ b/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProduct.java @@ -1,32 +1,32 @@ package shop.mtcoding.metamall.model.orderproduct; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import shop.mtcoding.metamall.model.ordersheet.OrderSheet; import shop.mtcoding.metamall.model.product.Product; import javax.persistence.*; import java.time.LocalDateTime; -@NoArgsConstructor -@Setter // DTO 만들면 삭제해야됨 @Getter @Table(name = "order_product_tb") @Entity +@NoArgsConstructor public class OrderProduct { // 주문 상품 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private Product product; private Integer count; // 상품 주문 개수 private Integer orderPrice; // 상품 주문 금액 private LocalDateTime createdAt; private LocalDateTime updatedAt; - - @ManyToOne + @JsonIgnore + @ManyToOne(fetch = FetchType.LAZY,optional = false) + @JoinColumn(name="order_sheet_id",nullable = false) private OrderSheet orderSheet; @PrePersist @@ -49,4 +49,10 @@ public OrderProduct(Long id, Product product, Integer count, Integer orderPrice, this.updatedAt = updatedAt; this.orderSheet = orderSheet; } + + public void syncOrderSheet(OrderSheet orderSheet){ + if(this.orderSheet != null) this.orderSheet.getOrderProductList().remove(this); + this.orderSheet = orderSheet; + orderSheet.getOrderProductList().add(this); + } } diff --git a/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProductRepository.java b/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProductRepository.java deleted file mode 100644 index 6f1238c..0000000 --- a/src/main/java/shop/mtcoding/metamall/model/orderproduct/OrderProductRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package shop.mtcoding.metamall.model.orderproduct; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface OrderProductRepository extends JpaRepository { -} diff --git a/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheet.java b/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheet.java index 7638710..c8c9c6a 100644 --- a/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheet.java +++ b/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheet.java @@ -3,9 +3,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import shop.mtcoding.metamall.model.orderproduct.OrderProduct; -import shop.mtcoding.metamall.model.product.Product; import shop.mtcoding.metamall.model.user.User; import javax.persistence.*; @@ -13,18 +11,17 @@ import java.util.ArrayList; import java.util.List; -@NoArgsConstructor -@Setter // DTO 만들면 삭제해야됨 @Getter @Table(name = "order_sheet_tb") @Entity +@NoArgsConstructor public class OrderSheet { // 주문서 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private User user; // 주문자 - @OneToMany(mappedBy = "orderSheet") + @OneToMany(mappedBy = "orderSheet", cascade = CascadeType.ALL, orphanRemoval = true) private List orderProductList = new ArrayList<>(); // 총 주문 상품 리스트 private Integer totalPrice; // 총 주문 금액 (총 주문 상품 리스트의 orderPrice 합) private LocalDateTime createdAt; diff --git a/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepository.java b/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepository.java index 5d59249..c54bfc4 100644 --- a/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepository.java +++ b/src/main/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepository.java @@ -1,6 +1,26 @@ package shop.mtcoding.metamall.model.ordersheet; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import shop.mtcoding.metamall.model.user.User; + +import java.util.List; +import java.util.Optional; public interface OrderSheetRepository extends JpaRepository { + + @Query("select distinct os from OrderSheet os left join fetch os.user u left join fetch os.orderProductList op left join fetch op.product p left join fetch p.seller s") + List findAll(); + @Query("select distinct os from OrderSheet os left join fetch os.orderProductList op left join fetch op.product p left join fetch p.seller s where os.user=:user") + List findByUser(@Param("user") User user); + + @Query("select distinct os from OrderSheet os left join fetch os.user u left join fetch os.orderProductList op left join fetch op.product p where p.seller=:seller") + List findBySeller(@Param("seller") User seller); + + @Query("select os from OrderSheet os left join fetch os.orderProductList op left join fetch op.product p left join fetch p.seller s where os.id=:id and os.user=:user") + Optional findByIdAndUser(@Param("id") Long id, @Param("user") User user); + + @Query("select os from OrderSheet os left join fetch os.user u left join fetch os.orderProductList op left join fetch op.product p left join fetch p.seller s where os.id=:id and s=:seller") + Optional findByIdAndSeller(@Param("id") Long id, @Param("seller") User seller); } diff --git a/src/main/java/shop/mtcoding/metamall/model/product/Product.java b/src/main/java/shop/mtcoding/metamall/model/product/Product.java index bc8c618..921cf85 100644 --- a/src/main/java/shop/mtcoding/metamall/model/product/Product.java +++ b/src/main/java/shop/mtcoding/metamall/model/product/Product.java @@ -3,16 +3,17 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; +import shop.mtcoding.metamall.core.exception.Exception400; +import shop.mtcoding.metamall.dto.product.ProductRequest; +import shop.mtcoding.metamall.model.user.User; import javax.persistence.*; import java.time.LocalDateTime; -@NoArgsConstructor -@Setter // DTO 만들면 삭제해야됨 @Getter @Table(name = "product_tb") @Entity +@NoArgsConstructor public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -20,9 +21,20 @@ public class Product { private String name; // 상품 이름 private Integer price; // 상품 가격 private Integer qty; // 상품 재고 + @ManyToOne(fetch = FetchType.LAZY) + private User seller; private LocalDateTime createdAt; private LocalDateTime updatedAt; + public void order(int count){ + if(this.qty < count) throw new Exception400("재고가 없습니다"); + this.qty -= count; + } + + public void cancelOrder(int count){ + this.qty += count; + } + @PrePersist protected void onCreate() { this.createdAt = LocalDateTime.now(); @@ -34,12 +46,19 @@ protected void onUpdate() { } @Builder - public Product(Long id, String name, Integer price, Integer qty, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Product(Long id, String name, Integer price, Integer qty, User user, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.name = name; this.price = price; this.qty = qty; + this.seller = user; this.createdAt = createdAt; this.updatedAt = updatedAt; } + + public void update(ProductRequest.ProductDto dto){ + this.name = dto.getName(); + this.price = dto.getPrice(); + this.qty = dto.getQty(); + } } diff --git a/src/main/java/shop/mtcoding/metamall/model/product/ProductRepository.java b/src/main/java/shop/mtcoding/metamall/model/product/ProductRepository.java index ba5def3..df49e7d 100644 --- a/src/main/java/shop/mtcoding/metamall/model/product/ProductRepository.java +++ b/src/main/java/shop/mtcoding/metamall/model/product/ProductRepository.java @@ -1,6 +1,19 @@ package shop.mtcoding.metamall.model.product; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import shop.mtcoding.metamall.model.user.User; + +import java.util.List; +import java.util.Optional; public interface ProductRepository extends JpaRepository { + + @Query("select p from Product p join fetch p.seller s") + List findAll(); + @Query("select p from Product p join fetch p.seller s where p.id=:id") + Optional findById(@Param("id") Long id); + @Query("select p from Product p join fetch p.seller s where p.id=:id and s=:user") + Optional findByIdAndUser(@Param("id") Long id, @Param("user") User user); } diff --git a/src/main/java/shop/mtcoding/metamall/model/user/User.java b/src/main/java/shop/mtcoding/metamall/model/user/User.java index c929ce5..28cabfb 100644 --- a/src/main/java/shop/mtcoding/metamall/model/user/User.java +++ b/src/main/java/shop/mtcoding/metamall/model/user/User.java @@ -1,26 +1,32 @@ package shop.mtcoding.metamall.model.user; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import javax.persistence.*; import java.time.LocalDateTime; -@NoArgsConstructor -@Setter // DTO 만들면 삭제해야됨 @Getter @Table(name = "user_tb") @Entity +@NoArgsConstructor public class User { + public enum Role{ + USER,SELLER,ADMIN + } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(unique = true, nullable = false) private String username; + @JsonIgnore + @Column(nullable = false) private String password; private String email; - private String role; // USER(고객), SELLER(판매자), ADMIN(관리자) + @Enumerated(EnumType.ORDINAL) + private Role role; // USER(고객), SELLER(판매자), ADMIN(관리자) private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -34,8 +40,12 @@ protected void onUpdate() { this.updatedAt = LocalDateTime.now(); } + public void updateLastLoginDate(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + @Builder - public User(Long id, String username, String password, String email, String role, LocalDateTime createdAt) { + public User(Long id, String username, String password, String email, Role role, LocalDateTime createdAt) { this.id = id; this.username = username; this.password = password; @@ -43,4 +53,4 @@ public User(Long id, String username, String password, String email, String role this.role = role; this.createdAt = createdAt; } -} +} \ No newline at end of file diff --git a/src/main/java/shop/mtcoding/metamall/model/user/UserRepository.java b/src/main/java/shop/mtcoding/metamall/model/user/UserRepository.java index 293a101..9964230 100644 --- a/src/main/java/shop/mtcoding/metamall/model/user/UserRepository.java +++ b/src/main/java/shop/mtcoding/metamall/model/user/UserRepository.java @@ -8,6 +8,8 @@ public interface UserRepository extends JpaRepository { + Optional findById(Long id); + @Query("select u from User u where u.username = :username") Optional findByUsername(@Param("username") String username); } diff --git a/src/test/java/shop/mtcoding/metamall/controller/OrderControllerTest.java b/src/test/java/shop/mtcoding/metamall/controller/OrderControllerTest.java new file mode 100644 index 0000000..40fbc78 --- /dev/null +++ b/src/test/java/shop/mtcoding/metamall/controller/OrderControllerTest.java @@ -0,0 +1,185 @@ +package shop.mtcoding.metamall.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; +import org.springframework.test.annotation.DirtiesContext; +import shop.mtcoding.metamall.core.jwt.JwtProvider; +import shop.mtcoding.metamall.dto.ResponseDto; +import shop.mtcoding.metamall.dto.order.OrderRequest; +import shop.mtcoding.metamall.model.user.User; +import shop.mtcoding.metamall.model.user.UserRepository; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class OrderControllerTest { + + @Autowired + TestRestTemplate testRestTemplate; + @Autowired + private UserRepository userRepository; + + private HttpHeaders headers(User user){ + String jwt = JwtProvider.create(user); + + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.setContentType(MediaType.APPLICATION_JSON); + requestHeaders.setAccept(List.of(MediaType.APPLICATION_JSON)); + requestHeaders.add(JwtProvider.HEADER,jwt); + + return requestHeaders; + } + + @Test + @DirtiesContext + @DisplayName("주문") + void order() throws JsonProcessingException { + //given + OrderRequest.OrderProductDto product1 = OrderRequest.OrderProductDto.builder() + .productId(1L) + .count(1) + .build(); + OrderRequest.OrderProductDto product2 = OrderRequest.OrderProductDto.builder() + .productId(2L) + .count(1) + .build(); + List products = Stream.of(product1,product2).collect(Collectors.toList()); + OrderRequest.OrderDto order = new OrderRequest.OrderDto(products); + User ssar = userRepository.findByUsername("ssar").orElse(null); + HttpHeaders headers = headers(ssar); + HttpEntity requestEntity = new HttpEntity<>(order, headers); + + //when + ResponseEntity response = testRestTemplate + .postForEntity( + "/api/order", + requestEntity, + ResponseDto.class + ); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode()); + ObjectMapper om = new ObjectMapper(); + JsonNode jsonNode = om.readTree(om.writeValueAsString(response.getBody())); + JsonNode productListNode = jsonNode.get("data").get("orderProductList"); + assertEquals(2,productListNode.size()); + assertEquals(0,productListNode.get(0).get("product").get("qty").asInt()); + } + + @Nested + @DisplayName("주문조회") + class Orders{ + @Test + @DisplayName("USER") + void getOrders1() throws JsonProcessingException { + //given + User ssar = userRepository.findByUsername("ssar").orElse(null); + HttpHeaders headers = headers(ssar); + HttpEntity requestEntity = new HttpEntity<>(headers); + + //when + + ResponseEntity response = testRestTemplate + .exchange( + "/api/orders", + HttpMethod.GET, + requestEntity, + ResponseDto.class + ); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode()); + ObjectMapper om = new ObjectMapper(); + JsonNode jsonNode = om.readTree(om.writeValueAsString(response.getBody())); + JsonNode productListNode = jsonNode.get("data").get(0).get("orderProductList"); + assertEquals(2,productListNode.size()); + } + + @Test + @DisplayName("SELLER") + void getOrders2() throws JsonProcessingException { + //given + User seller = userRepository.findByUsername("seller1").orElse(null); + HttpHeaders headers = headers(seller); + HttpEntity requestEntity = new HttpEntity<>(headers); + + //when + + ResponseEntity response = testRestTemplate + .exchange( + "/api/orders", + HttpMethod.GET, + requestEntity, + ResponseDto.class + ); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode()); + ObjectMapper om = new ObjectMapper(); + JsonNode jsonNode = om.readTree(om.writeValueAsString(response.getBody())); + JsonNode productListNode = jsonNode.get("data").get(0).get("orderProductList"); + assertEquals(1,productListNode.size()); + } + } + + @Test + @DisplayName("모든 주문조회(관리자)") + void getAllOrders(){ + //given + User ssar = userRepository.findByUsername("admin").orElse(null); + HttpHeaders headers = headers(ssar); + HttpEntity requestEntity = new HttpEntity<>(headers); + + //when + + ResponseEntity response = testRestTemplate + .exchange( + "/api/admin/orders", + HttpMethod.GET, + requestEntity, + ResponseDto.class + ); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test + @DirtiesContext + @DisplayName("주문취소") + void cancelOrder() throws JsonProcessingException { + //given + long id = 1; + User ssar = userRepository.findByUsername("ssar").orElse(null); + HttpHeaders headers = headers(ssar); + HttpEntity requestEntity = new HttpEntity<>(headers); + + //when + ResponseEntity response = testRestTemplate + .exchange( + "/api/order/"+id, + HttpMethod.DELETE, + requestEntity, + ResponseDto.class + ); + + //then + assertEquals(HttpStatus.OK, response.getStatusCode()); + ObjectMapper om = new ObjectMapper(); + JsonNode jsonNode = om.readTree(om.writeValueAsString(response.getBody())); + JsonNode productListNode = jsonNode.get("data").get("orderProductList"); + assertEquals(2,productListNode.get(0).get("product").get("qty").asInt()); + } +} \ No newline at end of file diff --git a/src/test/java/shop/mtcoding/metamall/controller/ProductControllerTest.java b/src/test/java/shop/mtcoding/metamall/controller/ProductControllerTest.java new file mode 100644 index 0000000..11133e0 --- /dev/null +++ b/src/test/java/shop/mtcoding/metamall/controller/ProductControllerTest.java @@ -0,0 +1,136 @@ +package shop.mtcoding.metamall.controller; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; +import shop.mtcoding.metamall.core.jwt.JwtProvider; +import shop.mtcoding.metamall.dto.ResponseDto; +import shop.mtcoding.metamall.dto.product.ProductRequest; +import shop.mtcoding.metamall.model.user.User; +import shop.mtcoding.metamall.model.user.UserRepository; + +import java.util.List; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ProductControllerTest { + + @Autowired + TestRestTemplate testRestTemplate; + + @Autowired + private UserRepository userRepository; + + private HttpHeaders headers(User user){ + String jwt = JwtProvider.create(user); + + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.setContentType(MediaType.APPLICATION_JSON); + requestHeaders.setAccept(List.of(MediaType.APPLICATION_JSON)); + requestHeaders.add(JwtProvider.HEADER,jwt); + + return requestHeaders; + } + + @Test + @DisplayName("상품 등록") + void registerProduct(){ + //given + User seller1 = userRepository.findByUsername("seller1").orElse(null); + HttpHeaders headers = headers(seller1); + ProductRequest.ProductDto product = ProductRequest.ProductDto.builder() + .name("제품3") + .price(100000) + .qty(1) + .build(); + HttpEntity requestEntity = new HttpEntity<>(product, headers); + + //when + + ResponseEntity response = testRestTemplate + .postForEntity( + "/api/product", + requestEntity, + ResponseDto.class + ); + + //then + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test + @DisplayName("상품 조회") + void getProducts() { + //given + User ssar = userRepository.findByUsername("ssar").orElse(null); + HttpHeaders headers = headers(ssar); + HttpEntity requestEntity = new HttpEntity<>(headers); + + //when + + ResponseEntity response = testRestTemplate + .exchange( + "/api/products", + HttpMethod.GET, + requestEntity, + ResponseDto.class + ); + + //then + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test + @DisplayName("상품 상세") + void getProductDetails() { + //given + long id = 1; + User ssar = userRepository.findByUsername("ssar").orElse(null); + HttpHeaders headers = headers(ssar); + HttpEntity requestEntity = new HttpEntity<>(headers); + + //when + + ResponseEntity response = testRestTemplate + .exchange( + "/api/product/"+id, + HttpMethod.GET, + requestEntity, + ResponseDto.class + ); + + //then + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test + @DisplayName("상품 수정") + void updateProduct() { + //given + long id = 1; + User seller1 = userRepository.findByUsername("seller1").orElse(null); + HttpHeaders headers = headers(seller1); + ProductRequest.ProductDto product = ProductRequest.ProductDto.builder() + .name("제품3") + .price(10000) + .qty(1) + .build(); + HttpEntity requestEntity = new HttpEntity<>(product, headers); + + //when + + ResponseEntity response = testRestTemplate + .exchange( + "/api/product/"+id, + HttpMethod.PUT, + requestEntity, + ResponseDto.class + ); + + //then + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + } +} \ No newline at end of file diff --git a/src/test/java/shop/mtcoding/metamall/controller/UserControllerTest.java b/src/test/java/shop/mtcoding/metamall/controller/UserControllerTest.java new file mode 100644 index 0000000..04b1e63 --- /dev/null +++ b/src/test/java/shop/mtcoding/metamall/controller/UserControllerTest.java @@ -0,0 +1,77 @@ +package shop.mtcoding.metamall.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import shop.mtcoding.metamall.dto.user.UserRequest; +import shop.mtcoding.metamall.model.user.User; +import shop.mtcoding.metamall.model.user.UserRepository; + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + @DisplayName("회원가입") + void signUp() throws Exception { + //given + UserRequest.SignUpDto singup = UserRequest.SignUpDto.builder() + .username("test1") + .email("test1@email.com") + .password("1234") + .passwordCheck("1234") + .role(User.Role.USER) + .build(); + String requestBody = new ObjectMapper().writeValueAsString(singup); + + //when + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders + .post("/api/user/signup") + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON); + ResultActions actions = mockMvc.perform(builder); + + //then + ResultMatcher isOk = MockMvcResultMatchers.status().isOk(); + actions.andExpect(isOk) + .andDo(MockMvcResultHandlers.print()); + } + + @Test + @DisplayName("로그인") + void login() throws Exception { + //given + UserRequest.LoginDto login = UserRequest.LoginDto.builder() + .username("ssar") + .password("1234") + .build(); + String requestBody = new ObjectMapper().writeValueAsString(login); + + //when + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders + .post("/api/user/login") + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON); + ResultActions actions = mockMvc.perform(builder); + + //then + ResultMatcher isOk = MockMvcResultMatchers.status().isOk(); + actions.andExpect(isOk) + .andDo(MockMvcResultHandlers.print()); + } +} \ No newline at end of file diff --git a/src/test/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepositoryTest.java b/src/test/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepositoryTest.java new file mode 100644 index 0000000..c75d1b1 --- /dev/null +++ b/src/test/java/shop/mtcoding/metamall/model/ordersheet/OrderSheetRepositoryTest.java @@ -0,0 +1,100 @@ +package shop.mtcoding.metamall.model.ordersheet; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import shop.mtcoding.metamall.model.user.User; +import shop.mtcoding.metamall.model.user.UserRepository; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DataJpaTest +class OrderSheetRepositoryTest { + + @Autowired + private UserRepository userRepository; + @Autowired + private OrderSheetRepository orderSheetRepository; + + @Test + void findAll(){ + //given + + //when + List orderSheets = orderSheetRepository.findAll(); + + //then + assertEquals(2,orderSheets.size()); + assertEquals("ssar",orderSheets.get(0).getUser().getUsername()); + assertEquals("tester",orderSheets.get(1).getUser().getUsername()); + } + + @Test + void findByUser(){ + //given + User user = userRepository.findByUsername("ssar").orElse(null); + + //when + List orderSheet = orderSheetRepository.findByUser(user); + + //then + assertEquals(1,orderSheet.size()); + assertEquals(2,orderSheet.get(0).getOrderProductList().size()); + assertEquals(user,orderSheet.get(0).getUser()); + } + + @Test + void findBySeller() throws JsonProcessingException { + //given + User seller = userRepository.findByUsername("seller1").orElse(null); + + //when + List orderSheet = orderSheetRepository.findBySeller(seller); + ObjectMapper om = new ObjectMapper(); + om.registerModule(new JavaTimeModule()); + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + for(OrderSheet sheet : orderSheet){ + System.out.println(om.writeValueAsString(sheet)); + } + + //then + assertEquals(2,orderSheet.size()); + assertEquals(1,orderSheet.get(0).getOrderProductList().size()); + assertEquals(seller,orderSheet.get(0).getOrderProductList().get(0).getProduct().getSeller()); + assertEquals("ssar",orderSheet.get(0).getUser().getUsername()); + assertEquals("tester",orderSheet.get(1).getUser().getUsername()); + } + + @Test + void findByIdAndUser() { + //given + long id = 1; + User user = userRepository.findByUsername("ssar").orElse(null); + + //when + OrderSheet orderSheet = orderSheetRepository.findByIdAndUser(id,user).orElse(null); + + //then + assertEquals(1,orderSheet.getId()); + assertEquals(user,orderSheet.getUser()); + } + + @Test + void findByIdAndSeller(){ + //given + long id = 1; + User seller = userRepository.findByUsername("seller1").orElse(null); + + //when + OrderSheet orderSheet = orderSheetRepository.findByIdAndSeller(id,seller).orElse(null); + + //then + assertEquals(id,orderSheet.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/shop/mtcoding/metamall/model/product/ProductRepositoryTest.java b/src/test/java/shop/mtcoding/metamall/model/product/ProductRepositoryTest.java new file mode 100644 index 0000000..7785652 --- /dev/null +++ b/src/test/java/shop/mtcoding/metamall/model/product/ProductRepositoryTest.java @@ -0,0 +1,58 @@ +package shop.mtcoding.metamall.model.product; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import shop.mtcoding.metamall.model.user.User; +import shop.mtcoding.metamall.model.user.UserRepository; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DataJpaTest +class ProductRepositoryTest { + + @Autowired + private ProductRepository productRepository; + + @Autowired + private UserRepository userRepository; + + @Test + void findAll() { + //given + + //when + List products = productRepository.findAll(); + + //then + assertEquals(2,products.size()); + } + + @Test + void findById() { + //given + long id = 1; + + //when + Product product = productRepository.findById(id).orElse(null); + + //then + assertEquals(id,product.getId()); + } + + @Test + void findByIdAndUser() { + //given + long id = 1; + User seller = userRepository.findByUsername("seller1").orElse(null); + + //when + Product product = productRepository.findByIdAndUser(id,seller).orElse(null); + + //then + assertEquals(id,product.getId()); + assertEquals(seller,product.getSeller()); + } +} \ No newline at end of file diff --git a/src/test/java/shop/mtcoding/metamall/model/user/UserRepositoryTest.java b/src/test/java/shop/mtcoding/metamall/model/user/UserRepositoryTest.java new file mode 100644 index 0000000..f6c408f --- /dev/null +++ b/src/test/java/shop/mtcoding/metamall/model/user/UserRepositoryTest.java @@ -0,0 +1,26 @@ +package shop.mtcoding.metamall.model.user; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @Test + void findByUsername() { + //given + String username = "ssar"; + + //when + User user = userRepository.findByUsername(username).orElse(null); + + //then + assertEquals(username,user.getUsername()); + } +} \ No newline at end of file