Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cb5a50a
FEAT: add entity architecture
JasonSong97 Apr 12, 2023
e7df22b
FEAT: fix entity architecture
JasonSong97 Apr 13, 2023
3a96014
FEAT: add repository
JasonSong97 Apr 13, 2023
368b859
FEAT: add dummy data
JasonSong97 Apr 13, 2023
ba11639
FEAT: add exception
JasonSong97 Apr 13, 2023
0cba54b
FEAT: add log feature
JasonSong97 Apr 13, 2023
80cb323
FEAT: add advice, resolver
JasonSong97 Apr 17, 2023
8040569
FEAT: add admin, seller interceptor
JasonSong97 Apr 17, 2023
da7f380
FEAT: add interceptor config
JasonSong97 Apr 17, 2023
8a34fea
FEAT: finish core setting
JasonSong97 Apr 17, 2023
9082995
FEAT: add regex
JasonSong97 Apr 17, 2023
576ef00
FEAT: add UserRequest JoinDto with regexp
JasonSong97 Apr 17, 2023
a6730cc
Create README.md
JasonSong97 Apr 17, 2023
87ed70f
FEAT add UserRequest toEntity method
JasonSong97 Apr 17, 2023
e98698c
FEAT: add product controller save method, add product SaveDto of prod…
JasonSong97 Apr 17, 2023
08d9cff
FEAT: add product controller 상ífindAll method
JasonSong97 Apr 17, 2023
aee2ac4
FEAT: add product controller ìfindById method
JasonSong97 Apr 17, 2023
ff63fde
FEAT: add product controller update method
JasonSong97 Apr 17, 2023
2b081b5
FEAT: add product controller deleteById method
JasonSong97 Apr 17, 2023
5fd62e7
FEAT: add product controller complete
JasonSong97 Apr 17, 2023
5edf078
FEAT: add SaveDTO of OrderRequest
JasonSong97 Apr 17, 2023
f1ab32c
FEAT: add JsonIgnoreProperties annotation
JasonSong97 Apr 17, 2023
6fd5b5c
FEAT: add admin controller
JasonSong97 Apr 17, 2023
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Springboot-MetaMall-Project
---
## 1. 사용 기술
- Filter
- Jwt
- Aop
- RoleInterceptor(권한 체크 ADMIN 여부)
- SessionArgumentResolver(세션 주입하기)
- ExceptionAdvice(예외핸들러)
- ValidAdvice(유효성검사)
- ErrorLogAdvice(에러로그 데이터베이스 기록)
- SameUserIdAdvice(자원 소유자 확인)
- Exception400, Exception401, Exception403, Exception, 404처리
- MyDateUtil(시간 포멧해서 응답)
- MyFilterResponseUtil(필터는 예외핸들러를 사용못하기 때문에 재사용 메서드 생성)
- test/RegexTest(정규표현식 테스트) https://github.com/codingspecialist/junit-bank-class/tree/main/class-note/regex
스프링부트 새로운 설정설정 (log4j, DB 파라메터 trace, 404처리)
- Hibernate + JPA
- 양방향 매핑
- Lazy Loading
- join fetch
- default_batch_fetch_size: 100 (인쿼리)
- 영속, 비영속, 준영속
- 더티체킹
- Repository Test
- Controller Test

## 2. hibernateLazyInitializer 해결법
---
원인은 MessageConverter가 비어있는 객체를 Lazy Loading할 때 발생한다. Jackson 라이브러리는 객체를 직렬화할 때 프록시 객체를 처리할 수 없기 때문이다.
- 직접 Lazy Loading 하기 (강력 추천 - 서비스 레이어에서 DTO 만들 때 사용하면 됨)
- join fetch 하기 (강력 추천)
- Eager 전략으로 변경하기 (현재 추천 - 서비스가 없으니까!!)
- fail-on-empty-beans: false (비 추천)
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-validation'
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'
Expand Down
15 changes: 9 additions & 6 deletions src/main/java/shop/mtcoding/metamall/MetamallApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import org.springframework.boot.SpringApplication;
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.order.product.OrderProductRepository;
import shop.mtcoding.metamall.model.order.sheet.OrderSheetRepository;
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;

