Compare commits

..

No commits in common. "dev_20260130_RemoveRedis" and "main" have entirely different histories.

44 changed files with 1372 additions and 2205 deletions

View File

@ -12,14 +12,6 @@
<artifactId>skyeye-service-manager</artifactId> <artifactId>skyeye-service-manager</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba.fastjson2</groupId> <groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId> <artifactId>fastjson2</artifactId>

View File

@ -1,6 +1,5 @@
package com.zhangy.skyeye; package com.zhangy.skyeye;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -10,10 +9,8 @@ import java.io.File;
@EnableScheduling @EnableScheduling
@MapperScan("com.zhangy.skyeye.**.mapper") @MapperScan("com.zhangy.skyeye.**.mapper")
@SpringBootApplication(scanBasePackages = "com.zhangy") @SpringBootApplication(scanBasePackages = "com.zhangy.**")
@Slf4j
public class SEApplication { public class SEApplication {
public static void main(String[] args) { public static void main(String[] args) {
String projectRoot = System.getProperty("user.dir"); String projectRoot = System.getProperty("user.dir");
// String libPath = projectRoot + "/library/logisen/GMTI"; // String libPath = projectRoot + "/library/logisen/GMTI";

View File

@ -1,51 +0,0 @@
package com.zhangy.skyeye.cache;
import org.springframework.cache.Cache;
import java.util.Map;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 抽象类型缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/3 14:31
*/
public abstract class AbstractTypedCache {
private final Cache cache;
protected AbstractTypedCache(Cache cache) {
this.cache = cache;
}
protected void put(String key, Object value) {
cache.put(key, value);
}
protected <T> T get(String key, Class<T> type) {
return cache.get(key, type);
}
protected Object getRaw(String key) {
Cache.ValueWrapper wrapper = cache.get(key);
return wrapper == null ? null : wrapper.get();
}
protected void evict(String key) {
cache.evict(key);
}
/** 获取原生 Caffeine Map用于全量读取 */
public Map<Object, Object> asMap() {
Object nativeCache = cache.getNativeCache();
if (nativeCache instanceof com.github.benmanes.caffeine.cache.Cache) {
return ((com.github.benmanes.caffeine.cache.Cache<Object, Object>) nativeCache).asMap();
}
return null;
}
protected Object getNativeCache() {
return cache.getNativeCache();
}
}

View File

@ -1,101 +0,0 @@
package com.zhangy.skyeye.cache.config;
import com.zhangy.skyeye.cache.image.ImageJoinTypedCache;
import com.zhangy.skyeye.cache.publics.UserTokenTypedCache;
import com.zhangy.skyeye.cache.sar.SarTypedCache;
import com.zhangy.skyeye.cache.uav.UavTypedCache;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: Cache Bean 显式注册
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/3 14:15
*/
@Configuration
public class CacheBeanConfig {
/* ================= USER ================= */
@Bean("userTokenNativeCache")
public Cache userTokens(CacheManager manager) {
return require(manager, "user-tokens");
}
// TypedCache 封装层
@Bean
public UserTokenTypedCache userTokensCache(
@Qualifier("userTokenNativeCache") Cache cache) {
return new UserTokenTypedCache(cache);
}
/* ================= IMAGE ================= */
@Bean("imageJoinShortNativeCache")
public Cache imageJoinShort(CacheManager manager) {
return require(manager, "image-join-short");
}
@Bean
public ImageJoinTypedCache imageJoinShortCache(
@Qualifier("imageJoinShortNativeCache") Cache cache) {
return new ImageJoinTypedCache(cache);
}
/* ================= SAR ================= */
@Bean("sarPermanentNativeCache")
public Cache sarPermanent(CacheManager manager) {
return require(manager, "sar-permanent");
}
@Bean
public SarTypedCache sarPermanentCache(
@Qualifier("sarPermanentNativeCache") Cache cache) {
return new SarTypedCache(cache);
}
@Bean("sarShortNativeCache")
public Cache sarShort(CacheManager manager) {
return require(manager, "sar-short");
}
@Bean
public SarTypedCache sarShortCache(
@Qualifier("sarShortNativeCache") Cache cache) {
return new SarTypedCache(cache);
}
/* ================= UAV ================= */
@Bean("uavPermanentNativeCache")
public Cache uavPermanent(CacheManager manager) {
return require(manager, "uav-permanent");
}
@Bean
public UavTypedCache uavPermanentCache(
@Qualifier("uavPermanentNativeCache") Cache cache) {
return new UavTypedCache(cache);
}
@Bean("uavShortNativeCache")
public Cache uavShort(CacheManager manager) {
return require(manager, "uav-short");
}
@Bean
public UavTypedCache uavShortCache(
@Qualifier("uavShortNativeCache") Cache cache) {
return new UavTypedCache(cache);
}
private Cache require(CacheManager manager, String name) {
Cache cache = manager.getCache(name);
if (cache == null) {
throw new IllegalStateException("未找到缓存: " + name);
}
return cache;
}
}

View File

@ -1,68 +0,0 @@
package com.zhangy.skyeye.cache.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.concurrent.TimeUnit;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 缓存配置
* @AUTHOR: GuanCheng Long
* @DATE: 2026/1/30 12:51
*/
@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
@Bean
@Primary
public CacheManager cacheManager() {
log.info("【重要】CacheConfig 被加载,开始创建 CacheManager");
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS) // 与原来 TOKEN_EXPIRE 一致24小时
.maximumSize(10000) // 根据用户量调整防止 OOM
.recordStats() // 可监控命中率
// 可加 .removalListener(...) 监听移除事件
);
// 专门给 token 一个独立的 cache name便于细粒度管理
cacheManager.registerCustomCache("user-tokens",
Caffeine.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS)
.maximumSize(5000) // 预计活跃用户数
.build());
// 永久缓存SAR 载荷专用不设过期
cacheManager.registerCustomCache("sar-permanent", Caffeine.newBuilder()
.maximumSize(5000) // 根据 SAR 载荷数量调整
.build());
// 短时状态/连接已存在确认过期时间
cacheManager.registerCustomCache("sar-short", Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS) // 建议 3~5 防边界丢失
.maximumSize(2000)
.build());
// 永久缓存UAV 专用不设过期
cacheManager.registerCustomCache("uav-permanent", Caffeine.newBuilder()
.maximumSize(5000) // 根据无人机数量调整
.build());
cacheManager.registerCustomCache("uav-short", Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 与原 CACHE_EXPIRE_SECONDS 一致
.maximumSize(1000) // 按设备/IP 数量预估
.recordStats()
.build());
cacheManager.registerCustomCache("image-join-short", Caffeine.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS) // 与原 CACHE_EXPIRE_SECOND 一致
.maximumSize(5000) // 航线数量预估
.build());
log.info("【重要】CacheManager 创建完成,缓存列表:{}", cacheManager.getCacheNames());
return cacheManager;
}
}

View File

@ -1,85 +0,0 @@
package com.zhangy.skyeye.cache.debug;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.Set;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 缓存调试工具统一查看所有缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/6 12:55
*/
@RestController
@RequestMapping("/debug/cache")
@RequiredArgsConstructor
@Profile("dev")
public class CacheDebugController {
private final CacheDebugService cacheDebugService;
/**
* 查看所有缓存
*/
@GetMapping("/all")
public Map<String, Object> allCaches() {
return cacheDebugService.allCaches();
}
/**
* 查看缓存key
*/
@GetMapping("/keys/{name}")
public Set<Object> keys(@PathVariable String name) {
return cacheDebugService.keys(name);
}
/**
* 查看某个缓存
*/
@GetMapping("/{name}")
public Map<Object, Object> cache(@PathVariable String name) {
return cacheDebugService.cache(name);
}
/**
* 查看某个key
*/
@GetMapping("/{name}/{key}")
public Object get(@PathVariable String name,
@PathVariable String key) {
return cacheDebugService.get(name, key);
}
/**
* 删除key
*/
@DeleteMapping("/{name}/{key}")
public String evict(@PathVariable String name,
@PathVariable String key) {
cacheDebugService.evict(name, key);
return "OK";
}
/**
* 清空缓存
*/
@DeleteMapping("/{name}")
public String clear(@PathVariable String name) {
cacheDebugService.clear(name);
return "OK";
}
/**
* 缓存统计
*/
@GetMapping("/stats")
public Object stats() {
return cacheDebugService.stats();
}
}

View File

@ -1,93 +0,0 @@
package com.zhangy.skyeye.cache.debug;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 监控逻辑
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/6 13:02
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Profile("dev")
public class CacheDebugService {
private final CacheManager cacheManager;
public Map<String, Object> allCaches() {
Map<String, Object> map = new LinkedHashMap<>();
for (String name : cacheManager.getCacheNames()) {
map.put(name, cache(name));
}
return map;
}
public Map<Object, Object> cache(String name) {
Cache cache = cacheManager.getCache(name);
if (!(cache instanceof CaffeineCache)) {
return Collections.emptyMap();
}
return ((CaffeineCache) cache)
.getNativeCache()
.asMap();
}
public Set<Object> keys(String name) {
return cache(name).keySet();
}
public Object get(String name, Object key) {
Cache cache = cacheManager.getCache(name);
if (cache == null) {
return null;
}
Cache.ValueWrapper value = cache.get(key);
return value == null ? null : value.get();
}
public void evict(String name, Object key) {
Cache cache = cacheManager.getCache(name);
if (cache != null) {
cache.evict(key);
}
}
public void clear(String name) {
Cache cache = cacheManager.getCache(name);
if (cache != null) {
cache.clear();
}
}
public Map<String, Object> stats() {
Map<String, Object> result = new LinkedHashMap<>();
for (String name : cacheManager.getCacheNames()) {
Cache cache = cacheManager.getCache(name);
if (!(cache instanceof CaffeineCache)) {
continue;
}
CaffeineCache caffeine = (CaffeineCache) cache;
CacheStats stats = caffeine.getNativeCache().stats();
Map<String, Object> info = new HashMap<>();
info.put("size", caffeine.getNativeCache().asMap().size());
info.put("hitCount", stats.hitCount());
info.put("missCount", stats.missCount());
info.put("hitRate", stats.hitRate());
info.put("evictionCount", stats.evictionCount());
result.put(name, info);
}
return result;
}
}

View File

@ -1,48 +0,0 @@
package com.zhangy.skyeye.cache.debug;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 自动监控缓存操作
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/6 13:06
*/
@Slf4j
@Aspect
@Component
@Profile("dev")
public class CacheMonitorAspect {
@After("execution(* org.springframework.cache.Cache.put(..))")
public void afterPut(JoinPoint jp) {
Cache cache = (Cache) jp.getTarget();
Object key = jp.getArgs()[0];
log.debug("CACHE PUT -> {} : {}",
cache.getName(),
key);
}
@After("execution(* org.springframework.cache.Cache.get(..))")
public void afterGet(JoinPoint jp) {
Cache cache = (Cache) jp.getTarget();
Object key = jp.getArgs()[0];
log.debug("CACHE GET -> {} : {}",
cache.getName(),
key);
}
@After("execution(* org.springframework.cache.Cache.evict(..))")
public void afterEvict(JoinPoint jp) {
Cache cache = (Cache) jp.getTarget();
Object key = jp.getArgs()[0];
log.warn("CACHE EVICT -> {} : {}",
cache.getName(),
key);
}
}

View File

@ -1,53 +0,0 @@
package com.zhangy.skyeye.cache.debug;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 实时监控任务
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/6 13:07
*/
@Component
@RequiredArgsConstructor
@Slf4j
@Profile("dev")
public class CacheWatcherUltimate {
private final CacheManager cacheManager;
@Scheduled(fixedDelay = 30000)
public void watchCaches() {
log.info("========== CACHE STATUS BEGIN ==========");
for (String name : cacheManager.getCacheNames()) {
org.springframework.cache.Cache springCache = cacheManager.getCache(name);
if (!(springCache instanceof CaffeineCache)) {
continue;
}
CaffeineCache caffeineCache = (CaffeineCache) springCache;
Cache<Object, Object> nativeCache = caffeineCache.getNativeCache();
Map<Object, Object> map = nativeCache.asMap();
log.info("CACHE [{}] SIZE = {}", name, map.size());
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
log.info(
" KEY = {} | VALUE_TYPE = {}",
key,
value == null ? "null" : value.getClass().getSimpleName()
);
}
}
log.info("========== CACHE STATUS END ==========");
}
}

View File

@ -1,36 +0,0 @@
package com.zhangy.skyeye.cache.image;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: IMAGE 缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/5 15:56
*/
@Component
@RequiredArgsConstructor
public class ImageCache {
private final Map<String, ImageJoinTypedCache> caches;
private ImageJoinTypedCache getShort() {
return caches.get("imageJoinShortCache");
}
public void put(String key, Object value) {
getShort().putValue(key, value);
}
public <T> T get(String key, Class<T> clazz) {
return getShort().getValue(key, clazz);
}
public void evict(String key) {
getShort().evictValue(key);
}
}

