diff --git a/src/main/java/com/trade_ham/domain/auth/controller/UserController.java b/src/main/java/com/trade_ham/domain/auth/controller/UserController.java new file mode 100644 index 0000000..e337462 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/auth/controller/UserController.java @@ -0,0 +1,30 @@ +package com.trade_ham.domain.auth.controller; + +import com.trade_ham.domain.auth.dto.CustomOAuth2User; +import com.trade_ham.domain.auth.dto.UserUpdateDTO; +import com.trade_ham.domain.auth.service.UserService; +import com.trade_ham.global.common.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/api/v1") +public class UserController { + + private final UserService userService; + + @PostMapping("/user") + public ApiResponse updateUser(@RequestBody UserUpdateDTO userUpdateDTO, @AuthenticationPrincipal CustomOAuth2User oAuth2User) { + Long sellerId = oAuth2User.getId(); + // ID가 제대로 나오는지 확인 + log.info("OAuth2 User ID: {}", oAuth2User.getId()); + + UserUpdateDTO userResponse = userService.updateUser(sellerId, userUpdateDTO); + + return ApiResponse.success(userResponse); + } +} diff --git a/src/main/java/com/trade_ham/domain/auth/dto/UserUpdateDTO.java b/src/main/java/com/trade_ham/domain/auth/dto/UserUpdateDTO.java new file mode 100644 index 0000000..6e9085b --- /dev/null +++ b/src/main/java/com/trade_ham/domain/auth/dto/UserUpdateDTO.java @@ -0,0 +1,11 @@ +package com.trade_ham.domain.auth.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserUpdateDTO { + private String account; // 계좌번호 + private String realname; +} diff --git a/src/main/java/com/trade_ham/domain/auth/entity/UserEntity.java b/src/main/java/com/trade_ham/domain/auth/entity/UserEntity.java index 5b6876a..4fd5f74 100644 --- a/src/main/java/com/trade_ham/domain/auth/entity/UserEntity.java +++ b/src/main/java/com/trade_ham/domain/auth/entity/UserEntity.java @@ -17,7 +17,8 @@ public class UserEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long user_id; + @Column(name = "user_id") + private Long id; private String email; // 이메일 private String username; // OAuth2 유일 식별자 @@ -36,10 +37,11 @@ public class UserEntity { private List purchasedProductEntities = new ArrayList<>(); // 추후 따로 받는다. - private String acount; // 계좌번호 + private String account; // 계좌번호 private String realname; // 실제이름 - public void updateNickname(String nickname) { + public void updateAccountAndNickname(String account, String nickname) { + this.account = account; this.nickname = nickname; } diff --git a/src/main/java/com/trade_ham/domain/auth/service/CustomOAuth2UserService.java b/src/main/java/com/trade_ham/domain/auth/service/CustomOAuth2UserService.java index 7f78879..164ad50 100644 --- a/src/main/java/com/trade_ham/domain/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/trade_ham/domain/auth/service/CustomOAuth2UserService.java @@ -52,7 +52,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic UserDTO userDTO = new UserDTO(); - userDTO.setId(userRepository.findByProviderAndEmail(oAuth2Response.getProvider(), oAuth2Response.getEmail()).getUser_id()); + userDTO.setId(userRepository.findByProviderAndEmail(oAuth2Response.getProvider(), oAuth2Response.getEmail()).getId()); userDTO.setEmail(oAuth2Response.getEmail()); userDTO.setNickname(oAuth2Response.getNickName()); userDTO.setRole(Role.USER); @@ -62,7 +62,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } else{ // 이미 존재한다면 UserDTO userDTO = new UserDTO(); - userDTO.setId(existData.getUser_id()); + userDTO.setId(existData.getId()); userDTO.setNickname(existData.getNickname()); userDTO.setEmail(existData.getEmail()); userDTO.setRole(existData.getRole()); diff --git a/src/main/java/com/trade_ham/domain/auth/service/UserService.java b/src/main/java/com/trade_ham/domain/auth/service/UserService.java new file mode 100644 index 0000000..b8c1ca8 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/auth/service/UserService.java @@ -0,0 +1,30 @@ +package com.trade_ham.domain.auth.service; + +import com.trade_ham.domain.auth.dto.UserUpdateDTO; +import com.trade_ham.domain.auth.entity.UserEntity; +import com.trade_ham.domain.auth.repository.UserRepository; +import com.trade_ham.global.common.exception.ErrorCode; +import com.trade_ham.global.common.exception.ResourceNotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + + public UserUpdateDTO updateUser(Long sellerId, UserUpdateDTO userUpdateDTO) { + UserEntity userEntity = userRepository.findById(sellerId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND)); + + userEntity.setAccount(userUpdateDTO.getAccount()); + userEntity.setNickname(userUpdateDTO.getRealname()); + + userRepository.save(userEntity); + + return userUpdateDTO; + } +} diff --git a/src/main/java/com/trade_ham/domain/locker/controller/NotificationController.java b/src/main/java/com/trade_ham/domain/locker/controller/NotificationController.java new file mode 100644 index 0000000..402c9d8 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/locker/controller/NotificationController.java @@ -0,0 +1,29 @@ +package com.trade_ham.domain.locker.controller; + +import com.trade_ham.domain.auth.dto.CustomOAuth2User; +import com.trade_ham.domain.locker.entity.NotificationEntity; +import com.trade_ham.domain.locker.service.NotificationService; +import com.trade_ham.global.common.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/notification") +public class NotificationController { + + private final NotificationService notificationService; + + @GetMapping() + public ApiResponse> getAllNotifications(@AuthenticationPrincipal CustomOAuth2User oAuth2User) { + Long userId = oAuth2User.getId(); + + List notifications = notificationService.allNotification(userId); + return ApiResponse.success(notifications); + } +} diff --git a/src/main/java/com/trade_ham/domain/locker/dto/NotificationBuyerDTO.java b/src/main/java/com/trade_ham/domain/locker/dto/NotificationBuyerDTO.java new file mode 100644 index 0000000..8955eb0 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/locker/dto/NotificationBuyerDTO.java @@ -0,0 +1,13 @@ +package com.trade_ham.domain.locker.dto; + +import com.trade_ham.domain.auth.entity.UserEntity; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class NotificationBuyerDTO { + private String message; + private boolean isRead; + private UserEntity userId; +} diff --git a/src/main/java/com/trade_ham/domain/locker/dto/NotificationLockerDTO.java b/src/main/java/com/trade_ham/domain/locker/dto/NotificationLockerDTO.java new file mode 100644 index 0000000..dc824e8 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/locker/dto/NotificationLockerDTO.java @@ -0,0 +1,15 @@ +package com.trade_ham.domain.locker.dto; + +import com.trade_ham.domain.auth.entity.UserEntity; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class NotificationLockerDTO { + private String message; + private Long lockerId; + private String lockerPassword; + private boolean isRead; + private UserEntity userId; +} diff --git a/src/main/java/com/trade_ham/domain/locker/entity/NotificationEntity.java b/src/main/java/com/trade_ham/domain/locker/entity/NotificationEntity.java new file mode 100644 index 0000000..271c173 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/locker/entity/NotificationEntity.java @@ -0,0 +1,51 @@ +package com.trade_ham.domain.locker.entity; + +import com.trade_ham.domain.auth.entity.UserEntity; +import com.trade_ham.domain.locker.dto.NotificationBuyerDTO; +import com.trade_ham.domain.locker.dto.NotificationLockerDTO; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +public class NotificationEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "notification_id") + private Long id; + + //seller + @Column(name = "locker_id") + private Long lockerId; + + @Column(name = "locker_password") + private String lockerPassword; + + // common + private String message; + + @Setter + private boolean isRead; + + @ManyToOne + @JoinColumn(name = "user_id") + private UserEntity user; + + // 판매자 전용 알림 + public NotificationEntity(NotificationLockerDTO notificationLockerDTO) { + this.message = notificationLockerDTO.getMessage(); + this.lockerId = notificationLockerDTO.getLockerId(); + this.lockerPassword = notificationLockerDTO.getLockerPassword(); + this.isRead = notificationLockerDTO.isRead(); + this.user = notificationLockerDTO.getUserId(); + } + + // 구매자 전용 알림 + public NotificationEntity(NotificationBuyerDTO NotificationBuyerDTO) { + this.message = NotificationBuyerDTO.getMessage(); + this.isRead = NotificationBuyerDTO.isRead(); + this.user = NotificationBuyerDTO.getUserId(); + } +} diff --git a/src/main/java/com/trade_ham/domain/locker/repository/NotificationRepository.java b/src/main/java/com/trade_ham/domain/locker/repository/NotificationRepository.java new file mode 100644 index 0000000..917166b --- /dev/null +++ b/src/main/java/com/trade_ham/domain/locker/repository/NotificationRepository.java @@ -0,0 +1,12 @@ +package com.trade_ham.domain.locker.repository; + +import com.trade_ham.domain.auth.entity.UserEntity; +import com.trade_ham.domain.locker.entity.NotificationEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface NotificationRepository extends JpaRepository { + List findByUser_IdAndIsReadFalse(Long Id); +} diff --git a/src/main/java/com/trade_ham/domain/locker/service/NotificationService.java b/src/main/java/com/trade_ham/domain/locker/service/NotificationService.java new file mode 100644 index 0000000..f6f5e1f --- /dev/null +++ b/src/main/java/com/trade_ham/domain/locker/service/NotificationService.java @@ -0,0 +1,27 @@ +package com.trade_ham.domain.locker.service; + +import com.trade_ham.domain.locker.entity.NotificationEntity; +import com.trade_ham.domain.locker.repository.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class NotificationService { + + private final NotificationRepository notificationRepository; + + @Transactional + public List allNotification(Long userId) { + List notifications = notificationRepository.findByUser_IdAndIsReadFalse(userId); + + for(NotificationEntity notificationEntity : notifications) { + notificationEntity.setRead(true); + } + + return notificationRepository.saveAll(notifications); + } +} diff --git a/src/main/java/com/trade_ham/domain/product/controller/LikeController.java b/src/main/java/com/trade_ham/domain/product/controller/LikeController.java new file mode 100644 index 0000000..7d6068e --- /dev/null +++ b/src/main/java/com/trade_ham/domain/product/controller/LikeController.java @@ -0,0 +1,49 @@ +package com.trade_ham.domain.product.controller; + +import com.trade_ham.domain.auth.dto.CustomOAuth2User; +import com.trade_ham.domain.product.entity.LikeEntity; +import com.trade_ham.domain.product.service.LikeService; +import com.trade_ham.global.common.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@Controller +@RequiredArgsConstructor +@RequestMapping("/api/v1") +public class LikeController { + + private final LikeService likeService; + + // 상품 좋아요 클릭 + @PostMapping("/likes/{productId}") + public ApiResponse toggleLike(@PathVariable Long productId, @AuthenticationPrincipal CustomOAuth2User oAuth2User) { + boolean isLiked = likeService.toggleLike(productId, oAuth2User.getId()); + + if (isLiked) { + return ApiResponse.success("좋아요 추가"); + } else { + return ApiResponse.success("좋아요 취소"); + } + } + + + // 상품별 좋아요 cnt 조회 + @GetMapping("/products/{productId}/likes") + public ApiResponse getLikeCount(@PathVariable Long productId) { + Long likeCount = likeService.getLikeCount(productId); + + return ApiResponse.success(likeCount); + } + + // 특정 유저가 좋아요한 상품 조회 + @GetMapping("/products/likes/{userId}") + public ApiResponse> getLikedProductsByUser(@PathVariable Long userId) { + List likeEntities = likeService.getLikedProductsByUser(userId); + return ApiResponse.success(likeEntities); + } +} diff --git a/src/main/java/com/trade_ham/domain/product/controller/PurchaseProductController.java b/src/main/java/com/trade_ham/domain/product/controller/PurchaseProductController.java index 5fcb222..a60df86 100644 --- a/src/main/java/com/trade_ham/domain/product/controller/PurchaseProductController.java +++ b/src/main/java/com/trade_ham/domain/product/controller/PurchaseProductController.java @@ -1,12 +1,10 @@ package com.trade_ham.domain.product.controller; -import com.trade_ham.domain.product.entity.ProductEntity; -import com.trade_ham.domain.product.entity.ProductStatus; +import com.trade_ham.domain.auth.dto.CustomOAuth2User; import com.trade_ham.domain.product.service.PurchaseProductService; -import com.trade_ham.global.common.exception.AccessDeniedException; -import com.trade_ham.global.common.exception.ErrorCode; import com.trade_ham.global.common.response.ApiResponse; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -17,15 +15,10 @@ public class PurchaseProductController { private final PurchaseProductService productService; @GetMapping("/product/purchase-page/{productId}") - public ApiResponse accessPurchasePage(@PathVariable Long productId) { - ProductEntity productEntity = productService.findProductById(productId); + public ApiResponse accessPurchasePage(@PathVariable Long productId, @AuthenticationPrincipal CustomOAuth2User oAuth2User) { + Long buyerId = oAuth2User.getId(); - // 상태가 SELL이 아니라면 예외 발생 - if (!productEntity.getStatus().equals(ProductStatus.SELL)) { - throw new AccessDeniedException(ErrorCode.ACCESS_DENIED); - } - - productService.purchaseProduct(productId); + productService.purchaseProduct(productId, buyerId); // 상태가 SELL이면 구매 페이지에 접근 가능 return ApiResponse.success("구매 페이지에 접근 가능합니다."); diff --git a/src/main/java/com/trade_ham/domain/product/controller/SearchProductController.java b/src/main/java/com/trade_ham/domain/product/controller/SearchProductController.java new file mode 100644 index 0000000..7e430c4 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/product/controller/SearchProductController.java @@ -0,0 +1,34 @@ +package com.trade_ham.domain.product.controller; + +import com.trade_ham.domain.product.entity.ProductEntity; +import com.trade_ham.domain.product.service.SearchProductService; +import com.trade_ham.global.common.response.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1") +public class SearchProductController { + + private final SearchProductService searchProductService; + + @GetMapping("/search") + public ApiResponse> searchSellProduct(@RequestParam String keyword) { + List productEntities = searchProductService.searchSellProduct(keyword); + + return ApiResponse.success(productEntities); + } + + // 상품 클릭 + @GetMapping("/products/{productId}") + public ApiResponse getProductDetail(@PathVariable Long productId, HttpServletRequest request, HttpServletResponse response) { + ProductEntity productEntity = searchProductService.getProductDetail(productId, request, response); + + return ApiResponse.success(productEntity); + } +} diff --git a/src/main/java/com/trade_ham/domain/product/controller/SellProductController.java b/src/main/java/com/trade_ham/domain/product/controller/SellProductController.java index 97bcaca..8adff2f 100644 --- a/src/main/java/com/trade_ham/domain/product/controller/SellProductController.java +++ b/src/main/java/com/trade_ham/domain/product/controller/SellProductController.java @@ -3,6 +3,7 @@ import com.trade_ham.domain.auth.dto.CustomOAuth2User; import com.trade_ham.domain.product.dto.ProductDTO; import com.trade_ham.domain.product.dto.ProductResponseDTO; +import com.trade_ham.domain.product.entity.ProductEntity; import com.trade_ham.domain.product.service.SellProductService; import com.trade_ham.global.common.response.ApiResponse; import lombok.RequiredArgsConstructor; @@ -50,7 +51,6 @@ public ApiResponse> searchProducts(@RequestParam String return ApiResponse.success(products); } - // 상태가 SELL인 전체 판매 물품 최신순으로 조회 @GetMapping("/all") public ApiResponse> findAllSellProducts() { diff --git a/src/main/java/com/trade_ham/domain/product/dto/ProductResponseDTO.java b/src/main/java/com/trade_ham/domain/product/dto/ProductResponseDTO.java index 9e4f831..54afba1 100644 --- a/src/main/java/com/trade_ham/domain/product/dto/ProductResponseDTO.java +++ b/src/main/java/com/trade_ham/domain/product/dto/ProductResponseDTO.java @@ -15,7 +15,7 @@ public class ProductResponseDTO { private ProductStatus status; public ProductResponseDTO(ProductEntity productEntity) { - this.productId = productEntity.getProductId(); + this.productId = productEntity.getId(); this.name = productEntity.getName(); this.description = productEntity.getDescription(); this.price = productEntity.getPrice(); diff --git a/src/main/java/com/trade_ham/domain/product/entity/AlbumEntity.java b/src/main/java/com/trade_ham/domain/product/entity/AlbumEntity.java index 901698f..b397ce7 100644 --- a/src/main/java/com/trade_ham/domain/product/entity/AlbumEntity.java +++ b/src/main/java/com/trade_ham/domain/product/entity/AlbumEntity.java @@ -7,9 +7,11 @@ public class AlbumEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long albumId; + @ManyToOne @JoinColumn(name = "product_id") private ProductEntity productEntity; + private String imageUrl; private Double fileSize; diff --git a/src/main/java/com/trade_ham/domain/product/entity/LikeEntity.java b/src/main/java/com/trade_ham/domain/product/entity/LikeEntity.java new file mode 100644 index 0000000..8c98c06 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/product/entity/LikeEntity.java @@ -0,0 +1,32 @@ +package com.trade_ham.domain.product.entity; + +import com.trade_ham.domain.auth.entity.UserEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Table(uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "product_id"}) +}) +public class LikeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "like_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private UserEntity user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private ProductEntity product; +} diff --git a/src/main/java/com/trade_ham/domain/product/entity/ProductEntity.java b/src/main/java/com/trade_ham/domain/product/entity/ProductEntity.java index 3dbb0d5..a49ad8f 100644 --- a/src/main/java/com/trade_ham/domain/product/entity/ProductEntity.java +++ b/src/main/java/com/trade_ham/domain/product/entity/ProductEntity.java @@ -2,18 +2,22 @@ import com.trade_ham.domain.auth.entity.UserEntity; import com.trade_ham.domain.locker.entity.LockerEntity; +import com.trade_ham.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.ColumnDefault; @Entity @Getter @Builder @NoArgsConstructor @AllArgsConstructor -public class ProductEntity { +public class ProductEntity extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long productId; + @Column(name = "product_id") + private Long id; + @ManyToOne @JoinColumn(name = "seller_id") private UserEntity seller; @@ -35,6 +39,16 @@ public class ProductEntity { @Column(nullable = false) private Long price; + @ColumnDefault("0") + @Column(nullable = false) + private Integer view; + + + // Redis 좋아요 개수 + @Setter + @ColumnDefault("0") + private Long likeCount; + @Setter @OneToOne @JoinColumn(name = "locker_id") diff --git a/src/main/java/com/trade_ham/domain/product/entity/TradeEntity.java b/src/main/java/com/trade_ham/domain/product/entity/TradeEntity.java index 421ec36..50fc57e 100644 --- a/src/main/java/com/trade_ham/domain/product/entity/TradeEntity.java +++ b/src/main/java/com/trade_ham/domain/product/entity/TradeEntity.java @@ -29,6 +29,6 @@ public class TradeEntity { @ManyToOne @JoinColumn(name = "product_id") - private ProductEntity productEntity; + private ProductEntity product; } diff --git a/src/main/java/com/trade_ham/domain/product/repository/LikeRepository.java b/src/main/java/com/trade_ham/domain/product/repository/LikeRepository.java new file mode 100644 index 0000000..9d21ad1 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/product/repository/LikeRepository.java @@ -0,0 +1,35 @@ +package com.trade_ham.domain.product.repository; + +import com.trade_ham.domain.product.entity.LikeEntity; +import io.lettuce.core.dynamic.annotation.Param; +import jakarta.persistence.LockModeType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +public interface LikeRepository extends JpaRepository { + // 상품별 좋아요 개수 조회 + // 다른 사용자가 좋아요를 누를 때 값이 변경되므로 동시성 처리를 위해 락 사용 + @Query("SELECT COUNT(l) FROM LikeEntity l WHERE l.product.id = :productId") + @Lock(LockModeType.PESSIMISTIC_READ) + Long countByProductId(@Param("productId") Long productId); + + // 특정 유저의 좋아요 여부 확인 + Optional findByUserIdAndProductId(Long userId, Long productId); + + // 좋아요 개수 업데이트 + @Modifying + @Transactional + @Query("UPDATE Like l SET l.likeCount = :likeCount WHERE 1.product.id = :productId") + void updateLikeCount(@Param("productId") Long productId, @Param("likeCount") Long likeCount); + + // 특정 유저가 좋아요한 상품 목록 + // N+1 고려하여 fetch join 사용 + @Query("SELECT l FROM LikeEntity l JOIN FETCH l.user u JOIN FETCH l.product p WHERE u.id = :userId") + List findByUserId(@Param("userId") Long userId); +} diff --git a/src/main/java/com/trade_ham/domain/product/repository/ProductRepository.java b/src/main/java/com/trade_ham/domain/product/repository/ProductRepository.java index 31e11ec..14567c1 100644 --- a/src/main/java/com/trade_ham/domain/product/repository/ProductRepository.java +++ b/src/main/java/com/trade_ham/domain/product/repository/ProductRepository.java @@ -3,8 +3,9 @@ import com.trade_ham.domain.auth.entity.UserEntity; import com.trade_ham.domain.product.entity.ProductEntity; import com.trade_ham.domain.product.entity.ProductStatus; -import io.lettuce.core.dynamic.annotation.Param; import jakarta.persistence.LockModeType; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.repository.query.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; @@ -13,14 +14,22 @@ import java.util.Optional; public interface ProductRepository extends JpaRepository { - Optional findByProductId(Long productId); + Optional findById(Long productId); List findByNameContainingIgnoreCase(String name); List findBySeller(UserEntity seller); List findByBuyer(UserEntity buyer); List findByStatusOrderByCreatedAtDesc(ProductStatus status); + @Query("SELECT p FROM ProductEntity p WHERE p.status = ProductStatus.SELL AND p.name LIKE %:keyword%") + List findByKeywordContainingAndStatusIsSell(@Param("keyword") String keyword); @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT p FROM ProductEntity p WHERE p.productId = :productId") - Optional findByIdWithPessimisticLock(@Param("productId") Long productId); + @Query("SELECT p FROM ProductEntity p WHERE p.id = :id") + Optional findByIdWithPessimisticLock(@Param("id") Long id); + + + // 동시성 처리를 위해 단일 UPDATE문 사용 + @Query("UPDATE ProductEntity p SET p.view = p.view + 1 WHERE p.id = :id") + @Modifying + void increaseView(@Param("id") Long id); } diff --git a/src/main/java/com/trade_ham/domain/product/repository/TradeRepository.java b/src/main/java/com/trade_ham/domain/product/repository/TradeRepository.java index 216a13f..b5886c9 100644 --- a/src/main/java/com/trade_ham/domain/product/repository/TradeRepository.java +++ b/src/main/java/com/trade_ham/domain/product/repository/TradeRepository.java @@ -3,4 +3,8 @@ import com.trade_ham.domain.product.entity.TradeEntity; import org.springframework.data.jpa.repository.JpaRepository; -public interface TradeRepository extends JpaRepository {} +import java.util.Optional; + +public interface TradeRepository extends JpaRepository { + Optional findByProduct_Id(Long productId); +} diff --git a/src/main/java/com/trade_ham/domain/product/service/LikeService.java b/src/main/java/com/trade_ham/domain/product/service/LikeService.java new file mode 100644 index 0000000..177cc01 --- /dev/null +++ b/src/main/java/com/trade_ham/domain/product/service/LikeService.java @@ -0,0 +1,189 @@ +package com.trade_ham.domain.product.service; + +import com.trade_ham.domain.auth.repository.UserRepository; +import com.trade_ham.domain.product.entity.LikeEntity; +import com.trade_ham.domain.product.entity.ProductEntity; +import com.trade_ham.domain.product.repository.LikeRepository; +import com.trade_ham.domain.product.repository.ProductRepository; +import com.trade_ham.global.common.exception.ErrorCode; +import com.trade_ham.global.common.exception.ResourceNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class LikeService { + + private final LikeRepository likeRepository; + private final ProductRepository productRepository; + private final UserRepository userRepository; + private final RedisTemplate redisTemplate; + + private static final String LIKE_KEY_PREFIX = "like:"; + + // 상품 좋아요 토글 + @Transactional + public boolean toggleLike(Long userId, Long productId) { + validateUserAndProductExistence(userId, productId); + + // Redis에 저장된 LikeEntity 조회 + String redisKey = LIKE_KEY_PREFIX + userId + ":" + productId; + LikeEntity existingLike = (LikeEntity) redisTemplate.opsForValue().get(redisKey); + + if (existingLike != null) { + // 좋아요가 있으면 Redis에서 삭제 (취소) + redisTemplate.delete(redisKey); + + // Redis에서 좋아요 수 감소 + String likeCountKey = LIKE_KEY_PREFIX + productId; + redisTemplate.opsForValue().decrement(likeCountKey); + + return false; + } else { + // Redis에 값이 없으면 DB에서 조회 + Optional dbLike = likeRepository.findByUserIdAndProductId(userId, productId); + + if (dbLike.isEmpty()) { + // DB에도 없으면, 좋아요 추가 + LikeEntity likeEntity = LikeEntity.builder() + .user(userRepository.findById(userId).get()) + .product(productRepository.findById(productId).get()) + .build(); + + // Redis에 좋아요 상태 저장 (TTL 2시간) + redisTemplate.opsForValue().set(redisKey, likeEntity, 2, TimeUnit.HOURS); + + // Redis에서 좋아요 수 증가 + incrementLike(userId, productId); + + return true; + } else { + dbLike.ifPresent(likeRepository::delete); + decrementLike(userId, productId); + + return false; + } + } + } + + // 좋아요 증가 (DB 업데이트 하지 않음) + @Transactional + public void incrementLike(Long userId, Long productId) { + // 유저 및 상품 검증 + validateUserAndProductExistence(userId, productId); + + String redisKey = LIKE_KEY_PREFIX + productId; + + // Redis에서 좋아요 수 조회 + String likeCount = (String) redisTemplate.opsForValue().get(redisKey); + + if (likeCount != null) { + // Redis에 값이 있으면 그 값을 +1 시킴 + redisTemplate.opsForValue().increment(redisKey); + } else { + // DB에 값이 있으면 그 값을 +1 시킴 + ProductEntity product = productRepository.findById(productId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); + + Long likeCountFromDb = product.getLikeCount(); + + if (likeCountFromDb == 0) { + // Redis에 값이 없으면 초기값을 설정 + redisTemplate.opsForValue().set(redisKey, "1"); + } else { + // DB에 좋아요 수가 0이 아니면 Redis에 해당 값을 저장 + redisTemplate.opsForValue().set(redisKey, String.valueOf(likeCountFromDb)); + } + + // Redis에 좋아요 수 증가 + redisTemplate.opsForValue().increment(redisKey); + } + } + + // 좋아요 감소 (DB 업데이트 하지 않음) + @Transactional + public void decrementLike(Long userId, Long productId) { + // 유저 및 상품 검증 + validateUserAndProductExistence(userId, productId); + + String redisKey = LIKE_KEY_PREFIX + productId; + + String likeCount = (String) redisTemplate.opsForValue().get(redisKey); + + redisTemplate.opsForValue().decrement(redisKey); + } + + // 스케줄러로 Redis에 저장된 데이터를 DB에 합치고 Redis에서 삭제하는 작업 + @Scheduled(cron = "0 0 * * * *") // 10분 마다 실행 + @Transactional + public void syncLikesFromRedisToDb() { + // Redis에서 모든 like:로 시작하는 키 가져오기 + Set redisKeys = redisTemplate.keys(LIKE_KEY_PREFIX + "*"); + + if (redisKeys != null && !redisKeys.isEmpty()) { + for (String redisKey : redisKeys) { + Long productId = Long.valueOf(redisKey.replace(LIKE_KEY_PREFIX, "")); + String redisValue = (String) redisTemplate.opsForValue().get(redisKey); + + if (redisValue != null) { + Long likeCountFromRedis = Long.valueOf(redisValue); + + // DB에서 현재 좋아요 수 조회 + Long likeCountFromDb = likeRepository.countByProductId(productId); + + // DB에 Redis 값을 더하여 업데이트 + Long totalLikes = likeCountFromRedis + likeCountFromDb; + likeRepository.updateLikeCount(productId, totalLikes); + + // Redis 데이터 삭제 + redisTemplate.delete(redisKey); + } + } + } + } + + // 상품 좋아요 수 조회 + public Long getLikeCount(Long productId) { + String redisKey = LIKE_KEY_PREFIX + productId; + + // Redis에서 조회 + String likeCount = (String) redisTemplate.opsForValue().get(redisKey); + + if (likeCount != null) { + // Redis에 값이 있으면 반환 + return Long.valueOf(likeCount); + } else { + // Redis에 값이 없으면 DB에서 조회하고 Redis에 캐싱 + Long countFromDb = likeRepository.countByProductId(productId); + redisTemplate.opsForValue().set(redisKey, String.valueOf(countFromDb), 2, TimeUnit.HOURS); + return countFromDb; + } + } + + // 유저가 좋아요한 상품 목록 조회 + public List getLikedProductsByUser(Long userId) { + // 유저 존재 여부 확인 + userRepository.findById(userId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND)); + + return likeRepository.findByUserId(userId); + } + + private void validateUserAndProductExistence(Long userId, Long productId) { + // 유저 존재 여부 확인 + userRepository.findById(userId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND)); + + // 상품 존재 여부 확인 + productRepository.findById(productId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); + } +} diff --git a/src/main/java/com/trade_ham/domain/product/service/PurchaseProductService.java b/src/main/java/com/trade_ham/domain/product/service/PurchaseProductService.java index c69611e..cbc62f2 100644 --- a/src/main/java/com/trade_ham/domain/product/service/PurchaseProductService.java +++ b/src/main/java/com/trade_ham/domain/product/service/PurchaseProductService.java @@ -2,8 +2,12 @@ import com.trade_ham.domain.auth.entity.UserEntity; import com.trade_ham.domain.auth.repository.UserRepository; +import com.trade_ham.domain.locker.dto.NotificationBuyerDTO; +import com.trade_ham.domain.locker.dto.NotificationLockerDTO; import com.trade_ham.domain.locker.entity.LockerEntity; +import com.trade_ham.domain.locker.entity.NotificationEntity; import com.trade_ham.domain.locker.repository.LockerRepository; +import com.trade_ham.domain.locker.repository.NotificationRepository; import com.trade_ham.domain.product.entity.ProductEntity; import com.trade_ham.domain.product.entity.ProductStatus; import com.trade_ham.domain.product.entity.TradeEntity; @@ -17,11 +21,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Random; + @Service @RequiredArgsConstructor -public class PurchaseProductService { +public class PurchaseProductService { private final ProductRepository productRepository; private final LockerRepository lockerRepository; + private final NotificationRepository notificationRepository; private final UserRepository userRepository; private final TradeRepository tradeRepository; @@ -31,7 +38,7 @@ public class PurchaseProductService { -> 물품 상태 변경 */ @Transactional - public ProductEntity purchaseProduct(Long productId) { + public ProductEntity purchaseProduct(Long productId, Long buyerId) { // 동시성을 고려해 비관적 락을 사용 ProductEntity productEntity = productRepository.findByIdWithPessimisticLock(productId) .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); @@ -44,6 +51,15 @@ public ProductEntity purchaseProduct(Long productId) { productEntity.setStatus(ProductStatus.CHECK); productRepository.save(productEntity); + // 구매자에게 판매자 실명과 계좌번호 전송 (알림) + UserEntity seller = productEntity.getSeller(); + UserEntity buyer = userRepository.findById(buyerId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND)); + + NotificationBuyerDTO notificationBuyerDTO = new NotificationBuyerDTO("판매자의 실명 및 계좌번호", false, buyer); + NotificationEntity notificationBuyerEntity = new NotificationEntity(notificationBuyerDTO); + notificationRepository.save(notificationBuyerEntity); + return productEntity; } @@ -51,7 +67,7 @@ public ProductEntity purchaseProduct(Long productId) { 구매 완료 버튼 클릭 물품 상태 변경 물품에 사물함을 할당하고 사물함 상태 변경 - (추후 판매자에게 알림을 보내주는 서비스 구현) + 판매자에게 알림을 보내주는 서비스 구현 거래 내역 생성 */ @Transactional @@ -76,12 +92,20 @@ public TradeEntity completePurchase(Long productId, Long buyerId) { productEntity.setLockerEntity(availableLockerEntity); productRepository.save(productEntity); + //판매자에게 사물함 ID와 비밀번호 전달 (알림) + UserEntity seller = productEntity.getSeller(); + + NotificationLockerDTO notificationLockerDTO = new NotificationLockerDTO("사물함 번호 및 비밀번호", availableLockerEntity.getId(), generateLockerPassword(), true, seller); + NotificationEntity notificationSellerEntity = new NotificationEntity(notificationLockerDTO); + notificationRepository.save(notificationSellerEntity); + + // 거래 내역 생성 UserEntity buyer = userRepository.findById(buyerId) .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND)); TradeEntity tradeEntity = TradeEntity.builder() - .productEntity(productEntity) + .product(productEntity) .buyer(buyer) .seller(productEntity.getSeller()) .lockerEntity(availableLockerEntity) @@ -93,9 +117,52 @@ public TradeEntity completePurchase(Long productId, Long buyerId) { } - public ProductEntity findProductById(Long productId) { return productRepository.findById(productId) .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); } + + // 비밀번호 생성 + public String generateLockerPassword() { + Random random = new Random(); + int password = 1000 + random.nextInt(9000); + return String.valueOf(password); + } + + /* + 판매자는 물건을 넣고 확인 버튼 누름 + 구매자에게 사물함에 물건이 보관되었다고 알림 + */ + @Transactional + public void productInLocker(Long productId) { + TradeEntity tradeEntity = tradeRepository.findByProduct_Id(productId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); + + LockerEntity lockerEntity = tradeEntity.getLockerEntity(); + UserEntity userEntity = tradeEntity.getBuyer(); + + NotificationLockerDTO notificationLockerDTO = new NotificationLockerDTO("물건이 사물함에 보관되었습니다.", lockerEntity.getId(), lockerEntity.getLockerPassword(), true, userEntity); + NotificationEntity notificationEntity = new NotificationEntity(notificationLockerDTO); + notificationRepository.save(notificationEntity); + } + + /* + 구매자 수령 완료 + 물품 상태 변경 + 사물함 상태 변경 + */ + @Transactional + public void completePurchaseByBuyer(Long productId) { + ProductEntity productEntity = productRepository.findById(productId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); + + productEntity.setStatus(ProductStatus.DONE); + productRepository.save(productEntity); + + TradeEntity tradeEntity = tradeRepository.findByProduct_Id(productId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); + LockerEntity lockerEntity = tradeEntity.getLockerEntity(); + lockerEntity.setLockerStatus(true); + lockerRepository.save(lockerEntity); + } } \ No newline at end of file diff --git a/src/main/java/com/trade_ham/domain/product/service/SearchProductService.java b/src/main/java/com/trade_ham/domain/product/service/SearchProductService.java new file mode 100644 index 0000000..4a1369f --- /dev/null +++ b/src/main/java/com/trade_ham/domain/product/service/SearchProductService.java @@ -0,0 +1,66 @@ +package com.trade_ham.domain.product.service; + +import com.trade_ham.domain.product.entity.ProductEntity; +import com.trade_ham.domain.product.repository.ProductRepository; +import com.trade_ham.global.common.exception.ErrorCode; +import com.trade_ham.global.common.exception.ResourceNotFoundException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SearchProductService { + + private final ProductRepository productRepository; + + public List searchSellProduct(String keyword) { + return productRepository.findByKeywordContainingAndStatusIsSell(keyword); + } + + + // 상품 클릭 시 상세정보 + public ProductEntity getProductDetail(Long productId, HttpServletRequest request, HttpServletResponse response) { + // 상품 존재 여부 확인 + productRepository.findById(productId) + .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); + + // 세션 쿠키에서 상품 조회 여부 확인 + if (!hasViewedProduct(request, productId)) { + productRepository.increaseView(productId); + addProductToViewedList(response, productId); + } + + // 조회수 증가 + productRepository.increaseView(productId); + + // 증가된 조회수가 반영된 상품 정보 조회 (이미 존재 확인했으므 get() 사용 가능) + return productRepository.findById(productId).get(); + } + + // 세션 쿠키에서 해당 상품을 조회했는지 확인 + private boolean hasViewedProduct(HttpServletRequest request, Long productId) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("viewedProducts".equals(cookie.getName()) && cookie.getValue().contains(String.valueOf(productId))) { + return true; // 이미 조회한 상품 + } + } + } + + return false; // 처음 조회한 상품 + } + + // 세션 쿠키에 조회한 상품 ID 추가 + private void addProductToViewedList(HttpServletResponse response, Long productId) { + Cookie cookie = new Cookie("viewedProducts", String.valueOf(productId)); + cookie.setMaxAge(60 * 60 * 24 * 7); // 쿠키의 유효 기간을 1주일로 설정 + cookie.setPath("/"); // 전체 경로에서 쿠키 사용 가능 + response.addCookie(cookie); + } +} diff --git a/src/main/java/com/trade_ham/domain/product/service/SellProductService.java b/src/main/java/com/trade_ham/domain/product/service/SellProductService.java index 6c2e81b..3026273 100644 --- a/src/main/java/com/trade_ham/domain/product/service/SellProductService.java +++ b/src/main/java/com/trade_ham/domain/product/service/SellProductService.java @@ -7,6 +7,7 @@ import com.trade_ham.domain.product.dto.ProductDTO; import com.trade_ham.domain.product.dto.ProductResponseDTO; import com.trade_ham.domain.product.repository.ProductRepository; +import com.trade_ham.global.common.exception.AccessDeniedException; import com.trade_ham.global.common.exception.ErrorCode; import com.trade_ham.global.common.exception.ResourceNotFoundException; import lombok.RequiredArgsConstructor; @@ -26,6 +27,14 @@ public ProductResponseDTO createProduct(ProductDTO productDTO, Long sellerId) { UserEntity seller = userRepository.findById(sellerId) .orElseThrow(() -> new ResourceNotFoundException(ErrorCode.USER_NOT_FOUND)); + //계좌번호 null인지 확인 + if (seller.getAccount() == null) { + throw new ResourceNotFoundException(ErrorCode.ACCOUNT_NOT_FOUND); + } + + if (seller.getRealname() == null) { + throw new ResourceNotFoundException(ErrorCode.REALNAME_NOT_FOUND); + } ProductEntity productEntity = ProductEntity.builder() .seller(seller) @@ -79,7 +88,6 @@ public List searchProducts(String keyword) { } - // 상태가 SELL인 전체 판매 물품 최신순 조회 // N+1 문제 발생 예상 지역 public List findAllSellProducts() { diff --git a/src/main/java/com/trade_ham/global/common/exception/ErrorCode.java b/src/main/java/com/trade_ham/global/common/exception/ErrorCode.java index e09b58e..1bdee1e 100644 --- a/src/main/java/com/trade_ham/global/common/exception/ErrorCode.java +++ b/src/main/java/com/trade_ham/global/common/exception/ErrorCode.java @@ -19,6 +19,8 @@ public enum ErrorCode { USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_NOT_FOUND", "사용자를 찾을 수 없습니다."), PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "PRODUCT_NOT_FOUND", "상품을 찾을 수 없습니다."), LOCKER_NOT_AVAILABLE(HttpStatus.NOT_FOUND, "LOCKER_NOT_AVAILABLE", "사용 가능한 사물함이 없습니다."), + ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, "ACCOUNT_NOT_FOUND", "계좌번호을 찾을 수 없습니다."), + REALNAME_NOT_FOUND(HttpStatus.NOT_FOUND, "REALNAME_NOT_FOUND", "실제이름을 찾을 수 없습니다."), // 409 Conflict INVALID_PRODUCT_STATE(HttpStatus.CONFLICT, "INVALID_PRODUCT_STATE", "상품 상태가 올바르지 않습니다."), diff --git a/src/main/java/com/trade_ham/global/config/RedisConfig.java b/src/main/java/com/trade_ham/global/config/RedisConfig.java index 2345c21..c8fd642 100644 --- a/src/main/java/com/trade_ham/global/config/RedisConfig.java +++ b/src/main/java/com/trade_ham/global/config/RedisConfig.java @@ -4,8 +4,21 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + + // Key와 Value의 직렬화 설정 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + + return redisTemplate; + } } diff --git a/src/main/java/com/trade_ham/security/config/SecurityConfig.java b/src/main/java/com/trade_ham/security/config/SecurityConfig.java index 0213b16..a5b09fa 100644 --- a/src/main/java/com/trade_ham/security/config/SecurityConfig.java +++ b/src/main/java/com/trade_ham/security/config/SecurityConfig.java @@ -74,13 +74,13 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { // 세션 설정: STATELESS http.sessionManagement(session -> session.sessionCreationPolicy(STATELESS)); - // 경로별 인가 작업 - http.securityMatcher("/**") // 모든 요청에 대해 - .authorizeHttpRequests(auth -> auth - .requestMatchers(WHITE_LIST_URL).permitAll() - .anyRequest().authenticated() - ); - return http.build(); + // 경로별 인가 작업 + http.securityMatcher("/**") // 모든 요청에 대해 + .authorizeHttpRequests(auth -> auth + .requestMatchers(WHITE_LIST_URL).permitAll() + .anyRequest().authenticated() + ); + return http.build(); } private static final String[] WHITE_LIST_URL = { diff --git a/src/main/java/com/trade_ham/security/handler/CustomSuccessHandler.java b/src/main/java/com/trade_ham/security/handler/CustomSuccessHandler.java index 8806066..e0eb02f 100644 --- a/src/main/java/com/trade_ham/security/handler/CustomSuccessHandler.java +++ b/src/main/java/com/trade_ham/security/handler/CustomSuccessHandler.java @@ -62,7 +62,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setHeader("access", access); // 응답헤더에 엑세스 토큰 response.addCookie(createCookie("refresh", refresh)); // 응답쿠키에 리프레시 토큰 //response.setStatus(HttpStatus.OK.value()); 추후 exception 코드로 변경 - response.sendRedirect("http://localhost:3000/"); + response.sendRedirect("http://localhost:8080/login"); } private void addRefreshEntity(Long id, String refresh, Long expiredMs) { diff --git a/src/main/java/com/trade_ham/security/jwt/JWTUtil.java b/src/main/java/com/trade_ham/security/jwt/JWTUtil.java index 317562f..247275d 100644 --- a/src/main/java/com/trade_ham/security/jwt/JWTUtil.java +++ b/src/main/java/com/trade_ham/security/jwt/JWTUtil.java @@ -49,6 +49,7 @@ public String createJwt(String category, Long id, String email, String role, Lon .claim("category", category) .claim("id", id) .claim("email", email) + .claim("role", role) .issuedAt(new Date(System.currentTimeMillis())) .expiration(new Date(System.currentTimeMillis() + expiredMs)) .signWith(secretKey) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0b48967..6a29be4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,11 +2,11 @@ spring.application.name=Trade-Ham # MySQL spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://localhost:3306/ktb_back?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true +spring.datasource.url=jdbc:mysql://localhost:3306/trade_ham?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true #spring.datasource.url=jdbc:mysql://db-tradeham.crm8w048mnut.ap-southeast-2.rds.amazonaws.com:3306/tradeham?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true -spring.datasource.username=milo -spring.datasource.password=5188 -spring.jpa.hibernate.ddl-auto=create +spring.datasource.username=root +spring.datasource.password=20375570 +spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # Redis @@ -22,8 +22,8 @@ spring.jwt.token.refresh-expiration-time=604800000 # 1) Kakao #registration spring.security.oauth2.client.registration.kakao.client-name=kakao -spring.security.oauth2.client.registration.kakao.client-id=d11f3ddb1fed5ab19e585137624feb86 -spring.security.oauth2.client.registration.kakao.client-secret=joxB6tCG2K7ijC6ZVm7edJf50c9ZRWsI +spring.security.oauth2.client.registration.kakao.client-id=5b85ff3fcb68284d8ad6197c7a103f2e +spring.security.oauth2.client.registration.kakao.client-secret=qGFqgPOCOXTIlc7cuXv0EvXlELMPsDBA spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:8080/login/oauth2/code/kakao spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post