Skip to content

Commit

Permalink
Merge pull request #88 from moidot/chore/enhance-with-cache
Browse files Browse the repository at this point in the history
chore: 캐싱 로직 추가 및 로그 파일 분리
  • Loading branch information
yujung7768903 authored Nov 1, 2023
2 parents 92b3aa8 + cc4a08f commit 872d5d1
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 89 deletions.
1 change: 0 additions & 1 deletion src/main/java/com/moim/backend/BackendApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
public class BackendApplication {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.moim.backend.domain.space.service;

import com.moim.backend.domain.space.config.OdsayProperties;
import com.moim.backend.domain.space.entity.Groups;
import com.moim.backend.domain.space.entity.Participation;
import com.moim.backend.domain.space.response.BusGraphicDataResponse;
import com.moim.backend.domain.space.response.BusPathResponse;
import com.moim.backend.domain.space.response.CarMoveInfo;
import com.moim.backend.domain.space.response.PlaceRouteResponse;
import com.moim.backend.domain.subway.response.BestPlaceInterface;
import com.moim.backend.domain.user.config.KakaoProperties;
import com.moim.backend.global.aspect.TimeCheck;
import com.moim.backend.global.util.LoggingUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
@Service
public class DirectionService {
private final RestTemplate restTemplate = new RestTemplate();
private final OdsayProperties odsayProperties;
private final KakaoProperties kakaoProperties;

@TimeCheck
public Optional<PlaceRouteResponse.MoveUserInfo> getBusRouteToResponse(
BestPlaceInterface bestPlace, Groups group, Participation participation
) {
Optional<PlaceRouteResponse.MoveUserInfo> moveUserInfo = Optional.empty();
URI searchPathUri = odsayProperties.getSearchPathUriWithParams(bestPlace, participation);
BusPathResponse busPathResponse = restTemplate.getForObject(searchPathUri, BusPathResponse.class);

if (busPathResponse.getResult() == null) {
LoggingUtil.builder()
.title("버스 길찾기")
.status("실패")
.message("지역: " + bestPlace.getName() + ", url: " + searchPathUri)
.build()
.print();
} else {
URI graphicDataUri = odsayProperties.getGraphicDataUriWIthParams(busPathResponse.getPathInfoMapObj());
BusGraphicDataResponse busGraphicDataResponse = restTemplate.getForObject(
graphicDataUri, BusGraphicDataResponse.class
);
if (busPathResponse.getResult() == null) {
LoggingUtil.builder()
.title("버스 그래픽 데이터 조회")
.status("실패")
.message("지역: " + bestPlace.getName() + ", url: " + graphicDataUri)
.build()
.print();
} else {
moveUserInfo = Optional.of(new PlaceRouteResponse.MoveUserInfo(group, participation, busGraphicDataResponse, busPathResponse));
}
}
return moveUserInfo;
}

@TimeCheck
public Optional<PlaceRouteResponse.MoveUserInfo> getCarRouteToResponse(
BestPlaceInterface bestPlace, Groups group, Participation participation
) {
Optional<PlaceRouteResponse.MoveUserInfo> moveUserInfo = Optional.empty();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoProperties.getClientId());
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

URI searchCarPathUri = kakaoProperties.getSearchCarPathUriWithParams(bestPlace, participation);
CarMoveInfo carMoveInfo = restTemplate.exchange(
searchCarPathUri, HttpMethod.GET, new HttpEntity<>(headers), CarMoveInfo.class
).getBody();
if (carMoveInfo.getRoutes() == null) {
LoggingUtil.builder()
.title("차 길찾기")
.status("실패")
.message("지역: " + bestPlace.getName() + ", url: " + searchCarPathUri)
.build()
.print();
} else {
moveUserInfo = Optional.of(new PlaceRouteResponse.MoveUserInfo(group, participation, carMoveInfo));
}
return moveUserInfo;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.moim.backend.domain.groupvote.entity.Vote;
import com.moim.backend.domain.groupvote.repository.VoteRepository;
import com.moim.backend.domain.hotplace.repository.HotPlaceRepository;
import com.moim.backend.domain.space.config.OdsayProperties;
import com.moim.backend.domain.space.entity.BestPlace;
import com.moim.backend.domain.space.entity.Groups;
import com.moim.backend.domain.space.entity.Participation;
Expand All @@ -19,14 +18,18 @@
import com.moim.backend.domain.subway.entity.Subway;
import com.moim.backend.domain.subway.repository.SubwayRepository;
import com.moim.backend.domain.subway.response.BestPlaceInterface;
import com.moim.backend.domain.user.config.KakaoProperties;
import com.moim.backend.domain.user.entity.Users;
import com.moim.backend.domain.user.repository.UserRepository;
import com.moim.backend.global.aspect.TimeCheck;
import com.moim.backend.global.common.CacheName;
import com.moim.backend.global.common.Result;
import com.moim.backend.global.common.exception.CustomException;
import com.moim.backend.global.util.DistanceCalculator;
import com.moim.backend.global.util.RedisDao;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -36,8 +39,6 @@
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
Expand All @@ -62,8 +63,8 @@ public class GroupService {
private final BestPlaceRepository bestPlaceRepository;
private final SubwayRepository subwayRepository;
private final HotPlaceRepository hotPlaceRepository;
private final OdsayProperties odsayProperties;
private final KakaoProperties kakaoProperties;
private final DirectionService directionService;
private final RedisDao redisDao;
private final RestTemplate restTemplate = new RestTemplate();

// 모임 생성
Expand Down Expand Up @@ -99,6 +100,7 @@ private void saveNearestStationList(Groups group, Double latitude, Double longit

// 모임 참여
@Transactional
@CacheEvict(value = CacheName.group, key = "#request.getGroupId()")
public GroupParticipateResponse participateGroup(GroupParticipateServiceRequest request, Users user) {
Groups group = getGroup(request.getGroupId());
participateGroupValidate(request, user, group);
Expand Down Expand Up @@ -158,6 +160,9 @@ public GroupExitResponse participateExit(Long participateId, Users user) {
// 투표 시작시 모임 나가기 불가
checkIfVoteStartedBeforeLeaving(optionalVote);

// 관련 Redis 삭제
redisDao.deleteSpringCache(CacheName.group, group.getGroupId().toString());

// 모임장이 나가는 경우 스페이스 삭제
if (deleteGroupIfAdminLeaves(user, group)) {
return GroupExitResponse.response(true, "모임이 삭제되었습니다.");
Expand Down Expand Up @@ -194,6 +199,7 @@ public Void participateRemoval(Long participateId, Users user) {

// 모임 삭제
@Transactional
@CacheEvict(value = CacheName.group, key = "#groupId")
public Void participateDelete(Long groupId, Users user) {
Groups group = getGroup(groupId);
validateAdminStatus(user.getUserId(), group.getAdminId());
Expand All @@ -203,6 +209,8 @@ public Void participateDelete(Long groupId, Users user) {
}

// 모임 추천 지역 조회하기
@TimeCheck
@Cacheable(value = CacheName.group, key = "#groupId")
public List<PlaceRouteResponse> getBestRegion(Long groupId) {
Groups group = getGroup(groupId);
// 중간 좌표 구하기
Expand Down Expand Up @@ -440,86 +448,17 @@ private List<PlaceRouteResponse.MoveUserInfo> getMoveUserInfoList(

participationList.forEach(participation -> {
if ((participation.getTransportation() == TransportationType.PUBLIC)) {
getBusRouteToResponse(bestPlace, group, participation)
directionService.getBusRouteToResponse(bestPlace, group, participation)
.ifPresent(moveUserInfo -> moveUserInfoList.add(moveUserInfo));
} else if (participation.getTransportation() == TransportationType.PERSONAL) {
getCarRouteToResponse(bestPlace, group, participation)
directionService.getCarRouteToResponse(bestPlace, group, participation)
.ifPresent(moveUserInfo -> moveUserInfoList.add(moveUserInfo));
}
});

return moveUserInfoList;
}

private Optional<PlaceRouteResponse.MoveUserInfo> getBusRouteToResponse(
BestPlaceInterface bestPlace, Groups group, Participation participation
) {
Instant start = Instant.now();

Optional<PlaceRouteResponse.MoveUserInfo> moveUserInfo = Optional.empty();
BusPathResponse busPathResponse = restTemplate.getForObject(
odsayProperties.getSearchPathUriWithParams(bestPlace, participation),
BusPathResponse.class
);

if (busPathResponse.getResult() == null) {
log.debug("[ 버스 길찾기 실패 ] ========================================");
log.debug("지역: {}, url: {}", bestPlace.getName(), odsayProperties.getSearchPathUriWithParams(bestPlace, participation));
} else {
log.debug("[ 버스 길찾기 성공 ] ========================================");
log.debug("지역: {}, url: {}", bestPlace.getName(), odsayProperties.getSearchPathUriWithParams(bestPlace, participation));

BusGraphicDataResponse busGraphicDataResponse = restTemplate.getForObject(
odsayProperties.getGraphicDataUriWIthParams(busPathResponse.getPathInfoMapObj()),
BusGraphicDataResponse.class
);
if (busPathResponse.getResult() == null) {
log.debug("[ 버스 그래픽 데이터 조회 실패 ] ========================================");
log.debug("지역: {}, url: {}", bestPlace.getName(), odsayProperties.getSearchPathUriWithParams(bestPlace, participation));
} else {
log.debug("[ 버스 그래픽 데이터 조회 성공 ] ========================================");
log.debug("지역: {}, url: {}", bestPlace.getName(), odsayProperties.getSearchPathUriWithParams(bestPlace, participation));

moveUserInfo = Optional.of(new PlaceRouteResponse.MoveUserInfo(group, participation, busGraphicDataResponse, busPathResponse));
}
}

Instant end = Instant.now();
log.info("[ 버스 경로 조회 시간: {}ms ] ========================================", Duration.between(start, end).toMillis());
return moveUserInfo;
}

private Optional<PlaceRouteResponse.MoveUserInfo> getCarRouteToResponse(
BestPlaceInterface bestPlace, Groups group, Participation participation
) {
Instant start = Instant.now();

Optional<PlaceRouteResponse.MoveUserInfo> moveUserInfo = Optional.empty();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoProperties.getClientId());
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

CarMoveInfo carMoveInfo = restTemplate.exchange(
kakaoProperties.getSearchCarPathUriWithParams(bestPlace, participation),
HttpMethod.GET,
new HttpEntity<>(headers),
CarMoveInfo.class
).getBody();
if (carMoveInfo.getRoutes() == null) {
log.debug("[ 차 길찾기 조회 실패 ] ========================================");
log.debug("지역: {}, url: {}", bestPlace.getName(), kakaoProperties.getSearchCarPathUriWithParams(bestPlace, participation));
} else {
log.debug("[ 차 길찾기 조회 성공 ] ========================================");
log.debug("지역: {}, url: {}", bestPlace.getName(), kakaoProperties.getSearchCarPathUriWithParams(bestPlace, participation));

moveUserInfo = Optional.of(new PlaceRouteResponse.MoveUserInfo(group, participation, carMoveInfo));
}

Instant end = Instant.now();
log.info("[ 차 경로 조회 시간: {}ms ] ========================================", Duration.between(start, end).toMillis());
return moveUserInfo;
}

private double getValidRange(List<Participation> participationList, MiddlePoint middlePoint) {
double maxDistance = participationList.stream().mapToDouble(participation ->
DistanceCalculator.getDistance(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class PerformanceAspect {
@Around("execution(* com.moim.backend.domain..controller.*.*(..))")
public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
@Around("execution(* com.moim.backend.domain..controller.*.*(..)) || @annotation(com.moim.backend.global.aspect.TimeCheck)")
public Object measureClassMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object returnValue = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/moim/backend/global/aspect/TimeCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.moim.backend.global.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeCheck {
}
5 changes: 5 additions & 0 deletions src/main/java/com/moim/backend/global/common/CacheName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.moim.backend.global.common;

public class CacheName {
public static final String group = "group";
}
25 changes: 25 additions & 0 deletions src/main/java/com/moim/backend/global/config/RedisCacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.moim.backend.global.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public CacheManager contentCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/moim/backend/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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
Expand All @@ -28,6 +29,8 @@ public LettuceConnectionFactory redisConnectionFactory() {
RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/moim/backend/global/filter/TimeCheckFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.moim.backend.global.filter;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class TimeCheckFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
return (event.getLoggerName().equals("com.moim.backend.global.aspect.PerformanceAspect"))
? FilterReply.ACCEPT
: FilterReply.DENY;
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/moim/backend/global/util/LoggingUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.moim.backend.global.util;

import lombok.Builder;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Builder
public class LoggingUtil {
private String title;
private String status = "";
private String message;

public void print() {
log.info("[ {} {} ] ========================================", title, status);
log.info(message);
log.info("=================================================================");
}
}
Loading

0 comments on commit 872d5d1

Please sign in to comment.