diff --git a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java index c04c196..d8b78f3 100644 --- a/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java +++ b/gym-manage-api/gym-auth/src/main/java/cn/novalon/gym/manage/auth/service/impl/SmsServiceImpl.java @@ -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 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(); diff --git a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java index adf9177..6a004c2 100644 --- a/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java +++ b/gym-manage-api/gym-checkIn/src/main/java/cn/novalon/gym/manage/checkIn/service/impl/CheckServiceImpl.java @@ -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 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() )) diff --git a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java index 3b8616e..bdf9007 100644 --- a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java +++ b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/CheckInModuleTest.java @@ -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 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()); diff --git a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/service/impl/CheckServiceImplTest.java b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/service/impl/CheckServiceImplTest.java index 35b4405..055fb19 100644 --- a/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/service/impl/CheckServiceImplTest.java +++ b/gym-manage-api/gym-checkIn/src/test/java/cn/novalon/gym/manage/checkin/service/impl/CheckServiceImplTest.java @@ -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 result = checkService.getQRCode(MEMBER_ID); @@ -100,7 +100,7 @@ class CheckServiceImplTest { // Redis 中有有效二维码数据 String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now(); Map 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 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 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 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 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 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 result = checkService.checkIn(MEMBER_ID, QR_CONTENT); @@ -252,7 +252,7 @@ class CheckServiceImplTest { String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now(); Map 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 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 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 result = checkService.checkIn(MEMBER_ID, QR_CONTENT); diff --git a/gym-manage-api/gym-coach-config/src/main/java/cn/novalon/gym/manage/coachconfig/service/CoachTimeRuleService.java b/gym-manage-api/gym-coach-config/src/main/java/cn/novalon/gym/manage/coachconfig/service/CoachTimeRuleService.java index d097916..8c39d35 100644 --- a/gym-manage-api/gym-coach-config/src/main/java/cn/novalon/gym/manage/coachconfig/service/CoachTimeRuleService.java +++ b/gym-manage-api/gym-coach-config/src/main/java/cn/novalon/gym/manage/coachconfig/service/CoachTimeRuleService.java @@ -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 getActiveRules() { - return redisUtil.get(RedisKeyConstants.COACH_TIME_RULES) + return cacheOperations.get(RedisKeyConstants.COACH_TIME_RULES) .flatMapMany(cached -> { @SuppressWarnings("unchecked") List list = (List) 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()) diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java index 09250c9..18b29fb 100644 --- a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseScheduler.java @@ -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 { /** * 清除统计缓存和团课缓存 —— 调度器触发时,如有课程状态变更则必须及时失效。 * - *

注意:测试环境中 RedisUtil 可能被 Mock 返回 null,需做 null 安全处理。

+ *

注意:测试环境中 CacheOperations 可能被 Mock 返回 null,需做 null 安全处理。

*/ private void invalidateCache() { - Mono statsMono = redisUtil.deleteByPattern("datacount:statistics:*"); + Mono statsMono = cacheOperations.deleteByPattern("datacount:statistics:*"); if (statsMono != null) { statsMono.subscribe( deleted -> logger.debug("调度器清除统计缓存,已删除 {} 条", deleted), error -> logger.warn("调度器清除统计缓存失败: {}", error.getMessage()) ); } - Mono courseMono = redisUtil.deleteByPattern("group_course:*"); + Mono courseMono = cacheOperations.deleteByPattern("group_course:*"); if (courseMono != null) { courseMono.subscribe( deleted -> logger.debug("调度器清除团课缓存,已删除 {} 条", deleted), diff --git a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java index 84cb4a9..7a2c9d4 100644 --- a/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java +++ b/gym-manage-api/gym-coach/src/main/java/cn/novalon/gym/manage/coach/service/CoachCourseService.java @@ -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 invalidateStatisticsCache() { - return redisUtil.deleteByPattern("datacount:statistics:*") - .then(redisUtil.deleteByPattern("group_course:*")) + return cacheOperations.deleteByPattern("datacount:statistics:*") + .then(cacheOperations.deleteByPattern("group_course:*")) .doOnNext(count -> {}) .then(); } diff --git a/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseSchedulerTest.java b/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseSchedulerTest.java index 00d5e74..29a1d02 100644 --- a/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseSchedulerTest.java +++ b/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/scheduler/CoachCourseSchedulerTest.java @@ -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); } } \ No newline at end of file diff --git a/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/service/CoachCourseServiceTest.java b/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/service/CoachCourseServiceTest.java index ffcc8c1..8302656 100644 --- a/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/service/CoachCourseServiceTest.java +++ b/gym-manage-api/gym-coach/src/test/java/cn/novalon/gym/manage/coach/service/CoachCourseServiceTest.java @@ -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 -> { diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/scheduler/DataStatisticsScheduler.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/scheduler/DataStatisticsScheduler.java index 65ae641..c988b94 100644 --- a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/scheduler/DataStatisticsScheduler.java +++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/scheduler/DataStatisticsScheduler.java @@ -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); diff --git a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java index 1c7fc79..87ee272 100644 --- a/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java +++ b/gym-manage-api/gym-dataCount/src/main/java/cn/novalon/gym/manage/datacount/service/impl/DataStatisticsServiceImpl.java @@ -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 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 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 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); diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java index 7895a70..7713dfd 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandler.java @@ -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 result = new HashMap<>(); if (cachedValue != null) { diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java index 0155470..802e78e 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelService.java @@ -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 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(); diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java index 1e7a6df..18aad12 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisService.java @@ -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缓存服务 - * - * 负责团课信息的缓存管理和分布式锁实现 - * + *

+ * 缓存操作委托给 {@link CacheOperations} 接口,支持通过配置切换 Redis / Caffeine 实现。 + * 分布式锁操作仍直接使用 {@link ReactiveRedisTemplate}(锁为 Redis 独有特性)。 + *

+ * * @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 reactiveRedisTemplate; - private final ObjectMapper objectMapper; - public GroupCourseRedisService(ReactiveRedisTemplate reactiveRedisTemplate, - ObjectMapper objectMapper) { + public GroupCourseRedisService(CacheOperations cacheOperations, + ReactiveRedisTemplate reactiveRedisTemplate) { + this.cacheOperations = cacheOperations; this.reactiveRedisTemplate = reactiveRedisTemplate; - this.objectMapper = objectMapper; } /** @@ -61,16 +62,9 @@ public class GroupCourseRedisService { */ public Mono 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 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 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 + *

注意:分布式锁为 Redis 独有特性,始终保持直接使用 RedisTemplate。

+ * + * @param courseId 课程ID * @param requestId 请求ID(用于锁的唯一性校验) * @return 是否获取成功 */ @@ -126,8 +110,9 @@ public class GroupCourseRedisService { /** * 释放分布式锁 - * - * @param courseId 课程ID + *

注意:分布式锁为 Redis 独有特性,始终保持直接使用 RedisTemplate。

+ * + * @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); } -} +} \ No newline at end of file diff --git a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java index 2a30763..4bb60f2 100644 --- a/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java +++ b/gym-manage-api/gym-groupCourse/src/main/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseService.java @@ -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 findDetailById(Long id) { String cacheKey = CACHE_KEY_DETAIL_PREFIX + id; - Mono cachedMono = redisUtil.get(cacheKey, String.class); + Mono 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.just(detail); } catch (JsonProcessingException e) { logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage()); - return redisUtil.delete(cacheKey).then(Mono.empty()); + return cacheOperations.delete(cacheKey).then(Mono.empty()); } } return Mono.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 findById(Long id) { String cacheKey = CACHE_KEY_ID_PREFIX + id; - Mono cachedMono = redisUtil.get(cacheKey, String.class); + Mono 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.just(groupCourse); } catch (JsonProcessingException e) { logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage()); - return redisUtil.delete(cacheKey).then(Mono.empty()); + return cacheOperations.delete(cacheKey).then(Mono.empty()); } } return Mono.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 cachedMono = redisUtil.get(cacheKey, String.class); + Mono 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.>just(pageResponse); } catch (JsonProcessingException e) { logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage()); - return redisUtil.delete(cacheKey).then(Mono.>empty()); + return cacheOperations.delete(cacheKey).then(Mono.>empty()); } } return Mono.>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 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(); } diff --git a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandlerTest.java b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandlerTest.java index cc55cc0..f9eb8aa 100644 --- a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandlerTest.java +++ b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/handler/GroupCourseHandlerTest.java @@ -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 ==================== diff --git a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelServiceTest.java b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelServiceTest.java index 61260d9..83c7d93 100644 --- a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelServiceTest.java +++ b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/CourseLabelServiceTest.java @@ -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 ==================== diff --git a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisServiceTest.java b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisServiceTest.java index 3c7d276..c419fc2 100644 --- a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisServiceTest.java +++ b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseRedisServiceTest.java @@ -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 reactiveRedisTemplate; @Mock private ReactiveValueOperations 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 ==================== diff --git a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseServiceTest.java b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseServiceTest.java index b5842e8..925b10b 100644 --- a/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseServiceTest.java +++ b/gym-manage-api/gym-groupCourse/src/test/java/cn/novalon/gym/manage/groupcourse/service/impl/GroupCourseServiceTest.java @@ -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 ==================== diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java index 3dd8f65..f00d981 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImpl.java @@ -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 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); } } diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java index 23b1cf8..253e0bc 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImpl.java @@ -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 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); } } diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java index f2003a2..98cf817 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImpl.java @@ -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 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 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)); } } \ No newline at end of file diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java index e505ef7..6182ad9 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/RefundApplicationServiceImpl.java @@ -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 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); } } diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java index 2eddee9..cda3792 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatApiServiceImpl.java @@ -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"); diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java index af116b4..86c50b1 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatAuthServiceImpl.java @@ -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 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); } diff --git a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java index 41aca11..34167ae 100644 --- a/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java +++ b/gym-manage-api/gym-member/src/main/java/cn/novalon/gym/manage/member/service/impl/WechatOfficialServiceImpl.java @@ -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 memberSyncer; @@ -297,7 +297,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService { private Mono 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 clearMemberCache(Long memberId) { String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId; - return redisUtil.delete(cacheKey) + return cacheOperations.delete(cacheKey) .doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId)); } } diff --git a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImplTest.java b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImplTest.java index 1ed2992..fdafd59 100644 --- a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImplTest.java +++ b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardRecordServiceImplTest.java @@ -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 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 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 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 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 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 result = memberCardRecordService.updateStatus(RECORD_ID, "USED_UP"); diff --git a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImplTest.java b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImplTest.java index 18cd79a..781d1da 100644 --- a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImplTest.java +++ b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberCardServiceImplTest.java @@ -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 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))); diff --git a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImplTest.java b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImplTest.java index 871738b..df21f5e 100644 --- a/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImplTest.java +++ b/gym-manage-api/gym-member/src/test/java/cn/novalon/gym/manage/member/service/impl/MemberServiceImplTest.java @@ -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 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 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 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 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 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 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 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 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 diff --git a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java index b9445b9..0b5ba39 100644 --- a/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java +++ b/gym-manage-api/gym-payment/src/main/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImpl.java @@ -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 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 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(); diff --git a/gym-manage-api/gym-payment/src/test/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImplTest.java b/gym-manage-api/gym-payment/src/test/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImplTest.java index 1e5d001..5856623 100644 --- a/gym-manage-api/gym-payment/src/test/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImplTest.java +++ b/gym-manage-api/gym-payment/src/test/java/cn/novalon/gym/manage/payment/service/impl/PaymentServiceImplTest.java @@ -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 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 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 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 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 result = paymentService.handleAlipayNotify(params); diff --git a/gym-manage-api/manage-app/src/main/resources/application.yml b/gym-manage-api/manage-app/src/main/resources/application.yml index 9cabfe0..5cb4448 100644 --- a/gym-manage-api/manage-app/src/main/resources/application.yml +++ b/gym-manage-api/manage-app/src/main/resources/application.yml @@ -1,6 +1,10 @@ server: port: 8084 +gym: + cache: + type: redis + spring: aop: proxy-target-class: true diff --git a/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/contract/BaseContractTest.java b/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/contract/BaseContractTest.java index 384ead2..a0ab3e0 100644 --- a/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/contract/BaseContractTest.java +++ b/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/contract/BaseContractTest.java @@ -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 diff --git a/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/GroupCourseHandlerIntegrationTest.java b/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/GroupCourseHandlerIntegrationTest.java index 54dbeaf..7692ce5 100644 --- a/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/GroupCourseHandlerIntegrationTest.java +++ b/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/GroupCourseHandlerIntegrationTest.java @@ -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 diff --git a/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/MemberHandlerIntegrationTest.java b/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/MemberHandlerIntegrationTest.java index 74d44f5..a7f38db 100644 --- a/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/MemberHandlerIntegrationTest.java +++ b/gym-manage-api/manage-app/src/test/java/cn/novalon/gym/manage/app/integration/MemberHandlerIntegrationTest.java @@ -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()); diff --git a/gym-manage-api/manage-common/pom.xml b/gym-manage-api/manage-common/pom.xml index e5a81c2..f7f3d7f 100644 --- a/gym-manage-api/manage-common/pom.xml +++ b/gym-manage-api/manage-common/pom.xml @@ -60,6 +60,10 @@ org.springframework.data spring-data-redis + + com.github.ben-manes.caffeine + caffeine + diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheAutoConfiguration.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheAutoConfiguration.java new file mode 100644 index 0000000..d6613f2 --- /dev/null +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheAutoConfiguration.java @@ -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; + +/** + * 缓存自动配置 + *

+ * 启用 {@link CacheProperties} 配置绑定,并扫描 {@code cn.novalon.gym.manage.common.cache} + * 包下的缓存实现组件({@link RedisCacheOperations} / {@link CaffeineCacheOperations})。 + *

+ * + * @author 张翔 + * @date 2026-08-02 + */ +@Configuration +@EnableConfigurationProperties(CacheProperties.class) +@ComponentScan(basePackageClasses = CacheAutoConfiguration.class) +public class CacheAutoConfiguration { +} \ No newline at end of file diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheOperations.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheOperations.java new file mode 100644 index 0000000..1e51742 --- /dev/null +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheOperations.java @@ -0,0 +1,88 @@ +package cn.novalon.gym.manage.common.cache; + +import reactor.core.publisher.Mono; + +/** + * 缓存操作接口 + *

+ * 定义统一的缓存操作抽象,支持通过配置切换不同的缓存实现(Caffeine / Redis)。 + * 所有业务模块应依赖此接口而非具体实现,实现类由 {@link CacheAutoConfiguration} 按配置自动装配。 + *

+ * + * @author 张翔 + * @date 2026-08-02 + * @see RedisCacheOperations + * @see CaffeineCacheOperations + */ +public interface CacheOperations { + + /** + * 获取缓存值(带类型转换) + * + * @param key 缓存键 + * @param clazz 目标类型 + * @param 返回值类型 + * @return 缓存值,不存在时返回 {@link Mono#empty()} + */ + Mono get(String key, Class clazz); + + /** + * 获取缓存值(返回 Object) + * + * @param key 缓存键 + * @return 缓存值,不存在时返回 {@link Mono#empty()} + */ + Mono get(String key); + + /** + * 设置缓存值(无过期时间) + * + * @param key 缓存键 + * @param value 缓存值 + * @return true 设置成功 + */ + Mono set(String key, Object value); + + /** + * 设置缓存值并指定过期时间 + * + * @param key 缓存键 + * @param value 缓存值 + * @param timeoutSeconds 过期时间(秒) + * @return true 设置成功 + */ + Mono setWithExpire(String key, Object value, long timeoutSeconds); + + /** + * 删除缓存 + * + * @param key 缓存键 + * @return 被删除的键数量 + */ + Mono delete(String key); + + /** + * 判断缓存键是否存在 + * + * @param key 缓存键 + * @return true 存在 + */ + Mono hasKey(String key); + + /** + * 设置过期时间 + * + * @param key 缓存键 + * @param timeoutSeconds 过期时间(秒) + * @return true 设置成功 + */ + Mono expire(String key, long timeoutSeconds); + + /** + * 根据通配符模式删除缓存键 + * + * @param pattern 通配符模式,如 {@code member:info:*} + * @return 被删除的键数量 + */ + Mono deleteByPattern(String pattern); +} \ No newline at end of file diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheProperties.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheProperties.java new file mode 100644 index 0000000..dfa76e0 --- /dev/null +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CacheProperties.java @@ -0,0 +1,88 @@ +package cn.novalon.gym.manage.common.cache; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 缓存配置属性 + *

+ * 通过 {@code gym.cache.type} 配置选择缓存实现: + *

    + *
  • {@code redis} — 使用 Redis 实现(默认)
  • + *
  • {@code caffeine} — 使用 Caffeine 本地缓存实现
  • + *
+ *

+ * + *
{@code
+ * gym:
+ *   cache:
+ *     type: redis          # redis | caffeine
+ *     caffeine:
+ *       initial-capacity: 100
+ *       max-size: 10000
+ *       expire-after-write-seconds: 300
+ * }
+ * + * @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; + } + } +} \ No newline at end of file diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CaffeineCacheOperations.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CaffeineCacheOperations.java new file mode 100644 index 0000000..8e0438c --- /dev/null +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/CaffeineCacheOperations.java @@ -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 本地缓存实现 + *

