스프링 쇼핑몰 프로젝트 1주차

윤장원·2023년 5월 24일
0

쇼핑몰프로젝트

목록 보기
1/5

스프링 프로젝트로 다양한 상품을 구매/판매하는 커머스 서비스를 개발하고자 한다. 구현할 기능들은 다음과 같다.

기능 설명

  1. 회원가입
  • 구매자/판매자 나누어서 회원가입
  1. 로그인/로그아웃
  • JWT 토큰으로 인증
  • 구매자는 구매자 권한을, 판매자는 판매자 권한을 받는다.
  1. 상품 등록하기
  • 상품 이름, 가격, 설명, 상품 수량
  1. 상품 조회하기
  • 최신순, 인기순(판매순?, 조회순?), 가격순으로 조회하기
  • 페이징 처리
  • 캐싱 처리
  1. 상품 검색하기
  • 상품 키워드로 검색하면 관련 상품들 불러오기
  1. 상품 상세 정보 불러오기
  2. 장바구니 담기/수정/삭제
  • 장바구니에 상품 정보와 수량 담기
  • 상품의 수량 변경 및 상품 삭제
  1. 주문하기
  • 장바구니 내용을 가져와서 구매자의 배송지 정보, 상품 정보 저장
  • 장바구니 초기화
  1. 주문 현황 보기
  2. 주문 취소하기
  3. 리뷰쓰기
  • 주문한 구매자에 한해서 상품 리뷰 작성 가능
  • 평점, 리뷰 내용 등록
  1. 리뷰 최신순 조회하기
  2. 판매자가 리뷰에 댓글 달기
  3. 적립금 시스템
  • 구매시 사용 가능
  • 상품 구매시 구매 금액의 1% 적립
  1. 최근 본 상품과 관련 인기 상품 불러오기
  2. 상품 문의
  • 구매자가 상품에 대한 문의 추가
  • 판매자가 문의에 대한 답변
  1. 상품 이미지 등록 및 조회

ERD는 다음과 같다.

회원가입

CustomerController

@PostMapping("/signUp")
  public ResponseEntity<String> signUp(@RequestBody CustomerSignUpForm customerSignUpForm) {
    customerService.signUp(customerSignUpForm.toServiceForm());
    return ResponseEntity.ok(SIGNUP_SUCCESS);
  }

CustomerService

@Override
  public void signUp(CustomerSignUpServiceForm customerSignUpServiceForm) {
    Optional<Customer> optionalCustomer =
        customerRepository.findByEmail(customerSignUpServiceForm.getEmail());

    optionalCustomer.ifPresent(it -> {
      throw new CustomException(ErrorCode.ALREADY_SIGNUP_EMAIL);
    });

    if (!isValidPassword(customerSignUpServiceForm.getPassword())) {
      throw new CustomException(ErrorCode.INVALID_PASSWORD);
    }

    if (!isValidPhone(customerSignUpServiceForm.getPhone())) {
      throw new CustomException(ErrorCode.INVALID_PHONE);
    }

    String encPassword = BCrypt.hashpw(customerSignUpServiceForm.getPassword(), BCrypt.gensalt());

    Customer customer = Customer.builder()
        .email(customerSignUpServiceForm.getEmail())
        .password(encPassword)
        .phone(customerSignUpServiceForm.getPhone())
        .address(customerSignUpServiceForm.getAddress())
        .point(0)
        .deletedYn(false)
        .build();

    customerRepository.save(customer);
  }

Controller에서 받는 dto와 Service에서 사용하는 dto를 분리시켰다. 회원가입시 이미 등록된 이메일이 있는지 확인한다. 비밀번호 유효성 검사, 연락처 형식 유효성 검사를 하고, 비밀번호는 암호화해서 데이터베이스에 저장한다. 기본 적립금은 0원으로 저장된다.

로그인

CustomerController

@PostMapping("/signIn")
  public ResponseEntity<String> signIn(@RequestBody CustomerSignInForm customerSignInForm) {
    return ResponseEntity.ok(customerService.signIn(customerSignInForm.toServiceForm()));
  }

CustomerService

