Ordered, Transaction 엔티티를 생성했다. 주문하기 요청을 받으면 Customer 장바구니 목록을 가져와서 ordered, transaction 테이블에 주문 정보를 저장한다. 그 후 Customer 장바구니 목록 전체를 삭제한다.
Ordered
@EqualsAndHashCode(callSuper = true) @AllArgsConstructor @NoArgsConstructor @Builder @Data @Entity public class Ordered extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "customer_id")
@ToString.Exclude
private Customer customer;
@ManyToOne
@JoinColumn(name = "product_id")
@ToString.Exclude
private Product product;
@ManyToOne
@JoinColumn(name = "seller_id")
@ToString.Exclude
private Seller seller;
@ManyToOne
@JoinColumn(name = "transaction_id")
@ToString.Exclude
private Transaction transaction;
private Integer price;
private Integer quantity;
private Integer total;
}
> Transaction
```java
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Transaction extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "customer_id")
@ToString.Exclude
private Customer customer;
@OneToMany(mappedBy = "transaction", fetch = FetchType.LAZY)
@ToString.Exclude
private List<Ordered> orderedListList = new ArrayList<>();
private LocalDateTime transactionDate;
private Integer transactionPrice;
private Integer pointUse;
private boolean deletedYn;
}
OrderedController
@PostMapping
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<String> orderItems(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
@RequestParam("point") Integer point
) {
orderService.orderItems(token, point);
return ResponseEntity.ok(ORDER_ITEMS_SUCCESS);
}
OrderedService
@Override
@Transactional
@CacheEvict(value = "productInfo", allEntries = true)
public void orderItems(String token, Integer point) {
Customer customer = getCustomerFromToken(token);
if (customer.getPoint() < point) {
throw new CustomException(ErrorCode.NOT_ENOUGH_POINT);
}
List<Cart> cartList = cartRepository.findByCustomerId(customer.getId());
if (cartList.size() == 0) {
throw new CustomException(ErrorCode.CART_NOT_FOUND);
}
List<Ordered> orderedList = new ArrayList<>();
List<Product> productList = new ArrayList<>();
int total = 0;
for (Cart cart : cartList) {
Product product = cart.getProduct();
product.setOrderedCnt(product.getOrderedCnt() + 1);
product.setQuantity(product.getQuantity() - cart.getQuantity());
Seller seller = cart.getProduct().getSeller();
int totalPrice = product.getPrice() * cart.getQuantity();
Ordered ordered = Ordered.builder()
.customer(customer)
.product(product)
.seller(seller)
.price(product.getPrice())
.quantity(cart.getQuantity())
.total(totalPrice)
.build();
orderedList.add(ordered);
productList.add(product);
total += totalPrice;
}
Transaction transaction = Transaction.builder()
.customer(customer)
.orderedListList(orderedList)
.transactionDate(LocalDateTime.now())
.transactionPrice(total - point)
.pointUse(point)
.build();
for (Ordered ordered : orderedList) {
ordered.setTransaction(transaction);
}
customer.setPoint(customer.getPoint() - point + total / 100);
productRepository.saveAll(productList);
customerRepository.save(customer);
transactionRepository.save(transaction);
orderedRepository.saveAll(orderedList);
cartRepository.deleteByCustomerId(customer.getId());
}
상품 주문이 완료되면 각 상품의 주문 횟수는 1씩 증가하게 되고, 남은 수량은 주문한 만큼 줄어들게 된다.
주문했던 상품들 전체 조회, 전체 주문 정보 조회, 한 주문의 상품들 조회 기능을 구현했다. 상품이나 주문 정보를 조회할 때 존재하지 않으면 Custom Error가 발생한다.
OrderedController
@ApiOperation(value = "전체 주문 정보 가져오기")
@GetMapping
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<List<OrderedItemsForm>> getTotalOrderedItems(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token
) {
return ResponseEntity.ok(orderService.getTotalOrderedItems(token));
}
@ApiOperation(value = "한 주문 정보에 대한 모든 ordered 조회하기")
@GetMapping("/transaction/{id}")
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<List<OrderedItemsForm>> getOrderedItemsByTransaction(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
@Parameter(name = "id", description = "주문 정보 아이디")
@PathVariable("id") long id
) {
return ResponseEntity.ok(orderService.getOrderedItemsByTransaction(token, id));
}
TransactionController
@ApiOperation(value = "Customer 주문 정보 조회")
@GetMapping
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<List<TransactionsForm>> getTotalTransactions(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token
) {
return ResponseEntity.ok(orderService.getTotalTransactions(token));
}
OrderedService
@Override
public List<OrderedItemsForm> getTotalOrderedItems(String token) {
Customer customer = getCustomerFromToken(token);
List<Ordered> orderedList = orderedRepository.findByCustomerId(customer.getId());
if (orderedList.size() == 0) {
throw new CustomException(ErrorCode.ORDERED_NOT_FOUND);
}
return OrderedItemsForm.of(orderedList);
}
@Override
public List<TransactionsForm> getTotalTransactions(String token) {
Customer customer = getCustomerFromToken(token);
List<Transaction> transactionList = transactionRepository.findByCustomerId(customer.getId());
if (transactionList.size() == 0) {
throw new CustomException(ErrorCode.TRANSACTION_NOT_FOUND);
}
return TransactionsForm.of(transactionList);
}
@Override
public List<OrderedItemsForm> getOrderedItemsByTransaction(String token, Long transactionId) {
Customer customer = getCustomerFromToken(token);
List<Ordered> orderedList = orderedRepository.findByCustomerIdAndTransactionId(customer.getId(),
transactionId);
if (orderedList.size() == 0) {
throw new CustomException(ErrorCode.ORDERED_NOT_FOUND);
}
return OrderedItemsForm.of(orderedList);
}
OrderedItemsForm
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderedItemsForm {
private String name;
private Integer price;
private Integer quantity;
private String image;
private Long transactionId;
private LocalDateTime transactionDate;
private boolean transactionCancelled;
public static List<OrderedItemsForm> of(List<Ordered> orderedList) {
return orderedList.stream()
.map(ordered -> OrderedItemsForm.builder()
.name(ordered.getProduct().getName())
.price(ordered.getPrice())
.quantity(ordered.getQuantity())
.image(ordered.getProduct().getImage())
.transactionId(ordered.getTransaction().getId())
.transactionDate(ordered.getTransaction().getTransactionDate())
.transactionCancelled(ordered.getTransaction().isDeletedYn())
.build())
.collect(Collectors.toList());
}
}
TransactionsForm
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransactionsForm {
private Long id;
private Integer price;
private Integer pointUse;
private LocalDateTime transactionDate;
private boolean transactionCancelled;
public static List<TransactionsForm> of(List<Transaction> transactionList) {
return transactionList.stream()
.map(transaction -> TransactionsForm.builder()
.id(transaction.getId())
.price(transaction.getTransactionPrice())
.pointUse(transaction.getPointUse())
.transactionDate(transaction.getTransactionDate())
.transactionCancelled(transaction.isDeletedYn())
.build())
.collect(Collectors.toList());
}
}
Customer가 주문한 상품에 대해 리뷰 작성하는 기능, 판매자가 판매하는 상품에 리뷰가 생기면 댓글을 작성하는 기능을 구현했다. 특정 상품에 대한 리뷰들 조회하기 기능 구현해서 리뷰에 달린 판매자의 댓글들도 조회하도록 했다.
Review
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Review extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "product_id")
@ToString.Exclude
private Product product;
@ManyToOne
@JoinColumn(name = "customer_id")
@ToString.Exclude
private Customer customer;
@ManyToOne
@JoinColumn(name = "seller_id")
@ToString.Exclude
private Seller seller;
@OneToOne
@JoinColumn(name = "ordered_id")
@ToString.Exclude
private Ordered ordered;
@OneToMany(mappedBy = "review", fetch = FetchType.LAZY)
@ToString.Exclude
private List<ReviewComment> reviewCommentList = new ArrayList<>();
private Integer rating;
private String contents;
private boolean deletedYn;
}
ReviewController
@RestController
@RequiredArgsConstructor
@RequestMapping("/review")
public class ReviewController {
private final ReviewService reviewService;
private static final String REGISTER_REVIEW_SUCCESS = "리뷰 등록 완료";
private static final String REGISTER_REVIEW_COMMENT_SUCCESS = "리뷰 댓글 등록 완료";
@ApiOperation(value = "Customer 리뷰 등록")
@PostMapping
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<String> registerReview(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
@RequestBody ReviewRegisterForm reviewRegisterForm) {
reviewService.register(token, reviewRegisterForm.toServiceForm());
return ResponseEntity.ok(REGISTER_REVIEW_SUCCESS);
}
@ApiOperation(value = "리뷰 페이지 가져오기")
@GetMapping
public ResponseEntity<List<ReviewDto>> getReviews(
@Parameter(name = "productId", description = "상품 아이디")
@RequestParam("productId") Long productId,
@Parameter(name = "page", description = "페이지")
@RequestParam("page") Integer page,
@Parameter(name = "size", description = "페이지 크기")
@RequestParam("size") Integer size
) {
return ResponseEntity.ok(reviewService.getReviews(productId, page, size));
}
@ApiOperation(value = "리뷰 답변 등록")
@PostMapping("/comment")
@PreAuthorize("hasRole('SELLER')")
public ResponseEntity<String> registerReviewComment(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
@RequestBody ReviewCommentRegisterForm reviewCommentRegisterForm) {
reviewService.registerReviewComment(token, reviewCommentRegisterForm.toServiceForm());
return ResponseEntity.ok(REGISTER_REVIEW_COMMENT_SUCCESS);
}
}
ReviewService
@Override
public void register(String token, ReviewRegisterServiceForm form) {
Customer customer = getCustomerFromToken(token);
if (!checkReviewRegisterForm(form)) {
throw new CustomException(ErrorCode.INVALID_REVIEW_FORM);
}
Ordered ordered = orderedRepository.findById(form.getOrderId())
.orElseThrow(() -> new CustomException(ErrorCode.ORDERED_NOT_FOUND));
Optional<Review> optionalReview = reviewRepository.findByOrderedId(ordered.getId());
if (optionalReview.isPresent()) {
throw new CustomException(ErrorCode.REVIEW_ALREADY_EXIST);
}
if (!Objects.equals(ordered.getCustomer().getId(), customer.getId())) {
throw new CustomException(ErrorCode.CUSTOMER_NOT_MATCH);
}
Product product = ordered.getProduct();
Seller seller = product.getSeller();
Review review = Review.builder()
.product(product)
.customer(customer)
.seller(seller)
.ordered(ordered)
.rating(form.getRating())
.contents(form.getContents())
.deletedYn(false)
.build();
reviewRepository.save(review);
}
@Override
public List<ReviewDto> getReviews(Long productId, Integer page, Integer size) {
Pageable pageable = PageRequest.of(page - 1, size);
List<Review> reviewList = reviewRepository.findByProductId(productId, pageable);
if (reviewList.size() == 0) {
throw new CustomException(ErrorCode.REVIEW_NOT_FOUND);
}
return ReviewDto.toDtoList(reviewList);
}
@Override
public void registerReviewComment(String token, ReviewCommentRegisterServiceForm form) {
if (isStringEmpty(form.getContents())) {
throw new CustomException(ErrorCode.INVALID_REVIEW_FORM);
}
Seller seller = getSellerFromToken(token);
Review review = reviewRepository.findById(form.getReviewId())
.orElseThrow(() -> new CustomException(ErrorCode.REVIEW_NOT_FOUND));
if (!Objects.equals(seller.getId(), review.getSeller().getId())) {
throw new CustomException(ErrorCode.SELLER_NOT_MATCH);
}
ReviewComment reviewComment = ReviewComment.builder()
.review(review)
.contents(form.getContents())
.deletedYn(false)
.build();
reviewCommentRepository.save(reviewComment);
}
상품 상세 정보를 조회하면 해당 IP가 조회한 상품을 데이터베이스에 저장한다. 최근 본 상품과 관련 인기 상품 불러오기를 요청하면 데이터베이스에서 상품을 조회하고 해당 카테고리에서 상품 주문 횟수가 많은 순으로 4개를 더 가져온다.
먼저 요청을 받으면 IP주소를 얻어오는 클래스를 구현했다.
ClientUtils
public class ClientUtils {
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null) {
ip = request.getRemoteAddr();
}
return ip;
}
}
그 후 상품 상세 정보 불러오기 기능에 IP를 받으면 조회한 상품 정보를 저장하는 기능을 추가했다.
ProductViewHistory
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class ProductViewHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "product_id")
@ToString.Exclude
private Product product;
private String userIp;
}
ProductController
@ApiOperation(value = "한 상품의 상세정보 조회")
@GetMapping("/info/{id}")
public ResponseEntity<ProductInfo> getProductInfo(
@PathVariable long id,
HttpServletRequest request
) {
String userIp = ClientUtils.getIp(request);
return ResponseEntity.ok(productService.getProductInfo(id, userIp));
}
@ApiOperation(value = "접속한 Ip에서 조회한 상품과 관련된 인기 상품 조회")
@GetMapping("/recent")
public ResponseEntity<List<ProductInfo>> getRecentViewedProducts(
HttpServletRequest request,
@Parameter(name = "page", description = "페이지")
@RequestParam("page") Integer page,
@Parameter(name = "size", description = "페이지 크기")
@RequestParam("size") Integer size
) {
String userIp = ClientUtils.getIp(request);
return ResponseEntity.ok(productService.getRecentViewedProducts(userIp, page, size));
}
ProductService
@Override
@Cacheable(key = "#id", value = "productInfo")
public ProductInfo getProductInfo(Long id, String userIp) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));
Optional<ProductViewHistory> optionalProductViewHistory = productViewHistoryRepository.findByUserIp(
userIp);
ProductViewHistory productViewHistory;
if (optionalProductViewHistory.isPresent()) {
productViewHistory = optionalProductViewHistory.get();
productViewHistory.setProduct(product);
} else {
productViewHistory = ProductViewHistory.builder()
.product(product)
.userIp(userIp)
.build();
}
productViewHistoryRepository.save(productViewHistory);
return ProductInfo.from(product);
}
@Override
public List<ProductInfo> getRecentViewedProducts(String userIp, Integer page, Integer size) {
Optional<ProductViewHistory> optionalProductViewHistory = productViewHistoryRepository.findByUserIp(
userIp);
if (optionalProductViewHistory.isEmpty()) {
throw new CustomException(ErrorCode.PRODUCT_NOT_FOUND);
}
Product product = optionalProductViewHistory.get().getProduct();
Long categoryId = product.getCategory().getId();
Pageable pageable = PageRequest.of(page - 1, size, Sort.by("orderedCnt").descending());
Page<Product> productList = productRepository.findAllByCategoryId(categoryId, pageable);
List<ProductInfo> productInfoList = ProductInfo.toList(productList);
productInfoList.add(0, ProductInfo.from(product));
return productInfoList;
}
Customer는 상품에 대한 문의를 등록, Seller는 자신이 판매하는 상품에 문의가 달리면 답변을 할 수 있는 기능을 구현했다. 상품에 대한 문의를 조회하면 문의와 문의에 대한 답변 목록들이 조회된다.
Question
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Question extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "product_id")
@ToString.Exclude
private Product product;
@ManyToOne
@JoinColumn(name = "customer_id")
@ToString.Exclude
private Customer customer;
@ManyToOne
@JoinColumn(name = "seller_id")
@ToString.Exclude
private Seller seller;
@OneToMany(mappedBy = "question", fetch = FetchType.LAZY)
@ToString.Exclude
private List<Answer> answerList = new ArrayList<>();
private String contents;
private boolean deletedYn;
}
Answer
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Entity
public class Answer extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "question_id")
@ToString.Exclude
private Question question;
@ManyToOne
@JoinColumn(name = "seller_id")
@ToString.Exclude
private Seller seller;
private String contents;
private boolean deletedYn;
}
QuestionController
@RestController
@RequiredArgsConstructor
@RequestMapping("/question")
public class QuestionController {
private final QuestionService questionService;
private static final String REGISTER_QUESTION_SUCCESS = "문의 등록 완료";
private static final String REGISTER_ANSWER_SUCCESS = "답변 등록 완료";
@ApiOperation(value = "Customer 상품 문의 등록")
@PostMapping
@PreAuthorize("hasRole('CUSTOMER')")
public ResponseEntity<String> registerQuestion(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
@RequestBody QuestionRegisterForm form) {
questionService.registerQuestion(token, form.toServiceForm());
return ResponseEntity.ok(REGISTER_QUESTION_SUCCESS);
}
@ApiOperation(value = "Seller 문의 답변 등록")
@PostMapping("/answer")
@PreAuthorize("hasRole('SELLER')")
public ResponseEntity<String> registerAnswer(
@RequestHeader(name = HttpHeaders.AUTHORIZATION) String token,
@RequestBody AnswerRegisterForm form) {
questionService.registerAnswer(token, form.toServiceForm());
return ResponseEntity.ok(REGISTER_ANSWER_SUCCESS);
}
@ApiOperation(value = "상품에 대한 문의 조회")
@GetMapping
public ResponseEntity<List<QuestionDto>> getQuestions(
@Parameter(name = "productId", description = "상품 아이디")
@RequestParam("productId") Long productId,
@Parameter(name = "page", description = "페이지")
@RequestParam("page") Integer page,
@Parameter(name = "size", description = "페이지 크기")
@RequestParam("size") Integer size
) {
return ResponseEntity.ok(questionService.getQuestions(productId, page, size));
}
}
QuestionService
@Override
public void registerQuestion(String token, QuestionRegisterServiceForm form) {
Customer customer = getCustomerFromToken(token);
Product product = productRepository.findById(form.getProductId())
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));
Seller seller = product.getSeller();
Question question = Question.builder()
.product(product)
.customer(customer)
.seller(seller)
.contents(form.getContents())
.deletedYn(false)
.build();
questionRepository.save(question);
}
@Override
public void registerAnswer(String token, AnswerRegisterServiceForm form) {
Seller seller = getSellerFromToken(token);
Question question = questionRepository.findById(form.getQuestionId())
.orElseThrow(() -> new CustomException(ErrorCode.QUESTION_NOT_FOUND));
Answer answer = Answer.builder()
.question(question)
.seller(seller)
.contents(form.getContents())
.deletedYn(false)
.build();
answerRepository.save(answer);
}
@Override
public List<QuestionDto> getQuestions(Long productId, Integer page, Integer size) {
Pageable pageable = PageRequest.of(page - 1, size);
List<Question> questionList = questionRepository.findByProductId(productId, pageable);
if (questionList.size() == 0) {
throw new CustomException(ErrorCode.QUESTION_NOT_FOUND);
}
return QuestionDto.toDtoList(questionList);
}