diff --git a/.github/workflows/be-cd.yml b/.github/workflows/be-cd.yml index a490f33..0913c5e 100644 --- a/.github/workflows/be-cd.yml +++ b/.github/workflows/be-cd.yml @@ -65,6 +65,6 @@ jobs: username: ${{ secrets.BE_SERVER_USER }} key: ${{ secrets.BE_SERVER_KEY }} script: | - cd ~/myapp + cd ../Shared/Team2-Getit/uniro_backend chmod +x ./deploy.sh ./deploy.sh diff --git a/uniro_backend/build.gradle b/uniro_backend/build.gradle index 7110d5c..5e08fd1 100644 --- a/uniro_backend/build.gradle +++ b/uniro_backend/build.gradle @@ -18,6 +18,10 @@ configurations { compileOnly { extendsFrom annotationProcessor } + + all { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + } } repositories { @@ -88,12 +92,18 @@ dependencies { implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' implementation 'com.auth0:java-jwt:4.4.0' - // redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // cache implementation 'org.springframework.boot:spring-boot-starter-cache' - implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' - implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate6' - implementation 'com.alibaba.fastjson2:fastjson2-extension-spring6:2.0.55' + implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' + + // fastjson + implementation 'com.alibaba.fastjson2:fastjson2-extension-spring6:2.0.56' + + // log4j2 + implementation "org.springframework.boot:spring-boot-starter-log4j2" // Spring Boot Log4j2 + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" // Jackson Dataforamt yaml + implementation "com.lmax:disruptor:3.4.4" + } tasks.named('test') { diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java index caf1013..4ede819 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/admin/service/AdminService.java @@ -1,6 +1,5 @@ package com.softeer5.uniro_backend.admin.service; -import static com.softeer5.uniro_backend.common.constant.UniroConst.*; import static com.softeer5.uniro_backend.common.error.ErrorCode.*; import com.softeer5.uniro_backend.admin.annotation.DisableAudit; @@ -13,7 +12,6 @@ import com.softeer5.uniro_backend.common.exception.custom.AdminException; import com.softeer5.uniro_backend.common.exception.custom.RouteException; import com.softeer5.uniro_backend.common.exception.custom.UnivException; -import com.softeer5.uniro_backend.external.redis.RedisService; import com.softeer5.uniro_backend.map.dto.response.AllRoutesInfo; import com.softeer5.uniro_backend.map.dto.response.GetChangedRoutesByRevisionResDTO; import com.softeer5.uniro_backend.map.dto.response.GetRiskRoutesResDTO; @@ -22,6 +20,7 @@ import com.softeer5.uniro_backend.map.entity.Route; import com.softeer5.uniro_backend.map.repository.NodeRepository; import com.softeer5.uniro_backend.map.repository.RouteRepository; +import com.softeer5.uniro_backend.map.cache.RouteCacheService; import com.softeer5.uniro_backend.map.service.RouteCalculator; import com.softeer5.uniro_backend.univ.entity.Univ; import com.softeer5.uniro_backend.univ.repository.UnivRepository; @@ -47,7 +46,7 @@ public class AdminService { private final RouteCalculator routeCalculator; - private final RedisService redisService; + private final RouteCacheService cacheService; public List getAllRevInfo(Long univId){ @@ -114,8 +113,7 @@ public void rollbackRev(Long univId, Long versionId){ } } - int routeCount = routes.size(); - redisService.deleteRoutesData(univId.toString(), routeCount / STREAM_FETCH_SIZE + 1); + cacheService.deleteFetchSizeAndLightRoutesByUnivId(univId); } public GetAllRoutesByRevisionResDTO getAllRoutesByRevision(Long univId, Long versionId){ diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/CaffeineConfig.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/CaffeineConfig.java new file mode 100644 index 0000000..fad3d6e --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/CaffeineConfig.java @@ -0,0 +1,29 @@ +package com.softeer5.uniro_backend.common.config; + +import java.util.concurrent.TimeUnit; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.github.benmanes.caffeine.cache.Caffeine; + +@Configuration +@EnableCaching +public class CaffeineConfig { + @Bean + public CacheManager cacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager("lightRoutes", "tmp"); // 여러 개의 캐시 네임 설정 가능 + cacheManager.setCaffeine(caffeineConfig()); + return cacheManager; + } + + public Caffeine caffeineConfig() { + return Caffeine.newBuilder() + .expireAfterWrite(36, TimeUnit.HOURS) // 36시간 후 캐시 만료 + .maximumSize(3000) // 최대 3000개 저장 + .recordStats(); // 캐시 통계 활성화 + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/RedisConfig.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/RedisConfig.java deleted file mode 100644 index b67115c..0000000 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/RedisConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.softeer5.uniro_backend.common.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -import com.alibaba.fastjson2.support.spring6.data.redis.FastJsonRedisSerializer; -import com.softeer5.uniro_backend.map.service.vo.LightRoutes; - -@Configuration -public class RedisConfig { - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - - // 키는 String, 값은 FastJson으로 변환된 JSON 문자열 저장 - StringRedisSerializer serializer = new StringRedisSerializer(); - FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(LightRoutes.class); - - template.setKeySerializer(serializer); - template.setValueSerializer(fastJsonRedisSerializer); - - return template; - } - - @Bean - public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) { - return new StringRedisTemplate(connectionFactory); - } -} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java index c485a94..a477cb1 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/config/WebMvcConfig.java @@ -1,6 +1,7 @@ package com.softeer5.uniro_backend.common.config; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,6 +50,22 @@ public void addInterceptors(InterceptorRegistry registry) { .excludePathPatterns("/**", HttpMethod.OPTIONS.name()) .order(1); // JWT 이후에 실행되도록 설정 } + + @Override + public void extendMessageConverters(List> converters) { + FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); + FastJsonConfig config = new FastJsonConfig(); + config.setDateFormat("yyyy-MM-dd HH:mm:ss"); + config.setReaderFeatures(JSONReader.Feature.FieldBased, JSONReader.Feature.SupportArrayToBean); + config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue); + converter.setFastJsonConfig(config); + converter.setDefaultCharset(StandardCharsets.UTF_8); + List supportedMediaTypes = new ArrayList<>(); + supportedMediaTypes.add(MediaType.APPLICATION_JSON); + supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM); + converter.setSupportedMediaTypes(supportedMediaTypes); + converters.add(0, converter); + } } diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java index 8fd7268..27ba179 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/common/logging/ExecutionLoggingAop.java @@ -1,5 +1,9 @@ package com.softeer5.uniro_backend.common.logging; +import static com.softeer5.uniro_backend.common.constant.UniroConst.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Enumeration; import java.util.List; @@ -8,21 +12,26 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.log4j.Log4j2; +import lombok.extern.slf4j.Slf4j; @Aspect @Component -@Log4j2 +@Slf4j @Profile("!test") public class ExecutionLoggingAop { + Logger asyncLogger = LoggerFactory.getLogger("async-logger"); + Logger synclogger = LoggerFactory.getLogger(ExecutionLoggingAop.class); private static final ThreadLocal userIdThreadLocal = new ThreadLocal<>(); @@ -48,24 +57,22 @@ public Object logExecutionTrace(ProceedingJoinPoint pjp) throws Throwable { } HttpServletRequest request = null; if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes) { - request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); } - if(request==null){ + if (request == null) { return pjp.proceed(); } - log.info("✅ [ userId = {} Start] [Call Method] {}: {}", userId, request.getMethod(), task); + asyncLogger.info("✅ [ userId = {} Start] [Call Method] {}: {}", userId, request.getMethod(), task); - try{ + try { if (isController) { logParameters(pjp.getArgs()); } - } - catch (Exception e){ + } catch (Exception e) { // 로깅 중에 발생한 에러는 무시하고 로깅을 계속 진행 - log.error("🚨🚨🚨 [ userId = {} ] {} 메서드 파라미터 로깅 중 에러 발생 : {} 🚨🚨🚨", userId, task, e.getMessage()); + asyncLogger.error("🚨🚨🚨 [ userId = {} ] {} 메서드 파라미터 로깅 중 에러 발생 : {} 🚨🚨🚨", userId, task, e.getMessage()); } - log.info(""); StopWatch sw = new StopWatch(); sw.start(); @@ -73,19 +80,18 @@ public Object logExecutionTrace(ProceedingJoinPoint pjp) throws Throwable { Object result; try { result = pjp.proceed(); + sw.stop(); + asyncLogger.info("🚨 [ExecutionTime] {} --> {} (ms) [ userId = {} ] {} End\n", task, sw.getTotalTimeMillis(), + userId, className); } catch (Exception e) { - log.warn("[ERROR] [ userId = {} ] {} 메서드 예외 발생 : {}", userId, task, e.getMessage()); + asyncLogger.warn("[ERROR] [ userId = {} ] {} 메서드 예외 발생 : {}", userId, task, e.getMessage()); throw e; } finally { - if (isController) { + if (isControllerOrService(target)) { userIdThreadLocal.remove(); } } - sw.stop(); - log.info("[ExecutionTime] {} --> {} (ms)", task, sw.getTotalTimeMillis()); - log.info("🚨 [ userId = {} ] {} End\n", userId, className); - return result; } @@ -94,13 +100,23 @@ private boolean isRestController(Object target) { .anyMatch(RestController.class::isInstance); } + private boolean isControllerOrService(Object target) { + boolean b = Arrays.stream(target.getClass().getDeclaredAnnotations()) + .anyMatch(RestController.class::isInstance); + + boolean b1 = Arrays.stream(target.getClass().getDeclaredAnnotations()) + .anyMatch(Service.class::isInstance); + + return b || b1; + } + private void logParameters(Object[] args) { StringBuilder parametersLogMessage = new StringBuilder(); Arrays.stream(args) .forEach(arg -> logDetail(arg, "[Parameter]", parametersLogMessage, 0)); - log.info("\n{}", parametersLogMessage.toString()); + asyncLogger.info("\n{}", parametersLogMessage); } private void logDetail(Object arg, String requestType, StringBuilder logMessage, int depth) { @@ -154,13 +170,16 @@ private void logObjectFields(Object object, StringBuilder logMessage, int depth) Object value = field.get(object); logDetail(value, "[Field] " + field.getName(), logMessage, depth + 1); } catch (IllegalAccessException e) { - logMessage.append(indent).append("[Field Access Error] Cannot access field: ").append(field.getName()).append("\n"); + logMessage.append(indent) + .append("[Field Access Error] Cannot access field: ") + .append(field.getName()) + .append("\n"); } }); } private void logHttpRequest(String userId) { - HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); // HTTP Request 메시지 출력 (RFC 2616 형식) StringBuilder httpMessage = new StringBuilder(); @@ -199,7 +218,6 @@ private void logHttpRequest(String userId) { } // 요청 메시지 출력 - log.info("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ New request"); - log.info("[ userId = "+ userId + " ] HTTP Request: \n" + httpMessage); + asyncLogger.info("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ New request\n [ userId = {} ] HTTP Request: {} \n", userId, httpMessage); } } \ No newline at end of file diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/redis/RedisService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/redis/RedisService.java deleted file mode 100644 index e771f22..0000000 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/external/redis/RedisService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.softeer5.uniro_backend.external.redis; - -import java.time.Duration; - -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import static com.softeer5.uniro_backend.common.constant.UniroConst.MAX_CACHE_SIZE; - -@Component -@RequiredArgsConstructor -@Slf4j -public class RedisService { - - private final RedisTemplate redisTemplate; - private final StringRedisTemplate stringRedisTemplate; - - public void saveData(String key, Object value) { - redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(10000)); - } - - public void saveDataToString(String key, Object value) { - stringRedisTemplate.opsForValue().set(key, value.toString(), Duration.ofMinutes(10000)); - } - - public Object getData(String key) { - return redisTemplate.opsForValue().get(key); - } - - public String getDataToString(String key){ - return stringRedisTemplate.opsForValue().get(key); - } - public boolean hasData(String key){ - return redisTemplate.hasKey(key); - } - - public void deleteRoutesData(String key, int batchNumber) { - String redisKeyPrefix = key + ":"; - - for(int i=1; i<= Math.max(MAX_CACHE_SIZE,batchNumber); i++){ - redisTemplate.delete(redisKeyPrefix + i); - } - } -} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheService.java new file mode 100644 index 0000000..ee0d2cb --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCacheService.java @@ -0,0 +1,19 @@ +package com.softeer5.uniro_backend.map.cache; + +import com.softeer5.uniro_backend.map.service.vo.LightRoutes; + +public interface RouteCacheService { + LightRoutes getLightRoutes(Long univId, int batchNumber); + + void saveLightRoutes(Long univId, int batchNumber, LightRoutes value); + + boolean hasLightRoutes(Long univId, int batchNumber); + + Integer getFetchSize(Long univId); + + void saveFetchSize(Long univId, Integer fetchSize); + + boolean hasFetchSize(Long univId); + + void deleteFetchSizeAndLightRoutesByUnivId(Long univId); +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCaffeineService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCaffeineService.java new file mode 100644 index 0000000..1dc2149 --- /dev/null +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/cache/RouteCaffeineService.java @@ -0,0 +1,59 @@ +package com.softeer5.uniro_backend.map.cache; + +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; +import org.springframework.cache.Cache; +import com.softeer5.uniro_backend.map.service.vo.LightRoutes; + +@Component +public class RouteCaffeineService implements RouteCacheService { + private static final String FETCH_SIZE_POSTFIX = ":fetch"; + + private final Cache routeCache; + + public RouteCaffeineService(CacheManager cacheManager) { + this.routeCache = cacheManager.getCache("lightRoutes"); + } + + public LightRoutes getLightRoutes(Long univId, int batchNumber) { + String lightRoutesKey = univId + ":" + batchNumber; + return routeCache.get(lightRoutesKey, LightRoutes.class); + } + + public void saveLightRoutes(Long univId, int batchNumber, LightRoutes value) { + String lightRoutesKey = univId + ":" + batchNumber; + routeCache.put(lightRoutesKey, value); + } + + public boolean hasLightRoutes(Long univId, int batchNumber){ + String lightRoutesKey = univId + ":" + batchNumber; + return routeCache.get(lightRoutesKey) != null; + } + + public Integer getFetchSize(Long univId){ + String fetchSizeKey = univId + FETCH_SIZE_POSTFIX; + return routeCache.get(fetchSizeKey, Integer.class); + } + + public void saveFetchSize(Long univId, Integer fetchSize){ + String fetchSizeKey = univId + FETCH_SIZE_POSTFIX; + routeCache.put(fetchSizeKey, fetchSize); + } + + public boolean hasFetchSize(Long univId){ + String fetchSizeKey = univId + FETCH_SIZE_POSTFIX; + return routeCache.get(fetchSizeKey) != null; + } + + public void deleteFetchSizeAndLightRoutesByUnivId(Long univId){ + String keyPrefix = univId + ":"; + + Integer batchNumber = getFetchSize(univId); + + if(batchNumber == null) return; + + for(int i=1; i<= batchNumber; i++){ + routeCache.evictIfPresent(keyPrefix + i); + } + } +} diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java index 14ba73a..f2598c1 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapApi.java @@ -23,13 +23,6 @@ @Tag(name = "간선 및 위험&주의 요소 관련 Api") public interface MapApi { - @Operation(summary = "모든 지도(노드,루트) 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "모든 지도 조회 성공"), - @ApiResponse(responseCode = "400", description = "EXCEPTION(임시)", content = @Content), - }) - ResponseEntity getAllRoutesAndNodes(@PathVariable("univId") Long univId); - @Operation(summary = "모든 지도(노드,루트) 조회 by stream") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "모든 지도 조회 성공"), diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java index 18426bd..73e1c27 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/controller/MapController.java @@ -25,13 +25,6 @@ public class MapController implements MapApi { private final MapService mapService; private final AdminService adminService; - @Override - @GetMapping("/{univId}/routes") - public ResponseEntity getAllRoutesAndNodes(@PathVariable("univId") Long univId){ - AllRoutesInfo allRoutes = mapService.getAllRoutes(univId); - return ResponseEntity.ok().body(allRoutes); - } - @Override @GetMapping("/{univId}/routes/stream") public ResponseEntity getAllRoutesAndNodesStream(@PathVariable("univId") Long univId){ diff --git a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java index 2052c9a..8a19de3 100644 --- a/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java +++ b/uniro_backend/src/main/java/com/softeer5/uniro_backend/map/service/MapService.java @@ -8,9 +8,7 @@ import java.util.stream.Stream; import com.softeer5.uniro_backend.admin.annotation.RevisionOperation; -import com.softeer5.uniro_backend.admin.entity.RevInfo; import com.softeer5.uniro_backend.admin.enums.RevisionOperationType; -import com.softeer5.uniro_backend.admin.repository.RevInfoRepository; import com.softeer5.uniro_backend.building.entity.Building; import com.softeer5.uniro_backend.common.error.ErrorCode; import com.softeer5.uniro_backend.common.exception.custom.BuildingException; @@ -18,7 +16,7 @@ import com.softeer5.uniro_backend.common.exception.custom.RouteCalculationException; import com.softeer5.uniro_backend.common.exception.custom.RouteException; import com.softeer5.uniro_backend.external.event.RouteCreatedEvent; -import com.softeer5.uniro_backend.external.redis.RedisService; +import com.softeer5.uniro_backend.map.cache.RouteCacheService; import com.softeer5.uniro_backend.map.dto.request.CreateRoutesReqDTO; import com.softeer5.uniro_backend.map.entity.Node; @@ -26,10 +24,10 @@ import com.softeer5.uniro_backend.map.repository.NodeRepository; import com.softeer5.uniro_backend.map.dto.request.CreateBuildingRouteReqDTO; import com.softeer5.uniro_backend.map.dto.response.*; -import jakarta.persistence.EntityManager; -import lombok.extern.slf4j.Slf4j; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -47,10 +45,8 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -@Slf4j public class MapService { private final RouteRepository routeRepository; - private final EntityManager entityManager; private final NodeRepository nodeRepository; private final BuildingRepository buildingRepository; private final ApplicationEventPublisher eventPublisher; @@ -58,56 +54,31 @@ public class MapService { private final RouteCalculator routeCalculator; private final RouteCacheCalculator routeCacheCalculator; - private final RedisService redisService; + private final RouteCacheService cacheService; - private final Map> cache = new HashMap<>(); - - - public AllRoutesInfo getAllRoutes(Long univId) { - - if(!redisService.hasData(univId.toString())){ - List routes = routeRepository.findAllRouteByUnivIdWithNodes(univId); - List lightRoutes = routes.stream().map(LightRoute::new).toList(); - LightRoutes value = new LightRoutes(lightRoutes); - redisService.saveData(univId.toString(), value); - } - else{ - log.info("🚀🚀🚀🚀🚀🚀🚀HIT🚀🚀🚀🚀🚀🚀🚀"); - } - - LightRoutes lightRoutes = (LightRoutes) redisService.getData(univId.toString()); - List routes = lightRoutes.getLightRoutes(); - - // 맵이 존재하지 않을 경우 예외 - if(routes.isEmpty()) { - throw new RouteException("Route Not Found", ROUTE_NOT_FOUND); - } - - return routeCacheCalculator.assembleRoutes(routes); - } + Logger log = LoggerFactory.getLogger("async-logger"); public AllRoutesInfo getAllRoutesByStream(Long univId) { List nodeInfos = new ArrayList<>(); List coreRoutes = new ArrayList<>(); List buildingRoutes = new ArrayList<>(); - String redisKeyPrefix = univId + ":"; int batchNumber = 1; - if (!processRedisDataByStream(redisKeyPrefix, batchNumber, nodeInfos, coreRoutes, buildingRoutes)) { - processDatabaseDataByStream(univId, redisKeyPrefix, batchNumber, nodeInfos, coreRoutes, buildingRoutes); + if (!processCacheDataByStream(univId, batchNumber, nodeInfos, coreRoutes, buildingRoutes)) { + processDatabaseDataByStream(univId, batchNumber, nodeInfos, coreRoutes, buildingRoutes); } return AllRoutesInfo.of(nodeInfos, coreRoutes, buildingRoutes); } - private boolean processRedisDataByStream(String redisKeyPrefix, + private boolean processCacheDataByStream(Long unvId, int batchNumber, List nodeInfos, List coreRoutes, List buildingRoutes){ - while (redisService.hasData(redisKeyPrefix + batchNumber)) { - LightRoutes lightRoutes = (LightRoutes) redisService.getData(redisKeyPrefix + batchNumber); + while (cacheService.hasLightRoutes(unvId, batchNumber)) { + LightRoutes lightRoutes = cacheService.getLightRoutes(unvId, batchNumber); if (lightRoutes == null) { break; } @@ -119,13 +90,13 @@ private boolean processRedisDataByStream(String redisKeyPrefix, return batchNumber > 1; } - private void processDatabaseDataByStream(Long univId, String redisKeyPrefix, int batchNumber, + private void processDatabaseDataByStream(Long univId, int batchNumber, List nodeInfos, List coreRoutes, List buildingRoutes) { int fetchSize = routeRepository.countByUnivId(univId); - int remain = fetchSize%STREAM_FETCH_SIZE; - fetchSize = fetchSize/STREAM_FETCH_SIZE + (remain > 0 ? 1 : 0); + int remain = fetchSize % STREAM_FETCH_SIZE; + fetchSize = fetchSize / STREAM_FETCH_SIZE + (remain > 0 ? 1 : 0); try (Stream routeStream = routeRepository.findAllLightRoutesByUnivId(univId)) { List batch = new ArrayList<>(STREAM_FETCH_SIZE); @@ -133,26 +104,27 @@ private void processDatabaseDataByStream(Long univId, String redisKeyPrefix, int batch.add(route); if (batch.size() == STREAM_FETCH_SIZE) { - saveAndSendBatchByStream(redisKeyPrefix, batchNumber++, batch, nodeInfos, coreRoutes, buildingRoutes); + saveAndSendBatchByStream(univId, batchNumber++, batch, nodeInfos, coreRoutes, buildingRoutes); } } // 남은 배치 처리 if (!batch.isEmpty()) { - saveAndSendBatchByStream(redisKeyPrefix, batchNumber, batch, nodeInfos, coreRoutes, buildingRoutes); + saveAndSendBatchByStream(univId, batchNumber, batch, nodeInfos, coreRoutes, buildingRoutes); } - redisService.saveDataToString(univId.toString() + ":fetch", String.valueOf(fetchSize)); + cacheService.saveFetchSize(univId, fetchSize); } } - private void saveAndSendBatchByStream(String redisKeyPrefix, int batchNumber, List batch, + private void saveAndSendBatchByStream(Long univId, int batchNumber, List batch, List nodeInfos, List coreRoutes, List buildingRoutes) { - LightRoutes value = new LightRoutes(batch); - redisService.saveData(redisKeyPrefix + batchNumber, value); + List copyBatch = new ArrayList<>(batch); + LightRoutes value = new LightRoutes(copyBatch); + cacheService.saveLightRoutes(univId, batchNumber, value); processBatchByStream(batch, nodeInfos, coreRoutes, buildingRoutes); batch.clear(); } @@ -166,55 +138,49 @@ private void processBatchByStream(List batch, nodeInfos.addAll(allRoutesInfo.getNodeInfos()); coreRoutes.addAll(allRoutesInfo.getCoreRoutes()); buildingRoutes.addAll(allRoutesInfo.getBuildingRoutes()); - batch.clear(); - entityManager.clear(); } } @Async public void getAllRoutesBySSE(Long univId, SseEmitter emitter) { - String redisKeyPrefix = univId + ":"; int batchNumber = 1; try { - // 1️⃣ Redis 데이터가 있다면 우선 처리 - if (processRedisData(univId, redisKeyPrefix, batchNumber, emitter)) { + if (cacheService.hasFetchSize(univId)) { + processCacheData(univId, batchNumber, emitter); return; } - // 2️⃣ Redis에 데이터가 없으면 기존 DB 조회 방식 사용 - processDatabaseData(univId, redisKeyPrefix, batchNumber, emitter); + processDatabaseData(univId, batchNumber, emitter); } catch (Exception e) { emitter.completeWithError(e); log.error("SSE error: {}", e.getMessage(), e); } } - private boolean processRedisData(Long univId, String redisKeyPrefix, int batchNumber, SseEmitter emitter) throws Exception { - while (redisService.hasData(redisKeyPrefix + batchNumber)) { - LightRoutes lightRoutes = (LightRoutes) redisService.getData(redisKeyPrefix + batchNumber); + private void processCacheData(Long univId, int batchNumber, SseEmitter emitter) { + Integer fetchSize = cacheService.getFetchSize(univId); + + while (cacheService.hasLightRoutes(univId, batchNumber)) { + LightRoutes lightRoutes = cacheService.getLightRoutes(univId, batchNumber); if (lightRoutes == null) { break; } - Integer fetchSize = Integer.parseInt(redisService.getDataToString(univId.toString() + ":fetch")); - processBatch(lightRoutes.getLightRoutes(), emitter, fetchSize); batchNumber++; } if (batchNumber > 1) { emitter.complete(); - log.info("[SSE emitter complete] Redis data used."); - return true; + log.info("[SSE emitter complete] Cache data fully processed."); } - return false; } - private void processDatabaseData(Long univId, String redisKeyPrefix, int batchNumber, SseEmitter emitter) { + private void processDatabaseData(Long univId, int batchNumber, SseEmitter emitter) { int fetchSize = routeRepository.countByUnivId(univId); - int remain = fetchSize%STREAM_FETCH_SIZE; - fetchSize = fetchSize/STREAM_FETCH_SIZE + (remain > 0 ? 1 : 0); + int remain = fetchSize % STREAM_FETCH_SIZE; + fetchSize = fetchSize / STREAM_FETCH_SIZE + (remain > 0 ? 1 : 0); try (Stream routeStream = routeRepository.findAllLightRoutesByUnivId(univId)) { List batch = new ArrayList<>(STREAM_FETCH_SIZE); @@ -222,16 +188,16 @@ private void processDatabaseData(Long univId, String redisKeyPrefix, int batchNu batch.add(route); if (batch.size() == STREAM_FETCH_SIZE) { - saveAndSendBatch(redisKeyPrefix, batchNumber++, batch, emitter, fetchSize); + saveAndSendBatch(univId, batchNumber++, batch, emitter, fetchSize); } } // 남은 배치 처리 if (!batch.isEmpty()) { - saveAndSendBatch(redisKeyPrefix, batchNumber, batch, emitter, fetchSize); + saveAndSendBatch(univId, batchNumber, batch, emitter, fetchSize); } - redisService.saveDataToString(univId.toString() + ":fetch", String.valueOf(fetchSize)); + cacheService.saveFetchSize(univId, fetchSize); emitter.complete(); log.info("[SSE emitter complete] DB data used."); } catch (Exception e) { @@ -239,21 +205,23 @@ private void processDatabaseData(Long univId, String redisKeyPrefix, int batchNu } } - private void saveAndSendBatch(String redisKeyPrefix, int batchNumber, List batch, SseEmitter emitter, Integer fetchSize) - throws Exception { - LightRoutes value = new LightRoutes(batch); - redisService.saveData(redisKeyPrefix + batchNumber, value); + private void saveAndSendBatch(Long univId, int batchNumber, List batch, SseEmitter emitter, Integer fetchSize) { + List copyBatch = new ArrayList<>(batch); + LightRoutes value = new LightRoutes(copyBatch); + cacheService.saveLightRoutes(univId, batchNumber, value); processBatch(batch, emitter, fetchSize); batch.clear(); } - private void processBatch(List batch, SseEmitter emitter, int fetchSize) throws Exception { + private void processBatch(List batch, SseEmitter emitter, int fetchSize) { if (!batch.isEmpty()) { AllRoutesInfo allRoutesInfo = routeCacheCalculator.assembleRoutes(batch); allRoutesInfo.setBatchSize(fetchSize); - emitter.send(allRoutesInfo); - batch.clear(); - entityManager.clear(); + try { + emitter.send(allRoutesInfo); + } catch (Exception e) { + log.error("🚨 SSE 전송 중 오류 발생: {}", e.getMessage(), e); + } } } @@ -283,8 +251,6 @@ public GetRiskRoutesResDTO getRiskRoutes(Long univId) { return routeCalculator.mapRisks(riskRoutes); } - - public GetRiskResDTO getRisk(Long univId, Long routeId) { Route route = routeRepository.findById(routeId) .orElseThrow(() -> new RouteException("Route not found", ROUTE_NOT_FOUND)); @@ -367,9 +333,7 @@ public synchronized AllRoutesInfo createRoute(Long univId, CreateRoutesReqDTO re nodeRepository.saveAll(nodesForSave); routeRepository.saveAll(routes); - int routeCount = routeRepository.countByUnivId(univId); - - redisService.deleteRoutesData(univId.toString(), routeCount / STREAM_FETCH_SIZE + 1); + cacheService.deleteFetchSizeAndLightRoutesByUnivId(univId); eventPublisher.publishEvent(new RouteCreatedEvent()); return routeCalculator.assembleRoutes(routes); diff --git a/uniro_backend/src/main/resources/application-dev.yml b/uniro_backend/src/main/resources/application-dev.yml index b174817..86cded2 100644 --- a/uniro_backend/src/main/resources/application-dev.yml +++ b/uniro_backend/src/main/resources/application-dev.yml @@ -3,7 +3,7 @@ spring: import: application.properties datasource: hikari: - maximum-pool-size: 30 + maximum-pool-size: 100 url: ${DB_URL} username: ${DB_USER} password: ${DB_PASSWORD} @@ -18,11 +18,7 @@ spring: show_sql: true open-in-view: false defer-datasource-initialization: true - data: - redis: - host: redis - password: ${REDIS_PASSWORD} - port: 6379 + map: api: key: ${google.api.key} @@ -44,4 +40,7 @@ cors: allowed-origins: ${allowed-origins} jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} + +logging: + config: classpath:log4j2/log4j2.yml \ No newline at end of file diff --git a/uniro_backend/src/main/resources/application-local.yml b/uniro_backend/src/main/resources/application-local.yml index 7858ace..f6df39f 100644 --- a/uniro_backend/src/main/resources/application-local.yml +++ b/uniro_backend/src/main/resources/application-local.yml @@ -1,7 +1,7 @@ spring: datasource: hikari: - maximum-pool-size: 30 + maximum-pool-size: 100 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/uniro?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC username: root @@ -19,11 +19,7 @@ spring: # sql: # init: # mode: always - data: - redis: - host: ${REDIS_HOST} - password: ${REDIS_PASSWORD} - port: 6379 + map: api: key: ${google.api.key} @@ -45,4 +41,7 @@ cors: allowed-origins: ${allowed-origins} jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} + +logging: + config: classpath:log4j2/log4j2.yml \ No newline at end of file diff --git a/uniro_backend/src/main/resources/application-test.yml b/uniro_backend/src/main/resources/application-test.yml index 451ab16..edd506b 100644 --- a/uniro_backend/src/main/resources/application-test.yml +++ b/uniro_backend/src/main/resources/application-test.yml @@ -26,4 +26,7 @@ cors: allowed-origins: ${allowed-origins} jwt: - secret: ${JWT_SECRET} \ No newline at end of file + secret: ${JWT_SECRET} + +logging: + config: classpath:log4j2/log4j2.yml \ No newline at end of file diff --git a/uniro_backend/src/main/resources/log4j2/log4j2.yml b/uniro_backend/src/main/resources/log4j2/log4j2.yml new file mode 100644 index 0000000..4f19e85 --- /dev/null +++ b/uniro_backend/src/main/resources/log4j2/log4j2.yml @@ -0,0 +1,67 @@ +Configutation: + # 구성 이름 + name: uniro-api + + # [Properties] 설정에 사용되는 속성들을 정의 + Properties: + Property: + - name: "charset-UTF-8" + value: "UTF-8" + - name: "log-pattern" + value: "%style{%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C}{bright,yellow}: %msg%n%throwable" + + # [Appenders] 로그 기록방식 정의 + Appenders: + # [Appenders - Console] 콘솔에 로그를 출력하는 방식 정의 + Console: + - name: console-appender + target: SYSTEM_OUT + PatternLayout: + pattern: ${log-pattern} + + # [Loggers] 로그 출력 범위를 정의 + Loggers: + # [Loggers - Root] 모든 로그를 기록하는 최상위 로그를 정의 + Root: + level: OFF + AppenderRef: + - ref: console-appender + + # [Loggers - AsyncLogger] 비동기 로깅에 대한 정의 + AsyncLogger: + name: async-logger # logger key + level: DEBUG + additivity: false + AppenderRef: + - ref: console-appender + + # [Loggers - Loggers] 특정 패키지나 클래스에 대한 로그를 정의 + Logger: + # 1. Spring Framework 로그 레벨 'INFO' 정의 + - name: org.springframework + additivity: "false" + level: INFO + AppenderRef: + - ref: console-appender + + # 2. Spring Framework 로그 레벨 'DEBUG' 정의 + - name: "com.softeer5.uniro_backend" + additivity: "false" + level: DEBUG + AppenderRef: + - ref: console-appender + + - name: jdbc + level: OFF + - name: jdbc.sql only + level: OFF + - name: jdbc.sliding + level: INFO + - name: jdbc.result-settable + level: OFF + - name: jdbc.audit + level: OFF + - name: jdbc.result + level: OFF + - name: jdbc.connection + level: OFF \ No newline at end of file diff --git a/uniro_backend/src/test/java/com/softeer5/uniro_backend/map/service/MapServiceTest.java b/uniro_backend/src/test/java/com/softeer5/uniro_backend/map/service/MapServiceTest.java index a3bdbcf..68cda17 100644 --- a/uniro_backend/src/test/java/com/softeer5/uniro_backend/map/service/MapServiceTest.java +++ b/uniro_backend/src/test/java/com/softeer5/uniro_backend/map/service/MapServiceTest.java @@ -40,21 +40,6 @@ @Transactional class MapServiceTest { - @Container - static final GenericContainer redis = new GenericContainer<>("redis:7.0.8-alpine") - .withExposedPorts(6379); - - @BeforeAll - static void setup() { - redis.start(); - } - - @DynamicPropertySource - static void configureProperties(DynamicPropertyRegistry registry) { - registry.add("spring.data.redis.host", redis::getHost); - registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379)); - } - @Autowired private MapService mapService; @Autowired