diff --git a/src/main/java/ewha/lux/once/domain/home/controller/HomeController.java b/src/main/java/ewha/lux/once/domain/home/controller/HomeController.java index a3d0f2f..83e0538 100644 --- a/src/main/java/ewha/lux/once/domain/home/controller/HomeController.java +++ b/src/main/java/ewha/lux/once/domain/home/controller/HomeController.java @@ -1,6 +1,8 @@ package ewha.lux.once.domain.home.controller; import ewha.lux.once.domain.card.dto.SearchStoresRequestDto; +import ewha.lux.once.domain.home.dto.AnnounceFavoriteRequestDto; +import ewha.lux.once.domain.home.dto.BeaconRequestDto; import ewha.lux.once.domain.home.service.HomeService; import ewha.lux.once.global.common.CommonResponse; import ewha.lux.once.global.common.CustomException; @@ -80,5 +82,27 @@ public CommonResponse nearFavorite(@AuthenticationPrincipal UserAccount user, return new CommonResponse<>(e.getStatus()); } } + // [Post] 알림 생성 요청 + @PostMapping("/announcement") + @ResponseBody + public CommonResponse announceFavorite(@AuthenticationPrincipal UserAccount user, @RequestBody AnnounceFavoriteRequestDto announceFavoriteRequestDto){ + try { + homeService.postAnnounceFavorite(announceFavoriteRequestDto, user.getUsers()); + return new CommonResponse<>(ResponseCode.SUCCESS); + } catch (CustomException e){ + return new CommonResponse<>(e.getStatus()); + } + } + // [Post] 비콘 알림 생성 요청 + @PostMapping("/beacon") + @ResponseBody + public CommonResponse beaconAnnouncement(@AuthenticationPrincipal UserAccount user, @RequestBody BeaconRequestDto beaconRequestDto){ + try { + homeService.postBeaconAnnouncement(beaconRequestDto, user.getUsers()); + return new CommonResponse<>(ResponseCode.SUCCESS); + } catch (CustomException e){ + return new CommonResponse<>(e.getStatus()); + } + } -} +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/dto/AnnounceFavoriteRequestDto.java b/src/main/java/ewha/lux/once/domain/home/dto/AnnounceFavoriteRequestDto.java new file mode 100644 index 0000000..b2764c1 --- /dev/null +++ b/src/main/java/ewha/lux/once/domain/home/dto/AnnounceFavoriteRequestDto.java @@ -0,0 +1,13 @@ +package ewha.lux.once.domain.home.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AnnounceFavoriteRequestDto { + private String store; + private String storeName; + private double latitude; + private double longitude; +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/dto/BeaconRequestDto.java b/src/main/java/ewha/lux/once/domain/home/dto/BeaconRequestDto.java new file mode 100644 index 0000000..4d00532 --- /dev/null +++ b/src/main/java/ewha/lux/once/domain/home/dto/BeaconRequestDto.java @@ -0,0 +1,17 @@ +package ewha.lux.once.domain.home.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class BeaconRequestDto { + private String proximityUUID; + private Integer major; + private Integer minor; +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/entity/Beacon.java b/src/main/java/ewha/lux/once/domain/home/entity/Beacon.java new file mode 100644 index 0000000..ef9d89a --- /dev/null +++ b/src/main/java/ewha/lux/once/domain/home/entity/Beacon.java @@ -0,0 +1,40 @@ +package ewha.lux.once.domain.home.entity; + + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="Beacon") +@Getter +@Setter +@Builder +@NoArgsConstructor(access= AccessLevel.PROTECTED) +@AllArgsConstructor +public class Beacon { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "beaconId") + private Long id; + + @Column(name = "proximityUUID", nullable = false) + private String proximityUUID; + + @Column(name = "major") + private Integer major; + + @Column(name = "minor") + private Integer minor; + + @Column(name = "name") + private String name; + + @Column(name = "store") + private String store; + + @Column(name = "latitude") + private String latitude; + + @Column(name = "longitude") + private String longitude; +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/service/AnnouncementService.java b/src/main/java/ewha/lux/once/domain/home/service/AnnouncementService.java index 58b5206..3fa3ee8 100644 --- a/src/main/java/ewha/lux/once/domain/home/service/AnnouncementService.java +++ b/src/main/java/ewha/lux/once/domain/home/service/AnnouncementService.java @@ -78,13 +78,14 @@ public void cheeringBenefitGoalAnnounce() throws CustomException { List fcmTokens = fcmTokenRepository.findAllByUsers(users); for ( FCMToken fcmToken : fcmTokens){ String token = fcmToken.getToken(); - firebaseCloudMessageService.sendNotification(new AnnouncementRequestDto(token,"ONCE",content)); + firebaseCloudMessageService.sendNotification(new AnnouncementRequestDto(token,"목표 응원 알림",content)); } } } @Scheduled(cron = "0 0 21 10,15,25 * ?") public void cardPerformanceAnnounce() throws CustomException { + String currentDate = String.valueOf(LocalDate.now().getMonthValue());; List ownedCardList = ownedCardRepository.findOwnedCardByIsMain(true); for (OwnedCard card : ownedCardList) { // 실적 업데이트 @@ -97,12 +98,12 @@ public void cardPerformanceAnnounce() throws CustomException { ownedCardRepository.save(card); String res = String.valueOf(Math.max(card.getPerformanceCondition()-card.getCurrentPerformance(),0)); - String content = "이번 달 "+card.getCard().getName()+" 실적까지 "+res+"원 남았어요!"; + String content = "이번 달 "+card.getCard().getName()+" 실적까지\n"+res+"원 남았어요!"; String moreInfo = card.getCard().getImgUrl(); Announcement announcement = Announcement.builder() .users(users) - .type(1) + .type(3) .content(content) .moreInfo(moreInfo) .hasCheck(false) @@ -111,7 +112,7 @@ public void cardPerformanceAnnounce() throws CustomException { List fcmTokens = fcmTokenRepository.findAllByUsers(users); for ( FCMToken fcmToken : fcmTokens){ String token = fcmToken.getToken(); - firebaseCloudMessageService.sendNotification(new AnnouncementRequestDto(token,"ONCE",content)); + firebaseCloudMessageService.sendNotification(new AnnouncementRequestDto(token,currentDate+"월 실적 알림",content)); } } diff --git a/src/main/java/ewha/lux/once/domain/home/service/CODEFAPIService.java b/src/main/java/ewha/lux/once/domain/home/service/CODEFAPIService.java index bf90349..4f92c4e 100644 --- a/src/main/java/ewha/lux/once/domain/home/service/CODEFAPIService.java +++ b/src/main/java/ewha/lux/once/domain/home/service/CODEFAPIService.java @@ -254,8 +254,8 @@ public List GetHistory(String code, String connectedId,String cardName, for (Object obj : dataArray) { JSONObject dataObject = (JSONObject) obj; String storeName = (String) dataObject.get("resMemberStoreName"); - String storeAddr = (String) dataObject.get("resMemberStoreAddr"); - String storeKey = storeName + "#" + storeAddr; // 고유 키 생성 +// String storeAddr = (String) dataObject.get("resMemberStoreAddr"); + String storeKey = storeName; // 고유 키 생성 // 맵에 있는지 확인하고 카운트 업데이트 storeCountMap.put(storeKey, storeCountMap.getOrDefault(storeKey, 0) + 1); @@ -265,11 +265,11 @@ public List GetHistory(String code, String connectedId,String cardName, List> sortedEntries = new ArrayList<>(storeCountMap.entrySet()); sortedEntries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); - // 상위 5개의 매장 정보 추출 + // 상위 10개의 매장 정보 추출 List topStores = new ArrayList<>(); int count = 0; for (Map.Entry entry : sortedEntries) { - if (count >= 5) break; + if (count >= 10) break; topStores.add(entry.getKey()); count++; } @@ -381,4 +381,4 @@ private static String encryptRSA(String plainText, String base64PublicKey) return encrypted; } -} +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/service/CODEFAsyncService.java b/src/main/java/ewha/lux/once/domain/home/service/CODEFAsyncService.java index 33a35b9..c9c6222 100644 --- a/src/main/java/ewha/lux/once/domain/home/service/CODEFAsyncService.java +++ b/src/main/java/ewha/lux/once/domain/home/service/CODEFAsyncService.java @@ -3,6 +3,9 @@ import ewha.lux.once.domain.card.dto.Place; import ewha.lux.once.domain.card.dto.GoogleMapPlaceResponseDto; import ewha.lux.once.domain.card.entity.OwnedCard; +import ewha.lux.once.domain.home.dto.OpenaiChatRequest; +import ewha.lux.once.domain.home.dto.OpenaiChatResponse; +import ewha.lux.once.domain.home.entity.Favorite; import ewha.lux.once.domain.user.entity.Users; import ewha.lux.once.global.common.CustomException; import ewha.lux.once.global.common.ResponseCode; @@ -10,6 +13,8 @@ import ewha.lux.once.global.repository.OwnedCardRepository; import ewha.lux.once.global.repository.UsersRepository; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -30,58 +35,41 @@ public class CODEFAsyncService { private final CODEFAPIService codefapi; private final FavoriteRepository favoriteRepository; private final UsersRepository usersRepository; - private final RestTemplate restTemplate; + // private final RestTemplate restTemplate; private final OwnedCardRepository ownedCardRepository; + + @Value("${openai.api.url}") + private String apiUrl; + + @Qualifier("openaiRestTemplate") + @Autowired + private RestTemplate restTemplate; + @Async public void saveFavorite(String code, String connectedId, OwnedCard ownedCard, Users nowUser, String cardNo) throws CustomException { -// // 승인 내역 조회 -> 단골 가게 카드별 5개 -// List favorites = codefapi.GetHistory(code,connectedId,ownedCard.getCard().getName(),cardNo); -// -// Map existingStores = storeRepository.findByNameIn(favorites.stream() -// .map(favorite -> favorite.split("#")[0]) -// .collect(Collectors.toList())) -// .stream() -// .collect(Collectors.toMap(Store::getName, Function.identity())); -// -// List newFavorites = new ArrayList<>(); -// for (String favorite : favorites) { -// String[] parts = favorite.split("#"); -// String storeName = parts[0]; -// String storeAddr = (parts.length > 1) ? parts[1] : ""; -// -// Store existingStore = existingStores.get(storeName); -// if (existingStore == null) { -// HashMap placeInfo = searchStoreAddr(storeName); -// if(storeAddr==""){ -// storeAddr = (String) placeInfo.get("formattedAddress"); -// } -// Store store = Store.builder() -// .name(storeName) -// .address(storeAddr) -// .build(); -// -// if(placeInfo.get("x") != null && placeInfo.get("y") != null) { -// double x = (double) placeInfo.get("x"); -// double y = (double) placeInfo.get("y"); -// store.setX(x); -// store.setY(y); -// } -// storeRepository.save(store); -// -// newFavorites.add(Favorite.builder() -// .store(store) -// .users(nowUser) -// .build()); -// } else { -// if (!favoriteRepository.existsByStoreAndUsers(existingStore, nowUser)) { -// newFavorites.add(Favorite.builder() -// .store(existingStore) -// .users(nowUser) -// .build()); -// } -// } -// } -// favoriteRepository.saveAll(newFavorites); + // 승인 내역 조회 -> 단골 가게 카드별 10개 + List favorites = codefapi.GetHistory(code,connectedId,ownedCard.getCard().getName(),cardNo); + + String system ="입력받은 가맹점명에서 브랜드 이름을 찾아서 뽑아줘. 출력은 단어만, 알아낼 수 없다면 null을 반환해줘."; + for (String keyword : favorites){ + OpenaiChatRequest request = new OpenaiChatRequest("gpt-4-turbo", system, keyword); + OpenaiChatResponse response = restTemplate.postForObject(apiUrl, request, OpenaiChatResponse.class); + if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) { + throw new CustomException(ResponseCode.FAILED_TO_OPENAI); + } + String result = response.getChoices().get(0).getMessage().getContent(); + System.out.println(result); + if (!"null".equals(result)) { + boolean exists = favoriteRepository.existsByNameAndUsers(result,nowUser); + if (!exists) { + Favorite favorite = Favorite.builder() + .users(nowUser) + .name(result) + .build(); + favoriteRepository.save(favorite); + } + } + } } @Async public void deleteConnectedID(Users nowUser,OwnedCard ownedCard) throws CustomException { @@ -148,4 +136,4 @@ public void updateOwnedCardsPerformanceCodef(Users nowUser)throws CustomExceptio } } } -} +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/service/HomeService.java b/src/main/java/ewha/lux/once/domain/home/service/HomeService.java index c2daf75..0d0cfa1 100644 --- a/src/main/java/ewha/lux/once/domain/home/service/HomeService.java +++ b/src/main/java/ewha/lux/once/domain/home/service/HomeService.java @@ -1,23 +1,22 @@ package ewha.lux.once.domain.home.service; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import ewha.lux.once.domain.card.dto.GoogleMapPlaceResponseDto; -import ewha.lux.once.domain.card.dto.SearchStoresRequestDto; import ewha.lux.once.domain.card.dto.Place; +import ewha.lux.once.domain.card.dto.SearchStoresRequestDto; import ewha.lux.once.domain.card.dto.SearchStoresResponseDto; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import ewha.lux.once.domain.card.entity.Card; import ewha.lux.once.domain.card.entity.OwnedCard; import ewha.lux.once.domain.home.dto.*; -import ewha.lux.once.domain.home.entity.Announcement; -import ewha.lux.once.domain.home.entity.ChatHistory; -import ewha.lux.once.domain.home.entity.Favorite; +import ewha.lux.once.domain.home.entity.*; import ewha.lux.once.domain.user.entity.Users; import ewha.lux.once.global.common.CustomException; import ewha.lux.once.global.common.ResponseCode; import ewha.lux.once.global.repository.*; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -35,13 +34,18 @@ public class HomeService { @Value("${google-map.api-key}") private String apiKey; private final RestTemplate restTemplate; + private final FavoriteRepository favoriteRepository; private final CardRepository cardRepository; private final OwnedCardRepository ownedCardRepository; private final ChatHistoryRepository chatHistoryRepository; private final AnnouncementRepository announcementRepository; - private final FavoriteRepository favoriteRepository; + private final GeminiService geminiService; private final OpenaiService openaiService; + private final FCMTokenRepository fcmTokenRepository; + private final FirebaseCloudMessageService firebaseCloudMessageService; + private final BeaconRepository beaconRepository; + private final CODEFAsyncService codefAsyncService; // 챗봇 카드 추천 public ChatDto getHomeChat(Users nowUser, String keyword, int paymentAmount) throws CustomException { @@ -311,4 +315,86 @@ public List searchStores(SearchStoresRequestDto dto, Us } return resultList; } + public void postAnnounceFavorite(AnnounceFavoriteRequestDto dto, Users nowUser) throws CustomException { + List fcmTokens = fcmTokenRepository.findAllByUsers(nowUser); + String keyword = dto.getStore(); + int paymentAmount=10000; + String response = openaiService.cardRecommend(nowUser, keyword, paymentAmount); + ObjectMapper objectMapper = new ObjectMapper(); + Integer cardId; + Card card; + String benefit; + Integer discount; + try { + Map map = objectMapper.readValue(response, Map.class); + cardId = (Integer) map.get("카드번호"); + card = cardRepository.findById(Long.valueOf(cardId)).orElse(null); + benefit = (String) map.get("혜택 정보"); + discount = (Integer) map.get("할인 금액"); + + } catch ( JsonProcessingException e) { + throw new CustomException(ResponseCode.FAILED_TO_OPENAI_RECOMMEND); + } + + String contents = dto.getStoreName()+" 근처시군요.\n"+card.getName()+" 사용해 보세요!"; + String title = dto.getStoreName()+" 근처시군요"; + String content = card.getName()+" 사용해 보세요!"; + String moreInfo = dto.getLatitude()+", "+dto.getLongitude(); + Announcement announcement = Announcement.builder() + .users(nowUser) + .type(0) + .content(contents) + .moreInfo(moreInfo) + .hasCheck(false) + .build(); + announcementRepository.save(announcement); + for ( FCMToken fcmToken : fcmTokens){ + + String token = fcmToken.getToken(); + firebaseCloudMessageService.sendNotification(new AnnouncementRequestDto(token,title,content)); + } + } + public void postBeaconAnnouncement(BeaconRequestDto dto, Users nowUser)throws CustomException { + List fcmTokens = fcmTokenRepository.findAllByUsers(nowUser); + + Beacon beacon = beaconRepository.findAllByProximityUUIDAndMajorAndMinor(dto.getProximityUUID(),dto.getMajor(),dto.getMinor()); + + String keyword = beacon.getName(); + int paymentAmount=10000; + String response = openaiService.cardRecommend(nowUser, keyword, paymentAmount); + ObjectMapper objectMapper = new ObjectMapper(); + Integer cardId; + Card card; + String benefit; + Integer discount; + try { + Map map = objectMapper.readValue(response, Map.class); + cardId = (Integer) map.get("카드번호"); + card = cardRepository.findById(Long.valueOf(cardId)).orElse(null); + benefit = (String) map.get("혜택 정보"); + discount = (Integer) map.get("할인 금액"); + + } catch ( JsonProcessingException e) { + throw new CustomException(ResponseCode.FAILED_TO_OPENAI_RECOMMEND); + } + + String title = beacon.getStore()+" 근처시군요"; + String content = card.getName()+" 사용해 보세요!"; + String contents = beacon.getStore()+" 근처시군요.\n"+card.getName()+" 사용해 보세요!"; + + String moreInfo = beacon.getLatitude()+", "+beacon.getLongitude(); + Announcement announcement = Announcement.builder() + .users(nowUser) + .type(0) + .content(contents) + .moreInfo(moreInfo) + .hasCheck(false) + .build(); + announcementRepository.save(announcement); + for ( FCMToken fcmToken : fcmTokens){ + String token = fcmToken.getToken(); + firebaseCloudMessageService.sendNotification(new AnnouncementRequestDto(token,title,content)); + } + + } } \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/domain/home/service/OpenaiService.java b/src/main/java/ewha/lux/once/domain/home/service/OpenaiService.java index 9785016..dfefe9d 100644 --- a/src/main/java/ewha/lux/once/domain/home/service/OpenaiService.java +++ b/src/main/java/ewha/lux/once/domain/home/service/OpenaiService.java @@ -47,23 +47,24 @@ public class OpenaiService { // 결제할 카드 추천 public String cardRecommend(Users nowUser, String keyword, int paymentAmount) throws CustomException { - String prompt = "결제처, 결제 금액, 카드들의 혜택정보를 Input으로 하여, 결제처에서 최적의 혜택을 누릴 수 있는 카드를 ”카드번호”, “혜택 정보”, “할인 금액”을 키로 가지는 json형식으로 반환. output에 ``` 붙이지 말 것.\\" - +"각 카드가 입력된 결제처에 해당되는 혜택을 가지고 있다면 할인 금액을 계산하고, 여러 카드 중 가장 할인 금액이 큰 카드를 찾아낼 것\\" - +"”카드번호” 는 해당 카드의 '카드 고유 번호', “혜택 정보”는 결제처에 해당되는 혜택 정보 요약 텍스트(특수문자 없이 20자 이내)"; + String prompt = "결제 금액, 결제처, 카드들의 혜택 정보를 입력으로 받아, 각 카드별로 결제처에 해당하는 혜택이 있다면 할인 금액을 계산합니다. 가장 큰 할인을 받을 수 있는 카드의 \"카드번호\", \"혜택 정보\", \"할인 금액\"을 JSON 형식으로 반환합니다.\\" + +"```를 붙이지 않습니다. 결제처에 해당하는 카드의 혜택이 없거나, 결제처가 분야·브랜드명이 아니라면, 모든 value에 0을 넣어 반환합니다.\\" + +"\"카드번호\" 는 해당 카드의 '카드 고유 번호', \"혜택 정보\"는 결제처에 해당되는 혜택 정보 요약 텍스트(특수문자 없이 20자 이내)를 의미합니다."; List ownedCards = ownedCardRepository.findOwnedCardByUsers(nowUser); - String userInput = "{”결제 금액”: " + paymentAmount + ", “결제처”: “" + keyword + "“, “카드들의 혜택 정보“: ["; + String userInput = "{\"결제 금액\": " + paymentAmount + ", \"결제처\": \"" + keyword + "\", \"카드들의 혜택 정보\": ["; for (OwnedCard ownedCard : ownedCards) { String name = ownedCard.getCard().getName(); String id = ownedCard.getCard().getId().toString(); Card card = ownedCard.getCard(); - userInput = userInput +"{”이름”: ”"+ name + "”, " + "”카드 고유 번호” : " + id + ", “혜택”: ”"; + userInput = userInput +"{\"이름\": \""+ name + "\", " + "\"카드 고유 번호\" : " + id + ", \"혜택\": [ "; List beneList = benefitSummaryRepository.findByCard(card); for( BenefitSummary benefit : beneList){ - userInput += benefit.getBenefitField()+" - "+benefit.getBenefitContents()+"/"; + userInput += "\""+benefit.getBenefitField()+" "+benefit.getBenefitContents()+"\","; } - userInput += "” },"; + userInput = userInput.substring(0, userInput.length() - 1); + userInput += "\" },"; } userInput = userInput.substring(0, userInput.length() - 1); userInput += "]"; diff --git a/src/main/java/ewha/lux/once/global/repository/BeaconRepository.java b/src/main/java/ewha/lux/once/global/repository/BeaconRepository.java new file mode 100644 index 0000000..d9916fc --- /dev/null +++ b/src/main/java/ewha/lux/once/global/repository/BeaconRepository.java @@ -0,0 +1,7 @@ +package ewha.lux.once.global.repository; + +import ewha.lux.once.domain.home.entity.Beacon; +import org.springframework.data.jpa.repository.JpaRepository; +public interface BeaconRepository extends JpaRepository { + Beacon findAllByProximityUUIDAndMajorAndMinor(String uuid, int major, int minor); +} \ No newline at end of file diff --git a/src/main/java/ewha/lux/once/global/repository/FavoriteRepository.java b/src/main/java/ewha/lux/once/global/repository/FavoriteRepository.java index 7d5b5ac..0855944 100644 --- a/src/main/java/ewha/lux/once/global/repository/FavoriteRepository.java +++ b/src/main/java/ewha/lux/once/global/repository/FavoriteRepository.java @@ -9,4 +9,5 @@ public interface FavoriteRepository extends JpaRepository { Optional> findAllByUsers(Users users); + Boolean existsByNameAndUsers(String name, Users users); }