이번 시간에는 위시리스트, 찜하기 기능을 추가하였다.
계속 기능을 추가하다보니 이제 챗지피티없이도 기능을 추가할 정도로 익숙해졌다.
그러면 코드를 살펴보자
먼저, 당연히 데이터베이스 테이블에 위시리스트를 저장할 테이블을 생성한다.
create table Wishlist (
id int not null auto_increment,
user_id varchar(50) not null,
product_id int not null,
number int not null default 0
primary key(id),
foreign key(user_id) references users(id)
on delete restrict
on update restrict,
foreign key(product_id) references products(id)
on delete restrict
on update restrict
)
다음으로 늘 하듯이 엔티티,리포지토리,서비스,컨트롤러,프론트코드 순으로 만들면 된다.
import jakarta.persistence.*;
@Entity
public class Wishlist {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String user_id;
@ManyToOne
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private Users user;
private int product_id;
@ManyToOne
@JoinColumn(name = "product_id", insertable = false, updatable = false)
private Products product;
@Column(name = "count")
private int count;
//getter and setter
위시리스트의 엔티티이다. 늘 하듯이 열을 매핑시켜주면 되겠다.
package com.shoppingmall.repository;
import com.shoppingmall.domain.Products;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import java.util.List;
public class MemoryProductRepository implements ProductRepository{
@PersistenceContext
public EntityManager em;
public MemoryProductRepository(EntityManager em) {
this.em = em;
}
@Override
public Products save(Products products) {
em.persist(products);
return products;
}
public Products findById(int id){
return em.find(Products.class, id);
}
@Override
public List<Products> findByName(String name){
TypedQuery<Products> query = em.createQuery("SELECT u FROM Products u WHERE u.product_name LIKE :name", Products.class);
query.setParameter("name", "%" + name + "%");
return query.getResultList();
}
@Override
public List<Products> findByCategory(int category) {
TypedQuery<Products> query = em.createQuery("SELECT u FROM Products u WHERE u.category = :category", Products.class);
query.setParameter("category", category);
return query.getResultList();
}
@Override
public List<Products> findBySellerId(String seller_id) {
TypedQuery<Products> query = em.createQuery("SELECT u FROM Products u WHERE u.seller_id like :seller_id", Products.class);
query.setParameter("seller_id", "%" + seller_id + "%");
return query.getResultList();
}
@Override
public boolean updatePrice(int id, int price) {
Products product = em.find(Products.class, id);
if (product != null) {
product.setPrice(price);
// `merge` 메서드를 사용하여 업데이트 수행
em.merge(product);
return true;
}
return false;
}
@Override
public boolean discount(int id, int discount) {
Products product = em.find(Products.class, id);
if (product != null) {
product.setDiscount(discount);
// `merge` 메서드를 사용하여 업데이트 수행
em.merge(product);
return true;
}
return false;
}
@Override
public boolean deleteProduct(int id) {
Products product = em.find(Products.class, id);
if (product != null) {
em.remove(product); // 엔티티 삭제
return true;
}
return false;
}
@Override
public List<Products> getAllProduct() {
String query = "SELECT c FROM Products c";
return em.createQuery(query, Products.class).getResultList();
}
}
위시리스트 리포지토리이다. 일단은 오늘 구현할 아주 기본적인 위시리스트 기능을 위한 함수들을 주로 추가해두었다.
package com.shoppingmall.service;
import com.shoppingmall.domain.Products;
import com.shoppingmall.domain.Users;
import com.shoppingmall.domain.Wishlist;
import com.shoppingmall.repository.WishlistRepository;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
public class WishlistService {
private final WishlistRepository wishlistRepository;
public WishlistService(WishlistRepository wishlistRepository) {
this.wishlistRepository = wishlistRepository;
}
public Wishlist save(Users user, Products products){
return wishlistRepository.save(user, products);
}
public List<Wishlist> getAllWishlistByUserId(String id){
return wishlistRepository.getWishlistByUserId(id);
}
public List<Wishlist> getAllWishlistByProductId(int id){
return wishlistRepository.getWishlistByProductId(id);
}
public Wishlist findByTwoId(String userId, int productId){
List<Wishlist> wishlists = wishlistRepository.getWishlistByUserId(userId);
for(Wishlist w : wishlists){
Products p = w.getProduct();
if(p.getId() == productId){
return w;
}
}
return null;
}
public boolean deleteWishlist(int id) { return wishlistRepository.deleteWishlist(id); }
}
위시리스트의 서비스 객체이다. 리포지토리로 만들어둔 함수를 여기서 제공해준다.
package com.shoppingmall.controller;
import com.shoppingmall.domain.Products;
import com.shoppingmall.domain.Users;
import com.shoppingmall.domain.Wishlist;
import com.shoppingmall.service.ProductService;
import com.shoppingmall.service.UserService;
import com.shoppingmall.service.WishlistService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class WishlistController {
private final WishlistService wishlistService;
private final UserService userService;
private final ProductService productService;
public WishlistController(WishlistService wishlistService, UserService userService, ProductService productService) {
this.wishlistService = wishlistService;
this.userService = userService;
this.productService = productService;
}
@PostMapping("/addWishlist")
public ResponseEntity<String> addToWishlist(@RequestParam("productId") int productId,
@AuthenticationPrincipal UserDetails userDetails) {
ResponseEntity response;
try {
String userId = userDetails.getUsername();
Wishlist wishlist = wishlistService.findByTwoId(userId,productId);
if(wishlist == null){
Users user = userService.findById(userId);
Products products = productService.findById(productId);
wishlistService.save(user,products);
response = ResponseEntity
.status(HttpStatus.CREATED)
.body("상품이 위시리스트에 추가되었습니다.");
}
else{
response = ResponseEntity
.status(HttpStatus.CREATED)
.body("이미 찜한 상품");
}
} catch (Exception ex) {
response = ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An exception occured due to " + ex.getMessage());
}
return response;
}
@PostMapping("/delWishlist")
public ResponseEntity<String> delToWishlist(@RequestParam("productId") int productId,
@AuthenticationPrincipal UserDetails userDetails) {
ResponseEntity response;
try {
String userId = userDetails.getUsername();
Wishlist wishlist = wishlistService.findByTwoId(userId,productId);
if(wishlist != null){
wishlistService.deleteWishlist(wishlist.getId());
response = ResponseEntity
.status(HttpStatus.CREATED)
.body("상품이 삭제되었습니다.");
}
else{
response = ResponseEntity
.status(HttpStatus.CREATED)
.body("제거실패");
}
} catch (Exception ex) {
response = ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An exception occured due to " + ex.getMessage());
}
return response;
}
}
위시리스트 컨트롤러이다 post명령을 받을 수 있도록 하여 위시리스트 추가,제거를 가능하도록 하였다
@GetMapping("/products/{id}")
public String getProductDetail(@PathVariable("id") int id,
@RequestParam(value = "query", required = false) String keyword,
@RequestParam(value = "category", required = false) String category,
@RequestParam(value = "sellerId", required = false) String sellerId,
@AuthenticationPrincipal UserDetails userDetails,
Model model) {
Products products = productService.findById(id);
int price = products.getPrice();
int discountPrice = (int) (price * (1-products.getDiscount()/100));
boolean isWishlist = false;
if(userDetails !=null) {
Wishlist wishlist = wishlistService.findByTwoId(userDetails.getUsername(), id);
if (wishlist != null) isWishlist = true;
}
ProductDto productDtos = new ProductDto(products,discountPrice);
model.addAttribute("productDto", productDtos);
model.addAttribute("query", keyword); // 검색 쿼리를 모델에 추가
model.addAttribute("category", category); // 검색 쿼리를 모델에 추가
model.addAttribute("sellerId", sellerId); // 검색 쿼리를 모델에 추가
model.addAttribute("isWishlist", isWishlist);
return "product/productDetail"; // 상세 페이지의 템플릿 이름
}
상품 상세페이지에서 위시리스트 추가가 가능하며, 이미 추가되어있다면 추가되어있음을 표시하도록 만들기 위해서 여기에서 위시리스트 또한 모델로 추가하여 사용할 수 있도록 했다.
document.addEventListener("DOMContentLoaded", function () {
var isWishlist = [[${isWishlist}]];
let product_id = [[${productDto.products.id}]];
console.log(product_id);
const wish = document.getElementById("wish_image");
if (isWishlist) {
wish.src = '/images/icons/wishlist-wish.png';
}
document.getElementById("wish").addEventListener("click", function () {
if(wish.src.includes("wishlist-nowish")){
const url = new URL('http://localhost:8080/addWishlist');
url.searchParams.append('productId', product_id);
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (response.status === 401) {
// 401 상태 코드인 경우 로그인 창으로 이동할지 물어보는 prompt 표시
const shouldRedirect = confirm('로그인이 필요합니다. 로그인 페이지로 이동하시겠습니까?');
if (shouldRedirect) {
window.location.href = '/login'; // 로그인 페이지로 이동
}
throw new Error('Unauthorized'); // 에러를 throw하여 나머지 then 블록이 실행되지 않도록 함
} else
return response.text();
})
.then(data => {
document.getElementById("wish_image").src = '/images/icons/wishlist-wish.png';
})
.catch(error => {
console.error('Error:', error);
if(error === 'Network response was not ok.')
alert('네트워크 오류.');
});
} else {
const url = new URL('http://localhost:8080/delWishlist');
url.searchParams.append('productId', product_id);
fetch(url, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (response.status === 401) {
// 401 상태 코드인 경우 로그인 창으로 이동할지 물어보는 prompt 표시
const shouldRedirect = confirm('로그인이 필요합니다. 로그인 페이지로 이동하시겠습니까?');
if (shouldRedirect) {
window.location.href = '/login'; // 로그인 페이지로 이동
}
throw new Error('Unauthorized'); // 에러를 throw하여 나머지 then 블록이 실행되지 않도록 함
} else
return response.text();
})
.then(data => {
document.getElementById("wish_image").src = '/images/icons/wishlist-nowish.png';
})
.catch(error => {
console.error('Error:', error);
if (error === 'Network response was not ok.')
alert('네트워크 오류.');
});
}
});
})
위시리스트가 추가되어있었다면, isWishlist가 true일 것이므로 이미지를 변경하여 추가되어있음을 표시하도록 했다.
그리고, 이 이미지 이름을 통하여 추가,제거를 프론트에서 판단하고 각각을 post명령으로 보낼 수 있도록 하였다.
초기 상태는 위 그림과 같이 색이 채워져있지않은 하트로 표시된다.
로그인을 하지않았을 때도 마찬가지로 표시된다.
로그인을 한 상태로 하트를 누르게되면
위 그림과 같이 색이 채워지며 위시리스트에 추가가 된다.
데이터베이스에도 해당 위시리스트와 관련된 정보들이 추가됨을 확인했다.
다음 시간에는 회원 등급 기능을 추가해보도록하자.
(위시리스트를 모아둔 페이지는 생각나는 기능들을 모두 추가해둔 후에 추가해두자.)