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

Release 1.2.0 #92

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
22 changes: 21 additions & 1 deletion .github/workflows/qa-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,33 @@ jobs:
push: true
tags: wedowhatwedo/mall-qa:latest

- name: Deploy
- name: Deploy to server 1
Copy link
Collaborator

Choose a reason for hiding this comment

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

파일 명이 qa-deploy 맞나요?? 개발서버 또는 운영서버에 배포하는거아닌가요?

uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_SSH_PORT }}
script: |
sudo docker pull wedowhatwedo/mall-qa:latest
sudo docker stop backend || true
sudo docker rm backend || true
sudo docker run -d --name backend -p 8080:8080 \
-v /home/ubuntu/logs:/var/logs \
-e COOLSMS_API_KEY="${{ secrets.COOLSMS_KEY }}" \
-e COOLSMS_API_SECRET="${{ secrets.COOLSMS_SECRET }}" \
-e COOLSMS_API_NUMBER="${{ secrets.COOLSMS_NUMBER }}" \
-e SPRING_MAIL_USERNAME="${{ secrets.GMAIL_USERNAME }}" \
-e SPRING_MAIL_PASSWORD="${{ secrets.GMAIL_PASSWORD }}" \
wedowhatwedo/mall-qa:latest

- name: Deploy to server 2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST2 }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_SSH_PORT }}
script: |
sudo docker pull wedowhatwedo/mall-qa:latest
sudo docker stop backend || true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableAsync
@EnableCaching
@EnableTransactionManagement
public class ShoppingMallFashionApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package com.example.flab.soft.shoppingmallfashion.common;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
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.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class RedisConfig {
Expand Down Expand Up @@ -35,6 +45,32 @@ public RedisTemplate<String, Object> redisTemplate() {

redisTemplate.setDefaultSerializer(new StringRedisSerializer());

redisTemplate.setEnableTransactionSupport(true);

return redisTemplate;
}

@Bean
public CacheManager cacheManager() {
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

Map<String, RedisCacheConfiguration> redisCacheConfigMap = new HashMap<>();
redisCacheConfigMap.put("ITEM_LIST", defaultConfig.entryTtl(Duration.ofMinutes(10)));
redisCacheConfigMap.put("ITEM_DETAILS", defaultConfig.entryTtl(Duration.ofDays(1)));
redisCacheConfigMap.put("ITEM_LIST_COUNT", defaultConfig.entryTtl(Duration.ofDays(1)));
redisCacheConfigMap.put("TOP_ITEMS_STORE", defaultConfig.entryTtl(Duration.ofMinutes(10)));
redisCacheConfigMap.put("TOP_ITEMS_CATEGORY", defaultConfig.entryTtl(Duration.ofMinutes(10)));
redisCacheConfigMap.put("STOCKS", defaultConfig.entryTtl(Duration.ofMinutes(10)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

이렇게 하면 모든 캐시에 적용되는데 ttl 설정을 추가하신 이유가 있을까요??


return RedisCacheManager.builder(redisConnectionFactory())
.withInitialCacheConfigurations(redisCacheConfigMap)
.build();
}

@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.example.flab.soft.shoppingmallfashion.common.SuccessResult;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemBriefDto;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemBriefDtos;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemDetailsDto;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemOptionStocksDto;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemQueryService;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemSearchKeywordDto;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemSearchKeywordService;
import com.example.flab.soft.shoppingmallfashion.item.service.ItemsCountDto;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -22,7 +24,6 @@
@RequiredArgsConstructor
public class ItemController {
private final ItemQueryService itemService;
private final ItemSearchKeywordService itemSearchKeywordService;

@GetMapping
public SuccessResult<Page<ItemBriefDto>> getItems(
Expand All @@ -37,6 +38,17 @@ public SuccessResult<Page<ItemBriefDto>> getItems(
return SuccessResult.<Page<ItemBriefDto>>builder().response(items).build();
}

@GetMapping("/count")
public SuccessResult<ItemsCountDto> getItemsCount(
ItemListRequest itemListRequest) {
return SuccessResult.<ItemsCountDto>builder()
.response(itemService.getItemCounts(
itemListRequest.getMinPrice(), itemListRequest.getMaxPrice(),
itemListRequest.getCategoryId(), itemListRequest.getStoreId(),
itemListRequest.getSex()))
.build();
}

@GetMapping("/items-with-keyword")
public SuccessResult<Page<ItemBriefDto>> getItemsWithKeyword(
@PageableDefault(
Expand All @@ -48,15 +60,42 @@ public SuccessResult<Page<ItemBriefDto>> getItemsWithKeyword(
}

@GetMapping("/{itemId}")
public SuccessResult<ItemDto> getItemDetails(
public SuccessResult<ItemDetailsDto> getItemDetails(
@PathVariable Long itemId) {
return SuccessResult.<ItemDetailsDto>builder()
.response(itemService.getItemDetails(itemId))
.build();
}

@GetMapping("/{itemId}/{itemOptionId}/stocks")
public SuccessResult<ItemOptionStocksDto> getStocks(
@PathVariable Long itemOptionId) {
return SuccessResult.<ItemOptionStocksDto>builder()
.response(itemService.getStocks(itemOptionId))
.build();
}

@GetMapping("/top-items-by-store")
public SuccessResult<ItemBriefDtos> getTopItemsByStore(
@RequestParam Long storeId) {
return SuccessResult.<ItemBriefDtos>builder()
.response(itemService.getTopItemsByStore(storeId))
.build();
}

@GetMapping("/top-items-by-category")
public SuccessResult<ItemBriefDtos> getTopItemsByCategory(
@RequestParam Long categoryId) {
return SuccessResult.<ItemBriefDtos>builder()
.response(itemService.getTopItemsByCategory(categoryId))
.build();
}

@GetMapping("/{itemId}/top-related-items")
public SuccessResult<List<ItemBriefDto>> getRelatedItems(
@PathVariable Long itemId) {
ItemDetailsDto itemDetails = itemService.getItemDetails(itemId);
ItemDto itemDto = ItemDto.builder()
.itemDetailsDto(itemDetails)
.sameStoreItems(itemService.getSameStoreItems(itemDetails.getItemBriefDto().getStoreId()))
.sameCategoryItems(itemService.getSameCategoryItems(itemDetails.getItemBriefDto().getCategoryId()))
.relatedItems(itemService.getRelatedItems(itemId))
return SuccessResult.<List<ItemBriefDto>>builder()
.response(itemService.getRelatedItems(itemId))
.build();
return SuccessResult.<ItemDto>builder().response(itemDto).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ public boolean isAllOptionsSoldOut() {
return itemOptions.stream().allMatch((ItemOption::isSoldOut));
}

public void renewSaleState() {
if (isAllOptionsSoldOut()) {
saleState = SaleState.SOLD_OUT;
}
}

public boolean hasProductTempSoldOut() {
return itemOptions.stream().anyMatch((ItemOption::isTempSoldOut));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;

@Entity(name = "item_options")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand Down Expand Up @@ -53,13 +54,18 @@ public ItemOption(String name, String size, String optionValue, Item item, SaleS
this.stocksCount = requireNotNull(stocksCount);
}

public Long reduceStocksCount(Integer amount) {
public Long reduceStocksCount(long amount) {
if (amount > stocksCount) {
throw new ApiException(ErrorEnum.OUT_OF_STOCK);
}
return stocksCount -= amount;
stocksCount -= amount;
if (isOutOfStock()) {
beSoldOut(false);
}
return stocksCount;
}

@CacheEvict(value = "ITEM_DETAILS", key = "#")
public void beSoldOut(Boolean isTemporarily) {
if (isSoldOut()) {
throw new ApiException(ErrorEnum.ALREADY_SOLD_OUT);
Expand All @@ -69,10 +75,6 @@ public void beSoldOut(Boolean isTemporarily) {
} else {
saleState = SaleState.SOLD_OUT;
}

if (item.isAllOptionsSoldOut()) {
item.changeSaleState(SaleState.SOLD_OUT);
}
}

public boolean isSoldOut() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.flab.soft.shoppingmallfashion.item.domain.Sex;
import java.util.List;
import java.util.Optional;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -14,30 +15,47 @@ public interface ItemRepository extends JpaRepository<Item, Long> {
@Query("SELECT i FROM items i JOIN FETCH i.store s " +
"JOIN FETCH i.category c JOIN FETCH c.largeCategory lc "
+ "WHERE " +
"(:storeId IS NULL OR i.store.id = :storeId) AND " +
"(:categoryId IS NULL OR i.category.id = :categoryId) AND " +
"(:minPrice IS NULL OR i.salePrice >= :minPrice) AND " +
"(:maxPrice IS NULL OR i.salePrice <= :maxPrice) AND " +
"(:categoryId IS NULL OR i.category.id = :categoryId) AND " +
"(:storeId IS NULL OR i.store.id = :storeId) AND " +
"(:sex IS NULL OR i.sex = :sex) AND " +
"i.saleState IN ('ON_SALE', 'TEMPORARILY_SOLD_OUT')")
Page<Item> findAllByFilters(@Param("minPrice") Integer minPrice,
List<Item> findAllByFilters(@Param("minPrice") Integer minPrice,
Copy link
Collaborator

Choose a reason for hiding this comment

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

QueryDsl 적용해서 동적쿼리 만들어주세요
아래처럼 불필요한 조건들이 없는 것이 더 명확합니다.

(:minPrice IS NULL OR i.salePrice >= :minPrice) 

@Param("maxPrice") Integer maxPrice,
@Param("categoryId") Long categoryId,
@Param("storeId") Long storeId,
@Param("sex") Sex sex,
Pageable pageable);

@Query("SELECT COUNT (i) FROM items i WHERE " +
"(:minPrice IS NULL OR i.salePrice >= :minPrice) AND " +
"(:maxPrice IS NULL OR i.salePrice <= :maxPrice) AND " +
"(:categoryId IS NULL OR i.category.id = :categoryId) AND " +
"(:storeId IS NULL OR i.store.id = :storeId) AND " +
"(:sex IS NULL OR i.sex = :sex) AND " +
"i.saleState IN ('ON_SALE', 'TEMPORARILY_SOLD_OUT')")
Long countByFilters(@Param("minPrice") Integer minPrice,
Copy link
Collaborator

Choose a reason for hiding this comment

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

count 쿼리도 querydsl로 처리해주세요

@Param("maxPrice") Integer maxPrice,
@Param("categoryId") Long categoryId,
@Param("storeId") Long storeId,
@Param("sex") Sex sex);

@Query("SELECT i FROM items i JOIN FETCH i.store s "
+ "JOIN FETCH i.category c JOIN FETCH c.largeCategory lc "
+ "JOIN FETCH i.itemOptions io "
+ "WHERE i.id = :id")
Optional<Item> findItemJoinFetchById(Long id);

@Query("SELECT i FROM items i WHERE i.category.id = :categoryId "
@Query("SELECT i FROM items i JOIN FETCH i.store s "
+ "JOIN FETCH i.category c JOIN FETCH c.largeCategory lc "
+ "WHERE i.category.id = :categoryId "
+ "ORDER BY i.itemStats.orderCount DESC")
List<Item> findTopItemsByCategoryId(Long categoryId, Pageable pageable);

@Query("SELECT i FROM items i WHERE i.store.id = :storeId "
@Query("SELECT i FROM items i JOIN FETCH i.store s "
+ "JOIN FETCH i.category c JOIN FETCH c.largeCategory lc "
+ "WHERE i.store.id = :storeId "
+ "ORDER BY i.itemStats.orderCount DESC")
List<Item> findTopItemsByStoreId(Long storeId, Pageable pageable);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.flab.soft.shoppingmallfashion.item.service;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ItemBriefDtos {
private List<ItemBriefDto> items;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example.flab.soft.shoppingmallfashion.item.service;

import com.example.flab.soft.shoppingmallfashion.item.domain.Sex;
import com.example.flab.soft.shoppingmallfashion.item.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ItemCacheService {
private final ItemRepository itemRepository;

@Transactional(readOnly = true)
@Cacheable(cacheNames = "ITEM_LIST",
key = "#categoryId + ':' + #storeId + ':' + #pageable.pageNumber",
Copy link
Collaborator

Choose a reason for hiding this comment

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

캐시 키에 pageNumber가 왜 있나요??

condition = "#pageable.pageNumber <= 3",
Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 조건을 넣으신 이유가 있을까요?

cacheManager = "cacheManager")
public ItemsDto getItems(Integer minPrice, Integer maxPrice,
Long categoryId, Long storeId, Sex sex, Pageable pageable) {
return ItemsDto.builder()
.items(itemRepository.findAllByFilters(minPrice, maxPrice, categoryId, storeId, sex, pageable)
.stream()
.map(ItemBriefDto::new)
.toList())
.build();
}

@CacheEvict(cacheNames = "ITEM_DETAILS",
key = "#itemId",
cacheManager = "cacheManager")
@Transactional
public void deleteItemDetails(Long itemId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

나중에 구현하실 메서들에는 주석으로 설명 넣어주시는게 좋습니다.
// TODO 추후 구현 예정

}

@CacheEvict(cacheNames = "STOCKS",
key = "#itemOptionId",
cacheManager = "cacheManager")
@Transactional
public void deleteItemOptionStocks(Long itemOptionId) {
}
}
Loading
Loading