@SpringBootApplication
public class MetamallApplication {

Expand All @@ -20,8 +20,11 @@ CommandLineRunner initData(UserRepository userRepository, ProductRepository prod
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").status(true).build();
User seller = User.builder().username("seller").password("1234").email("seller@nate.com").role("SELLER").status(true).build();
User admin = User.builder().username("admin").password("1234").email("admin@nate.com").role("ADMIN").status(true).build();
userRepository.saveAll(Arrays.asList(ssar, seller, admin));
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import shop.mtcoding.metamall.core.filter.JwtVerifyFilter;
import shop.mtcoding.metamall.core.filter.MyJwtVerifyFilter;


@Configuration
public class FilterRegisterConfig {
public class FilterRegisterConfig { // 인증 체크
@Bean
public FilterRegistrationBean<?> jwtVerifyFilterAdd() {
FilterRegistrationBean<JwtVerifyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new JwtVerifyFilter());
registration.addUrlPatterns("/user/*");
FilterRegistrationBean<MyJwtVerifyFilter> registration = new
FilterRegistrationBean<>();
registration.setFilter(new MyJwtVerifyFilter());

registration.addUrlPatterns("/users/*"); // 토큰
registration.addUrlPatterns("/products/*"); // 토큰
registration.addUrlPatterns("/orders/*");// 토큰

// 인터셉터 권한체크
registration.addUrlPatterns("/admin/*"); // ADMIN 권한
registration.addUrlPatterns("/seller/*"); // ADMIN, SELLER

registration.setOrder(1);
return registration;
}
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/shop/mtcoding/metamall/config/MyWebMvcConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package shop.mtcoding.metamall.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
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.core.interceptor.MyAdminInterceptor;
import shop.mtcoding.metamall.core.interceptor.MySellerInterceptor;
import shop.mtcoding.metamall.core.resolver.MySessionArgumentResolver;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

private final MyAdminInterceptor adminInterceptor;
private final MySellerInterceptor sellerInterceptor;
private final MySessionArgumentResolver mySessionArgumentResolver;

@Override
public void addCorsMappings(CorsRegistry registry) { // CORS 설정
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*") // GET, POST, PUT, DELETE (Javascript 요청 허용)
.allowedOriginPatterns("*") // 모든 IP 주소 허용 (프론트 앤드 IP만 허용하게 변경해야함. * 안됨)
.allowCredentials(true)
.exposedHeaders("Authorization"); // 옛날에는 디폴트로 브라우저에 노출되어 있었는데 지금은 아님
}

// AOP는 매개변수 값 확인해서 권한 비교해야할 떄 사용
// Interceptor는 세션 권한으로 체크할 떄 사용
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor)
.addPathPatterns("/admin/**");
registry.addInterceptor(sellerInterceptor)
.addPathPatterns("/seller/**");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(mySessionArgumentResolver);
}
}
18 changes: 0 additions & 18 deletions src/main/java/shop/mtcoding/metamall/config/WebMvcConfig.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package shop.mtcoding.metamall.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.metamall.core.exception.Exception400;
import shop.mtcoding.metamall.dto.ResponseDTO;
import shop.mtcoding.metamall.dto.user.UserRequest;
import shop.mtcoding.metamall.model.user.User;
import shop.mtcoding.metamall.model.user.UserRepository;
import javax.validation.Valid;
/**
* 권한 변경
*/
@RequiredArgsConstructor
@RestController
public class AdminController {

private final UserRepository userRepository;

@Transactional // 트랜잭션이 시작되지 않으면 강제로 em.flush() 를 할 수 없고, 더티체킹도 할 수 없다. (원래는 서비스에서)
@PutMapping("/admin/user/{id}/role")
public ResponseEntity<?> updateRole(@PathVariable Long id, @RequestBody @Valid UserRequest.RoleUpdateDTO roleUpdateDTO, Errors errors) {
User userPS = userRepository.findById(id).orElseThrow(() ->
new Exception400("id", "해당 유저를 찾을 수 없습니다."));
userPS.updateRole(roleUpdateDTO.getRole());

ResponseDTO<?> responseDto = new ResponseDTO<>();
return ResponseEntity.ok().body(responseDto);
}
}
147 changes: 147 additions & 0 deletions src/main/java/shop/mtcoding/metamall/controller/OrderController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package shop.mtcoding.metamall.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.metamall.core.annotation.MySessionStore;
import shop.mtcoding.metamall.core.exception.Exception400;
import shop.mtcoding.metamall.core.exception.Exception403;
import shop.mtcoding.metamall.core.session.SessionUser;
import shop.mtcoding.metamall.dto.ResponseDTO;
import shop.mtcoding.metamall.dto.order.OrderRequest;
import shop.mtcoding.metamall.model.order.product.OrderProduct;
import shop.mtcoding.metamall.model.order.product.OrderProductRepository;
import shop.mtcoding.metamall.model.order.sheet.OrderSheet;
import shop.mtcoding.metamall.model.order.sheet.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 javax.validation.Valid;
import java.util.List;

