Skip to content

Commit

Permalink
Merge pull request #167 from supercoding-commerce/dev
Browse files Browse the repository at this point in the history
refactor: 캐시적용, gzip 압축 등 추가
  • Loading branch information
MoonJongHyeon1095 authored Jan 6, 2024
2 parents 10af84b + 5fb6bdb commit 2827430
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 76 deletions.
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-cache'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// Caffeine Cache
implementation 'com.github.ben-manes.caffeine:caffeine:2.9.0'

//spring batch
implementation 'org.springframework.boot:spring-boot-starter-batch'

Expand Down Expand Up @@ -62,6 +66,9 @@ dependencies {
//h2
testImplementation 'com.h2database:h2'

//prometheus & grafana
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
}

tasks.named('test') {
Expand Down
41 changes: 29 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
#version: '3'
#services:
# commerce:
# build:
# context: .
# dockerfile: Dockerfile
# restart: always
# ports:
# - "8080:8080"
# env_file:
# - .env

version: '3'
services:
commerce:
build:
context: .
dockerfile: Dockerfile
image: zin354/commerce:latest
restart: always
ports:
- "8080:8080"
env_file:
- .env

#version: '3'
#services:
# commerce:
# image: zin354/commerce:latest
# restart: always
# ports:
# - "8080:8080"
# env_file:
# - .env
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml

grafana:
image: grafana/grafana
user: "$UID:$GID"
ports:
- "3000:3000"
volumes:
- ./grafana-data:/var/lib/grafana
depends_on:
- prometheus

2 changes: 2 additions & 0 deletions src/main/java/com/github/commerce/CommerceApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableBatchProcessing //Spring Batch 사용 위해 필요
@EnableScheduling //@Scheduled 위해 필요
@EnableJpaAuditing //@CreatedAt 위해 필요
@EnableCaching
@SpringBootApplication
public class CommerceApplication {

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/github/commerce/config/CacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.github.commerce.config;
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;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("products");
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // TTL 설정, 예시로 10분
.maximumSize(100)); // 최대 캐시 크기. 용량이 아니라 그냥 항목의 개수
return cacheManager;
}
}
41 changes: 39 additions & 2 deletions src/main/java/com/github/commerce/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.github.commerce.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
Expand All @@ -14,15 +16,50 @@
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}

private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}

@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}

@Bean
public Docket api() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public class WebSecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
private static final String[] PERMIT_URL_ARRAY = {
"/v1/api/user/**","/v1/api/product/**","/v1/api/coupon","/GuerrillaCommerce", "/v1/api/health-check",
"/api/v2/**", "/swagger-ui.html", "/swagger/**","/swagger-resources/**", "/webjars/**", "/v2/api-docs"
"/api/v2/**", "/swagger-ui.html", "/swagger/**","/swagger-resources/**", "/webjars/**", "/v2/api-docs",
"/actuator/**"
};

@Bean
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/com/github/commerce/entity/collection/Chat.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.*;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.annotation.Id;

import java.util.List;
import java.util.Map;

