From c820238787f427390d2f711e501e7e5cd0448f57 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:45:46 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[chore]=20Redis=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dateroad-api/build.gradle | 1 + .../dateroad/config/RedisStreamConfig.java | 125 ++++++++++++++++++ .../course/dto/request/PointUseReq.java | 20 +++ .../course/service/CoursePaymentType.java | 5 + .../point/event/pointEventListener.java | 32 +++++ 5 files changed, 183 insertions(+) create mode 100644 dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java create mode 100644 dateroad-api/src/main/java/org/dateroad/course/dto/request/PointUseReq.java create mode 100644 dateroad-api/src/main/java/org/dateroad/course/service/CoursePaymentType.java create mode 100644 dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java diff --git a/dateroad-api/build.gradle b/dateroad-api/build.gradle index 6fa0c7b4..731a49d5 100644 --- a/dateroad-api/build.gradle +++ b/dateroad-api/build.gradle @@ -2,6 +2,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' runtimeOnly 'org.postgresql:postgresql' implementation project(path: ':dateroad-common') diff --git a/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java b/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java new file mode 100644 index 00000000..bcbe3630 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java @@ -0,0 +1,125 @@ +package org.dateroad.config; + + +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.codec.StringCodec; +import io.lettuce.core.output.StatusOutput; +import io.lettuce.core.protocol.CommandArgs; +import io.lettuce.core.protocol.CommandKeyword; +import io.lettuce.core.protocol.CommandType; +import lombok.RequiredArgsConstructor; +import org.dateroad.date.repository.CourseRepository; +import org.dateroad.point.event.FreeEventListener; +import org.dateroad.point.event.pointEventListener; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.connection.stream.*; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.data.redis.stream.StreamMessageListenerContainer; +import org.springframework.data.redis.stream.Subscription; + +import java.time.Duration; +import java.util.Iterator; +import java.util.Objects; + +@Configuration +@RequiredArgsConstructor +public class RedisStreamConfig { + private final pointEventListener pointEventListener; + private final FreeEventListener freeEventListener; + @Value("${spring.data.redis.host}") + private String host; + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + return redisTemplate; + } + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + public void createStreamConsumerGroup(String streamKey, String consumerGroupName) { + // Stream이 존재 하지 않으면, MKSTREAM 옵션을 통해 만들고, ConsumerGroup또한 생성한다 + System.out.println(streamKey + consumerGroupName); + // Stream이 존재하지 않으면, MKSTREAM 옵션을 통해 스트림과 소비자 그룹을 생성 + if (Boolean.FALSE.equals(redisTemplate().hasKey(streamKey))) { + RedisAsyncCommands commands = (RedisAsyncCommands) Objects.requireNonNull( + redisTemplate() + .getConnectionFactory()) + .getConnection() + .getNativeConnection(); + + CommandArgs args = new CommandArgs<>(StringCodec.UTF8) + .add(CommandKeyword.CREATE) + .add(streamKey) + .add(consumerGroupName) + .add("0") + .add("MKSTREAM"); + // MKSTREAM 옵션을 사용하여 스트림과 그룹을 생성 + commands.dispatch(CommandType.XGROUP, new StatusOutput<>(StringCodec.UTF8), args).toCompletableFuture() + .join(); + } + // Stream 존재 시, ConsumerGroup 존재 여부 확인 후 ConsumerGroup을 생성 + else { + if (!isStreamConsumerGroupExist(streamKey, consumerGroupName)) { + redisTemplate().opsForStream().createGroup(streamKey, ReadOffset.from("0"), consumerGroupName); + } + } + } + // ConsumerGroup 존재 여부 확인 + public boolean isStreamConsumerGroupExist(String streamKey, String consumerGroupName) { + Iterator iterator = redisTemplate() + .opsForStream().groups(streamKey).stream().iterator(); + + while (iterator.hasNext()) { + StreamInfo.XInfoGroup xInfoGroup = iterator.next(); + if (xInfoGroup.groupName().equals(consumerGroupName)) { + return true; + } + } + return false; + } + + @Bean + public Subscription PointSubscription() { + createStreamConsumerGroup("coursePoint", "courseGroup"); + StreamMessageListenerContainer.StreamMessageListenerContainerOptions> containerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions + .builder().pollTimeout(Duration.ofMillis(100)).build(); + + StreamMessageListenerContainer> container = StreamMessageListenerContainer.create( + redisConnectionFactory(), + containerOptions); + + Subscription subscription = container.receiveAutoAck(Consumer.from("courseGroup", "instance-1"), + StreamOffset.create("course", ReadOffset.lastConsumed()), pointEventListener); + container.start(); + return subscription; + } + + @Bean + public Subscription FreeSubscription() { + createStreamConsumerGroup("courseFree", "courseGroup"); + StreamMessageListenerContainer.StreamMessageListenerContainerOptions> containerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions + .builder().pollTimeout(Duration.ofMillis(100)).build(); + StreamMessageListenerContainer> container = StreamMessageListenerContainer.create( + redisConnectionFactory(), + containerOptions); + Subscription subscription = container.receiveAutoAck(Consumer.from("courseGroup", "instance-2"), + StreamOffset.create("courseFree", ReadOffset.lastConsumed()), freeEventListener); + container.start(); + return subscription; + } +} + diff --git a/dateroad-api/src/main/java/org/dateroad/course/dto/request/PointUseReq.java b/dateroad-api/src/main/java/org/dateroad/course/dto/request/PointUseReq.java new file mode 100644 index 00000000..3581a04b --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/course/dto/request/PointUseReq.java @@ -0,0 +1,20 @@ +package org.dateroad.course.dto.request; + +import lombok.AccessLevel; +import lombok.Builder; +import org.dateroad.point.domain.TransactionType; + +@Builder(access = AccessLevel.PROTECTED) +public record PointUseReq( + int point, + TransactionType type, + String description +) { + public static PointUseReq of(int point, TransactionType type, String description) { + return PointUseReq.builder() + .point(point) + .type(type) + .description(description) + .build(); + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/course/service/CoursePaymentType.java b/dateroad-api/src/main/java/org/dateroad/course/service/CoursePaymentType.java new file mode 100644 index 00000000..28b51694 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/course/service/CoursePaymentType.java @@ -0,0 +1,5 @@ +package org.dateroad.course.service; + +public enum CoursePaymentType { + FREE,POINT +} diff --git a/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java b/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java new file mode 100644 index 00000000..28a4bbfe --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java @@ -0,0 +1,32 @@ +package org.dateroad.point.event; + +import java.util.Map; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.dateroad.code.FailureCode; +import org.dateroad.exception.DateRoadException; +import org.dateroad.user.domain.User; +import org.dateroad.user.repository.UserRepository; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.stream.StreamListener; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class pointEventListener implements StreamListener> { + private final UserRepository userRepository; + + @Override + @Transactional + public void onMessage(MapRecord message) { + Map map = message.getValue(); + Long userId = Long.valueOf(map.get("userId")); + User user = userRepository.findById(userId).orElseThrow( + () -> new DateRoadException(FailureCode.USER_NOT_FOUND) + ); + int point = Integer.parseInt(map.get("point")); // 감소시킬 포인트 + user.setTotalPoint(user.getTotalPoint() + point); + userRepository.save(user); + } +} From 6dac86cc5b5b31886e9670b754a5372c4670a651 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:46:12 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[feat]=20RedisStream=20Config=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/dateroad/config/RedisStreamConfig.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java b/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java index bcbe3630..d3b47bba 100644 --- a/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java +++ b/dateroad-api/src/main/java/org/dateroad/config/RedisStreamConfig.java @@ -50,14 +50,14 @@ public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(host, port); } - public void createStreamConsumerGroup(String streamKey, String consumerGroupName) { + public void createStreamConsumerGroup(final String streamKey, final String consumerGroupName) { // Stream이 존재 하지 않으면, MKSTREAM 옵션을 통해 만들고, ConsumerGroup또한 생성한다 System.out.println(streamKey + consumerGroupName); // Stream이 존재하지 않으면, MKSTREAM 옵션을 통해 스트림과 소비자 그룹을 생성 if (Boolean.FALSE.equals(redisTemplate().hasKey(streamKey))) { RedisAsyncCommands commands = (RedisAsyncCommands) Objects.requireNonNull( - redisTemplate() - .getConnectionFactory()) + redisTemplate() + .getConnectionFactory()) .getConnection() .getNativeConnection(); @@ -78,8 +78,9 @@ public void createStreamConsumerGroup(String streamKey, String consumerGroupName } } } + // ConsumerGroup 존재 여부 확인 - public boolean isStreamConsumerGroupExist(String streamKey, String consumerGroupName) { + public boolean isStreamConsumerGroupExist(final String streamKey, final String consumerGroupName) { Iterator iterator = redisTemplate() .opsForStream().groups(streamKey).stream().iterator(); @@ -103,7 +104,7 @@ public Subscription PointSubscription() { containerOptions); Subscription subscription = container.receiveAutoAck(Consumer.from("courseGroup", "instance-1"), - StreamOffset.create("course", ReadOffset.lastConsumed()), pointEventListener); + StreamOffset.create("coursePoint", ReadOffset.lastConsumed()), pointEventListener); container.start(); return subscription; } From 24b575f64495c731702bfb35a327fa3d4b0a4f39 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:47:41 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[feat]=20Point=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dateroad/course/api/CourseController.java | 14 ++++++ .../dateroad/course/facade/AsyncService.java | 21 +++++++++ .../course/service/CourseService.java | 43 ++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/dateroad-api/src/main/java/org/dateroad/course/api/CourseController.java b/dateroad-api/src/main/java/org/dateroad/course/api/CourseController.java index b04144af..0116ac15 100644 --- a/dateroad-api/src/main/java/org/dateroad/course/api/CourseController.java +++ b/dateroad-api/src/main/java/org/dateroad/course/api/CourseController.java @@ -8,6 +8,7 @@ import org.dateroad.course.dto.request.CourseGetAllReq; import org.dateroad.course.dto.request.CourseCreateReq; 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.CourseCreateRes; import org.dateroad.course.dto.response.CourseGetAllRes; @@ -20,7 +21,10 @@ import org.springframework.http.ResponseEntity; 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.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; @@ -64,4 +68,14 @@ public ResponseEntity createCourse( HttpStatus.CREATED ).body(CourseCreateRes.of(course.getId())); } + + @PostMapping("/{courseId}/date-access") + public ResponseEntity openCourse( + @UserId final Long userId, + @PathVariable final Long courseId, + @RequestBody final PointUseReq pointUseReq + ) { + courseService.openCourse(userId,courseId,pointUseReq); + return ResponseEntity.ok().build(); + } } diff --git a/dateroad-api/src/main/java/org/dateroad/course/facade/AsyncService.java b/dateroad-api/src/main/java/org/dateroad/course/facade/AsyncService.java index 2ee4eaed..d3d3c3b8 100644 --- a/dateroad-api/src/main/java/org/dateroad/course/facade/AsyncService.java +++ b/dateroad-api/src/main/java/org/dateroad/course/facade/AsyncService.java @@ -1,15 +1,20 @@ package org.dateroad.course.facade; +import java.util.HashMap; import java.util.List; +import java.util.Map; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.dateroad.Image.service.ImageService; 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.service.CoursePlaceService; import org.dateroad.course.service.CourseTagService; import org.dateroad.date.domain.Course; import org.dateroad.image.domain.Image; +import org.dateroad.user.domain.User; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +27,8 @@ public class AsyncService { private final CoursePlaceService coursePlaceService; private final CourseTagService courseTagService; private final ImageService imageService; + private final StringRedisTemplate redisTemplate; + public Image findFirstByCourseOrderBySequenceAsc(final Course course) { return imageService.findFirstByCourseOrderBySequenceAsc(course); @@ -45,4 +52,18 @@ public void createCourseTags(final List tags, final Course course) public void createCoursePlace(final List places, final Course course) { coursePlaceService.createCoursePlace(places, course); } + + public void publishEvenUserPoint(User user, PointUseReq pointUseReq) { + Map fieldMap = new HashMap<>(); + fieldMap.put("userId", user.getId().toString()); + fieldMap.put("point", Integer.toString(pointUseReq.point())); + fieldMap.put("type", pointUseReq.type().toString()); + redisTemplate.opsForStream().add("coursePoint", fieldMap); + } + + public void publishEventUserFree(User user) { + Map fieldMap = new HashMap<>(); + fieldMap.put("userId", user.getId().toString()); + redisTemplate.opsForStream().add("courseFree", fieldMap); + } } diff --git a/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java b/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java index abd10e66..70d7a8d4 100644 --- a/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java +++ b/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java @@ -9,16 +9,20 @@ import org.dateroad.course.dto.request.CourseGetAllReq; import org.dateroad.course.dto.request.CourseCreateReq; import org.dateroad.course.dto.request.CoursePlaceGetReq; +import org.dateroad.course.dto.request.PointUseReq; import org.dateroad.course.dto.response.CourseDtoGetRes; import org.dateroad.course.dto.response.CourseGetAllRes; import org.dateroad.course.dto.response.DateAccessGetAllRes; import org.dateroad.course.facade.AsyncService; import org.dateroad.date.domain.Course; import org.dateroad.date.repository.CourseRepository; +import org.dateroad.dateAccess.domain.DateAccess; import org.dateroad.dateAccess.repository.DateAccessRepository; import org.dateroad.exception.DateRoadException; import org.dateroad.image.domain.Image; import org.dateroad.like.repository.LikeRepository; +import org.dateroad.point.domain.Point; +import org.dateroad.point.repository.PointRepository; import org.dateroad.user.domain.User; import org.dateroad.user.repository.UserRepository; import org.springframework.data.jpa.domain.Specification; @@ -35,6 +39,7 @@ public class CourseService { private final DateAccessRepository dateAccessRepository; private final UserRepository userRepository; private final AsyncService asyncService; + private final PointRepository pointRepository; public CourseGetAllRes getAllCourses(CourseGetAllReq courseGetAllReq) { Specification spec = CourseSpecifications.filterByCriteria(courseGetAllReq); @@ -67,7 +72,7 @@ private CourseDtoGetRes convertToDto(Course course) { ); } - public DateAccessGetAllRes getAllDataAccessCourse(Long userId) { + public DateAccessGetAllRes getAllDataAccessCourse(final Long userId) { List accesses = dateAccessRepository.findCoursesByUserId(userId); List courseDtoGetResList = convertToDtoList(accesses, Function.identity()); return DateAccessGetAllRes.of(courseDtoGetResList); @@ -100,4 +105,40 @@ public Course createCourse(final Long userId, final CourseCreateReq courseRegist course.setThumbnail(thumnailUrl); return saveCourse; } + + @Transactional + public void openCourse(final Long userId, final Long courseId, final PointUseReq pointUseReq) { + User user = userRepository.findById(userId).orElseThrow( + () -> new DateRoadException(FailureCode.USER_NOT_FOUND) + ); + Course course = courseRepository.findById(courseId).orElseThrow( + () -> new DateRoadException(FailureCode.COURSE_NOT_FOUND) + ); + Point point = Point.create(user, pointUseReq.point(), pointUseReq.type(), pointUseReq.description()); + CoursePaymentType coursePaymentType = validateUserFreeOrPoint(user, pointUseReq.point()); + processCoursePayment(coursePaymentType, user, point, pointUseReq); + dateAccessRepository.save(DateAccess.create(course, user)); + } + + private CoursePaymentType validateUserFreeOrPoint(final User user, final int requiredPoints) { + if (user.getFree() > 0) { + return CoursePaymentType.FREE; // User가 free를 갖고 있으면 true를 반환 + } else if (user.getTotalPoint() < requiredPoints) { + throw new DateRoadException(FailureCode.INSUFFICIENT_USER_POINTS); + } + return CoursePaymentType.POINT; + } + + public void processCoursePayment(final CoursePaymentType coursePaymentType, User user, final Point point, + final PointUseReq pointUseReq) { + switch (coursePaymentType) { + case FREE -> { + asyncService.publishEventUserFree(user); + } + case POINT -> { + pointRepository.save(point); + asyncService.publishEvenUserPoint(user, pointUseReq); + } + } + } } From 0415ab478a96b530fedf9f02af5bb575398f15a2 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:48:46 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[del]=20dateAccess=20id=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20NotNull=20=EC=A0=9C=EA=B1=B0=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/dateroad/dateAccess/domain/DateAccess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dateroad-domain/src/main/java/org/dateroad/dateAccess/domain/DateAccess.java b/dateroad-domain/src/main/java/org/dateroad/dateAccess/domain/DateAccess.java index 272b9624..15602e50 100644 --- a/dateroad-domain/src/main/java/org/dateroad/dateAccess/domain/DateAccess.java +++ b/dateroad-domain/src/main/java/org/dateroad/dateAccess/domain/DateAccess.java @@ -13,6 +13,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.dateroad.common.BaseTimeEntity; import org.dateroad.date.domain.Course; @@ -28,7 +29,6 @@ public class DateAccess extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "date_access_id") - @NotNull private Long id; @OneToOne From 9c1ada00064bd9f24710f9cfc4365a14a9682897 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:49:28 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[feat]=20Point,=20Free=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20EventListener=20=EA=B5=AC=ED=98=84=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../point/event/FreeEventListener.java | 32 +++++++++++++++++++ .../point/event/pointEventListener.java | 4 +-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java diff --git a/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java b/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java new file mode 100644 index 00000000..c4f7ae2a --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java @@ -0,0 +1,32 @@ +package org.dateroad.point.event; + +import java.util.Map; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.dateroad.code.FailureCode; +import org.dateroad.exception.DateRoadException; +import org.dateroad.user.domain.User; +import org.dateroad.user.repository.UserRepository; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.stream.StreamListener; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class FreeEventListener implements StreamListener> { + private final UserRepository userRepository; + + @Override + @Transactional + public void onMessage(final MapRecord message) { + Map map = message.getValue(); + Long userId = Long.valueOf(map.get("userId")); + User user = userRepository.findById(userId).orElseThrow( + () -> new DateRoadException(FailureCode.USER_NOT_FOUND) + ); + int userPoint = user.getFree(); + user.setFree(userPoint -1); + userRepository.save(user); + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java b/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java index 28a4bbfe..30997df9 100644 --- a/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java +++ b/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java @@ -19,14 +19,14 @@ public class pointEventListener implements StreamListener message) { + public void onMessage(final MapRecord message) { Map map = message.getValue(); Long userId = Long.valueOf(map.get("userId")); User user = userRepository.findById(userId).orElseThrow( () -> new DateRoadException(FailureCode.USER_NOT_FOUND) ); int point = Integer.parseInt(map.get("point")); // 감소시킬 포인트 - user.setTotalPoint(user.getTotalPoint() + point); + user.setTotalPoint(user.getTotalPoint() - point); userRepository.save(user); } } From 3e7ce1f1641cd7edc131e6cf9b9a201414a01e9d Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:49:48 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[feat]=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dateroad/code/FailureCode.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java b/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java index ee6f5c1a..6f3530af 100644 --- a/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java +++ b/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java @@ -56,6 +56,8 @@ public enum FailureCode { DATE_TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "e4045", "데이트 태그를 찾을 수 없습니다."), DATE_PLACE_NOT_FOUND(HttpStatus.NOT_FOUND, "e4046", "데이트 장소를 찾을 수 없습니다."), + COURSE_NOT_FOUND(HttpStatus.NOT_FOUND, "e4047", "코스를 찾을수 없습니다."), + INSUFFICIENT_USER_POINTS(HttpStatus.NOT_FOUND, "e4048", "유저의 포인트가 부족합니다."), /** * 405 Method Not Allowed */ @@ -68,11 +70,11 @@ public enum FailureCode { DUPLICATE_USER(HttpStatus.CONFLICT, "e4091", "이미 존재하는 유저입니다."), DUPLICATE_NICKNAME(HttpStatus.CONFLICT, "e4092", "이미 존재하는 닉네임입니다."), - + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "e5000", "서버 내부 오류입니다."); /** * 500 Internal Server Error */ - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "e5000", "서버 내부 오류입니다."); + private final HttpStatus httpStatus; private final String code; From 5c82f2aeafeac68707ef6350beab82de07d4e62a Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:50:19 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[chore]=20=EB=9D=84=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=9C=EA=B1=B0=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dateroad/point/service/PointService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dateroad-api/src/main/java/org/dateroad/point/service/PointService.java b/dateroad-api/src/main/java/org/dateroad/point/service/PointService.java index 303ca134..ca29baf8 100644 --- a/dateroad-api/src/main/java/org/dateroad/point/service/PointService.java +++ b/dateroad-api/src/main/java/org/dateroad/point/service/PointService.java @@ -14,7 +14,6 @@ @RequiredArgsConstructor public class PointService { private final PointRepository pointRepository; - public PointGetAllRes getAllPoints(Long userId) { List points = pointRepository.findAllByUserId(userId) .stream().map(PointDto::of) @@ -29,4 +28,5 @@ public PointsDto pointTypeChecktoList(List points, TransactionType typ .map(PointDtoRes::of) .toList()); } + } From e3e7691928fa5dd9ab2cc5a91be11a11cc3bb127 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 01:50:53 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[feat]=20User=20free,=20totalPoint=20?= =?UTF-8?q?=EC=97=90=20Setter=20=EC=B6=94=EA=B0=80=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dateroad/user/domain/User.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java b/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java index 2a089682..bb372156 100644 --- a/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java +++ b/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java @@ -7,6 +7,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import org.dateroad.common.BaseTimeEntity; import org.dateroad.tag.domain.UserTag; @@ -45,11 +46,13 @@ public class User extends BaseTimeEntity { @Builder.Default @Column(name = "free") @NotNull + @Setter private int free = 3; @Builder.Default @Column(name = "total_point") @NotNull + @Setter private int totalPoint = 0; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) From 36c23ba31a56d59412efe0fecc086dead763c05c Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Fri, 12 Jul 2024 05:14:06 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[refactor]=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20-=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/service/CourseService.java | 28 ++++++++++++------- .../point/event/FreeEventListener.java | 10 +++++-- .../point/event/pointEventListener.java | 10 +++++-- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java b/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java index 70d7a8d4..74dbb154 100644 --- a/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java +++ b/dateroad-api/src/main/java/org/dateroad/course/service/CourseService.java @@ -41,21 +41,21 @@ public class CourseService { private final AsyncService asyncService; private final PointRepository pointRepository; - public CourseGetAllRes getAllCourses(CourseGetAllReq courseGetAllReq) { + public CourseGetAllRes getAllCourses(final CourseGetAllReq courseGetAllReq) { Specification spec = CourseSpecifications.filterByCriteria(courseGetAllReq); List courses = courseRepository.findAll(spec); List courseDtoGetResList = convertToDtoList(courses, Function.identity()); return CourseGetAllRes.of(courseDtoGetResList); } - private List convertToDtoList(List entities, Function converter) { + private List convertToDtoList(final List entities, final Function converter) { return entities.stream() .map(converter) .map(this::convertToDto) .collect(Collectors.toList()); } - private CourseDtoGetRes convertToDto(Course course) { + private CourseDtoGetRes convertToDto(final Course course) { int likeCount = likeRepository.countByCourse(course) .orElse(0); Image thumbnailImage = asyncService.findFirstByCourseOrderBySequenceAsc(course); @@ -108,18 +108,26 @@ public Course createCourse(final Long userId, final CourseCreateReq courseRegist @Transactional public void openCourse(final Long userId, final Long courseId, final PointUseReq pointUseReq) { - User user = userRepository.findById(userId).orElseThrow( - () -> new DateRoadException(FailureCode.USER_NOT_FOUND) - ); - Course course = courseRepository.findById(courseId).orElseThrow( - () -> new DateRoadException(FailureCode.COURSE_NOT_FOUND) - ); + User user = getUser(userId); + Course course = getCourse(courseId); Point point = Point.create(user, pointUseReq.point(), pointUseReq.type(), pointUseReq.description()); CoursePaymentType coursePaymentType = validateUserFreeOrPoint(user, pointUseReq.point()); processCoursePayment(coursePaymentType, user, point, pointUseReq); dateAccessRepository.save(DateAccess.create(course, user)); } + private Course getCourse(final Long courseId) { + return courseRepository.findById(courseId).orElseThrow( + () -> new DateRoadException(FailureCode.COURSE_NOT_FOUND) + ); + } + + private User getUser(final Long userId) { + return userRepository.findById(userId).orElseThrow( + () -> new DateRoadException(FailureCode.USER_NOT_FOUND) + ); + } + private CoursePaymentType validateUserFreeOrPoint(final User user, final int requiredPoints) { if (user.getFree() > 0) { return CoursePaymentType.FREE; // User가 free를 갖고 있으면 true를 반환 @@ -129,7 +137,7 @@ private CoursePaymentType validateUserFreeOrPoint(final User user, final int req return CoursePaymentType.POINT; } - public void processCoursePayment(final CoursePaymentType coursePaymentType, User user, final Point point, + public void processCoursePayment(final CoursePaymentType coursePaymentType, final User user, final Point point, final PointUseReq pointUseReq) { switch (coursePaymentType) { case FREE -> { diff --git a/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java b/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java index c4f7ae2a..8966766a 100644 --- a/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java +++ b/dateroad-api/src/main/java/org/dateroad/point/event/FreeEventListener.java @@ -22,11 +22,15 @@ public class FreeEventListener implements StreamListener message) { Map map = message.getValue(); Long userId = Long.valueOf(map.get("userId")); - User user = userRepository.findById(userId).orElseThrow( - () -> new DateRoadException(FailureCode.USER_NOT_FOUND) - ); + User user = getUser(userId); int userPoint = user.getFree(); user.setFree(userPoint -1); userRepository.save(user); } + + private User getUser(Long userId) { + return userRepository.findById(userId).orElseThrow( + () -> new DateRoadException(FailureCode.USER_NOT_FOUND) + ); + } } diff --git a/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java b/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java index 30997df9..ddff2aff 100644 --- a/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java +++ b/dateroad-api/src/main/java/org/dateroad/point/event/pointEventListener.java @@ -22,11 +22,15 @@ public class pointEventListener implements StreamListener message) { Map map = message.getValue(); Long userId = Long.valueOf(map.get("userId")); - User user = userRepository.findById(userId).orElseThrow( - () -> new DateRoadException(FailureCode.USER_NOT_FOUND) - ); + User user = getUser(userId); int point = Integer.parseInt(map.get("point")); // 감소시킬 포인트 user.setTotalPoint(user.getTotalPoint() - point); userRepository.save(user); } + + private User getUser(Long userId) { + return userRepository.findById(userId).orElseThrow( + () -> new DateRoadException(FailureCode.USER_NOT_FOUND) + ); + } }