Skip to content

Commit

Permalink
Merge pull request #93 from KTB16Team/feature/90-caching-list-api
Browse files Browse the repository at this point in the history
Feature/90 caching list api
  • Loading branch information
yooooonshine authored Dec 16, 2024
2 parents 8b16b68 + 11cfe33 commit d4301bf
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 20 deletions.
4 changes: 3 additions & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ java {

jar {
archiveBaseName.set("backend-server")
archiveVersion .set('')
archiveVersion.set('')
}

configurations {
Expand Down Expand Up @@ -56,6 +56,7 @@ dependencies {
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.security:spring-security-crypto:5.7.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
//jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
Expand All @@ -75,6 +76,7 @@ dependencies {
implementation 'io.micrometer:micrometer-registry-prometheus'
//rabbitmq
implementation 'org.springframework.boot:spring-boot-starter-amqp'

}

tasks.named('test') {
Expand Down
37 changes: 37 additions & 0 deletions backend/src/main/java/aimo/backend/common/dto/PageResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package aimo.backend.common.dto;

import java.util.List;

import org.springframework.data.domain.Page;

import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PageResponse<T> {
private List<T> content;
private int currentPage;
private int totalPages;
private long totalItems;

private PageResponse(List<T> content, int number, int totalPages, long totalElements) {
this.content = content;
this.currentPage = number;
this.totalPages = totalPages;
this.totalItems = totalElements;
}

public static <T> PageResponse<T> from(Page<T> page) {
return new PageResponse<>(
page.getContent(),
page.getNumber(),
page.getTotalPages(),
page.getTotalElements()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package aimo.backend.common.redis;

import java.time.Duration;

import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
@EnableCaching // Spring Boot의 캐싱 설정을 활성화
public class RedisCacheConfig {

// 기본 캐시 전략
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(1)) // TTL 설정
.disableCachingNullValues() // null 값 캐싱 방지
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer(new ObjectMapper()) // Custom ObjectMapper 사용
));
}

// 커스텀 캐시 전략
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("shortTermCache",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(1)) // cache1에 TTL 1분
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer())));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package aimo.backend.domains.post.controller;

import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -13,6 +12,7 @@
import org.springframework.web.bind.annotation.RestController;

import aimo.backend.common.dto.DataResponse;
import aimo.backend.common.dto.PageResponse;
import aimo.backend.common.util.memberLoader.MemberLoader;
import aimo.backend.domains.post.dto.parameter.DeletePostParameter;
import aimo.backend.domains.post.dto.parameter.FindPostAndCommentsByIdParameter;
Expand Down Expand Up @@ -51,7 +51,7 @@ public ResponseEntity<DataResponse<SavePostResponse>> savePost(@RequestBody @Val
}

@GetMapping
public ResponseEntity<DataResponse<Page<FindPostsByPostTypeResponse>>> findPostsByPostType(
public ResponseEntity<DataResponse<PageResponse<FindPostsByPostTypeResponse>>> findPostsByPostType(
@RequestParam("type") @NotNull PostType postType,
@RequestParam("page") @NotNull Integer page,
@RequestParam("size") @NotNull Integer size
Expand All @@ -60,7 +60,8 @@ public ResponseEntity<DataResponse<Page<FindPostsByPostTypeResponse>>> findPosts

FindPostByPostTypeParameter parameter = FindPostByPostTypeParameter.of(memberId, postType, page, size);

return ResponseEntity.ok(DataResponse.from(postService.findPostDtosByPostType(parameter)));
PageResponse<FindPostsByPostTypeResponse> responses = postService.findPostDtosByPostType(parameter);
return ResponseEntity.ok(DataResponse.from(responses));
}

@GetMapping("/{postId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@
import java.time.LocalDateTime;

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 aimo.backend.domains.post.dto.requset.FindCommentedPostsByIdRequest;
import aimo.backend.domains.post.entity.Post;

public record FindPostsByPostTypeResponse(
Long id,
String title,
String contentPreview,
Integer likesCount,
Integer viewsCount,
Integer commentsCount,
Float voteRatePlaintiff,
Float voteRateDefendant,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createdAt
Long id,
String title,
String contentPreview,
Integer likesCount,
Integer viewsCount,
Integer commentsCount,
Float voteRatePlaintiff,
Float voteRateDefendant,

@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createdAt
) {

public static FindPostsByPostTypeResponse from(FindCommentedPostsByIdRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

import java.util.List;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import aimo.backend.common.dto.PageResponse;
import aimo.backend.common.exception.ApiException;
import aimo.backend.domains.comment.entity.ParentComment;
import aimo.backend.domains.comment.repository.ChildCommentRepository;
Expand Down Expand Up @@ -96,7 +98,13 @@ public FindJudgementResponse findJudgementBy(Long postId) {
}

// PostType으로 글 조회
public Page<FindPostsByPostTypeResponse> findPostDtosByPostType(FindPostByPostTypeParameter parameter) {
// Popular Post는 캐시 적용
@Cacheable(
cacheNames = "shortTermCache",
key = "'popular-posts:page:' + #parameter.page() + ':size:' + #parameter.size() + ':postType:' + #parameter.postType()",
condition = "#parameter.postType() == T(aimo.backend.domains.post.model.PostType).POPULAR"
)
public PageResponse<FindPostsByPostTypeResponse> findPostDtosByPostType(FindPostByPostTypeParameter parameter) {
PostType postType = parameter.postType();

Pageable pageable = PageRequest.of(
Expand All @@ -105,7 +113,8 @@ public Page<FindPostsByPostTypeResponse> findPostDtosByPostType(FindPostByPostTy
Sort.by(Sort.Direction.DESC, "id"));

// 각 PostType의 execute 메서드를 호출해 로직 실행
return postType.findPosts(this, parameter, pageable);
Page<FindPostsByPostTypeResponse> posts = postType.findPosts(this, parameter, pageable);
return PageResponse.from(posts);
}

// 내가 쓴 글 조회
Expand All @@ -118,6 +127,7 @@ public Page<FindPostsByPostTypeResponse> findMyPosts(Long memberId, Pageable pag

// 인기 글 조회
public Page<FindPostsByPostTypeResponse> findPopularPosts(Pageable pageable) {
log.info("find popular posts");
return postRepository
.findAllByOrderByPostViewsCountDesc(pageable)
.map(FindPostsByPostTypeResponse::from);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package aimo.backend.domains.privatePost.controller;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand All @@ -16,6 +15,7 @@
import org.springframework.web.bind.annotation.RestController;

import aimo.backend.common.dto.DataResponse;
import aimo.backend.common.dto.PageResponse;
import aimo.backend.common.util.memberLoader.MemberLoader;
import aimo.backend.domains.privatePost.dto.parameter.DeletePrivatePostParameter;
import aimo.backend.domains.privatePost.dto.parameter.FindPrivatePostParameter;
Expand Down Expand Up @@ -104,7 +104,7 @@ public ResponseEntity<DataResponse<PrivatePostResponse>> findPrivatePost(
}

@GetMapping
public ResponseEntity<DataResponse<Page<PrivatePostPreviewResponse>>> findPrivatePostPage(
public ResponseEntity<DataResponse<PageResponse<PrivatePostPreviewResponse>>> findPrivatePostPage(
@Valid @RequestParam(defaultValue = "0") Integer page,
@Valid @RequestParam(defaultValue = "10") Integer size
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import aimo.backend.common.dto.PageResponse;
import aimo.backend.common.exception.ApiException;
import aimo.backend.common.exception.ErrorCode;
import aimo.backend.common.messageQueue.MessageQueueService;
Expand Down Expand Up @@ -141,6 +142,7 @@ public PrivatePostResponse findPrivatePostResponseBy(FindPrivatePostParameter pa

return PrivatePostResponse.from(privatePost);
}

// 개인글 상태에 따른 에러 리턴 및 실패시 개인글 삭제
@Transactional(rollbackFor = ApiException.class)
public void validatePrivatePostStatus(PrivatePost privatePost) {
Expand All @@ -167,9 +169,13 @@ public void validatePrivatePostStatus(PrivatePost privatePost) {
}

// 개인글 목록 조회
public Page<PrivatePostPreviewResponse> findPrivatePostPreviewsBy(FindPrivatePostPreviewParameter parameter) {
return privatePostRepository.findByMemberId(parameter.memberId(), parameter.pageable())
public PageResponse<PrivatePostPreviewResponse> findPrivatePostPreviewsBy(
FindPrivatePostPreviewParameter parameter) {
Page<PrivatePostPreviewResponse> privatePosts = privatePostRepository.findByMemberId(parameter.memberId(),
parameter.pageable())
.map(PrivatePostPreviewResponse::from);

return PageResponse.from(privatePosts);
}

// 개인글 공개 처리
Expand Down

0 comments on commit d4301bf

Please sign in to comment.