Skip to content

Commit

Permalink
Merge pull request #88 from Korea-Certified-Store/develop
Browse files Browse the repository at this point in the history
Develop to Main 릴리즈 (#87)
  • Loading branch information
sungjindev authored Feb 7, 2024
2 parents 5770c90 + 92c51bc commit 8a2155e
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 12 deletions.
13 changes: 10 additions & 3 deletions .github/workflows/action-develop-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
branches:
- develop

# 코드의 내용을 이 파일을 실행하여 action을 수행하는 주체(Github Actions에서 사용하는 VM)가 읽을 수 있도록 권한을 설정
# 코드의 내용을 이 파일을 실행하여 action을 수행하는 주체(Github Actions에서 사용하는 VM)가 읽을 수 있도록 권한을 설정
permissions:
contents: read

Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:
username: ubuntu
host: ${{ secrets.KCS_HOST_DEV }}
key: ${{ secrets.KCS_KEY_DEV }}
source: "src/main/resources/backend-submodule/docker-compose-dev.yml,./nginx/nginx.conf"
source: "src/main/resources/backend-submodule,./nginx/nginx.conf"
target: "/home/ubuntu/"

# Docker hub 로그인
Expand All @@ -68,6 +68,14 @@ jobs:
cache-from: type=gha
cache-to: type=gha, mode=max

# Discord 에 알람 보내기
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
with:
args: '{{ EVENT_PAYLOAD.repository.full_name }} 가 배포 되었 습니다. 개발 서버가 재시작 됩니다.'

# appleboy/ssh-action@master 액션을 사용하여 지정한 서버에 ssh로 접속하고, script를 실행합니다.
# 실행 시, docker-compose를 사용합니다.
# useranme : ubuntu 우분투 기반 ec2 일 경우 기본이름
Expand All @@ -83,5 +91,4 @@ jobs:
docker-compose -f docker-compose-dev.yml down
docker rmi $(docker images -q)
cp -f ./src/main/resources/backend-submodule/docker-compose-dev.yml .
rm -r src
docker-compose -f docker-compose-dev.yml up -d
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ out/
env.yml

### 로그 파일 ###
/src/main/resources/logs/
/src/main/resources/logs/
/logs
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ dependencies {
implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter', version: '1.2.8.RELEASE'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-storage', version: '1.2.8.RELEASE'

// for JMX 모니터링
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// for /actuator/prometheus 엔드포인트 제공을 받기위한 의존성 추가
implementation 'io.micrometer:micrometer-registry-prometheus'

// JsonParser를 사용하기 위해 추가
dependencies { implementation 'com.google.code.gson:gson:2.8.8' }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,4 @@ public List<StoreRegularOpeningHours> parseWeekdayDescriptions(List<String> week




Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,4 @@ public List<StoreRegularOpeningHours> parseWeekdayDescriptions(List<String> week
return storeRegularOpeningHoursList;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,4 @@ public List<StoreRegularOpeningHours> parseWeekdayDescriptions(List<String> week
return storeRegularOpeningHoursList;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ public class Store {
@ElementCollection
private List<String> googlePhotos; //나중에 Google Map API에서 더 가져와야하는 사진들
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

@RestController
@RequiredArgsConstructor
public class StoreCertificationApi {
private final StoreCertificationService storeCertificationService;

//북서쪽, 남동쪽 좌표를 받아 두 좌표로 만들어지는 가장 작은 사각형 내 모든 가게 상세 정보를 반환
//총 사각형 영역의 네 꼭짓점 좌표를 받아, 해당 가게들의 상세 정보를 반환
@Tag(name = "가게 상세 정보")
@Operation(summary = "사용자 위치 기반 가게 상세 정보 제공", description = "사용자 위치를 기준으로 좌상단, 우하단 위도 경도 좌표를 넘겨 받아 두 좌표로 만들 수 있는 최소 크기의 사각형 범위 내 모든 가게의 상세 정보를 전달해줍니다.<br><br>" +
@Operation(summary = "사용자 위치 기반 가게 상세 정보 제공 V1", description = "총 사각형 영역의 네 꼭짓점 좌표를 받아 해당 가게들의 상세 정보를 반환해줍니다.<br><br>" +
"[Request Body]<br>" +
"nwLong: 북서쪽 좌표 경도<br>" +
"nwLat: 북서쪽 좌표 위도<br>" +
Expand Down Expand Up @@ -68,4 +69,30 @@ public Result<List<StoreCertificationsByLocationResponse>> findStoreCertificatio

return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, storeCertificationsByLocationResponses);
}
}

//총 사각형 영역의 네 꼭짓점 좌표를 받아 최대 75개를 랜덤하게 뽑아낸 뒤, 해당 가게들의 상세 정보를 반환
@Tag(name = "가게 상세 정보")
@Operation(summary = "사용자 위치 기반 가게 상세 정보 제공 V2", description = "총 사각형 영역의 네 꼭짓점 좌표를 받아 최대 75개를 랜덤하게 뽑아낸 뒤, 해당 가게들의 상세 정보를 반환해줍니다.<br><br>" +
"[Request Body]<br>" +
"nwLong: 북서쪽 좌표 경도<br>" +
"nwLat: 북서쪽 좌표 위도<br>" +
"seLong: 남동쪽 좌표 경도<br>" +
"seLat: 남동쪽 좌표 위도<br><br>" +
"[Response Body]<br>" +
"id: Database 내 Primary Key값<br>" +
"displayName: 가게 이름<br>" +
"primaryTypeDisplayName: 업종<br>" +
"formattedAddress: 주소<br>" +
"phoneNumber: 전화번호<br>" +
"location: (경도, 위도) 가게 좌표<br>" +
"regularOpeningHours: 영업 시간<br>" +
"=> 특정 요일이 휴무인 경우에는 해당 요일에 대한 데이터가 들어있지 않습니다. Break time이 있는 경우 동일한 요일에 대해 영업 시간 데이터가 여러 개 존재할 수 있습니다. <br>" +
"localPhotos: 저장된 가게 사진 URL<br>" +
"certificationName: 가게의 인증제 목록<br>" +
"=> 각 인증제별 순서는 보장되지 않습니다.")
@GetMapping("api/v2/storecertification/byLocation")
public Result<List<List<StoreCertificationsByLocationResponse>>> findStoreCertificationsByLocationRandomly(@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<List<StoreCertificationsByLocationResponse>> storeCertificationsByLocationRandomly = storeCertificationService.findStoreCertificationsByLocationRandomly(new Location(nwLong, nwLat), new Location(swLong, swLat), new Location(seLong, seLat), new Location(neLong, neLat));
return new Result<>(Result.CODE_SUCCESS, Result.MESSAGE_OK, storeCertificationsByLocationRandomly);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
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 com.nainga.nainga.domain.storecertification.dto.StoreCertificationsByLocationResponse;
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.*;
import java.util.stream.Collectors;

@Service
Expand Down Expand Up @@ -45,6 +44,52 @@ public List<StoreCertification> findStoreCertificationsByLocation(Location north
return storeCertificationRepository.findStoreCertificationsByLocation(northWestLocation, southWestLocation, southEastLocation, northEastLocation);
}

public List<List<StoreCertificationsByLocationResponse>> findStoreCertificationsByLocationRandomly(Location northWestLocation, Location southWestLocation, Location southEastLocation, Location northEastLocation) {
List<StoreCertification> storeCertificationsByLocation = storeCertificationRepository.findStoreCertificationsByLocationRandomly(northWestLocation, southWestLocation, southEastLocation, northEastLocation);
List<StoreCertificationsByLocationResponse> storeCertificationsByLocationResponses = new ArrayList<>(); //반환해줄 StoreCertificationsByLocationResponse들의 List

Map<Long, Boolean> isChecked = new HashMap<>(); //이미 조회한 가게인지 여부를 저장하는 HashMap

for (StoreCertification storeCertification : storeCertificationsByLocation) {
if (!duplicatedStoreIds.contains(storeCertification.getStore().getId())) {
storeCertificationsByLocationResponses.add(new StoreCertificationsByLocationResponse(storeCertification));
} else {
Boolean result = isChecked.get(storeCertification.getStore().getId());
if (result == null) {
StoreCertificationsByLocationResponse storeCertificationsByLocationResponse = new StoreCertificationsByLocationResponse(storeCertification);

List<StoreCertification> storeCertificationsByStoreId = storeCertificationRepository.findStoreCertificationsByStoreId(storeCertification.getStore().getId());
for (StoreCertification storeCertificationByStoreId : storeCertificationsByStoreId) { //위에서 이미 추가해준 인증제 이름일 경우 제외
if(!storeCertificationByStoreId.getCertification().getName().equals(storeCertification.getCertification().getName()))
storeCertificationsByLocationResponse.getCertificationName().add(storeCertificationByStoreId.getCertification().getName());
}

storeCertificationsByLocationResponses.add(storeCertificationsByLocationResponse);
isChecked.put(storeCertification.getStore().getId(), true); //체크되었다고 기록
}
}
}

List<List<StoreCertificationsByLocationResponse>> storeCertificationsByLocationListResponses = new ArrayList<>();

List<StoreCertificationsByLocationResponse> subArray = new ArrayList<>();
for(int i=1; i <= storeCertificationsByLocationResponses.size(); ++i) {
subArray.add(storeCertificationsByLocationResponses.get(i-1));

if (i == 75 || i == storeCertificationsByLocationResponses.size()) { //요구사항에 따른 가게 최대 개수가 75개 이므로, 0부터 74까지만!
storeCertificationsByLocationListResponses.add(subArray);
break;
}

if (i % 15 == 0) { //15개씩 1회차를 나눠주기 위해
storeCertificationsByLocationListResponses.add(subArray);
subArray = new ArrayList<>();
}
}

return storeCertificationsByLocationListResponses;
}

public List<Long> getDuplicatedStoreIds() {
return duplicatedStoreIds;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.nainga.nainga.domain.storecertification.domain.StoreCertification;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

Expand Down Expand Up @@ -50,4 +51,32 @@ public List<StoreCertification> findStoreCertificationsByLocation(Location north

return query.getResultList();
}

//북서쪽 좌표, 남서쪽 좌표, 남동쪽 좌표, 북동쪽 좌표를 받아 그 네 좌표로 만들어지는 사각형 영역 내에 위치하는 가게들 리턴
public List<StoreCertification> findStoreCertificationsByLocationRandomly(Location northWestLocation, Location southWestLocation, Location southEastLocation, Location northEastLocation) {
String pointFormat = String.format(
"'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()
);

TypedQuery<StoreCertification> query = em.createQuery(
"SELECT sc FROM StoreCertification sc " +
"JOIN FETCH sc.store s " +
"JOIN FETCH sc.certification c " +
"WHERE ST_CONTAINS(ST_POLYGONFROMTEXT(" + pointFormat + "), s.location) ORDER BY RAND() LIMIT 225",
StoreCertification.class);

return query.getResultList();
}

public List<StoreCertification> findStoreCertificationsByStoreId(Long storeId) { //storeId를 통해 관련된 모든 StoreCertification 조회
TypedQuery<StoreCertification> query = em.createQuery(
"SELECT sc FROM StoreCertification sc " +
"JOIN FETCH sc.certification c " +
"WHERE sc.store.id = :storeId", StoreCertification.class
).setParameter("storeId", storeId);

return query.getResultList();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ public StoreCertificationsByLocationResponse(StoreCertification storeCertificati
this.certificationName.add(storeCertification.getCertification().getName());
}
}

47 changes: 47 additions & 0 deletions src/main/java/com/nainga/nainga/global/aspect/LogAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.nainga.nainga.global.aspect;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Order(1)
@Component
public class LogAspect {

@Pointcut("bean(*Service)")
private void allService() {
}

@Pointcut("bean(*Api)")
private void allRequest() {
}

@AfterThrowing(pointcut = "allService()", throwing = "ex")
private void logException(JoinPoint joinPoint, RuntimeException ex) {
String Name = ex.getClass().getSimpleName();
String Message = ex.getMessage();
String methodName = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
log.warn("[Exception] {} Name=[{}] Message=[{}] args=[{}]",
methodName, Name, Message, args);
}

@Around("allRequest()")
private Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
long beforeRequest = System.currentTimeMillis();
String methodName = joinPoint.getSignature().toShortString();
Object result = joinPoint.proceed();
long timeTaken = System.currentTimeMillis() - beforeRequest;
log.debug("[Request] {} time=[{}ms]", methodName, timeTaken);
return result;
}
}
87 changes: 87 additions & 0 deletions src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<configuration>

<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

<property name="CUSTOM_CONSOLE_LOG_PATTERN" value="[%X{request_id:-nainga}] ${CONSOLE_LOG_PATTERN}"/>
<property name="CUSTOM_FILE_LOG_PATTERN" value="[%X{request_id:-nainga}] ${FILE_LOG_PATTERN}"/>

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CUSTOM_CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>

<appender name="FILE-DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${CUSTOM_FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/debug/debug-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>

<appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${CUSTOM_FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/info/info-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>

<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${CUSTOM_FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/warn/warn-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>

<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${CUSTOM_FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/error/error-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>

<logger name="com.nainga.nainga" level="DEBUG" />
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE-DEBUG"/>
<appender-ref ref="FILE-INFO"/>
<appender-ref ref="FILE-WARN"/>
<appender-ref ref="FILE-ERROR"/>
</root>
</configuration>
Loading

0 comments on commit 8a2155e

Please sign in to comment.