From d192bbedbf8bb56046330405997e530e3d641eea Mon Sep 17 00:00:00 2001 From: sungjindev Date: Tue, 13 Feb 2024 22:57:08 +0900 Subject: [PATCH 01/57] =?UTF-8?q?feat=20:=20Redis=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20build.gradle?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index d7c05c4..9b0b7e4 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,9 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' } + //Redis를 사용하기 위해 추가 + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + } test { From fa5fefeceeeb5b98051cfb33f76cf01011a32aff Mon Sep 17 00:00:00 2001 From: sungjindev Date: Wed, 14 Feb 2024 16:49:49 +0900 Subject: [PATCH 02/57] =?UTF-8?q?feat=20:=20Redis=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EA=B8=B0=EB=B3=B8=20Config=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/global/config/RedisConfig.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/global/config/RedisConfig.java diff --git a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java new file mode 100644 index 0000000..6fc3399 --- /dev/null +++ b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java @@ -0,0 +1,25 @@ +package com.nainga.nainga.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +@Configuration +public class RedisConfig { //Redis 연결을 위한 기본 설정 + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory( + new RedisStandaloneConfiguration(host, port) + ); + } +} \ No newline at end of file From 171940234997ccfdc2423f59077c9389102ccfef Mon Sep 17 00:00:00 2001 From: sungjindev Date: Wed, 14 Feb 2024 20:52:38 +0900 Subject: [PATCH 03/57] =?UTF-8?q?feat=20:=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=99=84=EC=84=B1=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=B4=20RedisSortedSetService=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/RedisSortedSetService.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java diff --git a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java new file mode 100644 index 0000000..852bdd5 --- /dev/null +++ b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java @@ -0,0 +1,21 @@ +package com.nainga.nainga.global.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RedisSortedSetService { //검색어 자동 완성을 구현할 때 사용하는 Redis의 SortedSet 관련 서비스 레이어 + private final RedisTemplate redisTemplate; + private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 + private int score = 0; //Score는 딱히 필요 없으므로 하나로 통일 + + public void addToSortedSet(String value) { //Redis SortedSet에 추가 + redisTemplate.opsForZSet().add(key, value, score); + } + + public Long findFromSortedSet(String value) { //Redis SortedSet에서 Value를 찾아 인덱스를 반환 + return redisTemplate.opsForZSet().rank(key, value); + } +} From d6b5b4c03febbfae6321d11ab4ea2d0e256c75f5 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Wed, 14 Feb 2024 21:20:52 +0900 Subject: [PATCH 04/57] =?UTF-8?q?feat=20:=20findAllDisplayName=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/application/StoreService.java | 32 +++++++++++++++++++ .../domain/store/dao/StoreRepository.java | 5 +++ 2 files changed, 37 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/store/application/StoreService.java diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java new file mode 100644 index 0000000..4df207b --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -0,0 +1,32 @@ +package com.nainga.nainga.domain.store.application; + +import com.nainga.nainga.domain.store.dao.StoreRepository; +import com.nainga.nainga.domain.storecertification.domain.StoreCertification; +import com.nainga.nainga.global.application.RedisSortedSetService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class StoreService { + private final StoreRepository storeRepository; + private final RedisSortedSetService redisSortedSetService; + + @PostConstruct + public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) + int pageSize = 1000; + int page = 0; + + while (true) { + storeRepository + } + + } +} diff --git a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java index fb5be1d..922d392 100644 --- a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java +++ b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java @@ -43,4 +43,9 @@ public List findAll() { return em.createQuery("select s from Store s", Store.class) .getResultList(); } + + public List findAllDisplayName() { + return em.createQuery("select s.displayName from Store s", Store.class) + .getResultList(); + } } From 20bdac9a06269ebbf29aae8707b2f1cc53d758c8 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Wed, 14 Feb 2024 21:47:53 +0900 Subject: [PATCH 05/57] =?UTF-8?q?feat=20:=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=99=84=EC=84=B1=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20=EB=AA=A8=EB=93=A0=20=EA=B0=80=EA=B2=8C=EB=AA=85?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=9D=8C=EC=A0=88=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=EB=A1=9C=20=EC=9E=98=EB=9D=BC=20Redis=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/application/StoreService.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 4df207b..b8d4ad4 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -1,6 +1,7 @@ package com.nainga.nainga.domain.store.application; import com.nainga.nainga.domain.store.dao.StoreRepository; +import com.nainga.nainga.domain.store.domain.Store; import com.nainga.nainga.domain.storecertification.domain.StoreCertification; import com.nainga.nainga.global.application.RedisSortedSetService; import jakarta.annotation.PostConstruct; @@ -21,12 +22,16 @@ public class StoreService { @PostConstruct public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) - int pageSize = 1000; - int page = 0; + saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 + } - while (true) { - storeRepository - } + private void saveAllSubstring(List allDisplayName) { //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 + for (Store displayName : allDisplayName) { + redisSortedSetService.addToSortedSet(displayName.getDisplayName() + "*"); //완벽한 형태의 단어일 경우에는 *을 붙여 구분 + for (int i = displayName.getDisplayName().length()-1; i > 0; --i) { //음절 단위로 잘라서 모든 Substring 구하기 + redisSortedSetService.addToSortedSet(displayName.getDisplayName().substring(0, i)); //곧바로 redis에 저장 + } + } } } From 7bd03eee08c8b580a160d7c057e82ca4e8afbf87 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Wed, 14 Feb 2024 21:58:37 +0900 Subject: [PATCH 06/57] =?UTF-8?q?feat=20:=20Redis=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20index=20=EC=9D=B4=ED=9B=84=EC=9D=98=20?= =?UTF-8?q?=EA=B0=92=EB=93=A4=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/store/application/StoreService.java | 10 ++++++++++ .../global/application/RedisSortedSetService.java | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index b8d4ad4..48661b8 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -34,4 +34,14 @@ private void saveAllSubstring(List allDisplayName) { //MySQL DB에 저장 } } } + + public List autocorrect(String keyword) { //검색어 자동 완성 기능 관련 로직 + Long index = redisSortedSetService.findFromSortedSet(keyword); //사용자가 입력한 검색어를 바탕으로 Redis에서 조회한 결과 매칭되는 index + + if (index == null) { + return new ArrayList<>(); //만약 사용자 검색어 바탕으로 자동 완성 검색어를 만들 수 없으면 Empty Array 리턴 + } + + + } } diff --git a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java index 852bdd5..6ac24ec 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.util.Set; @Service @RequiredArgsConstructor @@ -18,4 +19,8 @@ public void addToSortedSet(String value) { //Redis SortedSet에 추가 public Long findFromSortedSet(String value) { //Redis SortedSet에서 Value를 찾아 인덱스를 반환 return redisTemplate.opsForZSet().rank(key, value); } + + public Set findAllValuesAfterIndexFromSortedSet(Long index) { + return redisTemplate.opsForZSet().range(key, index, index + 200); //전체를 다 불러오기 보다는 200개 정도만 가져와도 자동 완성을 구현하는 데 무리가 없으므로 200개로 rough하게 설정 + } } From 43eaafed1db328724d7e48638ca667a9fe237263 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Wed, 14 Feb 2024 22:05:18 +0900 Subject: [PATCH 07/57] =?UTF-8?q?feat=20:=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=99=84=EC=84=B1=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EA=B3=BC=20=EA=B4=80=EB=A0=A8=EB=90=9C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/application/StoreService.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 48661b8..be1f074 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -6,12 +6,14 @@ import com.nainga.nainga.global.application.RedisSortedSetService; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; @Service @Transactional(readOnly = true) @@ -19,6 +21,8 @@ public class StoreService { private final StoreRepository storeRepository; private final RedisSortedSetService redisSortedSetService; + private String suffix = "*"; //검색어 자동 완성 기능에서 실제 노출될 수 있는 완벽한 형태의 단어를 구분하기 위한 접미사 + private int maxSize = 5; //검색어 자동 완성 기능 최대 개수 @PostConstruct public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) @@ -27,7 +31,7 @@ public void init() { //이 Service Bean이 생성된 이후에 검색어 자 private void saveAllSubstring(List allDisplayName) { //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 for (Store displayName : allDisplayName) { - redisSortedSetService.addToSortedSet(displayName.getDisplayName() + "*"); //완벽한 형태의 단어일 경우에는 *을 붙여 구분 + redisSortedSetService.addToSortedSet(displayName.getDisplayName() + suffix); //완벽한 형태의 단어일 경우에는 *을 붙여 구분 for (int i = displayName.getDisplayName().length()-1; i > 0; --i) { //음절 단위로 잘라서 모든 Substring 구하기 redisSortedSetService.addToSortedSet(displayName.getDisplayName().substring(0, i)); //곧바로 redis에 저장 @@ -42,6 +46,14 @@ public List autocorrect(String keyword) { //검색어 자동 완성 기 return new ArrayList<>(); //만약 사용자 검색어 바탕으로 자동 완성 검색어를 만들 수 없으면 Empty Array 리턴 } + Set allValuesAfterIndexFromSortedSet = redisSortedSetService.findAllValuesAfterIndexFromSortedSet(index); //사용자 검색어 이후로 정렬된 Redis 데이터들 가져오기 + List autocorrectKeywords = allValuesAfterIndexFromSortedSet.stream() + .filter(value -> value.endsWith(suffix) && value.startsWith(keyword)) + .map(value -> StringUtils.removeEnd(value, suffix)) + .limit(maxSize) + .toList(); //자동 완성을 통해 만들어진 최대 maxSize개의 키워드들 + + return autocorrectKeywords; } } From ecadff3d6614da6311792b4f2103d2512b94d359 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Thu, 15 Feb 2024 02:10:49 +0900 Subject: [PATCH 08/57] =?UTF-8?q?feat=20:=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=99=84=EC=84=B1=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20API=20=EA=B5=AC=ED=98=84=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/store/api/StoreApi.java | 18 ++++++++++++++++++ .../domain/store/dao/StoreRepository.java | 2 +- src/main/resources/backend-submodule | 2 +- src/test/resources/backend-submodule | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java b/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java index a9a8b5a..2312eaa 100644 --- a/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java +++ b/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java @@ -3,9 +3,11 @@ import com.nainga.nainga.domain.store.application.GoodPriceGoogleMapStoreService; import com.nainga.nainga.domain.store.application.MobeomGoogleMapStoreService; import com.nainga.nainga.domain.store.application.SafeGoogleMapStoreService; +import com.nainga.nainga.domain.store.application.StoreService; import com.nainga.nainga.domain.store.dto.CreateDividedGoodPriceStoresResponse; import com.nainga.nainga.domain.store.dto.CreateDividedMobeomStoresResponse; import com.nainga.nainga.domain.store.dto.CreateDividedSafeStoresResponse; +import com.nainga.nainga.domain.storecertification.dto.StoreCertificationsByLocationResponse; import com.nainga.nainga.global.util.Result; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; @@ -15,12 +17,15 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequiredArgsConstructor public class StoreApi { private final MobeomGoogleMapStoreService mobeomGoogleMapStoreService; private final SafeGoogleMapStoreService safeGoogleMapStoreService; private final GoodPriceGoogleMapStoreService goodPriceGoogleMapStoreService; + private final StoreService storeService; @Hidden @Tag(name = "초기 Data 생성") @@ -78,5 +83,18 @@ public Result createDividedGoodPriceStores System.out.println("response = " + response); //편하게 콘솔 로그에서 확인하기 위한 용도 return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, response); } + + //검색어를 이용해 가게 이름에 대해 검색하여 나온 검색 결과를 바탕으로 검색어를 자동 완성해서 최대 5개의 자동 완성된 검색어를 리턴 + @Tag(name = "[New] 검색어 자동 완성") + @Operation(summary = "사용자의 검색 키워드를 바탕으로 검색어 자동 완성", description = "사용자의 검색 키워드를 바탕으로 DB에서 매칭되는 가게 이름을 조회하여 최대 5개까지 검색어를 자동으로 완성하여 반환해줍니다.

" + + "[Request Body]
" + + "searchKeyword: 사용자의 검색 키워드
" + + "[Response Body]
" + + "자동으로 완성된 최대 5개의 검색어
") + @GetMapping("api/store/autocorrect/v1") + public Result> autocorrect(String searchKeyword) { + List autocorrectResult = storeService.autocorrect(searchKeyword); + return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, autocorrectResult); + } } diff --git a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java index 922d392..b5a22e5 100644 --- a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java +++ b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java @@ -45,7 +45,7 @@ public List findAll() { } public List findAllDisplayName() { - return em.createQuery("select s.displayName from Store s", Store.class) + return em.createQuery("select s from Store s", Store.class) .getResultList(); } } diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule index 864e24e..6ee111c 160000 --- a/src/main/resources/backend-submodule +++ b/src/main/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 864e24eecb92e71b9c8988e9684e4faf9802dcd8 +Subproject commit 6ee111c8697d5426fe4c7ee77e345f83d518b162 diff --git a/src/test/resources/backend-submodule b/src/test/resources/backend-submodule index 732770b..6ee111c 160000 --- a/src/test/resources/backend-submodule +++ b/src/test/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 732770ba3e8a25abfb4212a7a75175249e2c099e +Subproject commit 6ee111c8697d5426fe4c7ee77e345f83d518b162 From a77e476a2c74d77f51f6ed6cb54e7c82603f737a Mon Sep 17 00:00:00 2001 From: hoon Date: Thu, 15 Feb 2024 03:48:41 +0900 Subject: [PATCH 09/57] =?UTF-8?q?feat=20:=20redis=20=EC=84=A4=EC=A0=95=20(?= =?UTF-8?q?#100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/backend-submodule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule index 864e24e..d4c5471 160000 --- a/src/main/resources/backend-submodule +++ b/src/main/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 864e24eecb92e71b9c8988e9684e4faf9802dcd8 +Subproject commit d4c5471ea68b445d3488db11f96b6cd861782e2c From 46c0a5494cbfcc92658a78f8f276ac4062e2ef5f Mon Sep 17 00:00:00 2001 From: hoon Date: Thu, 15 Feb 2024 03:48:50 +0900 Subject: [PATCH 10/57] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=EA=B1=B0=20=EC=B6=94=EA=B0=80=20(#100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/action-develop-cd.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/action-develop-cd.yml b/.github/workflows/action-develop-cd.yml index e654d6a..ead5c18 100644 --- a/.github/workflows/action-develop-cd.yml +++ b/.github/workflows/action-develop-cd.yml @@ -3,10 +3,13 @@ name: action-develop-cd # 언제 이 파일의 내용이 실행될 것인지 정의 on: push: + branches: + - develop + pull_request: # 테스트트리거 branches: - develop - # 코드의 내용을 이 파일을 실행하여 action을 수행하는 주체(Github Actions에서 사용하는 VM)가 읽을 수 있도록 권한을 설정 + # 코드의 내용을 이 파일을 실행하여 action을 수행하는 주체(Github Actions에서 사용하는 VM)가 읽을 수 있도록 권한을 설정 permissions: contents: read From 340a9679218ba5edae0c0ee90384bdb553345c81 Mon Sep 17 00:00:00 2001 From: hoon Date: Thu, 15 Feb 2024 03:58:14 +0900 Subject: [PATCH 11/57] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=EA=B1=B0=20=EC=A0=9C=EA=B1=B0=20(#100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/action-develop-cd.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/action-develop-cd.yml b/.github/workflows/action-develop-cd.yml index ead5c18..8d607c0 100644 --- a/.github/workflows/action-develop-cd.yml +++ b/.github/workflows/action-develop-cd.yml @@ -3,9 +3,6 @@ name: action-develop-cd # 언제 이 파일의 내용이 실행될 것인지 정의 on: push: - branches: - - develop - pull_request: # 테스트트리거 branches: - develop From 0f176ee60ff3e6cb1a7fa3049a00517d21683260 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Thu, 15 Feb 2024 04:36:43 +0900 Subject: [PATCH 12/57] =?UTF-8?q?fix=20:=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=99=84=EC=84=B1=20=EC=B5=9C=EB=8C=80=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=EB=A5=BC=205=EA=B0=9C=EC=97=90=EC=84=9C=2010?= =?UTF-8?q?=EA=B0=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/nainga/nainga/domain/store/api/StoreApi.java | 4 ++-- .../nainga/nainga/domain/store/application/StoreService.java | 2 +- src/main/resources/backend-submodule | 2 +- src/test/resources/backend-submodule | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java b/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java index 2312eaa..b8cf94b 100644 --- a/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java +++ b/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java @@ -84,9 +84,9 @@ public Result createDividedGoodPriceStores return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, response); } - //검색어를 이용해 가게 이름에 대해 검색하여 나온 검색 결과를 바탕으로 검색어를 자동 완성해서 최대 5개의 자동 완성된 검색어를 리턴 + //검색어를 이용해 가게 이름에 대해 검색하여 나온 검색 결과를 바탕으로 검색어를 자동 완성해서 최대 10개의 자동 완성된 검색어를 리턴 @Tag(name = "[New] 검색어 자동 완성") - @Operation(summary = "사용자의 검색 키워드를 바탕으로 검색어 자동 완성", description = "사용자의 검색 키워드를 바탕으로 DB에서 매칭되는 가게 이름을 조회하여 최대 5개까지 검색어를 자동으로 완성하여 반환해줍니다.