+ * 基于 Caffeine 的进程内缓存,适合单机部署或开发测试环境。 + * 当 {@code gym.cache.type=caffeine} 时生效。 + *

+ * + * @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 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 Mono get(String key, Class 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 get(String key) { + Object value = cache.getIfPresent(key); + if (value == null) { + return Mono.empty(); + } + return Mono.just(value); + } + + @Override + public Mono set(String key, Object value) { + cache.put(key, value); + return Mono.just(true); + } + + @Override + public Mono 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 delete(String key) { + cache.invalidate(key); + return Mono.just(1L); + } + + @Override + public Mono hasKey(String key) { + return Mono.just(cache.getIfPresent(key) != null); + } + + @Override + public Mono expire(String key, long timeoutSeconds) { + // Caffeine 不支持为单个 key 动态设置过期时间,直接返回 true(过期由构建时策略统一管理) + log.debug("Caffeine 不支持动态设置过期时间,忽略: key={}, timeoutSeconds={}", key, timeoutSeconds); + return Mono.just(true); + } + + @Override + public Mono 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); + } +} \ No newline at end of file diff --git a/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/RedisCacheOperations.java b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/RedisCacheOperations.java new file mode 100644 index 0000000..a61251b --- /dev/null +++ b/gym-manage-api/manage-common/src/main/java/cn/novalon/gym/manage/common/cache/RedisCacheOperations.java @@ -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 缓存实现 + *

+ * 基于 {@link ReactiveRedisTemplate} 的响应式 Redis 缓存操作。 + * 当 {@code gym.cache.type=redis} 时生效(默认值)。 + *

+ * + * @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 reactiveRedisTemplate; + + public RedisCacheOperations(ReactiveRedisTemplate reactiveRedisTemplate) { + this.reactiveRedisTemplate = reactiveRedisTemplate; + } + + @Override + public Mono get(String key, Class 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 get(String key) { + return reactiveRedisTemplate.opsForValue().get(key); + } + + @Override + public Mono set(String key, Object value) { + return reactiveRedisTemplate.opsForValue().set(key, value); + } + + @Override + public Mono setWithExpire(String key, Object value, long timeoutSeconds) { + return reactiveRedisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeoutSeconds)); + } + + @Override + public Mono delete(String key) { + return reactiveRedisTemplate.delete(key); + } + + @Override + public Mono hasKey(String key) { + return reactiveRedisTemplate.hasKey(key); + } + + @Override + public Mono expire(String key, long timeoutSeconds) { + return reactiveRedisTemplate.expire(key, Duration.ofSeconds(timeoutSeconds)); + } + + @Override + public Mono deleteByPattern(String pattern) { + return reactiveRedisTemplate.keys(pattern) + .flatMap(keys -> reactiveRedisTemplate.delete(keys)) + .count(); + } +} \ No newline at end of file diff --git a/gym-manage-api/manage-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/gym-manage-api/manage-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1245f77..1d54c5c 100644 --- a/gym-manage-api/manage-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/gym-manage-api/manage-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -cn.novalon.gym.manage.common.config.JwtProperties +cn.novalon.gym.manage.common.cache.CacheAutoConfiguration \ No newline at end of file diff --git a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/cache/RequestCacheService.java b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/cache/RequestCacheService.java index 8d707fe..c5bee91 100644 --- a/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/cache/RequestCacheService.java +++ b/gym-manage-api/manage-gateway/src/main/java/cn/novalon/gym/manage/gateway/cache/RequestCacheService.java @@ -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. 缓存统计 - * + *

