From b456a43700a943846744f57108a373f4fa676da0 Mon Sep 17 00:00:00 2001 From: zhuxinru Date: Tue, 3 Mar 2026 16:49:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=89=8D=E7=AB=AF=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E5=B7=A5=E5=8F=B7/=E5=A7=93=E5=90=8D=E7=AD=89=E6=A8=A1?= =?UTF-8?q?=E7=B3=8A=E5=8C=B9=E9=85=8D=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sdm/common/config/CacheConfig.java | 104 +++++++++++-- .../common/service/UserNameCacheService.java | 140 +++++++++++++++++- .../system/controller/SysUserController.java | 14 ++ 3 files changed, 240 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/com/sdm/common/config/CacheConfig.java b/common/src/main/java/com/sdm/common/config/CacheConfig.java index fedf78ce..dbd87547 100644 --- a/common/src/main/java/com/sdm/common/config/CacheConfig.java +++ b/common/src/main/java/com/sdm/common/config/CacheConfig.java @@ -5,6 +5,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.SimpleCacheErrorHandler; import org.springframework.cache.support.SimpleCacheManager; @@ -12,6 +13,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; @Configuration @EnableCaching @@ -23,16 +26,29 @@ public class CacheConfig { */ @Bean public CacheManager cacheManager() { - SimpleCacheManager cacheManager = new SimpleCacheManager(); - cacheManager.setCaches(Arrays.asList( - // 用户名缓存 - new ConcurrentMapCache("userNames"), - // 查询任务树列表缓存 - new ConcurrentMapCache("taskTreeListCache"), - // 同步待办缓存 - new ConcurrentMapCache("syncTodo") - // 可以添加其他缓存配置 - )); + ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() { + @Override + protected Cache createConcurrentMapCache(String name) { + // 为 allUsers 创建带过期时间的缓存 + if ("allUsers".equals(name)) { + return new ExpiringCache(name, 60, TimeUnit.MINUTES); + } else if ("userSearch".equals(name)) { + return new ExpiringCache(name, 60, TimeUnit.MINUTES); + } + // 其他缓存使用默认实现 + return super.createConcurrentMapCache(name); + } + }; + + // 预声明缓存名称(可选) + cacheManager.setCacheNames(Arrays.asList( + "userNames", + "taskTreeListCache", + "syncTodo", + "allUsers", + "userSearch" + )); + return cacheManager; } @@ -41,15 +57,75 @@ public class CacheConfig { return new SimpleCacheErrorHandler() { @Override public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { - log.warn("缓存获取失败, key: {}, 错误: {}", key, exception.getMessage()); - // 不抛出异常,继续执行 + log.warn("缓存获取失败, cache: {}, key: {}, 错误: {}", + cache != null ? cache.getName() : "unknown", key, exception.getMessage()); } @Override public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { - log.warn("缓存存入失败, key: {}, 错误: {}", key, exception.getMessage()); - // 不抛出异常,继续执行 + log.warn("缓存存入失败, cache: {}, key: {}, 错误: {}", + cache != null ? cache.getName() : "unknown", key, exception.getMessage()); } }; } + + /** + * 自定义过期缓存实现 + */ + public static class ExpiringCache extends ConcurrentMapCache { + + private final long ttl; // 过期时间(毫秒) + private final ConcurrentHashMap cacheMap = new ConcurrentHashMap<>(); + + public ExpiringCache(String name, long ttl, TimeUnit timeUnit) { + super(name); + this.ttl = timeUnit.toMillis(ttl); + } + + @Override + protected Object lookup(Object key) { + CacheEntry entry = cacheMap.get(key); + if (entry == null) { + return null; + } + + // 检查是否过期 + if (System.currentTimeMillis() > entry.expiryTime) { + cacheMap.remove(key); + return null; + } + + return entry.value; + } + + @Override + public void put(Object key, Object value) { + if (value != null) { + cacheMap.put(key, new CacheEntry(value, System.currentTimeMillis() + ttl)); + } + } + + @Override + public void evict(Object key) { + cacheMap.remove(key); + } + + @Override + public void clear() { + cacheMap.clear(); + } + + /** + * 缓存条目,包含值和过期时间 + */ + private static class CacheEntry { + private final Object value; + private final long expiryTime; + + public CacheEntry(Object value, long expiryTime) { + this.value = value; + this.expiryTime = expiryTime; + } + } + } } \ No newline at end of file diff --git a/common/src/main/java/com/sdm/common/service/UserNameCacheService.java b/common/src/main/java/com/sdm/common/service/UserNameCacheService.java index 260aff37..aa5f249f 100644 --- a/common/src/main/java/com/sdm/common/service/UserNameCacheService.java +++ b/common/src/main/java/com/sdm/common/service/UserNameCacheService.java @@ -1,13 +1,17 @@ package com.sdm.common.service; +import com.alibaba.nacos.common.utils.StringUtils; import com.sdm.common.common.SdmResponse; +import com.sdm.common.common.ThreadLocalContext; +import com.sdm.common.entity.req.system.UserListReq; import com.sdm.common.entity.req.system.UserQueryReq; +import com.sdm.common.entity.resp.PageDataResp; import com.sdm.common.entity.resp.system.CIDUserResp; import com.sdm.common.feign.impl.system.SysUserFeignClientImpl; -import com.sdm.common.feign.inter.system.ISysUserFeignClient; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -18,8 +22,136 @@ import java.util.stream.Collectors; @Slf4j public class UserNameCacheService { - @Autowired - private SysUserFeignClientImpl sysUserFeignClient; + private final SysUserFeignClientImpl sysUserFeignClient; + private final CacheManager cacheManager; // 注入CacheManager + + public UserNameCacheService(SysUserFeignClientImpl sysUserFeignClient, + CacheManager cacheManager) { + this.sysUserFeignClient = sysUserFeignClient; + this.cacheManager = cacheManager; + } + + + +// /** +// * 初始化加载缓存 +// */ +// @PostConstruct +// public void init() { +// refreshCache(); +// } +// +// /** +// * 定时刷新缓存 - 每10分钟执行一次 +// */ +// @Scheduled(fixedDelay = 600000) +// @CacheEvict(value = "users", allEntries = true) // 清空所有用户相关的缓存 +// public void refreshCache() { +// try { +// UserListReq userListReq = new UserListReq(); +// userListReq.setTenantId(ThreadLocalContext.getTenantId()); +// userListReq.setCurrent(1); +// userListReq.setSize(9999); +// SdmResponse>> pageDataRespSdmResponse = sysUserFeignClient.listUser(userListReq); +// if (pageDataRespSdmResponse.isSuccess() && pageDataRespSdmResponse.getData() != null) { +// List users = pageDataRespSdmResponse.getData().getData(); +// if (users != null) { +// this.allUsers = new CopyOnWriteArrayList<>(users); +// log.info("缓存刷新成功,用户数:{}", users.size()); +// } +// } +// } catch (Exception e) { +// log.error("缓存刷新失败", e); +// } +// } + + /** + * 获取所有用户列表 - 缓存1小时 + * 第一次调用时加载,之后1小时内从缓存获取 + */ + @Cacheable(value = "allUsers", key = "'userList'") + public List getAllUsers() { + log.info("缓存未命中,从用户系统加载全量数据..."); + long startTime = System.currentTimeMillis(); + + try { + UserListReq userListReq = new UserListReq(); + userListReq.setTenantId(ThreadLocalContext.getTenantId()); + userListReq.setCurrent(1); + userListReq.setSize(9999); + SdmResponse>> pageDataRespSdmResponse = sysUserFeignClient.listUser(userListReq); + if (pageDataRespSdmResponse.isSuccess() && pageDataRespSdmResponse.getData() != null) { + List users = pageDataRespSdmResponse.getData().getData(); + if (CollectionUtils.isNotEmpty(users)) { + log.info("加载完成,用户数:{},耗时:{}ms", users.size(), System.currentTimeMillis() - startTime); + return users; + } + } + return Collections.emptyList(); + } catch (Exception e) { + log.error("加载用户列表失败", e); + return Collections.emptyList(); // 返回空列表,避免缓存null + } + } + + /** + * 根据关键词搜索用户 - 结果缓存1小时 + * key = "search_" + keyword,比如搜索"张三"会缓存为 "search_张三" + */ + @Cacheable(value = "userSearch", key = "'search_' + #keyword") + public List searchUsers(String keyword) { + log.info("===== 缓存未命中,执行搜索:keyword={} =====", keyword); + long startTime = System.currentTimeMillis(); + + try { + // 1. 先获取所有用户(这个方法会单独缓存) + List allUsers = getAllUsers(); + + // 2. 如果没有关键词,返回所有用户 + if (StringUtils.isEmpty(keyword)) { + log.info("空关键词搜索,返回全部用户,数量:{},耗时:{}ms", + allUsers.size(), System.currentTimeMillis() - startTime); + return allUsers; + } + + // 3. 根据关键词过滤 + String lowerKeyword = keyword.trim().toLowerCase(); + List result = allUsers.parallelStream() + .filter(user -> matchesKeyword(user, lowerKeyword)) + .collect(Collectors.toList()); + + log.info("搜索完成,keyword:{},结果数:{},耗时:{}ms", + keyword, result.size(), System.currentTimeMillis() - startTime); + + return result; + + } catch (Exception e) { + log.error("搜索用户失败,keyword:{}", keyword, e); + return Collections.emptyList(); + } + } + + /** + * 手动清除所有缓存 + */ + @CacheEvict(value = "allUsers", key = "'userList'") + public void clearAllCaches() { + log.info("手动清除所有缓存"); + } + + private boolean matchesKeyword(CIDUserResp user, String lowerKeyword) { + if (user == null) return false; + + if (user.getUsername() != null && user.getUsername().toLowerCase().contains(lowerKeyword)) { + return true; + } + + if (user.getNickname() != null && user.getNickname().toLowerCase().contains(lowerKeyword)) { + return true; + } + + return false; + } /** * 批量获取用户名 - 带缓存 TODO 后续加入Redis的时候改成Redis diff --git a/system/src/main/java/com/sdm/system/controller/SysUserController.java b/system/src/main/java/com/sdm/system/controller/SysUserController.java index 326548ed..e743f415 100644 --- a/system/src/main/java/com/sdm/system/controller/SysUserController.java +++ b/system/src/main/java/com/sdm/system/controller/SysUserController.java @@ -8,6 +8,7 @@ import com.sdm.common.entity.req.system.UserQueryReq; import com.sdm.common.entity.resp.system.*; import com.sdm.common.entity.resp.PageDataResp; import com.sdm.common.feign.inter.system.ISysUserFeignClient; +import com.sdm.common.service.UserNameCacheService; import com.sdm.system.model.req.user.*; import com.sdm.system.service.ISimulationRolePermissionService; import com.sdm.system.service.ISysUserService; @@ -35,6 +36,9 @@ public class SysUserController implements ISysUserFeignClient { @Autowired private ISimulationRolePermissionService rolePermissionService; + @Autowired + private UserNameCacheService userNameCacheService; + /** * 新增用户 * @@ -68,6 +72,16 @@ public class SysUserController implements ISysUserFeignClient { return ISysUserService.listUser(req); } + /** + * 通过关键字查询用户列表 + * 关键字:前端输入的模糊查询条件,可能是昵称或工号 + */ + @Operation(summary = "通过关键字查询用户列表", description = "通过关键字查询用户列表") + @GetMapping("/listUserWithKeyWord") + public SdmResponse> listUserWithKeyWord(@Parameter(description = "通过关键字查询用户列表") @RequestParam String keyword) { + return SdmResponse.success(userNameCacheService.searchUsers(keyword)); + } + /** * 查询用户详情 *