View File

@ -1,32 +0,0 @@
package com.zhangy.skyeye.cache.image;
import com.zhangy.skyeye.cache.AbstractTypedCache;
import org.springframework.cache.Cache;
import java.util.Map;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: IMAGE 缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/5 15:55
*/
public class ImageJoinTypedCache extends AbstractTypedCache {
public ImageJoinTypedCache(Cache cache) {
super(cache);
}
public void putValue(String key, Object value) {
super.put(key, value);
}
public <T> T getValue(String key, Class<T> clazz) {
return super.get(key, clazz);
}
public void evictValue(String key) {
super.evict(key);
}
}

View File

@ -1,49 +0,0 @@
package com.zhangy.skyeye.cache.publics;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 业务聚合层对外暴露用户 token 操作
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/5 14:06
*/
@Component
@RequiredArgsConstructor
public class UserTokenCache {
private final Map<String, UserTokenTypedCache> caches;
private UserTokenTypedCache getUserTokenCache() {
return caches.get("userTokensCache");
}
// 保存 token
public void storeToken(String tokenKey, String token) {
getUserTokenCache().put(tokenKey, token);
}
// 获取并验证 token 是否一致
public boolean validateToken(String tokenKey, String currentToken) {
String stored = getUserTokenCache().get(tokenKey);
return stored != null && stored.equals(currentToken);
}
// 获取 token
public String getToken(String tokenKey) {
return getUserTokenCache().get(tokenKey);
}
// 删除 token
public void evictToken(String tokenKey) {
getUserTokenCache().evictValue(tokenKey);
}
public Object getNativeCache() {
return getUserTokenCache().getNativeCacheObject(); // UserTokenTypedCache 里需要有 getCache()
}
}

View File

@ -1,34 +0,0 @@
package com.zhangy.skyeye.cache.publics;
import com.zhangy.skyeye.cache.AbstractTypedCache;
import org.springframework.cache.Cache;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 安全封装 User Token Cache
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/5 14:14
*/
public class UserTokenTypedCache extends AbstractTypedCache {
public UserTokenTypedCache(Cache cache) {
super(cache);
}
public void put(String key, String token) {
super.put(key, token);
}
public String get(String key) {
return super.get(key, String.class);
}
public void evictValue(String key) {
super.evict(key);
}
public Object getNativeCacheObject() {
return super.getNativeCache(); // UserTokenTypedCache 里需要有 getCache()
}
}

View File

@ -1,95 +0,0 @@
package com.zhangy.skyeye.cache.sar;
import com.zhangy.skyeye.device.entity.SkyeyePayload;
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.sar.dto.SarErrorDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: Sar 缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/3 14:24
*/
@Component
@RequiredArgsConstructor
public class SarCache {
private final Map<String, SarTypedCache> caches;
private SarTypedCache getPermanent() {
return caches.get("sarPermanentCache");
}
private SarTypedCache getShort() {
return caches.get("sarShortCache");
}
/* ========== 永久缓存SAR 载荷操作 ========== */
// 缓存 SAR 载荷
public void cacheSar(SkyeyePayload sar) {
getPermanent().putValue(sar.getId().toString(), sar);
}
// 获取单个 SAR
public SkyeyePayload getOne(Long sarId) {
return getPermanent().getValue(sarId.toString(), SkyeyePayload.class);
}
// 删除指定 SAR
public void evictSar(Long sarId) {
getPermanent().evictValue(sarId.toString());
}
// 获取永久缓存中所有 SAR 对象
public List<SkyeyePayload> getAll() {
Map<Object, Object> map = getPermanent().asMap();
if (map == null || map.isEmpty()) return List.of();
return map.values().stream()
.filter(v -> v instanceof SkyeyePayload)
.map(v -> (SkyeyePayload) v)
.collect(Collectors.toList());
}
/* ========== 连接状态 ========== */
public void markConnected(String ip) {
getPermanent().putValue(CacheKey.getSarConnected(ip), Boolean.TRUE);
}
public boolean isConnected(String ip) {
return getPermanent().getValue(CacheKey.getSarConnected(ip), Boolean.class) != null;
}
public void removeConnection(String ip) {
getPermanent().evictValue(CacheKey.getSarConnected(ip));
}
/* ========== 实时状态 ========== */
public void saveStatus(String ip, JmSarStatusDTO dto) {
getShort().putValue(CacheKey.getSarStatus(ip), dto);
}
public JmSarStatusDTO getLatestStatus(String ip) {
return getShort().getValue(CacheKey.getSarStatus(ip), JmSarStatusDTO.class);
}
/* ========== 控制回执 ========== */
public void saveControlBack(String ip, SarErrorDTO dto) {
getShort().putValue(CacheKey.getSarControlBack(ip), dto);
}
public SarErrorDTO getControlBack(String ip) {
return getShort().getValue(CacheKey.getSarControlBack(ip), SarErrorDTO.class);
}
public void clearControlBack(String ip) {
getShort().evictValue(CacheKey.getSarControlBack(ip));
}
}

View File

@ -1,39 +0,0 @@
package com.zhangy.skyeye.cache.sar;
import com.zhangy.skyeye.cache.AbstractTypedCache;
import org.springframework.cache.Cache;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 通用 SAR 缓存可作为永久或短期缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/5 12:06
*/
public class SarTypedCache extends AbstractTypedCache {
public SarTypedCache(Cache cache) {
super(cache);
}
/**
* 类型安全读取
*/
public <T> T getValue(String key, Class<T> type) {
return super.get(key, type);
}
/**
* 类型安全写入
*/
public void putValue(String key, Object value) {
super.put(key, value);
}
/**
* 类型安全删除
*/
public void evictValue(String key) {
super.evict(key);
}
}

View File

@ -1,68 +0,0 @@
package com.zhangy.skyeye.cache.uav;
import com.zhangy.skyeye.cache.sar.SarTypedCache;
import com.zhangy.skyeye.device.entity.SkyeyeUav;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: Uav 缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/4 14:19
*/
@Component
@RequiredArgsConstructor
public class UavCache {
private final Map<String, UavTypedCache> caches;
private UavTypedCache getPermanent() {
return caches.get("uavPermanentCache");
}
private UavTypedCache getShort() {
return caches.get("uavShortCache");
}
/* ========== 永久缓存UAV 操作 ========== */
public void put(SkyeyeUav uav) {
getPermanent().putValue(uav.getId().toString(), uav);
}
public SkyeyeUav get(Long id) {
return getPermanent().getValue(id.toString(), SkyeyeUav.class);
}
public void evict(Long id) {
getPermanent().evictValue(id.toString());
}
public List<SkyeyeUav> getAll() {
Map<Object, Object> map = getPermanent().asMap();
if (map == null) {
return List.of();
}
return map.values().stream()
.map(v -> (SkyeyeUav) v)
.collect(Collectors.toList());
}
/* ========== 短期缓存 ========== */
public void putShort(String key, Object value) {
getShort().putValue(key, value);
}
public <T> T getShort(String key, Class<T> clazz) {
return getShort().getValue(key, clazz);
}
public void evictShort(String key) {
getShort().evictValue(key);
}
}

View File

@ -1,30 +0,0 @@
package com.zhangy.skyeye.cache.uav;
import com.zhangy.skyeye.cache.AbstractTypedCache;
import org.springframework.cache.Cache;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: Uav 缓存可作为永久或短期缓存
* @AUTHOR: GuanCheng Long
* @DATE: 2026/3/4 14:20
*/
public class UavTypedCache extends AbstractTypedCache {
public UavTypedCache(Cache cache) {
super(cache);
}
public <T> T getValue(String key, Class<T> type) {
return super.get(key, type);
}
public void putValue(String key, Object value) {
super.put(key, value);
}
public void evictValue(String key) {
super.evict(key);
}
}

View File

