Skip to content

Commit

Permalink
[merge] v1.0.4 릴리즈 - #314
Browse files Browse the repository at this point in the history
[RELEASE] v1.0.4 릴리즈
  • Loading branch information
gardening-y authored Oct 29, 2024
2 parents 929afa6 + 18fdcb8 commit 746f8bc
Show file tree
Hide file tree
Showing 20 changed files with 197 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private StreamMessageListenerContainer<String, MapRecord<String, String, String>
StreamOffset.create(streamKey, ReadOffset.lastConsumed()))
.cancelOnError(t -> true) // 오류 발생 시 구독 취소
.consumer(Consumer.from(consumerGroup, consumerName))
.autoAcknowledge(true)
.autoAcknowledge(false)
.build(), eventListener);

container.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
)
@OpenAPIDefinition(
info = @io.swagger.v3.oas.annotations.info.Info(
title = "SOPT APP Team API",
title = "Team DateRoad API",
version = "v2",
description = "SOPT 공식 앱팀 API입니다."
description = "팀 DateRoad API입니다."
),
servers= {
@Server(url = "${app.base.url}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.dateroad.auth.argumentresolve.UserId;
import org.dateroad.course.dto.request.CourseCreateReq;
import org.dateroad.course.dto.request.CourseCreateSwaggerDto;
Expand Down Expand Up @@ -355,7 +356,7 @@ ResponseEntity<CourseCreateRes> createCourse(
@RequestPart("tags") @Validated @Size(min = 1, max = 3) final List<TagCreateReq> tags,
@RequestPart("places") @Validated @Size(min = 1) final List<CoursePlaceGetReq> places,
@RequestPart("images") @Validated @Size(min =1, max = 10) final List<MultipartFile> images
);
) throws ExecutionException, InterruptedException;


@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,35 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import java.util.List;
import java.util.concurrent.ExecutionException;
import lombok.RequiredArgsConstructor;
import org.dateroad.auth.argumentresolve.UserId;
import org.dateroad.code.FailureCode;
import org.dateroad.course.dto.request.CourseGetAllReq;
import org.dateroad.course.dto.request.CourseCreateReq;
import org.dateroad.course.dto.request.CourseGetAllReq;
import org.dateroad.course.dto.request.CoursePlaceGetReq;
import org.dateroad.course.dto.request.PointUseReq;
import org.dateroad.course.dto.request.TagCreateReq;
import org.dateroad.course.dto.response.CourseAccessGetAllRes;
import org.dateroad.course.dto.response.CourseCreateRes;
import org.dateroad.course.dto.response.CourseGetAllRes;
import org.dateroad.course.dto.response.CourseAccessGetAllRes;
import org.dateroad.course.dto.response.DateAccessCreateRes;
import org.dateroad.course.service.AsyncService;
import org.dateroad.course.service.CourseService;
import org.dateroad.date.dto.response.CourseGetDetailRes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
Expand Down Expand Up @@ -65,7 +74,7 @@ public ResponseEntity<CourseCreateRes> createCourse(
@RequestPart("tags") @Validated @Size(min = 1, max = 3) final List<TagCreateReq> tags,
@RequestPart("places") @Validated @Size(min = 1) final List<CoursePlaceGetReq> places,
@RequestPart("images") @Validated @Size(min =1, max = 10) final List<MultipartFile> images
) {
) throws ExecutionException, InterruptedException {
validateListSizeMin(places, 1, FailureCode.WRONG_COURSE_PLACE_SIZE);
validateListSizeMin(tags, 1, FailureCode.WRONG_TAG_SIZE);
validateListSizeMax(tags, 3, FailureCode.WRONG_TAG_SIZE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dateroad.course.dto;

import java.util.List;
import lombok.Builder;
import org.dateroad.place.domain.CoursePlace;
import org.dateroad.tag.domain.CourseTag;


@Builder
public record CourseWithPlacesAndTagsDto(List<CoursePlace> coursePlaces, List<CourseTag> courseTags) {
public static CourseWithPlacesAndTagsDto of(List<CoursePlace> coursePlaces, List<CourseTag> courseTags) {
return CourseWithPlacesAndTagsDto.builder().coursePlaces(coursePlaces).courseTags(courseTags).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,48 @@
package org.dateroad.course.service;

import static java.lang.Thread.startVirtualThread;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dateroad.code.FailureCode;
import org.dateroad.course.dto.request.CoursePlaceGetReq;
import org.dateroad.course.dto.CourseWithPlacesAndTagsDto;
import org.dateroad.course.dto.request.CourseCreateEvent;
import org.dateroad.course.dto.request.PointUseReq;
import org.dateroad.course.dto.request.TagCreateReq;
import org.dateroad.date.domain.Course;
import org.dateroad.exception.DateRoadException;
import org.dateroad.image.domain.Image;
import org.dateroad.image.service.ImageService;
import org.dateroad.place.domain.CoursePlace;
import org.dateroad.point.event.MessageDto.FreeMessageDTO;
import org.dateroad.point.event.MessageDto.PointMessageDTO;
import org.dateroad.tag.domain.CourseTag;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Transactional(readOnly = true)
@Slf4j
public class AsyncService {
private final CoursePlaceService coursePlaceService;
private final CourseTagService courseTagService;
private final ImageService imageService;
private final RedisTemplate<String, String> redistemplateForCluster;

public List<Image> createImage(final List<MultipartFile> images, final Course course) {
return imageService.saveImages(images, course);
}

public void createCourseTags(final List<TagCreateReq> tags, final Course course) {
courseTagService.createCourseTags(tags, course);
}

public void createCoursePlace(final List<CoursePlaceGetReq> places, final Course course) {
coursePlaceService.createCoursePlace(places, course);
}

public void publishEvenUserPoint(final Long userId, PointUseReq pointUseReq) {
Map<String, String> fieldMap = new HashMap<>();
@Transactional
public RecordId publishEvenUserPoint(final Long userId, PointUseReq pointUseReq) {
try {
fieldMap.put("userId", userId.toString());
fieldMap.put("point", String.valueOf(pointUseReq.getPoint()));
fieldMap.put("type", pointUseReq.getType().name());
fieldMap.put("description", pointUseReq.getDescription());
redistemplateForCluster.opsForStream().add("coursePoint", fieldMap);
PointMessageDTO pointMessage = PointMessageDTO.of(userId, pointUseReq);
return redistemplateForCluster.opsForStream().add("coursePoint", pointMessage.toMap());
} catch (QueryTimeoutException e) {
log.error("Redis command timed out for userId: {} - Retrying...", userId, e);
throw new DateRoadException(FailureCode.REDIS_CONNECTION_ERROR);
Expand All @@ -62,11 +52,11 @@ public void publishEvenUserPoint(final Long userId, PointUseReq pointUseReq) {
}
}

@Transactional
public void publishEventUserFree(final Long userId) {
Map<String, String> fieldMap = new HashMap<>();
try {
fieldMap.put("userId", userId.toString());
redistemplateForCluster.opsForStream().add("courseFree", fieldMap);
FreeMessageDTO freeMessage = FreeMessageDTO.of(userId);
redistemplateForCluster.opsForStream().add("courseFree", freeMessage.toMap());
} catch (QueryTimeoutException e) {
log.error("Redis command timed out for userId: {} - Retrying...", userId, e);
throw new DateRoadException(FailureCode.REDIS_CONNECTION_ERROR);
Expand All @@ -77,39 +67,18 @@ public void publishEventUserFree(final Long userId) {
}

@Transactional
public void runAsyncTasks(List<CoursePlaceGetReq> places, List<TagCreateReq> tags,
Course saveCourse)
throws InterruptedException {
List<Thread> threads = new ArrayList<>();
final boolean[] hasError = {false}; // 에러 발생 여부 확인
threads.add(runAsyncTaskWithExceptionHandling(() -> {
createCoursePlace(places, saveCourse);
}, hasError));
threads.add(runAsyncTaskWithExceptionHandling(() -> {
createCourseTags(tags, saveCourse);
}, hasError));
for (Thread thread : threads) {
thread.join();
}
if (hasError[0]) {
throw new RuntimeException("코스 생성중 오류 발생"); // 예외 발생 시 전체 작업 실패 처리
public CourseWithPlacesAndTagsDto runAsyncTasks(CourseCreateEvent event) {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
CompletableFuture<List<CoursePlace>> placeFuture = CompletableFuture.supplyAsync(() -> coursePlaceService.createCoursePlace(event.getPlaces(), event.getCourse()), executor);
CompletableFuture<List<CourseTag>> tagFuture = CompletableFuture.supplyAsync(() -> courseTagService.createCourseTags(event.getTags(), event.getCourse()), executor);
CompletableFuture<Void> allFutures = CompletableFuture.allOf(placeFuture, tagFuture);
allFutures.join();
return CourseWithPlacesAndTagsDto.of(placeFuture.join(), tagFuture.join());
}
}

@Transactional
public String createCourseImages(List<MultipartFile> images, Course course) {
List<Image> imageList = createImage(images, course);
return imageList.getFirst().getImageUrl();
}

public Thread runAsyncTaskWithExceptionHandling(Runnable task, boolean[] hasError) {
return startVirtualThread(() -> {
try {
task.run();
} catch (Exception e) {
hasError[0] = true;
throw new DateRoadException(FailureCode.COURSE_CREATE_ERROR);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,24 @@
package org.dateroad.course.service;

import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.dateroad.course.dto.request.CoursePlaceGetReq;
import org.dateroad.date.domain.Course;
import org.dateroad.place.domain.CoursePlace;
import org.dateroad.place.repository.CoursePlaceRepository;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class CoursePlaceService {
private final CoursePlaceRepository coursePlaceRepository;

public float findTotalDurationByCourseId(final Long id) {
return coursePlaceRepository.findTotalDurationByCourseId(id);
}

public void createCoursePlace(final List<CoursePlaceGetReq> places, final Course course) {
List<CoursePlace> coursePlaces = places.stream()
public List<CoursePlace> createCoursePlace(final List<CoursePlaceGetReq> places, final Course course) {
return places.stream()
.map(placeReq -> CoursePlace.create(
placeReq.getTitle(),
placeReq.getDuration(),
course,
placeReq.getSequence()
))
.collect(Collectors.toList());
coursePlaceRepository.saveAll(coursePlaces);
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.dateroad.course.service;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dateroad.code.FailureCode;
import org.dateroad.exception.DateRoadException;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.PendingMessage;
import org.springframework.data.redis.connection.stream.PendingMessages;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Slf4j
public class CourseRollbackService {
private final RedisTemplate<String, String> redistemplateForCluster;

public void rollbackCourse(final RecordId recordId) {
PendingMessages pendingMessage = redistemplateForCluster.opsForStream().pending(
"coursePoint", Consumer.from("coursePointGroup", "instance-1"), Range.unbounded(), 100L
);
for (PendingMessage message : pendingMessage) {
if(message.getId() == recordId){
redistemplateForCluster.opsForStream().acknowledge("coursePoint", "coursePointGroup", recordId);
throw new DateRoadException(FailureCode.COURSE_CREATE_ERROR);
}
}
}
}
Loading

0 comments on commit 746f8bc

Please sign in to comment.