" + + @Operation(summary = "사용자의 검색 키워드를 바탕으로 검색어 자동 완성", description = "사용자의 검색 키워드를 바탕으로 DB에서 매칭되는 가게 이름을 조회하여 최대 10개까지 검색어를 자동으로 완성하여 반환해줍니다.

" + "[Request Body]
" + "searchKeyword: 사용자의 검색 키워드
" + "[Response Body]
" + diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index be1f074..6e252f7 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -22,7 +22,7 @@ public class StoreService { private final StoreRepository storeRepository; private final RedisSortedSetService redisSortedSetService; private String suffix = "*"; //검색어 자동 완성 기능에서 실제 노출될 수 있는 완벽한 형태의 단어를 구분하기 위한 접미사 - private int maxSize = 5; //검색어 자동 완성 기능 최대 개수 + private int maxSize = 10; //검색어 자동 완성 기능 최대 개수 @PostConstruct public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule index d4c5471..4e581de 160000 --- a/src/main/resources/backend-submodule +++ b/src/main/resources/backend-submodule @@ -1 +1 @@ -Subproject commit d4c5471ea68b445d3488db11f96b6cd861782e2c +Subproject commit 4e581de872bb372c49ec83495262d8d04d7e8e76 diff --git a/src/test/resources/backend-submodule b/src/test/resources/backend-submodule index d4c5471..4e581de 160000 --- a/src/test/resources/backend-submodule +++ b/src/test/resources/backend-submodule @@ -1 +1 @@ -Subproject commit d4c5471ea68b445d3488db11f96b6cd861782e2c +Subproject commit 4e581de872bb372c49ec83495262d8d04d7e8e76 From 319fc20f94855064908470d10076b7a170f8b147 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Thu, 15 Feb 2024 05:00:11 +0900 Subject: [PATCH 13/57] =?UTF-8?q?refactor=20:=20findAllDisplayName()=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/store/application/StoreService.java | 10 +++++----- .../nainga/domain/store/dao/StoreRepository.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 6e252f7..dc84cdb 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -29,12 +29,12 @@ public void init() { //이 Service Bean이 생성된 이후에 검색어 자 saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 } - private void saveAllSubstring(List allDisplayName) { //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 - for (Store displayName : allDisplayName) { - redisSortedSetService.addToSortedSet(displayName.getDisplayName() + suffix); //완벽한 형태의 단어일 경우에는 *을 붙여 구분 + private void saveAllSubstring(List allDisplayName) { //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 + for (String displayName : allDisplayName) { + redisSortedSetService.addToSortedSet(displayName + suffix); //완벽한 형태의 단어일 경우에는 *을 붙여 구분 - for (int i = displayName.getDisplayName().length()-1; i > 0; --i) { //음절 단위로 잘라서 모든 Substring 구하기 - redisSortedSetService.addToSortedSet(displayName.getDisplayName().substring(0, i)); //곧바로 redis에 저장 + for (int i = displayName.length()-1; i > 0; --i) { //음절 단위로 잘라서 모든 Substring 구하기 + redisSortedSetService.addToSortedSet(displayName.substring(0, i)); //곧바로 redis에 저장 } } } diff --git a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java index b5a22e5..90d16d5 100644 --- a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java +++ b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java @@ -44,8 +44,8 @@ public List findAll() { .getResultList(); } - public List findAllDisplayName() { - return em.createQuery("select s from Store s", Store.class) + public List findAllDisplayName() { + return em.createQuery("select s.displayName from Store s", String.class) .getResultList(); } } From 94ab06fe7860f5142929bd2484446b0aba9bf6f9 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Fri, 16 Feb 2024 16:41:32 +0900 Subject: [PATCH 14/57] =?UTF-8?q?refactor=20:=20=EB=B3=91=EB=AA=A9=20?= =?UTF-8?q?=ED=98=84=EC=83=81=EC=9D=B4=20=EA=B1=B8=EB=A6=AC=EB=8D=98=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=A9=80=ED=8B=B0=20=EC=8A=A4?= =?UTF-8?q?=EB=A0=88=EB=93=9C=20=EB=B3=91=EB=A0=AC=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC=20175=EC=B4=88?= =?UTF-8?q?=EC=97=90=EC=84=9C=204=EC=B4=88=EB=A1=9C=20=EC=84=B1=EB=8A=A5?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/store/api/StoreApi.java | 4 ++-- .../store/application/StoreService.java | 21 ++++++++++++------- .../application/RedisSortedSetService.java | 4 ++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java b/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java index b8cf94b..dc5177f 100644 --- a/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java +++ b/src/main/java/com/nainga/nainga/domain/store/api/StoreApi.java @@ -90,9 +90,9 @@ public Result createDividedGoodPriceStores "[Request Body]
" + "searchKeyword: 사용자의 검색 키워드
" + "[Response Body]
" + - "자동으로 완성된 최대 5개의 검색어
") + "자동으로 완성된 최대 10개의 검색어
") @GetMapping("api/store/autocorrect/v1") - public Result> autocorrect(String searchKeyword) { + public Result> autocorrect(@RequestParam(value = "searchKeyword") String searchKeyword) { List autocorrectResult = storeService.autocorrect(searchKeyword); return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, autocorrectResult); } diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index dc84cdb..089e272 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -14,6 +14,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @Service @Transactional(readOnly = true) @@ -29,14 +31,19 @@ public void init() { //이 Service Bean이 생성된 이후에 검색어 자 saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 } - private void saveAllSubstring(List allDisplayName) { //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 + private void saveAllSubstring(List allDisplayName) { + ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 + for (String displayName : allDisplayName) { - redisSortedSetService.addToSortedSet(displayName + suffix); //완벽한 형태의 단어일 경우에는 *을 붙여 구분 + executorService.submit(() -> { //submit 메서드를 사용해서 병렬 처리할 작업 추가 + redisSortedSetService.addToSortedSet(displayName + suffix); - for (int i = displayName.length()-1; i > 0; --i) { //음절 단위로 잘라서 모든 Substring 구하기 - redisSortedSetService.addToSortedSet(displayName.substring(0, i)); //곧바로 redis에 저장 - } + for (int i = displayName.length(); i > 0; --i) { + redisSortedSetService.addToSortedSet(displayName.substring(0, i)); + } + }); } + executorService.shutdown(); //작업이 모두 완료되면 스레드풀을 종료 } public List autocorrect(String keyword) { //검색어 자동 완성 기능 관련 로직 @@ -48,12 +55,10 @@ public List autocorrect(String keyword) { //검색어 자동 완성 기 Set allValuesAfterIndexFromSortedSet = redisSortedSetService.findAllValuesAfterIndexFromSortedSet(index); //사용자 검색어 이후로 정렬된 Redis 데이터들 가져오기 - List autocorrectKeywords = allValuesAfterIndexFromSortedSet.stream() + return allValuesAfterIndexFromSortedSet.stream() .filter(value -> value.endsWith(suffix) && value.startsWith(keyword)) .map(value -> StringUtils.removeEnd(value, suffix)) .limit(maxSize) .toList(); //자동 완성을 통해 만들어진 최대 maxSize개의 키워드들 - - return autocorrectKeywords; } } diff --git a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java index 6ac24ec..e040ca2 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java @@ -1,8 +1,12 @@ package com.nainga.nainga.global.application; import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; + +import java.util.List; import java.util.Set; @Service From d1f81771a61240367b4bbf87af85ce38ac02c8c3 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 04:53:31 +0900 Subject: [PATCH 15/57] =?UTF-8?q?refactor=20:=20=EB=B3=91=EB=AA=A9=20?= =?UTF-8?q?=ED=98=84=EC=83=81=EC=9D=B4=20=EA=B1=B8=EB=A6=AC=EB=8D=98=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20158=EC=B4=88=EC=97=90=EC=84=9C=20?= =?UTF-8?q?0.009=EC=B4=88=EB=A1=9C=20=EA=B0=9C=EC=84=A0=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/nainga/domain/store/application/StoreService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 089e272..0fe2a48 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -1,8 +1,6 @@ package com.nainga.nainga.domain.store.application; import com.nainga.nainga.domain.store.dao.StoreRepository; -import com.nainga.nainga.domain.store.domain.Store; -import com.nainga.nainga.domain.storecertification.domain.StoreCertification; import com.nainga.nainga.global.application.RedisSortedSetService; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -11,7 +9,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; From 63b9b73307e5b84de99ad2efd26a4cf95913482c Mon Sep 17 00:00:00 2001 From: hoon Date: Sat, 17 Feb 2024 07:47:33 +0900 Subject: [PATCH 16/57] =?UTF-8?q?feat=20:=20redis=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/action-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/action-test.yml b/.github/workflows/action-test.yml index 899bd44..d5a19db 100644 --- a/.github/workflows/action-test.yml +++ b/.github/workflows/action-test.yml @@ -46,6 +46,9 @@ jobs: mysql user: 'test' mysql password: ${{ secrets.DB_PASSWORD }} + - name: Set up Redis + uses: shogo82148/actions-setup-redis@v1.33.0 + # github action 에서 Gradle dependency 캐시 사용 - name: Cache Gradle packages uses: actions/cache@v3 From a350f14e5892903ca6d34adae393271f5dceaa7b Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 14:55:02 +0900 Subject: [PATCH 17/57] =?UTF-8?q?fix=20:=20Redis=EC=97=90=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=A0=91=EA=B7=BC=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nainga/nainga/global/config/RedisConfig.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java index 6fc3399..ff41fb6 100644 --- a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java +++ b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java @@ -16,10 +16,13 @@ public class RedisConfig { //Redis 연결을 위한 기본 설정 @Value("${spring.data.redis.port}") private int port; + @Value("${spring.data.redis.password}") + private String password; + @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory( - new RedisStandaloneConfiguration(host, port) - ); + RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); + config.setPassword(password); + return new LettuceConnectionFactory(config); } } \ No newline at end of file From 84f016c79f9f71de09e16dd85573c08ab18896f3 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 15:36:09 +0900 Subject: [PATCH 18/57] =?UTF-8?q?fix=20:=20backend-submodule=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=98=EC=98=81=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/backend-submodule | 2 +- src/test/resources/backend-submodule | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule index 4e581de..bf26087 160000 --- a/src/main/resources/backend-submodule +++ b/src/main/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 4e581de872bb372c49ec83495262d8d04d7e8e76 +Subproject commit bf2608775a4bbddd2dbdd757a3c6de9113a32865 diff --git a/src/test/resources/backend-submodule b/src/test/resources/backend-submodule index 4e581de..bf26087 160000 --- a/src/test/resources/backend-submodule +++ b/src/test/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 4e581de872bb372c49ec83495262d8d04d7e8e76 +Subproject commit bf2608775a4bbddd2dbdd757a3c6de9113a32865 From 84ceafd547b3d395bcbef768eec20f9e8c34e0a7 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 20:04:47 +0900 Subject: [PATCH 19/57] =?UTF-8?q?feat=20:=20=EB=88=84=EB=9D=BD=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EB=8D=98=20=EA=B2=80=EC=83=89=EC=96=B4=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=99=84=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/application/StoreService.java | 2 +- .../store/application/StoreServiceTest.java | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 0fe2a48..6da94ae 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -28,7 +28,7 @@ public void init() { //이 Service Bean이 생성된 이후에 검색어 자 saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 } - private void saveAllSubstring(List allDisplayName) { + public void saveAllSubstring(List allDisplayName) { ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 for (String displayName : allDisplayName) { diff --git a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java new file mode 100644 index 0000000..549aad7 --- /dev/null +++ b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java @@ -0,0 +1,46 @@ +package com.nainga.nainga.domain.store.application; + +import com.nainga.nainga.global.application.RedisSortedSetService; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +class StoreServiceTest { + + @Autowired + StoreService storeService; + + @Autowired + RedisSortedSetService redisSortedSetService; + + @Test + public void autocorrect() throws Exception { //검색어 자동 완성 기능에 대한 테스트 + //given + //테스트를 실행시키는 환경에 따라 잘못된 결과가 나올 수 있으므로 테스트용 가게 이름 제일 앞에는 실제 Production DB에 존재하지 않는 이름인 *을 붙여 사용 + List allDisplayName = List.of("*김밥천국", "*김밥나라", "*김빱월드", "*김밥천지"); //List의 팩토리 메서드 사용 + System.out.println("allDisplayName = " + allDisplayName); + storeService.saveAllSubstring(allDisplayName); //검색어 자동 완성 기능을 위해 필요한 Substring들을 뽑아 Redis에 저장 + + //when + List resultByKim = storeService.autocorrect("*김"); //Redis 상에 사전순 정렬되어 있으므로 *김밥나라, *김밥천국, *김밥천지, *김빱월드 순으로 나옴 + List resultByKimBap = storeService.autocorrect("*김밥"); //*김밥나라, *김밥천국, *김밥천지가 나와야 함 + List resultByKimBapCheon = storeService.autocorrect("*김밥천"); //*김밥천국, *김밥천지가 나와야 함 + List resultByKimBapCheonGuk = storeService.autocorrect("*김밥천국"); //*김밥천국이 나와야 함 + + //then + assertThat(resultByKim).containsExactly("*김밥나라", "*김밥천국", "*김밥천지", "*김빱월드"); + assertThat(resultByKimBap).containsExactly("*김밥나라", "*김밥천국", "*김밥천지"); + assertThat(resultByKimBapCheon).containsExactly("*김밥천국", "*김밥천지"); + assertThat(resultByKimBapCheonGuk).containsExactly("*김밥천국"); + } +} \ No newline at end of file From 66570c512fa55700e2e62392c96f5e467ec69c52 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 20:07:35 +0900 Subject: [PATCH 20/57] =?UTF-8?q?feat=20:=20Redis=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=93=A4=EC=9D=B4=20=EC=9E=98=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=90=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=A0=84=EC=B2=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=A7=80=EC=9B=8C=EC=A3=BC=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/global/application/RedisSortedSetService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java index e040ca2..374f7f7 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java @@ -27,4 +27,8 @@ public Long findFromSortedSet(String value) { //Redis SortedSet에서 Value를 public Set findAllValuesAfterIndexFromSortedSet(Long index) { return redisTemplate.opsForZSet().range(key, index, index + 200); //전체를 다 불러오기 보다는 200개 정도만 가져와도 자동 완성을 구현하는 데 무리가 없으므로 200개로 rough하게 설정 } + + public void removeAllOfSortedSet() { + redisTemplate.opsForZSet().removeRange(key, 0, -1); + } } From 4c831de015a3feb28ace572697f583c261e99ccd Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 20:37:49 +0900 Subject: [PATCH 21/57] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=8F=99=EA=B8=B0=20?= =?UTF-8?q?=EB=A9=80=ED=8B=B0=20=EC=8A=A4=EB=A0=88=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=8C=80=ED=95=B4=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=EC=9D=B4=20=EB=B0=94=EB=A1=9C=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EC=9D=B4=EC=8A=88=EA=B0=80=20=EC=9E=88?= =?UTF-8?q?=EC=96=B4=EC=84=9C=20slepp=20=EC=B6=94=EA=B0=80=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/nainga/domain/store/application/StoreService.java | 1 + .../nainga/domain/store/application/StoreServiceTest.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 6da94ae..8b9a815 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -25,6 +25,7 @@ public class StoreService { @PostConstruct public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) + redisSortedSetService.removeAllOfSortedSet(); saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 } diff --git a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java index 549aad7..7a10fdc 100644 --- a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java +++ b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java @@ -30,6 +30,7 @@ public void autocorrect() throws Exception { //검색어 자동 완성 기능 List allDisplayName = List.of("*김밥천국", "*김밥나라", "*김빱월드", "*김밥천지"); //List의 팩토리 메서드 사용 System.out.println("allDisplayName = " + allDisplayName); storeService.saveAllSubstring(allDisplayName); //검색어 자동 완성 기능을 위해 필요한 Substring들을 뽑아 Redis에 저장 + Thread.sleep(1000); //직전에 실행시킨 saveAllSubstring이 멀티 스레드 기반 병렬 처리로 구현되어 있어서 바로 다음 검증 로직으로 넘어가버리면 아직 데이터가 전부 안들어가서 간헐적으로 실패하는 오류가 있음 //when List resultByKim = storeService.autocorrect("*김"); //Redis 상에 사전순 정렬되어 있으므로 *김밥나라, *김밥천국, *김밥천지, *김빱월드 순으로 나옴 @@ -42,5 +43,7 @@ public void autocorrect() throws Exception { //검색어 자동 완성 기능 assertThat(resultByKimBap).containsExactly("*김밥나라", "*김밥천국", "*김밥천지"); assertThat(resultByKimBapCheon).containsExactly("*김밥천국", "*김밥천지"); assertThat(resultByKimBapCheonGuk).containsExactly("*김밥천국"); + + redisSortedSetService.removeAllOfSortedSet(); //Test 이후 Redis가 Roll back 될 수 있도록 모든 데이터를 제거 } } \ No newline at end of file From b1772e7cd8f2a75ee3ab041d9d0e14fcc291155e Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 21:15:48 +0900 Subject: [PATCH 22/57] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A0=9C=EB=B3=B4=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=B6=94=EC=83=81=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=9D=B8=20Report=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/domain/Report.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/domain/Report.java diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/Report.java b/src/main/java/com/nainga/nainga/domain/report/domain/Report.java new file mode 100644 index 0000000..71c03f7 --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/domain/Report.java @@ -0,0 +1,16 @@ +package com.nainga.nainga.domain.report.domain; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name = "dtype") +public abstract class Report { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "report_id") + private Long id; +} From 037cd1648a3b0e935e9857ad0393bfb376e829d6 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 22:10:10 +0900 Subject: [PATCH 23/57] =?UTF-8?q?feat=20:=20=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=EA=B0=80=EA=B2=8C=EB=A5=BC=20=EC=9A=94=EC=B2=AD=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20NewStoreReport=20Entity=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/domain/NewStoreReport.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/domain/NewStoreReport.java diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/NewStoreReport.java b/src/main/java/com/nainga/nainga/domain/report/domain/NewStoreReport.java new file mode 100644 index 0000000..af19f01 --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/domain/NewStoreReport.java @@ -0,0 +1,21 @@ +package com.nainga.nainga.domain.report.domain; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@Builder +@DiscriminatorValue("new") +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class NewStoreReport extends Report { + private String storeName; //가게 이름 + private String formattedAddress; //가게 주소 + @ElementCollection + private List certifications; //가게가 가지고 있는 인증제 이름 리스트 +} From 9c89ad55ea90aa8aa956b25cdd3bf1f2e0e7de8a Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 22:14:49 +0900 Subject: [PATCH 24/57] =?UTF-8?q?feat=20:=20=EB=93=B1=EB=A1=9D=EB=90=9C=20?= =?UTF-8?q?=EA=B0=80=EA=B2=8C=EB=A5=BC=20=EC=88=98=EC=A0=95=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20FixStoreRepo?= =?UTF-8?q?rt=20Entity=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/domain/FixStoreReport.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java b/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java new file mode 100644 index 0000000..29c5e67 --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java @@ -0,0 +1,19 @@ +package com.nainga.nainga.domain.report.domain; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@Builder +@DiscriminatorValue("fix") +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class FixStoreReport extends Report { + private Long storeId; //가게 id + private String contents; //신고 내용 +} From 43e0fefb79dfe6ce817246e45724f88d19664ce7 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 22:18:17 +0900 Subject: [PATCH 25/57] =?UTF-8?q?feat=20:=20=EB=93=B1=EB=A1=9D=EB=90=9C=20?= =?UTF-8?q?=EA=B0=80=EA=B2=8C=EB=A5=BC=20=EC=82=AD=EC=A0=9C=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20DelStoreRepo?= =?UTF-8?q?rt=20Entity=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/domain/DelStoreReport.java | 16 ++++++++++++++++ .../domain/report/domain/FixStoreReport.java | 3 --- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java b/src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java new file mode 100644 index 0000000..1bb1993 --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java @@ -0,0 +1,16 @@ +package com.nainga.nainga.domain.report.domain; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import lombok.*; + +@Entity +@Getter +@Builder +@DiscriminatorValue("del") +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DelStoreReport extends Report { + private Long storeId; //가게 id + private String contents; //신고 내용 +} diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java b/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java index 29c5e67..46c3baf 100644 --- a/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java +++ b/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java @@ -1,12 +1,9 @@ package com.nainga.nainga.domain.report.domain; import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import lombok.*; -import java.util.List; - @Entity @Getter @Builder From b64cc0c5e5f08ac4feab7dbfb09dac51adac509a Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 23:01:49 +0900 Subject: [PATCH 26/57] =?UTF-8?q?feat=20:=20ReportRepository=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/dao/ReportRepository.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java diff --git a/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java b/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java new file mode 100644 index 0000000..f579be5 --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java @@ -0,0 +1,32 @@ +package com.nainga.nainga.domain.report.dao; + +import com.nainga.nainga.domain.report.domain.Report; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class ReportRepository { + + private final EntityManager em; + + public void save(Report report) { + em.persist(report); + } + + public Optional findById(Long id) { + List result = em.createQuery("select r from Report r where r.id = :id", Report.class) + .setParameter("id", id) + .getResultList(); + return result.stream().findAny(); + } + + public List findAll() { + return em.createQuery("select r from Report r", Report.class) + .getResultList(); + } +} From cabc31a740c1a47ca908c5762b15d8d0f54613dd Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 23:16:15 +0900 Subject: [PATCH 27/57] =?UTF-8?q?feat=20:=20ReportService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/application/ReportService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/application/ReportService.java diff --git a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java new file mode 100644 index 0000000..73c0252 --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java @@ -0,0 +1,18 @@ +package com.nainga.nainga.domain.report.application; + +import com.nainga.nainga.domain.report.dao.ReportRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ReportService { + private final ReportRepository reportRepository; + + @Transactional + public Long saveReport() { + + } +} From d9d194b444d72b28d7f60061acca0a7fc0324adf Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 23:22:31 +0900 Subject: [PATCH 28/57] =?UTF-8?q?feat=20:=20SaveNewStoreReportRequest=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/dto/SaveNewStoreReportRequest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java new file mode 100644 index 0000000..efd6dcb --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -0,0 +1,12 @@ +package com.nainga.nainga.domain.report.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class SaveNewStoreReportRequest { + private String storeName; //가게 이름 + private String formattedAddress; //가게 주소 + private List certifications; //가게가 가지고 있는 인증제 이름 리스트 +} From 49ccb30ff5e8ed7cd51cd72b95850c259af4c129 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 23:31:52 +0900 Subject: [PATCH 29/57] =?UTF-8?q?feat=20:=20SaveNewStoreReportRequest=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/dto/SaveNewStoreReportRequest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java index efd6dcb..933f40f 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -1,12 +1,16 @@ package com.nainga.nainga.domain.report.dto; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.List; @Data public class SaveNewStoreReportRequest { + @NotNull private String storeName; //가게 이름 + @NotNull private String formattedAddress; //가게 주소 + @NotNull private List certifications; //가게가 가지고 있는 인증제 이름 리스트 -} +} \ No newline at end of file From f1e0b135944429e53e99e8ee3aa5c332feebe295 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 23:37:32 +0900 Subject: [PATCH 30/57] =?UTF-8?q?feat=20:=20SaveSpecificStoreReportRequest?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...toreReport.java => DelSpecificStoreReport.java} | 2 +- ...toreReport.java => FixSpecificStoreReport.java} | 2 +- .../report/dto/SaveNewStoreReportRequest.java | 2 ++ .../report/dto/SaveSpecificStoreReportRequest.java | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) rename src/main/java/com/nainga/nainga/domain/report/domain/{DelStoreReport.java => DelSpecificStoreReport.java} (87%) rename src/main/java/com/nainga/nainga/domain/report/domain/{FixStoreReport.java => FixSpecificStoreReport.java} (87%) create mode 100644 src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java b/src/main/java/com/nainga/nainga/domain/report/domain/DelSpecificStoreReport.java similarity index 87% rename from src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java rename to src/main/java/com/nainga/nainga/domain/report/domain/DelSpecificStoreReport.java index 1bb1993..a2649b5 100644 --- a/src/main/java/com/nainga/nainga/domain/report/domain/DelStoreReport.java +++ b/src/main/java/com/nainga/nainga/domain/report/domain/DelSpecificStoreReport.java @@ -10,7 +10,7 @@ @DiscriminatorValue("del") @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class DelStoreReport extends Report { +public class DelSpecificStoreReport extends Report { private Long storeId; //가게 id private String contents; //신고 내용 } diff --git a/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java b/src/main/java/com/nainga/nainga/domain/report/domain/FixSpecificStoreReport.java similarity index 87% rename from src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java rename to src/main/java/com/nainga/nainga/domain/report/domain/FixSpecificStoreReport.java index 46c3baf..19d4b08 100644 --- a/src/main/java/com/nainga/nainga/domain/report/domain/FixStoreReport.java +++ b/src/main/java/com/nainga/nainga/domain/report/domain/FixSpecificStoreReport.java @@ -10,7 +10,7 @@ @DiscriminatorValue("fix") @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class FixStoreReport extends Report { +public class FixSpecificStoreReport extends Report { private Long storeId; //가게 id private String contents; //신고 내용 } diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java index 933f40f..2c51150 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -7,6 +7,8 @@ @Data public class SaveNewStoreReportRequest { + @NotNull + private String dtype; //Report 종류를 구분하기 위한 type @NotNull private String storeName; //가게 이름 @NotNull diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java new file mode 100644 index 0000000..6706bed --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java @@ -0,0 +1,14 @@ +package com.nainga.nainga.domain.report.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class SaveSpecificStoreReportRequest { + @NotNull + private String dtype; //Report 종류를 구분하기 위한 type + @NotNull + private Long storeId; //가게 id + @NotNull + private String contents; //신고 내용 +} From 20e1c04aee779f7c07efd5d3909d0ce242cca6a1 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sat, 17 Feb 2024 23:46:39 +0900 Subject: [PATCH 31/57] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EC=8B=A0=EA=B7=9C=20=EA=B0=80=EA=B2=8C=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=9A=94=EC=B2=AD=EC=9D=B4=20=EB=93=A4=EC=96=B4?= =?UTF-8?q?=EC=98=A4=EB=A9=B4=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/application/ReportService.java | 10 +++++++++- .../nainga/domain/report/dao/ReportRepository.java | 3 ++- .../domain/report/dto/SaveNewStoreReportRequest.java | 2 -- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java index 73c0252..71b8369 100644 --- a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java +++ b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java @@ -1,6 +1,8 @@ package com.nainga.nainga.domain.report.application; import com.nainga.nainga.domain.report.dao.ReportRepository; +import com.nainga.nainga.domain.report.domain.NewStoreReport; +import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,7 +14,13 @@ public class ReportService { private final ReportRepository reportRepository; @Transactional - public Long saveReport() { + public Long saveNewStoreReport(SaveNewStoreReportRequest saveNewStoreReportRequest) { //사용자의 신규 가게 등록 요청 + NewStoreReport newStoreReport = NewStoreReport.builder() + .storeName(saveNewStoreReportRequest.getStoreName()) + .formattedAddress(saveNewStoreReportRequest.getFormattedAddress()) + .certifications(saveNewStoreReportRequest.getCertifications()) + .build(); + return reportRepository.save(newStoreReport); } } diff --git a/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java b/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java index f579be5..5b6c398 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java +++ b/src/main/java/com/nainga/nainga/domain/report/dao/ReportRepository.java @@ -14,8 +14,9 @@ public class ReportRepository { private final EntityManager em; - public void save(Report report) { + public Long save(Report report) { em.persist(report); + return report.getId(); } public Optional findById(Long id) { diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java index 2c51150..933f40f 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -7,8 +7,6 @@ @Data public class SaveNewStoreReportRequest { - @NotNull - private String dtype; //Report 종류를 구분하기 위한 type @NotNull private String storeName; //가게 이름 @NotNull From 66658315769a09a90529bcbbdc32702185095e3e Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 00:01:56 +0900 Subject: [PATCH 32/57] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=ED=8A=B9=EC=A0=95=20=EA=B0=80=EA=B2=8C=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=EC=9D=B4=20=EB=93=A4=EC=96=B4=EC=98=A4?= =?UTF-8?q?=EB=A9=B4=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/application/ReportService.java | 26 +++++++++++++++++++ .../global/exception/ReportErrorCode.java | 17 ++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java diff --git a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java index 71b8369..3ebf749 100644 --- a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java +++ b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java @@ -1,8 +1,13 @@ package com.nainga.nainga.domain.report.application; import com.nainga.nainga.domain.report.dao.ReportRepository; +import com.nainga.nainga.domain.report.domain.DelSpecificStoreReport; +import com.nainga.nainga.domain.report.domain.FixSpecificStoreReport; import com.nainga.nainga.domain.report.domain.NewStoreReport; import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; +import com.nainga.nainga.domain.report.dto.SaveSpecificStoreReportRequest; +import com.nainga.nainga.global.exception.GlobalException; +import com.nainga.nainga.global.exception.ReportErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,4 +28,25 @@ public Long saveNewStoreReport(SaveNewStoreReportRequest saveNewStoreReportReque return reportRepository.save(newStoreReport); } + + @Transactional + public Long saveSpecificStoreReport(SaveSpecificStoreReportRequest saveSpecificStoreReportRequest) throws GlobalException { //사용자의 특정 가게에 대한 수정, 삭제 요청 + if (saveSpecificStoreReportRequest.getDtype().equals("fix")) { + FixSpecificStoreReport fixSpecificStoreReport = FixSpecificStoreReport.builder() + .storeId(saveSpecificStoreReportRequest.getStoreId()) + .contents(saveSpecificStoreReportRequest.getContents()) + .build(); + + return reportRepository.save(fixSpecificStoreReport); + } else if (saveSpecificStoreReportRequest.getDtype().equals("del")) { + DelSpecificStoreReport delSpecificStoreReport = DelSpecificStoreReport.builder() + .storeId(saveSpecificStoreReportRequest.getStoreId()) + .contents(saveSpecificStoreReportRequest.getContents()) + .build(); + + return reportRepository.save(delSpecificStoreReport); + } else { + throw new GlobalException(ReportErrorCode.INVALID_DTYPE); //잘못된 DTYPE이 들어왔을 경우에 Custom GlobalException 처리 + } + } } diff --git a/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java b/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java new file mode 100644 index 0000000..5f95163 --- /dev/null +++ b/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java @@ -0,0 +1,17 @@ +package com.nainga.nainga.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +/* + Report와 관련된 도메인에서 발생할 수 있는 ErrorCode를 정의합니다. + */ +@Getter +@RequiredArgsConstructor +public enum ReportErrorCode implements ErrorCode { + INVALID_DTYPE(HttpStatus.NOT_FOUND, "There is a wrong dtype. You can only use a dtype such as fix or del."); + + private final HttpStatus httpStatus; + private final String message; +} From e4645b6a10ad7b9d224159a676bc948353f98af7 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 00:12:52 +0900 Subject: [PATCH 33/57] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EC=8B=A0=EA=B7=9C=20=EA=B0=80=EA=B2=8C=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?API=20=EA=B0=9C=EB=B0=9C=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/api/ReportApi.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java diff --git a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java new file mode 100644 index 0000000..3115ccd --- /dev/null +++ b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java @@ -0,0 +1,32 @@ +package com.nainga.nainga.domain.report.api; + +import com.nainga.nainga.domain.report.application.ReportService; +import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; +import com.nainga.nainga.global.util.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class ReportApi { + private final ReportService reportService; + + //사용자의 신규 가게 등록 요청에 대한 제보를 저장 + @Tag(name = "[New] 사용자 제보") + @Operation(summary = "사용자의 신규 가게 등록 요청에 대한 제보를 서버에 저장", description = "사용자의 신규 가게 등록 요청에 대한 제보를 서버에 저장합니다.

