Skip to content

Commit

Permalink
Merge pull request #72 from Korea-Certified-Store/develop
Browse files Browse the repository at this point in the history
Develop to Main 릴리즈 (#71)
  • Loading branch information
sungjindev authored Jan 20, 2024
2 parents 4ed46a4 + f492cc7 commit 00a4ad5
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 33 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/action-develop-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 로그인
Expand All @@ -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: .
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/action-production-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 로그인
Expand Down Expand Up @@ -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
41 changes: 41 additions & 0 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -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 주소들이 쉼표로 구분되어 포함
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public class StoreCertificationApi {
"certificationName: 가게의 인증제 목록<br>" +
"=> 각 인증제별 순서는 보장되지 않습니다.")
@GetMapping("api/v1/storecertification/byLocation")
public Result<List<StoreCertificationsByLocationResponse>> findStoreCertificationsByLocation(@RequestParam("nwLong") double nwLong, @RequestParam("nwLat") double nwLat, @RequestParam("seLong") double seLong, @RequestParam("seLat") double seLat) {
List<StoreCertification> storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(new Location(nwLong, nwLat), new Location(seLong, seLat));
List<Long> storeIdsWithMultipleCertifications = storeCertificationService.findStoreIdsWithMultipleCertifications(); //여러 인증제를 가지고 있는 가게의 id 리스트
public Result<List<StoreCertificationsByLocationResponse>> 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<StoreCertification> storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(new Location(nwLong, nwLat), new Location(swLong, swLat), new Location(seLong, seLat), new Location(neLong, neLat));
List<Long> storeIdsWithMultipleCertifications = storeCertificationService.getDuplicatedStoreIds(); //여러 인증제를 가지고 있는 가게의 id 리스트
List<StoreCertificationsByLocationResponse> storeCertificationsByLocationResponses = new ArrayList<>(); //반환해줄 StoreCertificationsByLocationResponse들의 List
Map<Long, StoreCertificationsByLocationResponse> map = new HashMap<>(); //여러 인증제를 가지고 있는 가게들의 response를 임시로 저장하고 있을 map

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<StoreCertification> findStoreCertificationsByLocation(Location northWestLocation, Location southEastLocation) {
return storeCertificationRepository.findStoreCertificationsByLocation(northWestLocation, southEastLocation);
private List<Long> duplicatedStoreIds; //여러 인증제를 가지는 중복된 storeId를 담고있는 리스트

@Autowired
public StoreCertificationService(StoreCertificationRepository storeCertificationRepository) {
this.storeCertificationRepository = storeCertificationRepository;
}

public List<Long> findStoreIdsWithMultipleCertifications() {
@PostConstruct
public void init() { //이 Service Bean이 생성된 이후에 한번만 중복된 storeId를 검사해서 Globally하게 저장
List<StoreCertification> allStoreCertifications = storeCertificationRepository.findAll(); //중복된 id를 검사하기 위함
List<Long> allStoreIds = new ArrayList<>();

HashSet<Long> uniqueStoreIds = new HashSet<>(); //조회 성능을 높이기 위해 HashSet으로 저장
HashSet<Long> 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<Long> duplicatedIds = allStoreIds.stream()
.filter(e -> allStoreIds.indexOf(e) != allStoreIds.lastIndexOf(e)) //중복된 StoreId가 있는 경우
.distinct() //해당 id를 모아서 1번씩만(중복 제거) 리스트에 담아 전달
.collect(Collectors.toList());
public List<StoreCertification> findStoreCertificationsByLocation(Location northWestLocation, Location southWestLocation, Location southEastLocation, Location northEastLocation) {
return storeCertificationRepository.findStoreCertificationsByLocation(northWestLocation, southWestLocation, southEastLocation, northEastLocation);
}

return duplicatedIds;
public List<Long> getDuplicatedStoreIds() {
return duplicatedStoreIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ public Optional<StoreCertification> findByStoreIdCertificationId(Long storeId, L
return result.stream().findAny();
}

//북서쪽 좌표와 남동쪽 좌표를 받아 그 좌표로 만들어지는 최소 사각형 내에 위치하는 가게들 리턴
public List<StoreCertification> findStoreCertificationsByLocation(Location northWestLocation, Location southEastLocation) {
//북서쪽 좌표, 남서쪽 좌표, 남동쪽 좌표, 북동쪽 좌표를 받아 그 좌표로 만들어지는 사각형 영역 내에 위치하는 가게들 리턴
public List<StoreCertification> 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();
}
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/com/nainga/nainga/global/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -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입니다.<br>" +
"본 API는 Google Cloud Storage에서 제공하는 API로 URL이 위와 같으며 이 정보는 각 가게별 local_photos 필드에 저장되어 있습니다.<br>" +
"Dev 환경에서 BUCKET_NAME은 kcs-dev-bucket1이고 Prod 환경에서 BUCKET_NAME은 kcs-prod-bucket1입니다.<br>" +
"가게 이름은 UUID를 활용한 난수로 제공됩니다.<br>" +
"참고로 Swagger 상에서는 Base URL이 달라 테스트가 불가능합니다.<br>" +
"만약 테스트를 원하신다면 브라우저 상에서 직접 URL을 입력해주시면 됩니다.<br>" +
"예) 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")))))))));
}
}

2 changes: 1 addition & 1 deletion src/main/resources/backend-submodule
Original file line number Diff line number Diff line change
Expand Up @@ -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<StoreCertification> 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<StoreCertification> storeCertificationsByLocation = storeCertificationService.findStoreCertificationsByLocation(location1, location2, location3, location4);

//then
assertThat(storeCertificationsByLocation.size()).isEqualTo(stores.size());
Expand Down
2 changes: 1 addition & 1 deletion src/test/resources/backend-submodule

0 comments on commit 00a4ad5

Please sign in to comment.