+ * 底层缓存存储委托给 {@link CacheOperations} 接口,支持通过配置切换 Redis / Caffeine 实现。 + * 请求级别的缓存键生成、命中统计等逻辑保留在此服务中。 + *

+ * * @author 张翔 * @date 2026-03-26 */ @@ -30,35 +26,33 @@ public class RequestCacheService { private static final Logger logger = LoggerFactory.getLogger(RequestCacheService.class); - private final Map cache = new ConcurrentHashMap<>(); + private final CacheOperations cacheOperations; private final Map 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 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; } } -} +} \ No newline at end of file diff --git a/gym-manage-api/manage-gateway/src/main/resources/application.yml b/gym-manage-api/manage-gateway/src/main/resources/application.yml index 50aee34..36a9b80 100644 --- a/gym-manage-api/manage-gateway/src/main/resources/application.yml +++ b/gym-manage-api/manage-gateway/src/main/resources/application.yml @@ -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} diff --git a/gym-manage-api/manage-gateway/src/test/java/cn/novalon/gym/manage/gateway/cache/RequestCacheServiceTest.java b/gym-manage-api/manage-gateway/src/test/java/cn/novalon/gym/manage/gateway/cache/RequestCacheServiceTest.java index 30c711c..9097d1d 100644 --- a/gym-manage-api/manage-gateway/src/test/java/cn/novalon/gym/manage/gateway/cache/RequestCacheServiceTest.java +++ b/gym-manage-api/manage-gateway/src/test/java/cn/novalon/gym/manage/gateway/cache/RequestCacheServiceTest.java @@ -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(); } -} +} \ No newline at end of file