/**
* 주문하기(고객), 주문목록보기(고객), 주문목록보기(판매자), 주문취소하기(고객), 주문취소하기
* (판매자)
*/
@RequiredArgsConstructor
@RestController
public class OrderController {
private final OrderProductRepository orderProductRepository;
private final OrderSheetRepository orderSheetRepository;
private final ProductRepository productRepository;
private final UserRepository userRepository;

@Transactional
@PostMapping("/orders")
public ResponseEntity<?> save(@RequestBody @Valid OrderRequest.SaveDTO saveDTO,
Errors errors, @MySessionStore SessionUser sessionUser) {
// 1. 세션값으로 유저 찾기
User userPS = userRepository.findById(sessionUser.getId())
.orElseThrow(
() -> new Exception400("id", "해당 유저를 찾을 수 없습니다")
);

// 2. 상품 찾기
List<Product> productListPS =
productRepository.findAllById(saveDTO.getIds());

// 3. 주문 상품
List<OrderProduct> orderProductListPS = saveDTO.toEntity(productListPS);

// 4. 주문서 만들기
Integer totalPrice = orderProductListPS.stream().mapToInt((orderProduct) ->
orderProduct.getOrderPrice()).sum();
OrderSheet orderSheet = OrderSheet.builder()
.user(userPS)
.totalPrice(totalPrice)
.build();
OrderSheet orderSheetPS = orderSheetRepository.save(orderSheet); // 영속화

// 5. 주문서에 상품추가하고 재고감소하기
orderProductListPS.stream().forEach(
(orderProductPS -> {
orderSheetPS.addOrderProduct(orderProductPS);
orderProductPS.getProduct().updateQty(orderProductPS.getCount());
})
);

// 6. 응답하기
ResponseDTO<?> responseDto = new ResponseDTO<>().data(orderSheetPS);
return ResponseEntity.ok().body(responseDto);
}

// 유저 주문서 조회
@GetMapping("/orders")
public ResponseEntity<?> findByUserId(@MySessionStore SessionUser sessionUser) {
List<OrderSheet> orderSheetListPS =
orderSheetRepository.findByUserId(sessionUser.getId());
ResponseDTO<?> responseDto = new ResponseDTO<>().data(orderSheetListPS);
return ResponseEntity.ok().body(responseDto);
}

// 그림 설명 필요!!
// 배달의 민족은 하나의 판매자에게서만 주문을 할 수 있다. (다른 판매자의 상품이 담기면, 하나만 담을수 있게로직이 변한다)
// 쇼핑몰은 여러 판매자에게서 주문을 할 수 있다.
// 판매자 주문서 조회
@GetMapping("/seller/orders")
public ResponseEntity<?> findBySellerId() {

// 판매자는 한명이기 때문에 orderProductRepository.findAll() 해도 된다.
List<OrderSheet> orderSheetListPS = orderSheetRepository.findAll();
ResponseDTO<?> responseDto = new ResponseDTO<>().data(orderSheetListPS);
return ResponseEntity.ok().body(responseDto);
}

// 유저 주문 취소
@DeleteMapping("/orders/{id}")
public ResponseEntity<?> delete(@PathVariable Long id, @MySessionStore
SessionUser sessionUser) {
// 1. 주문서 찾기
OrderSheet orderSheetPS = orderSheetRepository.findById(id).orElseThrow(
() -> new Exception400("id", "해당 주문을 찾을 수 없습니다")
);

// 2. 해당 주문서의 주인 여부 확인
if (!orderSheetPS.getUser().getId().equals(sessionUser.getId())) {
throw new Exception403("권한이 없습니다");
}

// 3. 재고 변경하기
orderSheetPS.getOrderProductList().stream().forEach(orderProduct -> {
orderProduct.getProduct().rollbackQty(orderProduct.getCount());
});

// 4. 주문서 삭제하기 (casecade 옵션)
orderSheetRepository.delete(orderSheetPS);

// 5. 응답하기
ResponseDTO<?> responseDto = new ResponseDTO<>();
return ResponseEntity.ok().body(responseDto);
}

// 판매자 주문 취소
@DeleteMapping("/seller/orders/{id}")
public ResponseEntity<?> deleteSeller(@PathVariable Long id) {
// 1. 주문서 찾기
OrderSheet orderSheetPS = orderSheetRepository.findById(id).orElseThrow(
() -> new Exception400("id", "해당 주문을 찾을 수 없습니다")
);

// 2. 재고 변경하기
orderSheetPS.getOrderProductList().stream().forEach(orderProduct -> {
orderProduct.getProduct().rollbackQty(orderProduct.getCount());
});

// 3. 주문서 삭제하기 (casecade 옵션)
orderSheetRepository.delete(orderSheetPS);

// 4. 응답하기
ResponseDTO<?> responseDto = new ResponseDTO<>();
return ResponseEntity.ok().body(responseDto);
}
}

Loading