refactor(cache): 缓存操作接口化,支持 Redis/Caffeine 双实现切换 #56

Merged
zhangxiang merged 1 commits from refactor/cache-api into dev 2026-08-02 18:29:51 +08:00
45 changed files with 798 additions and 476 deletions
Showing only changes of commit 4ba4af2f53 - Show all commits
@@ -3,7 +3,7 @@ package cn.novalon.gym.manage.auth.service.impl;
import cn.novalon.gym.manage.auth.config.SmsProperties;
import cn.novalon.gym.manage.auth.service.SmsService;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
@@ -31,7 +31,7 @@ public class SmsServiceImpl implements SmsService {
private static final long CODE_EXPIRE_SECONDS = 300;
private final SmsProperties smsProperties;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final ObjectMapper objectMapper;
@Override
@@ -40,7 +40,7 @@ public class SmsServiceImpl implements SmsService {
String rateLimitKey = RedisKeyConstants.SMS_CODE + phone + ":rate_limit";
return redisUtil.get(rateLimitKey, Long.class)
return cacheOperations.get(rateLimitKey, Long.class)
.defaultIfEmpty(0L)
.flatMap(lastSendTime -> {
long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
@@ -97,7 +97,7 @@ public class SmsServiceImpl implements SmsService {
if (verifyCodeNode != null) {
String verifyCode = verifyCodeNode.asText();
String smsCodeKey = RedisKeyConstants.SMS_CODE + phone;
redisUtil.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
cacheOperations.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
log.info("验证码已存入Redis, phone: {}, key: {}, expire: {}秒", phone, smsCodeKey, CODE_EXPIRE_SECONDS);
} else {
log.warn("响应中未找到Model.VerifyCode字段, 原始响应: {}", responseData);
@@ -106,7 +106,7 @@ public class SmsServiceImpl implements SmsService {
log.warn("响应中未找到Model字段, 原始响应: {}", responseData);
}
redisUtil.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
cacheOperations.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
return true;
}
@@ -180,7 +180,7 @@ public class SmsServiceImpl implements SmsService {
public Mono<String> getVerificationCode(String phone) {
try {
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
return redisUtil.get(cacheKey, String.class);
return cacheOperations.get(cacheKey, String.class);
} catch (Exception e) {
log.error("获取验证码异常, phone: {}", phone, e);
return Mono.empty();
@@ -14,7 +14,7 @@ import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
@@ -44,7 +44,7 @@ import java.util.Map;
public class CheckServiceImpl implements ICheckInService {
private final QRCodeConfig qrCodeConfig;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final MemberCardRecordRepository memberCardRecordRepository;
private final MemberCardRepository memberCardRepository;
private final SignInRecordRepository signInRecordRepository;
@@ -63,7 +63,7 @@ public class CheckServiceImpl implements ICheckInService {
redisMap.put("isUsed", false);
redisMap.put("memberId", memberId);
return redisUtil.setWithExpire(
return cacheOperations.setWithExpire(
RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(),
redisMap,
getSecondsUntilEndOfDay()
@@ -87,7 +87,7 @@ public class CheckServiceImpl implements ICheckInService {
MyWebSocketHandler.sendFailure(qrContent, "您已经在" + checkInTime + "完成签到,请勿重复签到");
return Mono.error(new RuntimeException("您已经在" + checkInTime + "完成签到,请勿重复签到"));
})
.then(Mono.defer(() -> redisUtil.get(key)))
.then(Mono.defer(() -> cacheOperations.get(key)))
.flatMap(cachedObj -> {
if (cachedObj != null) {
Map<String, Object> map;
@@ -157,11 +157,11 @@ public class CheckServiceImpl implements ICheckInService {
redisMap.put("checkInTime", now.format(DATE_FORMATTER));
return saveSignInRecord(memberId, null, null)
.then(redisUtil.set(RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(), redisMap))
.then(cacheOperations.set(RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(), redisMap))
// 清除统计缓存和课程缓存,确保管理端/教练端立即反映最新数据
.then(Mono.defer(() ->
redisUtil.deleteByPattern("datacount:statistics:*")
.then(Mono.defer(() -> redisUtil.deleteByPattern("group_course:*")))
cacheOperations.deleteByPattern("datacount:statistics:*")
.then(Mono.defer(() -> cacheOperations.deleteByPattern("group_course:*")))
.doOnSuccess(v -> log.info("已清除统计缓存和课程缓存, memberId: {}", memberId))
.then()
))
@@ -10,7 +10,7 @@ import cn.novalon.gym.manage.checkIn.vo.QRCodeVo;
import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
@@ -44,7 +44,7 @@ class CheckInModuleTest {
private QRCodeConfig qrCodeConfig;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private MemberCardRecordRepository memberCardRecordRepository;
@@ -75,7 +75,7 @@ class CheckInModuleTest {
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
checkService = new CheckServiceImpl(qrCodeConfig, redisUtil, memberCardRecordRepository,
checkService = new CheckServiceImpl(qrCodeConfig, cacheOperations, memberCardRecordRepository,
memberCardRepository, signInRecordRepository, groupCourseBookingService,
groupCourseBookingRepository);
@@ -103,7 +103,7 @@ class CheckInModuleTest {
void testGetQRCode() {
when(memberCardRecordRepository.findActiveCardsByMemberId(1L))
.thenReturn(Flux.just(mockMemberCardRecord));
when(redisUtil.setWithExpire(any(String.class), any(Map.class), any(Long.class)))
when(cacheOperations.setWithExpire(any(String.class), any(Map.class), any(Long.class)))
.thenReturn(Mono.just(true));
Mono<QRCodeVo> result = checkService.getQRCode(1L);
@@ -130,12 +130,12 @@ class CheckInModuleTest {
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
when(redisUtil.deleteByPattern(any(String.class))).thenReturn(Mono.empty());
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
when(cacheOperations.deleteByPattern(any(String.class))).thenReturn(Mono.empty());
when(memberCardRecordRepository.findById(1L)).thenReturn(Mono.just(mockMemberCardRecord));
when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(mockMemberCard));
when(signInRecordRepository.save(any(SignInRecord.class))).thenReturn(Mono.just(mockSignInRecord));
when(redisUtil.set(any(String.class), any(Map.class))).thenReturn(Mono.just(true));
when(cacheOperations.set(any(String.class), any(Map.class))).thenReturn(Mono.just(true));
when(groupCourseBookingService.getBookingsByMemberId(memberId)).thenReturn(Flux.empty());
when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
.thenReturn(Mono.empty());
@@ -259,7 +259,7 @@ class CheckInModuleTest {
void testCheckIn_QRCodeInvalid() {
Long memberId = 1L;
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
when(redisUtil.get(eq(key))).thenReturn(Mono.just(new HashMap<>()));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(new HashMap<>()));
when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
.thenReturn(Mono.empty());
@@ -275,7 +275,7 @@ class CheckInModuleTest {
void testCheckIn_QRCodeNotFound() {
Long memberId = 1L;
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
when(redisUtil.get(eq(key))).thenReturn(Mono.empty());
when(cacheOperations.get(eq(key))).thenReturn(Mono.empty());
when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
.thenReturn(Mono.empty());
@@ -8,7 +8,7 @@ import cn.novalon.gym.manage.checkIn.vo.QRCodeVo;
import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
@@ -40,7 +40,7 @@ class CheckServiceImplTest {
private QRCodeConfig qrCodeConfig;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private MemberCardRecordRepository memberCardRecordRepository;
@@ -70,7 +70,7 @@ class CheckServiceImplTest {
void getQRCode_shouldReturnQRCodeVo() {
when(qrCodeConfig.getWidth()).thenReturn(300);
when(qrCodeConfig.getHeight()).thenReturn(300);
when(redisUtil.setWithExpire(anyString(), any(Map.class), anyLong()))
when(cacheOperations.setWithExpire(anyString(), any(Map.class), anyLong()))
.thenReturn(Mono.just(true));
Mono<QRCodeVo> result = checkService.getQRCode(MEMBER_ID);
@@ -100,7 +100,7 @@ class CheckServiceImplTest {
// Redis 中有有效二维码数据
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
Map<String, Object> qrData = buildQrData(false);
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
// 无预约
when(groupCourseBookingService.getBookingsByMemberId(MEMBER_ID)).thenReturn(Flux.empty());
@@ -110,10 +110,10 @@ class CheckServiceImplTest {
.thenReturn(Mono.just(createMockSignInRecord()));
// 更新缓存
when(redisUtil.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
when(cacheOperations.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
// 清除缓存
when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.empty());
when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.empty());
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
@@ -122,8 +122,8 @@ class CheckServiceImplTest {
.verifyComplete();
verify(signInRecordRepository).save(any(SignInRecord.class));
verify(redisUtil).set(anyString(), any(Map.class));
verify(redisUtil, times(2)).deleteByPattern(anyString());
verify(cacheOperations).set(anyString(), any(Map.class));
verify(cacheOperations, times(2)).deleteByPattern(anyString());
}
@Test
@@ -136,7 +136,7 @@ class CheckServiceImplTest {
// Redis 中有有效二维码数据
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
Map<String, Object> qrData = buildQrData(false);
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
// 有有效预约
GroupCourseBooking booking = createValidBooking();
@@ -148,10 +148,10 @@ class CheckServiceImplTest {
.thenReturn(Mono.just(createMockSignInRecord()));
// 更新缓存
when(redisUtil.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
when(cacheOperations.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
// 清除缓存
when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.empty());
when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.empty());
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
@@ -172,7 +172,7 @@ class CheckServiceImplTest {
// Redis 中缓存数据为 JSON 字符串格式
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
String jsonData = "{\"qrContent\":\"" + QR_CONTENT + "\",\"isUsed\":false,\"memberId\":" + MEMBER_ID + "}";
when(redisUtil.get(eq(key))).thenReturn(Mono.just(jsonData));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(jsonData));
// 无预约
when(groupCourseBookingService.getBookingsByMemberId(MEMBER_ID)).thenReturn(Flux.empty());
@@ -182,10 +182,10 @@ class CheckServiceImplTest {
.thenReturn(Mono.just(createMockSignInRecord()));
// 更新缓存
when(redisUtil.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
when(cacheOperations.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
// 清除缓存
when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.empty());
when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.empty());
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
@@ -217,7 +217,7 @@ class CheckServiceImplTest {
&& e.getMessage().contains("请勿重复签到"))
.verify();
verify(redisUtil, never()).get(anyString());
verify(cacheOperations, never()).get(anyString());
}
@Test
@@ -231,7 +231,7 @@ class CheckServiceImplTest {
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
Map<String, Object> qrData = buildQrData(true);
qrData.put("checkInTime", "2026-07-31 10:00:00");
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
@@ -252,7 +252,7 @@ class CheckServiceImplTest {
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
Map<String, Object> qrData = buildQrData(false);
qrData.put("qrContent", "different-qr-content");
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
@@ -271,7 +271,7 @@ class CheckServiceImplTest {
// Redis 中无数据
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
when(redisUtil.get(eq(key))).thenReturn(Mono.empty());
when(cacheOperations.get(eq(key))).thenReturn(Mono.empty());
Mono<String> result = checkService.checkIn(MEMBER_ID, "not-exist");
@@ -289,7 +289,7 @@ class CheckServiceImplTest {
// Redis 返回非 Map 非 String 的数据
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
when(redisUtil.get(eq(key))).thenReturn(Mono.just(12345));
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(12345));
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
@@ -3,7 +3,7 @@ package cn.novalon.gym.manage.coachconfig.service;
import cn.novalon.gym.manage.coachconfig.domain.CoachTimeRule;
import cn.novalon.gym.manage.coachconfig.repository.ICoachTimeRuleRepository;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -43,11 +43,11 @@ public class CoachTimeRuleService {
private static final long CACHE_TTL_SECONDS = 300;
private final ICoachTimeRuleRepository repository;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
public CoachTimeRuleService(ICoachTimeRuleRepository repository, RedisUtil redisUtil) {
public CoachTimeRuleService(ICoachTimeRuleRepository repository, CacheOperations cacheOperations) {
this.repository = repository;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
}
// ==================== 规则匹配 ====================
@@ -118,7 +118,7 @@ public class CoachTimeRuleService {
* 获取所有启用规则(带 Redis 缓存)
*/
private Flux<CoachTimeRule> getActiveRules() {
return redisUtil.get(RedisKeyConstants.COACH_TIME_RULES)
return cacheOperations.get(RedisKeyConstants.COACH_TIME_RULES)
.flatMapMany(cached -> {
@SuppressWarnings("unchecked")
List<CoachTimeRule> list = (List<CoachTimeRule>) cached;
@@ -130,7 +130,7 @@ public class CoachTimeRuleService {
return repository.findByStatusAndDeletedAtIsNull("1")
.collectList()
.flatMapMany(list -> {
redisUtil.setWithExpire(RedisKeyConstants.COACH_TIME_RULES, list, CACHE_TTL_SECONDS)
cacheOperations.setWithExpire(RedisKeyConstants.COACH_TIME_RULES, list, CACHE_TTL_SECONDS)
.subscribe(
ok -> logger.debug("教练时间规则已写入 Redis 缓存,共 {} 条", list.size()),
err -> logger.warn("教练时间规则写入 Redis 缓存失败: {}", err.getMessage())
@@ -144,7 +144,7 @@ public class CoachTimeRuleService {
* 写操作后清除缓存(热更新入口)
*/
private void invalidateCache() {
redisUtil.delete(RedisKeyConstants.COACH_TIME_RULES)
cacheOperations.delete(RedisKeyConstants.COACH_TIME_RULES)
.subscribe(
count -> logger.info("教练时间规则缓存已清除"),
err -> logger.warn("教练时间规则缓存清除失败: {}", err.getMessage())
@@ -2,7 +2,7 @@ package cn.novalon.gym.manage.coach.scheduler;
import cn.novalon.gym.manage.coach.enums.ViolationReason;
import cn.novalon.gym.manage.coachconfig.service.CoachTimeRuleService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
@@ -35,18 +35,18 @@ public class CoachCourseScheduler {
private final GroupCourseDao groupCourseDao;
private final GroupCourseBookingDao groupCourseBookingDao;
private final DatabaseClient databaseClient;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final CoachTimeRuleService timeRuleService;
public CoachCourseScheduler(GroupCourseDao groupCourseDao,
GroupCourseBookingDao groupCourseBookingDao,
DatabaseClient databaseClient,
RedisUtil redisUtil,
CacheOperations cacheOperations,
CoachTimeRuleService timeRuleService) {
this.groupCourseDao = groupCourseDao;
this.groupCourseBookingDao = groupCourseBookingDao;
this.databaseClient = databaseClient;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
this.timeRuleService = timeRuleService;
}
@@ -166,17 +166,17 @@ public class CoachCourseScheduler {
/**
* 清除统计缓存和团课缓存 —— 调度器触发时,如有课程状态变更则必须及时失效。
*
* <p>注意:测试环境中 RedisUtil 可能被 Mock 返回 null,需做 null 安全处理。</p>
* <p>注意:测试环境中 CacheOperations 可能被 Mock 返回 null,需做 null 安全处理。</p>
*/
private void invalidateCache() {
Mono<Long> statsMono = redisUtil.deleteByPattern("datacount:statistics:*");
Mono<Long> statsMono = cacheOperations.deleteByPattern("datacount:statistics:*");
if (statsMono != null) {
statsMono.subscribe(
deleted -> logger.debug("调度器清除统计缓存,已删除 {} 条", deleted),
error -> logger.warn("调度器清除统计缓存失败: {}", error.getMessage())
);
}
Mono<Long> courseMono = redisUtil.deleteByPattern("group_course:*");
Mono<Long> courseMono = cacheOperations.deleteByPattern("group_course:*");
if (courseMono != null) {
courseMono.subscribe(
deleted -> logger.debug("调度器清除团课缓存,已删除 {} 条", deleted),
@@ -5,7 +5,7 @@ import cn.novalon.gym.manage.coach.entity.CoachViolationEntity;
import cn.novalon.gym.manage.coach.enums.ViolationReason;
import cn.novalon.gym.manage.coachconfig.domain.CoachTimeRule;
import cn.novalon.gym.manage.coachconfig.service.CoachTimeRuleService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.common.util.StatusConstants;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
@@ -56,7 +56,7 @@ public class CoachCourseService {
private final CoachViolationDao violationDao;
private final DatabaseClient databaseClient;
private final PasswordEncoder passwordEncoder;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final CoachTimeRuleService timeRuleService;
public CoachCourseService(ISysUserRepository userRepository,
@@ -69,7 +69,7 @@ public class CoachCourseService {
CoachViolationDao violationDao,
DatabaseClient databaseClient,
PasswordEncoder passwordEncoder,
RedisUtil redisUtil,
CacheOperations cacheOperations,
CoachTimeRuleService timeRuleService) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
@@ -81,7 +81,7 @@ public class CoachCourseService {
this.violationDao = violationDao;
this.databaseClient = databaseClient;
this.passwordEncoder = passwordEncoder;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
this.timeRuleService = timeRuleService;
}
@@ -334,8 +334,8 @@ public class CoachCourseService {
* 清除统计缓存和团课缓存 —— 课程状态变更后调用,返回 Mono 确保链式执行
*/
private Mono<Void> invalidateStatisticsCache() {
return redisUtil.deleteByPattern("datacount:statistics:*")
.then(redisUtil.deleteByPattern("group_course:*"))
return cacheOperations.deleteByPattern("datacount:statistics:*")
.then(cacheOperations.deleteByPattern("group_course:*"))
.doOnNext(count -> {})
.then();
}
@@ -3,7 +3,7 @@ package cn.novalon.gym.manage.coach.scheduler;
import cn.novalon.gym.manage.coach.enums.ViolationReason;
import cn.novalon.gym.manage.coachconfig.domain.CoachTimeRule;
import cn.novalon.gym.manage.coachconfig.service.CoachTimeRuleService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
@@ -38,7 +38,7 @@ class CoachCourseSchedulerTest {
private DatabaseClient databaseClient;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private CoachTimeRuleService timeRuleService;
@@ -54,7 +54,7 @@ class CoachCourseSchedulerTest {
void setUp() {
scheduler = new CoachCourseScheduler(
groupCourseDao, groupCourseBookingDao,
databaseClient, redisUtil, timeRuleService
databaseClient, cacheOperations, timeRuleService
);
}
@@ -372,7 +372,7 @@ class CoachCourseSchedulerTest {
verify(groupCourseDao).findByStatusInAndEndTimeBefore(eq(databaseClient), any(String[].class), any(LocalDateTime.class));
// 验证未触发缓存清除(count=0,不进入 if 分支)
verifyNoInteractions(redisUtil);
verifyNoInteractions(cacheOperations);
}
@Test
@@ -403,20 +403,20 @@ class CoachCourseSchedulerTest {
.thenReturn(Flux.empty());
// mock RedisUtil 返回非 null 的 Mono
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
// 执行
scheduler.checkAndProcessCourses();
// 验证缓存清除被调用
verify(redisUtil).deleteByPattern("datacount:statistics:*");
verify(redisUtil).deleteByPattern("group_course:*");
verify(cacheOperations).deleteByPattern("datacount:statistics:*");
verify(cacheOperations).deleteByPattern("group_course:*");
}
@Test
void checkAndProcessCourses_shouldHandleRedisUtilReturningNull() {
// 准备:测试 RedisUtil 返回 null 的边界情况
void checkAndProcessCourses_shouldHandleCacheOperationsReturningNull() {
// 准备:测试 CacheOperations 返回 null 的边界情况
LocalDateTime startTime = LocalDateTime.of(2026, 7, 31, 10, 0);
LocalDateTime endTime = LocalDateTime.of(2026, 7, 31, 11, 0);
@@ -440,15 +440,15 @@ class CoachCourseSchedulerTest {
.thenReturn(Flux.empty());
// RedisUtil.deleteByPattern 返回 null(模拟 null 安全检查)
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(null);
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(null);
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(null);
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(null);
// 执行:不应抛出 NPE
scheduler.checkAndProcessCourses();
// 验证:RedisUtil 被调用(即使返回 null
verify(redisUtil).deleteByPattern("datacount:statistics:*");
verify(redisUtil).deleteByPattern("group_course:*");
verify(cacheOperations).deleteByPattern("datacount:statistics:*");
verify(cacheOperations).deleteByPattern("group_course:*");
}
@Test
@@ -465,6 +465,6 @@ class CoachCourseSchedulerTest {
// 验证
verify(groupCourseDao).findByStatusAndStartTimeBefore(eq(databaseClient), eq("0"), any(LocalDateTime.class));
verify(groupCourseDao).findByStatusInAndEndTimeBefore(eq(databaseClient), any(String[].class), any(LocalDateTime.class));
verifyNoInteractions(redisUtil);
verifyNoInteractions(cacheOperations);
}
}
@@ -4,7 +4,7 @@ import cn.novalon.gym.manage.coach.dao.CoachViolationDao;
import cn.novalon.gym.manage.coach.enums.ViolationReason;
import cn.novalon.gym.manage.coachconfig.domain.CoachTimeRule;
import cn.novalon.gym.manage.coachconfig.service.CoachTimeRuleService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.common.util.StatusConstants;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
@@ -76,7 +76,7 @@ class CoachCourseServiceTest {
private PasswordEncoder passwordEncoder;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private CoachTimeRuleService timeRuleService;
@@ -95,7 +95,7 @@ class CoachCourseServiceTest {
groupCourseRepository, bookingRepository,
groupCourseDao, groupCourseBookingDao,
violationDao, databaseClient,
passwordEncoder, redisUtil, timeRuleService
passwordEncoder, cacheOperations, timeRuleService
);
}
@@ -328,8 +328,8 @@ class CoachCourseServiceTest {
updatedUser.setStatus(StatusConstants.DISABLED);
when(userRepository.update(any(SysUser.class))).thenReturn(Mono.just(updatedUser));
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.disableCoach(COACH_USER_ID))
.verifyComplete();
@@ -338,15 +338,15 @@ class CoachCourseServiceTest {
verify(groupCourseRepository).countByCoachIdAndStatus(COACH_USER_ID, 3L);
verify(groupCourseRepository).cancelCoursesByCoachIdExceptStatus(COACH_USER_ID, 3L);
verify(userRepository).update(any(SysUser.class));
verify(redisUtil).deleteByPattern("datacount:statistics:*");
verify(redisUtil).deleteByPattern("group_course:*");
verify(cacheOperations).deleteByPattern("datacount:statistics:*");
verify(cacheOperations).deleteByPattern("group_course:*");
}
@Test
void disableCoach_shouldThrowWhenCoachNotFound() {
when(userRepository.findById(COACH_USER_ID)).thenReturn(Mono.empty());
// invalidateStatisticsCache() 会在 .then() 参数求值时被调用,需要 stub
lenient().when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.just(1L));
lenient().when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.disableCoach(COACH_USER_ID))
.expectErrorMatches(e -> e instanceof RuntimeException
@@ -363,7 +363,7 @@ class CoachCourseServiceTest {
when(groupCourseRepository.countByCoachIdAndStatus(COACH_USER_ID, 3L)).thenReturn(Mono.just(2L));
// invalidateStatisticsCache() 会在 .then() 参数求值时被调用,需要 stub
lenient().when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.just(1L));
lenient().when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.disableCoach(COACH_USER_ID))
.expectErrorMatches(e -> e instanceof RuntimeException
@@ -461,8 +461,8 @@ class CoachCourseServiceTest {
when(groupCourseDao.updateStartInfo(eq(COURSE_ID), eq("3"), any(LocalDateTime.class), any(LocalDateTime.class))).thenReturn(Mono.just(1));
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.startCourse(COURSE_ID, COACH_USER_ID))
.assertNext(entity -> {
@@ -500,8 +500,8 @@ class CoachCourseServiceTest {
when(groupCourseDao.updateStartInfo(eq(COURSE_ID), eq("7"), any(LocalDateTime.class), any(LocalDateTime.class))).thenReturn(Mono.just(1));
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.startCourse(COURSE_ID, COACH_USER_ID))
.assertNext(entity -> {
@@ -608,8 +608,8 @@ class CoachCourseServiceTest {
when(groupCourseDao.updateEndInfo(eq(COURSE_ID), eq("2"), any(LocalDateTime.class), any(LocalDateTime.class))).thenReturn(Mono.just(1));
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.endCourse(COURSE_ID, COACH_USER_ID))
.assertNext(entity -> {
@@ -697,8 +697,8 @@ class CoachCourseServiceTest {
when(groupCourseDao.updateEndInfo(eq(COURSE_ID), eq("2"), any(LocalDateTime.class), any(LocalDateTime.class))).thenReturn(Mono.just(1));
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(redisUtil.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(1L));
when(cacheOperations.deleteByPattern("group_course:*")).thenReturn(Mono.just(1L));
StepVerifier.create(coachCourseService.endCourse(COURSE_ID, COACH_USER_ID))
.assertNext(entity -> {
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.datacount.scheduler;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.datacount.domain.DataStatistics;
import cn.novalon.gym.manage.datacount.service.IDataStatisticsService;
import org.slf4j.Logger;
@@ -98,7 +99,7 @@ public class DataStatisticsScheduler {
// 清理Redis中的旧统计数据
String pattern = "datacount:statistics:*:" + cutoffDate.toString();
cn.novalon.gym.manage.common.util.RedisUtil redisUtil = null;
CacheOperations cacheOperations = null;
try {
// 这里可以通过注入的service来清理,但当前实现使用Redis缓存30天自动过期
log.info("Old statistics cleanup completed, cutoff date: {}", cutoffDate);
@@ -1,7 +1,7 @@
package cn.novalon.gym.manage.datacount.service.impl;
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.datacount.dao.DataStatisticsDao;
import cn.novalon.gym.manage.datacount.domain.*;
import cn.novalon.gym.manage.datacount.service.IDataStatisticsService;
@@ -46,7 +46,7 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
private DataStatisticsDao dataStatisticsDao;
@Autowired
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Autowired
private ObjectMapper objectMapper;
@@ -223,7 +223,7 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
public reactor.core.publisher.Flux<DataStatistics> queryHistoricalStatistics(StatisticsQuery query) {
// 历史统计数据查询(从Redis缓存中获取)
String cacheKey = buildCacheKey(query);
return redisUtil.get(cacheKey, String.class)
return cacheOperations.get(cacheKey, String.class)
.flatMapMany(json -> {
try {
java.util.List<DataStatistics> stats = objectMapper.readValue(json,
@@ -270,9 +270,9 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
String signInJson = objectMapper.writeValueAsString(signInStats);
return reactor.core.publisher.Flux.merge(
redisUtil.setWithExpire(memberKey, memberJson, Duration.ofDays(30).getSeconds()),
redisUtil.setWithExpire(bookingKey, bookingJson, Duration.ofDays(30).getSeconds()),
redisUtil.setWithExpire(signInKey, signInJson, Duration.ofDays(30).getSeconds())
cacheOperations.setWithExpire(memberKey, memberJson, Duration.ofDays(30).getSeconds()),
cacheOperations.setWithExpire(bookingKey, bookingJson, Duration.ofDays(30).getSeconds()),
cacheOperations.setWithExpire(signInKey, signInJson, Duration.ofDays(30).getSeconds())
).then();
} catch (Exception e) {
log.error("Failed to serialize statistics data", e);
@@ -445,13 +445,13 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
public Mono<StatisticsSummary> getStatisticsSummaryWithCache(StatisticsQuery query) {
String cacheKey = buildCacheKey(query);
return redisUtil.get(cacheKey, StatisticsSummary.class)
return cacheOperations.get(cacheKey, StatisticsSummary.class)
.switchIfEmpty(
getStatisticsSummary(query)
.flatMap(summary -> {
try {
String json = objectMapper.writeValueAsString(summary);
return redisUtil.setWithExpire(cacheKey, json, cacheExpireSeconds)
return cacheOperations.setWithExpire(cacheKey, json, cacheExpireSeconds)
.thenReturn(summary);
} catch (Exception e) {
log.error("Failed to serialize statistics summary", e);
@@ -2,7 +2,7 @@
package cn.novalon.gym.manage.groupcourse.handler;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail;
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
@@ -30,16 +30,16 @@ public class GroupCourseHandler {
private static final Logger logger = LoggerFactory.getLogger(GroupCourseHandler.class);
private final IGroupCourseService groupCourseService;
private final Validator validator;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final ObjectMapper objectMapper;
public GroupCourseHandler(IGroupCourseService groupCourseService,
Validator validator,
RedisUtil redisUtil,
CacheOperations cacheOperations,
ObjectMapper objectMapper){
this.groupCourseService = groupCourseService;
this.validator = validator;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
this.objectMapper = objectMapper;
}
@@ -258,7 +258,7 @@ public class GroupCourseHandler {
return ServerResponse.badRequest().bodyValue(error);
}
return redisUtil.get(key)
return cacheOperations.get(key)
.map(cachedValue -> {
Map<String, Object> result = new HashMap<>();
if (cachedValue != null) {
@@ -2,7 +2,7 @@ package cn.novalon.gym.manage.groupcourse.service.impl;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository;
@@ -24,21 +24,21 @@ public class CourseLabelService implements ICourseLabelService {
private final ICourseLabelRepository courseLabelRepository;
private final IGroupCourseRepository groupCourseRepository;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
public CourseLabelService(ICourseLabelRepository courseLabelRepository,
IGroupCourseRepository groupCourseRepository,
RedisUtil redisUtil) {
CacheOperations cacheOperations) {
this.courseLabelRepository = courseLabelRepository;
this.groupCourseRepository = groupCourseRepository;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
}
private Mono<Void> invalidateGroupCourseDetailCache(Long typeId) {
return groupCourseRepository.findByCourseType(typeId)
.flatMap(course -> {
String cacheKey = CACHE_KEY_DETAIL_PREFIX + course.getId();
return redisUtil.delete(cacheKey)
return cacheOperations.delete(cacheKey)
.doOnSuccess(deleted -> logger.debug("清除团课详情缓存 - courseId={}", course.getId()));
})
.then();
@@ -1,8 +1,7 @@
package cn.novalon.gym.manage.groupcourse.service.impl;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
@@ -13,9 +12,11 @@ import java.time.Duration;
/**
* 团课Redis缓存服务
*
* 负责团课信息的缓存管理和分布式锁实现
*
* <p>
* 缓存操作委托给 {@link CacheOperations} 接口,支持通过配置切换 Redis / Caffeine 实现
* 分布式锁操作仍直接使用 {@link ReactiveRedisTemplate}(锁为 Redis 独有特性)。
* </p>
*
* @author 张翔
* @date 2026-06-01
*/
@@ -33,13 +34,13 @@ public class GroupCourseRedisService {
// 锁过期时间(30秒)
private static final Duration LOCK_EXPIRE_TIME = Duration.ofSeconds(30);
private final CacheOperations cacheOperations;
private final ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
private final ObjectMapper objectMapper;
public GroupCourseRedisService(ReactiveRedisTemplate<String, Object> reactiveRedisTemplate,
ObjectMapper objectMapper) {
public GroupCourseRedisService(CacheOperations cacheOperations,
ReactiveRedisTemplate<String, Object> reactiveRedisTemplate) {
this.cacheOperations = cacheOperations;
this.reactiveRedisTemplate = reactiveRedisTemplate;
this.objectMapper = objectMapper;
}
/**
@@ -61,16 +62,9 @@ public class GroupCourseRedisService {
*/
public Mono<Void> cacheCourse(GroupCourse course) {
String key = getCourseCacheKey(course.getId());
try {
String value = objectMapper.writeValueAsString(course);
return reactiveRedisTemplate.opsForValue()
.set(key, value, CACHE_EXPIRE_TIME)
.doOnSuccess(result -> logger.debug("团课信息已缓存:courseId={}", course.getId()))
.then();
} catch (JsonProcessingException e) {
logger.error("序列化团课信息失败:courseId={}", course.getId(), e);
return Mono.error(e);
}
return cacheOperations.setWithExpire(key, course, CACHE_EXPIRE_TIME.toSeconds())
.doOnSuccess(result -> logger.debug("团课信息已缓存:courseId={}", course.getId()))
.then();
}
/**
@@ -78,19 +72,8 @@ public class GroupCourseRedisService {
*/
public Mono<GroupCourse> getCachedCourse(Long courseId) {
String key = getCourseCacheKey(courseId);
return reactiveRedisTemplate.opsForValue()
.get(key)
.cast(String.class)
.flatMap(value -> {
try {
GroupCourse course = objectMapper.readValue(value, GroupCourse.class);
logger.debug("从缓存获取团课信息:courseId={}", courseId);
return Mono.just(course);
} catch (JsonProcessingException e) {
logger.error("反序列化团课信息失败:courseId={}", courseId, e);
return Mono.empty();
}
})
return cacheOperations.get(key, GroupCourse.class)
.doOnNext(course -> logger.debug("从缓存获取团课信息:courseId={}", courseId))
.switchIfEmpty(Mono.fromRunnable(() -> logger.debug("缓存中未找到团课信息:courseId={}", courseId)));
}
@@ -99,15 +82,16 @@ public class GroupCourseRedisService {
*/
public Mono<Void> invalidateCourseCache(Long courseId) {
String key = getCourseCacheKey(courseId);
return reactiveRedisTemplate.delete(key)
return cacheOperations.delete(key)
.doOnSuccess(result -> logger.debug("团课缓存已删除:courseId={}", courseId))
.then();
}
/**
* 获取分布式锁
*
* @param courseId 课程ID
* <p>注意:分布式锁为 Redis 独有特性,始终保持直接使用 RedisTemplate。</p>
*
* @param courseId 课程ID
* @param requestId 请求ID(用于锁的唯一性校验)
* @return 是否获取成功
*/
@@ -126,8 +110,9 @@ public class GroupCourseRedisService {
/**
* 释放分布式锁
*
* @param courseId 课程ID
* <p>注意:分布式锁为 Redis 独有特性,始终保持直接使用 RedisTemplate。</p>
*
* @param courseId 课程ID
* @param requestId 请求ID(用于锁的唯一性校验)
* @return 是否释放成功
*/
@@ -142,11 +127,11 @@ public class GroupCourseRedisService {
.map(deleted -> deleted > 0)
.doOnSuccess(result -> logger.debug("释放预约锁成功:courseId={}, requestId={}", courseId, requestId));
} else {
logger.warn("锁归属校验失败:courseId={}, expectedRequestId={}, actualRequestId={}",
logger.warn("锁归属校验失败:courseId={}, expectedRequestId={}, actualRequestId={}",
courseId, requestId, storedRequestId);
return Mono.just(false);
}
})
.defaultIfEmpty(false);
}
}
}
@@ -1,9 +1,9 @@
package cn.novalon.gym.manage.groupcourse.service.impl;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
@@ -53,7 +53,7 @@ public class GroupCourseService implements IGroupCourseService {
private final ICourseLabelRepository courseLabelRepository;
private final IMemberCardRecordService memberCardRecordService;
private final MemberCardRepository memberCardRepository;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final ObjectMapper objectMapper;
private final GroupCourseStateMachine stateMachine;
private final DatabaseClient databaseClient;
@@ -73,7 +73,7 @@ public class GroupCourseService implements IGroupCourseService {
ICourseLabelRepository courseLabelRepository,
IMemberCardRecordService memberCardRecordService,
MemberCardRepository memberCardRepository,
RedisUtil redisUtil,
CacheOperations cacheOperations,
ObjectMapper objectMapper,
GroupCourseStateMachine stateMachine,
DatabaseClient databaseClient,
@@ -85,7 +85,7 @@ public class GroupCourseService implements IGroupCourseService {
this.courseLabelRepository = courseLabelRepository;
this.memberCardRecordService = memberCardRecordService;
this.memberCardRepository = memberCardRepository;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
this.objectMapper = objectMapper;
this.stateMachine = stateMachine;
this.databaseClient = databaseClient;
@@ -97,7 +97,7 @@ public class GroupCourseService implements IGroupCourseService {
public Mono<GroupCourseDetail> findDetailById(Long id) {
String cacheKey = CACHE_KEY_DETAIL_PREFIX + id;
Mono<String> cachedMono = redisUtil.get(cacheKey, String.class);
Mono<String> cachedMono = cacheOperations.get(cacheKey, String.class);
return cachedMono
.flatMap(cachedJson -> {
if (cachedJson != null && !cachedJson.isEmpty()) {
@@ -107,7 +107,7 @@ public class GroupCourseService implements IGroupCourseService {
return Mono.<GroupCourseDetail>just(detail);
} catch (JsonProcessingException e) {
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
return redisUtil.delete(cacheKey).then(Mono.<GroupCourseDetail>empty());
return cacheOperations.delete(cacheKey).then(Mono.<GroupCourseDetail>empty());
}
}
return Mono.<GroupCourseDetail>empty();
@@ -141,7 +141,7 @@ public class GroupCourseService implements IGroupCourseService {
.flatMap(detail -> {
try {
String jsonData = objectMapper.writeValueAsString(detail);
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
.thenReturn(detail)
.doOnSuccess(d -> logger.debug("缓存已设置 - findDetailById: id={}", id));
} catch (JsonProcessingException e) {
@@ -233,7 +233,7 @@ public class GroupCourseService implements IGroupCourseService {
public Mono<GroupCourse> findById(Long id) {
String cacheKey = CACHE_KEY_ID_PREFIX + id;
Mono<String> cachedMono = redisUtil.get(cacheKey, String.class);
Mono<String> cachedMono = cacheOperations.get(cacheKey, String.class);
return cachedMono
.flatMap(cachedJson -> {
if (cachedJson != null && !cachedJson.isEmpty()) {
@@ -243,7 +243,7 @@ public class GroupCourseService implements IGroupCourseService {
return Mono.<GroupCourse>just(groupCourse);
} catch (JsonProcessingException e) {
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
return redisUtil.delete(cacheKey).then(Mono.<GroupCourse>empty());
return cacheOperations.delete(cacheKey).then(Mono.<GroupCourse>empty());
}
}
return Mono.<GroupCourse>empty();
@@ -253,7 +253,7 @@ public class GroupCourseService implements IGroupCourseService {
.flatMap(groupCourse -> {
try {
String jsonData = objectMapper.writeValueAsString(groupCourse);
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
.thenReturn(groupCourse)
.doOnSuccess(gc -> logger.debug("缓存已设置 - findById: id={}", id));
} catch (JsonProcessingException e) {
@@ -332,7 +332,7 @@ public class GroupCourseService implements IGroupCourseService {
String cacheKey = CACHE_KEY_PREFIX + page + ":" + size + ":" + includeDeleted + ":" + sort + ":" + order + ":" + keyword + ":" + status;
Mono<String> cachedMono = redisUtil.get(cacheKey, String.class);
Mono<String> cachedMono = cacheOperations.get(cacheKey, String.class);
return cachedMono
.flatMap(cachedJson -> {
if (cachedJson != null && !cachedJson.isEmpty()) {
@@ -343,7 +343,7 @@ public class GroupCourseService implements IGroupCourseService {
return Mono.<PageResponse<GroupCourse>>just(pageResponse);
} catch (JsonProcessingException e) {
logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage());
return redisUtil.delete(cacheKey).then(Mono.<PageResponse<GroupCourse>>empty());
return cacheOperations.delete(cacheKey).then(Mono.<PageResponse<GroupCourse>>empty());
}
}
return Mono.<PageResponse<GroupCourse>>empty();
@@ -361,7 +361,7 @@ public class GroupCourseService implements IGroupCourseService {
return resultMono.flatMap(pageResponse -> {
try {
String jsonData = objectMapper.writeValueAsString(pageResponse);
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
.thenReturn(pageResponse)
.doOnSuccess(pr -> logger.debug("缓存已设置 - findByPage: key={}", cacheKey));
} catch (JsonProcessingException e) {
@@ -739,10 +739,10 @@ public class GroupCourseService implements IGroupCourseService {
}
private Mono<Void> clearCache() {
return redisUtil.deleteByPattern(CACHE_KEY_PREFIX + "*")
.then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
.then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*"))
.then(redisUtil.deleteByPattern("datacount:statistics:*"))
return cacheOperations.deleteByPattern(CACHE_KEY_PREFIX + "*")
.then(cacheOperations.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
.then(cacheOperations.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*"))
.then(cacheOperations.deleteByPattern("datacount:statistics:*"))
.then();
}
@@ -1,6 +1,6 @@
package cn.novalon.gym.manage.groupcourse.handler;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail;
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService;
@@ -35,7 +35,7 @@ class GroupCourseHandlerTest {
private Validator validator;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private ObjectMapper objectMapper;
@@ -44,7 +44,7 @@ class GroupCourseHandlerTest {
@BeforeEach
void setUp() {
handler = new GroupCourseHandler(groupCourseService, validator, redisUtil, objectMapper);
handler = new GroupCourseHandler(groupCourseService, validator, cacheOperations, objectMapper);
}
// ==================== getAllGroupCourse ====================
@@ -2,7 +2,7 @@ package cn.novalon.gym.manage.groupcourse.service.impl;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository;
@@ -38,13 +38,13 @@ class CourseLabelServiceTest {
private IGroupCourseRepository groupCourseRepository;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
private CourseLabelService courseLabelService;
@BeforeEach
void setUp() {
courseLabelService = new CourseLabelService(courseLabelRepository, groupCourseRepository, redisUtil);
courseLabelService = new CourseLabelService(courseLabelRepository, groupCourseRepository, cacheOperations);
}
// ==================== findById ====================
@@ -287,16 +287,16 @@ class CourseLabelServiceTest {
GroupCourse course2 = new GroupCourse();
course2.setId(102L);
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(course1, course2));
when(redisUtil.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
when(redisUtil.delete("group_course:detail:102")).thenReturn(Mono.just(1L));
when(cacheOperations.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
when(cacheOperations.delete("group_course:detail:102")).thenReturn(Mono.just(1L));
StepVerifier.create(courseLabelService.addLabelsToType(1L, List.of(1L, 2L)))
.verifyComplete();
verify(courseLabelRepository).addLabelsToType(1L, List.of(1L, 2L));
verify(groupCourseRepository).findByCourseType(1L);
verify(redisUtil).delete("group_course:detail:101");
verify(redisUtil).delete("group_course:detail:102");
verify(cacheOperations).delete("group_course:detail:101");
verify(cacheOperations).delete("group_course:detail:102");
}
@Test
@@ -309,7 +309,7 @@ class CourseLabelServiceTest {
verify(courseLabelRepository).addLabelsToType(1L, List.of(1L));
verify(groupCourseRepository).findByCourseType(1L);
verify(redisUtil, never()).delete(anyString());
verify(cacheOperations, never()).delete(anyString());
}
// ==================== removeLabelFromType ====================
@@ -321,14 +321,14 @@ class CourseLabelServiceTest {
GroupCourse course = new GroupCourse();
course.setId(101L);
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(course));
when(redisUtil.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
when(cacheOperations.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
StepVerifier.create(courseLabelService.removeLabelFromType(1L, 1L))
.verifyComplete();
verify(courseLabelRepository).removeLabelFromType(1L, 1L);
verify(groupCourseRepository).findByCourseType(1L);
verify(redisUtil).delete("group_course:detail:101");
verify(cacheOperations).delete("group_course:detail:101");
}
@Test
@@ -340,7 +340,7 @@ class CourseLabelServiceTest {
.verifyComplete();
verify(courseLabelRepository).removeLabelFromType(1L, 1L);
verify(redisUtil, never()).delete(anyString());
verify(cacheOperations, never()).delete(anyString());
}
// ==================== clearLabelsFromType ====================
@@ -354,16 +354,16 @@ class CourseLabelServiceTest {
GroupCourse course2 = new GroupCourse();
course2.setId(102L);
when(groupCourseRepository.findByCourseType(1L)).thenReturn(Flux.just(course1, course2));
when(redisUtil.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
when(redisUtil.delete("group_course:detail:102")).thenReturn(Mono.just(1L));
when(cacheOperations.delete("group_course:detail:101")).thenReturn(Mono.just(1L));
when(cacheOperations.delete("group_course:detail:102")).thenReturn(Mono.just(1L));
StepVerifier.create(courseLabelService.clearLabelsFromType(1L))
.verifyComplete();
verify(courseLabelRepository).clearLabelsFromType(1L);
verify(groupCourseRepository).findByCourseType(1L);
verify(redisUtil).delete("group_course:detail:101");
verify(redisUtil).delete("group_course:detail:102");
verify(cacheOperations).delete("group_course:detail:101");
verify(cacheOperations).delete("group_course:detail:102");
}
@Test
@@ -375,7 +375,7 @@ class CourseLabelServiceTest {
.verifyComplete();
verify(courseLabelRepository).clearLabelsFromType(1L);
verify(redisUtil, never()).delete(anyString());
verify(cacheOperations, never()).delete(anyString());
}
// ==================== findByPage ====================
@@ -1,7 +1,7 @@
package cn.novalon.gym.manage.groupcourse.service.impl;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -21,14 +21,15 @@ import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class GroupCourseRedisServiceTest {
@Mock
private CacheOperations cacheOperations;
@Mock
private ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
@Mock
private ReactiveValueOperations<String, Object> reactiveValueOps;
private ObjectMapper objectMapper = new ObjectMapper();
private GroupCourseRedisService service;
private GroupCourse testCourse;
@@ -36,7 +37,7 @@ class GroupCourseRedisServiceTest {
@BeforeEach
void setUp() {
lenient().when(reactiveRedisTemplate.opsForValue()).thenReturn(reactiveValueOps);
service = new GroupCourseRedisService(reactiveRedisTemplate, objectMapper);
service = new GroupCourseRedisService(cacheOperations, reactiveRedisTemplate);
testCourse = new GroupCourse();
testCourse.setId(1L);
@@ -47,20 +48,20 @@ class GroupCourseRedisServiceTest {
// ==================== cacheCourse ====================
@Test
void cacheCourse_shouldSerializeAndSetInRedis() {
when(reactiveValueOps.set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5))))
void cacheCourse_shouldSetInCache() {
when(cacheOperations.setWithExpire(eq("group_course:1"), eq(testCourse), eq(300L)))
.thenReturn(Mono.just(true));
StepVerifier.create(service.cacheCourse(testCourse))
.verifyComplete();
verify(reactiveValueOps).set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5)));
verify(cacheOperations).setWithExpire(eq("group_course:1"), eq(testCourse), eq(300L));
}
@Test
void cacheCourse_shouldErrorWhenRedisFails() {
when(reactiveValueOps.set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5))))
.thenReturn(Mono.error(new RuntimeException("Redis error")));
void cacheCourse_shouldErrorWhenCacheFails() {
when(cacheOperations.setWithExpire(eq("group_course:1"), eq(testCourse), eq(300L)))
.thenReturn(Mono.error(new RuntimeException("Cache error")));
StepVerifier.create(service.cacheCourse(testCourse))
.expectError(RuntimeException.class)
@@ -70,9 +71,9 @@ class GroupCourseRedisServiceTest {
// ==================== getCachedCourse ====================
@Test
void getCachedCourse_shouldReturnCachedCourseWhenFound() throws Exception {
String json = objectMapper.writeValueAsString(testCourse);
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.just(json));
void getCachedCourse_shouldReturnCachedCourseWhenFound() {
when(cacheOperations.get(eq("group_course:1"), eq(GroupCourse.class)))
.thenReturn(Mono.just(testCourse));
StepVerifier.create(service.getCachedCourse(1L))
.assertNext(course -> {
@@ -84,16 +85,8 @@ class GroupCourseRedisServiceTest {
@Test
void getCachedCourse_shouldReturnEmptyWhenCacheMiss() {
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.empty());
StepVerifier.create(service.getCachedCourse(1L))
.verifyComplete();
}
@Test
void getCachedCourse_shouldReturnEmptyWhenDeserializationFails() {
String invalidJson = "invalid json";
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.just(invalidJson));
when(cacheOperations.get(eq("group_course:1"), eq(GroupCourse.class)))
.thenReturn(Mono.empty());
StepVerifier.create(service.getCachedCourse(1L))
.verifyComplete();
@@ -102,13 +95,13 @@ class GroupCourseRedisServiceTest {
// ==================== invalidateCourseCache ====================
@Test
void invalidateCourseCache_shouldDeleteFromRedis() {
when(reactiveRedisTemplate.delete("group_course:1")).thenReturn(Mono.just(1L));
void invalidateCourseCache_shouldDeleteFromCache() {
when(cacheOperations.delete("group_course:1")).thenReturn(Mono.just(1L));
StepVerifier.create(service.invalidateCourseCache(1L))
.verifyComplete();
verify(reactiveRedisTemplate).delete("group_course:1");
verify(cacheOperations).delete("group_course:1");
}
// ==================== acquireLock ====================
@@ -2,7 +2,7 @@ package cn.novalon.gym.manage.groupcourse.service.impl;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.file.core.domain.SysFile;
import cn.novalon.gym.manage.file.core.service.ISysFileService;
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
@@ -63,7 +63,7 @@ class GroupCourseServiceTest {
@Mock
private MemberCardRepository memberCardRepository;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private GroupCourseStateMachine stateMachine;
@Mock
@@ -90,7 +90,7 @@ class GroupCourseServiceTest {
groupCourseService = new GroupCourseService(
groupCourseRepository, bookingRepository, groupCourseTypeRepository,
courseLabelRepository, memberCardRecordService, memberCardRepository,
redisUtil, objectMapper, stateMachine, databaseClient, fileService, sysUserRepository
cacheOperations, objectMapper, stateMachine, databaseClient, fileService, sysUserRepository
);
testCourse = new GroupCourse();
@@ -136,10 +136,10 @@ class GroupCourseServiceTest {
}
private void mockClearCache() {
when(redisUtil.deleteByPattern("group_course:page:*")).thenReturn(Mono.just(0L));
when(redisUtil.deleteByPattern("group_course:id:*")).thenReturn(Mono.just(0L));
when(redisUtil.deleteByPattern("group_course:detail:*")).thenReturn(Mono.just(0L));
when(redisUtil.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(0L));
when(cacheOperations.deleteByPattern("group_course:page:*")).thenReturn(Mono.just(0L));
when(cacheOperations.deleteByPattern("group_course:id:*")).thenReturn(Mono.just(0L));
when(cacheOperations.deleteByPattern("group_course:detail:*")).thenReturn(Mono.just(0L));
when(cacheOperations.deleteByPattern("datacount:statistics:*")).thenReturn(Mono.just(0L));
}
private void mockEnrichCurrentMembers() {
@@ -157,7 +157,7 @@ class GroupCourseServiceTest {
detail.setCoachName("张教练");
String json = objectMapper.writeValueAsString(detail);
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just(json));
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.just(json));
// switchIfEmpty 的 Mono 参数被急切求值,需要 mock 以避免 NPE
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
@@ -169,7 +169,7 @@ class GroupCourseServiceTest {
})
.verifyComplete();
verify(redisUtil).get(cacheKey, String.class);
verify(cacheOperations).get(cacheKey, String.class);
// switchIfEmpty 的 Mono 参数被急切求值,findByIdAndDeletedAtIsNull 会被调用但不会订阅
}
@@ -177,13 +177,13 @@ class GroupCourseServiceTest {
void findDetailById_cacheMissWithType_shouldBuildAndCacheDetail() throws Exception {
String cacheKey = "group_course:detail:1";
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
when(groupCourseTypeRepository.findById(100L)).thenReturn(Mono.just(testType));
when(courseLabelRepository.findByTypeId(100L)).thenReturn(Flux.just(testLabel));
when(sysUserRepository.findByIdIncludingDeleted(10L)).thenReturn(Mono.just(testCoach));
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(8L));
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
StepVerifier.create(groupCourseService.findDetailById(1L))
.assertNext(result -> {
@@ -198,13 +198,13 @@ class GroupCourseServiceTest {
})
.verifyComplete();
verify(redisUtil).get(cacheKey, String.class);
verify(cacheOperations).get(cacheKey, String.class);
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
verify(groupCourseTypeRepository).findById(100L);
verify(courseLabelRepository).findByTypeId(100L);
verify(sysUserRepository).findByIdIncludingDeleted(10L);
verify(bookingRepository).countValidBookings(1L);
verify(redisUtil).setWithExpire(eq(cacheKey), anyString(), eq(300L));
verify(cacheOperations).setWithExpire(eq(cacheKey), anyString(), eq(300L));
}
@Test
@@ -212,11 +212,11 @@ class GroupCourseServiceTest {
String cacheKey = "group_course:detail:1";
testCourse.setCourseType(null);
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
when(sysUserRepository.findByIdIncludingDeleted(10L)).thenReturn(Mono.just(testCoach));
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(3L));
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
StepVerifier.create(groupCourseService.findDetailById(1L))
.assertNext(result -> {
@@ -236,7 +236,7 @@ class GroupCourseServiceTest {
void findDetailById_cacheMissNotFound_shouldReturnEmpty() {
String cacheKey = "group_course:detail:1";
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
StepVerifier.create(groupCourseService.findDetailById(1L))
@@ -250,15 +250,15 @@ class GroupCourseServiceTest {
void findDetailById_cacheParseError_shouldDeleteCacheAndFallback() throws Exception {
String cacheKey = "group_course:detail:1";
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just("invalid-json"));
when(redisUtil.delete(cacheKey)).thenReturn(Mono.just(1L));
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.just("invalid-json"));
when(cacheOperations.delete(cacheKey)).thenReturn(Mono.just(1L));
// After cache delete, switchIfEmpty kicks in
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
when(groupCourseTypeRepository.findById(100L)).thenReturn(Mono.just(testType));
when(courseLabelRepository.findByTypeId(100L)).thenReturn(Flux.just(testLabel));
when(sysUserRepository.findByIdIncludingDeleted(10L)).thenReturn(Mono.just(testCoach));
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
StepVerifier.create(groupCourseService.findDetailById(1L))
.assertNext(result -> {
@@ -267,7 +267,7 @@ class GroupCourseServiceTest {
})
.verifyComplete();
verify(redisUtil).delete(cacheKey);
verify(cacheOperations).delete(cacheKey);
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
}
@@ -278,7 +278,7 @@ class GroupCourseServiceTest {
String cacheKey = "group_course:id:1";
String json = objectMapper.writeValueAsString(testCourse);
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just(json));
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.just(json));
// switchIfEmpty 的 Mono 参数被急切求值,需要 mock 以避免 NPE
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
@@ -290,7 +290,7 @@ class GroupCourseServiceTest {
})
.verifyComplete();
verify(redisUtil).get(cacheKey, String.class);
verify(cacheOperations).get(cacheKey, String.class);
verify(bookingRepository).countValidBookings(1L);
// switchIfEmpty 的 Mono 参数被急切求值,findByIdAndDeletedAtIsNull 会被调用但不会订阅
}
@@ -299,9 +299,9 @@ class GroupCourseServiceTest {
void findById_cacheMiss_shouldQueryDbAndCache() throws Exception {
String cacheKey = "group_course:id:1";
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(testCourse));
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
when(bookingRepository.countValidBookings(1L)).thenReturn(Mono.just(5L));
StepVerifier.create(groupCourseService.findById(1L))
@@ -312,7 +312,7 @@ class GroupCourseServiceTest {
.verifyComplete();
verify(groupCourseRepository).findByIdAndDeletedAtIsNull(1L);
verify(redisUtil).setWithExpire(eq(cacheKey), anyString(), eq(300L));
verify(cacheOperations).setWithExpire(eq(cacheKey), anyString(), eq(300L));
verify(bookingRepository).countValidBookings(1L);
}
@@ -320,7 +320,7 @@ class GroupCourseServiceTest {
void findById_cacheMissNotFound_shouldReturnEmpty() {
String cacheKey = "group_course:id:1";
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(groupCourseRepository.findByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());
StepVerifier.create(groupCourseService.findById(1L))
@@ -383,7 +383,7 @@ class GroupCourseServiceTest {
String cacheKey = "group_course:page:0:10:false:id:asc::";
String json = objectMapper.writeValueAsString(pageResponse);
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.just(json));
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.just(json));
StepVerifier.create(groupCourseService.findByPage(pageRequest, false))
.assertNext(result -> {
@@ -393,7 +393,7 @@ class GroupCourseServiceTest {
})
.verifyComplete();
verify(redisUtil).get(cacheKey, String.class);
verify(cacheOperations).get(cacheKey, String.class);
verifyNoInteractions(groupCourseRepository);
}
@@ -409,9 +409,9 @@ class GroupCourseServiceTest {
String cacheKey = "group_course:page:0:10:false:id:asc::";
when(redisUtil.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(cacheOperations.get(cacheKey, String.class)).thenReturn(Mono.empty());
when(groupCourseRepository.findByPageAndNotDeleted(pageRequest)).thenReturn(Mono.just(pageResponse));
when(redisUtil.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire(eq(cacheKey), anyString(), eq(300L))).thenReturn(Mono.just(true));
StepVerifier.create(groupCourseService.findByPage(pageRequest, false))
.assertNext(result -> {
@@ -421,7 +421,7 @@ class GroupCourseServiceTest {
.verifyComplete();
verify(groupCourseRepository).findByPageAndNotDeleted(pageRequest);
verify(redisUtil).setWithExpire(eq(cacheKey), anyString(), eq(300L));
verify(cacheOperations).setWithExpire(eq(cacheKey), anyString(), eq(300L));
}
// ==================== create ====================
@@ -6,7 +6,7 @@ import cn.novalon.gym.manage.member.enums.MemberCardRecordStatus;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@@ -29,30 +29,30 @@ import java.util.stream.Collectors;
public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
private final MemberCardRecordRepository memberCardRecordRepository;
private final MemberCardRepository memberCardRepository;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private static final String MEMBER_CARD_RECORD_CACHE_PREFIX = "member:card:record:";
private static final long CACHE_EXPIRE_SECONDS = 300;
public MemberCardRecordServiceImpl(MemberCardRecordRepository memberCardRecordRepository,
MemberCardRepository memberCardRepository,
RedisUtil redisUtil) {
CacheOperations cacheOperations) {
this.memberCardRecordRepository = memberCardRecordRepository;
this.memberCardRepository = memberCardRepository;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
}
@Override
public Mono<MemberCardRecord> findById(Long recordId) {
String cacheKey = MEMBER_CARD_RECORD_CACHE_PREFIX + recordId;
return redisUtil.get(cacheKey)
return cacheOperations.get(cacheKey)
.filter(cached -> cached instanceof MemberCardRecord)
.map(cached -> (MemberCardRecord) cached)
.switchIfEmpty(Mono.defer(() ->
memberCardRecordRepository.findById(recordId)
.doOnSuccess(record -> {
if (record != null) {
redisUtil.setWithExpire(cacheKey, record, CACHE_EXPIRE_SECONDS);
cacheOperations.setWithExpire(cacheKey, record, CACHE_EXPIRE_SECONDS);
}
})
));
@@ -255,7 +255,7 @@ public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
private void clearRecordCache(Long recordId) {
String cacheKey = MEMBER_CARD_RECORD_CACHE_PREFIX + recordId;
redisUtil.delete(cacheKey);
cacheOperations.delete(cacheKey);
log.debug("清除会员卡记录缓存, recordId: {}", recordId);
}
}
@@ -1,6 +1,6 @@
package cn.novalon.gym.manage.member.service.impl;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
@@ -40,7 +40,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
private final DistributedLockService distributedLockService;
private final ExpirationReminderService expirationReminderService;
private final RefundSagaHandler refundSagaHandler;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private static final String MEMBER_CARD_CACHE_PREFIX = "member:card:";
private static final long CACHE_EXPIRE_SECONDS = 300;
@@ -53,7 +53,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
DistributedLockService distributedLockService,
ExpirationReminderService expirationReminderService,
RefundSagaHandler refundSagaHandler,
RedisUtil redisUtil) {
CacheOperations cacheOperations) {
this.memberCardRepository = memberCardRepository;
this.recordRepository = recordRepository;
this.transactionService = transactionService;
@@ -61,13 +61,13 @@ public class MemberCardServiceImpl implements IMemberCardService {
this.distributedLockService = distributedLockService;
this.expirationReminderService = expirationReminderService;
this.refundSagaHandler = refundSagaHandler;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
}
@Override
public Mono<MemberCard> findByMemberCardIdAndDeletedAtIsNull(Long memberCardId) {
String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
Object cached = redisUtil.get(cacheKey);
Object cached = cacheOperations.get(cacheKey);
if (cached != null && cached instanceof MemberCard) {
log.debug("从缓存获取会员卡信息, memberCardId: {}", memberCardId);
return Mono.just((MemberCard) cached);
@@ -76,7 +76,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
return memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(memberCardId)
.doOnSuccess(card -> {
if (card != null) {
redisUtil.setWithExpire(cacheKey, card, CACHE_EXPIRE_SECONDS);
cacheOperations.setWithExpire(cacheKey, card, CACHE_EXPIRE_SECONDS);
}
});
}
@@ -439,7 +439,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
private void clearCardCache(Long memberCardId) {
String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
redisUtil.delete(cacheKey);
cacheOperations.delete(cacheKey);
log.debug("清除会员卡缓存, memberCardId: {}", memberCardId);
}
}
@@ -18,7 +18,7 @@ import cn.novalon.gym.manage.member.util.AesUtil;
import cn.novalon.gym.manage.member.util.BeanConvertUtil;
import cn.novalon.gym.manage.member.util.EsSyncUtils;
import cn.novalon.gym.manage.member.util.WechatPhoneUtil;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.vo.MemberCardInfoVO;
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
@@ -50,7 +50,7 @@ public class MemberServiceImpl implements MemberService {
private final IMemberRepository memberRepository;
private final MemberESRepository memberESRepository;
private final EsSyncUtils esSyncUtils;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
@@ -68,7 +68,7 @@ public class MemberServiceImpl implements MemberService {
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
// 先查缓存
return redisUtil.get(cacheKey, MemberInfoVO.class)
return cacheOperations.get(cacheKey, MemberInfoVO.class)
.flatMap(cached -> {
if (cached != null) {
log.debug("从缓存获取会员信息, memberId: {}", memberId);
@@ -91,7 +91,7 @@ public class MemberServiceImpl implements MemberService {
.map(this::buildMemberInfoResponse)
.flatMap(vo -> {
// 查询到数据后更新缓存
return redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
.then(Mono.just(vo));
})
.switchIfEmpty(Mono.error(() -> {
@@ -269,7 +269,7 @@ public class MemberServiceImpl implements MemberService {
String cacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
return redisUtil.get(cacheKey, MemberDetailVO.class)
return cacheOperations.get(cacheKey, MemberDetailVO.class)
.filter(cached -> cached != null)
.switchIfEmpty(Mono.defer(() ->
memberRepository.findById(memberId)
@@ -312,7 +312,7 @@ public class MemberServiceImpl implements MemberService {
return memberDetailVO;
}
)
.flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
.flatMap(vo -> cacheOperations.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
.then(Mono.just(vo)))
));
}
@@ -383,8 +383,8 @@ public class MemberServiceImpl implements MemberService {
private Mono<Long> clearMemberCache(Long memberId) {
String infoCacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
String detailCacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
return redisUtil.delete(infoCacheKey)
.then(redisUtil.delete(detailCacheKey))
return cacheOperations.delete(infoCacheKey)
.then(cacheOperations.delete(detailCacheKey))
.doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
}
}
@@ -5,7 +5,7 @@ import cn.novalon.gym.manage.member.entity.RefundApplication;
import cn.novalon.gym.manage.member.enums.RefundStatus;
import cn.novalon.gym.manage.member.repository.RefundApplicationRepository;
import cn.novalon.gym.manage.member.service.IRefundApplicationService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@@ -23,14 +23,14 @@ import java.time.LocalDateTime;
public class RefundApplicationServiceImpl implements IRefundApplicationService {
private final RefundApplicationRepository refundApplicationRepository;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private static final String REFUND_APPLICATION_CACHE_PREFIX = "member:refund:";
private static final long CACHE_EXPIRE_SECONDS = 300;
public RefundApplicationServiceImpl(RefundApplicationRepository refundApplicationRepository, RedisUtil redisUtil) {
public RefundApplicationServiceImpl(RefundApplicationRepository refundApplicationRepository, CacheOperations cacheOperations) {
this.refundApplicationRepository = refundApplicationRepository;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
}
@Override
@@ -103,7 +103,7 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
@Override
public Mono<RefundApplication> findByRecordId(Long recordId) {
String cacheKey = REFUND_APPLICATION_CACHE_PREFIX + recordId;
Object cached = redisUtil.get(cacheKey);
Object cached = cacheOperations.get(cacheKey);
if (cached != null && cached instanceof RefundApplication) {
log.debug("从缓存获取退款申请, recordId: {}", recordId);
return Mono.just((RefundApplication) cached);
@@ -112,14 +112,14 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
return refundApplicationRepository.findByRecordId(recordId)
.doOnSuccess(application -> {
if (application != null) {
redisUtil.setWithExpire(cacheKey, application, CACHE_EXPIRE_SECONDS);
cacheOperations.setWithExpire(cacheKey, application, CACHE_EXPIRE_SECONDS);
}
});
}
private void clearRefundCache(Long recordId) {
String cacheKey = REFUND_APPLICATION_CACHE_PREFIX + recordId;
redisUtil.delete(cacheKey);
cacheOperations.delete(cacheKey);
log.debug("清除退款申请缓存, recordId: {}", recordId);
}
}
@@ -4,7 +4,7 @@ import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.SystemException;
import cn.novalon.gym.manage.member.config.WechatProperties;
import cn.novalon.gym.manage.member.service.WechatApiService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
@@ -31,7 +31,7 @@ import java.util.Map;
public class WechatApiServiceImpl implements WechatApiService {
private final WechatProperties wechatProperties;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private static final String ACCESS_TOKEN_CACHE_PREFIX = "wechat:access_token:";
private static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7000; // 比官方过期时间短100秒
@@ -173,7 +173,7 @@ public class WechatApiServiceImpl implements WechatApiService {
String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + appType;
return redisUtil.get(cacheKey, String.class)
return cacheOperations.get(cacheKey, String.class)
.flatMap(cachedToken -> {
if (cachedToken != null) {
log.debug("从缓存获取access_token, appType: {}", appType);
@@ -203,7 +203,7 @@ public class WechatApiServiceImpl implements WechatApiService {
String accessToken = (String) response.get("access_token");
Integer expiresIn = (Integer) response.get("expires_in");
log.info("获取access_token成功, expires_in: {}s", expiresIn);
return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
.then(Mono.just(accessToken));
} else {
String errmsg = (String) response.get("errmsg");
@@ -14,7 +14,7 @@ import cn.novalon.gym.manage.member.service.WechatAuthService;
import cn.novalon.gym.manage.member.util.AesUtil;
import cn.novalon.gym.manage.member.util.EsSyncUtils;
import cn.novalon.gym.manage.member.util.MemberNoGenerator;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.util.WechatPhoneUtil;
import cn.novalon.gym.manage.member.vo.WechatLoginVO;
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
@@ -46,7 +46,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
private final MemberESRepository memberESRepository;
private final EsSyncUtils esSyncUtils;
private final JwtTokenProvider jwtTokenProvider;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
@@ -217,7 +217,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
private void clearMemberCache(Long memberId) {
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
redisUtil.delete(cacheKey);
cacheOperations.delete(cacheKey);
log.debug("清除会员缓存, memberId: {}", memberId);
}
@@ -8,7 +8,7 @@ import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
import cn.novalon.gym.manage.member.repository.IMemberRepository;
import cn.novalon.gym.manage.member.service.WechatOfficialService;
import cn.novalon.gym.manage.member.util.EsSyncUtils;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.vo.WechatUserInfoVO;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -43,7 +43,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
private final ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
private final EsSyncUtils esSyncUtils;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
@@ -297,7 +297,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
private Mono<String> getAccessToken() {
String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + "mp";
return redisUtil.get(cacheKey, String.class)
return cacheOperations.get(cacheKey, String.class)
.flatMap(cachedToken -> {
if (cachedToken != null) {
log.debug("从缓存获取服务号access_token");
@@ -322,7 +322,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
throw new RuntimeException("获取AccessToken失败: " + response.get("errmsg"));
}
String accessToken = (String) response.get("access_token");
return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
.then(Mono.just(accessToken));
});
});
@@ -373,7 +373,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
private Mono<Long> clearMemberCache(Long memberId) {
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
return redisUtil.delete(cacheKey)
return cacheOperations.delete(cacheKey)
.doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
}
}
@@ -1,6 +1,6 @@
package cn.novalon.gym.manage.member.service.impl;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.enums.MemberCardRecordStatus;
@@ -37,7 +37,7 @@ class MemberCardRecordServiceImplTest {
private MemberCardRepository memberCardRepository;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@InjectMocks
private MemberCardRecordServiceImpl memberCardRecordService;
@@ -82,7 +82,7 @@ class MemberCardRecordServiceImplTest {
@DisplayName("缓存命中时应从缓存返回")
void shouldReturnFromCacheWhenHit() {
MemberCardRecord record = createRecord();
when(redisUtil.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(record));
when(cacheOperations.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(record));
Mono<MemberCardRecord> result = memberCardRecordService.findById(RECORD_ID);
@@ -97,7 +97,7 @@ class MemberCardRecordServiceImplTest {
@DisplayName("缓存未命中时应从数据库查询并更新缓存")
void shouldQueryDatabaseWhenCacheMiss() {
MemberCardRecord record = createRecord();
when(redisUtil.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
when(cacheOperations.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
when(memberCardRecordRepository.findById(anyLong())).thenReturn(Mono.just(record));
Mono<MemberCardRecord> result = memberCardRecordService.findById(RECORD_ID);
@@ -112,7 +112,7 @@ class MemberCardRecordServiceImplTest {
@Test
@DisplayName("记录不存在时应返回空")
void shouldReturnEmptyWhenNotFound() {
when(redisUtil.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
when(cacheOperations.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
when(memberCardRecordRepository.findById(anyLong())).thenReturn(Mono.empty());
Mono<MemberCardRecord> result = memberCardRecordService.findById(RECORD_ID);
@@ -210,7 +210,7 @@ class MemberCardRecordServiceImplTest {
@DisplayName("扣减成功时应清除缓存")
void shouldClearCacheWhenDeducted() {
when(memberCardRecordRepository.deductUsage(RECORD_ID, 1, 0.0)).thenReturn(Mono.just(1));
when(redisUtil.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
Mono<Integer> result = memberCardRecordService.deductUsage(RECORD_ID, 1, 0.0);
@@ -218,7 +218,7 @@ class MemberCardRecordServiceImplTest {
.assertNext(updated -> assertThat(updated).isEqualTo(1))
.verifyComplete();
verify(redisUtil).delete("member:card:record:" + RECORD_ID);
verify(cacheOperations).delete("member:card:record:" + RECORD_ID);
}
@Test
@@ -232,7 +232,7 @@ class MemberCardRecordServiceImplTest {
.assertNext(updated -> assertThat(updated).isZero())
.verifyComplete();
verify(redisUtil, never()).delete(anyString());
verify(cacheOperations, never()).delete(anyString());
}
}
@@ -247,7 +247,7 @@ class MemberCardRecordServiceImplTest {
void shouldClearCacheWhenRenewed() {
LocalDateTime newExpire = LocalDateTime.now().plusDays(30);
when(memberCardRecordRepository.renewCard(RECORD_ID, 10, null, newExpire)).thenReturn(Mono.just(1));
when(redisUtil.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
Mono<Integer> result = memberCardRecordService.renewCard(RECORD_ID, 10, null, newExpire);
@@ -267,7 +267,7 @@ class MemberCardRecordServiceImplTest {
@DisplayName("状态更新成功时应清除缓存")
void shouldClearCacheWhenStatusUpdated() {
when(memberCardRecordRepository.updateStatus(RECORD_ID, "USED_UP")).thenReturn(Mono.just(1));
when(redisUtil.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
Mono<Integer> result = memberCardRecordService.updateStatus(RECORD_ID, "USED_UP");
@@ -1,6 +1,6 @@
package cn.novalon.gym.manage.member.service.impl;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.enums.CardEvent;
@@ -57,7 +57,7 @@ class MemberCardServiceImplTest {
private RefundSagaHandler refundSagaHandler;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@InjectMocks
private MemberCardServiceImpl memberCardService;
@@ -155,7 +155,7 @@ class MemberCardServiceImplTest {
@DisplayName("缓存未命中时应从数据库查询并更新缓存")
void shouldQueryDatabaseWhenCacheMiss() {
MemberCard card = createTimeCard();
when(redisUtil.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
when(cacheOperations.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(MEMBER_CARD_ID))
.thenReturn(Mono.just(card));
@@ -171,7 +171,7 @@ class MemberCardServiceImplTest {
@Test
@DisplayName("卡片不存在时应返回空")
void shouldReturnEmptyWhenCardNotFound() {
when(redisUtil.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
when(cacheOperations.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(MEMBER_CARD_ID))
.thenReturn(Mono.empty());
@@ -214,7 +214,7 @@ class MemberCardServiceImplTest {
void shouldClearCacheWhenSaved() {
MemberCard card = createTimeCard();
when(memberCardRepository.save(card)).thenReturn(Mono.just(card));
when(redisUtil.delete("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.just(1L));
Mono<MemberCard> result = memberCardService.save(card);
@@ -222,7 +222,7 @@ class MemberCardServiceImplTest {
.assertNext(c -> assertThat(c.getMemberCardName()).isEqualTo("月卡"))
.verifyComplete();
verify(redisUtil).delete("member:card:" + MEMBER_CARD_ID);
verify(cacheOperations).delete("member:card:" + MEMBER_CARD_ID);
}
}
@@ -274,7 +274,7 @@ class MemberCardServiceImplTest {
@DisplayName("购买时间卡应成功创建记录")
void shouldPurchaseTimeCardSuccessfully() {
MemberCard timeCard = createTimeCard();
lenient().when(redisUtil.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
lenient().when(cacheOperations.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
when(memberCardRepository.findByIdAndDeletedAtIsNull(MEMBER_CARD_ID)).thenReturn(Mono.just(timeCard));
when(recordRepository.insertActiveRecord(anyLong(), anyLong(), any(), anyInt(), anyDouble(), any()))
.thenReturn(Mono.just(createActiveRecord(timeCard, MemberCardRecordStatus.ACTIVE)));
@@ -4,7 +4,7 @@ import cn.novalon.gym.manage.common.exception.ConflictException;
import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.NotFoundException;
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
import cn.novalon.gym.manage.member.entity.Member;
@@ -52,7 +52,7 @@ class MemberServiceImplTest {
private EsSyncUtils esSyncUtils;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@InjectMocks
private MemberServiceImpl memberServiceImpl;
@@ -140,7 +140,7 @@ class MemberServiceImplTest {
@DisplayName("缓存命中时应直接返回缓存数据")
void shouldReturnCachedDataWhenCacheHit() {
MemberInfoVO cachedVO = createTestMemberInfoVO();
when(redisUtil.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.just(cachedVO));
when(cacheOperations.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.just(cachedVO));
Mono<MemberInfoVO> result = memberServiceImpl.getMemberInfo(MEMBER_ID);
@@ -151,17 +151,17 @@ class MemberServiceImplTest {
})
.verifyComplete();
verify(redisUtil).get(CACHE_INFO_KEY, MemberInfoVO.class);
verify(cacheOperations).get(CACHE_INFO_KEY, MemberInfoVO.class);
verifyNoInteractions(memberRepository);
}
@Test
@DisplayName("缓存未命中时应从数据库查询并更新缓存")
void shouldQueryDatabaseAndCacheWhenCacheMiss() {
when(redisUtil.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
when(cacheOperations.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
Member member = createTestMember();
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
when(redisUtil.setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), anyLong()))
when(cacheOperations.setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), anyLong()))
.thenReturn(Mono.just(true));
try (MockedStatic<AesUtil> aesUtilMock = mockStatic(AesUtil.class)) {
@@ -179,14 +179,14 @@ class MemberServiceImplTest {
.verifyComplete();
verify(memberRepository).findById(MEMBER_ID);
verify(redisUtil).setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), eq(300L));
verify(cacheOperations).setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), eq(300L));
}
}
@Test
@DisplayName("会员不存在时应抛出 NotFoundException")
void shouldThrowNotFoundExceptionWhenMemberNotFound() {
when(redisUtil.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
when(cacheOperations.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.empty());
Mono<MemberInfoVO> result = memberServiceImpl.getMemberInfo(MEMBER_ID);
@@ -198,7 +198,7 @@ class MemberServiceImplTest {
&& throwable.getMessage().equals("会员不存在"))
.verify();
verify(redisUtil).get(CACHE_INFO_KEY, MemberInfoVO.class);
verify(cacheOperations).get(CACHE_INFO_KEY, MemberInfoVO.class);
verify(memberRepository).findById(MEMBER_ID);
}
}
@@ -217,8 +217,8 @@ class MemberServiceImplTest {
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
when(memberRepository.save(any(Member.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
when(redisUtil.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(redisUtil.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
try (MockedStatic<AesUtil> aesUtilMock = mockStatic(AesUtil.class)) {
aesUtilMock.when(() -> AesUtil.decrypt(ENCRYPTED_PHONE)).thenReturn(DECRYPTED_PHONE);
@@ -236,8 +236,8 @@ class MemberServiceImplTest {
verify(memberRepository).findById(MEMBER_ID);
verify(memberRepository).save(any(Member.class));
verify(mockSyncer).sync(any(Member.class));
verify(redisUtil).delete("member:info:" + MEMBER_ID);
verify(redisUtil).delete("member:detail:" + MEMBER_ID);
verify(cacheOperations).delete("member:info:" + MEMBER_ID);
verify(cacheOperations).delete("member:detail:" + MEMBER_ID);
}
}
@@ -274,8 +274,8 @@ class MemberServiceImplTest {
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
when(memberRepository.save(any(Member.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
when(redisUtil.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(redisUtil.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
try (MockedStatic<AesUtil> aesUtilMock = mockStatic(AesUtil.class)) {
aesUtilMock.when(() -> AesUtil.encrypt(TEST_PHONE)).thenReturn(ENCRYPTED_PHONE);
@@ -493,7 +493,7 @@ class MemberServiceImplTest {
.nickname("测试会员")
.build();
when(redisUtil.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.just(cachedDetail));
when(cacheOperations.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.just(cachedDetail));
Mono<MemberDetailVO> result = memberServiceImpl.getMemberDetail(MEMBER_ID);
@@ -504,14 +504,14 @@ class MemberServiceImplTest {
})
.verifyComplete();
verify(redisUtil).get(CACHE_DETAIL_KEY, MemberDetailVO.class);
verify(cacheOperations).get(CACHE_DETAIL_KEY, MemberDetailVO.class);
verifyNoInteractions(memberRepository);
}
@Test
@DisplayName("缓存未命中时应从数据库查询并构建详情")
void shouldQueryDatabaseAndBuildDetailWhenCacheMiss() {
when(redisUtil.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
when(cacheOperations.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
Member member = createTestMember();
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
@@ -520,7 +520,7 @@ class MemberServiceImplTest {
when(memberRepository.findCardRecordsWithCardInfoByMemberId(MEMBER_ID))
.thenReturn(Flux.just(cardInfo));
when(redisUtil.setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), anyLong()))
when(cacheOperations.setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), anyLong()))
.thenReturn(Mono.just(true));
Mono<MemberDetailVO> result = memberServiceImpl.getMemberDetail(MEMBER_ID);
@@ -539,13 +539,13 @@ class MemberServiceImplTest {
verify(memberRepository).findById(MEMBER_ID);
verify(memberRepository).findCardRecordsWithCardInfoByMemberId(MEMBER_ID);
verify(redisUtil).setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), eq(300L));
verify(cacheOperations).setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), eq(300L));
}
@Test
@DisplayName("会员不存在时应抛出 NotFoundException")
void shouldThrowNotFoundExceptionWhenMemberNotFound() {
when(redisUtil.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
when(cacheOperations.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.empty());
// zipWith 会订阅所有源即使另一个是 error也需要桩避免 NPE
lenient().when(memberRepository.findCardRecordsWithCardInfoByMemberId(MEMBER_ID))
@@ -577,8 +577,8 @@ class MemberServiceImplTest {
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
when(memberRepository.save(any(Member.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
when(redisUtil.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(redisUtil.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
when(cacheOperations.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
Mono<Boolean> result = memberServiceImpl.adminUpdateMemberInfo(MEMBER_ID, dto);
@@ -589,8 +589,8 @@ class MemberServiceImplTest {
verify(memberRepository).findById(MEMBER_ID);
verify(memberRepository).save(any(Member.class));
verify(mockSyncer).sync(any(Member.class));
verify(redisUtil).delete("member:info:" + MEMBER_ID);
verify(redisUtil).delete("member:detail:" + MEMBER_ID);
verify(cacheOperations).delete("member:info:" + MEMBER_ID);
verify(cacheOperations).delete("member:detail:" + MEMBER_ID);
}
@Test
@@ -1,6 +1,6 @@
package cn.novalon.gym.manage.payment.service.impl;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.payment.config.HuifuProperties;
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
@@ -46,16 +46,16 @@ public class PaymentServiceImpl implements PaymentService {
private final HuifuProperties huifuProperties;
private final PaymentOrderRepository paymentOrderRepository;
private final RedisUtil redisUtil;
private final CacheOperations cacheOperations;
private final PaymentNotifyService paymentNotifyService;
public PaymentServiceImpl(HuifuProperties huifuProperties,
PaymentOrderRepository paymentOrderRepository,
RedisUtil redisUtil,
CacheOperations cacheOperations,
PaymentNotifyService paymentNotifyService) {
this.huifuProperties = huifuProperties;
this.paymentOrderRepository = paymentOrderRepository;
this.redisUtil = redisUtil;
this.cacheOperations = cacheOperations;
this.paymentNotifyService = paymentNotifyService;
}
@@ -860,7 +860,7 @@ public class PaymentServiceImpl implements PaymentService {
}
private Mono<Boolean> isTradeProcessed(String reqSeqId) {
return redisUtil.hasKey(PROCESSED_TRADE_PREFIX + reqSeqId)
return cacheOperations.hasKey(PROCESSED_TRADE_PREFIX + reqSeqId)
.onErrorResume(e -> {
log.error("[Huifu] 检查交易处理状态异常: reqSeqId={}", reqSeqId, e);
return Mono.just(false);
@@ -868,7 +868,7 @@ public class PaymentServiceImpl implements PaymentService {
}
private Mono<Void> markTradeProcessed(String reqSeqId) {
return redisUtil.setWithExpire(PROCESSED_TRADE_PREFIX + reqSeqId, "1", IDEMPOTENT_EXPIRE_SECONDS)
return cacheOperations.setWithExpire(PROCESSED_TRADE_PREFIX + reqSeqId, "1", IDEMPOTENT_EXPIRE_SECONDS)
.onErrorResume(e -> {
log.error("[Huifu] 标记交易处理状态异常: reqSeqId={}", reqSeqId, e);
return Mono.empty();
@@ -1,6 +1,6 @@
package cn.novalon.gym.manage.payment.service.impl;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.payment.config.HuifuProperties;
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
@@ -35,7 +35,7 @@ class PaymentServiceImplTest {
private PaymentOrderRepository paymentOrderRepository;
@Mock
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@Mock
private PaymentNotifyService paymentNotifyService;
@@ -58,7 +58,7 @@ class PaymentServiceImplTest {
lenient().when(huifuProperties.isSdkInitialized()).thenReturn(false);
paymentService = new PaymentServiceImpl(huifuProperties, paymentOrderRepository, redisUtil, paymentNotifyService);
paymentService = new PaymentServiceImpl(huifuProperties, paymentOrderRepository, cacheOperations, paymentNotifyService);
}
private PaymentOrder createTestOrder(String status) {
@@ -369,7 +369,7 @@ class PaymentServiceImplTest {
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(true));
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(true));
Mono<String> result = paymentService.handleAlipayNotify(params);
@@ -388,10 +388,10 @@ class PaymentServiceImplTest {
PaymentOrder order = createTestOrder("PENDING");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenReturn(Mono.just(order));
when(redisUtil.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "SUCCESS", MEMBER_ID)).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
@@ -401,7 +401,7 @@ class PaymentServiceImplTest {
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
verify(redisUtil).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
verify(cacheOperations).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "SUCCESS", MEMBER_ID);
}
@@ -413,10 +413,10 @@ class PaymentServiceImplTest {
PaymentOrder order = createTestOrder("PENDING");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenReturn(Mono.just(order));
when(redisUtil.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
when(cacheOperations.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "FAIL", MEMBER_ID)).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
@@ -426,7 +426,7 @@ class PaymentServiceImplTest {
.verifyComplete();
verify(paymentOrderRepository).save(any(PaymentOrder.class));
verify(redisUtil).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
verify(cacheOperations).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "FAIL", MEMBER_ID);
}
@@ -438,7 +438,7 @@ class PaymentServiceImplTest {
PaymentOrder order = createTestOrder("PENDING");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "PENDING", MEMBER_ID)).thenReturn(Mono.empty());
@@ -458,7 +458,7 @@ class PaymentServiceImplTest {
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.empty());
Mono<String> result = paymentService.handleAlipayNotify(params);
@@ -474,7 +474,7 @@ class PaymentServiceImplTest {
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
params.put("sign", "testSign");
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.error(new RuntimeException("DB error")));
Mono<String> result = paymentService.handleAlipayNotify(params);
@@ -1,6 +1,10 @@
server:
port: 8084
gym:
cache:
type: redis
spring:
aop:
proxy-target-class: true
@@ -4,7 +4,7 @@ import cn.novalon.gym.manage.app.ManageApplication;
import cn.novalon.gym.manage.auth.config.DCloudUniverifyConfig;
import cn.novalon.gym.manage.auth.service.PhoneAuthService;
import cn.novalon.gym.manage.auth.service.SmsService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
import cn.novalon.gym.manage.payment.config.HuifuProperties;
import cn.novalon.gym.manage.payment.service.PaymentNotifyService;
@@ -74,9 +74,9 @@ public abstract class BaseContractTest {
@MockBean
protected ReactiveStringRedisTemplate reactiveStringRedisTemplate;
/** Mock RedisUtil — 阻止 Handler 中的 Redis 操作导致异常 */
/** Mock CacheOperations — 阻止 Handler 中的 Redis 操作导致异常 */
@MockBean
protected RedisUtil redisUtil;
protected CacheOperations cacheOperations;
/** Mock ES Repository — 防止连接 Elasticsearch */
@MockBean
@@ -1,7 +1,7 @@
package cn.novalon.gym.manage.app.integration;
import cn.novalon.gym.manage.app.contract.BaseContractTest;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -26,13 +26,13 @@ import static org.mockito.Mockito.when;
class GroupCourseHandlerIntegrationTest extends BaseContractTest {
@Autowired
private RedisUtil redisUtil;
private CacheOperations cacheOperations;
@BeforeEach
void setUp() {
// Mock Redis 操作避免 GroupCourseService 中的 Redis 缓存逻辑导致 NPE
when(redisUtil.get(anyString(), any())).thenReturn(Mono.empty());
when(redisUtil.setWithExpire(anyString(), any(), anyLong())).thenReturn(Mono.just(true));
when(cacheOperations.get(anyString(), any())).thenReturn(Mono.empty());
when(cacheOperations.setWithExpire(anyString(), any(), anyLong())).thenReturn(Mono.just(true));
}
@Test
@@ -1,7 +1,7 @@
package cn.novalon.gym.manage.app.integration;
import cn.novalon.gym.manage.app.contract.BaseContractTest;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.member.entity.Member;
import cn.novalon.gym.manage.member.es.entity.MemberES;
import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
@@ -70,10 +70,10 @@ class MemberHandlerIntegrationTest extends BaseContractTest {
memberRepository.deleteAll().block();
when(authUtil.getMemberIdOrThrow(any())).thenReturn(1L);
// Mock Redis 缓存未命中确保每次查询都走数据库
when(redisUtil.get(anyString(), any())).thenReturn(Mono.empty());
when(redisUtil.setWithExpire(anyString(), any(), anyLong())).thenReturn(Mono.just(true));
when(cacheOperations.get(anyString(), any())).thenReturn(Mono.empty());
when(cacheOperations.setWithExpire(anyString(), any(), anyLong())).thenReturn(Mono.just(true));
// Mock Redis 删除操作clearMemberCache 调用防止 NPE 导致 adminUpdateMemberInfo 返回 false
when(redisUtil.delete(anyString())).thenReturn(Mono.just(1L));
when(cacheOperations.delete(anyString())).thenReturn(Mono.just(1L));
// Mock ES 搜索返回空结果防止因 ES 不可用导致 500
when(memberESRepository.findByMemberNoOrPhoneOrNicknameContaining(anyString(), anyString(), anyString(), any()))
.thenReturn(Flux.empty());
+4
View File
@@ -60,6 +60,10 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
<build>
@@ -0,0 +1,21 @@
package cn.novalon.gym.manage.common.cache;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 缓存自动配置
* <p>
* 启用 {@link CacheProperties} 配置绑定并扫描 {@code cn.novalon.gym.manage.common.cache}
* 包下的缓存实现组件{@link RedisCacheOperations} / {@link CaffeineCacheOperations}
* </p>
*
* @author 张翔
* @date 2026-08-02
*/
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
@ComponentScan(basePackageClasses = CacheAutoConfiguration.class)
public class CacheAutoConfiguration {
}
@@ -0,0 +1,88 @@
package cn.novalon.gym.manage.common.cache;
import reactor.core.publisher.Mono;
/**
* 缓存操作接口
* <p>
* 定义统一的缓存操作抽象支持通过配置切换不同的缓存实现Caffeine / Redis
* 所有业务模块应依赖此接口而非具体实现实现类由 {@link CacheAutoConfiguration} 按配置自动装配
* </p>
*
* @author 张翔
* @date 2026-08-02
* @see RedisCacheOperations
* @see CaffeineCacheOperations
*/
public interface CacheOperations {
/**
* 获取缓存值带类型转换
*
* @param key 缓存键
* @param clazz 目标类型
* @param <T> 返回值类型
* @return 缓存值不存在时返回 {@link Mono#empty()}
*/
<T> Mono<T> get(String key, Class<T> clazz);
/**
* 获取缓存值返回 Object
*
* @param key 缓存键
* @return 缓存值不存在时返回 {@link Mono#empty()}
*/
Mono<Object> get(String key);
/**
* 设置缓存值无过期时间
*
* @param key 缓存键
* @param value 缓存值
* @return true 设置成功
*/
Mono<Boolean> set(String key, Object value);
/**
* 设置缓存值并指定过期时间
*
* @param key 缓存键
* @param value 缓存值
* @param timeoutSeconds 过期时间
* @return true 设置成功
*/
Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds);
/**
* 删除缓存
*
* @param key 缓存键
* @return 被删除的键数量
*/
Mono<Long> delete(String key);
/**
* 判断缓存键是否存在
*
* @param key 缓存键
* @return true 存在
*/
Mono<Boolean> hasKey(String key);
/**
* 设置过期时间
*
* @param key 缓存键
* @param timeoutSeconds 过期时间
* @return true 设置成功
*/
Mono<Boolean> expire(String key, long timeoutSeconds);
/**
* 根据通配符模式删除缓存键
*
* @param pattern 通配符模式 {@code member:info:*}
* @return 被删除的键数量
*/
Mono<Long> deleteByPattern(String pattern);
}
@@ -0,0 +1,88 @@
package cn.novalon.gym.manage.common.cache;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 缓存配置属性
* <p>
* 通过 {@code gym.cache.type} 配置选择缓存实现
* <ul>
* <li>{@code redis} 使用 Redis 实现默认</li>
* <li>{@code caffeine} 使用 Caffeine 本地缓存实现</li>
* </ul>
* </p>
*
* <pre>{@code
* gym:
* cache:
* type: redis # redis | caffeine
* caffeine:
* initial-capacity: 100
* max-size: 10000
* expire-after-write-seconds: 300
* }</pre>
*
* @author 张翔
* @date 2026-08-02
*/
@ConfigurationProperties(prefix = "gym.cache")
public class CacheProperties {
/** 缓存类型:redis / caffeine,默认 redis */
private String type = "redis";
/** Caffeine 本地缓存专属配置 */
private Caffeine caffeine = new Caffeine();
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Caffeine getCaffeine() {
return caffeine;
}
public void setCaffeine(Caffeine caffeine) {
this.caffeine = caffeine;
}
public static class Caffeine {
/** 初始容量 */
private int initialCapacity = 100;
/** 最大条数 */
private long maxSize = 10000;
/** 写入后过期时间(秒),默认 5 分钟 */
private long expireAfterWriteSeconds = 300;
public int getInitialCapacity() {
return initialCapacity;
}
public void setInitialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
}
public long getMaxSize() {
return maxSize;
}
public void setMaxSize(long maxSize) {
this.maxSize = maxSize;
}
public long getExpireAfterWriteSeconds() {
return expireAfterWriteSeconds;
}
public void setExpireAfterWriteSeconds(long expireAfterWriteSeconds) {
this.expireAfterWriteSeconds = expireAfterWriteSeconds;
}
}
}
@@ -0,0 +1,111 @@
package cn.novalon.gym.manage.common.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* Caffeine 本地缓存实现
* <p>
* 基于 Caffeine 的进程内缓存适合单机部署或开发测试环境
* {@code gym.cache.type=caffeine} 时生效
* </p>
*
* @author 张翔
* @date 2026-08-02
* @see CacheOperations
* @see RedisCacheOperations
*/
@Component
@ConditionalOnProperty(name = "gym.cache.type", havingValue = "caffeine")
public class CaffeineCacheOperations implements CacheOperations {
private static final Logger log = LoggerFactory.getLogger(CaffeineCacheOperations.class);
private final Cache<String, Object> cache;
public CaffeineCacheOperations(CacheProperties cacheProperties) {
CacheProperties.Caffeine config = cacheProperties.getCaffeine();
this.cache = Caffeine.newBuilder()
.initialCapacity(config.getInitialCapacity())
.maximumSize(config.getMaxSize())
.expireAfterWrite(config.getExpireAfterWriteSeconds(), TimeUnit.SECONDS)
.recordStats()
.build();
log.info("Caffeine 缓存已初始化: initialCapacity={}, maxSize={}, expireAfterWrite={}s",
config.getInitialCapacity(), config.getMaxSize(), config.getExpireAfterWriteSeconds());
}
@Override
@SuppressWarnings("unchecked")
public <T> Mono<T> get(String key, Class<T> clazz) {
Object value = cache.getIfPresent(key);
if (value == null) {
return Mono.empty();
}
if (clazz.isInstance(value)) {
return Mono.just((T) value);
}
log.warn("Caffeine 缓存类型不匹配: key={}, expected={}, actual={}",
key, clazz.getName(), value.getClass().getName());
return Mono.empty();
}
@Override
public Mono<Object> get(String key) {
Object value = cache.getIfPresent(key);
if (value == null) {
return Mono.empty();
}
return Mono.just(value);
}
@Override
public Mono<Boolean> set(String key, Object value) {
cache.put(key, value);
return Mono.just(true);
}
@Override
public Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds) {
// Caffeine 在构建时已指定 expireAfterWrite此处仅做 put 操作
// 如需 per-entry TTL可考虑 Caffeine Expiry 接口但当前场景统一 TTL 已足够
cache.put(key, value);
return Mono.just(true);
}
@Override
public Mono<Long> delete(String key) {
cache.invalidate(key);
return Mono.just(1L);
}
@Override
public Mono<Boolean> hasKey(String key) {
return Mono.just(cache.getIfPresent(key) != null);
}
@Override
public Mono<Boolean> expire(String key, long timeoutSeconds) {
// Caffeine 不支持为单个 key 动态设置过期时间直接返回 true过期由构建时策略统一管理
log.debug("Caffeine 不支持动态设置过期时间,忽略: key={}, timeoutSeconds={}", key, timeoutSeconds);
return Mono.just(true);
}
@Override
public Mono<Long> deleteByPattern(String pattern) {
Pattern regex = Pattern.compile(pattern.replace("*", ".*"));
long count = cache.asMap().keySet().stream()
.filter(k -> regex.matcher(k).matches())
.peek(cache::invalidate)
.count();
return Mono.just(count);
}
}
@@ -0,0 +1,83 @@
package cn.novalon.gym.manage.common.cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* Redis 缓存实现
* <p>
* 基于 {@link ReactiveRedisTemplate} 的响应式 Redis 缓存操作
* {@code gym.cache.type=redis} 时生效默认值
* </p>
*
* @author 张翔
* @date 2026-08-02
* @see CacheOperations
* @see CaffeineCacheOperations
*/
@Component
@ConditionalOnProperty(name = "gym.cache.type", havingValue = "redis", matchIfMissing = true)
public class RedisCacheOperations implements CacheOperations {
private static final Logger log = LoggerFactory.getLogger(RedisCacheOperations.class);
private final ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
public RedisCacheOperations(ReactiveRedisTemplate<String, Object> reactiveRedisTemplate) {
this.reactiveRedisTemplate = reactiveRedisTemplate;
}
@Override
public <T> Mono<T> get(String key, Class<T> clazz) {
return reactiveRedisTemplate.opsForValue().get(key)
.filter(clazz::isInstance)
.map(clazz::cast)
.onErrorResume(e -> {
log.warn("读取 Redis 缓存失败(可能为旧格式): key={}, error={}", key, e.getMessage());
return Mono.empty();
});
}
@Override
public Mono<Object> get(String key) {
return reactiveRedisTemplate.opsForValue().get(key);
}
@Override
public Mono<Boolean> set(String key, Object value) {
return reactiveRedisTemplate.opsForValue().set(key, value);
}
@Override
public Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds) {
return reactiveRedisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeoutSeconds));
}
@Override
public Mono<Long> delete(String key) {
return reactiveRedisTemplate.delete(key);
}
@Override
public Mono<Boolean> hasKey(String key) {
return reactiveRedisTemplate.hasKey(key);
}
@Override
public Mono<Boolean> expire(String key, long timeoutSeconds) {
return reactiveRedisTemplate.expire(key, Duration.ofSeconds(timeoutSeconds));
}
@Override
public Mono<Long> deleteByPattern(String pattern) {
return reactiveRedisTemplate.keys(pattern)
.flatMap(keys -> reactiveRedisTemplate.delete(keys))
.count();
}
}
@@ -1 +1 @@
cn.novalon.gym.manage.common.config.JwtProperties
cn.novalon.gym.manage.common.cache.CacheAutoConfiguration
@@ -1,5 +1,6 @@
package cn.novalon.gym.manage.gateway.cache;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -12,16 +13,11 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* 请求缓存服务
*
* 文件定义实现网关请求的缓存机制
* 涉及业务响应缓存缓存失效缓存统计
*
* 核心功能
* 1. 请求响应缓存
* 2. 缓存键生成
* 3. 缓存失效管理
* 4. 缓存统计
*
* <p>
* 底层缓存存储委托给 {@link CacheOperations} 接口支持通过配置切换 Redis / Caffeine 实现
* 请求级别的缓存键生成命中统计等逻辑保留在此服务中
* </p>
*
* @author 张翔
* @date 2026-03-26
*/
@@ -30,35 +26,33 @@ public class RequestCacheService {
private static final Logger logger = LoggerFactory.getLogger(RequestCacheService.class);
private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
private final CacheOperations cacheOperations;
private final Map<String, CacheStats> stats = new ConcurrentHashMap<>();
private boolean cacheEnabled = true;
private Duration defaultTtl = Duration.ofMinutes(5);
private int maxCacheSize = 10000;
public RequestCacheService(CacheOperations cacheOperations) {
this.cacheOperations = cacheOperations;
}
public Mono<String> get(ServerHttpRequest request) {
if (!cacheEnabled) {
return Mono.empty();
}
String cacheKey = generateCacheKey(request);
CacheEntry entry = cache.get(cacheKey);
if (entry == null) {
recordMiss(cacheKey);
return Mono.empty();
}
if (isExpired(entry)) {
cache.remove(cacheKey);
recordMiss(cacheKey);
return Mono.empty();
}
recordHit(cacheKey);
logger.debug("Cache hit for key: {}", cacheKey);
return Mono.just(entry.getValue());
return cacheOperations.get(cacheKey, String.class)
.doOnNext(value -> {
recordHit(cacheKey);
logger.debug("Cache hit for key: {}", cacheKey);
})
.switchIfEmpty(Mono.defer(() -> {
recordMiss(cacheKey);
return Mono.empty();
}));
}
public void put(ServerHttpRequest request, String response) {
@@ -67,37 +61,31 @@ public class RequestCacheService {
}
String cacheKey = generateCacheKey(request);
if (cache.size() >= maxCacheSize) {
evictOldestEntries();
}
CacheEntry entry = new CacheEntry(
response,
System.currentTimeMillis(),
defaultTtl.toMillis()
);
cache.put(cacheKey, entry);
logger.debug("Cached response for key: {}", cacheKey);
cacheOperations.setWithExpire(cacheKey, response, defaultTtl.toSeconds())
.doOnSuccess(result -> logger.debug("Cached response for key: {}", cacheKey))
.subscribe();
}
public void evict(ServerHttpRequest request) {
String cacheKey = generateCacheKey(request);
cache.remove(cacheKey);
logger.debug("Evicted cache for key: {}", cacheKey);
cacheOperations.delete(cacheKey)
.doOnSuccess(result -> logger.debug("Evicted cache for key: {}", cacheKey))
.subscribe();
}
public void evictByPattern(String pattern) {
cache.keySet().removeIf(key -> key.matches(pattern));
logger.info("Evicted cache entries matching pattern: {}", pattern);
cacheOperations.deleteByPattern(pattern)
.doOnSuccess(count -> logger.info("Evicted {} cache entries matching pattern: {}", count, pattern))
.subscribe();
}
public void clear() {
int size = cache.size();
cache.clear();
stats.clear();
logger.info("Cleared all cache entries. Removed {} entries", size);
cacheOperations.deleteByPattern(".*")
.doOnSuccess(count -> {
stats.clear();
logger.info("Cleared all cache entries. Removed {} entries", count);
})
.subscribe();
}
private String generateCacheKey(ServerHttpRequest request) {
@@ -106,7 +94,7 @@ public class RequestCacheService {
String query = request.getURI().getQuery();
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(method).append(":").append(path);
keyBuilder.append("gateway:").append(method).append(":").append(path);
if (query != null && !query.isEmpty()) {
keyBuilder.append("?").append(query);
@@ -115,25 +103,6 @@ public class RequestCacheService {
return keyBuilder.toString();
}
private boolean isExpired(CacheEntry entry) {
long currentTime = System.currentTimeMillis();
return (currentTime - entry.getCreatedAt()) > entry.getTtl();
}
private void evictOldestEntries() {
int entriesToRemove = maxCacheSize / 10;
cache.entrySet().stream()
.sorted((e1, e2) ->
Long.compare(e1.getValue().getCreatedAt(),
e2.getValue().getCreatedAt()))
.limit(entriesToRemove)
.map(Map.Entry::getKey)
.forEach(cache::remove);
logger.info("Evicted {} oldest cache entries", entriesToRemove);
}
private void recordHit(String cacheKey) {
stats.compute(cacheKey, (key, stat) -> {
if (stat == null) {
@@ -154,10 +123,6 @@ public class RequestCacheService {
});
}
public int getCacheSize() {
return cache.size();
}
public long getHitCount() {
return stats.values().stream()
.mapToLong(CacheStats::getHits)
@@ -197,30 +162,6 @@ public class RequestCacheService {
logger.info("Max cache size set to: {}", maxSize);
}
private static class CacheEntry {
private final String value;
private final long createdAt;
private final long ttl;
public CacheEntry(String value, long createdAt, long ttl) {
this.value = value;
this.createdAt = createdAt;
this.ttl = ttl;
}
public String getValue() {
return value;
}
public long getCreatedAt() {
return createdAt;
}
public long getTtl() {
return ttl;
}
}
private static class CacheStats {
private long hits = 0;
private long misses = 0;
@@ -241,4 +182,4 @@ public class RequestCacheService {
return misses;
}
}
}
}
@@ -64,6 +64,11 @@ rate:
limit-refresh-period: ${RATE_LIMIT_USER_PERIOD:1s}
timeout-duration: ${RATE_LIMIT_USER_TIMEOUT:0}
# 缓存配置(网关使用本地 Caffeine 缓存,无需 Redis
gym:
cache:
type: caffeine
signature:
enabled: ${SIGNATURE_ENABLED:true}
secret: ${SIGNATURE_SECRET:NovalonManageSystemSecretKey2026}
@@ -1,5 +1,8 @@
package cn.novalon.gym.manage.gateway.cache;
import cn.novalon.gym.manage.common.cache.CacheOperations;
import cn.novalon.gym.manage.common.cache.CaffeineCacheOperations;
import cn.novalon.gym.manage.common.cache.CacheProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -14,10 +17,14 @@ import static org.junit.jupiter.api.Assertions.*;
class RequestCacheServiceTest {
private RequestCacheService cacheService;
private CacheOperations cacheOperations;
@BeforeEach
void setUp() {
cacheService = new RequestCacheService();
CacheProperties cacheProperties = new CacheProperties();
cacheProperties.setType("caffeine");
cacheOperations = new CaffeineCacheOperations(cacheProperties);
cacheService = new RequestCacheService(cacheOperations);
}
@Test
@@ -52,7 +59,6 @@ class RequestCacheServiceTest {
String response = "{\"data\":\"test\"}";
cacheService.put(request, response);
cacheService.evict(request);
StepVerifier.create(cacheService.get(request))
@@ -71,9 +77,13 @@ class RequestCacheServiceTest {
cacheService.put(request1, "response1");
cacheService.put(request2, "response2");
cacheService.evictByPattern("GET:/api/test.*");
cacheService.evictByPattern(".*/api/test.*");
assertEquals(0, cacheService.getCacheSize());
// 验证缓存已清空
StepVerifier.create(cacheService.get(request1))
.verifyComplete();
StepVerifier.create(cacheService.get(request2))
.verifyComplete();
}
@Test
@@ -90,7 +100,11 @@ class RequestCacheServiceTest {
cacheService.clear();
assertEquals(0, cacheService.getCacheSize());
// 验证缓存已清空
StepVerifier.create(cacheService.get(request1))
.verifyComplete();
StepVerifier.create(cacheService.get(request2))
.verifyComplete();
}
@Test
@@ -105,8 +119,6 @@ class RequestCacheServiceTest {
StepVerifier.create(cacheService.get(request))
.verifyComplete();
assertEquals(0, cacheService.getCacheSize());
}
@Test
@@ -140,20 +152,6 @@ class RequestCacheServiceTest {
assertEquals(0.0, cacheService.getHitRate());
}
@Test
void testMaxCacheSize() {
cacheService.setMaxCacheSize(5);
for (int i = 0; i < 10; i++) {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test" + i)
.build();
cacheService.put(request, "response" + i);
}
assertTrue(cacheService.getCacheSize() <= 10);
}
@Test
void testCacheWithQueryParams() {
ServerHttpRequest request = MockServerHttpRequest
@@ -188,4 +186,4 @@ class RequestCacheServiceTest {
.expectNext("postResponse")
.verifyComplete();
}
}
}