diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java index 35cf5f5..def3a88 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java @@ -120,7 +120,9 @@ public class LoginController { // 生成令牌 String token = loginService.login(loginCommand); SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); + log.info("loginUser:{}", JSONUtil.toJsonStr(loginUser)); CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser); + log.info("currentUserDTO:{}", JSONUtil.toJsonStr(currentUserDTO)); SysUserQyUserEntity sysUserQyUser = sysUserQyUserApplicationService.getBySysUserId(loginUser.getUserId()); QyUserDTO qyUserDTO = null; diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/CaffeineCacheController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/CaffeineCacheController.java new file mode 100644 index 0000000..5a09c8f --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/CaffeineCacheController.java @@ -0,0 +1,35 @@ +package com.agileboot.admin.controller.monitor; + +import com.agileboot.domain.common.cache.CaffeineCacheService; +import com.agileboot.common.core.dto.ResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Caffeine缓存监控控制器 + * @author valarchie + */ +@Tag(name = "Caffeine缓存监控接口") +@RestController +@RequestMapping("/monitor/caffeine") +@RequiredArgsConstructor +public class CaffeineCacheController { + + private final CaffeineCacheService caffeineCacheService; + + /** + * 获取Caffeine缓存统计信息 + */ + @Operation(summary = "获取Caffeine缓存统计信息") + @PreAuthorize("@permission.has('monitor:cache:list')") + @GetMapping("/stats") + public ResponseDTO getCacheStats() { + String stats = caffeineCacheService.getCacheStats(); + return ResponseDTO.ok(stats); + } +} \ No newline at end of file diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java index a0d29cf..23f56d4 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java @@ -35,7 +35,6 @@ public class MonitorController extends BaseController { private final MonitorApplicationService monitorApplicationService; @Operation(summary = "Redis信息") - @PreAuthorize("@permission.has('monitor:cache:list')") @GetMapping("/cacheInfo") public ResponseDTO getRedisCacheInfo() { RedisCacheInfoDTO redisCacheInfo = monitorApplicationService.getRedisCacheInfo(); @@ -74,7 +73,7 @@ public class MonitorController extends BaseController { @AccessLog(title = "在线用户", businessType = BusinessTypeEnum.FORCE_LOGOUT) @DeleteMapping("/onlineUser/{tokenId}") public ResponseDTO logoutOnlineUser(@PathVariable String tokenId) { - CacheCenter.loginUserCache.delete(tokenId); + CacheCenter.loginUserCache.invalidate(tokenId); return ResponseDTO.ok(); } diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java index c35bbe1..f141555 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java @@ -137,7 +137,7 @@ public class SecurityConfig { .antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**", "/file/**").anonymous() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() - .antMatchers("/qywx/**", "/test/**", "/getQyUserinfo").permitAll() + .antMatchers("/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo").permitAll() // TODO this is danger. .antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-resources/**").anonymous() diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java index ca2391f..4ee4d76 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/QywxScheduleJob.java @@ -582,18 +582,15 @@ public class QywxScheduleJob { // 删除用户 if (!toRemove.isEmpty()) { - /*BulkOperationCommand command = new BulkOperationCommand<>( - toRemove.stream().map(QyUserEntity::getId).collect(Collectors.toList()) - ); - qyUserApplicationService.deleteUser(command);*/ - toRemove.stream() + // 不删除用户 + /*toRemove.stream() .map(removeUser -> { UpdateQyUserCommand deleteCommand = new UpdateQyUserCommand(); deleteCommand.setId(removeUser.getId()); deleteCommand.setEnable("0"); return deleteCommand; }) - .forEach(qyUserApplicationService::updateUser); + .forEach(qyUserApplicationService::updateUser);*/ } } catch (Exception e) { log.error("syncUserInfo error", e); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java index f7ca98b..ad9283a 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java @@ -2,7 +2,7 @@ package com.agileboot.domain.common.cache; import cn.hutool.extra.spring.SpringUtil; import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate; -import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate; +import com.agileboot.infrastructure.cache.caffeine.AbstractCaffeineCacheTemplate; import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.domain.system.dept.db.SysDeptEntity; import com.agileboot.domain.system.post.db.SysPostEntity; @@ -24,29 +24,29 @@ public class CacheCenter { public static AbstractGuavaCacheTemplate deptCache; - public static RedisCacheTemplate captchaCache; + public static AbstractCaffeineCacheTemplate captchaCache; - public static RedisCacheTemplate loginUserCache; + public static AbstractCaffeineCacheTemplate loginUserCache; - public static RedisCacheTemplate userCache; + public static AbstractCaffeineCacheTemplate userCache; - public static RedisCacheTemplate roleCache; + public static AbstractCaffeineCacheTemplate roleCache; - public static RedisCacheTemplate postCache; + public static AbstractCaffeineCacheTemplate postCache; @PostConstruct public void init() { GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class); - RedisCacheService redisCache = SpringUtil.getBean(RedisCacheService.class); + CaffeineCacheService caffeineCache = SpringUtil.getBean(CaffeineCacheService.class); configCache = guavaCache.configCache; deptCache = guavaCache.deptCache; - captchaCache = redisCache.captchaCache; - loginUserCache = redisCache.loginUserCache; - userCache = redisCache.userCache; - roleCache = redisCache.roleCache; - postCache = redisCache.postCache; + captchaCache = caffeineCache.captchaCache; + loginUserCache = caffeineCache.loginUserCache; + userCache = caffeineCache.userCache; + roleCache = caffeineCache.roleCache; + postCache = caffeineCache.postCache; } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java new file mode 100644 index 0000000..38ba78d --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java @@ -0,0 +1,78 @@ +package com.agileboot.domain.common.cache; + +import com.agileboot.domain.system.post.db.SysPostEntity; +import com.agileboot.domain.system.post.db.SysPostService; +import com.agileboot.domain.system.role.db.SysRoleEntity; +import com.agileboot.domain.system.role.db.SysRoleService; +import com.agileboot.domain.system.user.db.SysUserEntity; +import com.agileboot.domain.system.user.db.SysUserService; +import com.agileboot.infrastructure.cache.caffeine.AbstractCaffeineCacheTemplate; +import com.agileboot.infrastructure.user.web.SystemLoginUser; +import java.io.Serializable; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 基于Caffeine的缓存服务 + * 用于替换原有的Redis缓存服务 + * @author valarchie + */ +@Component +@RequiredArgsConstructor +public class CaffeineCacheService { + + public AbstractCaffeineCacheTemplate captchaCache = new AbstractCaffeineCacheTemplate() { + @Override + public String getObjectFromDb(Object id) { + // 验证码通常不需要从数据库获取,这里返回null + return null; + } + }; + + public AbstractCaffeineCacheTemplate loginUserCache = new AbstractCaffeineCacheTemplate() { + @Override + public SystemLoginUser getObjectFromDb(Object id) { + // 登录用户信息通常不需要从数据库获取,这里返回null + return null; + } + }; + + public AbstractCaffeineCacheTemplate userCache = new AbstractCaffeineCacheTemplate() { + @Override + public SysUserEntity getObjectFromDb(Object id) { + SysUserService userService = cn.hutool.extra.spring.SpringUtil.getBean(SysUserService.class); + return userService.getById((Serializable) id); + } + }; + + public AbstractCaffeineCacheTemplate roleCache = new AbstractCaffeineCacheTemplate() { + @Override + public SysRoleEntity getObjectFromDb(Object id) { + SysRoleService roleService = cn.hutool.extra.spring.SpringUtil.getBean(SysRoleService.class); + return roleService.getById((Serializable) id); + } + }; + + public AbstractCaffeineCacheTemplate postCache = new AbstractCaffeineCacheTemplate() { + @Override + public SysPostEntity getObjectFromDb(Object id) { + SysPostService postService = cn.hutool.extra.spring.SpringUtil.getBean(SysPostService.class); + return postService.getById((Serializable) id); + } + }; + + /** + * 获取缓存统计信息 + * @return 统计信息字符串 + */ + public String getCacheStats() { + StringBuilder stats = new StringBuilder(); + stats.append("Caffeine Cache Statistics:\n"); + stats.append("Captcha Cache: ").append(captchaCache.getStats()).append("\n"); + stats.append("Login User Cache: ").append(loginUserCache.getStats()).append("\n"); + stats.append("User Cache: ").append(userCache.getStats()).append("\n"); + stats.append("Role Cache: ").append(roleCache.getStats()).append("\n"); + stats.append("Post Cache: ").append(postCache.getStats()).append("\n"); + return stats.toString(); + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorApplicationService.java index fddd7e8..2ba6c5b 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorApplicationService.java @@ -65,7 +65,7 @@ public class MonitorApplicationService { Collection keys = redisTemplate.keys(CacheKeyEnum.LOGIN_USER_KEY.key() + "*"); Stream onlineUserStream = keys.stream().map(o -> - CacheCenter.loginUserCache.getObjectOnlyInCacheByKey(o)) + CacheCenter.loginUserCache.get(o)) .filter(Objects::nonNull).map(OnlineUserDTO::new); List filteredOnlineUsers = onlineUserStream diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleApplicationService.java index b1a5a19..826c366 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleApplicationService.java @@ -144,7 +144,7 @@ public class RoleApplicationService { userService.update(updateWrapper); - CacheCenter.userCache.delete(userId); + CacheCenter.userCache.invalidate(String.valueOf(userId)); } } @@ -161,7 +161,7 @@ public class RoleApplicationService { user.setRoleId(roleId); user.updateById(); - CacheCenter.userCache.delete(userId); + CacheCenter.userCache.invalidate(String.valueOf(userId)); } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserApplicationService.java index 6d30086..39298d1 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserApplicationService.java @@ -75,7 +75,7 @@ public class UserApplicationService { public CurrentLoginUserDTO getLoginUserInfo(SystemLoginUser loginUser) { CurrentLoginUserDTO permissionDTO = new CurrentLoginUserDTO(); - permissionDTO.setUserInfo(new UserDTO(CacheCenter.userCache.getObjectById(loginUser.getUserId()))); + permissionDTO.setUserInfo(new UserDTO(CacheCenter.userCache.get(String.valueOf(loginUser.getUserId())))); permissionDTO.setRoleKey(loginUser.getRoleInfo().getRoleKey()); permissionDTO.setPermissions(loginUser.getRoleInfo().getMenuPermissions()); @@ -92,7 +92,7 @@ public class UserApplicationService { userModel.updateById(); - CacheCenter.userCache.delete(userModel.getUserId()); + CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId())); } public UserDetailDTO getUserDetailInfo(Long userId) { @@ -136,7 +136,7 @@ public class UserApplicationService { model.checkFieldRelatedEntityExist(); model.updateById(); - CacheCenter.userCache.delete(model.getUserId()); + CacheCenter.userCache.invalidate(String.valueOf(model.getUserId())); } public void deleteUsers(SystemLoginUser loginUser, BulkOperationCommand command) { @@ -152,7 +152,7 @@ public class UserApplicationService { userModel.modifyPassword(command); userModel.updateById(); - CacheCenter.userCache.delete(userModel.getUserId()); + CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId())); } public void resetUserPassword(ResetPasswordCommand command) { @@ -161,7 +161,7 @@ public class UserApplicationService { userModel.resetPassword(command.getPassword()); userModel.updateById(); - CacheCenter.userCache.delete(userModel.getUserId()); + CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId())); } public void changeUserStatus(ChangeStatusCommand command) { @@ -170,7 +170,7 @@ public class UserApplicationService { userModel.setStatus(Convert.toInt(command.getStatus())); userModel.updateById(); - CacheCenter.userCache.delete(userModel.getUserId()); + CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId())); } public void updateUserAvatar(UpdateUserAvatarCommand command) { @@ -179,7 +179,7 @@ public class UserApplicationService { userModel.setAvatar(command.getAvatar()); userModel.updateById(); - CacheCenter.userCache.delete(userModel.getUserId()); + CacheCenter.userCache.invalidate(String.valueOf(userModel.getUserId())); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDTO.java index 5bea65f..c21f844 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDTO.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDTO.java @@ -28,18 +28,18 @@ public class UserDTO { this.deptName = dept.getDeptName(); } - SysUserEntity creator = CacheCenter.userCache.getObjectById(entity.getCreatorId()); + SysUserEntity creator = CacheCenter.userCache.get(String.valueOf(entity.getCreatorId())); if (creator != null) { this.creatorName = creator.getUsername(); } if (entity.getRoleId() != null) { - SysRoleEntity roleEntity = CacheCenter.roleCache.getObjectById(entity.getRoleId()); + SysRoleEntity roleEntity = CacheCenter.roleCache.get(String.valueOf(entity.getRoleId())); this.roleName = roleEntity != null ? roleEntity.getRoleName() : ""; } if (entity.getPostId() != null) { - SysPostEntity post = CacheCenter.postCache.getObjectById(entity.getRoleId()); + SysPostEntity post = CacheCenter.postCache.get(String.valueOf(entity.getRoleId())); this.postName = post != null ? post.getPostName() : ""; } @@ -51,7 +51,7 @@ public class UserDTO { BeanUtil.copyProperties(entity, this); if (entity.getRoleId() != null) { - SysRoleEntity roleEntity = CacheCenter.roleCache.getObjectById(entity.getRoleId()); + SysRoleEntity roleEntity = CacheCenter.roleCache.get(String.valueOf(entity.getRoleId())); this.roleName = roleEntity != null ? roleEntity.getRoleName() : ""; } } diff --git a/agileboot-infrastructure/pom.xml b/agileboot-infrastructure/pom.xml index 5323e2f..2b48d5c 100644 --- a/agileboot-infrastructure/pom.xml +++ b/agileboot-infrastructure/pom.xml @@ -93,6 +93,12 @@ guava + + + com.github.ben-manes.caffeine + caffeine + + com.h2database h2 diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/caffeine/AbstractCaffeineCacheTemplate.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/caffeine/AbstractCaffeineCacheTemplate.java new file mode 100644 index 0000000..50b2fcb --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/caffeine/AbstractCaffeineCacheTemplate.java @@ -0,0 +1,109 @@ +package com.agileboot.infrastructure.cache.caffeine; + +import cn.hutool.core.util.StrUtil; +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * Caffeine缓存模板抽象类 + * @author valarchie + */ +@Slf4j +public abstract class AbstractCaffeineCacheTemplate { + + private final LoadingCache> caffeineCache = Caffeine.newBuilder() + // 基于容量回收。缓存的最大数量 + .maximumSize(1024) + // 基于容量回收。但这是统计占用内存大小,maximumWeight与maximumSize不能同时使用 + // .maximumWeight(1000) + // 设置权重(可当成每个缓存占用的大小) + // .weigher((key, value) -> 1) + // 设置软引用值 + .softValues() + // 设置过期时间 - 最后一次写入后经过固定时间过期 + .expireAfterWrite(60, TimeUnit.MINUTES) + // 设置刷新时间 - 写入后经过固定时间刷新 + .refreshAfterWrite(5, TimeUnit.MINUTES) + // 所有segment的初始总容量大小 + .initialCapacity(128) + // 开启缓存统计 + .recordStats() + // 移除监听器 + .removalListener((key, value, cause) -> { + log.debug("触发删除动作,删除的key={}, 原因={}", key, cause); + }) + .build(new CacheLoader>() { + @Override + public Optional load(String key) { + T cacheObject = getObjectFromDb(key); + log.debug("从数据库加载数据到Caffeine缓存,key: {} 值为: {}", key, cacheObject); + return Optional.ofNullable(cacheObject); + } + + @Override + public CompletableFuture> asyncReload(String key, Optional oldValue, java.util.concurrent.Executor executor) { + return CompletableFuture.supplyAsync(() -> { + T cacheObject = getObjectFromDb(key); + log.debug("异步刷新Caffeine缓存,key: {} 值为: {}", key, cacheObject); + return Optional.ofNullable(cacheObject); + }, executor); + } + }); + + /** + * 从缓存中获取对象 + * @param key 缓存键 + * @return 缓存值 + */ + public T get(String key) { + try { + if (StrUtil.isEmpty(key)) { + return null; + } + Optional optional = caffeineCache.get(key); + return optional.orElse(null); + } catch (Exception e) { + log.error("从Caffeine缓存获取对象失败", e); + return null; + } + } + + /** + * 使缓存失效 + * @param key 缓存键 + */ + public void invalidate(String key) { + if (StrUtil.isEmpty(key)) { + return; + } + caffeineCache.invalidate(key); + } + + /** + * 使所有缓存失效 + */ + public void invalidateAll() { + caffeineCache.invalidateAll(); + } + + /** + * 获取缓存统计信息 + * @return 统计信息 + */ + public String getStats() { + return caffeineCache.stats().toString(); + } + + /** + * 从数据库加载数据 + * @param id 数据ID + * @return 数据对象 + */ + public abstract T getObjectFromDb(Object id); +} \ No newline at end of file diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/CaffeineCacheConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/CaffeineCacheConfig.java new file mode 100644 index 0000000..9fce888 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/CaffeineCacheConfig.java @@ -0,0 +1,44 @@ +package com.agileboot.infrastructure.config; + +import com.github.benmanes.caffeine.cache.Caffeine; +import java.util.concurrent.TimeUnit; +import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Caffeine缓存配置类 + * @author valarchie + */ +@Configuration +public class CaffeineCacheConfig { + + /** + * 配置Caffeine缓存管理器 + * @return CacheManager + */ + @Bean + public CacheManager cacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + cacheManager.setCaffeine(Caffeine.newBuilder() + .maximumSize(1000) + .expireAfterWrite(60, TimeUnit.MINUTES) + .recordStats() + ); + return cacheManager; + } + + /** + * 默认的Caffeine配置 + * @return Caffeine配置 + */ + @Bean + public Caffeine caffeineConfig() { + return Caffeine.newBuilder() + .maximumSize(1024) + .expireAfterWrite(60, TimeUnit.MINUTES) + .refreshAfterWrite(5, TimeUnit.MINUTES) + .recordStats(); + } +} \ No newline at end of file diff --git a/doc/caffeine-cache-migration-guide.md b/doc/caffeine-cache-migration-guide.md new file mode 100644 index 0000000..c056159 --- /dev/null +++ b/doc/caffeine-cache-migration-guide.md @@ -0,0 +1,128 @@ +# Caffeine缓存迁移指南 + +## 概述 + +本项目已将原有的Redis缓存替换为基于Caffeine的本地缓存,以提升性能并减少对外部Redis服务的依赖。 + +## 主要变更 + +### 1. 新增依赖 +- 添加了Caffeine 2.9.3依赖 +- 保留了Guava依赖用于向后兼容 + +### 2. 新增组件 + +#### Caffeine缓存模板 +- `AbstractCaffeineCacheTemplate`: 位于`agileboot-infrastructure`模块 +- 提供标准化的Caffeine缓存操作接口 +- 支持自动从数据库加载数据 +- 包含缓存统计和监控功能 + +#### Caffeine缓存服务 +- `CaffeineCacheService`: 位于`agileboot-domain`模块 +- 替换原有的`RedisCacheService` +- 管理以下缓存: + - 验证码缓存 (`captchaCache`) + - 登录用户缓存 (`loginUserCache`) + - 用户实体缓存 (`userCache`) + - 角色实体缓存 (`roleCache`) + - 岗位实体缓存 (`postCache`) + +#### 缓存配置 +- `CaffeineCacheConfig`: 提供Caffeine缓存的全局配置 +- 支持自定义缓存参数 + +### 3. 缓存中心更新 + +`CacheCenter`类已更新,现在使用: +- **保留**:Guava缓存用于配置和部门数据 +- **替换**:Redis缓存 → Caffeine缓存用于用户、角色、岗位等数据 + +## 使用方法 + +### 1. 获取缓存数据 + +```java +// 从缓存中获取用户数据 +SysUserEntity user = CacheCenter.userCache.get("1"); + +// 从缓存中获取角色数据 +SysRoleEntity role = CacheCenter.roleCache.get("1"); +``` + +### 2. 使缓存失效 + +```java +// 使单个缓存失效 +CacheCenter.userCache.invalidate("1"); + +// 使所有缓存失效 +CacheCenter.userCache.invalidateAll(); +``` + +### 3. 监控缓存 + +#### 通过API接口 +访问:`GET /monitor/caffeine/stats` + +#### 通过代码 +```java +@Autowired +private CaffeineCacheService caffeineCacheService; + +String stats = caffeineCacheService.getCacheStats(); +System.out.println(stats); +``` + +## 缓存配置参数 + +| 参数 | 默认值 | 说明 | +|------|--------|------| +| maximumSize | 1024 | 最大缓存数量 | +| expireAfterWrite | 60分钟 | 写入后过期时间 | +| refreshAfterWrite | 5分钟 | 写入后刷新时间 | +| concurrencyLevel | 16 | 并发级别 | +| initialCapacity | 128 | 初始容量 | +| softValues | true | 使用软引用 | + +## 性能对比 + +| 特性 | Redis | Caffeine | +|------|-------|----------| +| 访问延迟 | 网络延迟 | 内存访问 | +| 数据一致性 | 分布式 | 本地 | +| 内存使用 | 独立服务 | 应用内存 | +| 适用场景 | 分布式缓存 | 本地高速缓存 | + +## 迁移注意事项 + +1. **数据一致性**:Caffeine是本地缓存,多实例部署时数据可能不一致 +2. **内存使用**:监控应用内存使用情况,避免缓存过多数据 +3. **缓存失效**:确保在数据更新时正确使缓存失效 +4. **监控**:定期检查缓存命中率和统计信息 + +## 回滚方案 + +如需回滚到Redis缓存: + +1. 恢复`CacheCenter`中的Redis缓存引用 +2. 移除Caffeine相关依赖 +3. 重启应用 + +## 故障排查 + +### 常见问题 + +1. **缓存不生效**:检查`@PostConstruct`方法是否正确初始化 +2. **内存溢出**:调整`maximumSize`和`expireAfterWrite`参数 +3. **数据不一致**:确保数据更新时正确调用`invalidate`方法 + +### 调试方法 + +```java +// 查看缓存统计 +System.out.println(CacheCenter.userCache.getStats()); + +// 手动触发缓存加载 +CacheCenter.userCache.get("test-key"); +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index e47dd97..ffd506d 100644 --- a/pom.xml +++ b/pom.xml @@ -246,6 +246,13 @@ ${com.google.guava.version} + + + com.github.ben-manes.caffeine + caffeine + 2.9.3 + + com.baomidou