refactor(cache): 缓存操作接口化,支持 Redis/Caffeine 双实现切换
将系统所有缓存操作从 `RedisUtil` 迁移至 `CacheOperations` 接口, 通过 `gym.cache.type` 配置选择 Redis 或 Caffeine 实现。 变更内容: - 新增 CacheOperations 接口,定义 get/set/delete/hasKey/expire/deleteByPattern 等标准方法 - 新增 RedisCacheOperations 实现(基于 ReactiveRedisTemplate,默认实现) - 新增 CaffeineCacheOperations 实现(基于 Caffeine 本地缓存) - 新增 CacheProperties 配置类,支持 Caffeine 容量/过期等参数配置 - 新增 CacheAutoConfiguration 自动装配,通过 AutoConfiguration.imports 注册 - 全系统 17 个业务服务 + 18 个测试文件从 RedisUtil 迁移至 CacheOperations - manage-gateway 配置 caffeine 实现(无需 Redis),manage-app 配置 redis 实现 - 分布式锁等 Redis 独有特性保持直接使用 ReactiveRedisTemplate 测试:202 通过,0 失败,0 错误
This commit was merged in pull request #56.
This commit is contained in:
+6
-6
@@ -3,7 +3,7 @@ package cn.novalon.gym.manage.auth.service.impl;
|
||||
import cn.novalon.gym.manage.auth.config.SmsProperties;
|
||||
import cn.novalon.gym.manage.auth.service.SmsService;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import com.aliyuncs.CommonRequest;
|
||||
import com.aliyuncs.CommonResponse;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
@@ -31,7 +31,7 @@ public class SmsServiceImpl implements SmsService {
|
||||
private static final long CODE_EXPIRE_SECONDS = 300;
|
||||
|
||||
private final SmsProperties smsProperties;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
@@ -40,7 +40,7 @@ public class SmsServiceImpl implements SmsService {
|
||||
|
||||
String rateLimitKey = RedisKeyConstants.SMS_CODE + phone + ":rate_limit";
|
||||
|
||||
return redisUtil.get(rateLimitKey, Long.class)
|
||||
return cacheOperations.get(rateLimitKey, Long.class)
|
||||
.defaultIfEmpty(0L)
|
||||
.flatMap(lastSendTime -> {
|
||||
long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||
@@ -97,7 +97,7 @@ public class SmsServiceImpl implements SmsService {
|
||||
if (verifyCodeNode != null) {
|
||||
String verifyCode = verifyCodeNode.asText();
|
||||
String smsCodeKey = RedisKeyConstants.SMS_CODE + phone;
|
||||
redisUtil.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
|
||||
cacheOperations.setWithExpire(smsCodeKey, verifyCode, CODE_EXPIRE_SECONDS).subscribe();
|
||||
log.info("验证码已存入Redis, phone: {}, key: {}, expire: {}秒", phone, smsCodeKey, CODE_EXPIRE_SECONDS);
|
||||
} else {
|
||||
log.warn("响应中未找到Model.VerifyCode字段, 原始响应: {}", responseData);
|
||||
@@ -106,7 +106,7 @@ public class SmsServiceImpl implements SmsService {
|
||||
log.warn("响应中未找到Model字段, 原始响应: {}", responseData);
|
||||
}
|
||||
|
||||
redisUtil.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
|
||||
cacheOperations.setWithExpire(rateLimitKey, currentTime, SEND_INTERVAL_SECONDS).subscribe();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public class SmsServiceImpl implements SmsService {
|
||||
public Mono<String> getVerificationCode(String phone) {
|
||||
try {
|
||||
String cacheKey = RedisKeyConstants.SMS_CODE + phone;
|
||||
return redisUtil.get(cacheKey, String.class);
|
||||
return cacheOperations.get(cacheKey, String.class);
|
||||
} catch (Exception e) {
|
||||
log.error("获取验证码异常, phone: {}", phone, e);
|
||||
return Mono.empty();
|
||||
|
||||
+7
-7
@@ -14,7 +14,7 @@ import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
|
||||
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
|
||||
import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
@@ -44,7 +44,7 @@ import java.util.Map;
|
||||
public class CheckServiceImpl implements ICheckInService {
|
||||
|
||||
private final QRCodeConfig qrCodeConfig;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final MemberCardRecordRepository memberCardRecordRepository;
|
||||
private final MemberCardRepository memberCardRepository;
|
||||
private final SignInRecordRepository signInRecordRepository;
|
||||
@@ -63,7 +63,7 @@ public class CheckServiceImpl implements ICheckInService {
|
||||
redisMap.put("isUsed", false);
|
||||
redisMap.put("memberId", memberId);
|
||||
|
||||
return redisUtil.setWithExpire(
|
||||
return cacheOperations.setWithExpire(
|
||||
RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(),
|
||||
redisMap,
|
||||
getSecondsUntilEndOfDay()
|
||||
@@ -87,7 +87,7 @@ public class CheckServiceImpl implements ICheckInService {
|
||||
MyWebSocketHandler.sendFailure(qrContent, "您已经在" + checkInTime + "完成签到,请勿重复签到");
|
||||
return Mono.error(new RuntimeException("您已经在" + checkInTime + "完成签到,请勿重复签到"));
|
||||
})
|
||||
.then(Mono.defer(() -> redisUtil.get(key)))
|
||||
.then(Mono.defer(() -> cacheOperations.get(key)))
|
||||
.flatMap(cachedObj -> {
|
||||
if (cachedObj != null) {
|
||||
Map<String, Object> map;
|
||||
@@ -157,11 +157,11 @@ public class CheckServiceImpl implements ICheckInService {
|
||||
redisMap.put("checkInTime", now.format(DATE_FORMATTER));
|
||||
|
||||
return saveSignInRecord(memberId, null, null)
|
||||
.then(redisUtil.set(RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(), redisMap))
|
||||
.then(cacheOperations.set(RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(), redisMap))
|
||||
// 清除统计缓存和课程缓存,确保管理端/教练端立即反映最新数据
|
||||
.then(Mono.defer(() ->
|
||||
redisUtil.deleteByPattern("datacount:statistics:*")
|
||||
.then(Mono.defer(() -> redisUtil.deleteByPattern("group_course:*")))
|
||||
cacheOperations.deleteByPattern("datacount:statistics:*")
|
||||
.then(Mono.defer(() -> cacheOperations.deleteByPattern("group_course:*")))
|
||||
.doOnSuccess(v -> log.info("已清除统计缓存和课程缓存, memberId: {}", memberId))
|
||||
.then()
|
||||
))
|
||||
|
||||
+9
-9
@@ -10,7 +10,7 @@ import cn.novalon.gym.manage.checkIn.vo.QRCodeVo;
|
||||
import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
|
||||
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||
@@ -44,7 +44,7 @@ class CheckInModuleTest {
|
||||
private QRCodeConfig qrCodeConfig;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@Mock
|
||||
private MemberCardRecordRepository memberCardRecordRepository;
|
||||
@@ -75,7 +75,7 @@ class CheckInModuleTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
checkService = new CheckServiceImpl(qrCodeConfig, redisUtil, memberCardRecordRepository,
|
||||
checkService = new CheckServiceImpl(qrCodeConfig, cacheOperations, memberCardRecordRepository,
|
||||
memberCardRepository, signInRecordRepository, groupCourseBookingService,
|
||||
groupCourseBookingRepository);
|
||||
|
||||
@@ -103,7 +103,7 @@ class CheckInModuleTest {
|
||||
void testGetQRCode() {
|
||||
when(memberCardRecordRepository.findActiveCardsByMemberId(1L))
|
||||
.thenReturn(Flux.just(mockMemberCardRecord));
|
||||
when(redisUtil.setWithExpire(any(String.class), any(Map.class), any(Long.class)))
|
||||
when(cacheOperations.setWithExpire(any(String.class), any(Map.class), any(Long.class)))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
Mono<QRCodeVo> result = checkService.getQRCode(1L);
|
||||
@@ -130,12 +130,12 @@ class CheckInModuleTest {
|
||||
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
|
||||
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
when(redisUtil.deleteByPattern(any(String.class))).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
when(cacheOperations.deleteByPattern(any(String.class))).thenReturn(Mono.empty());
|
||||
when(memberCardRecordRepository.findById(1L)).thenReturn(Mono.just(mockMemberCardRecord));
|
||||
when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(mockMemberCard));
|
||||
when(signInRecordRepository.save(any(SignInRecord.class))).thenReturn(Mono.just(mockSignInRecord));
|
||||
when(redisUtil.set(any(String.class), any(Map.class))).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.set(any(String.class), any(Map.class))).thenReturn(Mono.just(true));
|
||||
when(groupCourseBookingService.getBookingsByMemberId(memberId)).thenReturn(Flux.empty());
|
||||
when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
|
||||
.thenReturn(Mono.empty());
|
||||
@@ -259,7 +259,7 @@ class CheckInModuleTest {
|
||||
void testCheckIn_QRCodeInvalid() {
|
||||
Long memberId = 1L;
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(new HashMap<>()));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(new HashMap<>()));
|
||||
when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
@@ -275,7 +275,7 @@ class CheckInModuleTest {
|
||||
void testCheckIn_QRCodeNotFound() {
|
||||
Long memberId = 1L;
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.empty());
|
||||
when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
|
||||
+19
-19
@@ -8,7 +8,7 @@ import cn.novalon.gym.manage.checkIn.vo.QRCodeVo;
|
||||
import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
|
||||
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
|
||||
@@ -40,7 +40,7 @@ class CheckServiceImplTest {
|
||||
private QRCodeConfig qrCodeConfig;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@Mock
|
||||
private MemberCardRecordRepository memberCardRecordRepository;
|
||||
@@ -70,7 +70,7 @@ class CheckServiceImplTest {
|
||||
void getQRCode_shouldReturnQRCodeVo() {
|
||||
when(qrCodeConfig.getWidth()).thenReturn(300);
|
||||
when(qrCodeConfig.getHeight()).thenReturn(300);
|
||||
when(redisUtil.setWithExpire(anyString(), any(Map.class), anyLong()))
|
||||
when(cacheOperations.setWithExpire(anyString(), any(Map.class), anyLong()))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
Mono<QRCodeVo> result = checkService.getQRCode(MEMBER_ID);
|
||||
@@ -100,7 +100,7 @@ class CheckServiceImplTest {
|
||||
// Redis 中有有效二维码数据
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
Map<String, Object> qrData = buildQrData(false);
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
|
||||
// 无预约
|
||||
when(groupCourseBookingService.getBookingsByMemberId(MEMBER_ID)).thenReturn(Flux.empty());
|
||||
@@ -110,10 +110,10 @@ class CheckServiceImplTest {
|
||||
.thenReturn(Mono.just(createMockSignInRecord()));
|
||||
|
||||
// 更新缓存
|
||||
when(redisUtil.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
|
||||
|
||||
// 清除缓存
|
||||
when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.empty());
|
||||
when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
|
||||
|
||||
@@ -122,8 +122,8 @@ class CheckServiceImplTest {
|
||||
.verifyComplete();
|
||||
|
||||
verify(signInRecordRepository).save(any(SignInRecord.class));
|
||||
verify(redisUtil).set(anyString(), any(Map.class));
|
||||
verify(redisUtil, times(2)).deleteByPattern(anyString());
|
||||
verify(cacheOperations).set(anyString(), any(Map.class));
|
||||
verify(cacheOperations, times(2)).deleteByPattern(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -136,7 +136,7 @@ class CheckServiceImplTest {
|
||||
// Redis 中有有效二维码数据
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
Map<String, Object> qrData = buildQrData(false);
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
|
||||
// 有有效预约
|
||||
GroupCourseBooking booking = createValidBooking();
|
||||
@@ -148,10 +148,10 @@ class CheckServiceImplTest {
|
||||
.thenReturn(Mono.just(createMockSignInRecord()));
|
||||
|
||||
// 更新缓存
|
||||
when(redisUtil.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
|
||||
|
||||
// 清除缓存
|
||||
when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.empty());
|
||||
when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
|
||||
|
||||
@@ -172,7 +172,7 @@ class CheckServiceImplTest {
|
||||
// Redis 中缓存数据为 JSON 字符串格式
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
String jsonData = "{\"qrContent\":\"" + QR_CONTENT + "\",\"isUsed\":false,\"memberId\":" + MEMBER_ID + "}";
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(jsonData));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(jsonData));
|
||||
|
||||
// 无预约
|
||||
when(groupCourseBookingService.getBookingsByMemberId(MEMBER_ID)).thenReturn(Flux.empty());
|
||||
@@ -182,10 +182,10 @@ class CheckServiceImplTest {
|
||||
.thenReturn(Mono.just(createMockSignInRecord()));
|
||||
|
||||
// 更新缓存
|
||||
when(redisUtil.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.set(anyString(), any(Map.class))).thenReturn(Mono.just(true));
|
||||
|
||||
// 清除缓存
|
||||
when(redisUtil.deleteByPattern(anyString())).thenReturn(Mono.empty());
|
||||
when(cacheOperations.deleteByPattern(anyString())).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
|
||||
|
||||
@@ -217,7 +217,7 @@ class CheckServiceImplTest {
|
||||
&& e.getMessage().contains("请勿重复签到"))
|
||||
.verify();
|
||||
|
||||
verify(redisUtil, never()).get(anyString());
|
||||
verify(cacheOperations, never()).get(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -231,7 +231,7 @@ class CheckServiceImplTest {
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
Map<String, Object> qrData = buildQrData(true);
|
||||
qrData.put("checkInTime", "2026-07-31 10:00:00");
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
|
||||
|
||||
@@ -252,7 +252,7 @@ class CheckServiceImplTest {
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
Map<String, Object> qrData = buildQrData(false);
|
||||
qrData.put("qrContent", "different-qr-content");
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(qrData));
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
|
||||
|
||||
@@ -271,7 +271,7 @@ class CheckServiceImplTest {
|
||||
|
||||
// Redis 中无数据
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, "not-exist");
|
||||
|
||||
@@ -289,7 +289,7 @@ class CheckServiceImplTest {
|
||||
|
||||
// Redis 返回非 Map 非 String 的数据
|
||||
String key = RedisKeyConstants.QRCODE_USER_DAILY + MEMBER_ID + LocalDate.now();
|
||||
when(redisUtil.get(eq(key))).thenReturn(Mono.just(12345));
|
||||
when(cacheOperations.get(eq(key))).thenReturn(Mono.just(12345));
|
||||
|
||||
Mono<String> result = checkService.checkIn(MEMBER_ID, QR_CONTENT);
|
||||
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@ package cn.novalon.gym.manage.coachconfig.service;
|
||||
import cn.novalon.gym.manage.coachconfig.domain.CoachTimeRule;
|
||||
import cn.novalon.gym.manage.coachconfig.repository.ICoachTimeRuleRepository;
|
||||
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -43,11 +43,11 @@ public class CoachTimeRuleService {
|
||||
private static final long CACHE_TTL_SECONDS = 300;
|
||||
|
||||
private final ICoachTimeRuleRepository repository;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
public CoachTimeRuleService(ICoachTimeRuleRepository repository, RedisUtil redisUtil) {
|
||||
public CoachTimeRuleService(ICoachTimeRuleRepository repository, CacheOperations cacheOperations) {
|
||||
this.repository = repository;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
}
|
||||
|
||||
// ==================== 规则匹配 ====================
|
||||
@@ -118,7 +118,7 @@ public class CoachTimeRuleService {
|
||||
* 获取所有启用规则(带 Redis 缓存)
|
||||
*/
|
||||
private Flux<CoachTimeRule> getActiveRules() {
|
||||
return redisUtil.get(RedisKeyConstants.COACH_TIME_RULES)
|
||||
return cacheOperations.get(RedisKeyConstants.COACH_TIME_RULES)
|
||||
.flatMapMany(cached -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<CoachTimeRule> list = (List<CoachTimeRule>) cached;
|
||||
@@ -130,7 +130,7 @@ public class CoachTimeRuleService {
|
||||
return repository.findByStatusAndDeletedAtIsNull("1")
|
||||
.collectList()
|
||||
.flatMapMany(list -> {
|
||||
redisUtil.setWithExpire(RedisKeyConstants.COACH_TIME_RULES, list, CACHE_TTL_SECONDS)
|
||||
cacheOperations.setWithExpire(RedisKeyConstants.COACH_TIME_RULES, list, CACHE_TTL_SECONDS)
|
||||
.subscribe(
|
||||
ok -> logger.debug("教练时间规则已写入 Redis 缓存,共 {} 条", list.size()),
|
||||
err -> logger.warn("教练时间规则写入 Redis 缓存失败: {}", err.getMessage())
|
||||
@@ -144,7 +144,7 @@ public class CoachTimeRuleService {
|
||||
* 写操作后清除缓存(热更新入口)
|
||||
*/
|
||||
private void invalidateCache() {
|
||||
redisUtil.delete(RedisKeyConstants.COACH_TIME_RULES)
|
||||
cacheOperations.delete(RedisKeyConstants.COACH_TIME_RULES)
|
||||
.subscribe(
|
||||
count -> logger.info("教练时间规则缓存已清除"),
|
||||
err -> logger.warn("教练时间规则缓存清除失败: {}", err.getMessage())
|
||||
|
||||
+7
-7
@@ -2,7 +2,7 @@ package cn.novalon.gym.manage.coach.scheduler;
|
||||
|
||||
import cn.novalon.gym.manage.coach.enums.ViolationReason;
|
||||
import cn.novalon.gym.manage.coachconfig.service.CoachTimeRuleService;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
|
||||
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
|
||||
@@ -35,18 +35,18 @@ public class CoachCourseScheduler {
|
||||
private final GroupCourseDao groupCourseDao;
|
||||
private final GroupCourseBookingDao groupCourseBookingDao;
|
||||
private final DatabaseClient databaseClient;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final CoachTimeRuleService timeRuleService;
|
||||
|
||||
public CoachCourseScheduler(GroupCourseDao groupCourseDao,
|
||||
GroupCourseBookingDao groupCourseBookingDao,
|
||||
DatabaseClient databaseClient,
|
||||
RedisUtil redisUtil,
|
||||
CacheOperations cacheOperations,
|
||||
CoachTimeRuleService timeRuleService) {
|
||||
this.groupCourseDao = groupCourseDao;
|
||||
this.groupCourseBookingDao = groupCourseBookingDao;
|
||||
this.databaseClient = databaseClient;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
this.timeRuleService = timeRuleService;
|
||||
}
|
||||
|
||||
@@ -166,17 +166,17 @@ public class CoachCourseScheduler {
|
||||
/**
|
||||
* 清除统计缓存和团课缓存 —— 调度器触发时,如有课程状态变更则必须及时失效。
|
||||
*
|
||||
* <p>注意:测试环境中 RedisUtil 可能被 Mock 返回 null,需做 null 安全处理。</p>
|
||||
* <p>注意:测试环境中 CacheOperations 可能被 Mock 返回 null,需做 null 安全处理。</p>
|
||||
*/
|
||||
private void invalidateCache() {
|
||||
Mono<Long> statsMono = redisUtil.deleteByPattern("datacount:statistics:*");
|
||||
Mono<Long> statsMono = cacheOperations.deleteByPattern("datacount:statistics:*");
|
||||
if (statsMono != null) {
|
||||
statsMono.subscribe(
|
||||
deleted -> logger.debug("调度器清除统计缓存,已删除 {} 条", deleted),
|
||||
error -> logger.warn("调度器清除统计缓存失败: {}", error.getMessage())
|
||||
);
|
||||
}
|
||||
Mono<Long> courseMono = redisUtil.deleteByPattern("group_course:*");
|
||||
Mono<Long> courseMono = cacheOperations.deleteByPattern("group_course:*");
|
||||
if (courseMono != null) {
|
||||
courseMono.subscribe(
|
||||
deleted -> logger.debug("调度器清除团课缓存,已删除 {} 条", deleted),
|
||||
|
||||
+6
-6
@@ -5,7 +5,7 @@ import cn.novalon.gym.manage.coach.entity.CoachViolationEntity;
|
||||
import cn.novalon.gym.manage.coach.enums.ViolationReason;
|
||||
import cn.novalon.gym.manage.coachconfig.domain.CoachTimeRule;
|
||||
import cn.novalon.gym.manage.coachconfig.service.CoachTimeRuleService;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.common.util.StatusConstants;
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseBookingDao;
|
||||
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
|
||||
@@ -56,7 +56,7 @@ public class CoachCourseService {
|
||||
private final CoachViolationDao violationDao;
|
||||
private final DatabaseClient databaseClient;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final CoachTimeRuleService timeRuleService;
|
||||
|
||||
public CoachCourseService(ISysUserRepository userRepository,
|
||||
@@ -69,7 +69,7 @@ public class CoachCourseService {
|
||||
CoachViolationDao violationDao,
|
||||
DatabaseClient databaseClient,
|
||||
PasswordEncoder passwordEncoder,
|
||||
RedisUtil redisUtil,
|
||||
CacheOperations cacheOperations,
|
||||
CoachTimeRuleService timeRuleService) {
|
||||
this.userRepository = userRepository;
|
||||
this.roleRepository = roleRepository;
|
||||
@@ -81,7 +81,7 @@ public class CoachCourseService {
|
||||
this.violationDao = violationDao;
|
||||
this.databaseClient = databaseClient;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
this.timeRuleService = timeRuleService;
|
||||
}
|
||||
|
||||
@@ -334,8 +334,8 @@ public class CoachCourseService {
|
||||
* 清除统计缓存和团课缓存 —— 课程状态变更后调用,返回 Mono 确保链式执行
|
||||
*/
|
||||
private Mono<Void> invalidateStatisticsCache() {
|
||||
return redisUtil.deleteByPattern("datacount:statistics:*")
|
||||
.then(redisUtil.deleteByPattern("group_course:*"))
|
||||
return cacheOperations.deleteByPattern("datacount:statistics:*")
|
||||
.then(cacheOperations.deleteByPattern("group_course:*"))
|
||||
.doOnNext(count -> {})
|
||||
.then();
|
||||
}
|
||||
|
||||
+15
-15
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
-17
@@ -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 -> {
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
package cn.novalon.gym.manage.datacount.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.datacount.dao.DataStatisticsDao;
|
||||
import cn.novalon.gym.manage.datacount.domain.*;
|
||||
import cn.novalon.gym.manage.datacount.service.IDataStatisticsService;
|
||||
@@ -46,7 +46,7 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
|
||||
private DataStatisticsDao dataStatisticsDao;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
@@ -223,7 +223,7 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
|
||||
public reactor.core.publisher.Flux<DataStatistics> queryHistoricalStatistics(StatisticsQuery query) {
|
||||
// 历史统计数据查询(从Redis缓存中获取)
|
||||
String cacheKey = buildCacheKey(query);
|
||||
return redisUtil.get(cacheKey, String.class)
|
||||
return cacheOperations.get(cacheKey, String.class)
|
||||
.flatMapMany(json -> {
|
||||
try {
|
||||
java.util.List<DataStatistics> stats = objectMapper.readValue(json,
|
||||
@@ -270,9 +270,9 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
|
||||
String signInJson = objectMapper.writeValueAsString(signInStats);
|
||||
|
||||
return reactor.core.publisher.Flux.merge(
|
||||
redisUtil.setWithExpire(memberKey, memberJson, Duration.ofDays(30).getSeconds()),
|
||||
redisUtil.setWithExpire(bookingKey, bookingJson, Duration.ofDays(30).getSeconds()),
|
||||
redisUtil.setWithExpire(signInKey, signInJson, Duration.ofDays(30).getSeconds())
|
||||
cacheOperations.setWithExpire(memberKey, memberJson, Duration.ofDays(30).getSeconds()),
|
||||
cacheOperations.setWithExpire(bookingKey, bookingJson, Duration.ofDays(30).getSeconds()),
|
||||
cacheOperations.setWithExpire(signInKey, signInJson, Duration.ofDays(30).getSeconds())
|
||||
).then();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize statistics data", e);
|
||||
@@ -445,13 +445,13 @@ public class DataStatisticsServiceImpl implements IDataStatisticsService {
|
||||
public Mono<StatisticsSummary> getStatisticsSummaryWithCache(StatisticsQuery query) {
|
||||
String cacheKey = buildCacheKey(query);
|
||||
|
||||
return redisUtil.get(cacheKey, StatisticsSummary.class)
|
||||
return cacheOperations.get(cacheKey, StatisticsSummary.class)
|
||||
.switchIfEmpty(
|
||||
getStatisticsSummary(query)
|
||||
.flatMap(summary -> {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(summary);
|
||||
return redisUtil.setWithExpire(cacheKey, json, cacheExpireSeconds)
|
||||
return cacheOperations.setWithExpire(cacheKey, json, cacheExpireSeconds)
|
||||
.thenReturn(summary);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize statistics summary", e);
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
package cn.novalon.gym.manage.groupcourse.handler;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseDetail;
|
||||
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
|
||||
@@ -30,16 +30,16 @@ public class GroupCourseHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupCourseHandler.class);
|
||||
private final IGroupCourseService groupCourseService;
|
||||
private final Validator validator;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public GroupCourseHandler(IGroupCourseService groupCourseService,
|
||||
Validator validator,
|
||||
RedisUtil redisUtil,
|
||||
CacheOperations cacheOperations,
|
||||
ObjectMapper objectMapper){
|
||||
this.groupCourseService = groupCourseService;
|
||||
this.validator = validator;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ public class GroupCourseHandler {
|
||||
return ServerResponse.badRequest().bodyValue(error);
|
||||
}
|
||||
|
||||
return redisUtil.get(key)
|
||||
return cacheOperations.get(key)
|
||||
.map(cachedValue -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (cachedValue != null) {
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@ package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.repository.ICourseLabelRepository;
|
||||
@@ -24,21 +24,21 @@ public class CourseLabelService implements ICourseLabelService {
|
||||
|
||||
private final ICourseLabelRepository courseLabelRepository;
|
||||
private final IGroupCourseRepository groupCourseRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
public CourseLabelService(ICourseLabelRepository courseLabelRepository,
|
||||
IGroupCourseRepository groupCourseRepository,
|
||||
RedisUtil redisUtil) {
|
||||
CacheOperations cacheOperations) {
|
||||
this.courseLabelRepository = courseLabelRepository;
|
||||
this.groupCourseRepository = groupCourseRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
}
|
||||
|
||||
private Mono<Void> invalidateGroupCourseDetailCache(Long typeId) {
|
||||
return groupCourseRepository.findByCourseType(typeId)
|
||||
.flatMap(course -> {
|
||||
String cacheKey = CACHE_KEY_DETAIL_PREFIX + course.getId();
|
||||
return redisUtil.delete(cacheKey)
|
||||
return cacheOperations.delete(cacheKey)
|
||||
.doOnSuccess(deleted -> logger.debug("清除团课详情缓存 - courseId={}", course.getId()));
|
||||
})
|
||||
.then();
|
||||
|
||||
+15
-30
@@ -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,8 +12,10 @@ import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 团课Redis缓存服务
|
||||
*
|
||||
* 负责团课信息的缓存管理和分布式锁实现
|
||||
* <p>
|
||||
* 缓存操作委托给 {@link CacheOperations} 接口,支持通过配置切换 Redis / Caffeine 实现。
|
||||
* 分布式锁操作仍直接使用 {@link ReactiveRedisTemplate}(锁为 Redis 独有特性)。
|
||||
* </p>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-01
|
||||
@@ -33,13 +34,13 @@ public class GroupCourseRedisService {
|
||||
// 锁过期时间(30秒)
|
||||
private static final Duration LOCK_EXPIRE_TIME = Duration.ofSeconds(30);
|
||||
|
||||
private final CacheOperations cacheOperations;
|
||||
private final ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public GroupCourseRedisService(ReactiveRedisTemplate<String, Object> reactiveRedisTemplate,
|
||||
ObjectMapper objectMapper) {
|
||||
public GroupCourseRedisService(CacheOperations cacheOperations,
|
||||
ReactiveRedisTemplate<String, Object> reactiveRedisTemplate) {
|
||||
this.cacheOperations = cacheOperations;
|
||||
this.reactiveRedisTemplate = reactiveRedisTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,16 +62,9 @@ public class GroupCourseRedisService {
|
||||
*/
|
||||
public Mono<Void> cacheCourse(GroupCourse course) {
|
||||
String key = getCourseCacheKey(course.getId());
|
||||
try {
|
||||
String value = objectMapper.writeValueAsString(course);
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.set(key, value, CACHE_EXPIRE_TIME)
|
||||
return cacheOperations.setWithExpire(key, course, CACHE_EXPIRE_TIME.toSeconds())
|
||||
.doOnSuccess(result -> logger.debug("团课信息已缓存:courseId={}", course.getId()))
|
||||
.then();
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("序列化团课信息失败:courseId={}", course.getId(), e);
|
||||
return Mono.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,19 +72,8 @@ public class GroupCourseRedisService {
|
||||
*/
|
||||
public Mono<GroupCourse> getCachedCourse(Long courseId) {
|
||||
String key = getCourseCacheKey(courseId);
|
||||
return reactiveRedisTemplate.opsForValue()
|
||||
.get(key)
|
||||
.cast(String.class)
|
||||
.flatMap(value -> {
|
||||
try {
|
||||
GroupCourse course = objectMapper.readValue(value, GroupCourse.class);
|
||||
logger.debug("从缓存获取团课信息:courseId={}", courseId);
|
||||
return Mono.just(course);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("反序列化团课信息失败:courseId={}", courseId, e);
|
||||
return Mono.empty();
|
||||
}
|
||||
})
|
||||
return cacheOperations.get(key, GroupCourse.class)
|
||||
.doOnNext(course -> logger.debug("从缓存获取团课信息:courseId={}", courseId))
|
||||
.switchIfEmpty(Mono.fromRunnable(() -> logger.debug("缓存中未找到团课信息:courseId={}", courseId)));
|
||||
}
|
||||
|
||||
@@ -99,13 +82,14 @@ public class GroupCourseRedisService {
|
||||
*/
|
||||
public Mono<Void> invalidateCourseCache(Long courseId) {
|
||||
String key = getCourseCacheKey(courseId);
|
||||
return reactiveRedisTemplate.delete(key)
|
||||
return cacheOperations.delete(key)
|
||||
.doOnSuccess(result -> logger.debug("团课缓存已删除:courseId={}", courseId))
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分布式锁
|
||||
* <p>注意:分布式锁为 Redis 独有特性,始终保持直接使用 RedisTemplate。</p>
|
||||
*
|
||||
* @param courseId 课程ID
|
||||
* @param requestId 请求ID(用于锁的唯一性校验)
|
||||
@@ -126,6 +110,7 @@ public class GroupCourseRedisService {
|
||||
|
||||
/**
|
||||
* 释放分布式锁
|
||||
* <p>注意:分布式锁为 Redis 独有特性,始终保持直接使用 RedisTemplate。</p>
|
||||
*
|
||||
* @param courseId 课程ID
|
||||
* @param requestId 请求ID(用于锁的唯一性校验)
|
||||
|
||||
+17
-17
@@ -1,9 +1,9 @@
|
||||
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.common.dto.PageRequest;
|
||||
import cn.novalon.gym.manage.common.dto.PageResponse;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.CourseLabel;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
|
||||
@@ -53,7 +53,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
private final ICourseLabelRepository courseLabelRepository;
|
||||
private final IMemberCardRecordService memberCardRecordService;
|
||||
private final MemberCardRepository memberCardRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final GroupCourseStateMachine stateMachine;
|
||||
private final DatabaseClient databaseClient;
|
||||
@@ -73,7 +73,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
ICourseLabelRepository courseLabelRepository,
|
||||
IMemberCardRecordService memberCardRecordService,
|
||||
MemberCardRepository memberCardRepository,
|
||||
RedisUtil redisUtil,
|
||||
CacheOperations cacheOperations,
|
||||
ObjectMapper objectMapper,
|
||||
GroupCourseStateMachine stateMachine,
|
||||
DatabaseClient databaseClient,
|
||||
@@ -85,7 +85,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
this.courseLabelRepository = courseLabelRepository;
|
||||
this.memberCardRecordService = memberCardRecordService;
|
||||
this.memberCardRepository = memberCardRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
this.objectMapper = objectMapper;
|
||||
this.stateMachine = stateMachine;
|
||||
this.databaseClient = databaseClient;
|
||||
@@ -97,7 +97,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
public Mono<GroupCourseDetail> findDetailById(Long id) {
|
||||
String cacheKey = CACHE_KEY_DETAIL_PREFIX + id;
|
||||
|
||||
Mono<String> cachedMono = redisUtil.get(cacheKey, String.class);
|
||||
Mono<String> cachedMono = cacheOperations.get(cacheKey, String.class);
|
||||
return cachedMono
|
||||
.flatMap(cachedJson -> {
|
||||
if (cachedJson != null && !cachedJson.isEmpty()) {
|
||||
@@ -107,7 +107,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
return Mono.<GroupCourseDetail>just(detail);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
|
||||
return redisUtil.delete(cacheKey).then(Mono.<GroupCourseDetail>empty());
|
||||
return cacheOperations.delete(cacheKey).then(Mono.<GroupCourseDetail>empty());
|
||||
}
|
||||
}
|
||||
return Mono.<GroupCourseDetail>empty();
|
||||
@@ -141,7 +141,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
.flatMap(detail -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(detail);
|
||||
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
return cacheOperations.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
.thenReturn(detail)
|
||||
.doOnSuccess(d -> logger.debug("缓存已设置 - findDetailById: id={}", id));
|
||||
} catch (JsonProcessingException e) {
|
||||
@@ -233,7 +233,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
public Mono<GroupCourse> findById(Long id) {
|
||||
String cacheKey = CACHE_KEY_ID_PREFIX + id;
|
||||
|
||||
Mono<String> cachedMono = redisUtil.get(cacheKey, String.class);
|
||||
Mono<String> cachedMono = cacheOperations.get(cacheKey, String.class);
|
||||
return cachedMono
|
||||
.flatMap(cachedJson -> {
|
||||
if (cachedJson != null && !cachedJson.isEmpty()) {
|
||||
@@ -243,7 +243,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
return Mono.<GroupCourse>just(groupCourse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - id: {}, error: {}", id, e.getMessage());
|
||||
return redisUtil.delete(cacheKey).then(Mono.<GroupCourse>empty());
|
||||
return cacheOperations.delete(cacheKey).then(Mono.<GroupCourse>empty());
|
||||
}
|
||||
}
|
||||
return Mono.<GroupCourse>empty();
|
||||
@@ -253,7 +253,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
.flatMap(groupCourse -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(groupCourse);
|
||||
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
return cacheOperations.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
.thenReturn(groupCourse)
|
||||
.doOnSuccess(gc -> logger.debug("缓存已设置 - findById: id={}", id));
|
||||
} catch (JsonProcessingException e) {
|
||||
@@ -332,7 +332,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
|
||||
String cacheKey = CACHE_KEY_PREFIX + page + ":" + size + ":" + includeDeleted + ":" + sort + ":" + order + ":" + keyword + ":" + status;
|
||||
|
||||
Mono<String> cachedMono = redisUtil.get(cacheKey, String.class);
|
||||
Mono<String> cachedMono = cacheOperations.get(cacheKey, String.class);
|
||||
return cachedMono
|
||||
.flatMap(cachedJson -> {
|
||||
if (cachedJson != null && !cachedJson.isEmpty()) {
|
||||
@@ -343,7 +343,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
return Mono.<PageResponse<GroupCourse>>just(pageResponse);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("缓存解析失败,删除缓存 - key: {}, error: {}", cacheKey, e.getMessage());
|
||||
return redisUtil.delete(cacheKey).then(Mono.<PageResponse<GroupCourse>>empty());
|
||||
return cacheOperations.delete(cacheKey).then(Mono.<PageResponse<GroupCourse>>empty());
|
||||
}
|
||||
}
|
||||
return Mono.<PageResponse<GroupCourse>>empty();
|
||||
@@ -361,7 +361,7 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
return resultMono.flatMap(pageResponse -> {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(pageResponse);
|
||||
return redisUtil.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
return cacheOperations.setWithExpire(cacheKey, jsonData, CACHE_EXPIRE_SECONDS)
|
||||
.thenReturn(pageResponse)
|
||||
.doOnSuccess(pr -> logger.debug("缓存已设置 - findByPage: key={}", cacheKey));
|
||||
} catch (JsonProcessingException e) {
|
||||
@@ -739,10 +739,10 @@ public class GroupCourseService implements IGroupCourseService {
|
||||
}
|
||||
|
||||
private Mono<Void> clearCache() {
|
||||
return redisUtil.deleteByPattern(CACHE_KEY_PREFIX + "*")
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
|
||||
.then(redisUtil.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*"))
|
||||
.then(redisUtil.deleteByPattern("datacount:statistics:*"))
|
||||
return cacheOperations.deleteByPattern(CACHE_KEY_PREFIX + "*")
|
||||
.then(cacheOperations.deleteByPattern(CACHE_KEY_ID_PREFIX + "*"))
|
||||
.then(cacheOperations.deleteByPattern(CACHE_KEY_DETAIL_PREFIX + "*"))
|
||||
.then(cacheOperations.deleteByPattern("datacount:statistics:*"))
|
||||
.then();
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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 ====================
|
||||
|
||||
+16
-16
@@ -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 ====================
|
||||
|
||||
+19
-26
@@ -1,7 +1,7 @@
|
||||
package cn.novalon.gym.manage.groupcourse.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -21,14 +21,15 @@ import static org.mockito.Mockito.*;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GroupCourseRedisServiceTest {
|
||||
|
||||
@Mock
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@Mock
|
||||
private ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
|
||||
|
||||
@Mock
|
||||
private ReactiveValueOperations<String, Object> reactiveValueOps;
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private GroupCourseRedisService service;
|
||||
|
||||
private GroupCourse testCourse;
|
||||
@@ -36,7 +37,7 @@ class GroupCourseRedisServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
lenient().when(reactiveRedisTemplate.opsForValue()).thenReturn(reactiveValueOps);
|
||||
service = new GroupCourseRedisService(reactiveRedisTemplate, objectMapper);
|
||||
service = new GroupCourseRedisService(cacheOperations, reactiveRedisTemplate);
|
||||
|
||||
testCourse = new GroupCourse();
|
||||
testCourse.setId(1L);
|
||||
@@ -47,20 +48,20 @@ class GroupCourseRedisServiceTest {
|
||||
// ==================== cacheCourse ====================
|
||||
|
||||
@Test
|
||||
void cacheCourse_shouldSerializeAndSetInRedis() {
|
||||
when(reactiveValueOps.set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5))))
|
||||
void cacheCourse_shouldSetInCache() {
|
||||
when(cacheOperations.setWithExpire(eq("group_course:1"), eq(testCourse), eq(300L)))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
StepVerifier.create(service.cacheCourse(testCourse))
|
||||
.verifyComplete();
|
||||
|
||||
verify(reactiveValueOps).set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5)));
|
||||
verify(cacheOperations).setWithExpire(eq("group_course:1"), eq(testCourse), eq(300L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheCourse_shouldErrorWhenRedisFails() {
|
||||
when(reactiveValueOps.set(eq("group_course:1"), anyString(), eq(Duration.ofMinutes(5))))
|
||||
.thenReturn(Mono.error(new RuntimeException("Redis error")));
|
||||
void cacheCourse_shouldErrorWhenCacheFails() {
|
||||
when(cacheOperations.setWithExpire(eq("group_course:1"), eq(testCourse), eq(300L)))
|
||||
.thenReturn(Mono.error(new RuntimeException("Cache error")));
|
||||
|
||||
StepVerifier.create(service.cacheCourse(testCourse))
|
||||
.expectError(RuntimeException.class)
|
||||
@@ -70,9 +71,9 @@ class GroupCourseRedisServiceTest {
|
||||
// ==================== getCachedCourse ====================
|
||||
|
||||
@Test
|
||||
void getCachedCourse_shouldReturnCachedCourseWhenFound() throws Exception {
|
||||
String json = objectMapper.writeValueAsString(testCourse);
|
||||
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.just(json));
|
||||
void getCachedCourse_shouldReturnCachedCourseWhenFound() {
|
||||
when(cacheOperations.get(eq("group_course:1"), eq(GroupCourse.class)))
|
||||
.thenReturn(Mono.just(testCourse));
|
||||
|
||||
StepVerifier.create(service.getCachedCourse(1L))
|
||||
.assertNext(course -> {
|
||||
@@ -84,16 +85,8 @@ class GroupCourseRedisServiceTest {
|
||||
|
||||
@Test
|
||||
void getCachedCourse_shouldReturnEmptyWhenCacheMiss() {
|
||||
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.getCachedCourse(1L))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCachedCourse_shouldReturnEmptyWhenDeserializationFails() {
|
||||
String invalidJson = "invalid json";
|
||||
when(reactiveValueOps.get("group_course:1")).thenReturn(Mono.just(invalidJson));
|
||||
when(cacheOperations.get(eq("group_course:1"), eq(GroupCourse.class)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier.create(service.getCachedCourse(1L))
|
||||
.verifyComplete();
|
||||
@@ -102,13 +95,13 @@ class GroupCourseRedisServiceTest {
|
||||
// ==================== invalidateCourseCache ====================
|
||||
|
||||
@Test
|
||||
void invalidateCourseCache_shouldDeleteFromRedis() {
|
||||
when(reactiveRedisTemplate.delete("group_course:1")).thenReturn(Mono.just(1L));
|
||||
void invalidateCourseCache_shouldDeleteFromCache() {
|
||||
when(cacheOperations.delete("group_course:1")).thenReturn(Mono.just(1L));
|
||||
|
||||
StepVerifier.create(service.invalidateCourseCache(1L))
|
||||
.verifyComplete();
|
||||
|
||||
verify(reactiveRedisTemplate).delete("group_course:1");
|
||||
verify(cacheOperations).delete("group_course:1");
|
||||
}
|
||||
|
||||
// ==================== acquireLock ====================
|
||||
|
||||
+31
-31
@@ -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 ====================
|
||||
|
||||
+7
-7
@@ -6,7 +6,7 @@ import cn.novalon.gym.manage.member.enums.MemberCardRecordStatus;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -29,30 +29,30 @@ import java.util.stream.Collectors;
|
||||
public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
|
||||
private final MemberCardRecordRepository memberCardRecordRepository;
|
||||
private final MemberCardRepository memberCardRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
private static final String MEMBER_CARD_RECORD_CACHE_PREFIX = "member:card:record:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
public MemberCardRecordServiceImpl(MemberCardRecordRepository memberCardRecordRepository,
|
||||
MemberCardRepository memberCardRepository,
|
||||
RedisUtil redisUtil) {
|
||||
CacheOperations cacheOperations) {
|
||||
this.memberCardRecordRepository = memberCardRecordRepository;
|
||||
this.memberCardRepository = memberCardRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MemberCardRecord> findById(Long recordId) {
|
||||
String cacheKey = MEMBER_CARD_RECORD_CACHE_PREFIX + recordId;
|
||||
return redisUtil.get(cacheKey)
|
||||
return cacheOperations.get(cacheKey)
|
||||
.filter(cached -> cached instanceof MemberCardRecord)
|
||||
.map(cached -> (MemberCardRecord) cached)
|
||||
.switchIfEmpty(Mono.defer(() ->
|
||||
memberCardRecordRepository.findById(recordId)
|
||||
.doOnSuccess(record -> {
|
||||
if (record != null) {
|
||||
redisUtil.setWithExpire(cacheKey, record, CACHE_EXPIRE_SECONDS);
|
||||
cacheOperations.setWithExpire(cacheKey, record, CACHE_EXPIRE_SECONDS);
|
||||
}
|
||||
})
|
||||
));
|
||||
@@ -255,7 +255,7 @@ public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
|
||||
|
||||
private void clearRecordCache(Long recordId) {
|
||||
String cacheKey = MEMBER_CARD_RECORD_CACHE_PREFIX + recordId;
|
||||
redisUtil.delete(cacheKey);
|
||||
cacheOperations.delete(cacheKey);
|
||||
log.debug("清除会员卡记录缓存, recordId: {}", recordId);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
|
||||
@@ -40,7 +40,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
private final DistributedLockService distributedLockService;
|
||||
private final ExpirationReminderService expirationReminderService;
|
||||
private final RefundSagaHandler refundSagaHandler;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
private static final String MEMBER_CARD_CACHE_PREFIX = "member:card:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
@@ -53,7 +53,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
DistributedLockService distributedLockService,
|
||||
ExpirationReminderService expirationReminderService,
|
||||
RefundSagaHandler refundSagaHandler,
|
||||
RedisUtil redisUtil) {
|
||||
CacheOperations cacheOperations) {
|
||||
this.memberCardRepository = memberCardRepository;
|
||||
this.recordRepository = recordRepository;
|
||||
this.transactionService = transactionService;
|
||||
@@ -61,13 +61,13 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
this.distributedLockService = distributedLockService;
|
||||
this.expirationReminderService = expirationReminderService;
|
||||
this.refundSagaHandler = refundSagaHandler;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MemberCard> findByMemberCardIdAndDeletedAtIsNull(Long memberCardId) {
|
||||
String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
|
||||
Object cached = redisUtil.get(cacheKey);
|
||||
Object cached = cacheOperations.get(cacheKey);
|
||||
if (cached != null && cached instanceof MemberCard) {
|
||||
log.debug("从缓存获取会员卡信息, memberCardId: {}", memberCardId);
|
||||
return Mono.just((MemberCard) cached);
|
||||
@@ -76,7 +76,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
return memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(memberCardId)
|
||||
.doOnSuccess(card -> {
|
||||
if (card != null) {
|
||||
redisUtil.setWithExpire(cacheKey, card, CACHE_EXPIRE_SECONDS);
|
||||
cacheOperations.setWithExpire(cacheKey, card, CACHE_EXPIRE_SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -439,7 +439,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
|
||||
private void clearCardCache(Long memberCardId) {
|
||||
String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
|
||||
redisUtil.delete(cacheKey);
|
||||
cacheOperations.delete(cacheKey);
|
||||
log.debug("清除会员卡缓存, memberCardId: {}", memberCardId);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -18,7 +18,7 @@ import cn.novalon.gym.manage.member.util.AesUtil;
|
||||
import cn.novalon.gym.manage.member.util.BeanConvertUtil;
|
||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||
import cn.novalon.gym.manage.member.util.WechatPhoneUtil;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.vo.MemberCardInfoVO;
|
||||
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
|
||||
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
|
||||
@@ -50,7 +50,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
private final IMemberRepository memberRepository;
|
||||
private final MemberESRepository memberESRepository;
|
||||
private final EsSyncUtils esSyncUtils;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
|
||||
|
||||
@@ -68,7 +68,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
|
||||
// 先查缓存
|
||||
return redisUtil.get(cacheKey, MemberInfoVO.class)
|
||||
return cacheOperations.get(cacheKey, MemberInfoVO.class)
|
||||
.flatMap(cached -> {
|
||||
if (cached != null) {
|
||||
log.debug("从缓存获取会员信息, memberId: {}", memberId);
|
||||
@@ -91,7 +91,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
.map(this::buildMemberInfoResponse)
|
||||
.flatMap(vo -> {
|
||||
// 查询到数据后更新缓存
|
||||
return redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
|
||||
return cacheOperations.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
|
||||
.then(Mono.just(vo));
|
||||
})
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
@@ -269,7 +269,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
String cacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
|
||||
|
||||
return redisUtil.get(cacheKey, MemberDetailVO.class)
|
||||
return cacheOperations.get(cacheKey, MemberDetailVO.class)
|
||||
.filter(cached -> cached != null)
|
||||
.switchIfEmpty(Mono.defer(() ->
|
||||
memberRepository.findById(memberId)
|
||||
@@ -312,7 +312,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
return memberDetailVO;
|
||||
}
|
||||
)
|
||||
.flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
|
||||
.flatMap(vo -> cacheOperations.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
|
||||
.then(Mono.just(vo)))
|
||||
));
|
||||
}
|
||||
@@ -383,8 +383,8 @@ public class MemberServiceImpl implements MemberService {
|
||||
private Mono<Long> clearMemberCache(Long memberId) {
|
||||
String infoCacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
String detailCacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
|
||||
return redisUtil.delete(infoCacheKey)
|
||||
.then(redisUtil.delete(detailCacheKey))
|
||||
return cacheOperations.delete(infoCacheKey)
|
||||
.then(cacheOperations.delete(detailCacheKey))
|
||||
.doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -5,7 +5,7 @@ import cn.novalon.gym.manage.member.entity.RefundApplication;
|
||||
import cn.novalon.gym.manage.member.enums.RefundStatus;
|
||||
import cn.novalon.gym.manage.member.repository.RefundApplicationRepository;
|
||||
import cn.novalon.gym.manage.member.service.IRefundApplicationService;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -23,14 +23,14 @@ import java.time.LocalDateTime;
|
||||
public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
|
||||
private final RefundApplicationRepository refundApplicationRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
private static final String REFUND_APPLICATION_CACHE_PREFIX = "member:refund:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
public RefundApplicationServiceImpl(RefundApplicationRepository refundApplicationRepository, RedisUtil redisUtil) {
|
||||
public RefundApplicationServiceImpl(RefundApplicationRepository refundApplicationRepository, CacheOperations cacheOperations) {
|
||||
this.refundApplicationRepository = refundApplicationRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +103,7 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
@Override
|
||||
public Mono<RefundApplication> findByRecordId(Long recordId) {
|
||||
String cacheKey = REFUND_APPLICATION_CACHE_PREFIX + recordId;
|
||||
Object cached = redisUtil.get(cacheKey);
|
||||
Object cached = cacheOperations.get(cacheKey);
|
||||
if (cached != null && cached instanceof RefundApplication) {
|
||||
log.debug("从缓存获取退款申请, recordId: {}", recordId);
|
||||
return Mono.just((RefundApplication) cached);
|
||||
@@ -112,14 +112,14 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
return refundApplicationRepository.findByRecordId(recordId)
|
||||
.doOnSuccess(application -> {
|
||||
if (application != null) {
|
||||
redisUtil.setWithExpire(cacheKey, application, CACHE_EXPIRE_SECONDS);
|
||||
cacheOperations.setWithExpire(cacheKey, application, CACHE_EXPIRE_SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void clearRefundCache(Long recordId) {
|
||||
String cacheKey = REFUND_APPLICATION_CACHE_PREFIX + recordId;
|
||||
redisUtil.delete(cacheKey);
|
||||
cacheOperations.delete(cacheKey);
|
||||
log.debug("清除退款申请缓存, recordId: {}", recordId);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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");
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ import cn.novalon.gym.manage.member.service.WechatAuthService;
|
||||
import cn.novalon.gym.manage.member.util.AesUtil;
|
||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||
import cn.novalon.gym.manage.member.util.MemberNoGenerator;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.util.WechatPhoneUtil;
|
||||
import cn.novalon.gym.manage.member.vo.WechatLoginVO;
|
||||
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
||||
@@ -46,7 +46,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
private final MemberESRepository memberESRepository;
|
||||
private final EsSyncUtils esSyncUtils;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
|
||||
|
||||
@@ -217,7 +217,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
|
||||
private void clearMemberCache(Long memberId) {
|
||||
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
redisUtil.delete(cacheKey);
|
||||
cacheOperations.delete(cacheKey);
|
||||
log.debug("清除会员缓存, memberId: {}", memberId);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -8,7 +8,7 @@ import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
|
||||
import cn.novalon.gym.manage.member.repository.IMemberRepository;
|
||||
import cn.novalon.gym.manage.member.service.WechatOfficialService;
|
||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.vo.WechatUserInfoVO;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -43,7 +43,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
private final EsSyncUtils esSyncUtils;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
|
||||
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
|
||||
|
||||
@@ -297,7 +297,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
private Mono<String> getAccessToken() {
|
||||
String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + "mp";
|
||||
|
||||
return redisUtil.get(cacheKey, String.class)
|
||||
return cacheOperations.get(cacheKey, String.class)
|
||||
.flatMap(cachedToken -> {
|
||||
if (cachedToken != null) {
|
||||
log.debug("从缓存获取服务号access_token");
|
||||
@@ -322,7 +322,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
throw new RuntimeException("获取AccessToken失败: " + response.get("errmsg"));
|
||||
}
|
||||
String accessToken = (String) response.get("access_token");
|
||||
return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
|
||||
return cacheOperations.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
|
||||
.then(Mono.just(accessToken));
|
||||
});
|
||||
});
|
||||
@@ -373,7 +373,7 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
|
||||
private Mono<Long> clearMemberCache(Long memberId) {
|
||||
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
return redisUtil.delete(cacheKey)
|
||||
return cacheOperations.delete(cacheKey)
|
||||
.doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardRecordStatus;
|
||||
@@ -37,7 +37,7 @@ class MemberCardRecordServiceImplTest {
|
||||
private MemberCardRepository memberCardRepository;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@InjectMocks
|
||||
private MemberCardRecordServiceImpl memberCardRecordService;
|
||||
@@ -82,7 +82,7 @@ class MemberCardRecordServiceImplTest {
|
||||
@DisplayName("缓存命中时应从缓存返回")
|
||||
void shouldReturnFromCacheWhenHit() {
|
||||
MemberCardRecord record = createRecord();
|
||||
when(redisUtil.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(record));
|
||||
when(cacheOperations.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(record));
|
||||
|
||||
Mono<MemberCardRecord> result = memberCardRecordService.findById(RECORD_ID);
|
||||
|
||||
@@ -97,7 +97,7 @@ class MemberCardRecordServiceImplTest {
|
||||
@DisplayName("缓存未命中时应从数据库查询并更新缓存")
|
||||
void shouldQueryDatabaseWhenCacheMiss() {
|
||||
MemberCardRecord record = createRecord();
|
||||
when(redisUtil.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
|
||||
when(memberCardRecordRepository.findById(anyLong())).thenReturn(Mono.just(record));
|
||||
|
||||
Mono<MemberCardRecord> result = memberCardRecordService.findById(RECORD_ID);
|
||||
@@ -112,7 +112,7 @@ class MemberCardRecordServiceImplTest {
|
||||
@Test
|
||||
@DisplayName("记录不存在时应返回空")
|
||||
void shouldReturnEmptyWhenNotFound() {
|
||||
when(redisUtil.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get("member:card:record:" + RECORD_ID)).thenReturn(Mono.empty());
|
||||
when(memberCardRecordRepository.findById(anyLong())).thenReturn(Mono.empty());
|
||||
|
||||
Mono<MemberCardRecord> result = memberCardRecordService.findById(RECORD_ID);
|
||||
@@ -210,7 +210,7 @@ class MemberCardRecordServiceImplTest {
|
||||
@DisplayName("扣减成功时应清除缓存")
|
||||
void shouldClearCacheWhenDeducted() {
|
||||
when(memberCardRecordRepository.deductUsage(RECORD_ID, 1, 0.0)).thenReturn(Mono.just(1));
|
||||
when(redisUtil.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
Mono<Integer> result = memberCardRecordService.deductUsage(RECORD_ID, 1, 0.0);
|
||||
|
||||
@@ -218,7 +218,7 @@ class MemberCardRecordServiceImplTest {
|
||||
.assertNext(updated -> assertThat(updated).isEqualTo(1))
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).delete("member:card:record:" + RECORD_ID);
|
||||
verify(cacheOperations).delete("member:card:record:" + RECORD_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -232,7 +232,7 @@ class MemberCardRecordServiceImplTest {
|
||||
.assertNext(updated -> assertThat(updated).isZero())
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil, never()).delete(anyString());
|
||||
verify(cacheOperations, never()).delete(anyString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ class MemberCardRecordServiceImplTest {
|
||||
void shouldClearCacheWhenRenewed() {
|
||||
LocalDateTime newExpire = LocalDateTime.now().plusDays(30);
|
||||
when(memberCardRecordRepository.renewCard(RECORD_ID, 10, null, newExpire)).thenReturn(Mono.just(1));
|
||||
when(redisUtil.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
Mono<Integer> result = memberCardRecordService.renewCard(RECORD_ID, 10, null, newExpire);
|
||||
|
||||
@@ -267,7 +267,7 @@ class MemberCardRecordServiceImplTest {
|
||||
@DisplayName("状态更新成功时应清除缓存")
|
||||
void shouldClearCacheWhenStatusUpdated() {
|
||||
when(memberCardRecordRepository.updateStatus(RECORD_ID, "USED_UP")).thenReturn(Mono.just(1));
|
||||
when(redisUtil.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:card:record:" + RECORD_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
Mono<Integer> result = memberCardRecordService.updateStatus(RECORD_ID, "USED_UP");
|
||||
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCard;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.enums.CardEvent;
|
||||
@@ -57,7 +57,7 @@ class MemberCardServiceImplTest {
|
||||
private RefundSagaHandler refundSagaHandler;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@InjectMocks
|
||||
private MemberCardServiceImpl memberCardService;
|
||||
@@ -155,7 +155,7 @@ class MemberCardServiceImplTest {
|
||||
@DisplayName("缓存未命中时应从数据库查询并更新缓存")
|
||||
void shouldQueryDatabaseWhenCacheMiss() {
|
||||
MemberCard card = createTimeCard();
|
||||
when(redisUtil.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
|
||||
when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(MEMBER_CARD_ID))
|
||||
.thenReturn(Mono.just(card));
|
||||
|
||||
@@ -171,7 +171,7 @@ class MemberCardServiceImplTest {
|
||||
@Test
|
||||
@DisplayName("卡片不存在时应返回空")
|
||||
void shouldReturnEmptyWhenCardNotFound() {
|
||||
when(redisUtil.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
|
||||
when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(MEMBER_CARD_ID))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
@@ -214,7 +214,7 @@ class MemberCardServiceImplTest {
|
||||
void shouldClearCacheWhenSaved() {
|
||||
MemberCard card = createTimeCard();
|
||||
when(memberCardRepository.save(card)).thenReturn(Mono.just(card));
|
||||
when(redisUtil.delete("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
Mono<MemberCard> result = memberCardService.save(card);
|
||||
|
||||
@@ -222,7 +222,7 @@ class MemberCardServiceImplTest {
|
||||
.assertNext(c -> assertThat(c.getMemberCardName()).isEqualTo("月卡"))
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).delete("member:card:" + MEMBER_CARD_ID);
|
||||
verify(cacheOperations).delete("member:card:" + MEMBER_CARD_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ class MemberCardServiceImplTest {
|
||||
@DisplayName("购买时间卡应成功创建记录")
|
||||
void shouldPurchaseTimeCardSuccessfully() {
|
||||
MemberCard timeCard = createTimeCard();
|
||||
lenient().when(redisUtil.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
|
||||
lenient().when(cacheOperations.get("member:card:" + MEMBER_CARD_ID)).thenReturn(Mono.empty());
|
||||
when(memberCardRepository.findByIdAndDeletedAtIsNull(MEMBER_CARD_ID)).thenReturn(Mono.just(timeCard));
|
||||
when(recordRepository.insertActiveRecord(anyLong(), anyLong(), any(), anyInt(), anyDouble(), any()))
|
||||
.thenReturn(Mono.just(createActiveRecord(timeCard, MemberCardRecordStatus.ACTIVE)));
|
||||
|
||||
+25
-25
@@ -4,7 +4,7 @@ import cn.novalon.gym.manage.common.exception.ConflictException;
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.NotFoundException;
|
||||
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
|
||||
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
@@ -52,7 +52,7 @@ class MemberServiceImplTest {
|
||||
private EsSyncUtils esSyncUtils;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@InjectMocks
|
||||
private MemberServiceImpl memberServiceImpl;
|
||||
@@ -140,7 +140,7 @@ class MemberServiceImplTest {
|
||||
@DisplayName("缓存命中时应直接返回缓存数据")
|
||||
void shouldReturnCachedDataWhenCacheHit() {
|
||||
MemberInfoVO cachedVO = createTestMemberInfoVO();
|
||||
when(redisUtil.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.just(cachedVO));
|
||||
when(cacheOperations.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.just(cachedVO));
|
||||
|
||||
Mono<MemberInfoVO> result = memberServiceImpl.getMemberInfo(MEMBER_ID);
|
||||
|
||||
@@ -151,17 +151,17 @@ class MemberServiceImplTest {
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).get(CACHE_INFO_KEY, MemberInfoVO.class);
|
||||
verify(cacheOperations).get(CACHE_INFO_KEY, MemberInfoVO.class);
|
||||
verifyNoInteractions(memberRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("缓存未命中时应从数据库查询并更新缓存")
|
||||
void shouldQueryDatabaseAndCacheWhenCacheMiss() {
|
||||
when(redisUtil.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
|
||||
Member member = createTestMember();
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
|
||||
when(redisUtil.setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), anyLong()))
|
||||
when(cacheOperations.setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), anyLong()))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
try (MockedStatic<AesUtil> aesUtilMock = mockStatic(AesUtil.class)) {
|
||||
@@ -179,14 +179,14 @@ class MemberServiceImplTest {
|
||||
.verifyComplete();
|
||||
|
||||
verify(memberRepository).findById(MEMBER_ID);
|
||||
verify(redisUtil).setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), eq(300L));
|
||||
verify(cacheOperations).setWithExpire(eq(CACHE_INFO_KEY), any(MemberInfoVO.class), eq(300L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("会员不存在时应抛出 NotFoundException")
|
||||
void shouldThrowNotFoundExceptionWhenMemberNotFound() {
|
||||
when(redisUtil.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(CACHE_INFO_KEY, MemberInfoVO.class)).thenReturn(Mono.empty());
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.empty());
|
||||
|
||||
Mono<MemberInfoVO> result = memberServiceImpl.getMemberInfo(MEMBER_ID);
|
||||
@@ -198,7 +198,7 @@ class MemberServiceImplTest {
|
||||
&& throwable.getMessage().equals("会员不存在"))
|
||||
.verify();
|
||||
|
||||
verify(redisUtil).get(CACHE_INFO_KEY, MemberInfoVO.class);
|
||||
verify(cacheOperations).get(CACHE_INFO_KEY, MemberInfoVO.class);
|
||||
verify(memberRepository).findById(MEMBER_ID);
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,8 @@ class MemberServiceImplTest {
|
||||
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
|
||||
when(memberRepository.save(any(Member.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
|
||||
when(redisUtil.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(redisUtil.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
try (MockedStatic<AesUtil> aesUtilMock = mockStatic(AesUtil.class)) {
|
||||
aesUtilMock.when(() -> AesUtil.decrypt(ENCRYPTED_PHONE)).thenReturn(DECRYPTED_PHONE);
|
||||
@@ -236,8 +236,8 @@ class MemberServiceImplTest {
|
||||
verify(memberRepository).findById(MEMBER_ID);
|
||||
verify(memberRepository).save(any(Member.class));
|
||||
verify(mockSyncer).sync(any(Member.class));
|
||||
verify(redisUtil).delete("member:info:" + MEMBER_ID);
|
||||
verify(redisUtil).delete("member:detail:" + MEMBER_ID);
|
||||
verify(cacheOperations).delete("member:info:" + MEMBER_ID);
|
||||
verify(cacheOperations).delete("member:detail:" + MEMBER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,8 +274,8 @@ class MemberServiceImplTest {
|
||||
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
|
||||
when(memberRepository.save(any(Member.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
|
||||
when(redisUtil.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(redisUtil.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
try (MockedStatic<AesUtil> aesUtilMock = mockStatic(AesUtil.class)) {
|
||||
aesUtilMock.when(() -> AesUtil.encrypt(TEST_PHONE)).thenReturn(ENCRYPTED_PHONE);
|
||||
@@ -493,7 +493,7 @@ class MemberServiceImplTest {
|
||||
.nickname("测试会员")
|
||||
.build();
|
||||
|
||||
when(redisUtil.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.just(cachedDetail));
|
||||
when(cacheOperations.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.just(cachedDetail));
|
||||
|
||||
Mono<MemberDetailVO> result = memberServiceImpl.getMemberDetail(MEMBER_ID);
|
||||
|
||||
@@ -504,14 +504,14 @@ class MemberServiceImplTest {
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(redisUtil).get(CACHE_DETAIL_KEY, MemberDetailVO.class);
|
||||
verify(cacheOperations).get(CACHE_DETAIL_KEY, MemberDetailVO.class);
|
||||
verifyNoInteractions(memberRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("缓存未命中时应从数据库查询并构建详情")
|
||||
void shouldQueryDatabaseAndBuildDetailWhenCacheMiss() {
|
||||
when(redisUtil.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
|
||||
|
||||
Member member = createTestMember();
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
|
||||
@@ -520,7 +520,7 @@ class MemberServiceImplTest {
|
||||
when(memberRepository.findCardRecordsWithCardInfoByMemberId(MEMBER_ID))
|
||||
.thenReturn(Flux.just(cardInfo));
|
||||
|
||||
when(redisUtil.setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), anyLong()))
|
||||
when(cacheOperations.setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), anyLong()))
|
||||
.thenReturn(Mono.just(true));
|
||||
|
||||
Mono<MemberDetailVO> result = memberServiceImpl.getMemberDetail(MEMBER_ID);
|
||||
@@ -539,13 +539,13 @@ class MemberServiceImplTest {
|
||||
|
||||
verify(memberRepository).findById(MEMBER_ID);
|
||||
verify(memberRepository).findCardRecordsWithCardInfoByMemberId(MEMBER_ID);
|
||||
verify(redisUtil).setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), eq(300L));
|
||||
verify(cacheOperations).setWithExpire(eq(CACHE_DETAIL_KEY), any(MemberDetailVO.class), eq(300L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("会员不存在时应抛出 NotFoundException")
|
||||
void shouldThrowNotFoundExceptionWhenMemberNotFound() {
|
||||
when(redisUtil.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
|
||||
when(cacheOperations.get(CACHE_DETAIL_KEY, MemberDetailVO.class)).thenReturn(Mono.empty());
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.empty());
|
||||
// zipWith 会订阅所有源,即使另一个是 error,也需要桩避免 NPE
|
||||
lenient().when(memberRepository.findCardRecordsWithCardInfoByMemberId(MEMBER_ID))
|
||||
@@ -577,8 +577,8 @@ class MemberServiceImplTest {
|
||||
|
||||
when(memberRepository.findById(MEMBER_ID)).thenReturn(Mono.just(member));
|
||||
when(memberRepository.save(any(Member.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
|
||||
when(redisUtil.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(redisUtil.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:info:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
when(cacheOperations.delete("member:detail:" + MEMBER_ID)).thenReturn(Mono.just(1L));
|
||||
|
||||
Mono<Boolean> result = memberServiceImpl.adminUpdateMemberInfo(MEMBER_ID, dto);
|
||||
|
||||
@@ -589,8 +589,8 @@ class MemberServiceImplTest {
|
||||
verify(memberRepository).findById(MEMBER_ID);
|
||||
verify(memberRepository).save(any(Member.class));
|
||||
verify(mockSyncer).sync(any(Member.class));
|
||||
verify(redisUtil).delete("member:info:" + MEMBER_ID);
|
||||
verify(redisUtil).delete("member:detail:" + MEMBER_ID);
|
||||
verify(cacheOperations).delete("member:info:" + MEMBER_ID);
|
||||
verify(cacheOperations).delete("member:detail:" + MEMBER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
package cn.novalon.gym.manage.payment.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.payment.config.HuifuProperties;
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
@@ -46,16 +46,16 @@ public class PaymentServiceImpl implements PaymentService {
|
||||
|
||||
private final HuifuProperties huifuProperties;
|
||||
private final PaymentOrderRepository paymentOrderRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
private final CacheOperations cacheOperations;
|
||||
private final PaymentNotifyService paymentNotifyService;
|
||||
|
||||
public PaymentServiceImpl(HuifuProperties huifuProperties,
|
||||
PaymentOrderRepository paymentOrderRepository,
|
||||
RedisUtil redisUtil,
|
||||
CacheOperations cacheOperations,
|
||||
PaymentNotifyService paymentNotifyService) {
|
||||
this.huifuProperties = huifuProperties;
|
||||
this.paymentOrderRepository = paymentOrderRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
this.cacheOperations = cacheOperations;
|
||||
this.paymentNotifyService = paymentNotifyService;
|
||||
}
|
||||
|
||||
@@ -860,7 +860,7 @@ public class PaymentServiceImpl implements PaymentService {
|
||||
}
|
||||
|
||||
private Mono<Boolean> isTradeProcessed(String reqSeqId) {
|
||||
return redisUtil.hasKey(PROCESSED_TRADE_PREFIX + reqSeqId)
|
||||
return cacheOperations.hasKey(PROCESSED_TRADE_PREFIX + reqSeqId)
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Huifu] 检查交易处理状态异常: reqSeqId={}", reqSeqId, e);
|
||||
return Mono.just(false);
|
||||
@@ -868,7 +868,7 @@ public class PaymentServiceImpl implements PaymentService {
|
||||
}
|
||||
|
||||
private Mono<Void> markTradeProcessed(String reqSeqId) {
|
||||
return redisUtil.setWithExpire(PROCESSED_TRADE_PREFIX + reqSeqId, "1", IDEMPOTENT_EXPIRE_SECONDS)
|
||||
return cacheOperations.setWithExpire(PROCESSED_TRADE_PREFIX + reqSeqId, "1", IDEMPOTENT_EXPIRE_SECONDS)
|
||||
.onErrorResume(e -> {
|
||||
log.error("[Huifu] 标记交易处理状态异常: reqSeqId={}", reqSeqId, e);
|
||||
return Mono.empty();
|
||||
|
||||
+13
-13
@@ -1,6 +1,6 @@
|
||||
package cn.novalon.gym.manage.payment.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.common.cache.CacheOperations;
|
||||
import cn.novalon.gym.manage.payment.config.HuifuProperties;
|
||||
import cn.novalon.gym.manage.payment.dto.CreatePaymentRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.PaymentResponse;
|
||||
@@ -35,7 +35,7 @@ class PaymentServiceImplTest {
|
||||
private PaymentOrderRepository paymentOrderRepository;
|
||||
|
||||
@Mock
|
||||
private RedisUtil redisUtil;
|
||||
private CacheOperations cacheOperations;
|
||||
|
||||
@Mock
|
||||
private PaymentNotifyService paymentNotifyService;
|
||||
@@ -58,7 +58,7 @@ class PaymentServiceImplTest {
|
||||
|
||||
lenient().when(huifuProperties.isSdkInitialized()).thenReturn(false);
|
||||
|
||||
paymentService = new PaymentServiceImpl(huifuProperties, paymentOrderRepository, redisUtil, paymentNotifyService);
|
||||
paymentService = new PaymentServiceImpl(huifuProperties, paymentOrderRepository, cacheOperations, paymentNotifyService);
|
||||
}
|
||||
|
||||
private PaymentOrder createTestOrder(String status) {
|
||||
@@ -369,7 +369,7 @@ class PaymentServiceImplTest {
|
||||
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
|
||||
params.put("sign", "testSign");
|
||||
|
||||
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(true));
|
||||
|
||||
Mono<String> result = paymentService.handleAlipayNotify(params);
|
||||
|
||||
@@ -388,10 +388,10 @@ class PaymentServiceImplTest {
|
||||
|
||||
PaymentOrder order = createTestOrder("PENDING");
|
||||
|
||||
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
|
||||
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenReturn(Mono.just(order));
|
||||
when(redisUtil.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
|
||||
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "SUCCESS", MEMBER_ID)).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = paymentService.handleAlipayNotify(params);
|
||||
@@ -401,7 +401,7 @@ class PaymentServiceImplTest {
|
||||
.verifyComplete();
|
||||
|
||||
verify(paymentOrderRepository).save(any(PaymentOrder.class));
|
||||
verify(redisUtil).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
|
||||
verify(cacheOperations).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
|
||||
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "SUCCESS", MEMBER_ID);
|
||||
}
|
||||
|
||||
@@ -413,10 +413,10 @@ class PaymentServiceImplTest {
|
||||
|
||||
PaymentOrder order = createTestOrder("PENDING");
|
||||
|
||||
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
|
||||
when(paymentOrderRepository.save(any(PaymentOrder.class))).thenReturn(Mono.just(order));
|
||||
when(redisUtil.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
|
||||
when(cacheOperations.setWithExpire("huifu:processed:trade:REQ123", "1", 86400)).thenReturn(Mono.just(true));
|
||||
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "FAIL", MEMBER_ID)).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = paymentService.handleAlipayNotify(params);
|
||||
@@ -426,7 +426,7 @@ class PaymentServiceImplTest {
|
||||
.verifyComplete();
|
||||
|
||||
verify(paymentOrderRepository).save(any(PaymentOrder.class));
|
||||
verify(redisUtil).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
|
||||
verify(cacheOperations).setWithExpire("huifu:processed:trade:REQ123", "1", 86400);
|
||||
verify(paymentNotifyService).notifyPaymentStatus(order.getOrderNo(), "FAIL", MEMBER_ID);
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ class PaymentServiceImplTest {
|
||||
|
||||
PaymentOrder order = createTestOrder("PENDING");
|
||||
|
||||
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.just(order));
|
||||
when(paymentNotifyService.notifyPaymentStatus(order.getOrderNo(), "PENDING", MEMBER_ID)).thenReturn(Mono.empty());
|
||||
|
||||
@@ -458,7 +458,7 @@ class PaymentServiceImplTest {
|
||||
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
|
||||
params.put("sign", "testSign");
|
||||
|
||||
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.empty());
|
||||
|
||||
Mono<String> result = paymentService.handleAlipayNotify(params);
|
||||
@@ -474,7 +474,7 @@ class PaymentServiceImplTest {
|
||||
params.put("resp_data", "{\"req_seq_id\":\"REQ123\",\"hf_seq_id\":\"HF123\",\"trans_stat\":\"S\"}");
|
||||
params.put("sign", "testSign");
|
||||
|
||||
when(redisUtil.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(cacheOperations.hasKey("huifu:processed:trade:REQ123")).thenReturn(Mono.just(false));
|
||||
when(paymentOrderRepository.findByReqSeqId("REQ123")).thenReturn(Mono.error(new RuntimeException("DB error")));
|
||||
|
||||
Mono<String> result = paymentService.handleAlipayNotify(params);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
server:
|
||||
port: 8084
|
||||
|
||||
gym:
|
||||
cache:
|
||||
type: redis
|
||||
|
||||
spring:
|
||||
aop:
|
||||
proxy-target-class: true
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
+4
-4
@@ -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());
|
||||
|
||||
@@ -60,6 +60,10 @@
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package cn.novalon.gym.manage.common.cache;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 缓存自动配置
|
||||
* <p>
|
||||
* 启用 {@link CacheProperties} 配置绑定,并扫描 {@code cn.novalon.gym.manage.common.cache}
|
||||
* 包下的缓存实现组件({@link RedisCacheOperations} / {@link CaffeineCacheOperations})。
|
||||
* </p>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-08-02
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CacheProperties.class)
|
||||
@ComponentScan(basePackageClasses = CacheAutoConfiguration.class)
|
||||
public class CacheAutoConfiguration {
|
||||
}
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
package cn.novalon.gym.manage.common.cache;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 缓存操作接口
|
||||
* <p>
|
||||
* 定义统一的缓存操作抽象,支持通过配置切换不同的缓存实现(Caffeine / Redis)。
|
||||
* 所有业务模块应依赖此接口而非具体实现,实现类由 {@link CacheAutoConfiguration} 按配置自动装配。
|
||||
* </p>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-08-02
|
||||
* @see RedisCacheOperations
|
||||
* @see CaffeineCacheOperations
|
||||
*/
|
||||
public interface CacheOperations {
|
||||
|
||||
/**
|
||||
* 获取缓存值(带类型转换)
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @param clazz 目标类型
|
||||
* @param <T> 返回值类型
|
||||
* @return 缓存值,不存在时返回 {@link Mono#empty()}
|
||||
*/
|
||||
<T> Mono<T> get(String key, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* 获取缓存值(返回 Object)
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @return 缓存值,不存在时返回 {@link Mono#empty()}
|
||||
*/
|
||||
Mono<Object> get(String key);
|
||||
|
||||
/**
|
||||
* 设置缓存值(无过期时间)
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @param value 缓存值
|
||||
* @return true 设置成功
|
||||
*/
|
||||
Mono<Boolean> set(String key, Object value);
|
||||
|
||||
/**
|
||||
* 设置缓存值并指定过期时间
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @param value 缓存值
|
||||
* @param timeoutSeconds 过期时间(秒)
|
||||
* @return true 设置成功
|
||||
*/
|
||||
Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds);
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @return 被删除的键数量
|
||||
*/
|
||||
Mono<Long> delete(String key);
|
||||
|
||||
/**
|
||||
* 判断缓存键是否存在
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @return true 存在
|
||||
*/
|
||||
Mono<Boolean> hasKey(String key);
|
||||
|
||||
/**
|
||||
* 设置过期时间
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @param timeoutSeconds 过期时间(秒)
|
||||
* @return true 设置成功
|
||||
*/
|
||||
Mono<Boolean> expire(String key, long timeoutSeconds);
|
||||
|
||||
/**
|
||||
* 根据通配符模式删除缓存键
|
||||
*
|
||||
* @param pattern 通配符模式,如 {@code member:info:*}
|
||||
* @return 被删除的键数量
|
||||
*/
|
||||
Mono<Long> deleteByPattern(String pattern);
|
||||
}
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
package cn.novalon.gym.manage.common.cache;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 缓存配置属性
|
||||
* <p>
|
||||
* 通过 {@code gym.cache.type} 配置选择缓存实现:
|
||||
* <ul>
|
||||
* <li>{@code redis} — 使用 Redis 实现(默认)</li>
|
||||
* <li>{@code caffeine} — 使用 Caffeine 本地缓存实现</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* gym:
|
||||
* cache:
|
||||
* type: redis # redis | caffeine
|
||||
* caffeine:
|
||||
* initial-capacity: 100
|
||||
* max-size: 10000
|
||||
* expire-after-write-seconds: 300
|
||||
* }</pre>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-08-02
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "gym.cache")
|
||||
public class CacheProperties {
|
||||
|
||||
/** 缓存类型:redis / caffeine,默认 redis */
|
||||
private String type = "redis";
|
||||
|
||||
/** Caffeine 本地缓存专属配置 */
|
||||
private Caffeine caffeine = new Caffeine();
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Caffeine getCaffeine() {
|
||||
return caffeine;
|
||||
}
|
||||
|
||||
public void setCaffeine(Caffeine caffeine) {
|
||||
this.caffeine = caffeine;
|
||||
}
|
||||
|
||||
public static class Caffeine {
|
||||
|
||||
/** 初始容量 */
|
||||
private int initialCapacity = 100;
|
||||
|
||||
/** 最大条数 */
|
||||
private long maxSize = 10000;
|
||||
|
||||
/** 写入后过期时间(秒),默认 5 分钟 */
|
||||
private long expireAfterWriteSeconds = 300;
|
||||
|
||||
public int getInitialCapacity() {
|
||||
return initialCapacity;
|
||||
}
|
||||
|
||||
public void setInitialCapacity(int initialCapacity) {
|
||||
this.initialCapacity = initialCapacity;
|
||||
}
|
||||
|
||||
public long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
public void setMaxSize(long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
public long getExpireAfterWriteSeconds() {
|
||||
return expireAfterWriteSeconds;
|
||||
}
|
||||
|
||||
public void setExpireAfterWriteSeconds(long expireAfterWriteSeconds) {
|
||||
this.expireAfterWriteSeconds = expireAfterWriteSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package cn.novalon.gym.manage.common.cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Caffeine 本地缓存实现
|
||||
* <p>
|
||||
* 基于 Caffeine 的进程内缓存,适合单机部署或开发测试环境。
|
||||
* 当 {@code gym.cache.type=caffeine} 时生效。
|
||||
* </p>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-08-02
|
||||
* @see CacheOperations
|
||||
* @see RedisCacheOperations
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "gym.cache.type", havingValue = "caffeine")
|
||||
public class CaffeineCacheOperations implements CacheOperations {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CaffeineCacheOperations.class);
|
||||
|
||||
private final Cache<String, Object> cache;
|
||||
|
||||
public CaffeineCacheOperations(CacheProperties cacheProperties) {
|
||||
CacheProperties.Caffeine config = cacheProperties.getCaffeine();
|
||||
this.cache = Caffeine.newBuilder()
|
||||
.initialCapacity(config.getInitialCapacity())
|
||||
.maximumSize(config.getMaxSize())
|
||||
.expireAfterWrite(config.getExpireAfterWriteSeconds(), TimeUnit.SECONDS)
|
||||
.recordStats()
|
||||
.build();
|
||||
log.info("Caffeine 缓存已初始化: initialCapacity={}, maxSize={}, expireAfterWrite={}s",
|
||||
config.getInitialCapacity(), config.getMaxSize(), config.getExpireAfterWriteSeconds());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Mono<T> get(String key, Class<T> clazz) {
|
||||
Object value = cache.getIfPresent(key);
|
||||
if (value == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
if (clazz.isInstance(value)) {
|
||||
return Mono.just((T) value);
|
||||
}
|
||||
log.warn("Caffeine 缓存类型不匹配: key={}, expected={}, actual={}",
|
||||
key, clazz.getName(), value.getClass().getName());
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> get(String key) {
|
||||
Object value = cache.getIfPresent(key);
|
||||
if (value == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.just(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> set(String key, Object value) {
|
||||
cache.put(key, value);
|
||||
return Mono.just(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds) {
|
||||
// Caffeine 在构建时已指定 expireAfterWrite,此处仅做 put 操作
|
||||
// 如需 per-entry TTL,可考虑 Caffeine 的 Expiry 接口,但当前场景统一 TTL 已足够
|
||||
cache.put(key, value);
|
||||
return Mono.just(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> delete(String key) {
|
||||
cache.invalidate(key);
|
||||
return Mono.just(1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> hasKey(String key) {
|
||||
return Mono.just(cache.getIfPresent(key) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> expire(String key, long timeoutSeconds) {
|
||||
// Caffeine 不支持为单个 key 动态设置过期时间,直接返回 true(过期由构建时策略统一管理)
|
||||
log.debug("Caffeine 不支持动态设置过期时间,忽略: key={}, timeoutSeconds={}", key, timeoutSeconds);
|
||||
return Mono.just(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> deleteByPattern(String pattern) {
|
||||
Pattern regex = Pattern.compile(pattern.replace("*", ".*"));
|
||||
long count = cache.asMap().keySet().stream()
|
||||
.filter(k -> regex.matcher(k).matches())
|
||||
.peek(cache::invalidate)
|
||||
.count();
|
||||
return Mono.just(count);
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package cn.novalon.gym.manage.common.cache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis 缓存实现
|
||||
* <p>
|
||||
* 基于 {@link ReactiveRedisTemplate} 的响应式 Redis 缓存操作。
|
||||
* 当 {@code gym.cache.type=redis} 时生效(默认值)。
|
||||
* </p>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-08-02
|
||||
* @see CacheOperations
|
||||
* @see CaffeineCacheOperations
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "gym.cache.type", havingValue = "redis", matchIfMissing = true)
|
||||
public class RedisCacheOperations implements CacheOperations {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisCacheOperations.class);
|
||||
|
||||
private final ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
|
||||
|
||||
public RedisCacheOperations(ReactiveRedisTemplate<String, Object> reactiveRedisTemplate) {
|
||||
this.reactiveRedisTemplate = reactiveRedisTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> get(String key, Class<T> clazz) {
|
||||
return reactiveRedisTemplate.opsForValue().get(key)
|
||||
.filter(clazz::isInstance)
|
||||
.map(clazz::cast)
|
||||
.onErrorResume(e -> {
|
||||
log.warn("读取 Redis 缓存失败(可能为旧格式): key={}, error={}", key, e.getMessage());
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> get(String key) {
|
||||
return reactiveRedisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> set(String key, Object value) {
|
||||
return reactiveRedisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds) {
|
||||
return reactiveRedisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeoutSeconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> delete(String key) {
|
||||
return reactiveRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> hasKey(String key) {
|
||||
return reactiveRedisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> expire(String key, long timeoutSeconds) {
|
||||
return reactiveRedisTemplate.expire(key, Duration.ofSeconds(timeoutSeconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> deleteByPattern(String pattern) {
|
||||
return reactiveRedisTemplate.keys(pattern)
|
||||
.flatMap(keys -> reactiveRedisTemplate.delete(keys))
|
||||
.count();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
cn.novalon.gym.manage.common.config.JwtProperties
|
||||
cn.novalon.gym.manage.common.cache.CacheAutoConfiguration
|
||||
+32
-91
@@ -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,15 +13,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 请求缓存服务
|
||||
*
|
||||
* 文件定义:实现网关请求的缓存机制
|
||||
* 涉及业务:响应缓存、缓存失效、缓存统计
|
||||
*
|
||||
* 核心功能:
|
||||
* 1. 请求响应缓存
|
||||
* 2. 缓存键生成
|
||||
* 3. 缓存失效管理
|
||||
* 4. 缓存统计
|
||||
* <p>
|
||||
* 底层缓存存储委托给 {@link CacheOperations} 接口,支持通过配置切换 Redis / Caffeine 实现。
|
||||
* 请求级别的缓存键生成、命中统计等逻辑保留在此服务中。
|
||||
* </p>
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-26
|
||||
@@ -30,35 +26,33 @@ public class RequestCacheService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RequestCacheService.class);
|
||||
|
||||
private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
|
||||
private final CacheOperations cacheOperations;
|
||||
private final Map<String, CacheStats> stats = new ConcurrentHashMap<>();
|
||||
|
||||
private boolean cacheEnabled = true;
|
||||
private Duration defaultTtl = Duration.ofMinutes(5);
|
||||
private int maxCacheSize = 10000;
|
||||
|
||||
public RequestCacheService(CacheOperations cacheOperations) {
|
||||
this.cacheOperations = cacheOperations;
|
||||
}
|
||||
|
||||
public Mono<String> get(ServerHttpRequest request) {
|
||||
if (!cacheEnabled) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
String cacheKey = generateCacheKey(request);
|
||||
CacheEntry entry = cache.get(cacheKey);
|
||||
|
||||
if (entry == null) {
|
||||
recordMiss(cacheKey);
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (isExpired(entry)) {
|
||||
cache.remove(cacheKey);
|
||||
recordMiss(cacheKey);
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return cacheOperations.get(cacheKey, String.class)
|
||||
.doOnNext(value -> {
|
||||
recordHit(cacheKey);
|
||||
logger.debug("Cache hit for key: {}", cacheKey);
|
||||
return Mono.just(entry.getValue());
|
||||
})
|
||||
.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();
|
||||
cacheOperations.deleteByPattern(".*")
|
||||
.doOnSuccess(count -> {
|
||||
stats.clear();
|
||||
logger.info("Cleared all cache entries. Removed {} entries", size);
|
||||
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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
+19
-21
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user