@Getter
Expand All @@ -27,7 +29,17 @@ public class Chat {

private String userName;

private Map<String, Map<String, String>> chats;
private List<Message> chats;


@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class Message {
private String timestamp; // 메시지의 타임스탬프
private String sender; // 메시지의 보낸 사람
private String content; // 메시지 내용
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Repository
public class ChatRepositoryCustomImpl implements ChatRepository {
Expand Down Expand Up @@ -51,29 +52,17 @@ public Optional<Chat> findByCustomRoomId(String customRoomId) {

public void cleanupOldChats() {
LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7);
String oldestChatTimeStr = sevenDaysAgo.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
int batchStartId = Integer.parseInt(oldestChatTimeStr.substring(oldestChatTimeStr.length() - 1));
int batchLastId = batchStartId + 100;
List<Chat> chats = mongoTemplate.findAll(Chat.class);

Query query = new Query(Criteria.where("sellerId").gte(batchStartId).lte(batchLastId));
List<Chat> chats = mongoTemplate.find(query, Chat.class);

// Iterate through each Chat object and filter its chats field
// 각 Chat 객체에 대해 최근 7일 이내의 메시지만 유지
for (Chat chat : chats) {
Map<String, Map<String, String>> chatMap = chat.getChats();

if (chatMap != null) {
Map<String, Map<String, String>> filteredChatMap = new HashMap<>();
for (Map.Entry<String, Map<String, String>> entry : chatMap.entrySet()) {
if (entry.getKey().compareTo(oldestChatTimeStr) > 0) {
filteredChatMap.put(entry.getKey(), entry.getValue());
}
}

// Update the Chat object's chats field
chat.setChats(filteredChatMap);
mongoTemplate.save(chat);
}
List<Chat.Message> recentMessages = chat.getChats().stream()
.filter(message -> LocalDateTime.parse(message.getTimestamp(), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.isAfter(sevenDaysAgo))
.collect(Collectors.toList());

chat.setChats(recentMessages);
mongoTemplate.save(chat);
}
}

Expand Down
14 changes: 6 additions & 8 deletions src/main/java/com/github/commerce/service/chat/ChatService.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public class ChatService {
@Transactional
public ChatDto getChatRoom(String customRoomId){
Chat chatEntity = chatRepository.findByCustomRoomId(customRoomId).orElseThrow(()->new ChatException(ChatErrorCode.ROOM_NOT_FOUND));
Map<String, Map<String, String>> chats = chatEntity.getChats();
List<Chat.Message> chats = chatEntity.getChats();

Map<String, Map<String, String>> sortedChats = sortChatsByDate(chats);
List<Chat.Message> sortedChats = sortChatsByDate(chats);
chatEntity.setChats(sortedChats);

return ChatDto.fromEntity(chatEntity);
Expand Down Expand Up @@ -117,12 +117,10 @@ protected String getSellerImage(Long sellerId){
return seller.getShopImageUrl();
}

protected Map<String, Map<String, String>> sortChatsByDate(Map<String, Map<String, String>> chats) {
return chats.entrySet()
.stream()
.sorted(Comparator.comparing(entry -> extractDateTimeFromKey(entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
//정렬된 순서대로 데이터를 유지하려면 LinkedHashMap이 필요합니다. 이렇게 하지 않으면, 정렬 순서가 Map에 저장될 때 무시될 수 있습니다.
protected List<Chat.Message> sortChatsByDate(List<Chat.Message> chats) {
return chats.stream()
.sorted(Comparator.comparing(Chat.Message::getTimestamp))
.collect(Collectors.toList());
}

private LocalDateTime extractDateTimeFromKey(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.github.commerce.web.dto.product.*;
import com.google.gson.Gson;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand All @@ -39,10 +40,11 @@ public class ProductService {
private final AwsS3Service awsS3Service;

@Transactional(readOnly = true)
public List<GetProductDto> searchProducts(Integer pageNumber, String searchWord, String ageCategory, String genderCategory, String sortBy) {
@Cacheable(value = "products", key = "#size.toString() + '_' + #pageNumber.toString() + '_' + #searchWord + '_' + #ageCategory + '_' + #genderCategory + '_' + #sortBy")
public List<GetProductDto> searchProducts(Integer size, Integer pageNumber, String searchWord, String ageCategory, String genderCategory, String sortBy) {
String inputAgeCategory = AgeCategoryEnum.switchCategory(ageCategory);
String inputGenderCategory = GenderCategoryEnum.switchCategory(genderCategory);
Pageable pageable = PageRequest.of(pageNumber - 1, 15); //한 페이지 15개
Pageable pageable = PageRequest.of(pageNumber - 1, size); //한 페이지 15개
String searchToken = "%"+searchWord+"%";

if (Objects.equals(sortBy, "price")) {
Expand Down Expand Up @@ -266,11 +268,12 @@ public ProductDto getOneProduct(Long productId, Long userId, String userName) {
}

@Transactional(readOnly = true)
public List<GetProductDto> getProductsByCategory(Integer pageNumber, String productCategory, String ageCategory, String genderCategory, String sortBy) {
@Cacheable(value = "products", key = "#size.toString() + '_' + #pageNumber.toString() + '_' + #productCategory + '_' + #ageCategory + '_' + #genderCategory + '_' + #sortBy")
public List<GetProductDto> getProductsByCategory(Integer size, Integer pageNumber, String productCategory, String ageCategory, String genderCategory, String sortBy) {
String inputProductCategory = ProductCategoryEnum.switchCategory(productCategory);
if(inputProductCategory == null) throw new ProductException(ProductErrorCode.INVALID_CATEGORY);

Pageable pageable = PageRequest.of(pageNumber - 1, 15); //한 페이지 15개
Pageable pageable = PageRequest.of(pageNumber - 1, size); //한 페이지 15개
String inputAgeCategory = AgeCategoryEnum.switchCategory(ageCategory);
String inputGenderCategory = GenderCategoryEnum.switchCategory(genderCategory);

Expand All @@ -287,8 +290,9 @@ public List<GetProductDto> getProductsByCategory(Integer pageNumber, String prod
}

@Transactional(readOnly = true)
public List<GetProductDto> getProductList(Integer pageNumber, String ageCategory, String genderCategory, String sortBy) {
Pageable pageable = PageRequest.of(pageNumber - 1, 15); //한 페이지 15개
@Cacheable(value = "products", key = "#size.toString() + '_' + #pageNumber.toString() + '_' + #ageCategory + '_' + #genderCategory + '_' + #sortBy")
public List<GetProductDto> getProductList(Integer size, Integer pageNumber, String ageCategory, String genderCategory, String sortBy) {
Pageable pageable = PageRequest.of(pageNumber - 1, size); //한 페이지 15개
String inputAgeCategory = AgeCategoryEnum.switchCategory(ageCategory);
String inputGenderCategory = GenderCategoryEnum.switchCategory(genderCategory);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,39 +40,42 @@ public class ProductController {
@ApiOperation(value = "상품 검색")
@GetMapping("/search") // ?pageNumber=1&searchWord=반바지
public ResponseEntity<List<GetProductDto>> searchProduct(
@RequestParam(name = "size", required = false, defaultValue = "15") Integer size,
@RequestParam(name = "pageNumber", required = false, defaultValue = "1") Integer pageNumber,
@RequestParam(name = "searchWord", required = false, defaultValue = "") String searchWord,
@RequestParam(name = "ageCategory", required = false, defaultValue = "") String ageCategory,
@RequestParam(name = "genderCategory", required = false, defaultValue = "") String genderCategory,
@RequestParam(name = "sortBy", required = false, defaultValue = "id") String sortBy
){
return ResponseEntity.ok(productService.searchProducts(pageNumber, searchWord, ageCategory,genderCategory, sortBy));
return ResponseEntity.ok(productService.searchProducts(size, pageNumber, searchWord, ageCategory,genderCategory, sortBy));
}

@ApiOperation(value = "메인페이지 무한스크롤")
@GetMapping
public ResponseEntity<List<GetProductDto>> getProducts(
@RequestParam(name = "size", required = false, defaultValue = "15") Integer size,
@RequestParam(name = "pageNumber", required = false, defaultValue = "1") Integer pageNumber,
@RequestParam(name = "ageCategory", required = false, defaultValue = "") String ageCategory,
@RequestParam(name = "genderCategory", required = false, defaultValue = "") String genderCategory,
@RequestParam(name = "sortBy", required = false, defaultValue = "id") String sortBy
)
{
return ResponseEntity.ok(productService.getProductList(pageNumber,ageCategory,genderCategory, sortBy));
return ResponseEntity.ok(productService.getProductList(size, pageNumber,ageCategory,genderCategory, sortBy));
}

@ApiOperation(value = "상품 카테고리별 조회")
@GetMapping("/category/{productCategory}")
public ResponseEntity<List<GetProductDto>> getProductsByCategory(

@PathVariable String productCategory, //필수
@RequestParam(name = "size", required = false, defaultValue = "15") Integer size,
@RequestParam(name = "pageNumber", required = false, defaultValue = "1") Integer pageNumber,
@RequestParam(name = "ageCategory", required = false, defaultValue = "") String ageCategory,
@RequestParam(name = "genderCategory", required = false, defaultValue = "") String genderCategory,
@RequestParam(name = "sortBy", required = false, defaultValue = "id") String sortBy
)
{
return ResponseEntity.ok(productService.getProductsByCategory(pageNumber, productCategory,ageCategory,genderCategory, sortBy));
return ResponseEntity.ok(productService.getProductsByCategory(size, pageNumber, productCategory,ageCategory,genderCategory, sortBy));
}

@ApiOperation(value = "상품 상세 조회")
Expand Down
Loading

0 comments on commit 2827430

Please sign in to comment.