@ -1,7 +1,6 @@
package com.zhangy.skyeye.device.service.impl; package com.zhangy.skyeye.device.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhangy.skyeye.cache.sar.SarCache;
import com.zhangy.skyeye.common.extend.dto.PageDTO; import com.zhangy.skyeye.common.extend.dto.PageDTO;
import com.zhangy.skyeye.common.extend.dto.QueryDTO; import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.common.extend.enums.EnumUtil; import com.zhangy.skyeye.common.extend.enums.EnumUtil;
@ -16,67 +15,68 @@ import com.zhangy.skyeye.jm.dto.JmJobStatusDTO;
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO; import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO; import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.jm.service.JmJobStatusService; import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.publics.consts.ExecStatusEnum; import com.zhangy.skyeye.publics.consts.ExecStatusEnum;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.sar.service.ISarControlService; import com.zhangy.skyeye.sar.service.ISarControlService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.*; import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@Service @Service
@RequiredArgsConstructor
public class PayloadServiceImpl implements IPayloadService { public class PayloadServiceImpl implements IPayloadService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired @Autowired
private PayloadMapper payloadMapper; private PayloadMapper payloadMapper;
@Autowired @Autowired
private JmJobStatusService sarJobStatusService; private JmJobStatusService sarJobStatusService;
@Autowired @Autowired
private ISarControlService sarControlService; private ISarControlService sarControlService;
private final SarCache sarCache;
@PostConstruct
public void initAndCacheAllSar() {
// 开始缓存所有 SAR如果 permanentCache null这里会安全跳过
PayloadQueryDTO payloadQueryDTO = new PayloadQueryDTO();
payloadQueryDTO.setType(PayloadTypeEnum.SAR.getCode());
List<SkyeyePayload> sarList = selectList(payloadQueryDTO);
sarList.forEach(sarCache::cacheSar);
log.info("SAR 载荷缓存完成,共 {} 条", sarList.size());
}
@Override @Override
public IPage<SkyeyePayload> selectPage(PageDTO param) { public IPage<SkyeyePayload> selectPage(PageDTO param) {
return payloadMapper.selectPage(param); return payloadMapper.selectPage(param);
} }
@PostConstruct
public void cacheAllSar() {
PayloadQueryDTO payloadQueryDTO = new PayloadQueryDTO();
payloadQueryDTO.setType(PayloadTypeEnum.SAR.getCode());
List<SkyeyePayload> sarList = selectList(payloadQueryDTO);
sarList.forEach(this::cacheSar);
}
private void cacheSar(SkyeyePayload e) {
redisTemplate.opsForHash().put(CacheKey.DEVICE_SAR, e.getId().toString(), e);
}
@Override @Override
public List<SkyeyePayload> getSar(Long... sarId) { public List<SkyeyePayload> getSar(Long... sarId) {
if (ObjectUtil.isNotEmpty(sarId)) { if (ObjectUtil.isNotEmpty(sarId)) {
return Arrays.stream(sarId) return redisTemplate.opsForHash().multiGet(CacheKey.DEVICE_SAR, Arrays.asList(sarId));
.map(sarCache::getOne)
.filter(Objects::nonNull)
.collect(Collectors.toList());
} }
return redisTemplate.opsForHash().values(CacheKey.DEVICE_SAR);
// 获取所有 SAR全量从缓存读取
return sarCache.getAll().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
} }
@Override @Override
public SkyeyePayload getOne(Long payloadId) { public SkyeyePayload getOne(Long payloadId) {
return sarCache.getOne(payloadId); SkyeyePayload p = (SkyeyePayload) redisTemplate.opsForHash().get(CacheKey.DEVICE_SAR, payloadId.toString());
// 若有其它种类载荷则判空继续查询
return p;
} }
@Override @Override
@ -84,6 +84,10 @@ public class PayloadServiceImpl implements IPayloadService {
return payloadMapper.selectList(param); return payloadMapper.selectList(param);
} }
@Autowired
private RedisUtil redisUtil;
@Override @Override
public List<SkyeyePayload> getEnableList() { public List<SkyeyePayload> getEnableList() {
// 从缓存查询所有sar // 从缓存查询所有sar
@ -94,14 +98,12 @@ public class PayloadServiceImpl implements IPayloadService {
// 从内存查询所有任务中的雷达 // 从内存查询所有任务中的雷达
Collection<JmJobStatusDTO> statusVos = sarJobStatusService.getAll(); Collection<JmJobStatusDTO> statusVos = sarJobStatusService.getAll();
Set<Long> jobSarSet = statusVos.stream() Set<Long> jobSarSet = statusVos.stream()
.flatMap(job -> job.getUavMap().values().stream() .flatMap(job -> job.getUavMap().values().stream().filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER))
.filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER)) .map(JmUavStatusDTO::getSarId)
.map(JmUavStatusDTO::getSarId) // 正确取 SAR ID
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 筛选出未在任务中且已连接的雷达 // 筛选出未在任务中且已连接的雷达
return sarList.stream() return sarList.stream()
.filter(sar -> !jobSarSet.contains(sar.getId()) .filter(sar -> !jobSarSet.contains(sar.getId()) && redisUtil.hasKey(CacheKey.getSarConnect(sar.getIp())))
&& sarCache.getLatestStatus(sar.getIp()) != null)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -126,7 +128,7 @@ public class PayloadServiceImpl implements IPayloadService {
} else { } else {
payloadMapper.insert(e); payloadMapper.insert(e);
} }
sarCache.cacheSar(e); cacheSar(e);
return e; return e;
} }
@ -140,7 +142,9 @@ public class PayloadServiceImpl implements IPayloadService {
payloadMapper.update(e); payloadMapper.update(e);
// 若是sar则更新缓存 // 若是sar则更新缓存
List<SkyeyePayload> list = selectById(PayloadTypeEnum.SAR, e.getId()); List<SkyeyePayload> list = selectById(PayloadTypeEnum.SAR, e.getId());
if (ObjectUtil.isNotEmpty(list)) sarCache.cacheSar(list.get(0)); if (ObjectUtil.isNotEmpty(list)) {
cacheSar(list.get(0));
}
return e; return e;
} }
@ -167,42 +171,38 @@ public class PayloadServiceImpl implements IPayloadService {
@Transactional @Transactional
@Override @Override
public void delete(Long... id) { public void delete(Long... id) {
// 查询任务中的sar
List<Long> inJobSarIds = sarJobStatusService.getAll() List<Long> inJobSarIds = sarJobStatusService.getAll()
.stream() .stream()
.flatMap(vo -> vo.getUavMap().values().stream()) .flatMap(vo -> vo.getUavMap().values().stream())
.filter(vo -> vo.getSarStatus() != ExecStatusEnum.OVER) .filter(vo -> vo.getSarStatus() != ExecStatusEnum.OVER)
.map(JmUavStatusDTO::getSarId) .map(JmUavStatusDTO::getSarId)
.collect(Collectors.toList()); .collect(Collectors.toList());
// 任务中的不可删除
for (Long sarId : id) { for (Long sarId : id) {
if (inJobSarIds.contains(sarId)) { if (inJobSarIds.contains(sarId)) {
List<SkyeyePayload> sar = getSar(sarId); throw ServiceException.noLog("[" + getSar(sarId).get(0).getName() + "]在执行任务,不可删除");
if (!sar.isEmpty()) {
throw ServiceException.noLog("[" + sar.get(0).getName() + "]在执行任务,不可删除");
} }
} }
}
payloadMapper.deleteLogic(id); payloadMapper.deleteLogic(id);
Arrays.stream(id).forEach(sarCache::evictSar); redisTemplate.opsForHash().delete(CacheKey.DEVICE_SAR, id);
} }
@Override @Override
public JmSarStatusDTO getLastStatus(String payloadIp) { public JmSarStatusDTO getLastStatus(String payloadIp) {
JmSarStatusDTO status = sarCache.getLatestStatus(payloadIp); String statusKey = CacheKey.getSarConnect(payloadIp);
if (status == null) { if (!redisUtil.hasKey(statusKey)) {
try { try {
sarControlService.connect(payloadIp); sarControlService.connect(payloadIp);
TimeUnit.SECONDS.sleep(1); TimeUnit.SECONDS.sleep(1);
status = sarCache.getLatestStatus(payloadIp);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
throw new ServiceException("获取雷达状态失败:" + ex.getMessage()); throw new ServiceException("获取雷达状态失败:" + ex.getMessage());
} }
if (status == null) { if (!redisUtil.hasKey(statusKey)) {
throw ServiceException.noLog("无法连接到SAR载荷[" + payloadIp + "]"); throw ServiceException.noLog("无法连接到SAR载荷[" + payloadIp + "]");
} }
} }
return status; return (JmSarStatusDTO) redisUtil.get(statusKey);
} }
@Override @Override

View File

@ -1,7 +1,6 @@
package com.zhangy.skyeye.device.service.impl; package com.zhangy.skyeye.device.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhangy.skyeye.cache.uav.UavCache;
import com.zhangy.skyeye.common.extend.dto.PageDTO; import com.zhangy.skyeye.common.extend.dto.PageDTO;
import com.zhangy.skyeye.common.extend.exception.ServiceException; import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.common.extend.util.ObjectUtil; import com.zhangy.skyeye.common.extend.util.ObjectUtil;
@ -10,21 +9,20 @@ import com.zhangy.skyeye.device.mapper.UavMapper;
import com.zhangy.skyeye.device.service.IUavService; import com.zhangy.skyeye.device.service.IUavService;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO; import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.jm.service.JmJobStatusService; import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.publics.consts.ExecStatusEnum; import com.zhangy.skyeye.publics.consts.ExecStatusEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor
public class UavServiceImpl implements IUavService { public class UavServiceImpl implements IUavService {
@Autowired @Autowired
@ -33,12 +31,8 @@ public class UavServiceImpl implements IUavService {
@Autowired @Autowired
private JmJobStatusService sarJobStatusService; private JmJobStatusService sarJobStatusService;
private final UavCache uavCache; @Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void cacheAll() {
selectList(null).forEach(uavCache::put);
}
@Override @Override
public IPage<SkyeyeUav> selectPage(PageDTO param) { public IPage<SkyeyeUav> selectPage(PageDTO param) {
@ -55,32 +49,37 @@ public class UavServiceImpl implements IUavService {
return uavMapper.selectById(id); return uavMapper.selectById(id);
} }
@PostConstruct
public void cacheAll() {
selectList(null).forEach(e -> {
cache(e);
});
}
private void cache(SkyeyeUav e) {
redisTemplate.opsForHash().put(CacheKey.DEVICE_UAV, e.getId(), e);
}
@Override @Override
public List<SkyeyeUav> get(Long... id) { public List<SkyeyeUav> get(Long... id) {
if (ObjectUtil.isNotEmpty(id)) { if (ObjectUtil.isNotEmpty(id)) {
return Arrays.stream(id) return redisTemplate.opsForHash().multiGet(CacheKey.DEVICE_UAV, Arrays.asList(id));
.map(uavCache::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
} }
// 全量获取 return redisTemplate.opsForHash().values(CacheKey.DEVICE_UAV);
return uavCache.getAll();
} }
@Override @Override
public SkyeyeUav getOne(Long id) { public SkyeyeUav getOne(Long id) {
SkyeyeUav uav = uavCache.get(id); List<SkyeyeUav> list = get(id);
if (uav != null) { if (ObjectUtil.isNotEmpty(list)) {
return uav; return list.get(0);
} }
list = uavMapper.selectById(id);
// 缓存 miss DB 查并回填
List<SkyeyeUav> list = uavMapper.selectById(id);
if (ObjectUtil.isEmpty(list)) { if (ObjectUtil.isEmpty(list)) {
throw new ServiceException("无效的无人机ID" + id); throw new ServiceException("无效的无人机ID" + id);
} }
uav = list.get(0); SkyeyeUav uav = list.get(0);
uavCache.put(uav); cache(uav);
return uav; return uav;
} }
@ -93,9 +92,8 @@ public class UavServiceImpl implements IUavService {
} }
// 从内存查询所有任务中的无人机暂时用SAR状态判断 // 从内存查询所有任务中的无人机暂时用SAR状态判断
Set<Long> jobUavSet = sarJobStatusService.getAll().stream() Set<Long> jobUavSet = sarJobStatusService.getAll().stream()
.flatMap(job -> job.getUavMap().values().stream() .flatMap(job -> job.getUavMap().values().stream().filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER))
.filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER)) .map(JmUavStatusDTO::getSarId)
.map(JmUavStatusDTO::getUavId) // 注意这里是 uavId不是 sarId
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 筛选出未在任务中的无人机 // 筛选出未在任务中的无人机
return uavList.stream() return uavList.stream()
@ -112,8 +110,8 @@ public class UavServiceImpl implements IUavService {
uavMapper.update(e); uavMapper.update(e);
} else { } else {
uavMapper.insert(e); uavMapper.insert(e);
cache(e);
} }
uavCache.put(e);
return e; return e;
} }
@ -125,14 +123,13 @@ public class UavServiceImpl implements IUavService {
// 若是sar则更新缓存 // 若是sar则更新缓存
List<SkyeyeUav> list = selectById(e.getId()); List<SkyeyeUav> list = selectById(e.getId());
if (ObjectUtil.isNotEmpty(list)) { if (ObjectUtil.isNotEmpty(list)) {
uavCache.put(list.get(0)); cache(list.get(0));
} }
return e; return e;
} }
/** /**
* 保存前校验 * 保存前校验
*
* @param e * @param e
* @return 返回已逻辑删除的载荷 * @return 返回已逻辑删除的载荷
*/ */
@ -151,11 +148,12 @@ public class UavServiceImpl implements IUavService {
@Transactional @Transactional
@Override @Override
public void delete(Long... ids) { public void delete(Long... ids) {
// 查询任务中的设备
List<Long> inJobUavIds = sarJobStatusService.getAll() List<Long> inJobUavIds = sarJobStatusService.getAll()
.stream() .stream()
.flatMap(vo -> vo.getUavMap().values().stream()) .flatMap(vo -> vo.getUavMap().values().stream())
.filter(vo -> vo.getSarStatus() != ExecStatusEnum.OVER) .filter(vo -> vo.getSarStatus() != ExecStatusEnum.OVER)
.map(JmUavStatusDTO::getUavId) .map(vo -> vo.getUavId())
.collect(Collectors.toList()); .collect(Collectors.toList());
// 任务中的不可删除 // 任务中的不可删除
for (Long id : ids) { for (Long id : ids) {
@ -164,6 +162,6 @@ public class UavServiceImpl implements IUavService {
} }
} }
uavMapper.deleteLogic(ids); uavMapper.deleteLogic(ids);
Arrays.stream(ids).forEach(uavCache::evict); redisTemplate.opsForHash().delete(CacheKey.DEVICE_UAV, ids);
} }
} }

View File