" + + "[Request Body]
" + + "storeName: 등록 요청하는 가게 이름
" + + "formattedAddress: 등록 요청하는 가게 주소
" + + "certifications: 가게가 가지고 있는 인증제 리스트들
" + + "[Response Body]
" + + "등록된 reportId
") + @PostMapping("api/report/newStore/v1") + public Result saveNewStoreReport(@RequestBody SaveNewStoreReportRequest saveNewStoreReportRequest) { + Long reportId = reportService.saveNewStoreReport(saveNewStoreReportRequest); + return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reportId); + } +} From ddc9fb94909d4900d17be482051bb8d5db2dce7a Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 00:21:59 +0900 Subject: [PATCH 34/57] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=ED=8A=B9=EC=A0=95=20=EA=B0=80=EA=B2=8C=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=ED=98=B9=EC=9D=80=20=EC=82=AD=EC=A0=9C=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20API=20=EA=B0=9C=EB=B0=9C=20(#1?= =?UTF-8?q?05)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/api/ReportApi.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java index 3115ccd..2fe7c4c 100644 --- a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java +++ b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java @@ -2,6 +2,7 @@ import com.nainga.nainga.domain.report.application.ReportService; import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; +import com.nainga.nainga.domain.report.dto.SaveSpecificStoreReportRequest; import com.nainga.nainga.global.util.Result; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -21,7 +22,7 @@ public class ReportApi { "[Request Body]
" + "storeName: 등록 요청하는 가게 이름
" + "formattedAddress: 등록 요청하는 가게 주소
" + - "certifications: 가게가 가지고 있는 인증제 리스트들
" + + "certifications: 가게가 가지고 있는 인증제들의 이름을 담은 리스트. 착한가격업소, 모범음식점, 안심식당이 아닌 경우 예외 발생
" + "[Response Body]
" + "등록된 reportId
") @PostMapping("api/report/newStore/v1") @@ -29,4 +30,19 @@ public Result saveNewStoreReport(@RequestBody SaveNewStoreReportRequest sa Long reportId = reportService.saveNewStoreReport(saveNewStoreReportRequest); return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reportId); } + + //사용자의 특정 가게에 대한 수정, 삭제 요청 정보를 저장 + @Tag(name = "[New] 사용자 제보") + @Operation(summary = "사용자의 특정 가게에 대한 정보 수정 혹은 삭제 요청에 대한 제보를 서버에 저장", description = "사용자의 특정 가게에 대한 정보 수정 혹은 삭제 요청에 대한 제보를 서버에 저장합니다.

