Spring Projcect/[갠플] Online-mall

[6일차] 관리자 로그인, 상품 등록

계란💕 2022. 9. 28. 01:29

  오류) 순환 참조 오류 & admin.main.do을 입력해도 자동으로 로그인 페이지(member.login)로 이동한다.

<hide/>

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  securityConfiguration defined in file [C:\Users\Ran\Desktop\R\zerobase\remote_mall\build\classes\java\main\com\example\mall\configuration\SecurityConfiguration.class]
↑     ↓
|  memberServiceImpl defined in file [C:\Users\Ran\Desktop\R\zerobase\remote_mall\build\classes\java\main\com\example\mall\service\MemberServiceImpl.class]
└─────┘
  • 오류: 임플 클래스와 시큐리티 컨피스 클래스 간의 순환 참조 문제
  • 원인: impl 클래스가 security 클래스의 bean을 주입 받고 security클래스가  impl 클래스의 bean을 주입 받는 상황?
  •  해결
    •  AppConfig 클래스에 @bean을 하나 더 만든다.  getBCrypt() 
      • bean이 겹치지 않도록 한다. => 클래스 삭제
    • Impl
      • 멤버 변수 passdwordEncoder 주석 처리하고 로그인 구현 부분을 모두 지운다. (SecurityConfig가 설정하도록 한다.)
      • loadByUsername()를 오버라이드한다.  loadByUsername는 SecurityConfig가 사용자를 인증하는 부분이다.
      • loadByUsername()는 MemberService가 구현하는UserDetailsService에 선언된 메서드이다.
        • 그런데 이 메서드의 매개변수 String name이 null() 이라는 오류가 계속 발생했다.
        • 그래서 매개변수 이름을 username 으로 바꿨다.
        • (여기서의 username은 Member의 userName과는 상관없다.)
    • member/login.html 파일의 변수 username, password는 로그인 post 했을 때 컨트롤러에서 넘어오는 데이터이다.따라서,  변수명을 "userId"가 아닌 "username"으로 변경한다.
      • 멤버 컨트롤러의 loginSubmit() 매개변수와는 관련 없이 동작한다.

 

 

<순환 참조의 원인이 되는 코드>

  • SecurityConfig  => MemberService(Impl) 참조
    • MemberService(Impl)를 참고하고 있다.
<hide/>
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(memberService)
            .passwordEncoder(getPasswordEncoder());
    super.configure(auth);
}

 

  • ServiceImpl => SecurityConfig 참조  
    • 아래와 같이 "passwordEncoder"를 사용해서 순환 오류가 발생했다.
    • 따라서, passwordEncoder.encode()를 지우고  대신 BCrypt.hashpw()를 이용한다.
//  private final PasswordEncoder passwordEncoder;
String encPassword = BCrypt.hashpw(parameter.getPassword(), BCrypt.gensalt());
    //passwordEncoder.encode(parameter.getPassword());

 

 

 

  참고)  yml - 이것만 바꾼다고 해서 순환 참조가 없어지지는 않는다.  순환 참조의 원인이 되는 근본적인 고리를 끊어내야한다.

spring:
	main:
    allow-circular-references: true

 

 

 

1. admin login

  • 관리자 기능
    • 관리자 로그인
    • 가입한 계정에 대해 데이터베이스에서 관리자로 설정을 바꿔준다.
    • 그 다음에 그 계정으로 로그인하면 관리자 페이지에 접속이 가능하다.
    • 권한이 없을 경우(404 에러)에는 denied되도록 구현할 예정이다.

 

  • 회원: 상세 정보, 이용 중 / 정지 / 탈퇴 
  • 상품: 등록 / 수정 / 삭제 

 

  • 컨트롤러
<hide/>
@Controller
public class AdminMainController extends  BaseController {

    @GetMapping("/admin/main.do")
    public String main() {      // 관리자가 아닌 경우의 처리는?
        return "admin/main";
    }
}

 

  • 관리자 메인 페이지
    • 미리 등록해둔 layout을 이용한다.
<hide/>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>관리자 메인 페이지</title>
</head>
<body>
<h1>Online mall - admin</h1>
<div th:replace="/fragments/layout.html :: fragment-admin-body-menu" ></div>
</body>
</html>

 

   Note) 실행 결과

  • admin 페이지를 들어가려면 멤버 로그인 창이 가장 먼저 나온다.
  • adminYn = true인 계정으로 로그인하고 나면 아래와 같은 메인 화면이 나온다.
  • admin아 아닌 경우에 404 에러가 나는데 이 부분을 깔끔하게 처리하는 부분이 필요한다.

 

 

 

 