@ -11,14 +11,20 @@ import com.zhangy.skyeye.jm.dto.JmJobImageDTO;
import com.zhangy.skyeye.jm.entity.JmImage; import com.zhangy.skyeye.jm.entity.JmImage;
import com.zhangy.skyeye.jm.service.JmImageService; import com.zhangy.skyeye.jm.service.JmImageService;
import com.zhangy.skyeye.publics.consts.FileTypeEnum; import com.zhangy.skyeye.publics.consts.FileTypeEnum;
import com.zhangy.skyeye.publics.utils.LocalLockUtil;
import com.zhangy.skyeye.publics.utils.OpenCVUtil; import com.zhangy.skyeye.publics.utils.OpenCVUtil;
import org.opencv.core.Core;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import javax.validation.Valid;
import java.io.File;
import java.net.ConnectException; import java.net.ConnectException;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.zhangy.skyeye.publics.consts.CacheKey.SAR_IMAGE_UPLOAD_LOCK; import static com.zhangy.skyeye.publics.consts.CacheKey.SAR_IMAGE_UPLOAD_LOCK;
@ -32,7 +38,7 @@ public class JmImageController {
private JmImageService sarImageService; private JmImageService sarImageService;
@Autowired @Autowired
private LocalLockUtil localLockUtil; private RedisTemplate redisTemplate;
/** /**
* 分页查询 * 分页查询
@ -74,19 +80,31 @@ public class JmImageController {
@PostMapping("/addHigh") @PostMapping("/addHigh")
public Object addHighImage(@Valid @RequestBody JmJobImageDTO dto) { public Object addHighImage(@Valid @RequestBody JmJobImageDTO dto) {
String lockKey = SAR_IMAGE_UPLOAD_LOCK; String lockKey = SAR_IMAGE_UPLOAD_LOCK;
// 尝试 60 秒获取锁比原来 1 分钟更灵活 String requestId = UUID.randomUUID().toString(); // 唯一标识当前请求
boolean locked = localLockUtil.tryLock(lockKey, 60);
if (!locked) {
throw ServiceException.noLog("正在上传其它图像,请稍后重试!");
}
try { try {
// 执行高精度图像添加逻辑 // 原子操作获取锁设置过期时间30秒
Boolean locked = redisTemplate.opsForValue().setIfAbsent(
lockKey,
requestId,
1,
TimeUnit.MINUTES
);
if (Boolean.FALSE.equals(locked)) {
throw ServiceException.noLog(MessageUtils.message("sar.image.add_highres.eagain"));
}
sarImageService.addHighImage(dto); sarImageService.addHighImage(dto);
} finally { } finally {
// 释放锁 // 仅删除自己的锁Lua脚本保证原子性
localLockUtil.unlock(lockKey); String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then "
+ "return redis.call('del', KEYS[1]) "
+ "else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey),
requestId
);
} }
return "操作完成"; return MessageUtils.message("sar.image.add_highres.success");
} }
/** /**
@ -120,7 +138,6 @@ public class JmImageController {
.map(e -> BeanUtil.copyProperties(e, JmImageKtyDTO.class)) .map(e -> BeanUtil.copyProperties(e, JmImageKtyDTO.class))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
static { static {
//System.loadLibrary(Core.NATIVE_LIBRARY_NAME); //System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
OpenCVUtil.loadNativeDylib(); OpenCVUtil.loadNativeDylib();

View File

@ -1,6 +1,5 @@
package com.zhangy.skyeye.jm.service.impl; package com.zhangy.skyeye.jm.service.impl;
import com.zhangy.skyeye.cache.sar.SarCache;
import com.zhangy.skyeye.common.extend.exception.ServiceException; import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.common.extend.util.MessageUtils; import com.zhangy.skyeye.common.extend.util.MessageUtils;
import com.zhangy.skyeye.jm.dto.*; import com.zhangy.skyeye.jm.dto.*;
@ -15,6 +14,7 @@ import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.publics.consts.ExecStatusEnum; import com.zhangy.skyeye.publics.consts.ExecStatusEnum;
import com.zhangy.skyeye.publics.consts.WebSocketKey; import com.zhangy.skyeye.publics.consts.WebSocketKey;
import com.zhangy.skyeye.publics.utils.CoordUtil; import com.zhangy.skyeye.publics.utils.CoordUtil;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.sar.listen.SarImageUdpProcessor; import com.zhangy.skyeye.sar.listen.SarImageUdpProcessor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -46,7 +46,7 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
private SarImageUdpProcessor imageProcessService; private SarImageUdpProcessor imageProcessService;
@Autowired @Autowired
private SarCache sarCache; private RedisUtil redisUtil;
// @Autowired // @Autowired
// private ISmpSubscriptService subscriptService; // private ISmpSubscriptService subscriptService;
@ -183,9 +183,10 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
} }
for (JmJobPayload payload : newUav.getPayloadList()) { for (JmJobPayload payload : newUav.getPayloadList()) {
// 校验雷达连接状态 // 校验雷达连接状态
if (!sarCache.isConnected(payload.getIp())) { if (!redisUtil.hasKey(CacheKey.getSarConnect(payload.getIp()))) {
throw ServiceException.warnLog(MessageUtils.message("device.sar.offline", payload.getPayloadName())); throw ServiceException.warnLog(MessageUtils.message("device.sar.offline", payload.getPayloadName()));
} }
if (runningPayloadIds.contains(payload.getPayloadId())) { if (runningPayloadIds.contains(payload.getPayloadId())) {
throw ServiceException.warnLog(MessageUtils.message("sar.control.turnon.esarinexec")); throw ServiceException.warnLog(MessageUtils.message("sar.control.turnon.esarinexec"));
} }

View File

@ -1,22 +1,27 @@
package com.zhangy.skyeye.jm.task; package com.zhangy.skyeye.jm.task;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.device.entity.SkyeyePayload; import com.zhangy.skyeye.device.entity.SkyeyePayload;
import com.zhangy.skyeye.device.service.IPayloadService; import com.zhangy.skyeye.device.service.IPayloadService;
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO; import com.zhangy.skyeye.jm.dto.JmJobStatusDTO;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.publics.service.ISysLoginService; import com.zhangy.skyeye.publics.service.ISysLoginService;
import com.zhangy.skyeye.cache.sar.SarCache;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum; import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.sar.exception.SarConnectException; import com.zhangy.skyeye.sar.exception.SarConnectException;
import com.zhangy.skyeye.sar.service.ISarControlService; import com.zhangy.skyeye.sar.service.ISarControlService;
// import com.zhangy.skyeye.smp.dto.SmpUavStatusWsDTO;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* 任务管理定时任务 * 任务管理定时任务
@ -29,6 +34,18 @@ public class JmTaskScheduler {
@Setter @Setter
private boolean isDebug; private boolean isDebug;
@Autowired
private RedisUtil redisUtil;
@Autowired
private JmJobStatusService jobStatusService;
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired @Autowired
private ISysLoginService loginService; private ISysLoginService loginService;
@ -39,7 +56,7 @@ public class JmTaskScheduler {
private IPayloadService payloadService; private IPayloadService payloadService;
@Autowired @Autowired
private SarCache sarCache; // 直接用领域缓存 private ISarControlService sarControlService;
/** /**
* 每1秒向前端推送雷达状态信息断开连接则所有数据置0返回 * 每1秒向前端推送雷达状态信息断开连接则所有数据置0返回
@ -98,24 +115,21 @@ public class JmTaskScheduler {
*/ */
@Scheduled(fixedRate = 10000) @Scheduled(fixedRate = 10000)
public void connectSar() { public void connectSar() {
if (!loginService.hasLogged() || isDebug) { if (!loginService.hasLogged() || isDebug) { // 断开
// 断开逻辑如果需要可遍历 permanentCache 或其他方式获取 ip 列表 /*Map<Object, Object> map = redisUtil.hmget(CacheKey.SAR_CONNECTED);
// 但当前注释掉了保持原样或删除 if (map != null && map.size() > 0) {
} else { map.keySet().forEach(ip -> controlInfoService.sendUdp((String) ip, SarControlTypeEnum.DISCONNECT));
}*/
} else { // 连接
List<SkyeyePayload> sarList = payloadService.getSar(null); List<SkyeyePayload> sarList = payloadService.getSar(null);
sarList.forEach(sar -> { sarList.forEach(sar -> {
String ip = sar.getIp(); if (!redisUtil.hHasKey(CacheKey.SAR_CONNECTED, sar.getIp()) ||
// 判断是否已连接 shortCache 里是否有该 ip 的状态最近有状态包 !redisUtil.hasKey(CacheKey.getSarConnect(sar.getIp()))) {
JmSarStatusDTO status = sarCache.getLatestStatus(ip);
// 判断是否已执行连接指令 permanentCache 里是否有该 ip 的连接标志
boolean hasConnectedFlag = sarCache.isConnected(ip);
// 如果缺少状态 缺少连接标志则尝试连接
if (!hasConnectedFlag || Objects.isNull(status)) {
try { try {
controlInfoService.sendUdp(ip, SarControlTypeEnum.CONNECT); controlInfoService.sendUdp(sar.getIp(), SarControlTypeEnum.CONNECT);
log.info("尝试连接 SAR IP: {}", ip);
} catch (SarConnectException ex) { } catch (SarConnectException ex) {
log.warn("连接雷达[{}]失败:{}", ip, ex.getMessage()); // sar可能没通电连接失败不做处理
log.warn("连接雷达[" + sar.getIp() + "]失败:" + ex.getMessage());
} }
} }
}); });

View File

@ -1,18 +1,16 @@
package com.zhangy.skyeye.publics.advice; package com.zhangy.skyeye.publics.advice;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.zhangy.skyeye.cache.publics.UserTokenCache;
import com.zhangy.skyeye.common.extend.anno.IgnoreAuth; import com.zhangy.skyeye.common.extend.anno.IgnoreAuth;
import com.zhangy.skyeye.common.extend.exception.AuthException; import com.zhangy.skyeye.common.extend.exception.AuthException;
import com.zhangy.skyeye.common.extend.util.JsonUtil; import com.zhangy.skyeye.common.extend.util.JsonUtil;
import com.zhangy.skyeye.common.utils.JwtUtil; import com.zhangy.skyeye.common.utils.JwtUtil;
import com.zhangy.skyeye.publics.dto.UserDTO; import com.zhangy.skyeye.common.utils.SpringUtil;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.publics.utils.SecurityUtil; import com.zhangy.skyeye.publics.utils.SecurityUtil;
import com.zhangy.skyeye.publics.utils.SpringContextUtil; import com.zhangy.skyeye.publics.dto.UserDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
@ -34,9 +32,10 @@ import java.util.Objects;
* 6. 续期 Redis 中的 token 有效期 * 6. 续期 Redis 中的 token 有效期
*/ */
@Slf4j @Slf4j
@Component
public class AuthInterceptor implements AsyncHandlerInterceptor { public class AuthInterceptor implements AsyncHandlerInterceptor {
private final RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行 CORS 预检请求OPTIONS由后续 Filter 统一处理跨域头 // 放行 CORS 预检请求OPTIONS由后续 Filter 统一处理跨域头
@ -58,13 +57,9 @@ public class AuthInterceptor implements AsyncHandlerInterceptor {
if (method.isAnnotationPresent(IgnoreAuth.class)) { if (method.isAnnotationPresent(IgnoreAuth.class)) {
return true; return true;
} }
// 注入 UserTokenCache
UserTokenCache userTokenCache = SpringContextUtil.getBean(UserTokenCache.class);
// 获取并校验 token // 获取并校验 token
String token = SecurityUtil.getToken(request); String token = SecurityUtil.getToken(request);
if (StrUtil.isBlank(token)) { if (token == null || token.trim().isEmpty()) {
throw new AuthException("请求未授权:缺少 token"); throw new AuthException("请求未授权:缺少 token");
} }
// 解析 token 中的用户名信息 // 解析 token 中的用户名信息
@ -86,24 +81,25 @@ public class AuthInterceptor implements AsyncHandlerInterceptor {
log.error("token 中的用户信息解析失败: {}", usernamePayload, e); log.error("token 中的用户信息解析失败: {}", usernamePayload, e);
throw new AuthException("token 格式错误,请重新登录"); throw new AuthException("token 格式错误,请重新登录");
} }
// 获取该用户在 CaffeineCache 中存储的 token // 获取该用户在 Redis 中存储的 token
String tokenKey = userDTO.getTokenKey(); String redisKey = userDTO.getTokenKey();
if (StrUtil.isBlank(tokenKey)) { if (redisKey == null || redisKey.trim().isEmpty()) {
throw new AuthException("用户信息异常,请重新登录"); throw new AuthException("用户信息异常,请重新登录");
} }
// CaffeineCache 中的 token 与当前请求不一致 多设备登录被踢 Object storedToken = redisUtil.get(redisKey);
String storedToken = userTokenCache.getToken(tokenKey); // Redis 中不存在 登录已失效
if (storedToken == null) { if (storedToken == null) {
// 显式处理 null token 已失效或被移除
throw new AuthException("登录状态已失效,请重新登录"); throw new AuthException("登录状态已失效,请重新登录");
} }
if (!Objects.equals(token, storedToken)) { // Redis 中的 token 与当前请求不一致 多设备登录被踢
log.warn("检测到多端登录冲突,用户: {}, 当前token: {}, storedToken: {}", if (!Objects.equals(token, storedToken.toString())) {
usernamePayload, token, storedToken); log.warn("检测到多端登录冲突,用户: {}, 当前token: {}, redisToken: {}",
usernamePayload, token.substring(0, 10) + "...",
storedToken.toString().substring(0, 10) + "...");
throw new AuthException("该账号已在其他设备登录,请重新登录"); throw new AuthException("该账号已在其他设备登录,请重新登录");
} }
// 续期Caffeine expireAfterWrite 是从最后写入开始算所以 put 一下即可续期 // 校验通过 续期 Redis token 有效时间
userTokenCache.storeToken(tokenKey, storedToken); redisUtil.expire(redisKey, JwtUtil.EXPIRE_TIME / 1000);
return true; return true;
} }
} }

View File

@ -1,58 +1,112 @@
package com.zhangy.skyeye.publics.consts; package com.zhangy.skyeye.publics.consts;
// import com.zhangy.skyeye.smp.dto.SmpWayPointResDTO;
public class CacheKey { public class CacheKey {
/*==================== publics 模块 ====================*/ /*==========================================
// 用户token30分钟 publics 模块用 skyeye: 前缀
==========================================*/
/**
* 用户token30分钟
*/
private static final String USER_TOEKN = "skyeye:user:token:%s@%s"; private static final String USER_TOEKN = "skyeye:user:token:%s@%s";
// 获取token key /**
public static String getToken(Long userId, String userName) { * 获取token key
*/
public static String getToekn(Long userId, String userName) {
return String.format(CacheKey.USER_TOEKN, userId, userName); return String.format(CacheKey.USER_TOEKN, userId, userName);
} }
/*==================== 分布式锁 ====================*/ /*==========================================
// 上传sar图像锁 分布式锁用 skyeye:lock: 前缀
==========================================*/
/**
* 上传sar图像锁
*/
public static final String SAR_IMAGE_UPLOAD_LOCK = "skyeye:lock:image"; public static final String SAR_IMAGE_UPLOAD_LOCK = "skyeye:lock:image";
/*==================== SAR 模块 ====================*/ /*==========================================
// 控制回包1秒 sar 模块用 skyeye:sar: 前缀
==========================================*/
/**
* 控制回包1秒
*/
private static final String SAR_CONTROL_BACK = "skyeye:sar:control"; private static final String SAR_CONTROL_BACK = "skyeye:sar:control";
// sar状态1秒
private static final String SAR_STATUS = "skyeye:sar:status"; /**
// 已执行连接命令的sar永久 * sar状态1秒
*/
private static final String SAR_STATUS = "skyeye:sar:status:";
/**
* 已执行连接命令的sar永久
*/
public static final String SAR_CONNECTED = "skyeye:sar:connected"; public static final String SAR_CONNECTED = "skyeye:sar:connected";
// 获取控制回包key加ip /**
* 获取控制回包key加ip
*/
public static String getSarControlBack(String ip) { public static String getSarControlBack(String ip) {
return SAR_CONTROL_BACK + ":" + ip; return SAR_CONTROL_BACK + ip;
}
// 获取状态key加ip
public static String getSarStatus(String ip) {
return SAR_STATUS + ":" + ip;
}
// 获取已执行连接命令的sar-key加ip
public static String getSarConnected(String ip) {
return SAR_CONNECTED + ":" + ip;
} }
/*==================== Device 模块 ====================*/ /**
// sar载荷永久 * 获取状态key加ip
*/
public static String getSarConnect(String ip) {
return SAR_STATUS + ip;
}
/*==========================================
device 模块用 skyeye:device: 前缀
==========================================*/
/**
* sar载荷永久
*/
public static final String DEVICE_SAR = "skyeye:device:sar"; public static final String DEVICE_SAR = "skyeye:device:sar";
// 无人机永久
/**
* 无人机永久
*/
public static final String DEVICE_UAV = "skyeye:device:uav"; public static final String DEVICE_UAV = "skyeye:device:uav";
/*==================== SMP 模块 ====================*/ /*==========================================
// 运动规划响应1秒 smp 模块用 skyeye:smp: 前缀
public static final String SMP_WAYPOINT_RES = "skyeye:smp:waypoint:"; ==========================================*/
// 飞行控制响应1秒
public static final String SMP_FLIGHT_RES = "skyeye:smp:flight:"; /**
// 云台控制响应1秒 * 运动规划响应1秒
public static final String SMP_GIMBALMGR_RES = "skyeye:smp:gimbalmgr:"; */
// 数据订阅响应1秒 //public static final String SMP_WAYPOINT_RES = "skyeye:smp:waypoint:";
public static final String SMP_SUBSCRIPT_RES = "skyeye:smp:subscript:";
// 数据订阅回传数据1秒 /**
public static final String SMP_SUBSCRIPT_DATA = "skyeye:smp:subscript:"; * 飞行控制响应1秒
// 相机视频流响应1秒 */
public static final String SMP_VIDEO_RES = "skyeye:smp:video:"; //public static final String SMP_FLIGHT_RES = "skyeye:smp:flight:";
/**
* 云台控制响应1秒
*/
//public static final String SMP_GIMBALMGR_RES = "skyeye:smp:gimbalmgr:";
/**
* 数据订阅响应1秒
*/
//public static final String SMP_SUBSCRIPT_RES = "skyeye:smp:subscript:";
/**
* 数据订阅回传数据1秒
*/
//public static final String SMP_SUBSCRIPT_DATA = "skyeye:smp:subscript:";
/**
* 相机视频流响应1秒
*/
//public static final String SMP_VIDEO_RES = "skyeye:smp:video:";
} }

View File

@ -2,7 +2,6 @@ package com.zhangy.skyeye.publics.controller;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.zhangy.skyeye.cache.publics.UserTokenCache;
import com.zhangy.skyeye.common.extend.anno.IgnoreAuth; import com.zhangy.skyeye.common.extend.anno.IgnoreAuth;
import com.zhangy.skyeye.common.extend.anno.OperationLog; import com.zhangy.skyeye.common.extend.anno.OperationLog;
import com.zhangy.skyeye.common.extend.dto.PageDTO; import com.zhangy.skyeye.common.extend.dto.PageDTO;
@ -11,6 +10,7 @@ import com.zhangy.skyeye.common.extend.util.MessageUtils;
import com.zhangy.skyeye.common.extend.util.ObjectUtil; import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.common.pojo.result.Result; import com.zhangy.skyeye.common.pojo.result.Result;
import com.zhangy.skyeye.common.utils.JwtUtil; import com.zhangy.skyeye.common.utils.JwtUtil;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.publics.consts.CacheKey; import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.publics.dto.RegisterDTO; import com.zhangy.skyeye.publics.dto.RegisterDTO;
import com.zhangy.skyeye.publics.dto.SysUserPwdDTO; import com.zhangy.skyeye.publics.dto.SysUserPwdDTO;
@ -19,7 +19,7 @@ import com.zhangy.skyeye.publics.entity.SysLog;
import com.zhangy.skyeye.publics.entity.SysUser; import com.zhangy.skyeye.publics.entity.SysUser;
import com.zhangy.skyeye.publics.service.ISysUserService; import com.zhangy.skyeye.publics.service.ISysUserService;
import com.zhangy.skyeye.publics.utils.SecurityUtil; import com.zhangy.skyeye.publics.utils.SecurityUtil;
import lombok.RequiredArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -30,7 +30,7 @@ import java.util.List;
@Validated @Validated
@RestController @RestController
@RequiredArgsConstructor @AllArgsConstructor
@Slf4j @Slf4j
@RequestMapping("/user") @RequestMapping("/user")
public class SysUserController { public class SysUserController {
@ -38,11 +38,14 @@ public class SysUserController {
@Autowired @Autowired
private ISysUserService sysUserService; private ISysUserService sysUserService;
private final UserTokenCache userTokenCache; @Autowired
private RedisUtil redisUtil;
/** token失效时间 */
private final int TOKEN_EXPIRE = 60 * 60 * 24;
/** /**
* 登录接口 * 登录接口
*
* @return * @return
*/ */
@IgnoreAuth @IgnoreAuth
@ -54,17 +57,15 @@ public class SysUserController {
throw ServiceException.noLog(MessageUtils.message("user.login.error")); throw ServiceException.noLog(MessageUtils.message("user.login.error"));
} }
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
String payload = JSONUtil.toJsonStr(userDTO); // 保持原逻辑 String key = JSONUtil.toJsonStr(userDTO);
String accessToken = JwtUtil.sign(payload, user.getPassword()); String accessToken = JwtUtil.sign(key, user.getPassword());
String tokenKey = userDTO.getTokenKey(); // skyeye:user:token:id@account redisUtil.set(userDTO.getTokenKey(), accessToken, TOKEN_EXPIRE);
userTokenCache.storeToken(tokenKey, accessToken);
userDTO.setToken(accessToken); userDTO.setToken(accessToken);
return Result.successData(MessageUtils.message("user.login.success"), userDTO); return Result.successData(MessageUtils.message("user.login.success"), userDTO);
} }
/** /**
* 修改密码 * 修改密码
*
* @param dto * @param dto
* @return * @return
*/ */
@ -93,8 +94,8 @@ public class SysUserController {
public Result logout() { public Result logout() {
UserDTO userDTO = SecurityUtil.getUser(); UserDTO userDTO = SecurityUtil.getUser();
if (userDTO != null) { if (userDTO != null) {
String tokenKey = CacheKey.getToken(userDTO.getId(), userDTO.getAccount()); String key = CacheKey.getToekn(userDTO.getId(), userDTO.getAccount());
userTokenCache.evictToken(tokenKey); redisUtil.del(key);
} }
return Result.status(true); return Result.status(true);
} }

View File

@ -10,37 +10,26 @@ public class UserDTO {
private Long id; private Long id;
/** /** 用户名 */
* 用户名
*/
private String account; private String account;
/** /** 状态 0禁用 1正常 */
* 状态 0禁用 1正常
*/
private Integer status; private Integer status;
/** /** 用户所有的权限信息 */
* 用户所有的权限信息
*/
private Set<String> authorities; private Set<String> authorities;
/** /** 角色ID */
* 角色ID
*/
private Integer roleId; private Integer roleId;
/** /** Token */
* Token
*/
private String token; private String token;
/** /**
* 获取token的缓存键 * 获取token的缓存键
*
* @return * @return
*/ */
public String getTokenKey() { public String getTokenKey() {
return CacheKey.getToken(id, account); return CacheKey.getToekn(id, account);
} }
} }

View File

@ -1,25 +1,57 @@
package com.zhangy.skyeye.publics.service.impl; package com.zhangy.skyeye.publics.service.impl;
import com.zhangy.skyeye.cache.publics.UserTokenCache; import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.publics.service.ISysLoginService; import com.zhangy.skyeye.publics.service.ISysLoginService;
import com.zhangy.skyeye.publics.utils.CacheUtil; import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException;
@Service @Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements ISysLoginService { public class SysLoginServiceImpl implements ISysLoginService {
private final UserTokenCache userTokenCache; @Autowired
private RedisTemplate redisTemplate;
@Override @Override
public boolean hasLogged() { public boolean hasLogged() {
// 获取底层 Caffeine Cache return Boolean.TRUE.equals(redisTemplate.execute((RedisCallback<Boolean>) connection -> {
return CacheUtil.size(userTokenCache) > 0; try (Cursor<byte[]> cursor = connection.scan(
ScanOptions.scanOptions()
.match("skyeye:user:token:*")
.count(1) // 只需要找到1个匹配项
.build())) {
return cursor.hasNext();
// } catch (IOException ex) {
} catch (Exception ex) {
throw new ServiceException("查询当前登录人数失败:" + ex.getMessage());
}
}));
} }
@Override @Override
public int countLogged() { public int countLogged() {
return (int) CacheUtil.size(userTokenCache); return (int) redisTemplate.execute((RedisCallback<Integer>) connection -> {
int count = 0;
ScanOptions options = ScanOptions.scanOptions()
.match("skyeye:user:token:*")
.count(100) // 合理的COUNT值
.build();
try (Cursor<byte[]> cursor = connection.scan(options)) {
while (cursor.hasNext()) {
count++;
cursor.next();
}
// } catch (IOException ex) {
} catch (Exception ex) {
}
return count;
});
} }
} }