@Override
  public String signIn(CustomerSignInServiceForm form) {
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    Optional<Customer> optionalCustomer =
        customerRepository.findByEmail(form.getEmail());

    if (optionalCustomer.isEmpty() || !encoder.matches(form.getPassword(),
        optionalCustomer.get().getPassword())) {
      throw new CustomException(ErrorCode.LOGIN_CHECK_FAIL);
    }

    Customer customer = optionalCustomer.get();

    return provider.createToken(customer.getEmail(), customer.getId(), UserType.CUSTOMER);
  }

JwtTokenProvider

public String createToken(String email, Long id, UserType userType) {
    Claims claims = Jwts.claims().setSubject(email).setId(id.toString());
    claims.put(USERTYPE, userType.toString());
    Date now = new Date();
    return Jwts.builder()
        .setClaims(claims)
        .setIssuedAt(now)
        .setExpiration(new Date(now.getTime() + TOKEN_EXPIRE))
        .signWith(SignatureAlgorithm.HS256, secretKey)
        .compact();
  }

  public Authentication getAuthentication(String token) {
    Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();

    String username = claims.getSubject();
    String userType = claims.get("userType", String.class);
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority("ROLE_" + userType));

    UserDetails userDetails = new User(username, "", authorities);

    return new UsernamePasswordAuthenticationToken(userDetails, "",
        userDetails.getAuthorities());
  }

  public boolean validateToken(String token) {
    try {
      Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
      return !claimsJws.getBody().getExpiration().before(new Date());
    } catch (Exception e) {
      return false;
    }
  }

  public UserVo getUserVo(String token) {
    Claims claims = Jwts.parser().setSigningKey(secretKey)
        .parseClaimsJws(token.substring(TOKEN_PREFIX.length())).getBody();
    return new UserVo(Long.parseLong(Objects.requireNonNull(claims.getId())),
        claims.getSubject());
  }

해당 이메일을 가진 Customer 데이터베이스를 조회하고, 암호화된 비밀번호를 비교한다. 해당 이메일을 가진 Customer가 존재하지 않거나 비밀번호가 일치하지 않을 경우, LOGIN_CHECK_FAIL 에러가 난다. 로그인에 성공하면 Customer id, email, Customer Role 정보를 가진 JWT 토큰이 발급된다. Seller로 로그인한 경우에는 JWT 토큰에 Seller Role 정보가 담긴다.

상품 등록

ProductController

@PostMapping
  @PreAuthorize("hasRole('SELLER')")
  public ResponseEntity<String> registerProduct(
      @RequestHeader(name = TOKEN_HEADER) String token,
      @RequestBody ProductRegisterForm productRegisterForm) {
    productService.register(token, productRegisterForm.toServiceForm());
    return ResponseEntity.ok(REGISTER_PRODUCT_SUCCESS);
  }

ProductService

@Override
  public void register(String token, ProductRegisterServiceForm form) {
    if (isStringEmpty(form.getName()) || form.getPrice() <= 0 || isStringEmpty(
        form.getDescription()) || form.getQuantity() <= 0) {
      throw new CustomException(ErrorCode.INVALID_PRODUCT_REGISTER);
    }

    UserVo vo = provider.getUserVo(token);

    Seller seller = sellerRepository.findByEmail(vo.getEmail())
        .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

    Category category = categoryRepository.findById(form.getCategoryId())
        .orElseThrow(() -> new CustomException(ErrorCode.CATEGORY_NOT_FOUND));

    Product product = Product.builder()
        .category(category)
        .seller(seller)
        .name(form.getName())
        .price(form.getPrice())
        .description(form.getDescription())
        .quantity(form.getQuantity())
        .image(form.getImage())
        .orderedCnt(0)
        .deletedYn(false)
        .build();

    productRepository.save(product);
  }

Seller인 사용자가 상품 등록한다. Customer로 로그인해서 발급 받은 JWT 토큰을 가지고 상품 등록을 하면 403 응답이 발생한다. 상품 등록시 상품 이름이 비어있거나, 상품 가격이 0 이하이거나, 상품 설명이 비어있거나, 상품 수량이 0 이하이거나, 카테고리가 존재하지 않으면 Custom Error가 발생한다. 기본 주문 횟수는 0으로 저장된다.

0개의 댓글