diff --git a/.github/workflows/action-develop-cd.yml b/.github/workflows/action-develop-cd.yml
index 32f0522..aef7857 100644
--- a/.github/workflows/action-develop-cd.yml
+++ b/.github/workflows/action-develop-cd.yml
@@ -40,14 +40,14 @@ jobs:
chmod +x ./gradlew
./gradlew clean build -x test
- # 도커 컴포즈 설정 파일 서버(EC2)로 전달
- - name: Send docker-compose.yml
+ # 설정 파일 서버(EC2)로 전달
+ - name: Send docker-compose.yml and nginx.conf
uses: appleboy/scp-action@master
with:
username: ubuntu
host: ${{ secrets.KCS_HOST_DEV }}
key: ${{ secrets.KCS_KEY_DEV }}
- source: "src/main/resources/backend-submodule/docker-compose-dev.yml"
+ source: "src/main/resources/backend-submodule/docker-compose-dev.yml,./nginx/nginx.conf"
target: "/home/ubuntu/"
# Docker hub 로그인
@@ -57,8 +57,8 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME}}
password: ${{ secrets.DOCKER_TOKEN}}
- # Docker Hub 에 푸시
- - name: Build and push
+ # Docker Hub 에 Springboot 푸시
+ - name: Build and push springboot
uses: docker/build-push-action@v4
with:
context: .
diff --git a/.github/workflows/action-production-cd.yml b/.github/workflows/action-production-cd.yml
index 39b7d02..6068852 100644
--- a/.github/workflows/action-production-cd.yml
+++ b/.github/workflows/action-production-cd.yml
@@ -40,14 +40,14 @@ jobs:
chmod +x ./gradlew
./gradlew clean build -x test
- # 도커 컴포즈 설정 파일 서버로 전달
- - name: Send docker-compose.yml
+ # 설정 파일 서버로 전달
+ - name: Send docker-compose.yml and nginx
uses: appleboy/scp-action@master
with:
username: ${{ secrets.KCS_USERNAME_PROD }}
host: ${{ secrets.KCS_HOST_PROD }}
key: ${{ secrets.KCS_KEY_PROD }}
- source: "src/main/resources/backend-submodule/docker-compose.yml"
+ source: "src/main/resources/backend-submodule/docker-compose.yml,src/main/resources/backend-submodule/nginx/nginx.conf"
target: "/home/g22203/"
# Docker hub 로그인
@@ -79,8 +79,9 @@ jobs:
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
sudo docker pull {{ secrets.DOCKER_REPOSITORY_PROD }}:latest
- docker-compose -f docker-compose-dev.yml down
+ docker-compose -f docker-compose.yml down
docker rmi $(docker images -q)
cp -f ./src/main/resources/backend-submodule/docker-compose.yml .
+ cp -rf ./src/main/resources/backend-submodule/nginx .
rm -r src
docker-compose -f docker-compose.yml up -d
\ No newline at end of file
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
new file mode 100644
index 0000000..a0f3503
--- /dev/null
+++ b/nginx/nginx.conf
@@ -0,0 +1,41 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+
+ # -------------------- spring-boot-dev WAS --------------------
+ upstream backend {
+ server server:8080;
+ }
+
+ server {
+ listen 80;
+ server_name dev.nainga.store;
+
+ # certbot 이 소유자임을 확인하는 경로
+ location /.well-known/acme-challenge {
+ root /var/lib/letsencrypt/; # 사용자 인증을 위한 파일이 생성 되는곳
+ }
+
+ # Redirect all traffic to HTTPS
+ location / {
+ return 301 https://$host$request_uri;
+ }
+ }
+
+ server {
+ listen 443 ssl;
+ server_name dev.nainga.store;
+
+ ssl_certificate /etc/letsencrypt/live/dev.nainga.store/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/dev.nainga.store/privkey.pem;
+
+ location / {
+ proxy_pass http://backend;
+ proxy_set_header Host $host; # 클라이언트가 요청한 호스트의 도메인
+ proxy_set_header X-Real-IP $remote_addr; # 클라이언트의 실제 IP 주소
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 원격 클라이언트의 실제 IP 주소와, 이전에 거친 프록시 서버의 IP 주소들이 쉼표로 구분되어 포함
+ }
+ }
+}
diff --git a/src/main/java/com/nainga/nainga/domain/storecertification/api/StoreCertificationApi.java b/src/main/java/com/nainga/nainga/domain/storecertification/api/StoreCertificationApi.java
index f0cad01..4d4dcad 100644
--- a/src/main/java/com/nainga/nainga/domain/storecertification/api/StoreCertificationApi.java
+++ b/src/main/java/com/nainga/nainga/domain/storecertification/api/StoreCertificationApi.java
@@ -43,9 +43,9 @@ public class StoreCertificationApi {
"certificationName: 가게의 인증제 목록
" +
"=> 각 인증제별 순서는 보장되지 않습니다.")
@GetMapping("api/v1/storecertification/byLocation")
- public Result> findStoreCertificationsByLocation(@RequestParam("nwLong") double nwLong, @RequestParam("nwLat") double nwLat, @RequestParam("seLong") double seLong, @RequestParam("seLat") double seLat) {
- List storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(new Location(nwLong, nwLat), new Location(seLong, seLat));
- List storeIdsWithMultipleCertifications = storeCertificationService.findStoreIdsWithMultipleCertifications(); //여러 인증제를 가지고 있는 가게의 id 리스트
+ public Result> findStoreCertificationsByLocation(@RequestParam("nwLong") double nwLong, @RequestParam("nwLat") double nwLat, @RequestParam("swLong") double swLong, @RequestParam("swLat") double swLat, @RequestParam("seLong") double seLong, @RequestParam("seLat") double seLat, @RequestParam("neLong") double neLong, @RequestParam("neLat") double neLat) {
+ List storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(new Location(nwLong, nwLat), new Location(swLong, swLat), new Location(seLong, seLat), new Location(neLong, neLat));
+ List storeIdsWithMultipleCertifications = storeCertificationService.getDuplicatedStoreIds(); //여러 인증제를 가지고 있는 가게의 id 리스트
List storeCertificationsByLocationResponses = new ArrayList<>(); //반환해줄 StoreCertificationsByLocationResponse들의 List
Map map = new HashMap<>(); //여러 인증제를 가지고 있는 가게들의 response를 임시로 저장하고 있을 map
diff --git a/src/main/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationService.java b/src/main/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationService.java
index c0b29e7..1390ef4 100644
--- a/src/main/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationService.java
+++ b/src/main/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationService.java
@@ -3,35 +3,49 @@
import com.nainga.nainga.domain.store.domain.Location;
import com.nainga.nainga.domain.storecertification.dao.StoreCertificationRepository;
import com.nainga.nainga.domain.storecertification.domain.StoreCertification;
+import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
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.stream.Collectors;
@Service
@Transactional(readOnly = true)
-@RequiredArgsConstructor
public class StoreCertificationService {
private final StoreCertificationRepository storeCertificationRepository;
- public List findStoreCertificationsByLocation(Location northWestLocation, Location southEastLocation) {
- return storeCertificationRepository.findStoreCertificationsByLocation(northWestLocation, southEastLocation);
+ private List duplicatedStoreIds; //여러 인증제를 가지는 중복된 storeId를 담고있는 리스트
+
+ @Autowired
+ public StoreCertificationService(StoreCertificationRepository storeCertificationRepository) {
+ this.storeCertificationRepository = storeCertificationRepository;
}
- public List findStoreIdsWithMultipleCertifications() {
+ @PostConstruct
+ public void init() { //이 Service Bean이 생성된 이후에 한번만 중복된 storeId를 검사해서 Globally하게 저장
List allStoreCertifications = storeCertificationRepository.findAll(); //중복된 id를 검사하기 위함
- List allStoreIds = new ArrayList<>();
+
+ HashSet uniqueStoreIds = new HashSet<>(); //조회 성능을 높이기 위해 HashSet으로 저장
+ HashSet duplicatedIds = new HashSet<>();
+
for (StoreCertification storeCertification : allStoreCertifications) {
- allStoreIds.add(storeCertification.getStore().getId());
+ Long storeId = storeCertification.getStore().getId();
+ if (!uniqueStoreIds.add(storeId)) { //HashSet에 add를 했을 때 이미 존재하는 데이터면 false가 리턴되는 것을 활용
+ duplicatedIds.add(storeId);
+ }
}
+ duplicatedStoreIds = new ArrayList<>(duplicatedIds);
+ }
- List duplicatedIds = allStoreIds.stream()
- .filter(e -> allStoreIds.indexOf(e) != allStoreIds.lastIndexOf(e)) //중복된 StoreId가 있는 경우
- .distinct() //해당 id를 모아서 1번씩만(중복 제거) 리스트에 담아 전달
- .collect(Collectors.toList());
+ public List findStoreCertificationsByLocation(Location northWestLocation, Location southWestLocation, Location southEastLocation, Location northEastLocation) {
+ return storeCertificationRepository.findStoreCertificationsByLocation(northWestLocation, southWestLocation, southEastLocation, northEastLocation);
+ }
- return duplicatedIds;
+ public List getDuplicatedStoreIds() {
+ return duplicatedStoreIds;
}
}
diff --git a/src/main/java/com/nainga/nainga/domain/storecertification/dao/StoreCertificationRepository.java b/src/main/java/com/nainga/nainga/domain/storecertification/dao/StoreCertificationRepository.java
index c7cea75..5f8df13 100644
--- a/src/main/java/com/nainga/nainga/domain/storecertification/dao/StoreCertificationRepository.java
+++ b/src/main/java/com/nainga/nainga/domain/storecertification/dao/StoreCertificationRepository.java
@@ -39,14 +39,14 @@ public Optional findByStoreIdCertificationId(Long storeId, L
return result.stream().findAny();
}
- //북서쪽 좌표와 남동쪽 좌표를 받아 그 두 좌표로 만들어지는 최소 사각형 내에 위치하는 가게들 리턴
- public List findStoreCertificationsByLocation(Location northWestLocation, Location southEastLocation) {
+ //북서쪽 좌표, 남서쪽 좌표, 남동쪽 좌표, 북동쪽 좌표를 받아 그 네 좌표로 만들어지는 사각형 영역 내에 위치하는 가게들 리턴
+ public List findStoreCertificationsByLocation(Location northWestLocation, Location southWestLocation, Location southEastLocation, Location northEastLocation) {
String pointFormat = String.format(
- "'LINESTRING(%f %f, %f %f)'", //POINT는 (경도, 위도) 순이다. 즉, (Logitude, Latitude)순
- northWestLocation.getLongitude(), northWestLocation.getLatitude(), southEastLocation.getLongitude(), southEastLocation.getLatitude()
+ "'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))'", //POINT는 (경도, 위도) 순이다. 즉, (Logitude, Latitude)순
+ northWestLocation.getLongitude(), northWestLocation.getLatitude(), southWestLocation.getLongitude(), southWestLocation.getLatitude(), southEastLocation.getLongitude(), southEastLocation.getLatitude(), northEastLocation.getLongitude(), northEastLocation.getLatitude(), northWestLocation.getLongitude(), northWestLocation.getLatitude()
);
- Query query = em.createNativeQuery("SELECT sc.* " + "FROM store_certification AS sc " + "JOIN store AS s ON sc.store_id = s.store_id " + "JOIN certification AS c ON sc.certification_id = c.certification_id " + "WHERE MBRCONTAINS(ST_LINESTRINGFROMTEXT(" + pointFormat + "), s.location)", StoreCertification.class);
+ Query query = em.createNativeQuery("SELECT sc.* " + "FROM store_certification AS sc " + "JOIN store AS s ON sc.store_id = s.store_id " + "JOIN certification AS c ON sc.certification_id = c.certification_id " + "WHERE ST_CONTAINS(ST_POLYGONFROMTEXT(" + pointFormat + "), s.location)", StoreCertification.class);
return query.getResultList();
}
diff --git a/src/main/java/com/nainga/nainga/global/config/SwaggerConfig.java b/src/main/java/com/nainga/nainga/global/config/SwaggerConfig.java
new file mode 100644
index 0000000..0472428
--- /dev/null
+++ b/src/main/java/com/nainga/nainga/global/config/SwaggerConfig.java
@@ -0,0 +1,43 @@
+package com.nainga.nainga.global.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.media.Content;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import io.swagger.v3.oas.models.tags.Tag;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ //Google Cloud Storage API는 별도로 Swagger에 명세
+ return new OpenAPI()
+ .paths(new Paths().addPathItem("https://storage.googleapis.com/{BUCKET_NAME}/{IMAGE_NAME}",
+ new PathItem().get(new Operation().summary("저장된 가게 이미지 제공")
+ .description("저장된 가게 이미지를 제공하는 API입니다.
" +
+ "본 API는 Google Cloud Storage에서 제공하는 API로 URL이 위와 같으며 이 정보는 각 가게별 local_photos 필드에 저장되어 있습니다.
" +
+ "Dev 환경에서 BUCKET_NAME은 kcs-dev-bucket1이고 Prod 환경에서 BUCKET_NAME은 kcs-prod-bucket1입니다.
" +
+ "가게 이름은 UUID를 활용한 난수로 제공됩니다.
" +
+ "참고로 Swagger 상에서는 Base URL이 달라 테스트가 불가능합니다.
" +
+ "만약 테스트를 원하신다면 브라우저 상에서 직접 URL을 입력해주시면 됩니다.
" +
+ "예) https://storage.googleapis.com/kcs-dev-bucket1/ad06294c-d4ed-42bd-9839-82af8714bd1e")
+ .tags(List.of("가게 상세 정보"))
+ .responses(new ApiResponses().addApiResponse("200",
+ new ApiResponse().description("OK")
+ .content(new Content().addMediaType("image/jpeg", new MediaType()
+ .schema(new Schema<>().type("string")
+ .format("binary")))))))));
+ }
+}
+
diff --git a/src/main/resources/backend-submodule b/src/main/resources/backend-submodule
index 614a5b7..67e3a62 160000
--- a/src/main/resources/backend-submodule
+++ b/src/main/resources/backend-submodule
@@ -1 +1 @@
-Subproject commit 614a5b75a7bd04035791b642f83a7c72e21dba6f
+Subproject commit 67e3a628d394ee510efccd846b3e70e63ce63e42
diff --git a/src/test/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationServiceTest.java b/src/test/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationServiceTest.java
index 68b9b67..142b24e 100644
--- a/src/test/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationServiceTest.java
+++ b/src/test/java/com/nainga/nainga/domain/storecertification/application/StoreCertificationServiceTest.java
@@ -55,9 +55,11 @@ void findStoreCertificationsByLocation() {
}
//when
- Location location1 = new Location(minLongitude - 1.0, minLatitude - 1.0);
- Location location2 = new Location(maxLongitude + 1.0, maxLatitude + 1.0);
- List storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(location1, location2);
+ Location location1 = new Location(minLongitude - 1.0, maxLatitude + 1.0);
+ Location location2 = new Location(minLongitude - 1.0, minLatitude - 1.0);
+ Location location3 = new Location(maxLongitude + 1.0, minLatitude - 1.0);
+ Location location4 = new Location(maxLongitude + 1.0, maxLatitude + 1.0);
+ List storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(location1, location2, location3, location4);
//then
assertThat(storeCertificationsByLocation.size()).isEqualTo(stores.size());
diff --git a/src/test/resources/backend-submodule b/src/test/resources/backend-submodule
index 614a5b7..67e3a62 160000
--- a/src/test/resources/backend-submodule
+++ b/src/test/resources/backend-submodule
@@ -1 +1 @@
-Subproject commit 614a5b75a7bd04035791b642f83a7c72e21dba6f
+Subproject commit 67e3a628d394ee510efccd846b3e70e63ce63e42