Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 28 additions & 0 deletions src/main/java/kr/co/pinup/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package kr.co.pinup.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class AsyncConfig {

@Bean
public ThreadPoolTaskExecutor taskExecutor() {
final ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(2);
taskExecutor.setMaxPoolSize(4);
taskExecutor.setQueueCapacity(10);

taskExecutor.setThreadNamePrefix("s3-upload-");
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(30);

taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();

return taskExecutor;
}
}
19 changes: 19 additions & 0 deletions src/main/java/kr/co/pinup/config/SchedulerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.co.pinup.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.function.Supplier;

@Configuration
public class SchedulerConfig {

private static final String ASIA_SEOUL_ZONE = "Asia/Seoul";

@Bean
public Supplier<LocalDate> todaySupplier() {
return () -> LocalDate.now(ZoneId.of(ASIA_SEOUL_ZONE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public LocationResponse createLocation(CreateLocationRequest request) {
return LocationResponse.from(savedLocation);
}

@Transactional(readOnly = true)
public Location getLocation(Long id) {
return locationRepository.findById(id)
.orElseThrow(LocationNotFoundException::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import kr.co.pinup.storecategories.repository.StoreCategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

Expand All @@ -15,17 +16,20 @@ public class StoreCategoryService {

private final StoreCategoryRepository storeCategoryRepository;

@Transactional(readOnly = true)
public List<StoreCategoryResponse> getCategories() {
return storeCategoryRepository.findAll().stream()
.map(StoreCategoryResponse::from)
.toList();
}

@Transactional(readOnly = true)
public StoreCategory findCategoryById(Long categoryId) {
return storeCategoryRepository.findById(categoryId)
.orElseThrow(StoreCategoryNotFoundException::new);
}

@Transactional(readOnly = true)
public StoreCategoryResponse getCategory(Long categoryId) {
return storeCategoryRepository.findById(categoryId)
.map(StoreCategoryResponse::from)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,14 @@ public StoreImageResponse getStoreThumbnailImage(Long storeId) {
return StoreImageResponse.from(storeImage);
}

@Transactional
public List<StoreImage> createUploadImages(final Store store, final List<MultipartFile> images, Long thumbnailIndex) {
final List<String> uploadUrls = s3UploadFiles(images);

final List<StoreImage> storeImages = IntStream.range(0, uploadUrls.size())
public List<StoreImage> createUploadImages(final Store store, final List<String> uploadUrls, Long thumbnailIndex) {
return IntStream.range(0, uploadUrls.size())
.mapToObj(i -> StoreImage.builder()
.imageUrl(uploadUrls.get(i))
.isThumbnail(i == thumbnailIndex)
.store(store)
.build())
.toList();

storeImageRepository.saveAll(storeImages);

return storeImages;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@

import jakarta.validation.Valid;
import kr.co.pinup.annotation.ValidImageFile;
import kr.co.pinup.stores.model.dto.StoreRequest;
import kr.co.pinup.stores.model.dto.StoreResponse;
import kr.co.pinup.stores.model.dto.StoreThumbnailResponse;
import kr.co.pinup.stores.model.dto.StoreUpdateRequest;
import kr.co.pinup.stores.model.dto.*;
import kr.co.pinup.stores.service.StoreService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -17,6 +14,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.util.List;

@Slf4j
Expand Down Expand Up @@ -47,13 +45,13 @@ public ResponseEntity<StoreResponse> getStoreById(@PathVariable Long id) {

@PreAuthorize("isAuthenticated() and hasRole('ROLE_ADMIN')")
@PostMapping
public ResponseEntity<StoreResponse> createStore(
public ResponseEntity<StoreCreateResponse> createStore(
@Valid @RequestPart("storeRequest") StoreRequest request,
@ValidImageFile @RequestParam(value = "images") List<MultipartFile> images) {
log.debug("createStore StoreRequest={}, images size={}", request, images.size());

return ResponseEntity.status(HttpStatus.CREATED)
.body(storeService.createStore(request, images));
.body(storeService.createStore(request, images, LocalDate.now()));
}

@PreAuthorize("isAuthenticated() and hasRole('ROLE_ADMIN')")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kr.co.pinup.stores.model.dto;

import kr.co.pinup.stores.Store;

import java.time.LocalDateTime;

public record StoreCreateResponse(Long id, LocalDateTime createdAt) {
public static StoreCreateResponse from(Store store) {
return new StoreCreateResponse(store.getId(), store.getCreatedAt());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public interface StoreRepository extends JpaRepository<Store, Long> {
List<Store> findAllByLocation_SigunguAndStoreStatusAndIsDeletedFalse(String sigungu, StoreStatus selectedStatus);

List<Store> findAllByLocation_SigunguAndIsDeletedFalse(String sigungu);

List<Store> findByStoreStatusInAndIsDeletedFalse(List<StoreStatus> storeStatuses);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,69 @@
import kr.co.pinup.stores.Store;
import kr.co.pinup.stores.model.enums.StoreStatus;
import kr.co.pinup.stores.repository.StoreRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.function.Supplier;

@Slf4j
@Component
@RequiredArgsConstructor
public class StoreStatusScheduler {

private final StoreRepository storeRepository;

private final Supplier<LocalDate> todaySupplier;

public StoreStatusScheduler(
final StoreRepository storeRepository,
@Qualifier("todaySupplier") final Supplier<LocalDate> todaySupplier
) {
this.storeRepository = storeRepository;
this.todaySupplier = todaySupplier;
}

@Transactional
@Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul")
public void updateStoreStatuses() {
List<Store> stores = storeRepository.findAll();
LocalDate today = LocalDate.now();

for (Store store : stores) {
StoreStatus newStatus;

if (store.getStartDate().isAfter(today)) {
newStatus = StoreStatus.PENDING;
} else if (store.getEndDate().isBefore(today)) {
newStatus = StoreStatus.DISMISSED;
} else {
newStatus = StoreStatus.RESOLVED;
}

if (store.getStoreStatus() != newStatus) {
store.updateStatus(newStatus);
log.info("스토어 [{}] 상태 {}로 변경", store.getId(), newStatus);
}
final LocalDate today = todaySupplier.get();

final List<Store> stores = storeRepository.findByStoreStatusInAndIsDeletedFalse(
List.of(StoreStatus.PENDING, StoreStatus.RESOLVED)
);

final long updatedCount = stores.stream()
.filter(store -> {
final StoreStatus changeStoreStatus = calculateStoreStatus(store, today);
if (store.getStoreStatus() != changeStoreStatus) {
store.updateStatus(changeStoreStatus);
log.info("스토어 [{}] 상태 {}로 변경", store.getId(), changeStoreStatus);
return true;
}

return false;
})
.count();

log.info("총 {}개의 스토어 상태를 갱신했습니다.", updatedCount);
}

private StoreStatus calculateStoreStatus(final Store store, final LocalDate today) {
final StoreStatus storeStatus = store.getStoreStatus();
if (storeStatus == StoreStatus.PENDING &&
!store.getStartDate().isAfter(today)) {
return StoreStatus.RESOLVED;
}

if (storeStatus == StoreStatus.RESOLVED &&
store.getEndDate().isBefore(today)) {
return StoreStatus.DISMISSED;
}

storeRepository.saveAll(stores);
return storeStatus;
}
}
Loading