diff --git a/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/CacheConfiguration.java b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/CacheConfiguration.java new file mode 100644 index 0000000..dccf4e1 --- /dev/null +++ b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/CacheConfiguration.java @@ -0,0 +1,33 @@ +package com.ljwx.common.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 缓存配置类 + * 启用异步和定时任务支持 + */ +@Slf4j +@Configuration +@EnableAsync +@EnableScheduling +@EnableAspectJAutoProxy(proxyTargetClass = true) +@ConditionalOnProperty(name = "ljwx.cache.enabled", havingValue = "true", matchIfMissing = true) +public class CacheConfiguration { + + public CacheConfiguration() { + log.info("🚀 Redis缓存高性能查询服务已启用"); + log.info("📊 支持的功能:"); + log.info(" - 租户级别查询优化"); + log.info(" - 用户-部门关联查询优化"); + log.info(" - 用户-设备关联查询优化"); + log.info(" - 部门-设备关联查询优化"); + log.info(" - 业务数据缓存优化"); + log.info(" - 自动缓存预热和定时刷新"); + log.info(" - 批量查询性能优化"); + } +} \ No newline at end of file diff --git a/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/HighPerformanceQueryService.java b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/HighPerformanceQueryService.java new file mode 100644 index 0000000..7df7f8f --- /dev/null +++ b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/HighPerformanceQueryService.java @@ -0,0 +1,676 @@ +package com.ljwx.common.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StopWatch; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +/** + * 高性能关联查询服务 + * 基于Redis缓存和闭包表实现毫秒级多维度关联查询 + */ +@Slf4j +@Service +public class HighPerformanceQueryService { + + @Autowired + private RedisRelationCacheService cacheService; + + @Autowired + private JdbcTemplate jdbcTemplate; + + private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + // ===================================================== + // 租户级别高效查询 + // ===================================================== + + /** + * 获取租户下所有用户(优先从缓存) + * 性能目标: <5ms + */ + public Set getTenantUsers(Long customerId) { + StopWatch stopWatch = new StopWatch("getTenantUsers"); + stopWatch.start("cache-lookup"); + + // 1. 尝试从缓存获取 + Set cachedUsers = cacheService.getTenantUsers(customerId); + if (!CollectionUtils.isEmpty(cachedUsers)) { + stopWatch.stop(); + log.debug("从缓存获取租户用户: customerId={}, users={}, time={}ms", + customerId, cachedUsers.size(), stopWatch.getTotalTimeMillis()); + return cachedUsers; + } + + stopWatch.start("db-query"); + + // 2. 缓存未命中,查询数据库 + String sql = """ + SELECT id FROM sys_user + WHERE customer_id = ? AND is_deleted = 0 AND status = '1' + """; + + List userIds = jdbcTemplate.queryForList(sql, Long.class, customerId); + Set users = new HashSet<>(userIds); + + stopWatch.stop(); + + // 3. 更新缓存 + if (!users.isEmpty()) { + cacheService.cacheTenantUsers(customerId, users); + } + + log.debug("从数据库获取租户用户: customerId={}, users={}, time={}ms", + customerId, users.size(), stopWatch.getTotalTimeMillis()); + + return users; + } + + /** + * 获取租户下所有部门(基于闭包表缓存) + * 性能目标: <5ms + */ + public Set getTenantOrgs(Long customerId) { + // 1. 尝试从缓存获取 + Set cachedOrgs = cacheService.getTenantOrgs(customerId); + if (!CollectionUtils.isEmpty(cachedOrgs)) { + return cachedOrgs; + } + + // 2. 查询数据库 + String sql = """ + SELECT id FROM sys_org_units + WHERE customer_id = ? AND is_deleted = 0 AND status = '1' + """; + + List orgIds = jdbcTemplate.queryForList(sql, Long.class, customerId); + Set orgs = new HashSet<>(orgIds); + + // 3. 更新缓存 + if (!orgs.isEmpty()) { + cacheService.cacheTenantOrgs(customerId, orgs); + } + + return orgs; + } + + // ===================================================== + // 用户-部门关联查询(基于闭包表优化) + // ===================================================== + + /** + * 获取用户所属所有部门(包含层级关系) + * 性能目标: <3ms + */ + public Set getUserOrgs(Long userId) { + // 1. 尝试从缓存获取 + Set cachedOrgs = cacheService.getUserOrgs(userId); + if (!CollectionUtils.isEmpty(cachedOrgs)) { + return cachedOrgs; + } + + // 2. 基于闭包表查询用户所属的所有层级部门 + String sql = """ + SELECT DISTINCT c.ancestor_id as org_id + FROM sys_user_org uo + INNER JOIN sys_org_closure c ON uo.org_id = c.descendant_id + WHERE uo.user_id = ? AND uo.is_deleted = 0 + """; + + List orgIds = jdbcTemplate.queryForList(sql, Long.class, userId); + Set orgs = new HashSet<>(orgIds); + + // 3. 缓存结果 + cacheService.cacheUserOrgs(userId, orgs); + + return orgs; + } + + /** + * 获取部门下所有用户(包含子部门用户) + * 性能目标: <3ms + */ + public Set getOrgUsers(Long orgId) { + // 1. 尝试从缓存获取 + Set cachedUsers = cacheService.getOrgUsers(orgId); + if (!CollectionUtils.isEmpty(cachedUsers)) { + return cachedUsers; + } + + // 2. 基于闭包表查询部门及其子部门的所有用户 + String sql = """ + SELECT DISTINCT uo.user_id + FROM sys_org_closure c + INNER JOIN sys_user_org uo ON c.descendant_id = uo.org_id + INNER JOIN sys_user u ON uo.user_id = u.id + WHERE c.ancestor_id = ? + AND uo.is_deleted = 0 + AND u.is_deleted = 0 + AND u.status = '1' + """; + + List userIds = jdbcTemplate.queryForList(sql, Long.class, orgId); + Set users = new HashSet<>(userIds); + + // 3. 缓存结果 + cacheService.cacheOrgUsers(orgId, users); + + return users; + } + + /** + * 获取部门所有后代部门(基于闭包表) + * 性能目标: <2ms + */ + public Set getOrgDescendants(Long orgId) { + // 1. 尝试从缓存获取 + Set cachedDescendants = cacheService.getOrgDescendants(orgId); + if (!CollectionUtils.isEmpty(cachedDescendants)) { + return cachedDescendants; + } + + // 2. 查询闭包表 + String sql = """ + SELECT descendant_id FROM sys_org_closure + WHERE ancestor_id = ? AND depth > 0 + """; + + List descendantIds = jdbcTemplate.queryForList(sql, Long.class, orgId); + Set descendants = new HashSet<>(descendantIds); + + // 3. 缓存结果 + cacheService.cacheOrgDescendants(orgId, descendants); + + return descendants; + } + + // ===================================================== + // 用户-设备关联查询 + // ===================================================== + + /** + * 获取用户关联的所有设备 + * 性能目标: <3ms + */ + public Set getUserDevices(Long userId) { + // 1. 尝试从缓存获取 + Set cachedDevices = cacheService.getUserDevices(userId); + if (!CollectionUtils.isEmpty(cachedDevices)) { + return cachedDevices; + } + + // 2. 查询数据库 + String sql = """ + SELECT du.device_sn + FROM t_device_user du + INNER JOIN t_device_info d ON du.device_sn = d.serial_number + WHERE du.user_id = ? + AND du.status = 'BIND' + AND d.status = 'online' + """; + + List deviceSns = jdbcTemplate.queryForList(sql, String.class, userId); + Set devices = new HashSet<>(deviceSns); + + // 3. 缓存结果 + cacheService.cacheUserDevices(userId, devices); + + return devices; + } + + /** + * 获取设备关联的用户 + * 性能目标: <2ms + */ + public Long getDeviceUser(String deviceSn) { + // 1. 尝试从缓存获取 + Long cachedUser = cacheService.getDeviceUser(deviceSn); + if (cachedUser != null) { + return cachedUser; + } + + // 2. 查询数据库 + String sql = """ + SELECT du.user_id + FROM t_device_user du + INNER JOIN sys_user u ON du.user_id = u.id + WHERE du.device_sn = ? + AND du.status = 'BIND' + AND u.is_deleted = 0 + """; + + List userIds = jdbcTemplate.queryForList(sql, Long.class, deviceSn); + Long userId = userIds.isEmpty() ? null : userIds.get(0); + + // 3. 缓存结果 + if (userId != null) { + cacheService.cacheDeviceUser(deviceSn, userId); + } + + return userId; + } + + // ===================================================== + // 部门-设备关联查询 + // ===================================================== + + /** + * 获取部门下所有设备(包含子部门设备) + * 性能目标: <5ms + */ + public Set getOrgDevices(Long orgId) { + // 1. 尝试从缓存获取 + Set cachedDevices = cacheService.getOrgDevices(orgId); + if (!CollectionUtils.isEmpty(cachedDevices)) { + return cachedDevices; + } + + // 2. 基于闭包表查询部门及其子部门的所有设备 + String sql = """ + SELECT DISTINCT du.device_sn + FROM sys_org_closure c + INNER JOIN sys_user_org uo ON c.descendant_id = uo.org_id + INNER JOIN sys_user u ON uo.user_id = u.id + INNER JOIN t_device_user du ON u.id = du.user_id + INNER JOIN t_device_info d ON du.device_sn = d.serial_number + WHERE c.ancestor_id = ? + AND uo.is_deleted = 0 + AND u.is_deleted = 0 + AND du.status = 'BIND' + AND d.status = 'online' + """; + + List deviceSns = jdbcTemplate.queryForList(sql, String.class, orgId); + Set devices = new HashSet<>(deviceSns); + + // 3. 缓存结果 + cacheService.cacheOrgDevices(orgId, devices); + + return devices; + } + + /** + * 获取设备所属部门 + * 性能目标: <3ms + */ + public Long getDeviceOrg(String deviceSn) { + // 1. 尝试从缓存获取 + Long cachedOrg = cacheService.getDeviceOrg(deviceSn); + if (cachedOrg != null) { + return cachedOrg; + } + + // 2. 查询数据库 + String sql = """ + SELECT uo.org_id + FROM t_device_user du + INNER JOIN sys_user u ON du.user_id = u.id + INNER JOIN sys_user_org uo ON u.id = uo.user_id + WHERE du.device_sn = ? + AND du.status = 'BIND' + AND u.is_deleted = 0 + AND uo.is_deleted = 0 + LIMIT 1 + """; + + List orgIds = jdbcTemplate.queryForList(sql, Long.class, deviceSn); + Long orgId = orgIds.isEmpty() ? null : orgIds.get(0); + + // 3. 缓存结果 + if (orgId != null) { + cacheService.cacheDeviceOrg(deviceSn, orgId); + } + + return orgId; + } + + // ===================================================== + // 业务数据关联查询 + // ===================================================== + + /** + * 获取用户最新健康数据 + * 性能目标: <3ms + */ + public Map getUserLatestHealth(Long userId) { + // 1. 尝试从缓存获取 + Map cachedHealth = cacheService.getUserLatestHealth(userId); + if (!CollectionUtils.isEmpty(cachedHealth)) { + return cachedHealth; + } + + // 2. 查询数据库 + String sql = """ + SELECT heart_rate, pressure_high, pressure_low, blood_oxygen, + temperature, step, calorie, timestamp + FROM t_user_health_data + WHERE user_id = ? + AND timestamp >= DATE_SUB(NOW(), INTERVAL 24 HOUR) + AND is_deleted = 0 + ORDER BY timestamp DESC + LIMIT 1 + """; + + List> results = jdbcTemplate.queryForList(sql, userId); + Map healthData = results.isEmpty() ? new HashMap<>() : results.get(0); + + // 3. 缓存结果 + if (!healthData.isEmpty()) { + cacheService.cacheUserLatestHealth(userId, healthData); + } + + return healthData; + } + + /** + * 获取用户活跃告警 + * 性能目标: <3ms + */ + public Set getUserActiveAlerts(Long userId) { + // 1. 尝试从缓存获取 + Set cachedAlerts = cacheService.getUserActiveAlerts(userId); + if (!CollectionUtils.isEmpty(cachedAlerts)) { + return cachedAlerts; + } + + // 2. 查询数据库 + String sql = """ + SELECT id FROM t_alert_info + WHERE user_id = ? + AND alert_status IN ('PENDING', 'IN_PROGRESS') + AND create_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) + """; + + List alertIds = jdbcTemplate.queryForList(sql, Long.class, userId); + Set alerts = new HashSet<>(alertIds); + + // 3. 缓存结果 + cacheService.cacheUserActiveAlerts(userId, alerts); + + return alerts; + } + + /** + * 获取部门健康数据汇总 + * 性能目标: <5ms + */ + public Map getOrgHealthSummary(Long orgId) { + // 1. 尝试从缓存获取 + Map cachedSummary = cacheService.getOrgHealthSummary(orgId); + if (!CollectionUtils.isEmpty(cachedSummary)) { + return cachedSummary; + } + + // 2. 基于闭包表查询部门健康数据汇总 + String sql = """ + SELECT COUNT(h.id) as total_records, + COUNT(DISTINCT h.user_id) as total_users, + AVG(h.heart_rate) as avg_heart_rate, + AVG(h.pressure_high) as avg_pressure_high, + AVG(h.pressure_low) as avg_pressure_low, + AVG(h.blood_oxygen) as avg_blood_oxygen, + SUM(h.step) as total_steps, + SUM(h.calorie) as total_calories + FROM sys_org_closure c + INNER JOIN sys_user_org uo ON c.descendant_id = uo.org_id + INNER JOIN sys_user u ON uo.user_id = u.id + INNER JOIN t_user_health_data h ON u.id = h.user_id + WHERE c.ancestor_id = ? + AND uo.is_deleted = 0 + AND u.is_deleted = 0 + AND h.timestamp >= DATE_SUB(NOW(), INTERVAL 24 HOUR) + AND h.is_deleted = 0 + """; + + List> results = jdbcTemplate.queryForList(sql, orgId); + Map summary = results.isEmpty() ? new HashMap<>() : results.get(0); + + // 3. 缓存结果 + if (!summary.isEmpty()) { + cacheService.cacheOrgHealthSummary(orgId, summary); + } + + return summary; + } + + // ===================================================== + // 批量查询优化 + // ===================================================== + + /** + * 批量获取用户设备关系 + * 性能目标: <10ms for 100 users + */ + public Map> batchGetUserDevices(Set userIds) { + if (CollectionUtils.isEmpty(userIds)) { + return new HashMap<>(); + } + + StopWatch stopWatch = new StopWatch("batchGetUserDevices"); + stopWatch.start("cache-batch-lookup"); + + // 1. 批量从缓存获取 + Map> result = cacheService.batchGetUserDevices(userIds); + + // 2. 找出缓存未命中的用户 + Set missingUsers = userIds.stream() + .filter(userId -> !result.containsKey(userId)) + .collect(Collectors.toSet()); + + stopWatch.stop(); + + if (!missingUsers.isEmpty()) { + stopWatch.start("db-batch-query"); + + // 3. 批量查询数据库 + String inClause = missingUsers.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + + String sql = String.format(""" + SELECT du.user_id, GROUP_CONCAT(du.device_sn) as device_sns + FROM t_device_user du + INNER JOIN t_device_info d ON du.device_sn = d.serial_number + WHERE du.user_id IN (%s) + AND du.status = 'BIND' + AND d.status = 'online' + GROUP BY du.user_id + """, inClause); + + List> dbResults = jdbcTemplate.queryForList(sql); + + // 4. 处理查询结果并更新缓存 + for (Map row : dbResults) { + Long userId = ((Number) row.get("user_id")).longValue(); + String deviceSnsStr = (String) row.get("device_sns"); + if (deviceSnsStr != null) { + Set devices = Arrays.stream(deviceSnsStr.split(",")) + .collect(Collectors.toSet()); + result.put(userId, devices); + + // 异步更新缓存 + CompletableFuture.runAsync(() -> + cacheService.cacheUserDevices(userId, devices)); + } + } + + stopWatch.stop(); + } + + log.debug("批量获取用户设备关系: users={}, cache_hits={}, db_queries={}, time={}ms", + userIds.size(), userIds.size() - missingUsers.size(), missingUsers.size(), + stopWatch.getTotalTimeMillis()); + + return result; + } + + /** + * 批量获取部门用户关系 + * 性能目标: <15ms for 50 orgs + */ + public Map> batchGetOrgUsers(Set orgIds) { + if (CollectionUtils.isEmpty(orgIds)) { + return new HashMap<>(); + } + + // 1. 批量从缓存获取 + Map> result = cacheService.batchGetOrgUsers(orgIds); + + // 2. 找出缓存未命中的部门 + Set missingOrgs = orgIds.stream() + .filter(orgId -> !result.containsKey(orgId)) + .collect(Collectors.toSet()); + + if (!missingOrgs.isEmpty()) { + // 3. 并行查询数据库 + missingOrgs.parallelStream().forEach(orgId -> { + Set users = getOrgUsers(orgId); // 这个方法内部会处理缓存 + if (!users.isEmpty()) { + result.put(orgId, users); + } + }); + } + + return result; + } + + // ===================================================== + // 复合查询场景 + // ===================================================== + + /** + * 获取用户完整关系信息(用户-部门-设备-健康数据) + * 性能目标: <8ms + */ + public Map getUserCompleteRelations(Long userId) { + Map result = new HashMap<>(); + + // 并行获取各种关系数据 + CompletableFuture> orgsFuture = CompletableFuture + .supplyAsync(() -> getUserOrgs(userId)); + + CompletableFuture> devicesFuture = CompletableFuture + .supplyAsync(() -> getUserDevices(userId)); + + CompletableFuture> healthFuture = CompletableFuture + .supplyAsync(() -> getUserLatestHealth(userId)); + + CompletableFuture> alertsFuture = CompletableFuture + .supplyAsync(() -> getUserActiveAlerts(userId)); + + // 等待所有异步任务完成 + CompletableFuture.allOf(orgsFuture, devicesFuture, healthFuture, alertsFuture).join(); + + try { + result.put("orgs", orgsFuture.get()); + result.put("devices", devicesFuture.get()); + result.put("latestHealth", healthFuture.get()); + result.put("activeAlerts", alertsFuture.get()); + result.put("timestamp", LocalDateTime.now().format(TIMESTAMP_FORMAT)); + } catch (Exception e) { + log.error("获取用户完整关系信息失败: userId={}", userId, e); + } + + return result; + } + + /** + * 获取部门完整关系信息(部门-用户-设备-健康汇总) + * 性能目标: <10ms + */ + public Map getOrgCompleteRelations(Long orgId) { + Map result = new HashMap<>(); + + // 并行获取各种关系数据 + CompletableFuture> usersFuture = CompletableFuture + .supplyAsync(() -> getOrgUsers(orgId)); + + CompletableFuture> devicesFuture = CompletableFuture + .supplyAsync(() -> getOrgDevices(orgId)); + + CompletableFuture> descendantsFuture = CompletableFuture + .supplyAsync(() -> getOrgDescendants(orgId)); + + CompletableFuture> healthSummaryFuture = CompletableFuture + .supplyAsync(() -> getOrgHealthSummary(orgId)); + + // 等待所有异步任务完成 + CompletableFuture.allOf(usersFuture, devicesFuture, descendantsFuture, healthSummaryFuture).join(); + + try { + result.put("users", usersFuture.get()); + result.put("devices", devicesFuture.get()); + result.put("descendants", descendantsFuture.get()); + result.put("healthSummary", healthSummaryFuture.get()); + result.put("timestamp", LocalDateTime.now().format(TIMESTAMP_FORMAT)); + } catch (Exception e) { + log.error("获取部门完整关系信息失败: orgId={}", orgId, e); + } + + return result; + } + + // ===================================================== + // 缓存管理工具方法 + // ===================================================== + + /** + * 刷新用户相关缓存 + */ + public void refreshUserCache(Long userId) { + log.info("刷新用户相关缓存: userId={}", userId); + + // 清除旧缓存 + cacheService.evictUserCache(userId); + + // 预热新缓存 + CompletableFuture.runAsync(() -> { + getUserOrgs(userId); + getUserDevices(userId); + getUserLatestHealth(userId); + getUserActiveAlerts(userId); + }); + } + + /** + * 刷新部门相关缓存 + */ + public void refreshOrgCache(Long orgId) { + log.info("刷新部门相关缓存: orgId={}", orgId); + + // 清除旧缓存 + cacheService.evictOrgCache(orgId); + + // 预热新缓存 + CompletableFuture.runAsync(() -> { + getOrgUsers(orgId); + getOrgDevices(orgId); + getOrgDescendants(orgId); + getOrgHealthSummary(orgId); + }); + } + + /** + * 刷新设备相关缓存 + */ + public void refreshDeviceCache(String deviceSn) { + log.info("刷新设备相关缓存: deviceSn={}", deviceSn); + + // 清除旧缓存 + cacheService.evictDeviceCache(deviceSn); + + // 预热新缓存 + CompletableFuture.runAsync(() -> { + getDeviceUser(deviceSn); + getDeviceOrg(deviceSn); + }); + } +} \ No newline at end of file diff --git a/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/ICacheWarmupService.java b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/ICacheWarmupService.java new file mode 100644 index 0000000..be54700 --- /dev/null +++ b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/ICacheWarmupService.java @@ -0,0 +1,44 @@ +package com.ljwx.common.cache; + +import java.util.concurrent.CompletableFuture; + +/** + * 缓存预热服务接口 + */ +public interface ICacheWarmupService { + + /** + * 手动触发缓存预热 + */ + void manualWarmup(); + + /** + * 获取预热状态 + */ + boolean isWarmupCompleted(); + + /** + * 定时刷新热点数据缓存 + */ + void refreshHotDataCache(); + + /** + * 预热租户关系数据 + */ + CompletableFuture warmupTenantRelations(); + + /** + * 预热用户-部门关系 + */ + CompletableFuture warmupUserOrgRelations(); + + /** + * 预热设备关系数据 + */ + CompletableFuture warmupDeviceRelations(); + + /** + * 预热组织层级结构 + */ + CompletableFuture warmupOrgHierarchy(); +} \ No newline at end of file diff --git a/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/RedisCacheWarmupService.java b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/RedisCacheWarmupService.java new file mode 100644 index 0000000..880c44b --- /dev/null +++ b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/RedisCacheWarmupService.java @@ -0,0 +1,484 @@ +package com.ljwx.common.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import jakarta.annotation.PostConstruct; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.stream.Collectors; + +/** + * Redis缓存预热和更新服务 + * 系统启动时自动预热核心关系数据,定时更新热点数据 + */ +@Slf4j +@Service +public class RedisCacheWarmupService implements ApplicationRunner, ICacheWarmupService { + + @Autowired + private RedisRelationCacheService relationCacheService; + + @Autowired + private JdbcTemplate jdbcTemplate; + + // 分批处理大小 + private static final int BATCH_SIZE = 1000; + + // 预热状态追踪 + private boolean warmupCompleted = false; + + /** + * 应用启动后自动执行缓存预热 + */ + @Override + public void run(ApplicationArguments args) { + log.info("开始执行Redis缓存预热..."); + long startTime = System.currentTimeMillis(); + + try { + // 并行执行预热任务 + CompletableFuture tenantWarmup = warmupTenantRelations(); + CompletableFuture userOrgWarmup = warmupUserOrgRelations(); + CompletableFuture deviceWarmup = warmupDeviceRelations(); + CompletableFuture orgHierarchyWarmup = warmupOrgHierarchy(); + + // 等待所有预热任务完成 + CompletableFuture.allOf(tenantWarmup, userOrgWarmup, deviceWarmup, orgHierarchyWarmup).join(); + + warmupCompleted = true; + long duration = System.currentTimeMillis() - startTime; + log.info("Redis缓存预热完成,耗时: {}ms", duration); + + } catch (Exception e) { + log.error("Redis缓存预热失败", e); + } + } + + /** + * 预热租户关系数据 + */ + @Override + @Async + public CompletableFuture warmupTenantRelations() { + log.info("开始预热租户关系数据..."); + + try { + // 1. 预热租户-用户关系 + String userSql = """ + SELECT customer_id, GROUP_CONCAT(id) as user_ids + FROM sys_user + WHERE is_deleted = 0 AND status = '1' + GROUP BY customer_id + """; + + List> tenantUsers = jdbcTemplate.queryForList(userSql); + for (Map row : tenantUsers) { + Long customerId = ((Number) row.get("customer_id")).longValue(); + String userIdsStr = (String) row.get("user_ids"); + if (userIdsStr != null) { + Set userIds = Arrays.stream(userIdsStr.split(",")) + .map(Long::valueOf) + .collect(Collectors.toSet()); + relationCacheService.cacheTenantUsers(customerId, userIds); + } + } + + // 2. 预热租户-部门关系 + String orgSql = """ + SELECT customer_id, GROUP_CONCAT(id) as org_ids + FROM sys_org_units + WHERE is_deleted = 0 AND status = '1' + GROUP BY customer_id + """; + + List> tenantOrgs = jdbcTemplate.queryForList(orgSql); + for (Map row : tenantOrgs) { + Long customerId = ((Number) row.get("customer_id")).longValue(); + String orgIdsStr = (String) row.get("org_ids"); + if (orgIdsStr != null) { + Set orgIds = Arrays.stream(orgIdsStr.split(",")) + .map(Long::valueOf) + .collect(Collectors.toSet()); + relationCacheService.cacheTenantOrgs(customerId, orgIds); + } + } + + log.info("租户关系数据预热完成: {} 个租户", tenantUsers.size()); + + } catch (Exception e) { + log.error("租户关系数据预热失败", e); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * 预热用户-部门关系(基于闭包表) + */ + @Override + @Async + public CompletableFuture warmupUserOrgRelations() { + log.info("开始预热用户-部门关系数据..."); + + try { + // 1. 预热用户-部门关系 + String userOrgSql = """ + SELECT uo.user_id, GROUP_CONCAT(uo.org_id) as org_ids + FROM sys_user_org uo + INNER JOIN sys_user u ON uo.user_id = u.id + WHERE uo.is_deleted = 0 AND u.is_deleted = 0 + GROUP BY uo.user_id + """; + + List> userOrgs = jdbcTemplate.queryForList(userOrgSql); + for (Map row : userOrgs) { + Long userId = ((Number) row.get("user_id")).longValue(); + String orgIdsStr = (String) row.get("org_ids"); + if (orgIdsStr != null) { + Set orgIds = Arrays.stream(orgIdsStr.split(",")) + .map(Long::valueOf) + .collect(Collectors.toSet()); + relationCacheService.cacheUserOrgs(userId, orgIds); + } + } + + // 2. 预热部门-用户关系(包含子部门用户) + String orgUserSql = """ + SELECT o.id as org_id, + GROUP_CONCAT(DISTINCT u.id) as direct_user_ids + FROM sys_org_units o + LEFT JOIN sys_user_org uo ON o.id = uo.org_id AND uo.is_deleted = 0 + LEFT JOIN sys_user u ON uo.user_id = u.id AND u.is_deleted = 0 + WHERE o.is_deleted = 0 AND o.status = '1' + GROUP BY o.id + """; + + List> orgUsers = jdbcTemplate.queryForList(orgUserSql); + for (Map row : orgUsers) { + Long orgId = ((Number) row.get("org_id")).longValue(); + String userIdsStr = (String) row.get("direct_user_ids"); + + // 获取包含子部门的所有用户 + Set allUserIds = getAllOrgUsers(orgId); + if (!allUserIds.isEmpty()) { + relationCacheService.cacheOrgUsers(orgId, allUserIds); + } + } + + log.info("用户-部门关系数据预热完成: {} 个用户, {} 个部门", userOrgs.size(), orgUsers.size()); + + } catch (Exception e) { + log.error("用户-部门关系数据预热失败", e); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * 预热设备关系数据 + */ + @Override + @Async + public CompletableFuture warmupDeviceRelations() { + log.info("开始预热设备关系数据..."); + + try { + // 1. 预热用户-设备关系 (解决字符集冲突问题) + String userDeviceSql = """ + SELECT du.user_id, GROUP_CONCAT(du.device_sn) as device_sns + FROM t_device_user du + INNER JOIN sys_user u ON du.user_id = u.id + INNER JOIN t_device_info d ON du.device_sn COLLATE utf8mb4_general_ci = d.serial_number + WHERE u.is_deleted = 0 AND d.status = 'online' + GROUP BY du.user_id + """; + + List> userDevices = jdbcTemplate.queryForList(userDeviceSql); + for (Map row : userDevices) { + Long userId = ((Number) row.get("user_id")).longValue(); + String deviceSnsStr = (String) row.get("device_sns"); + if (deviceSnsStr != null) { + Set deviceSns = Arrays.stream(deviceSnsStr.split(",")) + .collect(Collectors.toSet()); + relationCacheService.cacheUserDevices(userId, deviceSns); + } + } + + // 2. 预热设备-用户关系 (解决字符集冲突问题) + String deviceUserSql = """ + SELECT du.device_sn, du.user_id + FROM t_device_user du + INNER JOIN sys_user u ON du.user_id = u.id + INNER JOIN t_device_info d ON du.device_sn COLLATE utf8mb4_general_ci = d.serial_number + WHERE u.is_deleted = 0 AND d.status = 'online' + """; + + List> deviceUsers = jdbcTemplate.queryForList(deviceUserSql); + for (Map row : deviceUsers) { + String deviceSn = (String) row.get("device_sn"); + Long userId = ((Number) row.get("user_id")).longValue(); + relationCacheService.cacheDeviceUser(deviceSn, userId); + } + + // 3. 预热设备-部门关系 (解决字符集冲突问题) + String deviceOrgSql = """ + SELECT d.serial_number, uo.org_id + FROM t_device_info d + INNER JOIN t_device_user du ON d.serial_number = du.device_sn COLLATE utf8mb4_general_ci + INNER JOIN sys_user u ON du.user_id = u.id + INNER JOIN sys_user_org uo ON u.id = uo.user_id + WHERE d.status = 'online' AND u.is_deleted = 0 AND uo.is_deleted = 0 + """; + + List> deviceOrgs = jdbcTemplate.queryForList(deviceOrgSql); + for (Map row : deviceOrgs) { + String deviceSn = (String) row.get("serial_number"); + Long orgId = ((Number) row.get("org_id")).longValue(); + relationCacheService.cacheDeviceOrg(deviceSn, orgId); + } + + // 4. 预热部门-设备关系(包含子部门设备) + preloadOrgDevices(); + + log.info("设备关系数据预热完成: {} 个用户设备, {} 个设备用户", userDevices.size(), deviceUsers.size()); + + } catch (Exception e) { + log.error("设备关系数据预热失败", e); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * 预热组织层级结构(基于闭包表) + */ + @Override + @Async + public CompletableFuture warmupOrgHierarchy() { + log.info("开始预热组织层级结构数据..."); + + try { + // 预热部门后代关系 + String descendantsSql = """ + SELECT ancestor_id, GROUP_CONCAT(descendant_id) as descendant_ids + FROM sys_org_closure + WHERE depth > 0 + GROUP BY ancestor_id + """; + + List> orgDescendants = jdbcTemplate.queryForList(descendantsSql); + for (Map row : orgDescendants) { + Long ancestorId = ((Number) row.get("ancestor_id")).longValue(); + String descendantIdsStr = (String) row.get("descendant_ids"); + if (descendantIdsStr != null) { + Set descendantIds = Arrays.stream(descendantIdsStr.split(",")) + .map(Long::valueOf) + .collect(Collectors.toSet()); + relationCacheService.cacheOrgDescendants(ancestorId, descendantIds); + } + } + + log.info("组织层级结构预热完成: {} 个层级关系", orgDescendants.size()); + + } catch (Exception e) { + log.error("组织层级结构预热失败", e); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * 定时更新热点数据缓存 - 每5分钟执行 + */ + @Override + @Scheduled(fixedRate = 300000) + public void refreshHotDataCache() { + if (!warmupCompleted) { + return; + } + + log.debug("开始刷新热点数据缓存..."); + + try { + // 1. 刷新用户最新健康数据 + refreshUserLatestHealthData(); + + // 2. 刷新用户活跃告警数据 + refreshUserActiveAlerts(); + + // 3. 刷新部门健康数据汇总 + refreshOrgHealthSummary(); + + } catch (Exception e) { + log.error("热点数据缓存刷新失败", e); + } + } + + /** + * 刷新用户最新健康数据 + */ + private void refreshUserLatestHealthData() { + String sql = """ + SELECT h.user_id, + h.heart_rate, + h.pressure_high, + h.pressure_low, + h.blood_oxygen, + h.temperature, + h.timestamp + FROM t_user_health_data h + INNER JOIN ( + SELECT user_id, MAX(timestamp) as latest_time + FROM t_user_health_data + WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 24 HOUR) + AND is_deleted = 0 + GROUP BY user_id + ) latest ON h.user_id = latest.user_id AND h.timestamp = latest.latest_time + WHERE h.is_deleted = 0 + """; + + List> latestHealthData = jdbcTemplate.queryForList(sql); + for (Map row : latestHealthData) { + Long userId = ((Number) row.get("user_id")).longValue(); + Map healthData = new HashMap<>(row); + healthData.remove("user_id"); + relationCacheService.cacheUserLatestHealth(userId, healthData); + } + + log.debug("刷新用户最新健康数据: {} 条", latestHealthData.size()); + } + + /** + * 刷新用户活跃告警数据 + */ + private void refreshUserActiveAlerts() { + String sql = """ + SELECT user_id, GROUP_CONCAT(id) as alert_ids + FROM t_alert_info + WHERE alert_status IN ('PENDING', 'IN_PROGRESS') + AND create_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) + GROUP BY user_id + """; + + List> activeAlerts = jdbcTemplate.queryForList(sql); + for (Map row : activeAlerts) { + Long userId = ((Number) row.get("user_id")).longValue(); + String alertIdsStr = (String) row.get("alert_ids"); + if (alertIdsStr != null) { + Set alertIds = Arrays.stream(alertIdsStr.split(",")) + .map(Long::valueOf) + .collect(Collectors.toSet()); + relationCacheService.cacheUserActiveAlerts(userId, alertIds); + } + } + + log.debug("刷新用户活跃告警数据: {} 个用户", activeAlerts.size()); + } + + /** + * 刷新部门健康数据汇总 + */ + private void refreshOrgHealthSummary() { + String sql = """ + SELECT uo.org_id, + COUNT(h.id) as total_records, + AVG(h.heart_rate) as avg_heart_rate, + AVG(h.pressure_high) as avg_pressure_high, + AVG(h.pressure_low) as avg_pressure_low, + AVG(h.blood_oxygen) as avg_blood_oxygen + FROM sys_user_org uo + INNER JOIN sys_user u ON uo.user_id = u.id + LEFT JOIN t_user_health_data h ON u.id = h.user_id + AND h.timestamp >= DATE_SUB(NOW(), INTERVAL 24 HOUR) + AND h.is_deleted = 0 + WHERE uo.is_deleted = 0 AND u.is_deleted = 0 + GROUP BY uo.org_id + HAVING total_records > 0 + """; + + List> orgSummaries = jdbcTemplate.queryForList(sql); + for (Map row : orgSummaries) { + Long orgId = ((Number) row.get("org_id")).longValue(); + Map summary = new HashMap<>(row); + summary.remove("org_id"); + relationCacheService.cacheOrgHealthSummary(orgId, summary); + } + + log.debug("刷新部门健康数据汇总: {} 个部门", orgSummaries.size()); + } + + /** + * 获取部门及其子部门的所有用户 + */ + private Set getAllOrgUsers(Long orgId) { + // 使用闭包表获取所有后代部门 + String sql = """ + SELECT DISTINCT u.id + FROM sys_org_closure c + INNER JOIN sys_user_org uo ON c.descendant_id = uo.org_id + INNER JOIN sys_user u ON uo.user_id = u.id + WHERE c.ancestor_id = ? AND uo.is_deleted = 0 AND u.is_deleted = 0 + """; + + List userIds = jdbcTemplate.queryForList(sql, Long.class, orgId); + return new HashSet<>(userIds); + } + + /** + * 预加载部门设备关系 + */ + private void preloadOrgDevices() { + // 获取所有部门ID + List orgIds = jdbcTemplate.queryForList( + "SELECT id FROM sys_org_units WHERE is_deleted = 0", Long.class); + + for (Long orgId : orgIds) { + // 获取部门及其子部门的所有设备 (解决字符集冲突问题) + String sql = """ + SELECT DISTINCT d.serial_number + FROM sys_org_closure c + INNER JOIN sys_user_org uo ON c.descendant_id = uo.org_id + INNER JOIN sys_user u ON uo.user_id = u.id + INNER JOIN t_device_user du ON u.id = du.user_id + INNER JOIN t_device_info d ON du.device_sn COLLATE utf8mb4_general_ci = d.serial_number + WHERE c.ancestor_id = ? + AND uo.is_deleted = 0 + AND u.is_deleted = 0 + AND d.status = 'online' + """; + + List deviceSns = jdbcTemplate.queryForList(sql, String.class, orgId); + if (!deviceSns.isEmpty()) { + relationCacheService.cacheOrgDevices(orgId, new HashSet<>(deviceSns)); + } + } + } + + /** + * 手动触发缓存预热 + */ + @Override + public void manualWarmup() { + log.info("手动触发缓存预热..."); + warmupCompleted = false; + run(null); + } + + /** + * 获取预热状态 + */ + @Override + public boolean isWarmupCompleted() { + return warmupCompleted; + } +} \ No newline at end of file diff --git a/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/RedisRelationCacheService.java b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/RedisRelationCacheService.java new file mode 100644 index 0000000..e7ae8a9 --- /dev/null +++ b/ljwx-boot/ljwx-boot-common/src/main/java/com/ljwx/common/cache/RedisRelationCacheService.java @@ -0,0 +1,454 @@ +package com.ljwx.common.cache; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Redis关系缓存服务 - 高性能多维度关联查询 + * 基于闭包表和Redis实现毫秒级查询响应 + */ +@Service +public class RedisRelationCacheService { + + @Autowired + private RedisTemplate redisTemplate; + + // ===================================================== + // 缓存KEY常量定义 + // ===================================================== + + // 租户相关 + private static final String TENANT_USERS = "tenant:users:"; // 租户-用户映射 + private static final String TENANT_ORGS = "tenant:orgs:"; // 租户-部门映射 + private static final String TENANT_DEVICES = "tenant:devices:"; // 租户-设备映射 + + // 用户关联 + private static final String USER_ORGS = "user:orgs:"; // 用户-部门映射 + private static final String USER_DEVICES = "user:devices:"; // 用户-设备映射 + private static final String USER_POSITIONS = "user:positions:"; // 用户-岗位映射 + + // 部门关联 + private static final String ORG_USERS = "org:users:"; // 部门-用户映射 + private static final String ORG_DEVICES = "org:devices:"; // 部门-设备映射 + private static final String ORG_DESCENDANTS = "org:descendants:"; // 部门-后代映射 + private static final String ORG_ANCESTORS = "org:ancestors:"; // 部门-祖先映射 + + // 设备关联 + private static final String DEVICE_USER = "device:user:"; // 设备-用户映射 + private static final String DEVICE_ORG = "device:org:"; // 设备-部门映射 + + // 业务数据关联 + private static final String USER_HEALTH_LATEST = "user:health:latest:"; // 用户最新健康数据 + private static final String ORG_HEALTH_SUMMARY = "org:health:summary:"; // 部门健康数据汇总 + private static final String USER_ALERTS_ACTIVE = "user:alerts:active:"; // 用户活跃告警 + private static final String ORG_ALERTS_SUMMARY = "org:alerts:summary:"; // 部门告警汇总 + + // 缓存过期时间(秒) + private static final long CACHE_EXPIRE_SHORT = 300; // 5分钟 - 频繁变更数据 + private static final long CACHE_EXPIRE_MEDIUM = 1800; // 30分钟 - 一般关系数据 + private static final long CACHE_EXPIRE_LONG = 3600; // 1小时 - 相对稳定数据 + + // ===================================================== + // 租户级别查询 - 顶层缓存 + // ===================================================== + + /** + * 获取租户下所有用户ID列表 + */ + public Set getTenantUsers(Long customerId) { + String key = TENANT_USERS + customerId; + Set userIds = redisTemplate.opsForSet().members(key); + return convertToLongSet(userIds); + } + + /** + * 缓存租户用户关系 + */ + public void cacheTenantUsers(Long customerId, Set userIds) { + String key = TENANT_USERS + customerId; + if (!CollectionUtils.isEmpty(userIds)) { + redisTemplate.opsForSet().add(key, userIds.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_LONG, TimeUnit.SECONDS); + } + } + + /** + * 获取租户下所有部门ID列表 + */ + public Set getTenantOrgs(Long customerId) { + String key = TENANT_ORGS + customerId; + Set orgIds = redisTemplate.opsForSet().members(key); + return convertToLongSet(orgIds); + } + + /** + * 缓存租户部门关系 + */ + public void cacheTenantOrgs(Long customerId, Set orgIds) { + String key = TENANT_ORGS + customerId; + if (!CollectionUtils.isEmpty(orgIds)) { + redisTemplate.opsForSet().add(key, orgIds.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_LONG, TimeUnit.SECONDS); + } + } + + // ===================================================== + // 用户-部门关联查询 (基于闭包表优化) + // ===================================================== + + /** + * 获取用户所属部门列表(包含层级关系) + */ + public Set getUserOrgs(Long userId) { + String key = USER_ORGS + userId; + Set orgIds = redisTemplate.opsForSet().members(key); + return convertToLongSet(orgIds); + } + + /** + * 缓存用户部门关系 + */ + public void cacheUserOrgs(Long userId, Set orgIds) { + String key = USER_ORGS + userId; + redisTemplate.delete(key); // 清除旧数据 + if (!CollectionUtils.isEmpty(orgIds)) { + redisTemplate.opsForSet().add(key, orgIds.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + } + + /** + * 获取部门下所有用户(包含子部门用户) + */ + public Set getOrgUsers(Long orgId) { + String key = ORG_USERS + orgId; + Set userIds = redisTemplate.opsForSet().members(key); + return convertToLongSet(userIds); + } + + /** + * 缓存部门用户关系(包含子部门) + */ + public void cacheOrgUsers(Long orgId, Set userIds) { + String key = ORG_USERS + orgId; + redisTemplate.delete(key); + if (!CollectionUtils.isEmpty(userIds)) { + redisTemplate.opsForSet().add(key, userIds.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + } + + /** + * 获取部门所有后代部门ID(基于闭包表) + */ + public Set getOrgDescendants(Long orgId) { + String key = ORG_DESCENDANTS + orgId; + Set descendants = redisTemplate.opsForSet().members(key); + return convertToLongSet(descendants); + } + + /** + * 缓存部门后代关系 + */ + public void cacheOrgDescendants(Long orgId, Set descendants) { + String key = ORG_DESCENDANTS + orgId; + redisTemplate.delete(key); + if (!CollectionUtils.isEmpty(descendants)) { + redisTemplate.opsForSet().add(key, descendants.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_LONG, TimeUnit.SECONDS); // 层级关系相对稳定 + } + } + + // ===================================================== + // 用户-设备关联查询 + // ===================================================== + + /** + * 获取用户关联的设备列表 + */ + public Set getUserDevices(Long userId) { + String key = USER_DEVICES + userId; + Set deviceSns = redisTemplate.opsForSet().members(key); + return convertToStringSet(deviceSns); + } + + /** + * 缓存用户设备关系 + */ + public void cacheUserDevices(Long userId, Set deviceSns) { + String key = USER_DEVICES + userId; + redisTemplate.delete(key); + if (!CollectionUtils.isEmpty(deviceSns)) { + redisTemplate.opsForSet().add(key, deviceSns.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + } + + /** + * 获取设备关联的用户 + */ + public Long getDeviceUser(String deviceSn) { + String key = DEVICE_USER + deviceSn; + Object userId = redisTemplate.opsForValue().get(key); + return userId != null ? Long.valueOf(userId.toString()) : null; + } + + /** + * 缓存设备用户关系 + */ + public void cacheDeviceUser(String deviceSn, Long userId) { + String key = DEVICE_USER + deviceSn; + redisTemplate.opsForValue().set(key, userId, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + + // ===================================================== + // 部门-设备关联查询 + // ===================================================== + + /** + * 获取部门下所有设备(包含子部门设备) + */ + public Set getOrgDevices(Long orgId) { + String key = ORG_DEVICES + orgId; + Set deviceSns = redisTemplate.opsForSet().members(key); + return convertToStringSet(deviceSns); + } + + /** + * 缓存部门设备关系 + */ + public void cacheOrgDevices(Long orgId, Set deviceSns) { + String key = ORG_DEVICES + orgId; + redisTemplate.delete(key); + if (!CollectionUtils.isEmpty(deviceSns)) { + redisTemplate.opsForSet().add(key, deviceSns.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + } + + /** + * 获取设备所属部门 + */ + public Long getDeviceOrg(String deviceSn) { + String key = DEVICE_ORG + deviceSn; + Object orgId = redisTemplate.opsForValue().get(key); + return orgId != null ? Long.valueOf(orgId.toString()) : null; + } + + /** + * 缓存设备部门关系 + */ + public void cacheDeviceOrg(String deviceSn, Long orgId) { + String key = DEVICE_ORG + deviceSn; + redisTemplate.opsForValue().set(key, orgId, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + + // ===================================================== + // 业务数据缓存查询 + // ===================================================== + + /** + * 获取用户最新健康数据 + */ + public Map getUserLatestHealth(Long userId) { + String key = USER_HEALTH_LATEST + userId; + Map healthData = redisTemplate.opsForHash().entries(key); + return convertToStringObjectMap(healthData); + } + + /** + * 缓存用户最新健康数据 + */ + public void cacheUserLatestHealth(Long userId, Map healthData) { + String key = USER_HEALTH_LATEST + userId; + if (!CollectionUtils.isEmpty(healthData)) { + redisTemplate.opsForHash().putAll(key, healthData); + redisTemplate.expire(key, CACHE_EXPIRE_SHORT, TimeUnit.SECONDS); + } + } + + /** + * 获取用户活跃告警列表 + */ + public Set getUserActiveAlerts(Long userId) { + String key = USER_ALERTS_ACTIVE + userId; + Set alertIds = redisTemplate.opsForSet().members(key); + return convertToLongSet(alertIds); + } + + /** + * 缓存用户活跃告警 + */ + public void cacheUserActiveAlerts(Long userId, Set alertIds) { + String key = USER_ALERTS_ACTIVE + userId; + redisTemplate.delete(key); + if (!CollectionUtils.isEmpty(alertIds)) { + redisTemplate.opsForSet().add(key, alertIds.toArray()); + redisTemplate.expire(key, CACHE_EXPIRE_SHORT, TimeUnit.SECONDS); + } + } + + /** + * 获取部门健康数据汇总 + */ + public Map getOrgHealthSummary(Long orgId) { + String key = ORG_HEALTH_SUMMARY + orgId; + Map summary = redisTemplate.opsForHash().entries(key); + return convertToStringObjectMap(summary); + } + + /** + * 缓存部门健康数据汇总 + */ + public void cacheOrgHealthSummary(Long orgId, Map summary) { + String key = ORG_HEALTH_SUMMARY + orgId; + if (!CollectionUtils.isEmpty(summary)) { + redisTemplate.opsForHash().putAll(key, summary); + redisTemplate.expire(key, CACHE_EXPIRE_MEDIUM, TimeUnit.SECONDS); + } + } + + // ===================================================== + // 批量操作优化 + // ===================================================== + + /** + * 批量获取用户设备关系 + */ + public Map> batchGetUserDevices(Set userIds) { + if (CollectionUtils.isEmpty(userIds)) { + return new HashMap<>(); + } + + Map> result = new HashMap<>(); + + // 逐个查询,因为Redis Set操作不支持批量获取members + for (Long userId : userIds) { + Set devices = getUserDevices(userId); + if (!devices.isEmpty()) { + result.put(userId, devices); + } + } + + return result; + } + + /** + * 批量获取部门用户关系 + */ + public Map> batchGetOrgUsers(Set orgIds) { + if (CollectionUtils.isEmpty(orgIds)) { + return new HashMap<>(); + } + + Map> result = new HashMap<>(); + + for (Long orgId : orgIds) { + Set users = getOrgUsers(orgId); + if (!CollectionUtils.isEmpty(users)) { + result.put(orgId, users); + } + } + + return result; + } + + // ===================================================== + // 缓存失效和清理 + // ===================================================== + + /** + * 清除用户相关缓存 + */ + public void evictUserCache(Long userId) { + Set keys = redisTemplate.keys("*:*:" + userId + "*"); + if (!CollectionUtils.isEmpty(keys)) { + redisTemplate.delete(keys); + } + } + + /** + * 清除部门相关缓存 + */ + public void evictOrgCache(Long orgId) { + Set keys = redisTemplate.keys("*:*:" + orgId + "*"); + if (!CollectionUtils.isEmpty(keys)) { + redisTemplate.delete(keys); + } + } + + /** + * 清除设备相关缓存 + */ + public void evictDeviceCache(String deviceSn) { + Set keys = redisTemplate.keys("*:*:" + deviceSn + "*"); + if (!CollectionUtils.isEmpty(keys)) { + redisTemplate.delete(keys); + } + } + + /** + * 清除租户相关缓存 + */ + public void evictTenantCache(Long customerId) { + Set keys = redisTemplate.keys("tenant:*:" + customerId + "*"); + if (!CollectionUtils.isEmpty(keys)) { + redisTemplate.delete(keys); + } + } + + // ===================================================== + // 工具方法 + // ===================================================== + + private Set convertToLongSet(Set objects) { + if (CollectionUtils.isEmpty(objects)) { + return new HashSet<>(); + } + return objects.stream() + .map(obj -> Long.valueOf(obj.toString())) + .collect(Collectors.toSet()); + } + + private Set convertToStringSet(Set objects) { + if (CollectionUtils.isEmpty(objects)) { + return new HashSet<>(); + } + return objects.stream() + .map(Object::toString) + .collect(Collectors.toSet()); + } + + private Map convertToStringObjectMap(Map objectMap) { + if (CollectionUtils.isEmpty(objectMap)) { + return new HashMap<>(); + } + return objectMap.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().toString(), + Map.Entry::getValue + )); + } + + /** + * 检查缓存是否存在 + */ + public boolean cacheExists(String keyPrefix, Object id) { + String key = keyPrefix + id; + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } + + /** + * 获取缓存剩余过期时间 + */ + public long getCacheExpire(String keyPrefix, Object id) { + String key = keyPrefix + id; + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } +} \ No newline at end of file