View File

@ -1,74 +0,0 @@
package com.zhangy.skyeye.publics.utils;
import com.zhangy.skyeye.cache.publics.UserTokenCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 缓存工具
* @AUTHOR: GuanCheng Long
* @DATE: 2026/1/30 13:34
*/
@Component
@Slf4j
public class CacheUtil {
@SuppressWarnings("unchecked")
public static <T> T get(Cache cache, Object key, Class<T> clazz) {
if (cache == null) return null;
Cache.ValueWrapper wrapper = cache.get(key);
if (wrapper == null) return null;
Object value = wrapper.get();
if (clazz.isInstance(value)) {
return (T) value;
}
// 可选log.warn("缓存类型不匹配,期望 {},实际 {}", clazz.getName(), value.getClass().getName());
return null;
}
// CacheUtil.java 中新增
@SuppressWarnings("unchecked")
public static <T> List<T> getAll(Cache cache, Class<T> clazz) {
if (cache == null) return Collections.emptyList();
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache =
(com.github.benmanes.caffeine.cache.Cache<Object, Object>) cache.getNativeCache();
return nativeCache.asMap().values().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(Collectors.toList());
}
public static long size(Cache springCache) {
if (springCache == null) {
return 0;
}
try {
com.github.benmanes.caffeine.cache.Cache<?, ?> nativeCache =
(com.github.benmanes.caffeine.cache.Cache<?, ?>) springCache.getNativeCache();
return nativeCache.estimatedSize(); // nativeCache.asMap().size()
} catch (Exception e) {
log.warn("无法获取缓存大小,可能是非 Caffeine 实现", e);
return 0;
}
}
public static long size(UserTokenCache tokenCache) {
if (tokenCache == null) return 0;
Object nativeCache = tokenCache.getNativeCache();
if (nativeCache instanceof com.github.benmanes.caffeine.cache.Cache) {
com.github.benmanes.caffeine.cache.Cache<?, ?> caffeineCache =
(com.github.benmanes.caffeine.cache.Cache<?, ?>) nativeCache;
return caffeineCache.estimatedSize();
}
return 0;
}
}

View File

