fix: 修复cache包被.gitignore误删除导致的编译错误
- 修复 .gitignore 中 cache/ 规则导致ljwx-boot中cache包被忽略的问题 - 改为更精确的缓存文件忽略规则:**/cache/*.log, **/cache/*.json, **/cache/*.tmp - 强制添加必要的缓存服务类: * HighPerformanceQueryService.java - 高性能查询服务 * RedisRelationCacheService.java - Redis关系缓存服务 * CacheConfiguration.java - 缓存配置类 * ICacheWarmupService.java - 缓存预热接口 * RedisCacheWarmupService.java - Redis缓存预热实现 - 解决TUserHealthDataServiceImpl.java编译依赖问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
+33
@@ -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(" - 批量查询性能优化");
|
||||
}
|
||||
}
|
||||
Vendored
+676
@@ -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<Long> getTenantUsers(Long customerId) {
|
||||
StopWatch stopWatch = new StopWatch("getTenantUsers");
|
||||
stopWatch.start("cache-lookup");
|
||||
|
||||
// 1. 尝试从缓存获取
|
||||
Set<Long> 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<Long> userIds = jdbcTemplate.queryForList(sql, Long.class, customerId);
|
||||
Set<Long> 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<Long> getTenantOrgs(Long customerId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<Long> 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<Long> orgIds = jdbcTemplate.queryForList(sql, Long.class, customerId);
|
||||
Set<Long> orgs = new HashSet<>(orgIds);
|
||||
|
||||
// 3. 更新缓存
|
||||
if (!orgs.isEmpty()) {
|
||||
cacheService.cacheTenantOrgs(customerId, orgs);
|
||||
}
|
||||
|
||||
return orgs;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 用户-部门关联查询(基于闭包表优化)
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 获取用户所属所有部门(包含层级关系)
|
||||
* 性能目标: <3ms
|
||||
*/
|
||||
public Set<Long> getUserOrgs(Long userId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<Long> 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<Long> orgIds = jdbcTemplate.queryForList(sql, Long.class, userId);
|
||||
Set<Long> orgs = new HashSet<>(orgIds);
|
||||
|
||||
// 3. 缓存结果
|
||||
cacheService.cacheUserOrgs(userId, orgs);
|
||||
|
||||
return orgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门下所有用户(包含子部门用户)
|
||||
* 性能目标: <3ms
|
||||
*/
|
||||
public Set<Long> getOrgUsers(Long orgId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<Long> 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<Long> userIds = jdbcTemplate.queryForList(sql, Long.class, orgId);
|
||||
Set<Long> users = new HashSet<>(userIds);
|
||||
|
||||
// 3. 缓存结果
|
||||
cacheService.cacheOrgUsers(orgId, users);
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门所有后代部门(基于闭包表)
|
||||
* 性能目标: <2ms
|
||||
*/
|
||||
public Set<Long> getOrgDescendants(Long orgId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<Long> 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<Long> descendantIds = jdbcTemplate.queryForList(sql, Long.class, orgId);
|
||||
Set<Long> descendants = new HashSet<>(descendantIds);
|
||||
|
||||
// 3. 缓存结果
|
||||
cacheService.cacheOrgDescendants(orgId, descendants);
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 用户-设备关联查询
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 获取用户关联的所有设备
|
||||
* 性能目标: <3ms
|
||||
*/
|
||||
public Set<String> getUserDevices(Long userId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<String> 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<String> deviceSns = jdbcTemplate.queryForList(sql, String.class, userId);
|
||||
Set<String> 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<Long> 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<String> getOrgDevices(Long orgId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<String> 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<String> deviceSns = jdbcTemplate.queryForList(sql, String.class, orgId);
|
||||
Set<String> 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<Long> 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<String, Object> getUserLatestHealth(Long userId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Map<String, Object> 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<Map<String, Object>> results = jdbcTemplate.queryForList(sql, userId);
|
||||
Map<String, Object> healthData = results.isEmpty() ? new HashMap<>() : results.get(0);
|
||||
|
||||
// 3. 缓存结果
|
||||
if (!healthData.isEmpty()) {
|
||||
cacheService.cacheUserLatestHealth(userId, healthData);
|
||||
}
|
||||
|
||||
return healthData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户活跃告警
|
||||
* 性能目标: <3ms
|
||||
*/
|
||||
public Set<Long> getUserActiveAlerts(Long userId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Set<Long> 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<Long> alertIds = jdbcTemplate.queryForList(sql, Long.class, userId);
|
||||
Set<Long> alerts = new HashSet<>(alertIds);
|
||||
|
||||
// 3. 缓存结果
|
||||
cacheService.cacheUserActiveAlerts(userId, alerts);
|
||||
|
||||
return alerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门健康数据汇总
|
||||
* 性能目标: <5ms
|
||||
*/
|
||||
public Map<String, Object> getOrgHealthSummary(Long orgId) {
|
||||
// 1. 尝试从缓存获取
|
||||
Map<String, Object> 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<Map<String, Object>> results = jdbcTemplate.queryForList(sql, orgId);
|
||||
Map<String, Object> summary = results.isEmpty() ? new HashMap<>() : results.get(0);
|
||||
|
||||
// 3. 缓存结果
|
||||
if (!summary.isEmpty()) {
|
||||
cacheService.cacheOrgHealthSummary(orgId, summary);
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 批量查询优化
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 批量获取用户设备关系
|
||||
* 性能目标: <10ms for 100 users
|
||||
*/
|
||||
public Map<Long, Set<String>> batchGetUserDevices(Set<Long> userIds) {
|
||||
if (CollectionUtils.isEmpty(userIds)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
StopWatch stopWatch = new StopWatch("batchGetUserDevices");
|
||||
stopWatch.start("cache-batch-lookup");
|
||||
|
||||
// 1. 批量从缓存获取
|
||||
Map<Long, Set<String>> result = cacheService.batchGetUserDevices(userIds);
|
||||
|
||||
// 2. 找出缓存未命中的用户
|
||||
Set<Long> 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<Map<String, Object>> dbResults = jdbcTemplate.queryForList(sql);
|
||||
|
||||
// 4. 处理查询结果并更新缓存
|
||||
for (Map<String, Object> row : dbResults) {
|
||||
Long userId = ((Number) row.get("user_id")).longValue();
|
||||
String deviceSnsStr = (String) row.get("device_sns");
|
||||
if (deviceSnsStr != null) {
|
||||
Set<String> 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<Long, Set<Long>> batchGetOrgUsers(Set<Long> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
// 1. 批量从缓存获取
|
||||
Map<Long, Set<Long>> result = cacheService.batchGetOrgUsers(orgIds);
|
||||
|
||||
// 2. 找出缓存未命中的部门
|
||||
Set<Long> missingOrgs = orgIds.stream()
|
||||
.filter(orgId -> !result.containsKey(orgId))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (!missingOrgs.isEmpty()) {
|
||||
// 3. 并行查询数据库
|
||||
missingOrgs.parallelStream().forEach(orgId -> {
|
||||
Set<Long> users = getOrgUsers(orgId); // 这个方法内部会处理缓存
|
||||
if (!users.isEmpty()) {
|
||||
result.put(orgId, users);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 复合查询场景
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 获取用户完整关系信息(用户-部门-设备-健康数据)
|
||||
* 性能目标: <8ms
|
||||
*/
|
||||
public Map<String, Object> getUserCompleteRelations(Long userId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 并行获取各种关系数据
|
||||
CompletableFuture<Set<Long>> orgsFuture = CompletableFuture
|
||||
.supplyAsync(() -> getUserOrgs(userId));
|
||||
|
||||
CompletableFuture<Set<String>> devicesFuture = CompletableFuture
|
||||
.supplyAsync(() -> getUserDevices(userId));
|
||||
|
||||
CompletableFuture<Map<String, Object>> healthFuture = CompletableFuture
|
||||
.supplyAsync(() -> getUserLatestHealth(userId));
|
||||
|
||||
CompletableFuture<Set<Long>> 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<String, Object> getOrgCompleteRelations(Long orgId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 并行获取各种关系数据
|
||||
CompletableFuture<Set<Long>> usersFuture = CompletableFuture
|
||||
.supplyAsync(() -> getOrgUsers(orgId));
|
||||
|
||||
CompletableFuture<Set<String>> devicesFuture = CompletableFuture
|
||||
.supplyAsync(() -> getOrgDevices(orgId));
|
||||
|
||||
CompletableFuture<Set<Long>> descendantsFuture = CompletableFuture
|
||||
.supplyAsync(() -> getOrgDescendants(orgId));
|
||||
|
||||
CompletableFuture<Map<String, Object>> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.ljwx.common.cache;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 缓存预热服务接口
|
||||
*/
|
||||
public interface ICacheWarmupService {
|
||||
|
||||
/**
|
||||
* 手动触发缓存预热
|
||||
*/
|
||||
void manualWarmup();
|
||||
|
||||
/**
|
||||
* 获取预热状态
|
||||
*/
|
||||
boolean isWarmupCompleted();
|
||||
|
||||
/**
|
||||
* 定时刷新热点数据缓存
|
||||
*/
|
||||
void refreshHotDataCache();
|
||||
|
||||
/**
|
||||
* 预热租户关系数据
|
||||
*/
|
||||
CompletableFuture<Void> warmupTenantRelations();
|
||||
|
||||
/**
|
||||
* 预热用户-部门关系
|
||||
*/
|
||||
CompletableFuture<Void> warmupUserOrgRelations();
|
||||
|
||||
/**
|
||||
* 预热设备关系数据
|
||||
*/
|
||||
CompletableFuture<Void> warmupDeviceRelations();
|
||||
|
||||
/**
|
||||
* 预热组织层级结构
|
||||
*/
|
||||
CompletableFuture<Void> warmupOrgHierarchy();
|
||||
}
|
||||
Vendored
+484
@@ -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<Void> tenantWarmup = warmupTenantRelations();
|
||||
CompletableFuture<Void> userOrgWarmup = warmupUserOrgRelations();
|
||||
CompletableFuture<Void> deviceWarmup = warmupDeviceRelations();
|
||||
CompletableFuture<Void> 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<Void> 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<Map<String, Object>> tenantUsers = jdbcTemplate.queryForList(userSql);
|
||||
for (Map<String, Object> row : tenantUsers) {
|
||||
Long customerId = ((Number) row.get("customer_id")).longValue();
|
||||
String userIdsStr = (String) row.get("user_ids");
|
||||
if (userIdsStr != null) {
|
||||
Set<Long> 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<Map<String, Object>> tenantOrgs = jdbcTemplate.queryForList(orgSql);
|
||||
for (Map<String, Object> row : tenantOrgs) {
|
||||
Long customerId = ((Number) row.get("customer_id")).longValue();
|
||||
String orgIdsStr = (String) row.get("org_ids");
|
||||
if (orgIdsStr != null) {
|
||||
Set<Long> 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<Void> 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<Map<String, Object>> userOrgs = jdbcTemplate.queryForList(userOrgSql);
|
||||
for (Map<String, Object> row : userOrgs) {
|
||||
Long userId = ((Number) row.get("user_id")).longValue();
|
||||
String orgIdsStr = (String) row.get("org_ids");
|
||||
if (orgIdsStr != null) {
|
||||
Set<Long> 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<Map<String, Object>> orgUsers = jdbcTemplate.queryForList(orgUserSql);
|
||||
for (Map<String, Object> row : orgUsers) {
|
||||
Long orgId = ((Number) row.get("org_id")).longValue();
|
||||
String userIdsStr = (String) row.get("direct_user_ids");
|
||||
|
||||
// 获取包含子部门的所有用户
|
||||
Set<Long> 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<Void> 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<Map<String, Object>> userDevices = jdbcTemplate.queryForList(userDeviceSql);
|
||||
for (Map<String, Object> row : userDevices) {
|
||||
Long userId = ((Number) row.get("user_id")).longValue();
|
||||
String deviceSnsStr = (String) row.get("device_sns");
|
||||
if (deviceSnsStr != null) {
|
||||
Set<String> 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<Map<String, Object>> deviceUsers = jdbcTemplate.queryForList(deviceUserSql);
|
||||
for (Map<String, Object> 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<Map<String, Object>> deviceOrgs = jdbcTemplate.queryForList(deviceOrgSql);
|
||||
for (Map<String, Object> 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<Void> 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<Map<String, Object>> orgDescendants = jdbcTemplate.queryForList(descendantsSql);
|
||||
for (Map<String, Object> row : orgDescendants) {
|
||||
Long ancestorId = ((Number) row.get("ancestor_id")).longValue();
|
||||
String descendantIdsStr = (String) row.get("descendant_ids");
|
||||
if (descendantIdsStr != null) {
|
||||
Set<Long> 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<Map<String, Object>> latestHealthData = jdbcTemplate.queryForList(sql);
|
||||
for (Map<String, Object> row : latestHealthData) {
|
||||
Long userId = ((Number) row.get("user_id")).longValue();
|
||||
Map<String, Object> 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<Map<String, Object>> activeAlerts = jdbcTemplate.queryForList(sql);
|
||||
for (Map<String, Object> row : activeAlerts) {
|
||||
Long userId = ((Number) row.get("user_id")).longValue();
|
||||
String alertIdsStr = (String) row.get("alert_ids");
|
||||
if (alertIdsStr != null) {
|
||||
Set<Long> 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<Map<String, Object>> orgSummaries = jdbcTemplate.queryForList(sql);
|
||||
for (Map<String, Object> row : orgSummaries) {
|
||||
Long orgId = ((Number) row.get("org_id")).longValue();
|
||||
Map<String, Object> summary = new HashMap<>(row);
|
||||
summary.remove("org_id");
|
||||
relationCacheService.cacheOrgHealthSummary(orgId, summary);
|
||||
}
|
||||
|
||||
log.debug("刷新部门健康数据汇总: {} 个部门", orgSummaries.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门及其子部门的所有用户
|
||||
*/
|
||||
private Set<Long> 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<Long> userIds = jdbcTemplate.queryForList(sql, Long.class, orgId);
|
||||
return new HashSet<>(userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载部门设备关系
|
||||
*/
|
||||
private void preloadOrgDevices() {
|
||||
// 获取所有部门ID
|
||||
List<Long> 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<String> 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;
|
||||
}
|
||||
}
|
||||
Vendored
+454
@@ -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<String, Object> 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<Long> getTenantUsers(Long customerId) {
|
||||
String key = TENANT_USERS + customerId;
|
||||
Set<Object> userIds = redisTemplate.opsForSet().members(key);
|
||||
return convertToLongSet(userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存租户用户关系
|
||||
*/
|
||||
public void cacheTenantUsers(Long customerId, Set<Long> 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<Long> getTenantOrgs(Long customerId) {
|
||||
String key = TENANT_ORGS + customerId;
|
||||
Set<Object> orgIds = redisTemplate.opsForSet().members(key);
|
||||
return convertToLongSet(orgIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存租户部门关系
|
||||
*/
|
||||
public void cacheTenantOrgs(Long customerId, Set<Long> 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<Long> getUserOrgs(Long userId) {
|
||||
String key = USER_ORGS + userId;
|
||||
Set<Object> orgIds = redisTemplate.opsForSet().members(key);
|
||||
return convertToLongSet(orgIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存用户部门关系
|
||||
*/
|
||||
public void cacheUserOrgs(Long userId, Set<Long> 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<Long> getOrgUsers(Long orgId) {
|
||||
String key = ORG_USERS + orgId;
|
||||
Set<Object> userIds = redisTemplate.opsForSet().members(key);
|
||||
return convertToLongSet(userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存部门用户关系(包含子部门)
|
||||
*/
|
||||
public void cacheOrgUsers(Long orgId, Set<Long> 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<Long> getOrgDescendants(Long orgId) {
|
||||
String key = ORG_DESCENDANTS + orgId;
|
||||
Set<Object> descendants = redisTemplate.opsForSet().members(key);
|
||||
return convertToLongSet(descendants);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存部门后代关系
|
||||
*/
|
||||
public void cacheOrgDescendants(Long orgId, Set<Long> 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<String> getUserDevices(Long userId) {
|
||||
String key = USER_DEVICES + userId;
|
||||
Set<Object> deviceSns = redisTemplate.opsForSet().members(key);
|
||||
return convertToStringSet(deviceSns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存用户设备关系
|
||||
*/
|
||||
public void cacheUserDevices(Long userId, Set<String> 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<String> getOrgDevices(Long orgId) {
|
||||
String key = ORG_DEVICES + orgId;
|
||||
Set<Object> deviceSns = redisTemplate.opsForSet().members(key);
|
||||
return convertToStringSet(deviceSns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存部门设备关系
|
||||
*/
|
||||
public void cacheOrgDevices(Long orgId, Set<String> 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<String, Object> getUserLatestHealth(Long userId) {
|
||||
String key = USER_HEALTH_LATEST + userId;
|
||||
Map<Object, Object> healthData = redisTemplate.opsForHash().entries(key);
|
||||
return convertToStringObjectMap(healthData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存用户最新健康数据
|
||||
*/
|
||||
public void cacheUserLatestHealth(Long userId, Map<String, Object> 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<Long> getUserActiveAlerts(Long userId) {
|
||||
String key = USER_ALERTS_ACTIVE + userId;
|
||||
Set<Object> alertIds = redisTemplate.opsForSet().members(key);
|
||||
return convertToLongSet(alertIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存用户活跃告警
|
||||
*/
|
||||
public void cacheUserActiveAlerts(Long userId, Set<Long> 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<String, Object> getOrgHealthSummary(Long orgId) {
|
||||
String key = ORG_HEALTH_SUMMARY + orgId;
|
||||
Map<Object, Object> summary = redisTemplate.opsForHash().entries(key);
|
||||
return convertToStringObjectMap(summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存部门健康数据汇总
|
||||
*/
|
||||
public void cacheOrgHealthSummary(Long orgId, Map<String, Object> 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<Long, Set<String>> batchGetUserDevices(Set<Long> userIds) {
|
||||
if (CollectionUtils.isEmpty(userIds)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<Long, Set<String>> result = new HashMap<>();
|
||||
|
||||
// 逐个查询,因为Redis Set操作不支持批量获取members
|
||||
for (Long userId : userIds) {
|
||||
Set<String> devices = getUserDevices(userId);
|
||||
if (!devices.isEmpty()) {
|
||||
result.put(userId, devices);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取部门用户关系
|
||||
*/
|
||||
public Map<Long, Set<Long>> batchGetOrgUsers(Set<Long> orgIds) {
|
||||
if (CollectionUtils.isEmpty(orgIds)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<Long, Set<Long>> result = new HashMap<>();
|
||||
|
||||
for (Long orgId : orgIds) {
|
||||
Set<Long> users = getOrgUsers(orgId);
|
||||
if (!CollectionUtils.isEmpty(users)) {
|
||||
result.put(orgId, users);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 缓存失效和清理
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 清除用户相关缓存
|
||||
*/
|
||||
public void evictUserCache(Long userId) {
|
||||
Set<String> keys = redisTemplate.keys("*:*:" + userId + "*");
|
||||
if (!CollectionUtils.isEmpty(keys)) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除部门相关缓存
|
||||
*/
|
||||
public void evictOrgCache(Long orgId) {
|
||||
Set<String> keys = redisTemplate.keys("*:*:" + orgId + "*");
|
||||
if (!CollectionUtils.isEmpty(keys)) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除设备相关缓存
|
||||
*/
|
||||
public void evictDeviceCache(String deviceSn) {
|
||||
Set<String> keys = redisTemplate.keys("*:*:" + deviceSn + "*");
|
||||
if (!CollectionUtils.isEmpty(keys)) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除租户相关缓存
|
||||
*/
|
||||
public void evictTenantCache(Long customerId) {
|
||||
Set<String> keys = redisTemplate.keys("tenant:*:" + customerId + "*");
|
||||
if (!CollectionUtils.isEmpty(keys)) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 工具方法
|
||||
// =====================================================
|
||||
|
||||
private Set<Long> convertToLongSet(Set<Object> objects) {
|
||||
if (CollectionUtils.isEmpty(objects)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return objects.stream()
|
||||
.map(obj -> Long.valueOf(obj.toString()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<String> convertToStringSet(Set<Object> objects) {
|
||||
if (CollectionUtils.isEmpty(objects)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return objects.stream()
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Map<String, Object> convertToStringObjectMap(Map<Object, Object> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user