2. product

  • product - cart 연관 관계: 다대일
    • 카트 하나는 멤버 한 명 - 일대일
    • 장바구니 하나에 여러 가지 상품이 들어갈 수 있다.

 

 

  Ex) 테스트 하기 위해 Impl 클래스에 상품 추가 코드를 넣는다.

  • Impl - 이메일 인증 코드의 아래에 추가해서 테스트
    • 회원 가입한 회원이 이메일 인증을 하면 자동으로 쿠폰, 장바구니,  상품이 생성된다.
    • 이 때, 임의로 만든 상품 a를 넣는다.
    • member만 save() 했는데 카트, 장바구니, 상품까지 자동으로 save() 된다. - 영속성 전이
<hide/>
// Cart 추가
Cart cart = new Cart();

// Product 추가 테스트
List<Product> products = new ArrayList<>();
Product product = Product.builder().productName("상품A").price(5000).cart(cart).build();
products.add(product);
cart.setProductList(products);
//        cartRepository.save(cart); 필요없음
//        productRepository.save(product);

// Coupon 추가
LocalDateTime now =  LocalDateTime.now();
Coupon coupon = Coupon.builder()
    .couponType(CouponType.COUPON_C)
    .couponRegDt(now)
    .couponExpirationDt(now.plusMonths(3))
//            .member(member)  => TransientPropertyValueException 발생
    .build();

// member에만 set()하고 cart, coupon은 멤버를 따로 저장하지 않는다.
member.setCart(cart);
member.setCoupon(coupon);
member.setUserStatus(MemberCode.MEMBER_STATUS_ING);
member.setEmailAuthYn(true);
member.setEmailAuthDt(LocalDateTime.now());
//        couponRepository.save(coupon);    영속성 전이 때문에 필요없어지는 부분
//        cartRepository.save(cart);
memberRepository.save(member);  // 연관관계의 주인만 저장해도 된다. CASCADE 설정해서 cart, coupon에도 저장한다.

 

  • member
<hide/>
@OneToOne(cascade = CascadeType.ALL)    // 추가하면 member 테이블이 달라질 때, cart도 달라진다.
@JoinColumn(name = "CART_ID")   // 카트의 변수명과 달라도되나???
private Cart cart;

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "COUPON_ID")   // 카트의 변수명과 달라도되나???
private Coupon coupon;

 

  •  cart
    • 일대다 매핑
    •  cascase = CascadeType.ALL로 설정한다.
    • cart가 바뀔 때 product까지 자동으로 업데이트 된다.
<hide/>
@OneToMany(mappedBy = "cart", cascade = CascadeType.ALL)   // cascase를 넣어줘야 cart 업로드 될 때 product까지 자동으로 올라간다.
private List<Product> productList = new ArrayList<>();

 

  • product
    • 다대일 매핑
<hide/>
@ManyToOne
@JoinColumn(name = "CART_ID")
private Cart cart;

 

  Note) 실행 결과

  • cascade()
  • save없이 저절로 다 들어간다.

 

MEMBER
COUPON
CART - ID
PRODUCT

 

3. category

  • 카테고리는 쿠폰과 다르게 앤티티 아니라 enumT으로 고정한다.
@Enumerated(EnumType.STRING)
private Category category;  // 딱 한개
<hide/>
public enum Category {

    CATEGORY_OUTER,
    CATEGORY_TOP,
    CATEGORY_PANTS,
    CATEGORY_SKIRT,
    CATEGORY_SHOES,
    CATEGORY_BAG,
    CATEGORY_HEADWEAR,
    CATEGORY_UNDERWEAR,
    CATEGORY_ACCESSORY
    ;
}

 

 

 

점검 사항

  • 로그인 에러날 때, 예외 처리를 페이지로 가게끔 할 수 있나? 컨트롤러가 아니더라도? 할 수 있는지 확인한다.
  • loadByUsername() 메서드를 내가 직접 사용하는 게 아니라 시큐리티가 사용자 인증할 때 사용하는 부분이다.
  • 그러면 컨트롤러에서 호출할 일이 없는데 이를 컨트롤러에서 호출하는 로직을 구현해도 될지 모르겠다.
  • 일대일, 다대일 처럼 "ONE"으로 끝나는 매핑은  fetchType(LAZY)로 바꿔야한다.
  • 정상 실행은 됐지만 CASCADE를 @OneToMany에 붙여도 되는가?
  • 앞으로 상품 등록 / 상품 주문 / 배송 구현해야한다.