" + + "[Request Body]
" + + "dtype: 제보 종류를 구분하기 위한 값. fix는 수정 요청, del은 삭제 요청. fix나 del이 아닌 경우 예외 발생
" + + "storeId: 수정 혹은 삭제를 요청하는 가게 id
" + + "contents: 제보 내용
" + + "[Response Body]
" + + "등록된 reportId
") + @PostMapping("api/report/specificStore/v1") + public Result saveSpecificStoreReport(@RequestBody SaveSpecificStoreReportRequest saveSpecificStoreReportRequest) { + Long reportId = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest); + return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reportId); + } } From 7374fc139f594b58dc45307b61c9695dda976b4c Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 00:28:36 +0900 Subject: [PATCH 35/57] =?UTF-8?q?feat=20:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=EC=A0=9C=20=EC=A0=95=EB=B3=B4=EA=B0=80=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EC=99=94=EC=9D=84=20=EB=95=8C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/application/ReportService.java | 11 ++++++++++- .../nainga/global/exception/ReportErrorCode.java | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java index 3ebf749..7735163 100644 --- a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java +++ b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java @@ -12,6 +12,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -19,7 +21,14 @@ public class ReportService { private final ReportRepository reportRepository; @Transactional - public Long saveNewStoreReport(SaveNewStoreReportRequest saveNewStoreReportRequest) { //사용자의 신규 가게 등록 요청 + public Long saveNewStoreReport(SaveNewStoreReportRequest saveNewStoreReportRequest) throws GlobalException { //사용자의 신규 가게 등록 요청 + List certificationList = List.of("착한가격업소", "모범음식점", "안심식당"); //현재 App에서 사용중인 Certification 목록 + for (String certification : saveNewStoreReportRequest.getCertifications()) { + if (!certificationList.contains(certification)) { + throw new GlobalException(ReportErrorCode.INVALID_CERTIFICATION); //잘못된 인증제 값이 들어온 것이므로 예외 발생 + } + } + NewStoreReport newStoreReport = NewStoreReport.builder() .storeName(saveNewStoreReportRequest.getStoreName()) .formattedAddress(saveNewStoreReportRequest.getFormattedAddress()) diff --git a/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java b/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java index 5f95163..fb04d82 100644 --- a/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java +++ b/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java @@ -10,7 +10,8 @@ @Getter @RequiredArgsConstructor public enum ReportErrorCode implements ErrorCode { - INVALID_DTYPE(HttpStatus.NOT_FOUND, "There is a wrong dtype. You can only use a dtype such as fix or del."); + INVALID_DTYPE(HttpStatus.NOT_FOUND, "There is a wrong dtype. You can only use a dtype such as fix or del."), + INVALID_CERTIFICATION(HttpStatus.NOT_FOUND, "There is a wrong certification. You can only use certifications such as 착한가격업소, 모범음식점, 안심식당."); private final HttpStatus httpStatus; private final String message; From bce3b7c308994260e9bcdfb19a9b996a43988db1 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 00:58:50 +0900 Subject: [PATCH 36/57] =?UTF-8?q?fix=20:=20Springdoc=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=AC=B8=EC=A0=9C=EA=B0=80=20?= =?UTF-8?q?=EC=9E=88=EC=96=B4=EC=84=9C=20=EC=B5=9C=EC=8B=A0=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=9C=BC=EB=A1=9C=20update=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9b0b7e4..fe2a6c0 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ dependencies { //spring doc 추가 dependencies { - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' } //Redis를 사용하기 위해 추가 From 7ed574df7957a26487ecb01e156282685835b54b Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 01:10:57 +0900 Subject: [PATCH 37/57] =?UTF-8?q?feat=20:=20=ED=8E=B8=EC=9D=98=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20Springdoc=20Request=20Body=EC=97=90=20Defa?= =?UTF-8?q?ult=20value=20=EC=84=A4=EC=A0=95=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/dto/SaveNewStoreReportRequest.java | 4 ++++ .../domain/report/dto/SaveSpecificStoreReportRequest.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java index 933f40f..64fc8be 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -1,5 +1,6 @@ package com.nainga.nainga.domain.report.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -8,9 +9,12 @@ @Data public class SaveNewStoreReportRequest { @NotNull + @Schema(defaultValue = "가게 이름", description = "새로 등록하고자 하는 가게 이름") private String storeName; //가게 이름 @NotNull + @Schema(defaultValue = "주소", description = "새로 등록하고자 하는 가게 주소") private String formattedAddress; //가게 주소 @NotNull + @Schema(defaultValue = "[\"착한가격업소\", \"모범음식점\", \"안심식당\"]", description = "새로 등록할 가게가 가지고 있는 인증제들의 이름을 담은 리스트. 착한가격업소, 모범음식점, 안심식당이 아닌 경우 예외 발생") private List certifications; //가게가 가지고 있는 인증제 이름 리스트 } \ No newline at end of file diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java index 6706bed..49ff15d 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java @@ -1,14 +1,18 @@ package com.nainga.nainga.domain.report.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class SaveSpecificStoreReportRequest { @NotNull + @Schema(defaultValue = "fix/del", description = "제보 종류를 구분하기 위한 값. fix는 수정 요청, del은 삭제 요청.") private String dtype; //Report 종류를 구분하기 위한 type @NotNull + @Schema(defaultValue = "0", description = "수정 혹은 삭제를 요청하는 가게 id") private Long storeId; //가게 id @NotNull + @Schema(defaultValue = "제보 내용", description = "제보 내용") private String contents; //신고 내용 } From 148e5145b4ab5b980bb16b074a311cde5dbb7eee Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 01:25:03 +0900 Subject: [PATCH 38/57] =?UTF-8?q?feat=20:=20Request=20Body=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=EB=A5=BC=20=EC=89=BD=EA=B2=8C=20=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20@Valid=20=EC=A0=81=EC=9A=A9=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ .../com/nainga/nainga/domain/report/api/ReportApi.java | 5 +++-- .../domain/report/dto/SaveNewStoreReportRequest.java | 7 ++++--- .../domain/report/dto/SaveSpecificStoreReportRequest.java | 5 +++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index fe2a6c0..62e3f2f 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,9 @@ dependencies { //Redis를 사용하기 위해 추가 implementation 'org.springframework.boot:spring-boot-starter-data-redis' + //Validation을 위해 추가 Spring 버전 업되면서 web 의존성안에 있던 constraints packeage가 아예 모듈로 빠졌다. + implementation 'org.springframework.boot:spring-boot-starter-validation' + } test { diff --git a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java index 2fe7c4c..7ca3e43 100644 --- a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java +++ b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java @@ -6,6 +6,7 @@ import com.nainga.nainga.global.util.Result; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -26,7 +27,7 @@ public class ReportApi { "[Response Body]
" + "등록된 reportId
") @PostMapping("api/report/newStore/v1") - public Result saveNewStoreReport(@RequestBody SaveNewStoreReportRequest saveNewStoreReportRequest) { + public Result saveNewStoreReport(@Valid @RequestBody SaveNewStoreReportRequest saveNewStoreReportRequest) { Long reportId = reportService.saveNewStoreReport(saveNewStoreReportRequest); return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reportId); } @@ -41,7 +42,7 @@ public Result saveNewStoreReport(@RequestBody SaveNewStoreReportRequest sa "[Response Body]
" + "등록된 reportId
") @PostMapping("api/report/specificStore/v1") - public Result saveSpecificStoreReport(@RequestBody SaveSpecificStoreReportRequest saveSpecificStoreReportRequest) { + public Result saveSpecificStoreReport(@Valid @RequestBody SaveSpecificStoreReportRequest saveSpecificStoreReportRequest) { Long reportId = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest); return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reportId); } diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java index 64fc8be..131c85b 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -1,6 +1,7 @@ package com.nainga.nainga.domain.report.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -8,13 +9,13 @@ @Data public class SaveNewStoreReportRequest { - @NotNull + @NotEmpty @Schema(defaultValue = "가게 이름", description = "새로 등록하고자 하는 가게 이름") private String storeName; //가게 이름 - @NotNull + @NotEmpty @Schema(defaultValue = "주소", description = "새로 등록하고자 하는 가게 주소") private String formattedAddress; //가게 주소 - @NotNull + @NotEmpty @Schema(defaultValue = "[\"착한가격업소\", \"모범음식점\", \"안심식당\"]", description = "새로 등록할 가게가 가지고 있는 인증제들의 이름을 담은 리스트. 착한가격업소, 모범음식점, 안심식당이 아닌 경우 예외 발생") private List certifications; //가게가 가지고 있는 인증제 이름 리스트 } \ No newline at end of file diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java index 49ff15d..574092e 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java @@ -1,18 +1,19 @@ package com.nainga.nainga.domain.report.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class SaveSpecificStoreReportRequest { - @NotNull + @NotEmpty @Schema(defaultValue = "fix/del", description = "제보 종류를 구분하기 위한 값. fix는 수정 요청, del은 삭제 요청.") private String dtype; //Report 종류를 구분하기 위한 type @NotNull @Schema(defaultValue = "0", description = "수정 혹은 삭제를 요청하는 가게 id") private Long storeId; //가게 id - @NotNull + @NotEmpty @Schema(defaultValue = "제보 내용", description = "제보 내용") private String contents; //신고 내용 } From 9870ce1886befbf2f978a028eb15093d64ab313c Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:03:04 +0900 Subject: [PATCH 39/57] =?UTF-8?q?feat=20:=20saveNewStoreReport()=EC=97=90?= =?UTF-8?q?=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/dto/SaveNewStoreReportRequest.java | 6 ++ .../dto/SaveSpecificStoreReportRequest.java | 6 ++ .../report/application/ReportServiceTest.java | 57 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java index 131c85b..64ae7ee 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveNewStoreReportRequest.java @@ -18,4 +18,10 @@ public class SaveNewStoreReportRequest { @NotEmpty @Schema(defaultValue = "[\"착한가격업소\", \"모범음식점\", \"안심식당\"]", description = "새로 등록할 가게가 가지고 있는 인증제들의 이름을 담은 리스트. 착한가격업소, 모범음식점, 안심식당이 아닌 경우 예외 발생") private List certifications; //가게가 가지고 있는 인증제 이름 리스트 + + public SaveNewStoreReportRequest(String storeName, String formattedAddress, List certifications) { + this.storeName = storeName; + this.formattedAddress = formattedAddress; + this.certifications = certifications; + } } \ No newline at end of file diff --git a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java index 574092e..9f25110 100644 --- a/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java +++ b/src/main/java/com/nainga/nainga/domain/report/dto/SaveSpecificStoreReportRequest.java @@ -16,4 +16,10 @@ public class SaveSpecificStoreReportRequest { @NotEmpty @Schema(defaultValue = "제보 내용", description = "제보 내용") private String contents; //신고 내용 + + public SaveSpecificStoreReportRequest(String dtype, Long storeId, String contents) { + this.dtype = dtype; + this.storeId = storeId; + this.contents = contents; + } } diff --git a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java new file mode 100644 index 0000000..6e644aa --- /dev/null +++ b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java @@ -0,0 +1,57 @@ +package com.nainga.nainga.domain.report.application; + +import com.nainga.nainga.domain.report.dao.ReportRepository; +import com.nainga.nainga.domain.report.domain.NewStoreReport; +import com.nainga.nainga.domain.report.domain.Report; +import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; +import com.nainga.nainga.global.exception.GlobalException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +class ReportServiceTest { + @Autowired + private ReportRepository reportRepository; + + @Autowired + private ReportService reportService; + + @Test + public void saveNewStoreReport() throws Exception { + //given + SaveNewStoreReportRequest saveNewStoreReportRequest1 = new SaveNewStoreReportRequest("가게1", "주소1", List.of("착한가격업소", "모범음식점")); //정상적인 테스트 케이스 + SaveNewStoreReportRequest saveNewStoreReportRequest2 = new SaveNewStoreReportRequest("가게2", "주소2", List.of("착한가격업")); //인증제 이름이 잘못되었을 때 + + //when + reportService.saveNewStoreReport(saveNewStoreReportRequest1); + List reports = reportRepository.findAll(); + NewStoreReport report = (NewStoreReport) reports.stream().findAny().get(); + + //then + assertArrayEquals(report.getCertifications().toArray(), saveNewStoreReportRequest1.getCertifications().toArray()); + assertThat(report.getStoreName()).isEqualTo(saveNewStoreReportRequest1.getStoreName()); + assertThat(report.getFormattedAddress()).isEqualTo(saveNewStoreReportRequest1.getFormattedAddress()); + assertThatThrownBy(() -> reportService.saveNewStoreReport(saveNewStoreReportRequest2)) //잘못된 인증제라서 예외가 터져야함 + .isInstanceOf(GlobalException.class); + } + + @Test + public void saveSpecificStoreReport() throws Exception { + //given + + //when + + //then + } + +} \ No newline at end of file From d5210357f28b727c770d4d8ff7af1b23669c0a61 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:13:22 +0900 Subject: [PATCH 40/57] =?UTF-8?q?feat=20:=20saveSpecificStoreReport()?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/application/ReportServiceTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java index 6e644aa..a71da79 100644 --- a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java +++ b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java @@ -1,9 +1,12 @@ package com.nainga.nainga.domain.report.application; import com.nainga.nainga.domain.report.dao.ReportRepository; +import com.nainga.nainga.domain.report.domain.DelSpecificStoreReport; +import com.nainga.nainga.domain.report.domain.FixSpecificStoreReport; import com.nainga.nainga.domain.report.domain.NewStoreReport; import com.nainga.nainga.domain.report.domain.Report; import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; +import com.nainga.nainga.domain.report.dto.SaveSpecificStoreReportRequest; import com.nainga.nainga.global.exception.GlobalException; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -48,10 +51,22 @@ public void saveNewStoreReport() throws Exception { @Test public void saveSpecificStoreReport() throws Exception { //given + SaveSpecificStoreReportRequest saveSpecificStoreReportRequest1 = new SaveSpecificStoreReportRequest("del", 123L, "내용1"); //정상적인 테스트 케이스 + SaveSpecificStoreReportRequest saveSpecificStoreReportRequest2 = new SaveSpecificStoreReportRequest("fix", 1234L, "내용2"); //정상적인 테스트 케이스 + SaveSpecificStoreReportRequest saveSpecificStoreReportRequest3 = new SaveSpecificStoreReportRequest("xxx", 12345L, "내용3"); //잘못된 dtype //when + reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest1); + reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest2); + List reports = reportRepository.findAll(); //then + DelSpecificStoreReport report1 = (DelSpecificStoreReport) reports.get(0); + FixSpecificStoreReport report2 = (FixSpecificStoreReport) reports.get(1); + assertThat(report1.getStoreId()).isEqualTo(saveSpecificStoreReportRequest1.getStoreId()); + assertThat(report2.getContents()).isEqualTo(saveSpecificStoreReportRequest2.getContents()); + assertThatThrownBy(() -> reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest3)) //잘못된 dtype이라서 예외가 터져야함 + .isInstanceOf(GlobalException.class); } } \ No newline at end of file From ff74cab08058fdb14d05aba29d1dadd44dcabdf1 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:19:00 +0900 Subject: [PATCH 41/57] =?UTF-8?q?feat=20:=20reportId=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A7=80=EA=B3=A0=20DB=EC=97=90=EC=84=9C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/report/application/ReportService.java | 11 +++++++++++ .../nainga/global/exception/ReportErrorCode.java | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java index 7735163..fd0e7e1 100644 --- a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java +++ b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java @@ -4,6 +4,7 @@ import com.nainga.nainga.domain.report.domain.DelSpecificStoreReport; import com.nainga.nainga.domain.report.domain.FixSpecificStoreReport; import com.nainga.nainga.domain.report.domain.NewStoreReport; +import com.nainga.nainga.domain.report.domain.Report; import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; import com.nainga.nainga.domain.report.dto.SaveSpecificStoreReportRequest; import com.nainga.nainga.global.exception.GlobalException; @@ -13,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; @Service @Transactional(readOnly = true) @@ -58,4 +60,13 @@ public Long saveSpecificStoreReport(SaveSpecificStoreReportRequest saveSpecificS throw new GlobalException(ReportErrorCode.INVALID_DTYPE); //잘못된 DTYPE이 들어왔을 경우에 Custom GlobalException 처리 } } + + public Report findById(Long id) throws GlobalException { //reportId를 가지고 report를 DB에서 조회하는 로직 + Optional report = reportRepository.findById(id); + if (report.isEmpty()) { + throw new GlobalException(ReportErrorCode.INVALID_REPORT_ID); //잘못된 reportId로 검색하는 경우에 Custom GlobalException 처리 + } else { + return report.get(); + } + } } diff --git a/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java b/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java index fb04d82..b2603b7 100644 --- a/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java +++ b/src/main/java/com/nainga/nainga/global/exception/ReportErrorCode.java @@ -11,7 +11,8 @@ @RequiredArgsConstructor public enum ReportErrorCode implements ErrorCode { INVALID_DTYPE(HttpStatus.NOT_FOUND, "There is a wrong dtype. You can only use a dtype such as fix or del."), - INVALID_CERTIFICATION(HttpStatus.NOT_FOUND, "There is a wrong certification. You can only use certifications such as 착한가격업소, 모범음식점, 안심식당."); + INVALID_CERTIFICATION(HttpStatus.NOT_FOUND, "There is a wrong certification. You can only use certifications such as 착한가격업소, 모범음식점, 안심식당."), + INVALID_REPORT_ID(HttpStatus.NOT_FOUND, "There is a wrong reportId."); private final HttpStatus httpStatus; private final String message; From d43eb94b977daaebaf6ca4288d081b7fcafd61be Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:20:15 +0900 Subject: [PATCH 42/57] =?UTF-8?q?feat=20:=20DB=EC=97=90=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EB=AA=A8=EB=93=A0=20Report=EB=A5=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=98=EB=8A=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/application/ReportService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java index fd0e7e1..c06197b 100644 --- a/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java +++ b/src/main/java/com/nainga/nainga/domain/report/application/ReportService.java @@ -69,4 +69,8 @@ public Report findById(Long id) throws GlobalException { //reportId를 가지 return report.get(); } } + + public List findAll() { //DB에 있는 모든 Report 조회 + return reportRepository.findAll(); + } } From b11f91b1ad149539748ce31c0ba2a8ecab85fa70 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:26:33 +0900 Subject: [PATCH 43/57] =?UTF-8?q?feat=20:=20reportId=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A7=80=EA=B3=A0=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=9C?= =?UTF-8?q?=EB=B3=B4=20=EB=82=B4=EC=9A=A9=EC=9D=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/report/api/ReportApi.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java index 7ca3e43..2c1aa95 100644 --- a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java +++ b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java @@ -1,16 +1,16 @@ package com.nainga.nainga.domain.report.api; import com.nainga.nainga.domain.report.application.ReportService; +import com.nainga.nainga.domain.report.domain.Report; import com.nainga.nainga.domain.report.dto.SaveNewStoreReportRequest; import com.nainga.nainga.domain.report.dto.SaveSpecificStoreReportRequest; import com.nainga.nainga.global.util.Result; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -46,4 +46,17 @@ public Result saveSpecificStoreReport(@Valid @RequestBody SaveSpecificStor Long reportId = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest); return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reportId); } + + //reportId를 가지고 사용자 제보 내용 조회 + @Tag(name = "[New] 사용자 제보") + @Operation(summary = "reportId를 가지고 사용자 제보 내용 조회", description = "reportId를 가지고 사용자 제보 내용을 조회합니다.

