diff --git a/.http/NaverMap.http b/.http/NaverMap.http index 2f8a54f..187f53b 100644 --- a/.http/NaverMap.http +++ b/.http/NaverMap.http @@ -5,6 +5,6 @@ X-NCP-APIGW-API-KEY: {{naver-map-client-secret}} ### 자동차 경로 계산 api -GET https://naveropenapi.apigw.ntruss.com/map-direction-15/v1/driving?start=127.1058342,37.359708&goal=126.9284261,37.5258975 +GET https://naveropenapi.apigw.ntruss.com/map-direction-15/v1/driving?start=128.5175868107,36.5376537450&goal=128.5163479914,36.5390674494 X-NCP-APIGW-API-KEY-ID: {{naver-map-client-id}} X-NCP-APIGW-API-KEY: {{naver-map-client-secret}} \ No newline at end of file diff --git a/.http/Odsay.http b/.http/Odsay.http index dbc5446..4bcb3ad 100644 --- a/.http/Odsay.http +++ b/.http/Odsay.http @@ -11,3 +11,10 @@ Origin: https://weplanplans.site GET https://api.odsay.com/v1/api/searchPubTransPathT?apiKey={{odsay-key}}&SX=127.1051573&SY=37.3718141&EX=227.124206&EY=37.495701 Origin: https://weplanplans.site +### 대중교통 길 찾기 +GET https://api.odsay.com/v1/api/searchPubTransPathT?apiKey={{odsay-key}}&SX=128.5163479914&SY=36.5390674494&EX=128.5163479914&EY=36.5390674494 +Origin: https://weplanplans.site + +### 대중교통 길 찾기 +GET https://api.odsay.com/v1/api/searchPubTransPathT?apiKey={{odsay-key}}&SX=128.5163479914&SY=36.5390674494&EX=128.3383767680&EY=38.0731872541 +Origin: https://weplanplans.site \ No newline at end of file diff --git a/.json/AddTripItems.json b/.json/AddTripItems.json new file mode 100644 index 0000000..7adb6e7 --- /dev/null +++ b/.json/AddTripItems.json @@ -0,0 +1,8 @@ +{ + "visitDate": "2024-01-04", + "newTripItems": [ + { + "tourItemId": 72 + } + ] +} \ No newline at end of file diff --git a/.json/GetPathAndItems.json b/.json/GetPathAndItems.json new file mode 100644 index 0000000..874f2ef --- /dev/null +++ b/.json/GetPathAndItems.json @@ -0,0 +1,3 @@ +{ + "visitDate": "2024-01-03" +} \ No newline at end of file diff --git a/.json/TripItemPriceUpdateMsg.json b/.json/TripItemPriceUpdateMsg.json new file mode 100644 index 0000000..14d023f --- /dev/null +++ b/.json/TripItemPriceUpdateMsg.json @@ -0,0 +1,3 @@ +{ + "price": 50000 +} \ No newline at end of file diff --git a/.json/TripItemTransportationUpdateMsg.json b/.json/TripItemTransportationUpdateMsg.json new file mode 100644 index 0000000..8838697 --- /dev/null +++ b/.json/TripItemTransportationUpdateMsg.json @@ -0,0 +1,3 @@ +{ + "transportation": "CAR" +} \ No newline at end of file diff --git a/.json/TripItemVistDateUpdateMsg.json b/.json/TripItemVistDateUpdateMsg.json new file mode 100644 index 0000000..3cad74e --- /dev/null +++ b/.json/TripItemVistDateUpdateMsg.json @@ -0,0 +1,3 @@ +{ + "visitDate": "2024-01-04" +} \ No newline at end of file diff --git a/.json/UpdateTripItemOrder.json b/.json/UpdateTripItemOrder.json new file mode 100644 index 0000000..d33b99a --- /dev/null +++ b/.json/UpdateTripItemOrder.json @@ -0,0 +1,21 @@ +{ + "visitDate": "2024-01-03", + "tripItemOrder": [ + { + "tripItemId": 1, + "seqNum": 3 + }, + { + "tripItemId": 3, + "seqNum": 4 + }, + { + "tripItemId": 4, + "seqNum": 1 + }, + { + "tripItemId": 6, + "seqNum": 2 + } + ] +} \ No newline at end of file diff --git a/src/main/java/org/tenten/tentenstomp/domain/member/entity/Member.java b/src/main/java/org/tenten/tentenstomp/domain/member/entity/Member.java index df942c0..8133199 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/member/entity/Member.java +++ b/src/main/java/org/tenten/tentenstomp/domain/member/entity/Member.java @@ -65,6 +65,4 @@ public class Member extends BaseTimeEntity { @OneToMany(mappedBy = "member", fetch = LAZY, cascade = REMOVE) private final List likedItems = new ArrayList<>(); - @OneToMany(mappedBy = "creator", fetch = LAZY, cascade = REMOVE) - private final List tripItems = new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/java/org/tenten/tentenstomp/domain/member/repository/MemberRepository.java b/src/main/java/org/tenten/tentenstomp/domain/member/repository/MemberRepository.java index 7a0b571..3d6e8bf 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/member/repository/MemberRepository.java +++ b/src/main/java/org/tenten/tentenstomp/domain/member/repository/MemberRepository.java @@ -1,7 +1,18 @@ package org.tenten.tentenstomp.domain.member.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.tenten.tentenstomp.domain.member.entity.Member; +import org.tenten.tentenstomp.domain.trip.dto.response.TripMemberInfoMsg; + +import java.util.List; +import java.util.Optional; public interface MemberRepository extends JpaRepository { + @Query("SELECT NEW org.tenten.tentenstomp.domain.trip.dto.response.TripMemberInfoMsg(m.id, m.nickname, m.profileImageUrl) " + + "FROM TripMember tm LEFT OUTER JOIN Member m ON tm.member.id = m.id WHERE tm.trip.id = :tripId") + List findTripMemberInfoByTripId(@Param("tripId") Long tripId); + @Query("SELECT NEW org.tenten.tentenstomp.domain.trip.dto.response.TripMemberInfoMsg(m.id, m.nickname, m.profileImageUrl) FROM Member m WHERE m.id = :memberId") + Optional findTripMemberInfoByMemberId(@Param("memberId") Long memberId); } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripController.java b/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripController.java index 7fe2912..c271e3f 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripController.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripController.java @@ -21,6 +21,21 @@ public void testKafka(@Payload TripUpdateMsg tripUpdateMsg) { kafkaProducer.send("kafka", tripUpdateMsg); } + @MessageMapping("/trips/{tripId}/connectMember") + public void connectMember(@DestinationVariable String tripId, @Payload MemberConnectMsg memberConnectMsg) { + tripService.connectMember(tripId, memberConnectMsg); + } + + @MessageMapping("/trips/{tripId}/disconnectMember") + public void disconnectMember(@DestinationVariable String tripId, @Payload MemberDisconnectMsg memberDisconnectMsg) { + tripService.disconnectMember(tripId, memberDisconnectMsg); + } + + @MessageMapping("/trips/{tripId}/enterMember") + public void enterMember(@DestinationVariable String tripId, @Payload MemberConnectMsg memberConnectMsg) { + tripService.enterMember(tripId, memberConnectMsg); + } + @MessageMapping("/trips/{tripId}/info") public void editPlan(@DestinationVariable String tripId, @Payload TripUpdateMsg tripUpdateMsg) { tripService.updateTrip(tripId, tripUpdateMsg); @@ -36,13 +51,8 @@ public void updateTripItemOrder(@DestinationVariable String tripId, @Payload Tri tripService.updateTripItemOrder(tripId, orderUpdateMsg); } - @MessageMapping("/trips/{tripId}/connectMember") - public void connectMember(@DestinationVariable String tripId, @Payload MemberConnectMsg memberConnectMsg) { - tripService.connectMember(tripId, memberConnectMsg); - } - - @MessageMapping("/trips/{tripId}/disconnectMember") - public void disconnectMember(@DestinationVariable String tripId, @Payload MemberDisconnectMsg memberDisconnectMsg) { - tripService.disconnectMember(tripId, memberDisconnectMsg); + @MessageMapping("/trips/{tripId}/getPathAndItems") + public void getPathAndItems(@DestinationVariable String tripId, @Payload PathAndItemRequestMsg pathAndItemRequestMsg) { + tripService.getPathAndItems(tripId, pathAndItemRequestMsg); } } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripItemController.java b/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripItemController.java index ae4a0f5..ba4acd1 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripItemController.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/controller/TripItemController.java @@ -6,6 +6,7 @@ import org.springframework.messaging.handler.annotation.Payload; import org.springframework.web.bind.annotation.RestController; import org.tenten.tentenstomp.domain.trip.dto.request.TripItemPriceUpdateMsg; +import org.tenten.tentenstomp.domain.trip.dto.request.TripItemTransportationUpdateMsg; import org.tenten.tentenstomp.domain.trip.dto.request.TripItemVisitDateUpdateMsg; import org.tenten.tentenstomp.domain.trip.service.TripItemService; @@ -24,6 +25,11 @@ public void updateTripItemVisitDate(@DestinationVariable String tripItemId, @Pay tripItemService.updateTripItemVisitDate(tripItemId, visitDateUpdateMsg); } + @MessageMapping("/tripItems/{tripItemId}/updateTransportation") + public void updateTripItemTransportation(@DestinationVariable String tripItemId, @Payload TripItemTransportationUpdateMsg tripItemTransportationUpdateMsg) { + tripItemService.updateTripItemTransportation(tripItemId, tripItemTransportationUpdateMsg); + } + @MessageMapping("/tripItems/{tripItemId}/deleteItem") public void deleteTripItem(@DestinationVariable String tripItemId) { tripItemService.deleteTripItem(tripItemId); diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/PathAndItemRequestMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/PathAndItemRequestMsg.java new file mode 100644 index 0000000..31cac18 --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/PathAndItemRequestMsg.java @@ -0,0 +1,5 @@ +package org.tenten.tentenstomp.domain.trip.dto.request; +public record PathAndItemRequestMsg( + String visitDate +) { +} diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemAddMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemAddMsg.java index 05369ad..ade9262 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemAddMsg.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemAddMsg.java @@ -1,19 +1,32 @@ package org.tenten.tentenstomp.domain.trip.dto.request; +import org.tenten.tentenstomp.domain.tour.entity.TourItem; +import org.tenten.tentenstomp.domain.trip.entity.Trip; +import org.tenten.tentenstomp.domain.trip.entity.TripItem; import org.tenten.tentenstomp.global.common.enums.Transportation; +import java.time.LocalDate; import java.util.List; +import static org.tenten.tentenstomp.global.common.enums.Transportation.PUBLIC_TRANSPORTATION; + public record TripItemAddMsg( String visitDate, List newTripItems ) { public record TripItemCreateRequest( - Long tourItemId, - Transportation transportation, - Long seqNum, - Long price + Long tourItemId ) { + public static TripItem toEntity(TourItem tourItem, Trip trip, Long seqNum, LocalDate visitDate) { + return TripItem.builder() + .transportation(PUBLIC_TRANSPORTATION) + .trip(trip) + .price(0L) + .seqNum(seqNum) + .tourItem(tourItem) + .visitDate(visitDate) + .build(); + } } } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemTransportationUpdateMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemTransportationUpdateMsg.java new file mode 100644 index 0000000..f63d4ce --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/request/TripItemTransportationUpdateMsg.java @@ -0,0 +1,8 @@ +package org.tenten.tentenstomp.domain.trip.dto.request; + +import org.tenten.tentenstomp.global.common.enums.Transportation; + +public record TripItemTransportationUpdateMsg( + Transportation transportation +) { +} diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripBudgetMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripBudgetMsg.java new file mode 100644 index 0000000..dccbe35 --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripBudgetMsg.java @@ -0,0 +1,8 @@ +package org.tenten.tentenstomp.domain.trip.dto.response; + +public record TripBudgetMsg( + Long tripId, + Long budget, + Long calculatedPrice +) { +} diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfo.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfo.java new file mode 100644 index 0000000..1ebb9a5 --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfo.java @@ -0,0 +1,19 @@ +package org.tenten.tentenstomp.domain.trip.dto.response; + +import org.tenten.tentenstomp.global.common.enums.Transportation; + +import java.time.LocalDate; + +public record TripItemInfo( + Long tripItemId, + Long tourItemId, + String name, + String thumbnailUrl, + Long contentTypeId, + Transportation transportation, + Long seqNum, + LocalDate visitDate, + Long price +) { + +} \ No newline at end of file diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfoMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfoMsg.java index aeca812..aa37fbb 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfoMsg.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemInfoMsg.java @@ -2,6 +2,8 @@ import org.tenten.tentenstomp.global.common.enums.Transportation; +import java.time.LocalDate; + public record TripItemInfoMsg( Long tripItemId, Long tourItemId, @@ -13,4 +15,5 @@ public record TripItemInfoMsg( String visitDate, Long price ) { + } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemMsg.java index e8e30ff..204b0ed 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemMsg.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripItemMsg.java @@ -1,6 +1,12 @@ package org.tenten.tentenstomp.domain.trip.dto.response; +import org.tenten.tentenstomp.domain.trip.dto.request.TripItemPriceUpdateMsg; +import org.tenten.tentenstomp.domain.trip.entity.Trip; +import org.tenten.tentenstomp.domain.trip.entity.TripItem; +import org.tenten.tentenstomp.global.common.enums.Category; + import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; public record TripItemMsg( @@ -8,4 +14,19 @@ public record TripItemMsg( String visitDate, List tripItems ) { + public static TripItemMsg fromTripItemList(Long tripId, String visitDate, List tripItems) { + return new TripItemMsg(tripId, visitDate, tripItems.stream().map(t-> new TripItemInfoMsg(t.getId(), t.getTourItem().getId(), t.getTourItem().getTitle(), t.getTourItem().getOriginalThumbnailUrl(), Category.fromCode(t.getTourItem().getContentTypeId()).toString(), t.getTransportation(), t.getSeqNum(), t.getVisitDate().toString(), t.getPrice())).toList()); + } + + public static TripItemMsg fromTripItemList(Long tripId, String visitDate, List tripItems, Long tripItemId, TripItemPriceUpdateMsg updateMsg) { + List tripItemInfoMsgs = new ArrayList<>(); + for (TripItem t : tripItems) { + if (t.getId().equals(tripItemId)) { + tripItemInfoMsgs.add(new TripItemInfoMsg(t.getId(), t.getTourItem().getId(), t.getTourItem().getTitle(), t.getTourItem().getOriginalThumbnailUrl(), Category.fromCode(t.getTourItem().getContentTypeId()).toString(), t.getTransportation(), t.getSeqNum(), t.getVisitDate().toString(), updateMsg.price())); + } else { + tripItemInfoMsgs.add(new TripItemInfoMsg(t.getId(), t.getTourItem().getId(), t.getTourItem().getTitle(), t.getTourItem().getOriginalThumbnailUrl(), Category.fromCode(t.getTourItem().getContentTypeId()).toString(), t.getTransportation(), t.getSeqNum(), t.getVisitDate().toString(), t.getPrice())); + } + } + return new TripItemMsg(tripId, visitDate, tripItemInfoMsgs); + } } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripMemberMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripMemberMsg.java index 352f539..a38205a 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripMemberMsg.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripMemberMsg.java @@ -4,6 +4,7 @@ public record TripMemberMsg( Long tripId, - List connectedMembers + List connectedMembers, + List tripMembers ) { } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripPathInfoMsg.java b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripPathInfoMsg.java index daa826b..19489a4 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripPathInfoMsg.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/dto/response/TripPathInfoMsg.java @@ -14,7 +14,7 @@ public record TripPathInfoMsg( ) { public record PathInfo( - Long price, + Integer price, Double totalDistance, Long totalTime ){} diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/entity/Trip.java b/src/main/java/org/tenten/tentenstomp/domain/trip/entity/Trip.java index 2475d81..53738fe 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/entity/Trip.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/entity/Trip.java @@ -5,14 +5,17 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; import org.tenten.tentenstomp.domain.trip.dto.request.TripUpdateMsg; import org.tenten.tentenstomp.domain.trip.dto.response.TripInfoMsg; import org.tenten.tentenstomp.global.common.BaseTimeEntity; import org.tenten.tentenstomp.global.common.enums.TripStatus; +import org.tenten.tentenstomp.global.converter.MapConverter; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static jakarta.persistence.CascadeType.REMOVE; import static jakarta.persistence.EnumType.STRING; @@ -41,6 +44,13 @@ public class Trip extends BaseTimeEntity { private Boolean isDeleted; private String tripName; private Long budget; + @ColumnDefault("0") + private Long tripItemPriceSum; + @ColumnDefault("0") + private Integer transportationPriceSum; + @Convert(converter = MapConverter.class) + @Column(columnDefinition = "JSON") + private Map tripPathPriceMap; @OneToMany(mappedBy = "trip", fetch = LAZY, cascade = REMOVE) private final List tripMembers = new ArrayList<>(); @@ -64,4 +74,19 @@ public TripInfoMsg changeTripInfo(TripUpdateMsg request) { return new TripInfoMsg(this.getId(), request.startDate(), request.endDate(), this.getNumberOfPeople(), this.getTripName(), this.getTripStatus(), this.getArea(), this.getSubarea(), this.getBudget()); } + + public TripInfoMsg toTripInfo() { + return new TripInfoMsg(this.getId(), this.startDate.toString(), this.endDate.toString(), this.getNumberOfPeople(), this.getTripName(), this.getTripStatus(), + this.getArea(), this.getSubarea(), this.getBudget()); + } + + public void updateTransportationPriceSum(Integer oldVisitDateTransportationPriceSum, Integer newVisitDateTransportationPriceSum) { + this.transportationPriceSum -= oldVisitDateTransportationPriceSum; + this.transportationPriceSum += newVisitDateTransportationPriceSum; + } + + public void updateTripItemPriceSum(Long oldTripItemPrice, Long newTripItemPrice) { + this.tripItemPriceSum -= oldTripItemPrice; + this.tripItemPriceSum += newTripItemPrice; + } } \ No newline at end of file diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripItem.java b/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripItem.java index 323ccff..e9d2f04 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripItem.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripItem.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; import org.tenten.tentenstomp.domain.member.entity.Member; import org.tenten.tentenstomp.domain.tour.entity.TourItem; import org.tenten.tentenstomp.global.common.BaseTimeEntity; @@ -27,21 +28,39 @@ public class TripItem extends BaseTimeEntity { @GeneratedValue(strategy = IDENTITY) @Column(name = "tripItemId") private Long id; + @Enumerated(STRING) private Transportation transportation; private Long seqNum; // 방문 순서 private LocalDate visitDate; + @ColumnDefault("0") private Long price; // 예상 비용 @ManyToOne @JoinColumn(name = "tripId") private Trip trip; - @ManyToOne - @JoinColumn(name = "memberId") - private Member creator; @ManyToOne @JoinColumn(name = "tourItemId") private TourItem tourItem; + + public void updateSeqNum(Long seqNum) { + this.seqNum = seqNum; + } + + public void updatePrice(Long price) { + this.price = price; + } + + public void updateVisitDate(LocalDate visitDate) { + this.visitDate = visitDate; + } + + public void updateTransportation(Transportation transportation) { + this.transportation = transportation; + } + public int compareWithSeqNum(TripItem compare) { + return Long.compare(this.seqNum, compare.seqNum); + } } \ No newline at end of file diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripLikedItemPreference.java b/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripLikedItemPreference.java index 84f7410..3b534ec 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripLikedItemPreference.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/entity/TripLikedItemPreference.java @@ -19,8 +19,8 @@ public class TripLikedItemPreference { @Column(name = "tripLikedItemPreferenceId") private Long id; - private Boolean liked; - private Boolean disliked; + private Boolean prefer; + private Boolean notPrefer; @ManyToOne @JoinColumn(name = "tripMemberId") diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripItemRepository.java b/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripItemRepository.java index 36a19e1..a330da2 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripItemRepository.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripItemRepository.java @@ -1,7 +1,26 @@ package org.tenten.tentenstomp.domain.trip.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.tenten.tentenstomp.domain.trip.dto.response.TripItemInfo; +import org.tenten.tentenstomp.domain.trip.entity.Trip; import org.tenten.tentenstomp.domain.trip.entity.TripItem; +import org.tenten.tentenstomp.global.component.dto.request.TripPlace; + +import java.time.LocalDate; +import java.util.List; public interface TripItemRepository extends JpaRepository { + @Query("SELECT NEW org.tenten.tentenstomp.domain.trip.dto.response.TripItemInfo(" + + "ti.id, t.id, t.title, t.originalThumbnailUrl, t.contentTypeId, ti.transportation, ti.seqNum, ti.visitDate, ti.price" + + ") FROM TripItem ti LEFT OUTER JOIN TourItem t ON ti.tourItem.id = t.id WHERE ti.trip.id = :tripId AND ti.visitDate = :visitDate ORDER BY ti.seqNum ASC") + List getTripItemInfoByTripIdAndVisitDate(@Param("tripId") Long tripId, @Param("visitDate") LocalDate visitDate); + + @Query("SELECT ti FROM TripItem ti JOIN FETCH ti.tourItem WHERE ti.trip.id = :tripId AND ti.visitDate = :visitDate ORDER BY ti.seqNum ASC") + List findTripItemByTripIdAndVisitDate(@Param("tripId") Long tripId, @Param("visitDate") LocalDate visitDate); + @Query("SELECT NEW org.tenten.tentenstomp.global.component.dto.request.TripPlace(" + + "ti.seqNum, ti.transportation, t.longitude, t.latitude, ti.price" + + ") FROM TripItem ti LEFT OUTER JOIN TourItem t ON ti.tourItem.id = t.id WHERE ti.trip.id = :tripId AND ti.visitDate = :visitDate ORDER BY ti.seqNum ASC") + List findTripPlaceByTripIdAndVisitDate(@Param("tripId") Long tripId, @Param("visitDate") LocalDate visitDate); } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripRepository.java b/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripRepository.java index f008fbd..d0a85c2 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripRepository.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/repository/TripRepository.java @@ -1,7 +1,13 @@ package org.tenten.tentenstomp.domain.trip.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.tenten.tentenstomp.domain.trip.entity.Trip; +import java.util.Optional; + public interface TripRepository extends JpaRepository { + @Query("SELECT t FROM Trip t JOIN FETCH t.tripItems WHERE t.id = :tripId") + Optional findTripByTripId(@Param("tripId") Long tripId); } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripItemService.java b/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripItemService.java index ccbe97e..2b741b8 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripItemService.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripItemService.java @@ -4,86 +4,186 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.tenten.tentenstomp.domain.trip.dto.request.TripItemPriceUpdateMsg; +import org.tenten.tentenstomp.domain.trip.dto.request.TripItemTransportationUpdateMsg; import org.tenten.tentenstomp.domain.trip.dto.request.TripItemVisitDateUpdateMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripItemMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripPathMsg; +import org.tenten.tentenstomp.domain.trip.dto.response.*; +import org.tenten.tentenstomp.domain.trip.entity.Trip; import org.tenten.tentenstomp.domain.trip.entity.TripItem; import org.tenten.tentenstomp.domain.trip.repository.TripItemRepository; +import org.tenten.tentenstomp.domain.trip.repository.TripRepository; +import org.tenten.tentenstomp.global.cache.RedisCache; +import org.tenten.tentenstomp.global.component.PathComponent; +import org.tenten.tentenstomp.global.component.dto.request.TripPlace; +import org.tenten.tentenstomp.global.component.dto.response.TripPathCalculationResult; import org.tenten.tentenstomp.global.exception.GlobalException; import org.tenten.tentenstomp.global.messaging.kafka.producer.KafkaProducer; -import org.tenten.tentenstomp.global.messaging.redis.publisher.RedisPublisher; -import org.tenten.tentenstomp.global.util.RedisChannelUtil; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import static org.springframework.http.HttpStatus.NOT_FOUND; -import static org.tenten.tentenstomp.global.common.constant.TopicConstant.PATH; -import static org.tenten.tentenstomp.global.common.constant.TopicConstant.TRIP_ITEM; +import static org.tenten.tentenstomp.global.common.constant.TopicConstant.*; @Service @RequiredArgsConstructor public class TripItemService { private final TripItemRepository tripItemRepository; - private final RedisChannelUtil redisChannelUtil; - private final RedisPublisher redisPublisher; + private final TripRepository tripRepository; + private final RedisCache redisCache; private final KafkaProducer kafkaProducer; + private final PathComponent pathComponent; @Transactional public void updateTripItemPrice(String tripItemId, TripItemPriceUpdateMsg priceUpdateMsg) { TripItem tripItem = tripItemRepository.findById(Long.parseLong(tripItemId)).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 tripItem이 없다 " + tripItemId, NOT_FOUND)); - /* - 비즈니스 로직 - */ - TripItemMsg tripItemMsg = new TripItemMsg( - tripItem.getTourItem().getId(), tripItem.getVisitDate().toString(), null - ); - - kafkaProducer.send(TRIP_ITEM, tripItemMsg); - + Long oldPrice = tripItem.getPrice(); + Long newPrice = priceUpdateMsg.price(); + Trip trip = tripItem.getTrip(); + trip.updateTripItemPriceSum(oldPrice, newPrice); + tripItem.updatePrice(newPrice); + List tripItems = trip.getTripItems(); + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg(trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum()); + TripItemMsg tripItemMsg = TripItemMsg.fromTripItemList(trip.getId(), tripItem.getVisitDate().toString(), tripItems, tripItem.getId(), priceUpdateMsg); + sendToKafkaAndSave(tripBudgetMsg, tripItemMsg); } @Transactional public void updateTripItemVisitDate(String tripItemId, TripItemVisitDateUpdateMsg visitDateUpdateMsg) { TripItem tripItem = tripItemRepository.findById(Long.parseLong(tripItemId)).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 tripItem이 없다 " + tripItemId, NOT_FOUND)); + Trip trip = tripRepository.findById(tripItem.getTrip().getId()).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 trip이 없다 " + tripItem.getTrip().getId(), NOT_FOUND)); LocalDate pastDate = tripItem.getVisitDate(); LocalDate newDate = LocalDate.parse(visitDateUpdateMsg.visitDate()); - /* - 비즈니스 로직 - */ - - TripItemMsg tripItemMsgToPastDate = new TripItemMsg( - tripItem.getTourItem().getId(), pastDate.toString(), null - ); - TripItemMsg tripItemMsgToNewDate = new TripItemMsg( - tripItem.getTourItem().getId(), newDate.toString(), null - ); - - TripPathMsg tripPathMsgToPastDate = new TripPathMsg( - tripItem.getTourItem().getId(), pastDate.toString(), null - ); - TripPathMsg tripPathMsgToNewDate = new TripPathMsg( - tripItem.getTourItem().getId(), newDate.toString(), null - ); - - kafkaProducer.send(TRIP_ITEM, tripItemMsgToPastDate); - kafkaProducer.send(TRIP_ITEM, tripItemMsgToNewDate); - kafkaProducer.send(PATH, tripPathMsgToPastDate); - kafkaProducer.send(PATH, tripPathMsgToNewDate); + + List pastDateTripItems = tripItemRepository.findTripItemByTripIdAndVisitDate(tripItem.getTrip().getId(), pastDate); + List newDateTripItems = tripItemRepository.findTripItemByTripIdAndVisitDate(tripItem.getTrip().getId(), newDate); + + Long oldSeqNum = tripItem.getSeqNum(); + Long newSeqNum = (long) newDateTripItems.size()+1; + List newPastDateTripItems = new ArrayList<>(); + for (TripItem pastDateTripItem : pastDateTripItems) { + if (pastDateTripItem.getId().equals(tripItem.getId())) { + continue; + } + if (pastDateTripItem.getSeqNum() > oldSeqNum) { + pastDateTripItem.updateSeqNum(pastDateTripItem.getSeqNum() - 1); + } + newPastDateTripItems.add(pastDateTripItem); + } + + tripItem.updateSeqNum(newSeqNum); + tripItem.updateVisitDate(LocalDate.parse(visitDateUpdateMsg.visitDate())); + newDateTripItems.add(tripItem); + + TripPathCalculationResult pastDateTripPath = pathComponent.getTripPath(TripPlace.fromTripItems(newPastDateTripItems)); + TripPathCalculationResult newDateTripPath = pathComponent.getTripPath(TripPlace.fromTripItems(newDateTripItems)); + + Map tripPathPriceMap = trip.getTripPathPriceMap(); + trip.updateTransportationPriceSum(tripPathPriceMap.getOrDefault(pastDate.toString(), 0), pastDateTripPath.pathPriceSum()); + trip.updateTransportationPriceSum(tripPathPriceMap.getOrDefault(newDate.toString(), 0), newDateTripPath.pathPriceSum()); + tripPathPriceMap.put(pastDate.toString(), pastDateTripPath.pathPriceSum()); + tripPathPriceMap.put(newDate.toString(), newDateTripPath.pathPriceSum()); + tripRepository.save(trip); + + TripItemMsg pastDateTripItemMsg = TripItemMsg.fromTripItemList(trip.getId(), pastDate.toString(), newPastDateTripItems); + TripItemMsg newDateTripItemMsg = TripItemMsg.fromTripItemList(trip.getId(), newDate.toString(), newDateTripItems); + TripPathMsg pastDateTripPathMsg = new TripPathMsg(trip.getId(), pastDate.toString(), pastDateTripPath.tripPathInfoMsgs()); + TripPathMsg newDateTripPathMsg = new TripPathMsg(trip.getId(), newDate.toString(), newDateTripPath.tripPathInfoMsgs()); + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg(trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum()); + + sendToKafkaAndSave(pastDateTripItemMsg, newDateTripItemMsg, pastDateTripPathMsg, newDateTripPathMsg, tripBudgetMsg); + } @Transactional public void deleteTripItem(String tripItemId) { TripItem tripItem = tripItemRepository.findById(Long.parseLong(tripItemId)).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 tripItem이 없다 " + tripItemId, NOT_FOUND)); + Trip trip = tripRepository.findById(tripItem.getTrip().getId()).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 trip이 없다 " + tripItem.getTrip().getId(), NOT_FOUND)); + LocalDate visitDate = tripItem.getVisitDate(); - /* - 비즈니스 로직 - */ - TripItemMsg tripItemMsg = new TripItemMsg( - Long.parseLong(tripItemId), visitDate.toString(), null - ); - TripPathMsg tripPathMsg = new TripPathMsg( - Long.parseLong(tripItemId), visitDate.toString(), null - ); - - kafkaProducer.send(TRIP_ITEM, tripItemMsg); - kafkaProducer.send(PATH, tripPathMsg); + + List tripItems = tripItemRepository.findTripItemByTripIdAndVisitDate(tripItem.getTrip().getId(), visitDate); + Long seqNum = tripItem.getSeqNum(); + List newTripItems = new ArrayList<>(); + for (TripItem newTripItem : tripItems) { + if (newTripItem.getId().equals(tripItem.getId())) { + continue; + } + if (newTripItem.getSeqNum() > seqNum) { + newTripItem.updateSeqNum(newTripItem.getSeqNum() - 1); + } + newTripItems.add(newTripItem); + } + + tripItemRepository.delete(tripItem); + TripPathCalculationResult tripPath = pathComponent.getTripPath(TripPlace.fromTripItems(newTripItems)); + Map tripPathPriceMap = trip.getTripPathPriceMap(); + trip.updateTransportationPriceSum(tripPathPriceMap.getOrDefault(visitDate.toString(), 0), tripPath.pathPriceSum()); + tripPathPriceMap.put(visitDate.toString(), tripPath.pathPriceSum()); + tripRepository.save(trip); + + TripItemMsg tripItemMsg = TripItemMsg.fromTripItemList(trip.getId(), visitDate.toString(), newTripItems); + TripPathMsg tripPathMsg = new TripPathMsg(trip.getId(), visitDate.toString(), tripPath.tripPathInfoMsgs()); + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg(trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum()); + + sendToKafkaAndSave(tripItemMsg, tripPathMsg, tripBudgetMsg); } + @Transactional + public void updateTripItemTransportation(String tripItemId, TripItemTransportationUpdateMsg tripItemTransportationUpdateMsg) { + TripItem tripItem = tripItemRepository.findById(Long.parseLong(tripItemId)).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 tripItem이 없다 " + tripItemId, NOT_FOUND)); + Trip trip = tripRepository.findById(tripItem.getTrip().getId()).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 trip이 없다 " + tripItem.getTrip().getId(), NOT_FOUND)); + + LocalDate visitDate = tripItem.getVisitDate(); + List tripItems = tripItemRepository.findTripItemByTripIdAndVisitDate(tripItem.getTrip().getId(), visitDate); + for (TripItem newTripItem : tripItems) { + if (newTripItem.getId().equals(tripItem.getId())) { + newTripItem.updateTransportation(tripItemTransportationUpdateMsg.transportation()); + } + } + + tripItem.updateTransportation(tripItemTransportationUpdateMsg.transportation()); + tripItemRepository.save(tripItem); + + TripPathCalculationResult tripPath = pathComponent.getTripPath(TripPlace.fromTripItems(tripItems)); + Map tripPathPriceMap = trip.getTripPathPriceMap(); + trip.updateTransportationPriceSum(tripPathPriceMap.getOrDefault(visitDate.toString(), 0), tripPath.pathPriceSum()); + tripPathPriceMap.put(visitDate.toString(), tripPath.pathPriceSum()); + tripRepository.save(trip); + + TripItemMsg tripItemMsg = TripItemMsg.fromTripItemList(trip.getId(), visitDate.toString(), tripItems); + TripPathMsg tripPathMsg = new TripPathMsg(trip.getId(), visitDate.toString(), tripPath.tripPathInfoMsgs()); + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg(trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum()); + + sendToKafkaAndSave(tripItemMsg, tripPathMsg, tripBudgetMsg); + } + + private void sendToKafkaAndSave(Object... dataArgs) { + for (Object data : dataArgs) { + if (data.getClass().equals(TripPathMsg.class)) { + kafkaProducer.send(PATH, data); + TripPathMsg tripPathMsg = (TripPathMsg) data; + redisCache.save(PATH, Long.toString(tripPathMsg.tripId()), tripPathMsg.visitDate(), tripPathMsg); + } + if (data.getClass().equals(TripItemMsg.class)) { + kafkaProducer.send(TRIP_ITEM, data); + TripItemMsg tripItemMsg = (TripItemMsg) data; + redisCache.save(TRIP_ITEM, Long.toString(tripItemMsg.tripId()), tripItemMsg.visitDate(), tripItemMsg); + } + if (data.getClass().equals(TripInfoMsg.class)) { + kafkaProducer.send(TRIP_INFO, data); + TripInfoMsg tripInfoMsg = (TripInfoMsg) data; + redisCache.save(TRIP_INFO, Long.toString(tripInfoMsg.tripId()), tripInfoMsg); + } + if (data.getClass().equals(TripMemberMsg.class)) { + kafkaProducer.send(MEMBER, data); + TripMemberMsg tripMemberMsg = (TripMemberMsg) data; + redisCache.save(MEMBER, Long.toString(tripMemberMsg.tripId()), tripMemberMsg); + } + if (data.getClass().equals(TripBudgetMsg.class)) { + kafkaProducer.send(BUDGET, data); + TripBudgetMsg tripBudgetMsg = (TripBudgetMsg) data; + redisCache.save(BUDGET, Long.toString(tripBudgetMsg.tripId()), tripBudgetMsg); + } + } + } + } diff --git a/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripService.java b/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripService.java index 1cfaf1c..17487d4 100644 --- a/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripService.java +++ b/src/main/java/org/tenten/tentenstomp/domain/trip/service/TripService.java @@ -1,27 +1,31 @@ package org.tenten.tentenstomp.domain.trip.service; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.tenten.tentenstomp.domain.member.repository.MemberRepository; +import org.tenten.tentenstomp.domain.tour.repository.TourItemRepository; import org.tenten.tentenstomp.domain.trip.dto.request.*; -import org.tenten.tentenstomp.domain.trip.dto.response.TripInfoMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripItemMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripMemberMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripPathMsg; +import org.tenten.tentenstomp.domain.trip.dto.request.TripItemAddMsg.TripItemCreateRequest; +import org.tenten.tentenstomp.domain.trip.dto.request.TripItemOrderUpdateMsg.OrderInfo; +import org.tenten.tentenstomp.domain.trip.dto.response.*; import org.tenten.tentenstomp.domain.trip.entity.Trip; +import org.tenten.tentenstomp.domain.trip.entity.TripItem; import org.tenten.tentenstomp.domain.trip.repository.TripItemRepository; -import org.tenten.tentenstomp.global.messaging.kafka.producer.KafkaProducer; -import org.tenten.tentenstomp.global.messaging.redis.publisher.RedisPublisher; import org.tenten.tentenstomp.domain.trip.repository.TripRepository; -import org.tenten.tentenstomp.global.response.GlobalStompResponse; -import org.tenten.tentenstomp.global.util.RedisChannelUtil; +import org.tenten.tentenstomp.global.cache.RedisCache; +import org.tenten.tentenstomp.global.common.enums.Category; +import org.tenten.tentenstomp.global.component.PathComponent; +import org.tenten.tentenstomp.global.component.dto.request.TripPlace; +import org.tenten.tentenstomp.global.component.dto.response.TripPathCalculationResult; +import org.tenten.tentenstomp.global.exception.GlobalException; +import org.tenten.tentenstomp.global.messaging.kafka.producer.KafkaProducer; import java.time.LocalDate; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; +import java.util.*; +import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.tenten.tentenstomp.global.common.constant.TopicConstant.*; @Service @@ -29,80 +33,205 @@ public class TripService { private final TripRepository tripRepository; - private final RedisChannelUtil redisChannelUtil; private final TripItemRepository tripItemRepository; - private final RedisPublisher redisPublisher; + private final MemberRepository memberRepository; + private final TourItemRepository tourItemRepository; private final KafkaProducer kafkaProducer; + private final RedisCache redisCache; + private final PathComponent pathComponent; + private final ObjectMapper objectMapper; + private final Map> tripConnectedMemberMap = new HashMap<>(); + + @Transactional + public void connectMember(String tripId, MemberConnectMsg memberConnectMsg) { + HashMap connectedMemberMap = tripConnectedMemberMap.getOrDefault(tripId, new HashMap<>()); + Optional tripMemberInfoByMemberId = memberRepository.findTripMemberInfoByMemberId(memberConnectMsg.memberId()); + tripMemberInfoByMemberId.ifPresent(tripMemberInfoMsg -> connectedMemberMap.put(memberConnectMsg.memberId(), tripMemberInfoMsg)); + + TripMemberMsg tripMemberMsg = new TripMemberMsg( + Long.parseLong(tripId), connectedMemberMap.values().stream().toList(), memberRepository.findTripMemberInfoByTripId(Long.parseLong(tripId)) + ); + tripConnectedMemberMap.put(tripId, connectedMemberMap); + sendToKafkaAndSave(tripMemberMsg); + + } + + @Transactional + public void disconnectMember(String tripId, MemberDisconnectMsg memberDisconnectMsg) { + HashMap connectedMemberMap = tripConnectedMemberMap.getOrDefault(tripId, new HashMap<>()); + connectedMemberMap.remove(memberDisconnectMsg.memberId()); + + TripMemberMsg tripMemberMsg = new TripMemberMsg( + Long.parseLong(tripId), connectedMemberMap.values().stream().toList(), memberRepository.findTripMemberInfoByTripId(Long.parseLong(tripId)) + ); + tripConnectedMemberMap.put(tripId, connectedMemberMap); + sendToKafkaAndSave(tripMemberMsg); + + } + + @Transactional + public void enterMember(String tripId, MemberConnectMsg memberConnectMsg) { + Trip trip = tripRepository.getReferenceById(Long.parseLong(tripId)); + + kafkaProducer.send(MEMBER, getTripMemberMsg(tripId)); + kafkaProducer.send(TRIP_INFO, trip.toTripInfo()); + kafkaProducer.send(TRIP_ITEM, getTripItemMsg(trip, trip.getStartDate().toString())); + kafkaProducer.send(PATH, getTripPathMsg(trip, trip.getStartDate().toString())); + kafkaProducer.send(BUDGET, getTripBudgetMsg(trip)); + } - private final Map> connectedMemberMap = new HashMap<>(); -// private final PathComponent pathComponent; @Transactional public void updateTrip(String tripId, TripUpdateMsg tripUpdateMsg) { Trip trip = tripRepository.getReferenceById(Long.parseLong(tripId)); - TripInfoMsg tripResponseMsg = trip.changeTripInfo(tripUpdateMsg); + TripInfoMsg tripInfoMsg = trip.changeTripInfo(tripUpdateMsg); + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg( + trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum() + ); tripRepository.save(trip); - kafkaProducer.send(TRIP_INFO, tripResponseMsg); + sendToKafkaAndSave(tripInfoMsg, tripBudgetMsg); + } + @Transactional public void addTripItem(String tripId, TripItemAddMsg tripItemAddMsg) { Trip trip = tripRepository.getReferenceById(Long.parseLong(tripId)); - /* - 비즈니스 로직 - */ + List tripItems = tripItemRepository.findTripItemByTripIdAndVisitDate(Long.parseLong(tripId), LocalDate.parse(tripItemAddMsg.visitDate())); + LocalDate visitDate = LocalDate.parse(tripItemAddMsg.visitDate()); + List newTripItems = new ArrayList<>(); + for (TripItemCreateRequest tripItemCreateRequest : tripItemAddMsg.newTripItems()) { + TripItem entity = TripItemCreateRequest.toEntity(tourItemRepository.getReferenceById(tripItemCreateRequest.tourItemId()), trip, (long) tripItems.size() + 1, visitDate); + newTripItems.add(entity); + tripItems.add(entity); + } + tripItemRepository.saveAll(newTripItems); + + updateBudgetAndItemsAndPath(trip, tripItems, tripItemAddMsg.visitDate()); - TripItemMsg tripItemMsg = new TripItemMsg( - Long.parseLong(tripId), LocalDate.parse(tripItemAddMsg.visitDate()).toString(), null - ); - TripPathMsg tripPathMsg = new TripPathMsg( - Long.parseLong(tripId), LocalDate.parse(tripItemAddMsg.visitDate()).toString(), null - ); + } + + private void updateBudgetAndItemsAndPath(Trip trip, List tripItems, String visitDate) { + TripPathCalculationResult tripPath = pathComponent.getTripPath(TripPlace.fromTripItems(tripItems)); + Map tripPathPriceMap = trip.getTripPathPriceMap(); + trip.updateTransportationPriceSum(tripPathPriceMap.getOrDefault(visitDate, 0), tripPath.pathPriceSum()); + tripPathPriceMap.put(visitDate, tripPath.pathPriceSum()); + tripRepository.save(trip); - kafkaProducer.send(TRIP_ITEM, tripItemMsg); - kafkaProducer.send(PATH, tripPathMsg); + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg(trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum()); + TripItemMsg tripItemMsg = TripItemMsg.fromTripItemList(trip.getId(), visitDate, tripItems); + TripPathMsg tripPathMsg = new TripPathMsg(trip.getId(), visitDate, tripPath.tripPathInfoMsgs()); + + sendToKafkaAndSave(tripBudgetMsg, tripItemMsg, tripPathMsg); } + @Transactional public void updateTripItemOrder(String tripId, TripItemOrderUpdateMsg orderUpdateMsg) { - /* - 비즈니스 로직 - */ - - TripItemMsg tripItemMsg = new TripItemMsg( - Long.parseLong(tripId), LocalDate.parse(orderUpdateMsg.visitDate()).toString(), null - ); - TripPathMsg tripPathMsg = new TripPathMsg( - Long.parseLong(tripId), LocalDate.parse(orderUpdateMsg.visitDate()).toString(), null - ); + Trip trip = tripRepository.findTripByTripId(Long.parseLong(tripId)).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없습니다 " + tripId, NOT_FOUND)); + Map itemOrderMap = new HashMap<>(); + for (OrderInfo orderInfo : orderUpdateMsg.tripItemOrder()) { + itemOrderMap.put(orderInfo.tripItemId(), orderInfo.seqNum()); + } + List tripItems = trip.getTripItems(); + for (TripItem tripItem : tripItems) { + tripItem.updateSeqNum(itemOrderMap.get(tripItem.getId())); + } + updateBudgetAndItemsAndPath(trip, tripItems, orderUpdateMsg.visitDate()); - kafkaProducer.send(TRIP_ITEM, tripItemMsg); - kafkaProducer.send(PATH, tripPathMsg); } @Transactional(readOnly = true) - public void connectMember(String tripId, MemberConnectMsg memberConnectMsg) { - /* - 비즈니스 로직 - */ - TripMemberMsg tripMemberMsg = new TripMemberMsg( - Long.parseLong(tripId), null - ); + public void getPathAndItems(String tripId, PathAndItemRequestMsg pathAndItemRequestMsg) { + Trip trip = tripRepository.getReferenceById(Long.parseLong(tripId)); - kafkaProducer.send(MEMBER, tripMemberMsg); + kafkaProducer.send(TRIP_ITEM, getTripItemMsg(trip, pathAndItemRequestMsg.visitDate())); + kafkaProducer.send(PATH, getTripPathMsg(trip, pathAndItemRequestMsg.visitDate())); } - @Transactional(readOnly = true) - public void disconnectMember(String tripId, MemberDisconnectMsg memberDisconnectMsg) { - /* - 비즈니스 로직 - */ + + private void sendToKafkaAndSave(Object... dataArgs) { + for (Object data : dataArgs) { + if (data.getClass().equals(TripPathMsg.class)) { + kafkaProducer.send(PATH, data); + TripPathMsg tripPathMsg = (TripPathMsg) data; + redisCache.save(PATH, Long.toString(tripPathMsg.tripId()), tripPathMsg.visitDate(), tripPathMsg); + } + if (data.getClass().equals(TripItemMsg.class)) { + kafkaProducer.send(TRIP_ITEM, data); + TripItemMsg tripItemMsg = (TripItemMsg) data; + redisCache.save(TRIP_ITEM, Long.toString(tripItemMsg.tripId()), tripItemMsg.visitDate(), tripItemMsg); + } + if (data.getClass().equals(TripInfoMsg.class)) { + kafkaProducer.send(TRIP_INFO, data); + TripInfoMsg tripInfoMsg = (TripInfoMsg) data; + redisCache.save(TRIP_INFO, Long.toString(tripInfoMsg.tripId()), tripInfoMsg); + } + if (data.getClass().equals(TripMemberMsg.class)) { + kafkaProducer.send(MEMBER, data); + TripMemberMsg tripMemberMsg = (TripMemberMsg) data; + redisCache.save(MEMBER, Long.toString(tripMemberMsg.tripId()), tripMemberMsg); + } + if (data.getClass().equals(TripBudgetMsg.class)) { + kafkaProducer.send(BUDGET, data); + TripBudgetMsg tripBudgetMsg = (TripBudgetMsg) data; + redisCache.save(BUDGET, Long.toString(tripBudgetMsg.tripId()), tripBudgetMsg); + } + } + } + + private TripMemberMsg getTripMemberMsg(String tripId) { + Object cached = redisCache.get(MEMBER, tripId); + if (cached != null) { + return objectMapper.convertValue(cached, TripMemberMsg.class); + } + HashMap connectedMemberMap = tripConnectedMemberMap.getOrDefault(tripId, new HashMap<>()); TripMemberMsg tripMemberMsg = new TripMemberMsg( - Long.parseLong(tripId), null + Long.parseLong(tripId), connectedMemberMap.values().stream().toList(), memberRepository.findTripMemberInfoByTripId(Long.parseLong(tripId)) ); + redisCache.save(MEMBER, tripId, tripMemberMsg); + return tripMemberMsg; + } + + private TripBudgetMsg getTripBudgetMsg(Trip trip) { - kafkaProducer.send(MEMBER, tripMemberMsg); + Object cached = redisCache.get(BUDGET, Long.toString(trip.getId())); + if (cached != null) { + return objectMapper.convertValue(cached, TripBudgetMsg.class); + } + TripBudgetMsg tripBudgetMsg = new TripBudgetMsg( + trip.getId(), trip.getBudget(), trip.getTripItemPriceSum() + trip.getTransportationPriceSum() + ); + redisCache.save(BUDGET, Long.toString(trip.getId()), tripBudgetMsg); + return tripBudgetMsg; } + private TripItemMsg getTripItemMsg(Trip trip, String visitDate) { + Object cached = redisCache.get(TRIP_ITEM, Long.toString(trip.getId()), visitDate); + if (cached != null) { + return objectMapper.convertValue(cached, TripItemMsg.class); + + } + List tripInfos = tripItemRepository.getTripItemInfoByTripIdAndVisitDate(trip.getId(), LocalDate.parse(visitDate)); + List tripItemInfoMsgs = tripInfos.stream().map(t -> new TripItemInfoMsg( + t.tripItemId(), t.tourItemId(), t.name(), t.thumbnailUrl(), Category.fromCode(t.contentTypeId()).getName(), t.transportation(), t.seqNum(), t.visitDate().toString(), t.price() + )).toList(); + TripItemMsg tripItemMsg = new TripItemMsg(trip.getId(), visitDate, tripItemInfoMsgs); + redisCache.save(TRIP_ITEM, Long.toString(trip.getId()), visitDate, tripItemMsg); + return tripItemMsg; + + } + + private TripPathMsg getTripPathMsg(Trip trip, String visitDate) { + Object cached = redisCache.get(PATH, Long.toString(trip.getId()), visitDate); + if (cached != null) { + return objectMapper.convertValue(cached, TripPathMsg.class); + } + TripPathCalculationResult tripPath = pathComponent.getTripPath(tripItemRepository.findTripPlaceByTripIdAndVisitDate(trip.getId(), LocalDate.parse(visitDate))); + TripPathMsg tripPathMsg = new TripPathMsg(trip.getId(), visitDate, tripPath.tripPathInfoMsgs()); + redisCache.save(PATH, Long.toString(trip.getId()), visitDate, tripPathMsg); + return tripPathMsg; + + } } diff --git a/src/main/java/org/tenten/tentenstomp/global/cache/RedisCache.java b/src/main/java/org/tenten/tentenstomp/global/cache/RedisCache.java new file mode 100644 index 0000000..922e655 --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/global/cache/RedisCache.java @@ -0,0 +1,40 @@ +package org.tenten.tentenstomp.global.cache; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.tenten.tentenstomp.global.common.constant.RedisConstant; + +import java.util.concurrent.TimeUnit; + +import static org.tenten.tentenstomp.global.common.constant.RedisConstant.CACHE_EXPIRE_TIME_MS; + +@Component +@RequiredArgsConstructor +public class RedisCache { + private final RedisTemplate redisTemplate; + + public void save(String topic, String key, Object data) { + redisTemplate.opsForValue().set(topic+":"+key, data, CACHE_EXPIRE_TIME_MS, TimeUnit.MILLISECONDS); + } + + public void save(String topic, String key, String visitDate, Object data) { + redisTemplate.opsForValue().set(topic + ":" + key + ":" + visitDate, data, CACHE_EXPIRE_TIME_MS, TimeUnit.MILLISECONDS); + } + + public Object get(String topic, String key) { + return redisTemplate.opsForValue().get(topic+":"+key); + } + + public Object get(String topic, String key, String visitDate) { + return redisTemplate.opsForValue().get(topic+":"+key+":"+visitDate); + } + + public void delete(String topic, String key) { + redisTemplate.delete(topic +":"+key); + } + + public void delete(String topic, String key, String visitDate) { + redisTemplate.delete(topic + ":" + key + ":" + visitDate); + } +} diff --git a/src/main/java/org/tenten/tentenstomp/global/common/constant/RedisConstant.java b/src/main/java/org/tenten/tentenstomp/global/common/constant/RedisConstant.java new file mode 100644 index 0000000..6f859bc --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/global/common/constant/RedisConstant.java @@ -0,0 +1,5 @@ +package org.tenten.tentenstomp.global.common.constant; + +public class RedisConstant { + public static final Long CACHE_EXPIRE_TIME_MS = 1000 * 60 * 30L; +} diff --git a/src/main/java/org/tenten/tentenstomp/global/common/constant/TopicConstant.java b/src/main/java/org/tenten/tentenstomp/global/common/constant/TopicConstant.java index 289df3c..6ec6bc3 100644 --- a/src/main/java/org/tenten/tentenstomp/global/common/constant/TopicConstant.java +++ b/src/main/java/org/tenten/tentenstomp/global/common/constant/TopicConstant.java @@ -5,4 +5,5 @@ public class TopicConstant { public static final String TRIP_ITEM = "tripItems"; public static final String PATH = "path"; public static final String MEMBER = "connectedMember"; + public static final String BUDGET = "budget"; } diff --git a/src/main/java/org/tenten/tentenstomp/global/component/NaverMapComponent.java b/src/main/java/org/tenten/tentenstomp/global/component/NaverMapComponent.java index 354bc88..f699661 100644 --- a/src/main/java/org/tenten/tentenstomp/global/component/NaverMapComponent.java +++ b/src/main/java/org/tenten/tentenstomp/global/component/NaverMapComponent.java @@ -6,12 +6,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg; import org.tenten.tentenstomp.global.exception.GlobalException; import java.util.List; @@ -19,7 +20,7 @@ import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpStatus.CONFLICT; -import static org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg.*; +import static org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg.PathInfo; import static org.tenten.tentenstomp.global.common.constant.NaverMapConstant.NAVER_MAP_API_KEY_HEADER; import static org.tenten.tentenstomp.global.common.constant.NaverMapConstant.NAVER_MAP_CLIENT_ID_HEADER; @@ -69,7 +70,7 @@ public PathInfo calculatePathInfo(String fromLongitude, Integer fuelPrice = (Integer) summary.get("fuelPrice"); Integer distance = (Integer) summary.get("distance"); Integer duration = (Integer) summary.get("duration"); - return new PathInfo((long) duration / 60_000 , (double) distance, (long)tollFare + fuelPrice); + return new PathInfo(tollFare + fuelPrice , (double) distance, (long) duration / 60_000); } else { throw new GlobalException("자동차 경로를 조회할 수 없습니다.", CONFLICT); } diff --git a/src/main/java/org/tenten/tentenstomp/global/component/OdsayComponent.java b/src/main/java/org/tenten/tentenstomp/global/component/OdsayComponent.java index 4c232d1..a5befda 100644 --- a/src/main/java/org/tenten/tentenstomp/global/component/OdsayComponent.java +++ b/src/main/java/org/tenten/tentenstomp/global/component/OdsayComponent.java @@ -13,7 +13,6 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg; import org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg.PathInfo; import org.tenten.tentenstomp.global.exception.GlobalException; @@ -56,10 +55,10 @@ public PathInfo calculatePathInfo(String fromLongitude, List> pathList = (List>) resultMap.get("path"); Map path = pathList.get(0); Map pathInfo = (Map) path.get("info"); - Integer payment = (Integer) pathInfo.get("payment"); - Double distance = (Double) pathInfo.get("totalDistance"); + Integer payment = (Integer) pathInfo.get("totalPayment"); + Double distance = Double.valueOf(Integer.toString((Integer) pathInfo.get("totalDistance"))); Integer totalTime = (Integer) pathInfo.get("totalTime"); - return new PathInfo((long) totalTime, distance, (long) payment); + return new PathInfo( payment, distance, (long) totalTime); } else { throw new GlobalException("대중교통 경로를 조회할 수 없습니다.", CONFLICT); } diff --git a/src/main/java/org/tenten/tentenstomp/global/component/PathComponent.java b/src/main/java/org/tenten/tentenstomp/global/component/PathComponent.java index 48a6cea..89f0d67 100644 --- a/src/main/java/org/tenten/tentenstomp/global/component/PathComponent.java +++ b/src/main/java/org/tenten/tentenstomp/global/component/PathComponent.java @@ -2,17 +2,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg; import org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg.PathInfo; import org.tenten.tentenstomp.global.common.annotation.GetExecutionTime; -import org.tenten.tentenstomp.global.component.dto.request.TripPlace; import org.tenten.tentenstomp.global.component.dto.request.PathCalculateRequest; +import org.tenten.tentenstomp.global.component.dto.request.TripPlace; +import org.tenten.tentenstomp.global.component.dto.response.TripPathCalculationResult; import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; import static org.tenten.tentenstomp.global.common.enums.Transportation.CAR; @@ -22,7 +21,7 @@ public class PathComponent { private final OdsayComponent odsayComponent; private final NaverMapComponent naverMapComponent; - @Async +// @Async public TripPathInfoMsg calculatePath(TripPlace fromPlace, TripPlace toPlace) { long startTime = System.currentTimeMillis(); PathInfo pathInfo; @@ -35,9 +34,19 @@ public TripPathInfoMsg calculatePath(TripPlace fromPlace, TripPlace toPlace) { return new TripPathInfoMsg(fromPlace.seqNum(), toPlace.seqNum(), fromPlace.longitude(), fromPlace.latitude(), toPlace.longitude(), toPlace.latitude(), toPlace.transportation(), pathInfo); } @GetExecutionTime - public List getTripPath(List tripPlaceList) { + public TripPathCalculationResult getTripPath(List tripPlaceList) { List pathCalculateRequests = toPathCalculateRequest(tripPlaceList); - return pathCalculateRequests.stream().flatMap(pathCalculateRequest -> Stream.of(calculatePath(pathCalculateRequest.from(), pathCalculateRequest.to()))).toList(); + int priceSum = 0; + List pathInfoMsgs = new ArrayList<>(); + for (PathCalculateRequest calculateRequest : pathCalculateRequests) { + TripPathInfoMsg tripPathInfoMsg = calculatePath(calculateRequest.from(), calculateRequest.to()); + pathInfoMsgs.add(tripPathInfoMsg); + if (tripPathInfoMsg.pathInfo() != null) { + priceSum += tripPathInfoMsg.pathInfo().price(); + } + } + return new TripPathCalculationResult(priceSum, pathInfoMsgs); + } diff --git a/src/main/java/org/tenten/tentenstomp/global/component/dto/request/TripPlace.java b/src/main/java/org/tenten/tentenstomp/global/component/dto/request/TripPlace.java index a22f361..239e3a3 100644 --- a/src/main/java/org/tenten/tentenstomp/global/component/dto/request/TripPlace.java +++ b/src/main/java/org/tenten/tentenstomp/global/component/dto/request/TripPlace.java @@ -1,11 +1,19 @@ package org.tenten.tentenstomp.global.component.dto.request; +import org.tenten.tentenstomp.domain.trip.entity.TripItem; import org.tenten.tentenstomp.global.common.enums.Transportation; +import java.util.ArrayList; +import java.util.List; + public record TripPlace( Long seqNum, Transportation transportation, String longitude, - String latitude + String latitude, + Long price ) { + public static List fromTripItems(List tripItems) { + return tripItems.stream().map(t -> new TripPlace(t.getSeqNum(), t.getTransportation(), t.getTourItem().getLongitude(), t.getTourItem().getLatitude(), t.getPrice())).toList(); + } } diff --git a/src/main/java/org/tenten/tentenstomp/global/component/dto/response/TripPathCalculationResult.java b/src/main/java/org/tenten/tentenstomp/global/component/dto/response/TripPathCalculationResult.java new file mode 100644 index 0000000..09059ee --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/global/component/dto/response/TripPathCalculationResult.java @@ -0,0 +1,11 @@ +package org.tenten.tentenstomp.global.component.dto.response; + +import org.tenten.tentenstomp.domain.trip.dto.response.TripPathInfoMsg; + +import java.util.List; + +public record TripPathCalculationResult( + Integer pathPriceSum, + List tripPathInfoMsgs +) { +} diff --git a/src/main/java/org/tenten/tentenstomp/global/converter/MapConverter.java b/src/main/java/org/tenten/tentenstomp/global/converter/MapConverter.java new file mode 100644 index 0000000..2294e41 --- /dev/null +++ b/src/main/java/org/tenten/tentenstomp/global/converter/MapConverter.java @@ -0,0 +1,42 @@ +package org.tenten.tentenstomp.global.converter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.util.Map; + +@Converter +public class MapConverter implements AttributeConverter, String> { + protected final ObjectMapper objectMapper; + + public MapConverter() { + this.objectMapper = new ObjectMapper(); + } + + @Override + public String convertToDatabaseColumn(Map map) { + if (ObjectUtils.isEmpty(map)) { + return null; + } + try { + return objectMapper.writeValueAsString(map); // 3 + } catch (Exception e) { + throw new RuntimeException(e); + } } + + @Override + public Map convertToEntityAttribute(String s) { + + if (StringUtils.hasText(s)) { + try { + return (Map) objectMapper.readValue(s, Map.class); // 5 + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return null; + } +} diff --git a/src/main/java/org/tenten/tentenstomp/global/messaging/kafka/consumer/KafkaConsumer.java b/src/main/java/org/tenten/tentenstomp/global/messaging/kafka/consumer/KafkaConsumer.java index 427965e..c6c1627 100644 --- a/src/main/java/org/tenten/tentenstomp/global/messaging/kafka/consumer/KafkaConsumer.java +++ b/src/main/java/org/tenten/tentenstomp/global/messaging/kafka/consumer/KafkaConsumer.java @@ -1,17 +1,11 @@ package org.tenten.tentenstomp.global.messaging.kafka.consumer; import lombok.RequiredArgsConstructor; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.common.internals.Topic; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Service; import org.tenten.tentenstomp.domain.trip.dto.request.TripUpdateMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripInfoMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripItemMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripMemberMsg; -import org.tenten.tentenstomp.domain.trip.dto.response.TripPathMsg; -import org.tenten.tentenstomp.global.common.constant.TopicConstant; +import org.tenten.tentenstomp.domain.trip.dto.response.*; import org.tenten.tentenstomp.global.response.GlobalStompResponse; import org.tenten.tentenstomp.global.util.TopicUtil; @@ -51,4 +45,10 @@ public void updateTripPath(TripPathMsg tripPathMsg) { public void updateConnectedTripMember(TripMemberMsg tripMemberMsg) { messagingTemplate.convertAndSend(topicUtil.topicToReturnEndPoint(tripMemberMsg.tripId(), MEMBER), GlobalStompResponse.ok(tripMemberMsg)); } + + @KafkaListener(topics = BUDGET, groupId = GROUP_ID_CONFIG) + public void updateBudget(TripBudgetMsg tripBudgetMsg) { + messagingTemplate.convertAndSend(topicUtil.topicToReturnEndPoint(tripBudgetMsg.tripId(), BUDGET), GlobalStompResponse.ok(tripBudgetMsg)); + + } }