@ -1,52 +0,0 @@
package com.zhangy.skyeye.publics.utils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @PROJECT_NAME: skyeyesystem_online
* @DESCRIPTION: 本地内存锁工具替代 Redis 分布式锁适用于单实例部署
* @AUTHOR: GuanCheng Long
* @DATE: 2026/1/30 13:14
*/
@Component
public class LocalLockUtil {
// Caffeine 缓存key=锁名, value=ReentrantLock
private static final Cache<String, ReentrantLock> LOCK_CACHE = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES) // 锁闲置 5 分钟自动清理
.maximumSize(100) // 最多 100 个锁
.build();
/**
* 获取或创建锁并尝试获取
*
* @param lockKey 锁的 key
* @param timeoutSeconds 尝试获取锁的超时时间
* @return true=获取成功false=获取失败被占用
*/
public boolean tryLock(String lockKey, long timeoutSeconds) {
ReentrantLock lock = LOCK_CACHE.get(lockKey, k -> new ReentrantLock());
try {
return lock.tryLock(timeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 释放锁仅释放自己的
*/
public void unlock(String lockKey) {
ReentrantLock lock = LOCK_CACHE.getIfPresent(lockKey);
if (lock != null && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}

View File

@ -1,25 +0,0 @@
package com.zhangy.skyeye.publics.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
log.info("SpringContextUtil 初始化完成");
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}

View File

@ -1,11 +1,15 @@
package com.zhangy.skyeye.sar.context; package com.zhangy.skyeye.sar.context;
import com.zhangy.skyeye.cache.uav.UavCache;
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO; import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO; import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
*
*/
/** /**
* @PROJECT_NAME: skyeyesystem * @PROJECT_NAME: skyeyesystem
* @DESCRIPTION: SAR 任务上下文提供者实现基于 Redis 缓存避免循环依赖 * @DESCRIPTION: SAR 任务上下文提供者实现基于 Redis 缓存避免循环依赖
@ -18,11 +22,14 @@ import org.springframework.stereotype.Component;
@Component @Component
public class SarTaskContextProviderImpl implements SarTaskContextProvider { public class SarTaskContextProviderImpl implements SarTaskContextProvider {
private final UavCache uavCache; @Autowired
private RedisUtil redisUtil;
public SarTaskContextProviderImpl(UavCache uavCache) { // Redis 键前缀
this.uavCache = uavCache; private static final String UAV_STATUS_KEY_PREFIX = "sar:context:uav:";
}
// 缓存过期时间
private static final long CACHE_EXPIRE_SECONDS = 600; // 10 分钟
@Override @Override
public JmUavStatusDTO getCurrentUav(String payloadIp) { public JmUavStatusDTO getCurrentUav(String payloadIp) {
@ -30,8 +37,16 @@ public class SarTaskContextProviderImpl implements SarTaskContextProvider {
log.warn("获取 uav 状态失败payloadIp 为空"); log.warn("获取 uav 状态失败payloadIp 为空");
return null; return null;
} }
String key = "sar:context:uav:" + payloadIp; // 保持原 key 格式
return uavCache.getShort(key, JmUavStatusDTO.class); String key = UAV_STATUS_KEY_PREFIX + payloadIp;
Object obj = redisUtil.get(key);
if (obj instanceof JmUavStatusDTO) {
return (JmUavStatusDTO) obj;
}
log.debug("Redis 中未找到 IP={} 的 uav 状态", payloadIp);
return null;
} }
@Override @Override
@ -65,10 +80,11 @@ public class SarTaskContextProviderImpl implements SarTaskContextProvider {
return; return;
} }
String key = "sar:context:uav:" + payloadIp; String key = UAV_STATUS_KEY_PREFIX + payloadIp;
try { try {
uavCache.putShort(key, uavStatus);// 自动过期 10 分钟 // 设置值 + 过期时间
log.debug("已更新缓存key={}, jobExecId={}", key, uavStatus.getJobExecId()); redisUtil.set(key, uavStatus, CACHE_EXPIRE_SECONDS);
log.debug("已更新 Redis 缓存key={}, jobExecId={}", key, uavStatus.getJobExecId());
} catch (Exception e) { } catch (Exception e) {
log.error("更新 SAR uav 状态缓存失败ip={}", payloadIp, e); log.error("更新 SAR uav 状态缓存失败ip={}", payloadIp, e);
} }
@ -81,8 +97,8 @@ public class SarTaskContextProviderImpl implements SarTaskContextProvider {
if (payloadIp == null) { if (payloadIp == null) {
return; return;
} }
String key = "sar:context:uav:" + payloadIp; String key = UAV_STATUS_KEY_PREFIX + payloadIp;
uavCache.evictShort(key); redisUtil.del(key);
log.debug("SAR uav 缓存已清理:{}", key); log.debug("SAR uav 缓存已标记清理(或自然过期){}", key);
} }
} }

View File

@ -1,20 +1,21 @@
package com.zhangy.skyeye.sar.control; package com.zhangy.skyeye.sar.control;
import com.zhangy.skyeye.cache.sar.SarCache; import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
import com.zhangy.skyeye.sar.dto.SarControlDTO; import com.zhangy.skyeye.sar.dto.SarControlDTO;
import com.zhangy.skyeye.sar.dto.SarControlParamDTO; import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* 控制建立连接 * 控制连接
* 连接成功后在回传状态时会将状态信息放入缓存这里只记录连接标志
*/ */
@Slf4j @Slf4j
@Order(1) @Order(1)
@ -22,11 +23,12 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SarControlConnectStrategy implements ISarControlStrategy { public class SarControlConnectStrategy implements ISarControlStrategy {
private final SarCache sarCache; private final RedisUtil redisUtil;
@Override @Override
public boolean supports(SarControlParamDTO param) { public boolean supports(SarControlParamDTO param) {
return param.getControlType() == SarControlTypeEnum.CONNECT; SarControlTypeEnum controlType = param.getControlType();
return controlType == SarControlTypeEnum.CONNECT;
} }
@Override @Override
@ -38,9 +40,8 @@ public class SarControlConnectStrategy implements ISarControlStrategy {
@Override @Override
public void sendPost(SarControlDTO sar) { public void sendPost(SarControlDTO sar) {
// 建立连接后记录连接标志 // 建立连接后回传状态时会将状态信息放入缓存
sarCache.markConnected(sar.getIp()); redisUtil.hset(CacheKey.SAR_CONNECTED, sar.getIp(), new Date());
log.info("SAR [{}] 已标记为连接状态", sar.getIp());
} }
@Override @Override
@ -48,3 +49,6 @@ public class SarControlConnectStrategy implements ISarControlStrategy {
return 0; return 0;
} }
} }

View File

@ -1,8 +1,8 @@
package com.zhangy.skyeye.sar.control; package com.zhangy.skyeye.sar.control;
import com.zhangy.skyeye.cache.sar.SarCache;
import com.zhangy.skyeye.common.extend.exception.ServiceException; import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.common.extend.util.ObjectUtil; import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.publics.consts.CacheKey; import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.sar.dto.SarControlDTO; import com.zhangy.skyeye.sar.dto.SarControlDTO;
import com.zhangy.skyeye.sar.dto.SarControlPackDTO; import com.zhangy.skyeye.sar.dto.SarControlPackDTO;
@ -14,6 +14,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -58,7 +59,7 @@ public class SarControlContext {
private final int POLLING_INTERVAL = 100; private final int POLLING_INTERVAL = 100;
@Autowired @Autowired
private SarCache sarCache; private RedisUtil redisUtil;
/** /**
* 应答超时 * 应答超时
@ -73,7 +74,6 @@ public class SarControlContext {
/** /**
* 执行 * 执行
*
* @param param * @param param
*/ */
public void execute(SarControlParamDTO param) { public void execute(SarControlParamDTO param) {
@ -103,92 +103,60 @@ public class SarControlContext {
/** /**
* 发送未收到回执则重发 * 发送未收到回执则重发
*
* @param control * @param control
*/ */
private void sendUdp(SarControlDTO control) { private void sendUdp(SarControlDTO control) {
SarControlTypeEnum controlType = control.getControlType(); SarControlTypeEnum controlType = control.getControlType();
String targetIp = control.getIp(); String ip = control.getIp();
log.info("准备发送雷达控制指令 | 类型:{} | 目标IP:{}", controlType, targetIp); log.debug("开始发送雷达控制指令[" + controlType + "]----------------------");
String cacheKey = CacheKey.getSarControlBack(targetIp); //System.out.println(control);
byte[] payload;
try {
payload = pack(control);
} catch (Exception e) {
log.error("打包控制指令失败 | 类型:{} | ip:{} | {}", controlType, targetIp, e.getMessage(), e);
throw new ServiceException("控制指令打包失败:" + e.getMessage());
}
try (DatagramSocket socket = new DatagramSocket()) { try (DatagramSocket socket = new DatagramSocket()) {
InetSocketAddress address = new InetSocketAddress(targetIp, PORT); byte[] content = pack(control);
socket.connect(address); socket.connect(new InetSocketAddress(ip, PORT));
DatagramPacket packet = new DatagramPacket(payload, payload.length); DatagramPacket packet = new DatagramPacket(content, content.length);
SarErrorDTO response = null;
int retryCount = 0;
while (response == null && retryCount < RETRY_MAX) {
// 发送
try {
socket.send(packet);
log.info("UDP指令已发送 | 第{}次尝试 | 类型:{} | ip:{}",
retryCount + 1, controlType, targetIp);
} catch (IOException e) {
log.warn("发送UDP失败 | 第{}次尝试 | {}", retryCount + 1, e.getMessage());
retryCount++;
continue;
}
// 等待回执轮询方式
response = waitForResponse(cacheKey, targetIp, controlType);
if (response == null) {
retryCount++;
log.warn("第{}次发送未收到回执 | 类型:{} | ip:{}", retryCount, controlType, targetIp);
}
}
// 最终判断
if (response == null) {
String msg = String.format("雷达[%s]控制指令[%s]发送失败:超时无应答", targetIp, controlType);
log.error(msg);
throw new SarConnectException(msg + ",请重试");
}
// 业务状态判断
if (response.getExecStatus() == 1) {
String msg = String.format("雷达[%s]控制指令[%s]执行失败:状态码=1数据结构错误",
targetIp, controlType);
log.error(msg);
throw new ServiceException(msg + ",请检查指令内容后重试");
}
log.info("雷达控制指令发送成功 | 类型:{} | ip:{} | 状态:{}", controlType, targetIp, response.getExecStatus());
} catch (IOException e) {
String msg = String.format("雷达控制指令[%s]网络异常 | ip:%s | %s", controlType, targetIp, e.getMessage());
log.error(msg, e);
throw ServiceException.errorLog(msg);
}
}
/** int failCount = 0;
* 轮询等待缓存中的回执带超时 SarErrorDTO info = null;
*/ while (info == null && failCount < RETRY_MAX) { // 失败重试
private SarErrorDTO waitForResponse(String cacheKey, String ip, SarControlTypeEnum controlType) { socket.send(packet);
long start = System.currentTimeMillis(); // 每0.1秒取回执1秒后超时
while (System.currentTimeMillis() - start < ANSWER_TIMEOUT) { long startTime = System.currentTimeMillis();
SarErrorDTO dto = sarCache.getControlBack(ip); // 如果 SarCache 封装了泛型方法可以直接用 String cacheKey = CacheKey.getSarControlBack(ip);
if (dto != null) {
sarCache.saveStatus(cacheKey, null); // 清空 shortCache 对应的 key while (System.currentTimeMillis() - startTime < ANSWER_TIMEOUT) {
log.info("收到雷达回执 | ip:{} | 控制类型:{} | 状态:{}", ip, controlType, dto.getExecStatus()); if (redisUtil.hasKey(cacheKey)) {
return dto; info = (SarErrorDTO) redisUtil.get(cacheKey);
} redisUtil.del(cacheKey);
break;
} else {
try { try {
Thread.sleep(POLLING_INTERVAL); Thread.sleep(POLLING_INTERVAL);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
log.warn("等待雷达回执时被中断 | ip:{}", ip);
return null;
} }
} }
return null; // 超时 }
if (info == null) { // 超时未收到回执
failCount++;
} else if (info.getExecStatus() == 1) {
// 只有发送的数据结构错误时才会返回错误状态并不会因为业务不允许返回错误
throw new ServiceException("控制指令[" + controlType + "]执行状态错误,请重试");
}
}
if (info == null) {
throw new SarConnectException("控制指令[" + controlType + "]发送失败,雷达[" + ip + "]无应答,请重试");
} else {
log.info("雷达控制指令[" + controlType + "]发送完毕----------------------");
}
} catch (RedisConnectionFailureException ex) {
throw ServiceException.errorLog("无法连接到Redis服务");
} catch (IOException ex) {
throw ServiceException.errorLog("控制指令[" + controlType + "]发送失败 " + ex.getMessage());
}
} }
/** /**
* 封包 * 封包
*
* @param sarControl * @param sarControl
* @return * @return
*/ */

View File

@ -1,9 +1,10 @@
package com.zhangy.skyeye.sar.control; package com.zhangy.skyeye.sar.control;
import com.zhangy.skyeye.cache.sar.SarCache; import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.sar.dto.SarControlDTO;
import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum; import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
import com.zhangy.skyeye.sar.dto.SarControlDTO;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@ -21,7 +22,7 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SarControlDisconnectStrategy implements ISarControlStrategy { public class SarControlDisconnectStrategy implements ISarControlStrategy {
private final SarCache sarCache; private final RedisUtil redisUtil;
@Override @Override
public boolean supports(SarControlParamDTO param) { public boolean supports(SarControlParamDTO param) {
@ -36,12 +37,15 @@ public class SarControlDisconnectStrategy implements ISarControlStrategy {
return Collections.singletonList(connect); return Collections.singletonList(connect);
} }
/**
* 删除缓存键
* @param sar
*/
@Override @Override
public void sendPost(SarControlDTO sar) { public void sendPost(SarControlDTO sar) {
// 清理短时状态 String connectKey = CacheKey.getSarConnect(sar.getIp());
sarCache.removeConnection(sar.getIp()); // permanentCache 移除连接标志 redisUtil.del(connectKey);
sarCache.saveStatus(sar.getIp(), null); // 等同于清空 shortCache 中的状态 redisUtil.hdel(CacheKey.SAR_CONNECTED, sar.getIp());
log.info("已断开 SAR IP: {},缓存清理完成", sar.getIp());
} }
@Override @Override

View File

@ -42,12 +42,14 @@ public class SarControlController {
*/ */
@RequestMapping("/turnon") @RequestMapping("/turnon")
public Result turnOn(@RequestBody String ip) { public Result turnOn(@RequestBody String ip) {
String ipVal; String ipVal = "";
try { try {
// 1. 使用 ObjectMapper JSON 字符串解析成一个 JsonNode // 1. 使用 ObjectMapper JSON 字符串解析成一个 JsonNode
JsonNode rootNode = objectMapper.readTree(ip); JsonNode rootNode = objectMapper.readTree(ip);
// 2. 从树中获取 "payloadId" 节点并将其值转换为文本 // 2. 从树中获取 "payloadId" 节点并将其值转换为文本
ipVal = rootNode.path("payloadId").asText(); ipVal = rootNode.path("payloadId").asText();
// 检查是否成功获取 // 检查是否成功获取
if (ipVal == null || ipVal.isEmpty()) { if (ipVal == null || ipVal.isEmpty()) {
// 根据你的业务逻辑返回错误例如 // 根据你的业务逻辑返回错误例如

View File

@ -1,15 +1,15 @@
package com.zhangy.skyeye.sar.listen; package com.zhangy.skyeye.sar.listen;
import com.zhangy.skyeye.cache.sar.SarCache; import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO; import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
import com.zhangy.skyeye.jm.service.JmJobStatusService; import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.sar.dto.SarErrorDTO; import com.zhangy.skyeye.sar.dto.SarErrorDTO;
import com.zhangy.skyeye.sar.dto.SarStatusPackDTO; import com.zhangy.skyeye.sar.dto.SarStatusPackDTO;
import com.zhangy.skyeye.sar.enums.SarErrorTypeEnum; import com.zhangy.skyeye.sar.enums.SarErrorTypeEnum;
import com.zhangy.skyeye.sar.task.CircularBufferQueue; import com.zhangy.skyeye.sar.task.CircularBufferQueue;
import com.zhangy.skyeye.sar.task.DiscardOldestPolicyWithLog; import com.zhangy.skyeye.sar.task.DiscardOldestPolicyWithLog;
import com.zhangy.skyeye.sar.task.PriorityThreadFactory; import com.zhangy.skyeye.sar.task.PriorityThreadFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit;
*/ */
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor
public class SarStatusListener extends SarAbstractListener { public class SarStatusListener extends SarAbstractListener {
/** /**
@ -50,7 +49,8 @@ public class SarStatusListener extends SarAbstractListener {
@Value("${skyeye.sar.udp.status.connect-timeout:15}") @Value("${skyeye.sar.udp.status.connect-timeout:15}")
private int connectTimeout; private int connectTimeout;
private final SarCache sarCache; @Autowired
private RedisUtil redisUtil;
@Autowired @Autowired
private JmJobStatusService sarJobStatusService; private JmJobStatusService sarJobStatusService;
@ -70,34 +70,38 @@ public class SarStatusListener extends SarAbstractListener {
@Override @Override
protected void processData(DatagramPacket packet) throws IOException { protected void processData(DatagramPacket packet) throws IOException {
socket.receive(packet); socket.receive(packet);
// 过滤无效数据包
if (packet.getLength() != 100) { if (packet.getLength() != 100) {
return; return;
} }
log.debug("接收到状态包----------------------"); log.debug("接收到状态包----------------------");
String ip = packet.getAddress().getHostAddress(); String ip = packet.getAddress().getHostAddress();
// 处理接收到的数据
SarStatusPackDTO packDTO = SarStatusPackDTO.parse(ip, packet.getData()); SarStatusPackDTO packDTO = SarStatusPackDTO.parse(ip, packet.getData());
if (packDTO == null) { if (packDTO == null) {
if (running) if (running)
log.warn("状态包校验失败,已丢弃。"); log.warn("[" + packDTO.getPayloadIp() + "]状态包校验失败,已丢弃。错误包=" +
packDTO.getErrorPacketStatus() + ",状态包=" + packDTO.getDevicePacketStatus());
return; return;
} }
// 错误包 回执 // 错误包结果放入缓存超时1秒
int errorStatus = packDTO.getErrorPacketStatus(); int errorStatus = packDTO.getErrorPacketStatus();
if (errorStatus == 1) { if (errorStatus == 1) {
SarErrorDTO info = packDTO.getDeviceErrorInfo(); SarErrorDTO info = packDTO.getDeviceErrorInfo();
if (info.getErrorPacketType() == SarErrorTypeEnum.RESULT) { if (info.getErrorPacketType() == SarErrorTypeEnum.RESULT) {
log.debug("收到回执包:{}", ip); log.debug("收到回执包:" + ip);
sarCache.saveControlBack(ip, info); // 使用 SarCache类型安全自动短期缓存自动 2s 过期 redisUtil.set(CacheKey.getSarControlBack(ip), info, answerTimeout, TimeUnit.SECONDS);
} }
} }
// 状态包 // 状态包
int deviceStatus = packDTO.getDevicePacketStatus(); int deviceStatus = packDTO.getDevicePacketStatus();
if (deviceStatus == 1) { if (deviceStatus == 1) {
JmSarStatusDTO info = packDTO.getDeviceStatusInfo(); JmSarStatusDTO info = packDTO.getDeviceStatusInfo();
log.debug("sar开机状态{}", info.getIsBoot()); log.debug("sar开机状态" + info.getIsBoot());
sarJobStatusService.update(ip, info); sarJobStatusService.update(ip, info);
sarCache.saveStatus(ip, info); // 使用 SarCache类型安全自动短期缓存自动过期 redisUtil.set(CacheKey.getSarConnect(ip), info, answerTimeout, TimeUnit.SECONDS);
//System.out.println(info);
} }
log.debug("状态包解析完毕"); log.debug("----------------------状态包解析完毕");
} }
} }

View File

@ -5,13 +5,16 @@ import com.zhangy.skyeye.common.extend.util.JsonUtil;
import com.zhangy.skyeye.jm.dto.JmJobDTO; import com.zhangy.skyeye.jm.dto.JmJobDTO;
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO; import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
import com.zhangy.skyeye.jm.entity.JmJobPayload; import com.zhangy.skyeye.jm.entity.JmJobPayload;
import com.zhangy.skyeye.cache.sar.SarCache; import com.zhangy.skyeye.jm.entity.JmJobUav;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.sar.control.SarControlContext; import com.zhangy.skyeye.sar.control.SarControlContext;
import com.zhangy.skyeye.sar.dto.SarControlParamDTO; import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum; import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
import com.zhangy.skyeye.sar.service.ISarControlService; import com.zhangy.skyeye.sar.service.ISarControlService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional; import java.util.Optional;
@ -27,7 +30,9 @@ import static com.zhangy.skyeye.jm.consts.JmJobModeEnum.CRUISE;
public class SarControlServiceImpl implements ISarControlService { public class SarControlServiceImpl implements ISarControlService {
private final SarControlContext udpSendContext; private final SarControlContext udpSendContext;
private final SarCache sarCache;
@Autowired
private RedisUtil redisUtil;
@Override @Override
public void sendUdp(JmJobDTO job) { public void sendUdp(JmJobDTO job) {
@ -62,6 +67,7 @@ public class SarControlServiceImpl implements ISarControlService {
}); });
} }
@Override @Override
public void sendUdp(SarControlParamDTO param) { public void sendUdp(SarControlParamDTO param) {
udpSendContext.execute(param); udpSendContext.execute(param);
@ -81,30 +87,28 @@ public class SarControlServiceImpl implements ISarControlService {
@Override @Override
public void turnOn(String ip) { public void turnOn(String ip) {
// 用领域缓存判断连接和状态 if (!redisUtil.hHasKey(CacheKey.SAR_CONNECTED, ip) || !redisUtil.hasKey(CacheKey.getSarConnect(ip))) {
boolean connected = sarCache.isConnected(ip);
JmSarStatusDTO status = sarCache.getLatestStatus(ip);
if (!connected || status == null) {
throw new ServiceException("请先加电并连接sar"); throw new ServiceException("请先加电并连接sar");
} }
SarControlParamDTO param = new SarControlParamDTO(ip, SarControlTypeEnum.TURNON); SarControlParamDTO param = new SarControlParamDTO(ip, SarControlTypeEnum.TURNON);
udpSendContext.execute(param); udpSendContext.execute(param);
} }
@Override @Override
public void endAll(String ip) { public void endAll(String ip) {
// 用领域缓存判断连接和状态 if (!redisUtil.hHasKey(CacheKey.SAR_CONNECTED, ip) || !redisUtil.hasKey(CacheKey.getSarConnect(ip))) {
boolean connected = sarCache.isConnected(ip);
JmSarStatusDTO status = sarCache.getLatestStatus(ip);
if (!connected || status == null) {
throw new ServiceException("请先加电并连接sar"); throw new ServiceException("请先加电并连接sar");
} }
SarControlParamDTO param = new SarControlParamDTO(ip, SarControlTypeEnum.ENDALL); SarControlParamDTO param = new SarControlParamDTO(ip, SarControlTypeEnum.ENDALL);
udpSendContext.execute(param); udpSendContext.execute(param);
} }
@Override @Override
public JmSarStatusDTO getLatestStatus(String ip) { public JmSarStatusDTO getLatestStatus(String ip) {
return sarCache.getLatestStatus(ip); String connectKey = CacheKey.getSarConnect(ip);
// 从缓存取载荷状态信息
return (JmSarStatusDTO) redisUtil.get(connectKey);
} }
} }

View File

@ -1,9 +1,9 @@
package com.zhangy.skyeye.sar.service.impl; package com.zhangy.skyeye.sar.service.impl;
import com.zhangy.skyeye.cache.image.ImageCache;
import com.zhangy.skyeye.common.extend.util.MathUtil; import com.zhangy.skyeye.common.extend.util.MathUtil;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO; import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.jm.dto.JmImageRotateDTO; import com.zhangy.skyeye.jm.dto.JmImageRotateDTO;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO; import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.jm.entity.JmImage; import com.zhangy.skyeye.jm.entity.JmImage;
@ -16,6 +16,7 @@ import com.zhangy.skyeye.publics.utils.OpenCVUtil;
import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO; import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
import com.zhangy.skyeye.sar.service.ISarImageService; import com.zhangy.skyeye.sar.service.ISarImageService;
import com.zhangy.skyeye.sar.service.SarWsAsyncService; import com.zhangy.skyeye.sar.service.SarWsAsyncService;
import com.zhangy.skyeye.sar.util.RadarDisplayOptions;
import com.zhangy.skyeye.sar.util.SarImageToneAdjuster; import com.zhangy.skyeye.sar.util.SarImageToneAdjuster;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -45,7 +46,7 @@ public class SarImageServiceImpl implements ISarImageService {
private SarWsAsyncService sarWsAsyncService; private SarWsAsyncService sarWsAsyncService;
@Autowired @Autowired
private ImageCache imageCache; private RedisUtil redisUtil;
// 图片最大宽度前端说和电脑有关4096保险点一般是4096 8192 16384 // 图片最大宽度前端说和电脑有关4096保险点一般是4096 8192 16384
@Value("${skyeye.sar.image.max:4096}") @Value("${skyeye.sar.image.max:4096}")
@ -55,6 +56,8 @@ public class SarImageServiceImpl implements ISarImageService {
private final String CACHE_FIELD_START_FRAME_NO = "startFrameNo"; private final String CACHE_FIELD_START_FRAME_NO = "startFrameNo";
// 当前帧号 // 当前帧号
private final String CACHE_FIELD_CURR_FRAME_NO = "currFrameNo"; private final String CACHE_FIELD_CURR_FRAME_NO = "currFrameNo";
// 缓存超时
private final long CACHE_EXPIRE_SECOND = 24 * 3600;
/** /**
* 获取基准图像信息 * 获取基准图像信息
@ -64,34 +67,32 @@ public class SarImageServiceImpl implements ISarImageService {
* @param frameNo 当前帧号 * @param frameNo 当前帧号
* @return 返回非空的图像信息其字段 imageNo 一定有值 * @return 返回非空的图像信息其字段 imageNo 一定有值
*/ */
private JmImage getBaseImage(Long airlineId, int singleWidth, int frameNo) { private JmImage getBaseImage(Long airlineId, int singleWidth, int frameNo) {IMG_MAX_WITH=1;
List<JmImage> imageList = imageService.selectLowByAirline(airlineId); List<JmImage> imageList = imageService.selectLowByAirline(airlineId);
String cachePrefix = "jmImgJoin-" + airlineId; String cacheKey = "jmImgJoin-" + airlineId;
JmImage base; JmImage base = null;
// 情况1航线第一张图 // 情况1航线第一张图
if (ObjectUtil.isEmpty(imageList)) { if (ObjectUtil.isEmpty(imageList)) {
base = new JmImage(); base = new JmImage();
base.setImageNo(1); base.setImageNo(1);
// 存起始帧号 redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
imageCache.put(cachePrefix + ":" + CACHE_FIELD_START_FRAME_NO, frameNo);
return base; return base;
} }
// 情况2如果最后一张还能拼图则直接返回继续拼 // 情况2如果最后一张还能拼图则直接返回继续拼
JmImage last = imageList.get(imageList.size() - 1); JmImage last = imageList.get(imageList.size() - 1);
Integer startFrameNo = imageCache.get(cachePrefix + ":" + CACHE_FIELD_START_FRAME_NO, Integer.class); Integer startFrameNo = (Integer) redisUtil.hget(cacheKey, CACHE_FIELD_START_FRAME_NO);
int currWidth = startFrameNo == null ? 0 : singleWidth * (frameNo - startFrameNo + 1); int currWidth = startFrameNo == null ? 0 : singleWidth * (frameNo - startFrameNo + 1); // 图宽当前图+基准图
int surplusNum = (IMG_MAX_WITH - currWidth) / singleWidth; // 还可以拼图片数 int surplusNum = (IMG_MAX_WITH - currWidth) / singleWidth; // 还可以拼图片数
Integer baseNo = imageCache.get(cachePrefix + ":" + CACHE_FIELD_CURR_FRAME_NO, Integer.class); Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineId, CACHE_FIELD_CURR_FRAME_NO);
if (startFrameNo == null || currWidth < IMG_MAX_WITH || if (startFrameNo == null || currWidth < IMG_MAX_WITH ||
baseNo == null || (frameNo - baseNo + 1 <= surplusNum)) { baseNo == null || (frameNo - baseNo + 1 <= surplusNum)) { // 当前图+填充 不能超过允许拼接数
log.info("当前宽度:{} < {} 可以继续拼接", currWidth, IMG_MAX_WITH); log.info("当前宽度:" + currWidth + " < " + IMG_MAX_WITH + " 可以继续拼接");
return last; return last;
} }
// 情况3已经拼接到最大数量或者当前图+填充数量超过允许拼接数量创建新图像文件 // 情况3已经拼接到最大数量或者当前图+填充数量超过允许拼接数量创建新图像文件
log.info("当前宽度:{} > {} 重新拼接,当前帧号{}作为首帧", currWidth, IMG_MAX_WITH, frameNo); log.info("当前宽度:" + currWidth + " > " + IMG_MAX_WITH + " 重新拼接,当前帧号" + frameNo + "作为首帧");
base = new JmImage(); base = new JmImage();
imageCache.put(cachePrefix + ":" + CACHE_FIELD_START_FRAME_NO, frameNo); redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
base.setImageNo(last.getImageNo() + 1); base.setImageNo(last.getImageNo() + 1);
return base; return base;
} }
@ -106,12 +107,47 @@ public class SarImageServiceImpl implements ISarImageService {
} }
// 使用前一张图的右侧坐标作为后一张图的左侧前提是没丢图 // 使用前一张图的右侧坐标作为后一张图的左侧前提是没丢图
if (!isFirst && !lostImage) { if (!isFirst && !lostImage) {
// 注释部分保持原样 /*imageFrame.setLon1(before[0]);
imageFrame.setLat1(before[1]);
imageFrame.setLon4(before[2]);
imageFrame.setLat4(before[3]);*/
} }
before[0] = imageFrame.getLon5(); before[0] = imageFrame.getLon5();
before[1] = imageFrame.getLat5(); before[1] = imageFrame.getLat5();
before[2] = imageFrame.getLon8(); before[2] = imageFrame.getLon8();
before[3] = imageFrame.getLat8(); before[3] = imageFrame.getLat8();
/*switch (rotateDTO.getType()) {
case 0:
case 1:
// 使用前一张图的右侧坐标作为后一张图的左侧前提是没丢图
if (!isFirst && !lostImage) {
imageFrame.setLon1(before[0]);
imageFrame.setLat1(before[1]);
imageFrame.setLon4(before[2]);
imageFrame.setLat4(before[3]);
} else if (before == null) {
before = new Double[4];
currAirline.setBeforeRight(before);
}
before[0] = imageFrame.getLon5();
before[1] = imageFrame.getLat5();
before[2] = imageFrame.getLon8();
before[3] = imageFrame.getLat8();
break;
case 2:
case 3:
if (!isFirst && !lostImage) {
imageFrame.setLon5(before[0]);
imageFrame.setLat5(before[1]);
imageFrame.setLon8(before[2]);
imageFrame.setLat8(before[3]);
}
before[0] = imageFrame.getLon1();
before[1] = imageFrame.getLat1();
before[2] = imageFrame.getLon4();
before[3] = imageFrame.getLat4();
break;
} */
} }
/** /**
@ -151,7 +187,7 @@ public class SarImageServiceImpl implements ISarImageService {
} }
// 3.保存图像png用航线ID+序号命名 // 3.保存图像png用航线ID+序号命名
JmImage base = getBaseImage(airlineExecId, currImage.width(), frameNo); JmImage base = getBaseImage(airlineExecId, currImage.width(), imageFrame.getFrameNo());
String imageName = airlineExecId + "-" + base.getImageNo() +".png"; String imageName = airlineExecId + "-" + base.getImageNo() +".png";
String[] imagePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, imageName); String[] imagePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, imageName);
String currPath = imagePath[0]; String currPath = imagePath[0];
@ -159,7 +195,7 @@ public class SarImageServiceImpl implements ISarImageService {
System.out.println("帧:" + frameNo); System.out.println("帧:" + frameNo);
// 4.保存基准图同步用于下次拼接 // 4.保存基准图同步用于下次拼接
Integer baseNo = imageCache.get("jmImgJoin-" + airlineExecId + ":" + CACHE_FIELD_CURR_FRAME_NO, Integer.class); Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO);
boolean lostImage = baseNo != null && (frameNo - baseNo > 1); // 判断是否丢图 boolean lostImage = baseNo != null && (frameNo - baseNo > 1); // 判断是否丢图
String basePath = sysFileTypeService.getAbsolutePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, String basePath = sysFileTypeService.getAbsolutePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId,
airlineExecId + "-" + base.getImageNo() +"-base.png"); airlineExecId + "-" + base.getImageNo() +"-base.png");
@ -168,11 +204,11 @@ public class SarImageServiceImpl implements ISarImageService {
return null; return null;
} }
if (lostImage) { if (lostImage) {
log.warn("丢图{}张!当前帧{},前帧{}", (frameNo - baseNo), frameNo, baseNo); log.warn("丢图"+(frameNo - baseNo)+"张!当前帧" + frameNo + ",前帧" + baseNo);
} }
//modCoord(imageFrame, lostImage, currAirline); //modCoord(imageFrame, lostImage, currAirline);
imageCache.put("jmImgJoin-" + airlineExecId + ":" + CACHE_FIELD_CURR_FRAME_NO, frameNo); redisUtil.hset("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);// 更新帧号
// ### 亮度调整用于可靠udp版本图像固定使用系数0.5 // ### 亮度调整用于可靠udp版本图像固定使用系数0.5
// 拆分多张图片去掉自适应调整 // 拆分多张图片去掉自适应调整
if (IMG_MAX_WITH > 20000) { if (IMG_MAX_WITH > 20000) {
@ -188,8 +224,8 @@ public class SarImageServiceImpl implements ISarImageService {
// 6.更新基准图坐标和帧号 // 6.更新基准图坐标和帧号
JmImage imageInfo = saveImage(uav, airlineExecId, base, imagePath, imageFrame, frameNo, lostImage); JmImage imageInfo = saveImage(uav, airlineExecId, base, imagePath, imageFrame, frameNo, lostImage);
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
log.info("生成{}位雷达回传图像:帧序号{}{},耗时{}秒", log.info("生成" + imageFrame.getImageBitDeep()+"位雷达回传图像:帧序号" + frameNo + "," +
imageFrame.getImageBitDeep(), frameNo, imageInfo.getRelativePath(), (end - start) / 1000); imageInfo.getRelativePath() + ",耗时" + (end - start)/1000 + "");
return imageInfo; return imageInfo;
} }

View File

@ -49,11 +49,11 @@ spring:
date-format: yyyy-MM-dd'T'HH:mm:ss'Z' date-format: yyyy-MM-dd'T'HH:mm:ss'Z'
#redis 配置 #redis 配置
#redis: redis:
#database: 0 database: 0
#host: 127.0.0.1 host: 127.0.0.1
#port: 6379 port: 6379
#password: 'P@ssw0rd' password: 'P@ssw0rd'
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapping/**/*Mapping.xml mapper-locations: classpath*:mapping/**/*Mapping.xml

View File

@ -22,10 +22,10 @@
</dependency> </dependency>
<!-- redis --> <!-- redis -->
<!-- <dependency>--> <dependency>
<!-- <groupId>org.springframework.boot</groupId>--> <groupId>org.springframework.boot</groupId>
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>--> <artifactId>spring-boot-starter-data-redis</artifactId>
<!-- </dependency>--> </dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,70 @@
package com.zhangy.skyeye.redis.config;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
/**
* Redis 配置
*/
@EnableCaching
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class DTRedisTemplateConfiguration {
/**
* value 序列化
* @return RedisSerializer
*/
@Bean
@ConditionalOnMissingBean(RedisSerializer.class)
public RedisSerializer<Object> redisSerializer() {
return new JdkSerializationRedisSerializer();
}
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(RedisTemplate.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer<Object> redisSerializer) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
RedisKeySerializer redisKeySerializer = new RedisKeySerializer();
// key 序列化
redisTemplate.setKeySerializer(redisKeySerializer);
redisTemplate.setHashKeySerializer(redisKeySerializer);
// value 序列化
redisTemplate.setValueSerializer(redisSerializer);
redisTemplate.setHashValueSerializer(redisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1));
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
@Bean(name = "redisUtil")
@ConditionalOnBean(RedisTemplate.class)
public RedisUtil redisUtils(RedisTemplate<String, Object> redisTemplate) {
return new RedisUtil(redisTemplate);
}
}

View File

@ -1,55 +1,55 @@
//package com.zhangy.skyeye.redis.config; package com.zhangy.skyeye.redis.config;
//
//import org.springframework.cache.interceptor.SimpleKey; import org.springframework.cache.interceptor.SimpleKey;
//import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
//import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
//import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer;
//import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
//
//import java.nio.charset.Charset; import java.nio.charset.Charset;
//import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
//import java.util.Objects; import java.util.Objects;
//
///** /**
// * 将redis key序列化为字符串 * 将redis key序列化为字符串
// * spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题 * spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
// */ */
//public class RedisKeySerializer implements RedisSerializer<Object> { public class RedisKeySerializer implements RedisSerializer<Object> {
// private final Charset charset; private final Charset charset;
// private final ConversionService converter; private final ConversionService converter;
//
// public RedisKeySerializer() { public RedisKeySerializer() {
// this(StandardCharsets.UTF_8); this(StandardCharsets.UTF_8);
// } }
//
// public RedisKeySerializer(Charset charset) { public RedisKeySerializer(Charset charset) {
// Objects.requireNonNull(charset, "Charset must not be null"); Objects.requireNonNull(charset, "Charset must not be null");
// this.charset = charset; this.charset = charset;
// this.converter = DefaultConversionService.getSharedInstance(); this.converter = DefaultConversionService.getSharedInstance();
// } }
//
// @Override @Override
// public Object deserialize(byte[] bytes) { public Object deserialize(byte[] bytes) {
// // redis keys 会用到反序列化 // redis keys 会用到反序列化
// if (bytes == null) { if (bytes == null) {
// return null; return null;
// } }
// return new String(bytes, charset); return new String(bytes, charset);
// } }
//
// @Override @Override
// @Nullable @Nullable
// public byte[] serialize(Object object) { public byte[] serialize(Object object) {
// Objects.requireNonNull(object, "redis key is null"); Objects.requireNonNull(object, "redis key is null");
// String key; String key;
// if (object instanceof SimpleKey) { if (object instanceof SimpleKey) {
// key = ""; key = "";
// } else if (object instanceof String) { } else if (object instanceof String) {
// key = (String) object; key = (String) object;
// } else { } else {
// key = converter.convert(object, String.class); key = converter.convert(object, String.class);
// } }
// return key.getBytes(this.charset); return key.getBytes(this.charset);
// } }
//
//} }

View File

@ -1 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zhangy.skyeye.redis.config.DTRedisTemplateConfiguration