Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 맛집 상세 정보 API 고도화 작업 #51

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
592bc0a
feat: redis config 파일 작성 (#38)
jooda00 Sep 2, 2024
b0fb05d
chore: LocalDateTime 타입 직렬화 / 역직렬화 dependency 추가 (#38)
jooda00 Sep 2, 2024
d5710bf
feat: ReviewRating 값 가져오기 위한 @Getter 추가 (#38)
jooda00 Sep 3, 2024
88640ef
feat: LocalDateTime 타입 직렬화 / 역직렬화 어노테이션 추가 (#38)
jooda00 Sep 3, 2024
caf8b6d
feat: RestaurantService의 getRestaurantDetails에 캐싱 작업 추가 (#38)
jooda00 Sep 3, 2024
34012c3
feat: JSON -> Point 역직렬화 클래스 추가 (#38)
jooda00 Sep 3, 2024
27785cb
feat: redis config 파일에 objectMapper 메소드 추가 (#38)
jooda00 Sep 3, 2024
7ed208e
feat: Restaurant 클래스에 location 역직렬화 어노테이션 등록 (#38)
jooda00 Sep 3, 2024
754d028
refactor: RestaurantDetailRes 좌표 반환 형식 double x, double y -> Point 객체…
jooda00 Sep 3, 2024
a23f297
refactor: objectMapper 시용해서 redis에 저장된 restaurant 타입 객체 받아오기 (#38)
jooda00 Sep 3, 2024
002481b
style: 주석 제거 (#38)
jooda00 Sep 3, 2024
38e575f
fix: log 메세지 수정 (#38)
jooda00 Sep 3, 2024
d983d49
Merge branch 'main' into feature/map-details-advance
jooda00 Sep 3, 2024
a310430
feat: main 브랜치와 충돌 해결
jooda00 Sep 3, 2024
c839c13
Merge remote-tracking branch 'origin/feature/map-details-advance' int…
jooda00 Sep 3, 2024
071fc56
Merge branch 'main' into feature/map-details-advance
jooda00 Sep 3, 2024
7089158
Merge branch 'main' into feature/map-details-advance
jooda00 Sep 3, 2024
854f98a
feat: main 브랜치 pull 작업
jooda00 Sep 3, 2024
09b5a08
Merge remote-tracking branch 'origin/feature/map-details-advance' int…
jooda00 Sep 3, 2024
ea2be7c
feat: application-template.yml redis 설정 추가 (#38)
jooda00 Sep 3, 2024
d945e52
feat: ci pipeline redis 환경변수 추가 (#38)
jooda00 Sep 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
DB_URL: ${{ secrets.DB_URL }}
DB_USERNAME: ${{ secrets.DB_USERNAME }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
REDIS_HOST: ${{ secrets.REDIS_HOST }}
REDIS_PORT: ${{ secrets.REDIS_PORT }}
steps:
- name: 체크아웃
uses: actions/checkout@v4
Expand Down
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ dependencies {
// Hibernate Spatial
implementation 'org.hibernate.orm:hibernate-spatial:6.5.2.Final'

// localdatetime 처리
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'


// localdatetime 처리
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'

// Apache Commons CSV
implementation 'org.apache.commons:commons-csv:1.11.0'

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/oz/yamyam_map/common/entity/BaseEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
Expand All @@ -20,11 +24,15 @@ public abstract class BaseEntity {
@Column(nullable = false, updatable = false)
@CreatedDate
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdAt;

@Column(nullable = false)
@LastModifiedDate
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updatedAt;

}
22 changes: 21 additions & 1 deletion src/main/java/oz/yamyam_map/common/util/GeoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import org.locationtech.jts.geom.PrecisionModel;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

Expand All @@ -28,7 +32,6 @@ public static Point createPoint(double longitude, double latitude) {
return point;
}


/**
* 위, 경도 소수점 6자리만 들어가도록 설정하는 메서드
**/
Expand All @@ -48,4 +51,21 @@ public void serialize(Point value, JsonGenerator gen, SerializerProvider seriali
gen.writeEndObject();
}
}

/**
* JSON을 Point 객체로 역직렬화하는 메서드 (redis에서 데이터를 가져올 때 필요)
**/
public static class PointDeserializer extends JsonDeserializer<Point> {
private final GeometryFactory geometryFactory = new GeometryFactory();

@Override
public Point deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);

double x = node.get("x").asDouble();
double y = node.get("y").asDouble();

return geometryFactory.createPoint(new Coordinate(x, y));
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/oz/yamyam_map/core/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package oz.yamyam_map.core.config;

import org.locationtech.jts.geom.Point;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

import oz.yamyam_map.common.util.GeoUtils;

@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}

@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Point.class, new GeoUtils.PointSerializer());
mapper.registerModule(module);
return mapper;
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));
return redisTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package oz.yamyam_map.module.restaurant.dto.response;

import org.locationtech.jts.geom.Point;

import lombok.Builder;
import lombok.Getter;
import oz.yamyam_map.common.enums.RestaurantType;
Expand All @@ -13,8 +15,7 @@ public class RestaurantDetailRes {
private String name;
private RestaurantType restaurantType;
private String phoneNumber;
private double pointX;
private double pointY;
private Point location;
private String oldAddressFull;
private String roadAddressFull;
private ReviewRating reviewRating;
Expand All @@ -25,8 +26,7 @@ public static RestaurantDetailRes from(Restaurant restaurant) {
.name(restaurant.getName())
.restaurantType(restaurant.getRestaurantType())
.phoneNumber(restaurant.getPhoneNumber())
.pointX(restaurant.getLocation().getX()) // Point의 x 좌표
.pointY(restaurant.getLocation().getY()) // Point의 y 좌표
.location(restaurant.getLocation())
.oldAddressFull(restaurant.getOldAddressFull())
.roadAddressFull(restaurant.getRoadAddressFull())
.reviewRating(restaurant.getReviewRating())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.locationtech.jts.geom.Point;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
Expand All @@ -19,7 +21,11 @@
import lombok.NoArgsConstructor;
import oz.yamyam_map.common.entity.BaseEntity;
import oz.yamyam_map.common.enums.RestaurantType;

import oz.yamyam_map.common.util.GeoUtils;

import oz.yamyam_map.module.region.entity.Region;
import oz.yamyam_map.common.util.GeoUtils;

@Entity
@Getter
Expand All @@ -45,7 +51,8 @@ public class Restaurant extends BaseEntity {

private String phoneNumber;

@Column(nullable = false, columnDefinition = "Point")
@Column(nullable = false, columnDefinition = "GEOMETRY")
@JsonDeserialize(using = GeoUtils.PointDeserializer.class)
private Point location;

private String oldAddressFull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.Getter;

@Embeddable
@Getter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

public class ReviewRating {

private Long totalReviews;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package oz.yamyam_map.module.restaurant.service;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import oz.yamyam_map.common.code.StatusCode;
import oz.yamyam_map.exception.custom.BusinessException;
import oz.yamyam_map.exception.custom.DataNotFoundException;
Expand All @@ -26,11 +31,14 @@
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class RestaurantService {
private static final String CACHE_PREFIX = "restaurant:";
private static final int TTL = 24;
private final ReviewRepository reviewRepository;
private final MemberRepository memberRepository;
private final RestaurantRepository restaurantRepository;

private final RedisTemplate<String, Object> redisTemplate;

@Transactional
public void uploadReview(Long memberId, Long restaurantId, ReviewUploadReq req) {
Expand All @@ -48,8 +56,26 @@ public void uploadReview(Long memberId, Long restaurantId, ReviewUploadReq req)
}

public RestaurantDetailRes getRestaurantDetails(Long restaurantId) {
ObjectMapper objectMapper = new ObjectMapper();
String cacheKey = CACHE_PREFIX + restaurantId;
Restaurant cachedRestaurant = objectMapper.convertValue(redisTemplate.opsForValue().get(cacheKey),
new TypeReference<Restaurant>() {
});
if (cachedRestaurant != null) {
log.info("{}번 맛집 데이터를 캐시에서 가져옵니다.", restaurantId);
return RestaurantDetailRes.from(cachedRestaurant);
}

log.info("{}번 맛집 데이터가 캐시에 존재하지 않아 DB에서 조회합니다.", restaurantId);
Restaurant restaurant = restaurantRepository.findById(restaurantId)
.orElseThrow(() -> new DataNotFoundException(StatusCode.RESTAURANT_NOT_FOUND));

if (restaurant.getReviewRating().getTotalReviews() >= 10) {
log.info("{}번 맛집 캐시 저장 작업을 시작합니다.", restaurantId);
redisTemplate.opsForValue().set(cacheKey, restaurant, TTL, TimeUnit.HOURS);
log.info("{}번 맛집 캐시 저장 작업을 완료했습니다.", restaurantId);
}

return RestaurantDetailRes.from(restaurant);
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ spring:
job:
enabled: false # 애플리케이션 실행 시 자동으로 Job이 실행되지 않도록 설정

data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}

jwt:
secret: ${JWT_SECRET}
expiration: 3600000 # 1 hour in milliseconds
Expand Down
Loading