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:
+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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user