" + + "[Request Body]
" + + "reportId: 검색할 사용자 제보의 reportId. 유효하지 않은 reportId의 경우 예외 발생
" + + "[Response Body]
" + + "해당 reportId로 검색된 사용자 제보 내용
") + @GetMapping("api/report/byId/v1") + public Result findById(@NotNull @RequestParam(value = "reportId") Long reportId) { + Report report = reportService.findById(reportId); + return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, report); + } } From c44a08724dcbaf62aed028ff58d6afd020ae5a29 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:41:11 +0900 Subject: [PATCH 44/57] =?UTF-8?q?feat=20:=20DB=EC=97=90=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EB=AA=A8=EB=93=A0=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A0=9C=EB=B3=B4=20=EB=82=B4=EC=9A=A9=EC=9D=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/nainga/domain/report/api/ReportApi.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java index 2c1aa95..7e5c4c3 100644 --- a/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java +++ b/src/main/java/com/nainga/nainga/domain/report/api/ReportApi.java @@ -12,6 +12,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor public class ReportApi { @@ -59,4 +61,15 @@ public Result findById(@NotNull @RequestParam(value = "reportId") Long r Report report = reportService.findById(reportId); return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, report); } + + //DB에 있는 모든 사용자 제보 내용 조회 + @Tag(name = "[New] 사용자 제보") + @Operation(summary = "DB에 있는 모든 사용자 제보 내용 조회", description = "DB에 있는 모든 사용자 제보 내용을 조회합니다.

