Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.back.web7_9_codecrete_be.domain.concerts.controller;

import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.result.SetResultResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertTicketTimeSetRequest;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertUpdateRequest;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.*;
import com.back.web7_9_codecrete_be.domain.concerts.service.ConcertNotifyService;
import com.back.web7_9_codecrete_be.domain.concerts.service.ConcertService;
import com.back.web7_9_codecrete_be.domain.concerts.service.KopisApiService;
Expand Down Expand Up @@ -125,6 +122,13 @@ public RsData<SetResultResponse> updateConcert() throws InterruptedException {
return RsData.success(kopisApiService.updateConcertData());
}

@Operation(summary = "공연 조회수 갱신", description = "캐시에 저장되어 있는 조회수를 DB에 반영하고 캐싱된 공연 목록을 초기화합니다.")
@PostMapping("updateConcertViewCount")
public RsData<Void> updateConcertViewCount(){
concertService.viewCountUpdate();
return RsData.success(null);
}

@Operation(summary = "알림 이메일 전송", description = "예매일이 오늘인 공연에 대해 알림 이메일을 전송합니다.")
@PostMapping("sendTicketingEmail")
public RsData<String> sendTicketingEmail(){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concert.ConcertListResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceDetailResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceListResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertLikeResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ListSort;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.*;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concertPlace.PlaceDetailResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.ticketOffice.TicketOfficeElement;
import com.back.web7_9_codecrete_be.domain.concerts.entity.TicketOffice;
Expand Down Expand Up @@ -80,7 +77,7 @@ public RsData<List<ConcertItem>> getLikedConcertList(

@Operation(summary = "공연 상세 조회", description = "공연에 대한 상세 목록을 조회합니다.")
@GetMapping("concertDetail")
public ConcertDetailResponse getConcertDetail(
public RsData<ConcertDetailResponse> getConcertDetail(
@RequestParam
@Schema(description = """
<h3>조회 기준이 되는 concertId입니다.</h3>
Expand All @@ -90,7 +87,7 @@ public ConcertDetailResponse getConcertDetail(
""")
long concertId
) {
return concertService.getConcertDetail(concertId);
return RsData.success(concertService.getConcertDetail(concertId));
}

@Operation(summary = "공연 예매처 조회", description = "공연에 대한 예매처들을 조회합니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.back.web7_9_codecrete_be.domain.concerts.dto.concert;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down Expand Up @@ -29,12 +35,18 @@ public class ConcertDetailResponse {
@Schema(description = "콘서트 장 주소입니다.")
private String placeAddress;

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@Schema(description = "콘서트 예매 시작 날짜입니다.")
private LocalDateTime ticketTime;

@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@Schema(description = "콘서트 시작 날짜입니다.",format = "yyyy-MM-dd")
private LocalDate startDate;

@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@Schema(description = "콘서트 종료 날짜입니다.",format = "yyyy-MM-dd")
private LocalDate endDate;

Expand All @@ -55,4 +67,6 @@ public class ConcertDetailResponse {

@Schema(description = "콘서트 이미지 목록입니다.")
private List<String> concertImageUrls;


}
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package com.back.web7_9_codecrete_be.domain.concerts.dto.concert;

import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Getter
@Setter
@RequiredArgsConstructor
public class ConcertItem {

@Schema(description = "콘서트 Id입니다.")
Expand All @@ -21,12 +29,18 @@ public class ConcertItem {
@Schema(description = "콘서트 장소 이름입니다.")
private String placeName;

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@Schema(description = "콘서트 예매 시작 날짜입니다.",format = "yyyy-MM-ddThh:mm:ss")
private LocalDateTime ticketTime;

@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@Schema(description = "콘서트 시작 날짜입니다.",format = "yyyy-MM-dd")
private LocalDate startDate ;

@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
@Schema(description = "콘서트 종료 날짜입니다.",format = "yyyy-MM-dd")
private LocalDate endDate ;

Expand Down Expand Up @@ -59,7 +73,7 @@ public ConcertItem(Concert concert) {
this.likeCount = concert.getLikeCount();
}

public ConcertItem(long id, String name, String placeName,LocalDateTime ticketTime, LocalDate startDate, LocalDate endDate, String posterUrl, int maxPrice, int minPrice, int viewCount, int likeCount) {
public ConcertItem(long id, String name, String placeName, LocalDateTime ticketTime, LocalDate startDate, LocalDate endDate, String posterUrl, int maxPrice, int minPrice, int viewCount, int likeCount) {
this.id = id;
this.name = name;
this.placeName = placeName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
package com.back.web7_9_codecrete_be.domain.concerts.repository;

import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem;
import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ListSort;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.*;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Repository;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Repository
@RequiredArgsConstructor
public class ConcertRedisRepository {
private final RedisTemplate<String,String> redisTemplate;
private final RedisTemplate<String, String> redisTemplate;
private final RedisTemplate<String, Object> objectRedisTemplate;

private static final String LOCK_FLAG_PREFIX = "initLoad: ";

private static final String CONCERT_DETAIL_PREFIX = "concertDetail: ";

private static final String CONCERT_LIST_PREFIX = "concertList: ";

private static final String VIEW_COUNT_MAP = "viewCountMap";

private static final int HOUR = 3600;

// 최초 공연 로드 락
public void lockSave(String key, String value) {
redisTemplate.opsForValue().set(
LOCK_FLAG_PREFIX + key,
Expand All @@ -29,4 +47,118 @@ public void unlockSave(String key) {
redisTemplate.delete(LOCK_FLAG_PREFIX + key);
}

// 공연 목록 캐싱
public void listSave(ListSort sort, Pageable pageable, List<ConcertItem> list) {
String key = CONCERT_LIST_PREFIX + sort.name() + pageable.getPageNumber();
objectRedisTemplate.opsForValue().set(key, list, HOUR, TimeUnit.SECONDS);
}

// 공연 목록 가져오기
public List<ConcertItem> getConcertsList(Pageable pageable, ListSort sort) {
String key = CONCERT_LIST_PREFIX + sort.name() + pageable.getPageNumber();
Object object = objectRedisTemplate.opsForValue().get(key);
List<ConcertItem> list = (List<ConcertItem>) object;
if (list == null || list.isEmpty()) return List.of(); // null 이 아닌 empty 값 반환
return list;
}

// 캐싱된 모든 공연 목록 삭제
public void deleteAllConcertsList() {
deleteAllItemsByPREFIX(CONCERT_LIST_PREFIX);
}

// 공연 상세 캐싱
public void detailSave(long concertId, ConcertDetailResponse concertDetailResponse) {
objectRedisTemplate.opsForValue().set(
CONCERT_DETAIL_PREFIX + concertId,
concertDetailResponse,
HOUR,
TimeUnit.SECONDS
);
}

// todo : 객체 일부의 값만 바뀌는거니 해당 값만 바꿔서 저장하거나 Redis 내부의 값만 갱신할 수 있는 방법 찾기
public ConcertDetailResponse getDetail(long concertId) {
ConcertDetailResponse concertDetailResponse = (ConcertDetailResponse) objectRedisTemplate.opsForValue().get(CONCERT_DETAIL_PREFIX + concertId);
if (concertDetailResponse == null) return null;
int viewCount = concertDetailResponse.getViewCount();
viewCountSet(concertId, viewCount + 1);
concertDetailResponse.setViewCount(viewCount + 1);
detailSave(concertId, concertDetailResponse);
return concertDetailResponse;
}

// 공연 상세 삭제
public void deleteDetail(String concertId) {
redisTemplate.delete(CONCERT_DETAIL_PREFIX + concertId);
}

// 모든 공연 상세 삭제
public void deleteAllConcertDetail() {
deleteAllItemsByPREFIX(CONCERT_DETAIL_PREFIX);
}

// 조회수 처리 -> 좀 지저분한데 개선 여지 찾아보기
public int viewCountSet(long concertId, int viewCount) {
Map<String, Integer> rawMap = (Map<String, Integer>) objectRedisTemplate.opsForValue().get(VIEW_COUNT_MAP);

if (rawMap == null) {
Map<Long, Integer> viewCountMap = new HashMap<>();
viewCountMap.put(concertId, viewCount);
objectRedisTemplate.opsForValue().set(VIEW_COUNT_MAP, viewCountMap);
} else {
Map<Long, Integer> viewCountMap = convertViewCountMap(rawMap);
viewCountMap.put(concertId, viewCount);
objectRedisTemplate.opsForValue().set(VIEW_COUNT_MAP, viewCountMap);
log.info(viewCountMap.size() + "view count size.");
}
return viewCount;
}

// 조회수 맵 조회
public Map<Long, Integer> getViewCountMap() {
Map<String, Integer> rawMap = (Map<String, Integer>) objectRedisTemplate.opsForValue().get(VIEW_COUNT_MAP);
if (rawMap == null) return null;
objectRedisTemplate.delete(VIEW_COUNT_MAP);
return convertViewCountMap(rawMap);
}

// 조회수 맵 삭제
public void deleteViewCountMap() {
objectRedisTemplate.delete(VIEW_COUNT_MAP);
}

// String Integer 타입 맵을 Long Integer로 변환
private Map<Long, Integer> convertViewCountMap(Map<String, Integer> rawMap) {
Map<Long, Integer> viewCountMap = new HashMap<>();
for (Map.Entry<String, Integer> stringIntegerEntry : rawMap.entrySet()) {
viewCountMap.put(Long.parseLong(stringIntegerEntry.getKey()), stringIntegerEntry.getValue());
}
return viewCountMap;
}

// 해당 접두어의 모든 항목 삭제
private void deleteAllItemsByPREFIX(String prefix) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

가져온 prefix 기반으로 삭제하는게 아닌 concertlist prefix만 삭제하게 되는 것 같습니다!

String pattern = CONCERT_LIST_PREFIX + "*";
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build();
Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> keySet = new HashSet<>();
try (Cursor<byte[]> cursor = connection.scan(options)) {
while (cursor.hasNext()) {
keySet.add(new String(cursor.next()));
}
}
return keySet;
}
);

if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
log.info("Successfully deleted %s items with prefix: %s".formatted(keys.size() + "", prefix));
} else {
log.info("no items with prefix: %s".formatted(prefix));
}
}


}
Loading