Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public record ApplicantsResponse(
String koreanName,
int studentCapacity,
Integer studentCapacity,
String region,
String country,
List<ApplicantResponse> applicants) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Object cache(ProceedingJoinPoint joinPoint, ThunderingHerdCaching thunder

if (redisUtils.isCacheExpiringSoon(key, ttl, Double.valueOf(REFRESH_LIMIT_PERCENT.getValue()))) {
log.info("Cache hit, but TTL is expiring soon. Key: {}, Thread: {}", key, Thread.currentThread().getName());
return refreshCache(cachedValue, ttl, key);
return refreshCache(joinPoint, cacheManager, cachedValue, ttl, key);
}

log.info("Cache hit. Key: {}, Thread: {}", key, Thread.currentThread().getName());
Expand All @@ -88,14 +88,24 @@ private Object createCache(ProceedingJoinPoint joinPoint, CacheManager cacheMana
);
}

private Object refreshCache(Object cachedValue, Long ttl, String key) {
private Object refreshCache(ProceedingJoinPoint joinPoint, CacheManager cacheManager, Object cachedValue, Long ttl, String key) {
return executeWithLock(
redisUtils.getRefreshLockKey(key),
redisUtils.getCreateLockKey(key),
() -> {
log.info("갱신락 흭득하였습니다. Key: {}, Thread: {}", key, Thread.currentThread().getName());
redisTemplate.opsForValue().getAndExpire(key, Duration.ofSeconds(ttl));
log.info("TTL 갱신을 마쳤습니다. Key: {}, Thread: {}", key, Thread.currentThread().getName());
return cachedValue;
try {
Object result = proceedJoinPoint(joinPoint);
cacheManager.put(key, result, ttl);
redisTemplate.convertAndSend(CREATE_CHANNEL.getValue(), key);
log.info("캐시 갱신을 마쳤습니다. Key: {}, Thread: {}", key, Thread.currentThread().getName());
return result;
} catch (CustomException e) {
throw e;
} catch (RuntimeException e) {
log.warn("캐시 갱신 중 오류가 발생하여 기존 캐시값을 반환합니다. Key: {}, Thread: {}",
key, Thread.currentThread().getName(), e);
return cachedValue;
}
},
() -> {
log.info("갱신락 흭득에 실패하였습니다. 캐시의 값을 바로 반환합니다. Key: {}, Thread: {}", key, Thread.currentThread().getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public enum ErrorCode {
SCHOOL_EMAIL_CONFIRM_CODE_DIFFERENT(HttpStatus.BAD_REQUEST.value(), "인증 코드가 일치하지 않습니다."),
SCHOOL_EMAIL_VERIFICATION_INFO_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR.value(), "학교 이메일 인증 정보 저장에 실패했습니다."),
SCHOOL_EMAIL_VERIFICATION_INFO_CORRUPTED(HttpStatus.INTERNAL_SERVER_ERROR.value(), "학교 이메일 인증 정보가 손상되었습니다. 인증 코드 발송을 다시 요청해주세요."),
SCHOOL_EMAIL_NOT_VERIFIED(HttpStatus.BAD_REQUEST.value(), "학교 이메일 인증이 필요합니다."),

// s3
S3_SERVICE_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "S3 서비스 에러 발생"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import java.util.List;

public record GpaScoreStatusesResponse(
String homeUniversityName,
List<GpaScoreStatusResponse> gpaScoreStatusResponseList
) {

public static GpaScoreStatusesResponse from(List<GpaScoreStatusResponse> gpaScoreStatusResponseList) {
return new GpaScoreStatusesResponse(gpaScoreStatusResponseList);
public static GpaScoreStatusesResponse of(
String homeUniversityName,
List<GpaScoreStatusResponse> gpaScoreStatusResponseList
) {
return new GpaScoreStatusesResponse(homeUniversityName, gpaScoreStatusResponseList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.example.solidconnection.score.repository.LanguageTestScoreRepository;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import com.example.solidconnection.university.service.HomeUniversityQueryService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
Expand All @@ -35,6 +36,7 @@ public class ScoreService {
private final S3Service s3Service;
private final LanguageTestScoreRepository languageTestScoreRepository;
private final SiteUserRepository siteUserRepository;
private final HomeUniversityQueryService homeUniversityQueryService;

@Transactional
public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, MultipartFile file) {
Expand Down Expand Up @@ -63,13 +65,14 @@ public Long submitLanguageTestScore(long siteUserId, LanguageTestScoreRequest la
public GpaScoreStatusesResponse getGpaScoreStatus(long siteUserId) {
SiteUser siteUser = siteUserRepository.findById(siteUserId)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
String homeUniversityName = homeUniversityQueryService.findNameByRequiredId(siteUser.getHomeUniversityId());
List<GpaScoreStatusResponse> gpaScoreStatusResponseList =
gpaScoreRepository.findBySiteUserId(siteUser.getId())
.stream()
.map(GpaScoreStatusResponse::from)
.toList();

return GpaScoreStatusesResponse.from(gpaScoreStatusResponseList);
return GpaScoreStatusesResponse.of(homeUniversityName, gpaScoreStatusResponseList);
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@
import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import com.example.solidconnection.university.domain.HostUniversity;
import com.example.solidconnection.university.domain.HomeUniversity;
import com.example.solidconnection.university.repository.HostUniversityRepository;
import com.example.solidconnection.university.repository.HomeUniversityRepository;
import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository;
import com.example.solidconnection.university.service.HomeUniversityQueryService;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
Expand All @@ -51,7 +50,7 @@ public class MyPageService {
private final CountryRepository countryRepository;
private final MentorRepository mentorRepository;
private final HostUniversityRepository hostUniversityRepository;
private final HomeUniversityRepository homeUniversityRepository;
private final HomeUniversityQueryService homeUniversityQueryService;
private final S3Service s3Service;
private final InterestedCountryService interestedCountryService;
private final InterestedRegionService interestedRegionService;
Expand All @@ -67,7 +66,7 @@ public MyPageResponse getMyPageInfo(long siteUserId) {

List<String> interestedCountries = null;
String universityKoreanName = null;
String homeUniversityName = findHomeUniversityName(siteUser);
String homeUniversityName = homeUniversityQueryService.findNameByNullableId(siteUser.getHomeUniversityId());
if (siteUser.getRole() == Role.MENTEE) {
interestedCountries = countryRepository.findKoreanNamesBySiteUserId(siteUser.getId());
} else if (siteUser.getRole() == Role.MENTOR) {
Expand All @@ -86,17 +85,6 @@ public MyPageResponse getMyPageInfo(long siteUserId) {
);
}

private String findHomeUniversityName(SiteUser siteUser) {
Long homeUniversityId = siteUser.getHomeUniversityId();
if (homeUniversityId == null) {
return null;
}

HomeUniversity homeUniversity = homeUniversityRepository.findById(homeUniversityId)
.orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND));
return homeUniversity.getName();
}

/*
* 마이페이지 정보를 수정한다.
* */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,41 @@

import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM;

import com.example.solidconnection.term.repository.TermRepository;
import com.example.solidconnection.cache.annotation.ThunderingHerdCaching;
import com.example.solidconnection.university.domain.UnivApplyInfo;
import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse;
import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse;
import com.example.solidconnection.university.repository.UnivApplyInfoRepository;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class GeneralUnivApplyInfoRecommendService {

private static final long GENERAL_RECOMMEND_CACHE_TTL_SEC = 3600;

/*
* 해당 시기에 열리는 대학교들 중 랜덤으로 선택해서 목록을 구성한다.
* */
private final UnivApplyInfoRepository univApplyInfoRepository;
private final TermRepository termRepository;

@Getter
private List<UnivApplyInfo> generalRecommends;
@Transactional(readOnly = true)
@ThunderingHerdCaching(
key = "university:recommend:general:{0}:{1}",
cacheManager = "customCacheManager",
ttlSec = GENERAL_RECOMMEND_CACHE_TTL_SEC
)
public UnivApplyInfoRecommendsResponse getGeneralRecommends(long termId, String termName) {
Pageable page = PageRequest.of(0, RECOMMEND_UNIV_APPLY_INFO_NUM);
List<UnivApplyInfo> generalRecommends = univApplyInfoRepository.findRandomByTermId(termId, page);

@EventListener(ApplicationReadyEvent.class)
public void init() {
termRepository.findByIsCurrentTrue().ifPresent(term -> {
Pageable page = PageRequest.of(0, RECOMMEND_UNIV_APPLY_INFO_NUM);
generalRecommends = univApplyInfoRepository.findRandomByTermId(term.getId(), page);
});
return new UnivApplyInfoRecommendsResponse(generalRecommends.stream()
.map(univApplyInfo -> UnivApplyInfoPreviewResponse.of(univApplyInfo, termName))
.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.example.solidconnection.university.service;

import static com.example.solidconnection.common.exception.ErrorCode.SCHOOL_EMAIL_NOT_VERIFIED;
import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND;

import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.university.domain.HomeUniversity;
import com.example.solidconnection.university.repository.HomeUniversityRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class HomeUniversityQueryService {

private final HomeUniversityRepository homeUniversityRepository;

@Transactional(readOnly = true)
public String findNameByNullableId(Long homeUniversityId) {
if (homeUniversityId == null) {
return null;
}

return findNameById(homeUniversityId);
}

@Transactional(readOnly = true)
public String findNameByRequiredId(Long homeUniversityId) {
if (homeUniversityId == null) {
throw new CustomException(SCHOOL_EMAIL_NOT_VERIFIED);
}

return findNameById(homeUniversityId);
}

private String findNameById(Long homeUniversityId) {
HomeUniversity homeUniversity = homeUniversityRepository.findById(homeUniversityId)
.orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND));
return homeUniversity.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static com.example.solidconnection.common.exception.ErrorCode.CURRENT_TERM_NOT_FOUND;

import com.example.solidconnection.cache.annotation.ThunderingHerdCaching;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.term.domain.Term;
import com.example.solidconnection.term.repository.TermRepository;
Expand Down Expand Up @@ -47,17 +46,37 @@ public UnivApplyInfoRecommendsResponse getPersonalRecommends(long siteUserId) {

// 맞춤 추천 대학교의 수가 6개보다 적다면, 일반 추천 대학교를 부족한 수 만큼 불러온다.
if (trimmedRecommends.size() < RECOMMEND_UNIV_APPLY_INFO_NUM) {
trimmedRecommends.addAll(getGeneralRecommendsExcludingSelected(trimmedRecommends));
return new UnivApplyInfoRecommendsResponse(getPersonalRecommendPreviewsWithGeneralFallback(trimmedRecommends, term));
}

return new UnivApplyInfoRecommendsResponse(trimmedRecommends.stream()
.map(univApplyInfo -> UnivApplyInfoPreviewResponse.of(univApplyInfo, term.getName()))
.toList());
}

private List<UnivApplyInfo> getGeneralRecommendsExcludingSelected(List<UnivApplyInfo> alreadyPicked) {
List<UnivApplyInfo> generalRecommend = new ArrayList<>(generalUnivApplyInfoRecommendService.getGeneralRecommends());
generalRecommend.removeAll(alreadyPicked);
private List<UnivApplyInfoPreviewResponse> getPersonalRecommendPreviewsWithGeneralFallback(
List<UnivApplyInfo> personalRecommends,
Term term
) {
List<UnivApplyInfoPreviewResponse> recommendPreviews = new ArrayList<>(personalRecommends.stream()
.map(univApplyInfo -> UnivApplyInfoPreviewResponse.of(univApplyInfo, term.getName()))
.toList());
recommendPreviews.addAll(getGeneralRecommendsExcludingSelected(personalRecommends, term));

return recommendPreviews;
}

private List<UnivApplyInfoPreviewResponse> getGeneralRecommendsExcludingSelected(
List<UnivApplyInfo> alreadyPicked,
Term term
) {
List<Long> alreadyPickedIds = alreadyPicked.stream()
.map(UnivApplyInfo::getId)
.toList();
List<UnivApplyInfoPreviewResponse> generalRecommend = new ArrayList<>(generalUnivApplyInfoRecommendService
.getGeneralRecommends(term.getId(), term.getName())
.recommendedUniversities());
generalRecommend.removeIf(univApplyInfo -> alreadyPickedIds.contains(univApplyInfo.id()));
Collections.shuffle(generalRecommend);
int needed = RECOMMEND_UNIV_APPLY_INFO_NUM - alreadyPicked.size();
return generalRecommend.subList(0, Math.min(needed, generalRecommend.size()));
Expand All @@ -67,15 +86,10 @@ private List<UnivApplyInfo> getGeneralRecommendsExcludingSelected(List<UnivApply
* 공통 추천 대학교를 불러온다.
* */
@Transactional(readOnly = true)
@ThunderingHerdCaching(key = "university:recommend:general", cacheManager = "customCacheManager", ttlSec = 86400)
public UnivApplyInfoRecommendsResponse getGeneralRecommends() {
Term term = termRepository.findByIsCurrentTrue()
.orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND));

List<UnivApplyInfo> generalRecommends = new ArrayList<>(generalUnivApplyInfoRecommendService.getGeneralRecommends());

return new UnivApplyInfoRecommendsResponse(generalRecommends.stream()
.map(univApplyInfo -> UnivApplyInfoPreviewResponse.of(univApplyInfo, term.getName()))
.toList());
return generalUnivApplyInfoRecommendService.getGeneralRecommends(term.getId(), term.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public String getRefreshLockKey(String key) {

public boolean isCacheExpiringSoon(String key, Long defaultTtl, Double percent) {
Long leftTtl = redisTemplate.getExpire(key);
return defaultTtl != null && ((double) leftTtl / defaultTtl) * 100 < percent;
if (defaultTtl == null || defaultTtl <= 0
|| leftTtl == null || leftTtl < 0
|| percent == null || percent <= 0 || percent > 100) {
return false;
}
return ((double) leftTtl / defaultTtl) * 100 < percent;
}
}
Loading
Loading