" + + "[Response Body]
" + + "DB에 있는 모든 사용자 제보 내용
") + @GetMapping("api/report/all/v1") + public Result> findAll() { + List reports = reportService.findAll(); + return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, reports); + } } From 48bdabc161d50b9b27a1a3f498ef13c29ae7ea6c Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 03:55:52 +0900 Subject: [PATCH 45/57] =?UTF-8?q?feat=20:=20reportId=EB=A1=9C=20report=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=EC=9D=84=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/application/ReportServiceTest.java | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java index a71da79..bfd213b 100644 --- a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java +++ b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java @@ -36,9 +36,8 @@ public void saveNewStoreReport() throws Exception { SaveNewStoreReportRequest saveNewStoreReportRequest2 = new SaveNewStoreReportRequest("가게2", "주소2", List.of("착한가격업")); //인증제 이름이 잘못되었을 때 //when - reportService.saveNewStoreReport(saveNewStoreReportRequest1); - List reports = reportRepository.findAll(); - NewStoreReport report = (NewStoreReport) reports.stream().findAny().get(); + Long report1Id = reportService.saveNewStoreReport(saveNewStoreReportRequest1); + NewStoreReport report = (NewStoreReport) reportService.findById(report1Id); //then assertArrayEquals(report.getCertifications().toArray(), saveNewStoreReportRequest1.getCertifications().toArray()); @@ -56,17 +55,36 @@ public void saveSpecificStoreReport() throws Exception { SaveSpecificStoreReportRequest saveSpecificStoreReportRequest3 = new SaveSpecificStoreReportRequest("xxx", 12345L, "내용3"); //잘못된 dtype //when - reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest1); - reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest2); - List reports = reportRepository.findAll(); + Long report1Id = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest1); + Long report2Id = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest2); + DelSpecificStoreReport report1 = (DelSpecificStoreReport) reportService.findById(report1Id); + FixSpecificStoreReport report2 = (FixSpecificStoreReport) reportService.findById(report2Id); //then - DelSpecificStoreReport report1 = (DelSpecificStoreReport) reports.get(0); - FixSpecificStoreReport report2 = (FixSpecificStoreReport) reports.get(1); assertThat(report1.getStoreId()).isEqualTo(saveSpecificStoreReportRequest1.getStoreId()); assertThat(report2.getContents()).isEqualTo(saveSpecificStoreReportRequest2.getContents()); assertThatThrownBy(() -> reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest3)) //잘못된 dtype이라서 예외가 터져야함 .isInstanceOf(GlobalException.class); } + @Test + public void findById() throws Exception { + //given + SaveNewStoreReportRequest saveNewStoreReportRequest1 = new SaveNewStoreReportRequest("가게1", "주소1", List.of("착한가격업소", "모범음식점")); //정상적인 테스트 케이스 + SaveSpecificStoreReportRequest saveSpecificStoreReportRequest1 = new SaveSpecificStoreReportRequest("del", 123L, "내용1"); //정상적인 테스트 케이스 + + //when + Long report1Id = reportService.saveNewStoreReport(saveNewStoreReportRequest1); + Long report2Id = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest1); + NewStoreReport report1 = (NewStoreReport) reportService.findById(report1Id); + DelSpecificStoreReport report2 = (DelSpecificStoreReport) reportService.findById(report2Id); + + //then + assertThat(report1.getFormattedAddress()).isEqualTo(saveNewStoreReportRequest1.getFormattedAddress()); + assertArrayEquals(report1.getCertifications().toArray(), saveNewStoreReportRequest1.getCertifications().toArray()); + assertThat(report1.getStoreName()).isEqualTo(saveNewStoreReportRequest1.getStoreName()); + assertThat(report2.getContents()).isEqualTo(saveSpecificStoreReportRequest1.getContents()); + assertThat(report2.getStoreId()).isEqualTo(saveSpecificStoreReportRequest1.getStoreId()); + } + } \ No newline at end of file From 07406a4ba96fe9fddaa7a9b324699419c09babf4 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 04:08:03 +0900 Subject: [PATCH 46/57] =?UTF-8?q?feat=20:=20DB=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20Report=20=EB=82=B4=EC=9A=A9=EC=9D=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/application/ReportServiceTest.java | 19 +++++++++++++++++++ .../store/application/StoreServiceTest.java | 3 +-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java index bfd213b..73ff010 100644 --- a/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java +++ b/src/test/java/com/nainga/nainga/domain/report/application/ReportServiceTest.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -87,4 +88,22 @@ public void findById() throws Exception { assertThat(report2.getStoreId()).isEqualTo(saveSpecificStoreReportRequest1.getStoreId()); } + @Test + public void findAll() throws Exception { + //given + SaveNewStoreReportRequest saveNewStoreReportRequest1 = new SaveNewStoreReportRequest("가게1", "주소1", List.of("착한가격업소", "모범음식점")); //정상적인 테스트 케이스 + SaveSpecificStoreReportRequest saveSpecificStoreReportRequest1 = new SaveSpecificStoreReportRequest("del", 123L, "내용1"); //정상적인 테스트 케이스 + + //when + Long report1Id = reportService.saveNewStoreReport(saveNewStoreReportRequest1); + Long report2Id = reportService.saveSpecificStoreReport(saveSpecificStoreReportRequest1); + + List reports = reportService.findAll(); + List reportIds = reports.stream().map(Report::getId).toList(); + + //then + assertArrayEquals(reportIds.toArray(), List.of(report1Id, report2Id).toArray()); + + } + } \ No newline at end of file diff --git a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java index 7a10fdc..9870906 100644 --- a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java +++ b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java @@ -28,9 +28,8 @@ public void autocorrect() throws Exception { //검색어 자동 완성 기능 //given //테스트를 실행시키는 환경에 따라 잘못된 결과가 나올 수 있으므로 테스트용 가게 이름 제일 앞에는 실제 Production DB에 존재하지 않는 이름인 *을 붙여 사용 List allDisplayName = List.of("*김밥천국", "*김밥나라", "*김빱월드", "*김밥천지"); //List의 팩토리 메서드 사용 - System.out.println("allDisplayName = " + allDisplayName); storeService.saveAllSubstring(allDisplayName); //검색어 자동 완성 기능을 위해 필요한 Substring들을 뽑아 Redis에 저장 - Thread.sleep(1000); //직전에 실행시킨 saveAllSubstring이 멀티 스레드 기반 병렬 처리로 구현되어 있어서 바로 다음 검증 로직으로 넘어가버리면 아직 데이터가 전부 안들어가서 간헐적으로 실패하는 오류가 있음 + Thread.sleep(2000); //직전에 실행시킨 saveAllSubstring이 멀티 스레드 기반 병렬 처리로 구현되어 있어서 바로 다음 검증 로직으로 넘어가버리면 아직 데이터가 전부 안들어가서 간헐적으로 실패하는 오류가 있음 //when List resultByKim = storeService.autocorrect("*김"); //Redis 상에 사전순 정렬되어 있으므로 *김밥나라, *김밥천국, *김밥천지, *김빱월드 순으로 나옴 From 7abeedffd5ad9ac4b34a6a92a26e4635a0a46387 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 04:17:25 +0900 Subject: [PATCH 47/57] =?UTF-8?q?fix=20:=20=EC=9D=BC=EB=B6=80=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 62e3f2f..9d49a33 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencies { //Redis를 사용하기 위해 추가 implementation 'org.springframework.boot:spring-boot-starter-data-redis' - //Validation을 위해 추가 Spring 버전 업되면서 web 의존성안에 있던 constraints packeage가 아예 모듈로 빠졌다. + //Validation을 위해 추가! Spring Boot 2.3버전 이후부터는 web 의존성안에 있던 validation 관련 package가 아예 모듈로 빠짐 implementation 'org.springframework.boot:spring-boot-starter-validation' } From 2cf26c4f77be0cc48b941a9297b5079e2a1061fd Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 18:34:33 +0900 Subject: [PATCH 48/57] =?UTF-8?q?feat=20:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EC=A0=81=ED=95=A9=ED=95=9C=20=EC=9E=90=EB=A3=8C=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EC=9D=B8=20Redis=20Hash=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/application/RedisHashService.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/com/nainga/nainga/global/application/RedisHashService.java diff --git a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java new file mode 100644 index 0000000..b0103ed --- /dev/null +++ b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java @@ -0,0 +1,49 @@ +package com.nainga.nainga.global.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@Service +public class RedisHashService { + private final HashOperations hashOperations; + + + public RedisHashService(RedisTemplate redisTemplate) { + this.hashOperations = redisTemplate.opsForHash(); + } + + private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 + + //Hash에 field-value 쌍을 추가하는 메서드 + public void addToHash(String key, String field, String value) { + hashOperations.put(key, field, value); + } + + public Set findAllValuesContainingSearchKeyword(String searchKeyword) { + //Redis에서는 case insensitive한 검색을 지원하는 내장 모듈이 없으므로 searchKeyword는 모두 소문자로 통일하여 검색하도록 구현 + //당연히 초기 Redis에 field를 저장할 때도 모두 소문자로 변형하여 저장했고 원본 문자열은 value에 저장! + Set result = new TreeSet<>(); //searchKeyword를 포함하는 원래 가게 이름들의 리스트. 최대 maxSize개까지 저장. 중복 허용하지 않고, 자동 사전순 정렬하기 위해 사용 + final int maxSize = 10; //최대 검색어 자동 완성 개수 + + ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + searchKeyword + "*").build(); //searchKeyword를 포함하는지를 검사하기 위한 scanOption + Cursor> cursor = hashOperations.scan(key, scanOptions); //기존 Redis Keys 로직의 성능 이슈를 해결하기 위해 10개 단위로 끊어서 조회하는 Scan 기능 사용 + + while (cursor.hasNext()) { //끊어서 조회하다보니 while loop로 조회 + Map.Entry entry = cursor.next(); + result.add(entry.getValue()); + + if(result.size() >= maxSize) //maxSize에 도달하면 scan 중단 + break; + } + cursor.close(); + return result; + } +} From 6a594420267d29ec149346862a6319c4d6fda07a Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 18:35:21 +0900 Subject: [PATCH 49/57] =?UTF-8?q?fix=20:=20RedisHashService=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=EA=B0=9C=EC=84=A0=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/nainga/nainga/global/application/RedisHashService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java index b0103ed..8fddc79 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java @@ -23,7 +23,7 @@ public RedisHashService(RedisTemplate redisTemplate) { private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 //Hash에 field-value 쌍을 추가하는 메서드 - public void addToHash(String key, String field, String value) { + public void addToHash(String field, String value) { hashOperations.put(key, field, value); } From f8e1aa4a71d6c547bcae6a288e584eba034d8d4b Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 18:49:46 +0900 Subject: [PATCH 50/57] =?UTF-8?q?feat=20:=20=EB=AA=A8=EB=93=A0=20=EA=B0=80?= =?UTF-8?q?=EA=B2=8C=20=EC=9D=B4=EB=A6=84=EC=9D=84=20Redis=20Hash=EC=97=90?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?saveAllDisplayName()=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/application/StoreService.java | 83 ++++++++++++------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 8b9a815..764d32b 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -1,6 +1,7 @@ package com.nainga.nainga.domain.store.application; import com.nainga.nainga.domain.store.dao.StoreRepository; +import com.nainga.nainga.global.application.RedisHashService; import com.nainga.nainga.global.application.RedisSortedSetService; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -19,44 +20,68 @@ @RequiredArgsConstructor public class StoreService { private final StoreRepository storeRepository; - private final RedisSortedSetService redisSortedSetService; - private String suffix = "*"; //검색어 자동 완성 기능에서 실제 노출될 수 있는 완벽한 형태의 단어를 구분하기 위한 접미사 - private int maxSize = 10; //검색어 자동 완성 기능 최대 개수 - - @PostConstruct - public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) - redisSortedSetService.removeAllOfSortedSet(); - saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 - } + // private final RedisSortedSetService redisSortedSetService; + private final RedisHashService redisHashService; + + /* + Redis Hash 자료 구조를 활용한 새로운 검색어 자동 완성 로직입니다. + 아래 로직은 Case insensitive하게 검색될 수 있도록 구현하였습니다. + */ - public void saveAllSubstring(List allDisplayName) { + public void saveAllDisplayName(List allDisplayName) { ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 for (String displayName : allDisplayName) { executorService.submit(() -> { //submit 메서드를 사용해서 병렬 처리할 작업 추가 - redisSortedSetService.addToSortedSet(displayName + suffix); - - for (int i = displayName.length(); i > 0; --i) { - redisSortedSetService.addToSortedSet(displayName.substring(0, i)); - } + //Redis 내장 기능이 아닌 case insensitive 조회를 구현하기 위해 소문자로 변환한 field값과 원래 string인 value의 쌍을 Hash에 저장 + redisHashService.addToHash(displayName.toLowerCase(), displayName); //case insensitive하게 검색어 자동 완성 기능을 직접 구현하기 위해 소문자로 통일해서 저장 }); } executorService.shutdown(); //작업이 모두 완료되면 스레드풀을 종료 } - public List autocorrect(String keyword) { //검색어 자동 완성 기능 관련 로직 - Long index = redisSortedSetService.findFromSortedSet(keyword); //사용자가 입력한 검색어를 바탕으로 Redis에서 조회한 결과 매칭되는 index + /* + 아래 주석처리 된 코드는 초기 검색어 자동 완성 로직입니다. + 검색어 자동 완성에 대한 요구 사항이 앞에서부터 매칭되는 글자가 아닌 Contains 개념으로 바뀌어서 주석 처리하여 임시로 남겨놓았습니다. + */ - if (index == null) { - return new ArrayList<>(); //만약 사용자 검색어 바탕으로 자동 완성 검색어를 만들 수 없으면 Empty Array 리턴 - } - - Set allValuesAfterIndexFromSortedSet = redisSortedSetService.findAllValuesAfterIndexFromSortedSet(index); //사용자 검색어 이후로 정렬된 Redis 데이터들 가져오기 - - return allValuesAfterIndexFromSortedSet.stream() - .filter(value -> value.endsWith(suffix) && value.startsWith(keyword)) - .map(value -> StringUtils.removeEnd(value, suffix)) - .limit(maxSize) - .toList(); //자동 완성을 통해 만들어진 최대 maxSize개의 키워드들 - } +// private String suffix = "*"; //검색어 자동 완성 기능에서 실제 노출될 수 있는 완벽한 형태의 단어를 구분하기 위한 접미사 +// private int maxSize = 10; //검색어 자동 완성 기능 최대 개수 +// +// @PostConstruct +// public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) +// redisSortedSetService.removeAllOfSortedSet(); +// saveAllSubstring(storeRepository.findAllDisplayName()); //MySQL DB에 저장된 모든 가게명을 음절 단위로 잘라 모든 Substring을 Redis에 저장해주는 로직 +// } +// +// public void saveAllSubstring(List allDisplayName) { +// ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 +// +// for (String displayName : allDisplayName) { +// executorService.submit(() -> { //submit 메서드를 사용해서 병렬 처리할 작업 추가 +// redisSortedSetService.addToSortedSet(displayName + suffix); +// +// for (int i = displayName.length(); i > 0; --i) { +// redisSortedSetService.addToSortedSet(displayName.substring(0, i)); +// } +// }); +// } +// executorService.shutdown(); //작업이 모두 완료되면 스레드풀을 종료 +// } +// +// public List autocorrect(String keyword) { //검색어 자동 완성 기능 관련 로직 +// Long index = redisSortedSetService.findFromSortedSet(keyword); //사용자가 입력한 검색어를 바탕으로 Redis에서 조회한 결과 매칭되는 index +// +// if (index == null) { +// return new ArrayList<>(); //만약 사용자 검색어 바탕으로 자동 완성 검색어를 만들 수 없으면 Empty Array 리턴 +// } +// +// Set allValuesAfterIndexFromSortedSet = redisSortedSetService.findAllValuesAfterIndexFromSortedSet(index); //사용자 검색어 이후로 정렬된 Redis 데이터들 가져오기 +// +// return allValuesAfterIndexFromSortedSet.stream() +// .filter(value -> value.endsWith(suffix) && value.startsWith(keyword)) +// .map(value -> StringUtils.removeEnd(value, suffix)) +// .limit(maxSize) +// .toList(); //자동 완성을 통해 만들어진 최대 maxSize개의 키워드들 +// } } From 7aa13bcf87892aad3f069e8c05e22e7a164bf2b7 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 18:53:06 +0900 Subject: [PATCH 51/57] =?UTF-8?q?feat=20:=20RedisHashService=EC=97=90=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20?= =?UTF-8?q?=EC=A7=80=EC=9A=B0=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/nainga/domain/store/application/StoreService.java | 5 +++++ .../nainga/nainga/global/application/RedisHashService.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 764d32b..0f83e2d 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -28,6 +28,11 @@ public class StoreService { 아래 로직은 Case insensitive하게 검색될 수 있도록 구현하였습니다. */ + @PostConstruct + public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) + + } + public void saveAllDisplayName(List allDisplayName) { ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 diff --git a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java index 8fddc79..a4af474 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java @@ -46,4 +46,8 @@ public Set findAllValuesContainingSearchKeyword(String searchKeyword) { cursor.close(); return result; } + + public void removeAllOfHash() { + hashOperations.delete(key); + } } From 55a1ceef3864d7ea2bf27b1bfb8acc23171aaf20 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 18:54:48 +0900 Subject: [PATCH 52/57] =?UTF-8?q?feat=20:=20StoreService=EC=97=90=20init()?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/nainga/domain/store/application/StoreService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index 0f83e2d..f0fdd8c 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -30,7 +30,8 @@ public class StoreService { @PostConstruct public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) - + redisHashService.removeAllOfHash(); + saveAllDisplayName(storeRepository.findAllDisplayName()); //모든 가게명을 소문자로 변환한 것을 field, 원래 가게 이름을 value로 매핑시켜서 Redis Hash에 저장 } public void saveAllDisplayName(List allDisplayName) { From b9d4235144e67347162cd33b9c8bddfd112d33a3 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Sun, 18 Feb 2024 18:58:58 +0900 Subject: [PATCH 53/57] =?UTF-8?q?feat=20:=20StoreService=EC=97=90=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=96=B4=20=EC=9E=90=EB=8F=99=20=EC=99=84?= =?UTF-8?q?=EC=84=B1=20=EA=B8=B0=EB=8A=A5=EA=B3=BC=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=EB=90=9C=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/domain/store/application/StoreService.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index f0fdd8c..fc0a6d1 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -46,6 +46,14 @@ public void saveAllDisplayName(List allDisplayName) { executorService.shutdown(); //작업이 모두 완료되면 스레드풀을 종료 } + public List autocorrect(String keyword) { //검색어 자동 완성 로직 + Set allValuesContainingSearchKeyword = redisHashService.findAllValuesContainingSearchKeyword(keyword); //case insensitive하게 serachKeyword를 포함하는 가게 이름 최대 10개 반환 + if(allValuesContainingSearchKeyword.isEmpty()) + return new ArrayList<>(); + else + return new ArrayList<>(allValuesContainingSearchKeyword); //자동 완성 결과가 존재하면 ArrayList로 변환하여 리턴 + } + /* 아래 주석처리 된 코드는 초기 검색어 자동 완성 로직입니다. 검색어 자동 완성에 대한 요구 사항이 앞에서부터 매칭되는 글자가 아닌 Contains 개념으로 바뀌어서 주석 처리하여 임시로 남겨놓았습니다. From 921c3958738d99bd56444709d2e9bc7f2663d1dc Mon Sep 17 00:00:00 2001 From: sungjindev Date: Mon, 19 Feb 2024 01:49:46 +0900 Subject: [PATCH 54/57] =?UTF-8?q?fix=20:=20RedisConfig=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nainga/global/application/RedisHashService.java | 13 +++++++++---- .../nainga/nainga/global/config/RedisConfig.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java index a4af474..4e22865 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java @@ -1,12 +1,12 @@ package com.nainga.nainga.global.application; -import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.stereotype.Service; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -14,12 +14,17 @@ @Service public class RedisHashService { private final HashOperations hashOperations; - + private final RedisTemplate redisTemplate; public RedisHashService(RedisTemplate redisTemplate) { this.hashOperations = redisTemplate.opsForHash(); + this.redisTemplate = redisTemplate; } + // public RedisHashService(RedisTemplate redisTemplate) { +// this.hashOperations = redisTemplate.opsForHash(); +// } + private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 //Hash에 field-value 쌍을 추가하는 메서드 @@ -30,7 +35,7 @@ public void addToHash(String field, String value) { public Set findAllValuesContainingSearchKeyword(String searchKeyword) { //Redis에서는 case insensitive한 검색을 지원하는 내장 모듈이 없으므로 searchKeyword는 모두 소문자로 통일하여 검색하도록 구현 //당연히 초기 Redis에 field를 저장할 때도 모두 소문자로 변형하여 저장했고 원본 문자열은 value에 저장! - Set result = new TreeSet<>(); //searchKeyword를 포함하는 원래 가게 이름들의 리스트. 최대 maxSize개까지 저장. 중복 허용하지 않고, 자동 사전순 정렬하기 위해 사용 + Set result = new HashSet<>(); //searchKeyword를 포함하는 원래 가게 이름들의 리스트. 최대 maxSize개까지 저장. 중복 허용하지 않고, 자동 사전순 정렬하기 위해 사용 final int maxSize = 10; //최대 검색어 자동 완성 개수 ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + searchKeyword + "*").build(); //searchKeyword를 포함하는지를 검사하기 위한 scanOption @@ -48,6 +53,6 @@ public Set findAllValuesContainingSearchKeyword(String searchKeyword) { } public void removeAllOfHash() { - hashOperations.delete(key); + redisTemplate.delete(key); } } diff --git a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java index ff41fb6..352a18c 100644 --- a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java +++ b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java @@ -6,6 +6,8 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { //Redis 연결을 위한 기본 설정 @@ -19,6 +21,17 @@ public class RedisConfig { //Redis 연결을 위한 기본 설정 @Value("${spring.data.redis.password}") private String password; + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new StringRedisSerializer()); + return redisTemplate; + } + @Bean public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); From 8fa4a800043d12639f22a015b1c085df6357129b Mon Sep 17 00:00:00 2001 From: sungjindev Date: Mon, 19 Feb 2024 04:35:01 +0900 Subject: [PATCH 55/57] =?UTF-8?q?refactor=20:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=EC=9D=B4=20=EB=B3=80=EA=B2=BD=EB=90=A8?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20Redis=EC=9D=98=20=EC=84=B1?= =?UTF-8?q?=EB=8A=A5=20=EC=9D=B4=EC=8A=88=EA=B0=80=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=ED=95=98=EC=97=AC=20MySQL=20=EB=A1=9C=EC=A7=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=AA=A8=EB=91=90=20=EC=A0=84=ED=99=98=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/application/StoreService.java | 65 ++++++----- .../domain/store/dao/StoreRepository.java | 7 ++ .../global/application/RedisHashService.java | 108 +++++++++--------- .../application/RedisSortedSetService.java | 68 +++++------ .../nainga/global/config/RedisConfig.java | 82 ++++++------- .../store/application/StoreServiceTest.java | 39 ++++--- 6 files changed, 186 insertions(+), 183 deletions(-) diff --git a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java index fc0a6d1..ed02554 100644 --- a/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java +++ b/src/main/java/com/nainga/nainga/domain/store/application/StoreService.java @@ -1,62 +1,61 @@ package com.nainga.nainga.domain.store.application; import com.nainga.nainga.domain.store.dao.StoreRepository; -import com.nainga.nainga.global.application.RedisHashService; -import com.nainga.nainga.global.application.RedisSortedSetService; -import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class StoreService { private final StoreRepository storeRepository; - // private final RedisSortedSetService redisSortedSetService; - private final RedisHashService redisHashService; + + public List autocorrect(String keyword) { //검색어 자동 완성 로직 + return storeRepository.findAllBySearchKeyword(keyword); + } + +// private final RedisSortedSetService redisSortedSetService; +// private final RedisHashService redisHashService; /* Redis Hash 자료 구조를 활용한 새로운 검색어 자동 완성 로직입니다. 아래 로직은 Case insensitive하게 검색될 수 있도록 구현하였습니다. + 지금은 MySQL이 성능이 더 좋아서 사용하지 않습니다. */ - @PostConstruct - public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) - redisHashService.removeAllOfHash(); - saveAllDisplayName(storeRepository.findAllDisplayName()); //모든 가게명을 소문자로 변환한 것을 field, 원래 가게 이름을 value로 매핑시켜서 Redis Hash에 저장 - } - - public void saveAllDisplayName(List allDisplayName) { - ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 +// @PostConstruct +// public void init() { //이 Service Bean이 생성된 이후에 검색어 자동 완성 기능을 위한 데이터들을 Redis에 저장 (Redis는 인메모리 DB라 휘발성을 띄기 때문) +// redisHashService.removeAllOfHash(); +// saveAllDisplayName(storeRepository.findAllDisplayName()); //모든 가게명을 소문자로 변환한 것을 field, 원래 가게 이름을 value로 매핑시켜서 Redis Hash에 저장 +// } - for (String displayName : allDisplayName) { - executorService.submit(() -> { //submit 메서드를 사용해서 병렬 처리할 작업 추가 - //Redis 내장 기능이 아닌 case insensitive 조회를 구현하기 위해 소문자로 변환한 field값과 원래 string인 value의 쌍을 Hash에 저장 - redisHashService.addToHash(displayName.toLowerCase(), displayName); //case insensitive하게 검색어 자동 완성 기능을 직접 구현하기 위해 소문자로 통일해서 저장 - }); - } - executorService.shutdown(); //작업이 모두 완료되면 스레드풀을 종료 - } +// public void saveAllDisplayName(List allDisplayName) { +// ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //병렬 처리를 위한 스레드풀을 생성하는 과정 +// +// for (String displayName : allDisplayName) { +// executorService.submit(() -> { //submit 메서드를 사용해서 병렬 처리할 작업 추가 +// //Redis 내장 기능이 아닌 case insensitive 조회를 구현하기 위해 소문자로 변환한 field값과 원래 string인 value의 쌍을 Hash에 저장 +// redisHashService.addToHash(displayName.toLowerCase(), displayName); //case insensitive하게 검색어 자동 완성 기능을 직접 구현하기 위해 소문자로 통일해서 저장 +// }); +// } +// executorService.shutdown(); //작업이 모두 완료되면 스레드풀을 종료 +// } - public List autocorrect(String keyword) { //검색어 자동 완성 로직 - Set allValuesContainingSearchKeyword = redisHashService.findAllValuesContainingSearchKeyword(keyword); //case insensitive하게 serachKeyword를 포함하는 가게 이름 최대 10개 반환 - if(allValuesContainingSearchKeyword.isEmpty()) - return new ArrayList<>(); - else - return new ArrayList<>(allValuesContainingSearchKeyword); //자동 완성 결과가 존재하면 ArrayList로 변환하여 리턴 - } +// public List autocorrect(String keyword) { //검색어 자동 완성 로직 +// Set allValuesContainingSearchKeyword = redisHashService.findAllValuesContainingSearchKeyword(keyword); //case insensitive하게 serachKeyword를 포함하는 가게 이름 최대 10개 반환 +// if(allValuesContainingSearchKeyword.isEmpty()) +// return new ArrayList<>(); +// else +// return new ArrayList<>(allValuesContainingSearchKeyword); //자동 완성 결과가 존재하면 ArrayList로 변환하여 리턴 +// } /* 아래 주석처리 된 코드는 초기 검색어 자동 완성 로직입니다. 검색어 자동 완성에 대한 요구 사항이 앞에서부터 매칭되는 글자가 아닌 Contains 개념으로 바뀌어서 주석 처리하여 임시로 남겨놓았습니다. + 지금은 MySQL이 성능이 더 좋기 때문에 사용하지 않는 코드입니다. */ // private String suffix = "*"; //검색어 자동 완성 기능에서 실제 노출될 수 있는 완벽한 형태의 단어를 구분하기 위한 접미사 diff --git a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java index 90d16d5..b6ead00 100644 --- a/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java +++ b/src/main/java/com/nainga/nainga/domain/store/dao/StoreRepository.java @@ -48,4 +48,11 @@ public List findAllDisplayName() { return em.createQuery("select s.displayName from Store s", String.class) .getResultList(); } + + public List findAllBySearchKeyword(String searchKeyword) { + return em.createQuery("select s.displayName from Store s where s.displayName like concat('%', :searchKeyword, '%')", String.class) + .setParameter("searchKeyword", searchKeyword) + .setMaxResults(10) + .getResultList(); + } } diff --git a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java index 4e22865..61e322c 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisHashService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisHashService.java @@ -1,58 +1,54 @@ -package com.nainga.nainga.global.application; - -import org.springframework.data.redis.core.Cursor; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ScanOptions; -import org.springframework.stereotype.Service; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -@Service -public class RedisHashService { - private final HashOperations hashOperations; - private final RedisTemplate redisTemplate; - - public RedisHashService(RedisTemplate redisTemplate) { - this.hashOperations = redisTemplate.opsForHash(); - this.redisTemplate = redisTemplate; - } - - // public RedisHashService(RedisTemplate redisTemplate) { +//package com.nainga.nainga.global.application; +// +//import org.springframework.data.redis.core.Cursor; +//import org.springframework.data.redis.core.HashOperations; +//import org.springframework.data.redis.core.RedisTemplate; +//import org.springframework.data.redis.core.ScanOptions; +//import org.springframework.stereotype.Service; +// +//import java.util.HashSet; +//import java.util.Map; +//import java.util.Set; +//import java.util.TreeSet; +// +//@Service +//public class RedisHashService { +// private final HashOperations hashOperations; +// private final RedisTemplate redisTemplate; +// +// public RedisHashService(RedisTemplate redisTemplate) { // this.hashOperations = redisTemplate.opsForHash(); +// this.redisTemplate = redisTemplate; // } - - private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 - - //Hash에 field-value 쌍을 추가하는 메서드 - public void addToHash(String field, String value) { - hashOperations.put(key, field, value); - } - - public Set findAllValuesContainingSearchKeyword(String searchKeyword) { - //Redis에서는 case insensitive한 검색을 지원하는 내장 모듈이 없으므로 searchKeyword는 모두 소문자로 통일하여 검색하도록 구현 - //당연히 초기 Redis에 field를 저장할 때도 모두 소문자로 변형하여 저장했고 원본 문자열은 value에 저장! - Set result = new HashSet<>(); //searchKeyword를 포함하는 원래 가게 이름들의 리스트. 최대 maxSize개까지 저장. 중복 허용하지 않고, 자동 사전순 정렬하기 위해 사용 - final int maxSize = 10; //최대 검색어 자동 완성 개수 - - ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + searchKeyword + "*").build(); //searchKeyword를 포함하는지를 검사하기 위한 scanOption - Cursor> cursor = hashOperations.scan(key, scanOptions); //기존 Redis Keys 로직의 성능 이슈를 해결하기 위해 10개 단위로 끊어서 조회하는 Scan 기능 사용 - - while (cursor.hasNext()) { //끊어서 조회하다보니 while loop로 조회 - Map.Entry entry = cursor.next(); - result.add(entry.getValue()); - - if(result.size() >= maxSize) //maxSize에 도달하면 scan 중단 - break; - } - cursor.close(); - return result; - } - - public void removeAllOfHash() { - redisTemplate.delete(key); - } -} +// +// private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 +// +// //Hash에 field-value 쌍을 추가하는 메서드 +// public void addToHash(String field, String value) { +// hashOperations.put(key, field, value); +// } +// +// public Set findAllValuesContainingSearchKeyword(String searchKeyword) { +// //Redis에서는 case insensitive한 검색을 지원하는 내장 모듈이 없으므로 searchKeyword는 모두 소문자로 통일하여 검색하도록 구현 +// //당연히 초기 Redis에 field를 저장할 때도 모두 소문자로 변형하여 저장했고 원본 문자열은 value에 저장! +// Set result = new HashSet<>(); //searchKeyword를 포함하는 원래 가게 이름들의 리스트. 최대 maxSize개까지 저장. 중복 허용하지 않고, 자동 사전순 정렬하기 위해 사용 +// final int maxSize = 10; //최대 검색어 자동 완성 개수 +// +// ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + searchKeyword + "*").build(); //searchKeyword를 포함하는지를 검사하기 위한 scanOption +// Cursor> cursor = hashOperations.scan(key, scanOptions); //기존 Redis Keys 로직의 성능 이슈를 해결하기 위해 10개 단위로 끊어서 조회하는 Scan 기능 사용 +// +// while (cursor.hasNext()) { //끊어서 조회하다보니 while loop로 조회 +// Map.Entry entry = cursor.next(); +// result.add(entry.getValue()); +// +// if(result.size() >= maxSize) //maxSize에 도달하면 scan 중단 +// break; +// } +// cursor.close(); +// return result; +// } +// +// public void removeAllOfHash() { +// redisTemplate.delete(key); +// } +//} diff --git a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java index 374f7f7..52a575b 100644 --- a/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java +++ b/src/main/java/com/nainga/nainga/global/application/RedisSortedSetService.java @@ -1,34 +1,34 @@ -package com.nainga.nainga.global.application; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisCallback; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ZSetOperations; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Set; - -@Service -@RequiredArgsConstructor -public class RedisSortedSetService { //검색어 자동 완성을 구현할 때 사용하는 Redis의 SortedSet 관련 서비스 레이어 - private final RedisTemplate redisTemplate; - private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 - private int score = 0; //Score는 딱히 필요 없으므로 하나로 통일 - - public void addToSortedSet(String value) { //Redis SortedSet에 추가 - redisTemplate.opsForZSet().add(key, value, score); - } - - public Long findFromSortedSet(String value) { //Redis SortedSet에서 Value를 찾아 인덱스를 반환 - return redisTemplate.opsForZSet().rank(key, value); - } - - public Set findAllValuesAfterIndexFromSortedSet(Long index) { - return redisTemplate.opsForZSet().range(key, index, index + 200); //전체를 다 불러오기 보다는 200개 정도만 가져와도 자동 완성을 구현하는 데 무리가 없으므로 200개로 rough하게 설정 - } - - public void removeAllOfSortedSet() { - redisTemplate.opsForZSet().removeRange(key, 0, -1); - } -} +//package com.nainga.nainga.global.application; +// +//import lombok.RequiredArgsConstructor; +//import org.springframework.data.redis.core.RedisCallback; +//import org.springframework.data.redis.core.RedisTemplate; +//import org.springframework.data.redis.core.ZSetOperations; +//import org.springframework.stereotype.Service; +// +//import java.util.List; +//import java.util.Set; +// +//@Service +//@RequiredArgsConstructor +//public class RedisSortedSetService { //검색어 자동 완성을 구현할 때 사용하는 Redis의 SortedSet 관련 서비스 레이어 +// private final RedisTemplate redisTemplate; +// private String key = "autocorrect"; //검색어 자동 완성을 위한 Redis 데이터 +// private int score = 0; //Score는 딱히 필요 없으므로 하나로 통일 +// +// public void addToSortedSet(String value) { //Redis SortedSet에 추가 +// redisTemplate.opsForZSet().add(key, value, score); +// } +// +// public Long findFromSortedSet(String value) { //Redis SortedSet에서 Value를 찾아 인덱스를 반환 +// return redisTemplate.opsForZSet().rank(key, value); +// } +// +// public Set findAllValuesAfterIndexFromSortedSet(Long index) { +// return redisTemplate.opsForZSet().range(key, index, index + 200); //전체를 다 불러오기 보다는 200개 정도만 가져와도 자동 완성을 구현하는 데 무리가 없으므로 200개로 rough하게 설정 +// } +// +// public void removeAllOfSortedSet() { +// redisTemplate.opsForZSet().removeRange(key, 0, -1); +// } +//} diff --git a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java index 352a18c..b0e7f73 100644 --- a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java +++ b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java @@ -1,41 +1,41 @@ -package com.nainga.nainga.global.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -@Configuration -public class RedisConfig { //Redis 연결을 위한 기본 설정 - - @Value("${spring.data.redis.host}") - private String host; - - @Value("${spring.data.redis.port}") - private int port; - - @Value("${spring.data.redis.password}") - private String password; - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(connectionFactory); - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashValueSerializer(new StringRedisSerializer()); - return redisTemplate; - } - - @Bean - public RedisConnectionFactory redisConnectionFactory() { - RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); - config.setPassword(password); - return new LettuceConnectionFactory(config); - } -} \ No newline at end of file +//package com.nainga.nainga.global.config; +// +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.data.redis.connection.RedisConnectionFactory; +//import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +//import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +//import org.springframework.data.redis.core.RedisTemplate; +//import org.springframework.data.redis.serializer.StringRedisSerializer; +// +//@Configuration +//public class RedisConfig { //Redis 연결을 위한 기본 설정 +// +// @Value("${spring.data.redis.host}") +// private String host; +// +// @Value("${spring.data.redis.port}") +// private int port; +// +// @Value("${spring.data.redis.password}") +// private String password; +// +// @Bean +// public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { +// RedisTemplate redisTemplate = new RedisTemplate<>(); +// redisTemplate.setConnectionFactory(connectionFactory); +// redisTemplate.setKeySerializer(new StringRedisSerializer()); +// redisTemplate.setValueSerializer(new StringRedisSerializer()); +// redisTemplate.setHashKeySerializer(new StringRedisSerializer()); +// redisTemplate.setHashValueSerializer(new StringRedisSerializer()); +// return redisTemplate; +// } +// +// @Bean +// public RedisConnectionFactory redisConnectionFactory() { +// RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); +// config.setPassword(password); +// return new LettuceConnectionFactory(config); +// } +//} \ No newline at end of file diff --git a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java index 9870906..cf87f9c 100644 --- a/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java +++ b/src/test/java/com/nainga/nainga/domain/store/application/StoreServiceTest.java @@ -1,16 +1,15 @@ package com.nainga.nainga.domain.store.application; -import com.nainga.nainga.global.application.RedisSortedSetService; -import org.assertj.core.api.Assertions; +import com.nainga.nainga.domain.store.dao.StoreRepository; +import com.nainga.nainga.domain.store.domain.Store; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @@ -21,28 +20,30 @@ class StoreServiceTest { StoreService storeService; @Autowired - RedisSortedSetService redisSortedSetService; + StoreRepository storeRepository; @Test public void autocorrect() throws Exception { //검색어 자동 완성 기능에 대한 테스트 //given - //테스트를 실행시키는 환경에 따라 잘못된 결과가 나올 수 있으므로 테스트용 가게 이름 제일 앞에는 실제 Production DB에 존재하지 않는 이름인 *을 붙여 사용 - List allDisplayName = List.of("*김밥천국", "*김밥나라", "*김빱월드", "*김밥천지"); //List의 팩토리 메서드 사용 - storeService.saveAllSubstring(allDisplayName); //검색어 자동 완성 기능을 위해 필요한 Substring들을 뽑아 Redis에 저장 - Thread.sleep(2000); //직전에 실행시킨 saveAllSubstring이 멀티 스레드 기반 병렬 처리로 구현되어 있어서 바로 다음 검증 로직으로 넘어가버리면 아직 데이터가 전부 안들어가서 간헐적으로 실패하는 오류가 있음 + Store store1 = Store.builder() + .displayName("^") + .build(); + Store store2 = Store.builder() + .displayName("^^") + .build(); + Store store3 = Store.builder() + .displayName("^*^") + .build(); + storeRepository.save(store1); + storeRepository.save(store2); + storeRepository.save(store3); //when - List resultByKim = storeService.autocorrect("*김"); //Redis 상에 사전순 정렬되어 있으므로 *김밥나라, *김밥천국, *김밥천지, *김빱월드 순으로 나옴 - List resultByKimBap = storeService.autocorrect("*김밥"); //*김밥나라, *김밥천국, *김밥천지가 나와야 함 - List resultByKimBapCheon = storeService.autocorrect("*김밥천"); //*김밥천국, *김밥천지가 나와야 함 - List resultByKimBapCheonGuk = storeService.autocorrect("*김밥천국"); //*김밥천국이 나와야 함 + List result1 = storeService.autocorrect("^"); + List result2 = storeService.autocorrect("^^"); //then - assertThat(resultByKim).containsExactly("*김밥나라", "*김밥천국", "*김밥천지", "*김빱월드"); - assertThat(resultByKimBap).containsExactly("*김밥나라", "*김밥천국", "*김밥천지"); - assertThat(resultByKimBapCheon).containsExactly("*김밥천국", "*김밥천지"); - assertThat(resultByKimBapCheonGuk).containsExactly("*김밥천국"); - - redisSortedSetService.removeAllOfSortedSet(); //Test 이후 Redis가 Roll back 될 수 있도록 모든 데이터를 제거 + assertArrayEquals(result1.toArray(), List.of("^", "^^", "^*^").toArray()); + assertArrayEquals(result2.toArray(), List.of("^^").toArray()); } } \ No newline at end of file From 46157d792d2b47fa0db084182df6a60310e78d75 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Mon, 19 Feb 2024 04:46:03 +0900 Subject: [PATCH 56/57] =?UTF-8?q?fix=20:=20Workflow=EC=99=80=20Submodule?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=8F=84=20Redis=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=A0=9C=EA=B1=B0=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/action-test.yml | 3 -- build.gradle | 3 -- .../nainga/global/config/RedisConfig.java | 41 ------------------- src/main/resources/backend-submodule | 2 +- src/test/resources/backend-submodule | 2 +- 5 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/com/nainga/nainga/global/config/RedisConfig.java diff --git a/.github/workflows/action-test.yml b/.github/workflows/action-test.yml index d5a19db..899bd44 100644 --- a/.github/workflows/action-test.yml +++ b/.github/workflows/action-test.yml @@ -46,9 +46,6 @@ jobs: mysql user: 'test' mysql password: ${{ secrets.DB_PASSWORD }} - - name: Set up Redis - uses: shogo82148/actions-setup-redis@v1.33.0 - # github action 에서 Gradle dependency 캐시 사용 - name: Cache Gradle packages uses: actions/cache@v3 diff --git a/build.gradle b/build.gradle index 9d49a33..de8f8cf 100644 --- a/build.gradle +++ b/build.gradle @@ -59,9 +59,6 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' } - //Redis를 사용하기 위해 추가 - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - //Validation을 위해 추가! Spring Boot 2.3버전 이후부터는 web 의존성안에 있던 validation 관련 package가 아예 모듈로 빠짐 implementation 'org.springframework.boot:spring-boot-starter-validation' diff --git a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java b/src/main/java/com/nainga/nainga/global/config/RedisConfig.java deleted file mode 100644 index b0e7f73..0000000 --- a/src/main/java/com/nainga/nainga/global/config/RedisConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -//package com.nainga.nainga.global.config; -// -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.data.redis.connection.RedisConnectionFactory; -//import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -//import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -//import org.springframework.data.redis.core.RedisTemplate; -//import org.springframework.data.redis.serializer.StringRedisSerializer; -// -//@Configuration -//public class RedisConfig { //Redis 연결을 위한 기본 설정 -// -// @Value("${spring.data.redis.host}") -// private String host; -// -// @Value("${spring.data.redis.port}") -// private int port; -// -// @Value("${spring.data.redis.password}") -// private String password; -// -// @Bean -// public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { -// RedisTemplate redisTemplate = new RedisTemplate<>(); -// redisTemplate.setConnectionFactory(connectionFactory); -// redisTemplate.setKeySerializer(new StringRedisSerializer()); -// redisTemplate.setValueSerializer(new StringRedisSerializer()); -// redisTemplate.setHashKeySerializer(new StringRedisSerializer()); -// redisTemplate.setHashValueSerializer(new StringRedisSerializer()); -// return redisTemplate; -// } -// -// @Bean -// public RedisConnectionFactory redisConnectionFactory() { -// RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); -// config.setPassword(password); -// return new LettuceConnectionFactory(config); -// } -//} \ No newline at end of file diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule index bf26087..30d8002 160000 --- a/src/main/resources/backend-submodule +++ b/src/main/resources/backend-submodule @@ -1 +1 @@ -Subproject commit bf2608775a4bbddd2dbdd757a3c6de9113a32865 +Subproject commit 30d80024a646f6897fcbc59a3305910159bc4a54 diff --git a/src/test/resources/backend-submodule b/src/test/resources/backend-submodule index bf26087..30d8002 160000 --- a/src/test/resources/backend-submodule +++ b/src/test/resources/backend-submodule @@ -1 +1 @@ -Subproject commit bf2608775a4bbddd2dbdd757a3c6de9113a32865 +Subproject commit 30d80024a646f6897fcbc59a3305910159bc4a54 From 87138a45b9870dbb737021d4aab4c17b0c09cfe4 Mon Sep 17 00:00:00 2001 From: sungjindev Date: Mon, 19 Feb 2024 15:15:39 +0900 Subject: [PATCH 57/57] =?UTF-8?q?fix=20:=20docker-compose=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=97=90=EC=84=9C=20Redis=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/backend-submodule | 2 +- src/test/resources/backend-submodule | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule index 30d8002..c741a71 160000 --- a/src/main/resources/backend-submodule +++ b/src/main/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 30d80024a646f6897fcbc59a3305910159bc4a54 +Subproject commit c741a71530d128886428736a347039c2d406bebc diff --git a/src/test/resources/backend-submodule b/src/test/resources/backend-submodule index 30d8002..c741a71 160000 --- a/src/test/resources/backend-submodule +++ b/src/test/resources/backend-submodule @@ -1 +1 @@ -Subproject commit 30d80024a646f6897fcbc59a3305910159bc4a54 +Subproject commit c741a71530d128886428736a347039c2d406bebc