Compare commits
21 Commits
main
...
dev_202601
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b46d40b7a3 | ||
|
|
1da7eff80e | ||
|
|
bff31311de | ||
|
|
27dfb2d878 | ||
|
|
29a81bea9b | ||
|
|
90d8311840 | ||
|
|
a3b29f6b90 | ||
|
|
d3ab2f2278 | ||
|
|
8d84e52e44 | ||
|
|
f775c6f173 | ||
|
|
5946d692c5 | ||
|
|
f2f829a2f8 | ||
|
|
6ce25eda79 | ||
|
|
75aeed56be | ||
|
|
c1575d47dd | ||
|
|
f34844f110 | ||
|
|
77e6de4c8b | ||
|
|
af1cf9c662 | ||
|
|
e82b63c8a8 | ||
|
|
b0e3347d6b | ||
|
|
b2f61137a9 |
@ -12,6 +12,14 @@
|
||||
<artifactId>skyeye-service-manager</artifactId>
|
||||
|
||||
<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>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.zhangy.skyeye;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@ -9,8 +10,10 @@ import java.io.File;
|
||||
|
||||
@EnableScheduling
|
||||
@MapperScan("com.zhangy.skyeye.**.mapper")
|
||||
@SpringBootApplication(scanBasePackages = "com.zhangy.**")
|
||||
@SpringBootApplication(scanBasePackages = "com.zhangy")
|
||||
@Slf4j
|
||||
public class SEApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
String projectRoot = System.getProperty("user.dir");
|
||||
// String libPath = projectRoot + "/library/logisen/GMTI";
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
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 ==========");
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.zhangy.skyeye.device.service.impl;
|
||||
|
||||
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.QueryDTO;
|
||||
import com.zhangy.skyeye.common.extend.enums.EnumUtil;
|
||||
@ -15,68 +16,67 @@ import com.zhangy.skyeye.jm.dto.JmJobStatusDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
|
||||
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.redis.utils.RedisUtil;
|
||||
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.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PayloadServiceImpl implements IPayloadService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private PayloadMapper payloadMapper;
|
||||
|
||||
@Autowired
|
||||
private JmJobStatusService sarJobStatusService;
|
||||
|
||||
@Autowired
|
||||
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
|
||||
public IPage<SkyeyePayload> selectPage(PageDTO 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
|
||||
public List<SkyeyePayload> getSar(Long... sarId) {
|
||||
if (ObjectUtil.isNotEmpty(sarId)) {
|
||||
return redisTemplate.opsForHash().multiGet(CacheKey.DEVICE_SAR, Arrays.asList(sarId));
|
||||
return Arrays.stream(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
|
||||
public SkyeyePayload getOne(Long payloadId) {
|
||||
SkyeyePayload p = (SkyeyePayload) redisTemplate.opsForHash().get(CacheKey.DEVICE_SAR, payloadId.toString());
|
||||
// 若有其它种类载荷,则判空,继续查询
|
||||
return p;
|
||||
return sarCache.getOne(payloadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,10 +84,6 @@ public class PayloadServiceImpl implements IPayloadService {
|
||||
return payloadMapper.selectList(param);
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public List<SkyeyePayload> getEnableList() {
|
||||
// 从缓存查询所有sar
|
||||
@ -98,12 +94,14 @@ public class PayloadServiceImpl implements IPayloadService {
|
||||
// 从内存查询所有任务中的雷达
|
||||
Collection<JmJobStatusDTO> statusVos = sarJobStatusService.getAll();
|
||||
Set<Long> jobSarSet = statusVos.stream()
|
||||
.flatMap(job -> job.getUavMap().values().stream().filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER))
|
||||
.map(JmUavStatusDTO::getSarId)
|
||||
.flatMap(job -> job.getUavMap().values().stream()
|
||||
.filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER))
|
||||
.map(JmUavStatusDTO::getSarId) // 正确取 SAR ID
|
||||
.collect(Collectors.toSet());
|
||||
// 筛选出未在任务中且已连接的雷达
|
||||
return sarList.stream()
|
||||
.filter(sar -> !jobSarSet.contains(sar.getId()) && redisUtil.hasKey(CacheKey.getSarConnect(sar.getIp())))
|
||||
.filter(sar -> !jobSarSet.contains(sar.getId())
|
||||
&& sarCache.getLatestStatus(sar.getIp()) != null)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@ -128,7 +126,7 @@ public class PayloadServiceImpl implements IPayloadService {
|
||||
} else {
|
||||
payloadMapper.insert(e);
|
||||
}
|
||||
cacheSar(e);
|
||||
sarCache.cacheSar(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
@ -142,9 +140,7 @@ public class PayloadServiceImpl implements IPayloadService {
|
||||
payloadMapper.update(e);
|
||||
// 若是sar则更新缓存
|
||||
List<SkyeyePayload> list = selectById(PayloadTypeEnum.SAR, e.getId());
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
cacheSar(list.get(0));
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(list)) sarCache.cacheSar(list.get(0));
|
||||
return e;
|
||||
}
|
||||
|
||||
@ -171,38 +167,42 @@ public class PayloadServiceImpl implements IPayloadService {
|
||||
@Transactional
|
||||
@Override
|
||||
public void delete(Long... id) {
|
||||
// 查询任务中的sar
|
||||
List<Long> inJobSarIds = sarJobStatusService.getAll()
|
||||
.stream()
|
||||
.flatMap(vo -> vo.getUavMap().values().stream())
|
||||
.filter(vo -> vo.getSarStatus() != ExecStatusEnum.OVER)
|
||||
.map(JmUavStatusDTO::getSarId)
|
||||
.collect(Collectors.toList());
|
||||
// 任务中的不可删除
|
||||
|
||||
for (Long sarId : id) {
|
||||
if (inJobSarIds.contains(sarId)) {
|
||||
throw ServiceException.noLog("[" + getSar(sarId).get(0).getName() + "]在执行任务,不可删除");
|
||||
List<SkyeyePayload> sar = getSar(sarId);
|
||||
if (!sar.isEmpty()) {
|
||||
throw ServiceException.noLog("[" + sar.get(0).getName() + "]在执行任务,不可删除");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
payloadMapper.deleteLogic(id);
|
||||
redisTemplate.opsForHash().delete(CacheKey.DEVICE_SAR, id);
|
||||
Arrays.stream(id).forEach(sarCache::evictSar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JmSarStatusDTO getLastStatus(String payloadIp) {
|
||||
String statusKey = CacheKey.getSarConnect(payloadIp);
|
||||
if (!redisUtil.hasKey(statusKey)) {
|
||||
JmSarStatusDTO status = sarCache.getLatestStatus(payloadIp);
|
||||
if (status == null) {
|
||||
try {
|
||||
sarControlService.connect(payloadIp);
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
status = sarCache.getLatestStatus(payloadIp);
|
||||
} catch (InterruptedException ex) {
|
||||
throw new ServiceException("获取雷达状态失败:" + ex.getMessage());
|
||||
}
|
||||
if (!redisUtil.hasKey(statusKey)) {
|
||||
if (status == null) {
|
||||
throw ServiceException.noLog("无法连接到SAR载荷[" + payloadIp + "]!");
|
||||
}
|
||||
}
|
||||
return (JmSarStatusDTO) redisUtil.get(statusKey);
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.zhangy.skyeye.device.service.impl;
|
||||
|
||||
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.exception.ServiceException;
|
||||
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
|
||||
@ -9,77 +10,77 @@ import com.zhangy.skyeye.device.mapper.UavMapper;
|
||||
import com.zhangy.skyeye.device.service.IUavService;
|
||||
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
|
||||
import com.zhangy.skyeye.jm.service.JmJobStatusService;
|
||||
import com.zhangy.skyeye.publics.consts.CacheKey;
|
||||
import com.zhangy.skyeye.publics.consts.ExecStatusEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UavServiceImpl implements IUavService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private UavMapper uavMapper;
|
||||
|
||||
@Autowired
|
||||
private JmJobStatusService sarJobStatusService;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
private final UavCache uavCache;
|
||||
|
||||
@PostConstruct
|
||||
public void cacheAll() {
|
||||
selectList(null).forEach(uavCache::put);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<SkyeyeUav> selectPage(PageDTO param) {
|
||||
return uavMapper.selectPage(param);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SkyeyeUav> selectList(PageDTO param) {
|
||||
return uavMapper.selectList(param);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SkyeyeUav> selectById(Long... 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
|
||||
public List<SkyeyeUav> get(Long... id) {
|
||||
if (ObjectUtil.isNotEmpty(id)) {
|
||||
return redisTemplate.opsForHash().multiGet(CacheKey.DEVICE_UAV, Arrays.asList(id));
|
||||
return Arrays.stream(id)
|
||||
.map(uavCache::get)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return redisTemplate.opsForHash().values(CacheKey.DEVICE_UAV);
|
||||
// 全量获取
|
||||
return uavCache.getAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkyeyeUav getOne(Long id) {
|
||||
List<SkyeyeUav> list = get(id);
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
return list.get(0);
|
||||
SkyeyeUav uav = uavCache.get(id);
|
||||
if (uav != null) {
|
||||
return uav;
|
||||
}
|
||||
list = uavMapper.selectById(id);
|
||||
|
||||
// 缓存 miss,从 DB 查并回填
|
||||
List<SkyeyeUav> list = uavMapper.selectById(id);
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
throw new ServiceException("无效的无人机ID:" + id);
|
||||
}
|
||||
SkyeyeUav uav = list.get(0);
|
||||
cache(uav);
|
||||
uav = list.get(0);
|
||||
uavCache.put(uav);
|
||||
return uav;
|
||||
}
|
||||
|
||||
@ -92,8 +93,9 @@ public class UavServiceImpl implements IUavService {
|
||||
}
|
||||
// 从内存查询所有任务中的无人机(暂时用SAR状态判断)
|
||||
Set<Long> jobUavSet = sarJobStatusService.getAll().stream()
|
||||
.flatMap(job -> job.getUavMap().values().stream().filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER))
|
||||
.map(JmUavStatusDTO::getSarId)
|
||||
.flatMap(job -> job.getUavMap().values().stream()
|
||||
.filter(uav -> uav.getSarStatus() != ExecStatusEnum.OVER))
|
||||
.map(JmUavStatusDTO::getUavId) // 注意:这里是 uavId,不是 sarId
|
||||
.collect(Collectors.toSet());
|
||||
// 筛选出未在任务中的无人机
|
||||
return uavList.stream()
|
||||
@ -110,11 +112,11 @@ public class UavServiceImpl implements IUavService {
|
||||
uavMapper.update(e);
|
||||
} else {
|
||||
uavMapper.insert(e);
|
||||
cache(e);
|
||||
}
|
||||
uavCache.put(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SkyeyeUav update(SkyeyeUav e) {
|
||||
@ -123,13 +125,14 @@ public class UavServiceImpl implements IUavService {
|
||||
// 若是sar则更新缓存
|
||||
List<SkyeyeUav> list = selectById(e.getId());
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
cache(list.get(0));
|
||||
uavCache.put(list.get(0));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前校验
|
||||
*
|
||||
* @param e
|
||||
* @return 返回已逻辑删除的载荷
|
||||
*/
|
||||
@ -144,16 +147,15 @@ public class UavServiceImpl implements IUavService {
|
||||
return local;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void delete(Long... ids) {
|
||||
// 查询任务中的设备
|
||||
List<Long> inJobUavIds = sarJobStatusService.getAll()
|
||||
.stream()
|
||||
.flatMap(vo -> vo.getUavMap().values().stream())
|
||||
.filter(vo -> vo.getSarStatus() != ExecStatusEnum.OVER)
|
||||
.map(vo -> vo.getUavId())
|
||||
.map(JmUavStatusDTO::getUavId)
|
||||
.collect(Collectors.toList());
|
||||
// 任务中的不可删除
|
||||
for (Long id : ids) {
|
||||
@ -162,6 +164,6 @@ public class UavServiceImpl implements IUavService {
|
||||
}
|
||||
}
|
||||
uavMapper.deleteLogic(ids);
|
||||
redisTemplate.opsForHash().delete(CacheKey.DEVICE_UAV, ids);
|
||||
Arrays.stream(ids).forEach(uavCache::evict);
|
||||
}
|
||||
}
|
||||
@ -11,20 +11,14 @@ import com.zhangy.skyeye.jm.dto.JmJobImageDTO;
|
||||
import com.zhangy.skyeye.jm.entity.JmImage;
|
||||
import com.zhangy.skyeye.jm.service.JmImageService;
|
||||
import com.zhangy.skyeye.publics.consts.FileTypeEnum;
|
||||
import com.zhangy.skyeye.publics.utils.LocalLockUtil;
|
||||
import com.zhangy.skyeye.publics.utils.OpenCVUtil;
|
||||
import org.opencv.core.Core;
|
||||
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.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.io.File;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.zhangy.skyeye.publics.consts.CacheKey.SAR_IMAGE_UPLOAD_LOCK;
|
||||
@ -33,12 +27,12 @@ import static com.zhangy.skyeye.publics.consts.CacheKey.SAR_IMAGE_UPLOAD_LOCK;
|
||||
@RestController
|
||||
@RequestMapping("/sar/image")
|
||||
public class JmImageController {
|
||||
|
||||
|
||||
@Autowired
|
||||
private JmImageService sarImageService;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
private LocalLockUtil localLockUtil;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
@ -47,16 +41,16 @@ public class JmImageController {
|
||||
public Object selectPage(@Valid @RequestBody JmImagePageDTO param) {
|
||||
return sarImageService.selectPage(param);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* 按任务查询低精度图像
|
||||
*/
|
||||
@GetMapping("/queryByJob")
|
||||
public Object selectList(@RequestParam Long jobId) {
|
||||
return sarImageService.selectByJob(FileTypeEnum.SAR_IMAGE_LOW, jobId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@ -80,31 +74,19 @@ public class JmImageController {
|
||||
@PostMapping("/addHigh")
|
||||
public Object addHighImage(@Valid @RequestBody JmJobImageDTO dto) {
|
||||
String lockKey = SAR_IMAGE_UPLOAD_LOCK;
|
||||
String requestId = UUID.randomUUID().toString(); // 唯一标识当前请求
|
||||
// 尝试 60 秒获取锁(比原来 1 分钟更灵活)
|
||||
boolean locked = localLockUtil.tryLock(lockKey, 60);
|
||||
if (!locked) {
|
||||
throw ServiceException.noLog("正在上传其它图像,请稍后重试!");
|
||||
}
|
||||
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);
|
||||
} finally {
|
||||
// 仅删除自己的锁(Lua脚本保证原子性)
|
||||
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
|
||||
);
|
||||
// 释放锁
|
||||
localLockUtil.unlock(lockKey);
|
||||
}
|
||||
return MessageUtils.message("sar.image.add_highres.success");
|
||||
return "操作完成";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,6 +120,7 @@ public class JmImageController {
|
||||
.map(e -> BeanUtil.copyProperties(e, JmImageKtyDTO.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static {
|
||||
//System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
OpenCVUtil.loadNativeDylib();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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.util.MessageUtils;
|
||||
import com.zhangy.skyeye.jm.dto.*;
|
||||
@ -14,7 +15,6 @@ import com.zhangy.skyeye.publics.consts.CacheKey;
|
||||
import com.zhangy.skyeye.publics.consts.ExecStatusEnum;
|
||||
import com.zhangy.skyeye.publics.consts.WebSocketKey;
|
||||
import com.zhangy.skyeye.publics.utils.CoordUtil;
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
import com.zhangy.skyeye.sar.listen.SarImageUdpProcessor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -46,7 +46,7 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
|
||||
private SarImageUdpProcessor imageProcessService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private SarCache sarCache;
|
||||
|
||||
// @Autowired
|
||||
// private ISmpSubscriptService subscriptService;
|
||||
@ -183,10 +183,9 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
|
||||
}
|
||||
for (JmJobPayload payload : newUav.getPayloadList()) {
|
||||
// 校验雷达连接状态
|
||||
if (!redisUtil.hasKey(CacheKey.getSarConnect(payload.getIp()))) {
|
||||
if (!sarCache.isConnected(payload.getIp())) {
|
||||
throw ServiceException.warnLog(MessageUtils.message("device.sar.offline", payload.getPayloadName()));
|
||||
}
|
||||
|
||||
if (runningPayloadIds.contains(payload.getPayloadId())) {
|
||||
throw ServiceException.warnLog(MessageUtils.message("sar.control.turnon.esarinexec"));
|
||||
}
|
||||
@ -295,11 +294,11 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
|
||||
}
|
||||
endAirline(jobId, jobVo, uavVo, airlineVo, wsVo, nextAirline == null);
|
||||
} else if (isBoot == 1 && aStatus == ExecStatusEnum.PROCESSING && overButSending) {
|
||||
// 前一航线的图片没传完,就开始了新航线
|
||||
endAirline(jobId, jobVo, uavVo, airlineVo, wsVo, nextAirline == null);
|
||||
if (nextAirline != null) {
|
||||
startAirline(nextAirline, wsVo);
|
||||
}
|
||||
// 前一航线的图片没传完,就开始了新航线
|
||||
endAirline(jobId, jobVo, uavVo, airlineVo, wsVo, nextAirline == null);
|
||||
if (nextAirline != null) {
|
||||
startAirline(nextAirline, wsVo);
|
||||
}
|
||||
} // else {
|
||||
// return;
|
||||
// }
|
||||
|
||||
@ -1,27 +1,22 @@
|
||||
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.service.IPayloadService;
|
||||
import com.zhangy.skyeye.jm.dto.JmJobStatusDTO;
|
||||
import com.zhangy.skyeye.publics.consts.CacheKey;
|
||||
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
|
||||
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.jm.service.JmJobStatusService;
|
||||
import com.zhangy.skyeye.sar.exception.SarConnectException;
|
||||
import com.zhangy.skyeye.sar.service.ISarControlService;
|
||||
// import com.zhangy.skyeye.smp.dto.SmpUavStatusWsDTO;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 任务管理定时任务
|
||||
@ -34,18 +29,6 @@ public class JmTaskScheduler {
|
||||
@Setter
|
||||
private boolean isDebug;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Autowired
|
||||
private JmJobStatusService jobStatusService;
|
||||
|
||||
@Autowired
|
||||
private SimpMessagingTemplate simpMessagingTemplate;
|
||||
|
||||
@Autowired
|
||||
RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private ISysLoginService loginService;
|
||||
|
||||
@ -56,7 +39,7 @@ public class JmTaskScheduler {
|
||||
private IPayloadService payloadService;
|
||||
|
||||
@Autowired
|
||||
private ISarControlService sarControlService;
|
||||
private SarCache sarCache; // ← 直接用领域缓存
|
||||
|
||||
/**
|
||||
* 每1秒向前端推送雷达状态信息,断开连接则所有数据置0返回
|
||||
@ -108,28 +91,31 @@ public class JmTaskScheduler {
|
||||
/**
|
||||
* 每10秒进行载荷连接判断:
|
||||
* 1. 如果有用户登录:
|
||||
* 1)未执行连接指令,进行连接
|
||||
* 2)已经执行连接指令,但没有获取到sar状态信息,进行重连
|
||||
* 1)未执行连接指令,进行连接
|
||||
* 2)已经执行连接指令,但没有获取到sar状态信息,进行重连
|
||||
* 2. 没有用户在线:
|
||||
* 无。因为断开连接会先结束任务,不能断开连接。等硬件断电
|
||||
* 无。因为断开连接会先结束任务,不能断开连接。等硬件断电
|
||||
*/
|
||||
@Scheduled(fixedRate = 10000)
|
||||
public void connectSar() {
|
||||
if (!loginService.hasLogged() || isDebug) { // 断开
|
||||
/*Map<Object, Object> map = redisUtil.hmget(CacheKey.SAR_CONNECTED);
|
||||
if (map != null && map.size() > 0) {
|
||||
map.keySet().forEach(ip -> controlInfoService.sendUdp((String) ip, SarControlTypeEnum.DISCONNECT));
|
||||
}*/
|
||||
} else { // 连接
|
||||
if (!loginService.hasLogged() || isDebug) {
|
||||
// 断开逻辑:如果需要,可遍历 permanentCache 或其他方式获取 ip 列表
|
||||
// 但当前注释掉了,保持原样或删除
|
||||
} else {
|
||||
List<SkyeyePayload> sarList = payloadService.getSar(null);
|
||||
sarList.forEach(sar -> {
|
||||
if (!redisUtil.hHasKey(CacheKey.SAR_CONNECTED, sar.getIp()) ||
|
||||
!redisUtil.hasKey(CacheKey.getSarConnect(sar.getIp()))) {
|
||||
String ip = sar.getIp();
|
||||
// 判断是否已连接:看 shortCache 里是否有该 ip 的状态(最近有状态包)
|
||||
JmSarStatusDTO status = sarCache.getLatestStatus(ip);
|
||||
// 判断是否已执行连接指令:看 permanentCache 里是否有该 ip 的连接标志
|
||||
boolean hasConnectedFlag = sarCache.isConnected(ip);
|
||||
// 如果缺少状态 或 缺少连接标志,则尝试连接
|
||||
if (!hasConnectedFlag || Objects.isNull(status)) {
|
||||
try {
|
||||
controlInfoService.sendUdp(sar.getIp(), SarControlTypeEnum.CONNECT);
|
||||
controlInfoService.sendUdp(ip, SarControlTypeEnum.CONNECT);
|
||||
log.info("尝试连接 SAR IP: {}", ip);
|
||||
} catch (SarConnectException ex) {
|
||||
// sar可能没通电,连接失败不做处理
|
||||
log.warn("连接雷达[" + sar.getIp() + "]失败:" + ex.getMessage());
|
||||
log.warn("连接雷达[{}]失败:{}", ip, ex.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
package com.zhangy.skyeye.publics.advice;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.exception.AuthException;
|
||||
import com.zhangy.skyeye.common.extend.util.JsonUtil;
|
||||
import com.zhangy.skyeye.common.utils.JwtUtil;
|
||||
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.dto.UserDTO;
|
||||
import com.zhangy.skyeye.publics.utils.SecurityUtil;
|
||||
import com.zhangy.skyeye.publics.utils.SpringContextUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
|
||||
@ -32,10 +34,9 @@ import java.util.Objects;
|
||||
* 6. 续期 Redis 中的 token 有效期
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthInterceptor implements AsyncHandlerInterceptor {
|
||||
|
||||
private final RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 放行 CORS 预检请求(OPTIONS),由后续 Filter 统一处理跨域头
|
||||
@ -57,9 +58,13 @@ public class AuthInterceptor implements AsyncHandlerInterceptor {
|
||||
if (method.isAnnotationPresent(IgnoreAuth.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 注入 UserTokenCache
|
||||
UserTokenCache userTokenCache = SpringContextUtil.getBean(UserTokenCache.class);
|
||||
|
||||
// 获取并校验 token
|
||||
String token = SecurityUtil.getToken(request);
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
if (StrUtil.isBlank(token)) {
|
||||
throw new AuthException("请求未授权:缺少 token");
|
||||
}
|
||||
// 解析 token 中的用户名信息
|
||||
@ -81,25 +86,24 @@ public class AuthInterceptor implements AsyncHandlerInterceptor {
|
||||
log.error("token 中的用户信息解析失败: {}", usernamePayload, e);
|
||||
throw new AuthException("token 格式错误,请重新登录");
|
||||
}
|
||||
// 获取该用户在 Redis 中存储的 token
|
||||
String redisKey = userDTO.getTokenKey();
|
||||
if (redisKey == null || redisKey.trim().isEmpty()) {
|
||||
// 获取该用户在 CaffeineCache 中存储的 token
|
||||
String tokenKey = userDTO.getTokenKey();
|
||||
if (StrUtil.isBlank(tokenKey)) {
|
||||
throw new AuthException("用户信息异常,请重新登录");
|
||||
}
|
||||
Object storedToken = redisUtil.get(redisKey);
|
||||
// Redis 中不存在 → 登录已失效
|
||||
// CaffeineCache 中的 token 与当前请求不一致 → 多设备登录,被踢
|
||||
String storedToken = userTokenCache.getToken(tokenKey);
|
||||
if (storedToken == null) {
|
||||
// 显式处理 null,即 token 已失效或被移除
|
||||
throw new AuthException("登录状态已失效,请重新登录");
|
||||
}
|
||||
// Redis 中的 token 与当前请求不一致 → 多设备登录,被踢
|
||||
if (!Objects.equals(token, storedToken.toString())) {
|
||||
log.warn("检测到多端登录冲突,用户: {}, 当前token: {}, redisToken: {}",
|
||||
usernamePayload, token.substring(0, 10) + "...",
|
||||
storedToken.toString().substring(0, 10) + "...");
|
||||
if (!Objects.equals(token, storedToken)) {
|
||||
log.warn("检测到多端登录冲突,用户: {}, 当前token: {}, storedToken: {}",
|
||||
usernamePayload, token, storedToken);
|
||||
throw new AuthException("该账号已在其他设备登录,请重新登录");
|
||||
}
|
||||
// 校验通过 → 续期 Redis token 有效时间
|
||||
redisUtil.expire(redisKey, JwtUtil.EXPIRE_TIME / 1000);
|
||||
// 续期:Caffeine 的 expireAfterWrite 是从最后写入开始算,所以 put 一下即可续期
|
||||
userTokenCache.storeToken(tokenKey, storedToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,112 +1,58 @@
|
||||
package com.zhangy.skyeye.publics.consts;
|
||||
|
||||
// import com.zhangy.skyeye.smp.dto.SmpWayPointResDTO;
|
||||
|
||||
public class CacheKey {
|
||||
|
||||
/*==========================================
|
||||
publics 模块用 skyeye: 前缀
|
||||
==========================================*/
|
||||
|
||||
/**
|
||||
* 用户token,30分钟
|
||||
*/
|
||||
/*==================== publics 模块 ====================*/
|
||||
// 用户token,30分钟
|
||||
private static final String USER_TOEKN = "skyeye:user:token:%s@%s";
|
||||
|
||||
/**
|
||||
* 获取token key
|
||||
*/
|
||||
public static String getToekn(Long userId, String userName) {
|
||||
// 获取token key
|
||||
public static String getToken(Long userId, String userName) {
|
||||
return String.format(CacheKey.USER_TOEKN, userId, userName);
|
||||
}
|
||||
|
||||
/*==========================================
|
||||
分布式锁用 skyeye:lock: 前缀
|
||||
==========================================*/
|
||||
|
||||
/**
|
||||
* 上传sar图像锁
|
||||
*/
|
||||
/*==================== 分布式锁 ====================*/
|
||||
// 上传sar图像锁
|
||||
public static final String SAR_IMAGE_UPLOAD_LOCK = "skyeye:lock:image";
|
||||
|
||||
/*==========================================
|
||||
sar 模块用 skyeye:sar: 前缀
|
||||
==========================================*/
|
||||
|
||||
/**
|
||||
* 控制回包,1秒
|
||||
*/
|
||||
/*==================== SAR 模块 ====================*/
|
||||
// 控制回包,1秒
|
||||
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";
|
||||
|
||||
/**
|
||||
* 获取控制回包key:加ip
|
||||
*/
|
||||
// 获取控制回包key:加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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态key:加ip
|
||||
*/
|
||||
public static String getSarConnect(String ip) {
|
||||
return SAR_STATUS + ip;
|
||||
}
|
||||
|
||||
/*==========================================
|
||||
device 模块用 skyeye:device: 前缀
|
||||
==========================================*/
|
||||
|
||||
/**
|
||||
* sar载荷,永久
|
||||
*/
|
||||
/*==================== Device 模块 ====================*/
|
||||
// sar载荷,永久
|
||||
public static final String DEVICE_SAR = "skyeye:device:sar";
|
||||
|
||||
/**
|
||||
* 无人机,永久
|
||||
*/
|
||||
// 无人机,永久
|
||||
public static final String DEVICE_UAV = "skyeye:device:uav";
|
||||
|
||||
/*==========================================
|
||||
smp 模块用 skyeye:smp: 前缀
|
||||
==========================================*/
|
||||
|
||||
/**
|
||||
* 运动规划响应,1秒
|
||||
*/
|
||||
//public static final String SMP_WAYPOINT_RES = "skyeye:smp:waypoint:";
|
||||
|
||||
/**
|
||||
* 飞行控制响应,1秒
|
||||
*/
|
||||
//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:";
|
||||
/*==================== SMP 模块 ====================*/
|
||||
// 运动规划响应,1秒
|
||||
public static final String SMP_WAYPOINT_RES = "skyeye:smp:waypoint:";
|
||||
// 飞行控制响应,1秒
|
||||
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:";
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.zhangy.skyeye.publics.controller;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
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.OperationLog;
|
||||
import com.zhangy.skyeye.common.extend.dto.PageDTO;
|
||||
@ -10,7 +11,6 @@ import com.zhangy.skyeye.common.extend.util.MessageUtils;
|
||||
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
|
||||
import com.zhangy.skyeye.common.pojo.result.Result;
|
||||
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.dto.RegisterDTO;
|
||||
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.service.ISysUserService;
|
||||
import com.zhangy.skyeye.publics.utils.SecurityUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@ -30,125 +30,124 @@ import java.util.List;
|
||||
|
||||
@Validated
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@RequestMapping("/user")
|
||||
public class SysUserController {
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private final UserTokenCache userTokenCache;
|
||||
|
||||
/** token失效时间(秒) */
|
||||
private final int TOKEN_EXPIRE = 60 * 60 * 24;
|
||||
/**
|
||||
* 登录接口
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@IgnoreAuth
|
||||
@OperationLog(value = "登录", type = SysLog.TYPE_USER)
|
||||
@PostMapping("/login")
|
||||
public Result login(@RequestBody RegisterDTO registerDTO) {
|
||||
SysUser user = sysUserService.selectByAccount(registerDTO.getUsername(), registerDTO.getPassword().toLowerCase());
|
||||
if (user == null) {
|
||||
throw ServiceException.noLog(MessageUtils.message("user.login.error"));
|
||||
}
|
||||
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
|
||||
String payload = JSONUtil.toJsonStr(userDTO); // 保持原逻辑
|
||||
String accessToken = JwtUtil.sign(payload, user.getPassword());
|
||||
String tokenKey = userDTO.getTokenKey(); // skyeye:user:token:id@account
|
||||
userTokenCache.storeToken(tokenKey, accessToken);
|
||||
userDTO.setToken(accessToken);
|
||||
return Result.successData(MessageUtils.message("user.login.success"), userDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
* @return
|
||||
*/
|
||||
@IgnoreAuth
|
||||
@OperationLog(value = "登录", type = SysLog.TYPE_USER)
|
||||
@PostMapping("/login")
|
||||
public Result login(@RequestBody RegisterDTO registerDTO) {
|
||||
SysUser user = sysUserService.selectByAccount(registerDTO.getUsername(), registerDTO.getPassword().toLowerCase());
|
||||
if (user == null) {
|
||||
throw ServiceException.noLog(MessageUtils.message("user.login.error"));
|
||||
}
|
||||
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
|
||||
String key = JSONUtil.toJsonStr(userDTO);
|
||||
String accessToken = JwtUtil.sign(key, user.getPassword());
|
||||
redisUtil.set(userDTO.getTokenKey(), accessToken, TOKEN_EXPIRE);
|
||||
userDTO.setToken(accessToken);
|
||||
return Result.successData(MessageUtils.message("user.login.success"), userDTO);
|
||||
}
|
||||
/**
|
||||
* 修改密码
|
||||
*
|
||||
* @param dto
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/updpwd")
|
||||
public Object updatePwd(@Valid SysUserPwdDTO dto) {
|
||||
List<SysUser> list = sysUserService.selectById(dto.getUserId());
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
throw ServiceException.noLog(MessageUtils.message("user.updpwd.error"));
|
||||
}
|
||||
SysUser user = list.get(0);
|
||||
if (user.getPassword().equals(dto.getOldPwd())) {
|
||||
throw ServiceException.noLog(MessageUtils.message("user.updpwd.nochg"));
|
||||
}
|
||||
user.setPassword(dto.getNewPwd());
|
||||
sysUserService.update(user);
|
||||
return MessageUtils.message("user.updpwd.success");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param dto
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/updpwd")
|
||||
public Object updatePwd(@Valid SysUserPwdDTO dto) {
|
||||
List<SysUser> list = sysUserService.selectById(dto.getUserId());
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
throw ServiceException.noLog(MessageUtils.message("user.updpwd.error"));
|
||||
}
|
||||
SysUser user = list.get(0);
|
||||
if (user.getPassword().equals(dto.getOldPwd())) {
|
||||
throw ServiceException.noLog(MessageUtils.message("user.updpwd.nochg"));
|
||||
}
|
||||
user.setPassword(dto.getNewPwd());
|
||||
sysUserService.update(user);
|
||||
return MessageUtils.message("user.updpwd.success");
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出接口
|
||||
*/
|
||||
@IgnoreAuth
|
||||
@OperationLog(value = "退出登录", type = SysLog.TYPE_USER)
|
||||
@RequestMapping("/logout")
|
||||
@GetMapping
|
||||
public Result logout() {
|
||||
UserDTO userDTO = SecurityUtil.getUser();
|
||||
if (userDTO != null) {
|
||||
String key = CacheKey.getToekn(userDTO.getId(), userDTO.getAccount());
|
||||
redisUtil.del(key);
|
||||
}
|
||||
return Result.status(true);
|
||||
}
|
||||
/**
|
||||
* 退出接口
|
||||
*/
|
||||
@IgnoreAuth
|
||||
@OperationLog(value = "退出登录", type = SysLog.TYPE_USER)
|
||||
@RequestMapping("/logout")
|
||||
@GetMapping
|
||||
public Result logout() {
|
||||
UserDTO userDTO = SecurityUtil.getUser();
|
||||
if (userDTO != null) {
|
||||
String tokenKey = CacheKey.getToken(userDTO.getId(), userDTO.getAccount());
|
||||
userTokenCache.evictToken(tokenKey);
|
||||
}
|
||||
return Result.status(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@RequestMapping("/page")
|
||||
public Object selectPage(@Valid @RequestBody PageDTO param) {
|
||||
return sysUserService.selectPage(param);
|
||||
}
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@RequestMapping("/page")
|
||||
public Object selectPage(@Valid @RequestBody PageDTO param) {
|
||||
return sysUserService.selectPage(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表查询
|
||||
*/
|
||||
@RequestMapping("/list")
|
||||
public Object selectList(@Valid @RequestBody PageDTO param) {
|
||||
return sysUserService.selectList(param);
|
||||
}
|
||||
/**
|
||||
* 列表查询
|
||||
*/
|
||||
@RequestMapping("/list")
|
||||
public Object selectList(@Valid @RequestBody PageDTO param) {
|
||||
return sysUserService.selectList(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
public Object selectById(@RequestParam Long... id) {
|
||||
return sysUserService.selectById(id);
|
||||
}
|
||||
/**
|
||||
* 查询详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
public Object selectById(@RequestParam Long... id) {
|
||||
return sysUserService.selectById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
@OperationLog(value = "用户新增", type = SysLog.TYPE_USER)
|
||||
public Object insert(@RequestBody SysUser e) {
|
||||
return sysUserService.insert(e);
|
||||
}
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
@OperationLog(value = "用户新增", type = SysLog.TYPE_USER)
|
||||
public Object insert(@RequestBody SysUser e) {
|
||||
return sysUserService.insert(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@OperationLog(value = "用户修改", type = SysLog.TYPE_USER)
|
||||
public Object update(@RequestBody SysUser e) {
|
||||
return sysUserService.update(e);
|
||||
}
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@OperationLog(value = "用户修改", type = SysLog.TYPE_USER)
|
||||
public Object update(@RequestBody SysUser e) {
|
||||
return sysUserService.update(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@OperationLog(value = "用户删除", type = SysLog.TYPE_USER)
|
||||
@PostMapping("/remove")
|
||||
public Object delete(@RequestBody Long... id) {
|
||||
return sysUserService.delete(id);
|
||||
}
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@OperationLog(value = "用户删除", type = SysLog.TYPE_USER)
|
||||
@PostMapping("/remove")
|
||||
public Object delete(@RequestBody Long... id) {
|
||||
return sysUserService.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,26 +10,37 @@ public class UserDTO {
|
||||
|
||||
private Long id;
|
||||
|
||||
/** 用户名 */
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String account;
|
||||
|
||||
/** 状态 0:禁用 1:正常 */
|
||||
/**
|
||||
* 状态 0:禁用 1:正常
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/** 用户所有的权限信息 */
|
||||
/**
|
||||
* 用户所有的权限信息
|
||||
*/
|
||||
private Set<String> authorities;
|
||||
|
||||
/** 角色ID */
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Integer roleId;
|
||||
|
||||
/** Token */
|
||||
/**
|
||||
* Token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 获取token的缓存键
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getTokenKey() {
|
||||
return CacheKey.getToekn(id, account);
|
||||
return CacheKey.getToken(id, account);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,57 +1,25 @@
|
||||
package com.zhangy.skyeye.publics.service.impl;
|
||||
|
||||
import com.zhangy.skyeye.common.extend.exception.ServiceException;
|
||||
import com.zhangy.skyeye.cache.publics.UserTokenCache;
|
||||
import com.zhangy.skyeye.publics.service.ISysLoginService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 com.zhangy.skyeye.publics.utils.CacheUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysLoginServiceImpl implements ISysLoginService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
private final UserTokenCache userTokenCache;
|
||||
|
||||
@Override
|
||||
public boolean hasLogged() {
|
||||
return Boolean.TRUE.equals(redisTemplate.execute((RedisCallback<Boolean>) connection -> {
|
||||
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());
|
||||
}
|
||||
}));
|
||||
// 获取底层 Caffeine Cache
|
||||
return CacheUtil.size(userTokenCache) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countLogged() {
|
||||
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;
|
||||
});
|
||||
return (int) CacheUtil.size(userTokenCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
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.JmUavStatusDTO;
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @PROJECT_NAME: skyeyesystem
|
||||
* @DESCRIPTION: SAR 任务上下文提供者实现(基于 Redis 缓存,避免循环依赖)
|
||||
* - JmJobStatusServiceImpl 在更新状态时,主动把 JmUavStatusDTO 推送到 Redis
|
||||
* - 此类只从 Redis 读取,不注入 JmJobStatusService
|
||||
* - JmJobStatusServiceImpl 在更新状态时,主动把 JmUavStatusDTO 推送到 Redis
|
||||
* - 此类只从 Redis 读取,不注入 JmJobStatusService
|
||||
* @AUTHOR: GuanCheng Long
|
||||
* @DATE: 2026/1/21 1:10
|
||||
*/
|
||||
@ -22,14 +18,11 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class SarTaskContextProviderImpl implements SarTaskContextProvider {
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private final UavCache uavCache;
|
||||
|
||||
// Redis 键前缀
|
||||
private static final String UAV_STATUS_KEY_PREFIX = "sar:context:uav:";
|
||||
|
||||
// 缓存过期时间(秒)
|
||||
private static final long CACHE_EXPIRE_SECONDS = 600; // 10 分钟
|
||||
public SarTaskContextProviderImpl(UavCache uavCache) {
|
||||
this.uavCache = uavCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JmUavStatusDTO getCurrentUav(String payloadIp) {
|
||||
@ -37,16 +30,8 @@ public class SarTaskContextProviderImpl implements SarTaskContextProvider {
|
||||
log.warn("获取 uav 状态失败:payloadIp 为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
String key = "sar:context:uav:" + payloadIp; // 保持原 key 格式
|
||||
return uavCache.getShort(key, JmUavStatusDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -80,11 +65,10 @@ public class SarTaskContextProviderImpl implements SarTaskContextProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = UAV_STATUS_KEY_PREFIX + payloadIp;
|
||||
String key = "sar:context:uav:" + payloadIp;
|
||||
try {
|
||||
// 设置值 + 过期时间
|
||||
redisUtil.set(key, uavStatus, CACHE_EXPIRE_SECONDS);
|
||||
log.debug("已更新 Redis 缓存:key={}, jobExecId={}", key, uavStatus.getJobExecId());
|
||||
uavCache.putShort(key, uavStatus);// 自动过期 10 分钟
|
||||
log.debug("已更新缓存:key={}, jobExecId={}", key, uavStatus.getJobExecId());
|
||||
} catch (Exception e) {
|
||||
log.error("更新 SAR uav 状态缓存失败,ip={}", payloadIp, e);
|
||||
}
|
||||
@ -97,8 +81,8 @@ public class SarTaskContextProviderImpl implements SarTaskContextProvider {
|
||||
if (payloadIp == null) {
|
||||
return;
|
||||
}
|
||||
String key = UAV_STATUS_KEY_PREFIX + payloadIp;
|
||||
redisUtil.del(key);
|
||||
log.debug("SAR uav 缓存已标记清理(或自然过期):{}", key);
|
||||
String key = "sar:context:uav:" + payloadIp;
|
||||
uavCache.evictShort(key);
|
||||
log.debug("SAR uav 缓存已清理:{}", key);
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
package com.zhangy.skyeye.sar.control;
|
||||
|
||||
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.cache.sar.SarCache;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlDTO;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
|
||||
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 控制:连接
|
||||
* 控制:建立连接
|
||||
* 连接成功后,在回传状态时会将状态信息放入缓存,这里只记录连接标志
|
||||
*/
|
||||
@Slf4j
|
||||
@Order(1)
|
||||
@ -23,12 +22,11 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class SarControlConnectStrategy implements ISarControlStrategy {
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
private final SarCache sarCache;
|
||||
|
||||
@Override
|
||||
public boolean supports(SarControlParamDTO param) {
|
||||
SarControlTypeEnum controlType = param.getControlType();
|
||||
return controlType == SarControlTypeEnum.CONNECT;
|
||||
return param.getControlType() == SarControlTypeEnum.CONNECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,15 +38,13 @@ public class SarControlConnectStrategy implements ISarControlStrategy {
|
||||
|
||||
@Override
|
||||
public void sendPost(SarControlDTO sar) {
|
||||
// 建立连接后,回传状态时会将状态信息放入缓存
|
||||
redisUtil.hset(CacheKey.SAR_CONNECTED, sar.getIp(), new Date());
|
||||
// 建立连接后,记录连接标志
|
||||
sarCache.markConnected(sar.getIp());
|
||||
log.info("SAR [{}] 已标记为连接状态", sar.getIp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int retryType() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
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.util.ObjectUtil;
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
import com.zhangy.skyeye.publics.consts.CacheKey;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlDTO;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlPackDTO;
|
||||
@ -14,7 +14,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.RedisConnectionFailureException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
@ -59,7 +58,7 @@ public class SarControlContext {
|
||||
private final int POLLING_INTERVAL = 100;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private SarCache sarCache;
|
||||
|
||||
/**
|
||||
* 应答超时
|
||||
@ -74,6 +73,7 @@ public class SarControlContext {
|
||||
|
||||
/**
|
||||
* 执行
|
||||
*
|
||||
* @param param
|
||||
*/
|
||||
public void execute(SarControlParamDTO param) {
|
||||
@ -89,12 +89,12 @@ public class SarControlContext {
|
||||
/**
|
||||
* 执行发送:失败则递归重试
|
||||
*
|
||||
* @param param 控制参数
|
||||
* @param param 控制参数
|
||||
* @param matchedStrategy 控制策略
|
||||
*/
|
||||
private void sendControl(SarControlParamDTO param, ISarControlStrategy matchedStrategy) {
|
||||
List<SarControlDTO> controls = matchedStrategy.handle(param);
|
||||
for(SarControlDTO control: controls) {
|
||||
for (SarControlDTO control : controls) {
|
||||
control.setIp(param.getIp());
|
||||
sendUdp(control);
|
||||
matchedStrategy.sendPost(control);
|
||||
@ -103,60 +103,92 @@ public class SarControlContext {
|
||||
|
||||
/**
|
||||
* 发送,未收到回执则重发
|
||||
*
|
||||
* @param control
|
||||
*/
|
||||
private void sendUdp(SarControlDTO control) {
|
||||
SarControlTypeEnum controlType = control.getControlType();
|
||||
String ip = control.getIp();
|
||||
log.debug("开始发送雷达控制指令[" + controlType + "]----------------------");
|
||||
//System.out.println(control);
|
||||
String targetIp = control.getIp();
|
||||
log.info("准备发送雷达控制指令 | 类型:{} | 目标IP:{}", controlType, targetIp);
|
||||
String cacheKey = CacheKey.getSarControlBack(targetIp);
|
||||
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()) {
|
||||
byte[] content = pack(control);
|
||||
socket.connect(new InetSocketAddress(ip, PORT));
|
||||
DatagramPacket packet = new DatagramPacket(content, content.length);
|
||||
|
||||
int failCount = 0;
|
||||
SarErrorDTO info = null;
|
||||
while (info == null && failCount < RETRY_MAX) { // 失败重试
|
||||
socket.send(packet);
|
||||
// 每0.1秒取回执,1秒后超时
|
||||
long startTime = System.currentTimeMillis();
|
||||
String cacheKey = CacheKey.getSarControlBack(ip);
|
||||
|
||||
while (System.currentTimeMillis() - startTime < ANSWER_TIMEOUT) {
|
||||
if (redisUtil.hasKey(cacheKey)) {
|
||||
info = (SarErrorDTO) redisUtil.get(cacheKey);
|
||||
redisUtil.del(cacheKey);
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(POLLING_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
InetSocketAddress address = new InetSocketAddress(targetIp, PORT);
|
||||
socket.connect(address);
|
||||
DatagramPacket packet = new DatagramPacket(payload, payload.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;
|
||||
}
|
||||
if (info == null) { // 超时未收到回执
|
||||
failCount++;
|
||||
} else if (info.getExecStatus() == 1) {
|
||||
// 只有发送的数据结构错误时才会返回错误状态,并不会因为业务不允许返回错误
|
||||
throw new ServiceException("控制指令[" + controlType + "]执行状态错误,请重试");
|
||||
// 等待回执(轮询方式)
|
||||
response = waitForResponse(cacheKey, targetIp, controlType);
|
||||
if (response == null) {
|
||||
retryCount++;
|
||||
log.warn("第{}次发送未收到回执 | 类型:{} | ip:{}", retryCount, controlType, targetIp);
|
||||
}
|
||||
}
|
||||
if (info == null) {
|
||||
throw new SarConnectException("控制指令[" + controlType + "]发送失败,雷达[" + ip + "]无应答,请重试");
|
||||
} else {
|
||||
log.info("雷达控制指令[" + controlType + "]发送完毕----------------------");
|
||||
// 最终判断
|
||||
if (response == null) {
|
||||
String msg = String.format("雷达[%s]控制指令[%s]发送失败:超时无应答", targetIp, controlType);
|
||||
log.error(msg);
|
||||
throw new SarConnectException(msg + ",请重试");
|
||||
}
|
||||
} catch (RedisConnectionFailureException ex) {
|
||||
throw ServiceException.errorLog("无法连接到Redis服务!");
|
||||
} catch (IOException ex) {
|
||||
throw ServiceException.errorLog("控制指令[" + controlType + "]发送失败 " + ex.getMessage());
|
||||
// 业务状态判断
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询等待缓存中的回执(带超时)
|
||||
*/
|
||||
private SarErrorDTO waitForResponse(String cacheKey, String ip, SarControlTypeEnum controlType) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < ANSWER_TIMEOUT) {
|
||||
SarErrorDTO dto = sarCache.getControlBack(ip); // 如果 SarCache 封装了泛型方法可以直接用
|
||||
if (dto != null) {
|
||||
sarCache.saveStatus(cacheKey, null); // 清空 shortCache 对应的 key
|
||||
log.info("收到雷达回执 | ip:{} | 控制类型:{} | 状态:{}", ip, controlType, dto.getExecStatus());
|
||||
return dto;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(POLLING_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("等待雷达回执时被中断 | ip:{}", ip);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null; // 超时
|
||||
}
|
||||
|
||||
/**
|
||||
* 封包
|
||||
*
|
||||
* @param sarControl
|
||||
* @return
|
||||
*/
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package com.zhangy.skyeye.sar.control;
|
||||
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
|
||||
import com.zhangy.skyeye.cache.sar.SarCache;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlDTO;
|
||||
import com.zhangy.skyeye.publics.consts.CacheKey;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
|
||||
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@ -22,7 +21,7 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class SarControlDisconnectStrategy implements ISarControlStrategy {
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
private final SarCache sarCache;
|
||||
|
||||
@Override
|
||||
public boolean supports(SarControlParamDTO param) {
|
||||
@ -37,15 +36,12 @@ public class SarControlDisconnectStrategy implements ISarControlStrategy {
|
||||
return Collections.singletonList(connect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存键
|
||||
* @param sar
|
||||
*/
|
||||
@Override
|
||||
public void sendPost(SarControlDTO sar) {
|
||||
String connectKey = CacheKey.getSarConnect(sar.getIp());
|
||||
redisUtil.del(connectKey);
|
||||
redisUtil.hdel(CacheKey.SAR_CONNECTED, sar.getIp());
|
||||
// 清理短时状态
|
||||
sarCache.removeConnection(sar.getIp()); // ← 从 permanentCache 移除连接标志
|
||||
sarCache.saveStatus(sar.getIp(), null); // ← 等同于清空 shortCache 中的状态
|
||||
log.info("已断开 SAR IP: {},缓存清理完成", sar.getIp());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -42,14 +42,12 @@ public class SarControlController {
|
||||
*/
|
||||
@RequestMapping("/turnon")
|
||||
public Result turnOn(@RequestBody String ip) {
|
||||
String ipVal = "";
|
||||
String ipVal;
|
||||
try {
|
||||
// 1. 使用 ObjectMapper 将 JSON 字符串解析成一个 JsonNode 树
|
||||
JsonNode rootNode = objectMapper.readTree(ip);
|
||||
|
||||
// 2. 从树中获取 "payloadId" 节点,并将其值转换为文本
|
||||
ipVal = rootNode.path("payloadId").asText();
|
||||
|
||||
// 检查是否成功获取
|
||||
if (ipVal == null || ipVal.isEmpty()) {
|
||||
// 根据你的业务逻辑返回错误,例如
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
package com.zhangy.skyeye.sar.listen;
|
||||
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
import com.zhangy.skyeye.cache.sar.SarCache;
|
||||
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
|
||||
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.SarStatusPackDTO;
|
||||
import com.zhangy.skyeye.sar.enums.SarErrorTypeEnum;
|
||||
import com.zhangy.skyeye.sar.task.CircularBufferQueue;
|
||||
import com.zhangy.skyeye.sar.task.DiscardOldestPolicyWithLog;
|
||||
import com.zhangy.skyeye.sar.task.PriorityThreadFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SarStatusListener extends SarAbstractListener {
|
||||
|
||||
/**
|
||||
@ -49,8 +50,7 @@ public class SarStatusListener extends SarAbstractListener {
|
||||
@Value("${skyeye.sar.udp.status.connect-timeout:15}")
|
||||
private int connectTimeout;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private final SarCache sarCache;
|
||||
|
||||
@Autowired
|
||||
private JmJobStatusService sarJobStatusService;
|
||||
@ -70,38 +70,34 @@ public class SarStatusListener extends SarAbstractListener {
|
||||
@Override
|
||||
protected void processData(DatagramPacket packet) throws IOException {
|
||||
socket.receive(packet);
|
||||
// 过滤无效数据包
|
||||
if (packet.getLength() != 100) {
|
||||
return;
|
||||
}
|
||||
log.debug("接收到状态包----------------------");
|
||||
String ip = packet.getAddress().getHostAddress();
|
||||
// 处理接收到的数据
|
||||
SarStatusPackDTO packDTO = SarStatusPackDTO.parse(ip, packet.getData());
|
||||
if (packDTO == null) {
|
||||
if (running)
|
||||
log.warn("[" + packDTO.getPayloadIp() + "]状态包校验失败,已丢弃。错误包=" +
|
||||
packDTO.getErrorPacketStatus() + ",状态包=" + packDTO.getDevicePacketStatus());
|
||||
log.warn("状态包校验失败,已丢弃。");
|
||||
return;
|
||||
}
|
||||
// 错误包,结果放入缓存,超时1秒
|
||||
// 错误包 → 回执
|
||||
int errorStatus = packDTO.getErrorPacketStatus();
|
||||
if (errorStatus == 1) {
|
||||
SarErrorDTO info = packDTO.getDeviceErrorInfo();
|
||||
if (info.getErrorPacketType() == SarErrorTypeEnum.RESULT) {
|
||||
log.debug("收到回执包:" + ip);
|
||||
redisUtil.set(CacheKey.getSarControlBack(ip), info, answerTimeout, TimeUnit.SECONDS);
|
||||
log.debug("收到回执包:{}", ip);
|
||||
sarCache.saveControlBack(ip, info); // 使用 SarCache,类型安全,自动短期缓存,自动 2s 过期
|
||||
}
|
||||
}
|
||||
// 状态包
|
||||
int deviceStatus = packDTO.getDevicePacketStatus();
|
||||
if (deviceStatus == 1) {
|
||||
JmSarStatusDTO info = packDTO.getDeviceStatusInfo();
|
||||
log.debug("sar开机状态:" + info.getIsBoot());
|
||||
log.debug("sar开机状态:{}", info.getIsBoot());
|
||||
sarJobStatusService.update(ip, info);
|
||||
redisUtil.set(CacheKey.getSarConnect(ip), info, answerTimeout, TimeUnit.SECONDS);
|
||||
//System.out.println(info);
|
||||
sarCache.saveStatus(ip, info); // 使用 SarCache,类型安全,自动短期缓存,自动过期
|
||||
}
|
||||
log.debug("----------------------状态包解析完毕");
|
||||
log.debug("状态包解析完毕");
|
||||
}
|
||||
}
|
||||
@ -5,16 +5,13 @@ import com.zhangy.skyeye.common.extend.util.JsonUtil;
|
||||
import com.zhangy.skyeye.jm.dto.JmJobDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
|
||||
import com.zhangy.skyeye.jm.entity.JmJobPayload;
|
||||
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.cache.sar.SarCache;
|
||||
import com.zhangy.skyeye.sar.control.SarControlContext;
|
||||
import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
|
||||
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
|
||||
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.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
@ -30,9 +27,7 @@ import static com.zhangy.skyeye.jm.consts.JmJobModeEnum.CRUISE;
|
||||
public class SarControlServiceImpl implements ISarControlService {
|
||||
|
||||
private final SarControlContext udpSendContext;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private final SarCache sarCache;
|
||||
|
||||
@Override
|
||||
public void sendUdp(JmJobDTO job) {
|
||||
@ -67,7 +62,6 @@ public class SarControlServiceImpl implements ISarControlService {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sendUdp(SarControlParamDTO param) {
|
||||
udpSendContext.execute(param);
|
||||
@ -87,28 +81,30 @@ public class SarControlServiceImpl implements ISarControlService {
|
||||
|
||||
@Override
|
||||
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");
|
||||
}
|
||||
|
||||
SarControlParamDTO param = new SarControlParamDTO(ip, SarControlTypeEnum.TURNON);
|
||||
udpSendContext.execute(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
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");
|
||||
}
|
||||
|
||||
SarControlParamDTO param = new SarControlParamDTO(ip, SarControlTypeEnum.ENDALL);
|
||||
udpSendContext.execute(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JmSarStatusDTO getLatestStatus(String ip) {
|
||||
String connectKey = CacheKey.getSarConnect(ip);
|
||||
// 从缓存取载荷状态信息
|
||||
return (JmSarStatusDTO) redisUtil.get(connectKey);
|
||||
return sarCache.getLatestStatus(ip);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
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.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
|
||||
import com.zhangy.skyeye.redis.utils.RedisUtil;
|
||||
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmImageRotateDTO;
|
||||
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
|
||||
import com.zhangy.skyeye.jm.entity.JmImage;
|
||||
@ -16,7 +16,6 @@ import com.zhangy.skyeye.publics.utils.OpenCVUtil;
|
||||
import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
|
||||
import com.zhangy.skyeye.sar.service.ISarImageService;
|
||||
import com.zhangy.skyeye.sar.service.SarWsAsyncService;
|
||||
import com.zhangy.skyeye.sar.util.RadarDisplayOptions;
|
||||
import com.zhangy.skyeye.sar.util.SarImageToneAdjuster;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -46,7 +45,7 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
private SarWsAsyncService sarWsAsyncService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
private ImageCache imageCache;
|
||||
|
||||
// 图片最大宽度,前端说和电脑有关,4096保险点,一般是4096 8192 16384
|
||||
@Value("${skyeye.sar.image.max:4096}")
|
||||
@ -56,43 +55,43 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
private final String CACHE_FIELD_START_FRAME_NO = "startFrameNo";
|
||||
// 当前帧号
|
||||
private final String CACHE_FIELD_CURR_FRAME_NO = "currFrameNo";
|
||||
// 缓存超时(秒)
|
||||
private final long CACHE_EXPIRE_SECOND = 24 * 3600;
|
||||
|
||||
/**
|
||||
* 获取基准图像信息
|
||||
*
|
||||
* @param airlineId 航线执行ID
|
||||
* @param airlineId 航线执行ID
|
||||
* @param singleWidth 单条图片宽度
|
||||
* @param frameNo 当前帧号
|
||||
* @param frameNo 当前帧号
|
||||
* @return 返回非空的图像信息,其字段 imageNo 一定有值
|
||||
*/
|
||||
private JmImage getBaseImage(Long airlineId, int singleWidth, int frameNo) {IMG_MAX_WITH=1;
|
||||
private JmImage getBaseImage(Long airlineId, int singleWidth, int frameNo) {
|
||||
List<JmImage> imageList = imageService.selectLowByAirline(airlineId);
|
||||
String cacheKey = "jmImgJoin-" + airlineId;
|
||||
JmImage base = null;
|
||||
String cachePrefix = "jmImgJoin-" + airlineId;
|
||||
JmImage base;
|
||||
// 情况1:航线第一张图
|
||||
if (ObjectUtil.isEmpty(imageList)) {
|
||||
base = new JmImage();
|
||||
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;
|
||||
}
|
||||
// 情况2:如果最后一张还能拼图,则直接返回继续拼
|
||||
JmImage last = imageList.get(imageList.size() - 1);
|
||||
Integer startFrameNo = (Integer) redisUtil.hget(cacheKey, CACHE_FIELD_START_FRAME_NO);
|
||||
int currWidth = startFrameNo == null ? 0 : singleWidth * (frameNo - startFrameNo + 1); // 图宽(当前图+基准图)
|
||||
Integer startFrameNo = imageCache.get(cachePrefix + ":" + CACHE_FIELD_START_FRAME_NO, Integer.class);
|
||||
int currWidth = startFrameNo == null ? 0 : singleWidth * (frameNo - startFrameNo + 1);
|
||||
int surplusNum = (IMG_MAX_WITH - currWidth) / singleWidth; // 还可以拼图片数
|
||||
Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineId, CACHE_FIELD_CURR_FRAME_NO);
|
||||
Integer baseNo = imageCache.get(cachePrefix + ":" + CACHE_FIELD_CURR_FRAME_NO, Integer.class);
|
||||
|
||||
if (startFrameNo == null || currWidth < IMG_MAX_WITH ||
|
||||
baseNo == null || (frameNo - baseNo + 1 <= surplusNum)) { // 当前图+填充 不能超过允许拼接数
|
||||
log.info("当前宽度:" + currWidth + " < " + IMG_MAX_WITH + " 可以继续拼接");
|
||||
baseNo == null || (frameNo - baseNo + 1 <= surplusNum)) {
|
||||
log.info("当前宽度:{} < {} 可以继续拼接", currWidth, IMG_MAX_WITH);
|
||||
return last;
|
||||
}
|
||||
// 情况3:已经拼接到最大数量,或者当前图+填充数量超过允许拼接数量,创建新图像文件
|
||||
log.info("当前宽度:" + currWidth + " > " + IMG_MAX_WITH + " 重新拼接,当前帧号" + frameNo + "作为首帧");
|
||||
log.info("当前宽度:{} > {} 重新拼接,当前帧号{}作为首帧", currWidth, IMG_MAX_WITH, frameNo);
|
||||
base = new JmImage();
|
||||
redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
|
||||
imageCache.put(cachePrefix + ":" + CACHE_FIELD_START_FRAME_NO, frameNo);
|
||||
base.setImageNo(last.getImageNo() + 1);
|
||||
return base;
|
||||
}
|
||||
@ -107,47 +106,12 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
}
|
||||
// 使用前一张图的右侧坐标作为后一张图的左侧,前提是没丢图
|
||||
if (!isFirst && !lostImage) {
|
||||
/*imageFrame.setLon1(before[0]);
|
||||
imageFrame.setLat1(before[1]);
|
||||
imageFrame.setLon4(before[2]);
|
||||
imageFrame.setLat4(before[3]);*/
|
||||
// 注释部分保持原样
|
||||
}
|
||||
before[0] = imageFrame.getLon5();
|
||||
before[1] = imageFrame.getLat5();
|
||||
before[2] = imageFrame.getLon8();
|
||||
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;
|
||||
} */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,8 +119,8 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
*
|
||||
* @param sourceIp
|
||||
* @param airlineExecId
|
||||
* @param frameData 图像帧数据
|
||||
* @param imageFrame 图像帧
|
||||
* @param frameData 图像帧数据
|
||||
* @param imageFrame 图像帧
|
||||
*/
|
||||
@Override
|
||||
public JmImage parseImage(String sourceIp, Long airlineExecId, byte[] frameData, SarBackImageFrameDTO imageFrame) {
|
||||
@ -187,28 +151,28 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
}
|
||||
|
||||
// 3.保存图像png,用航线ID+序号命名
|
||||
JmImage base = getBaseImage(airlineExecId, currImage.width(), imageFrame.getFrameNo());
|
||||
String imageName = airlineExecId + "-" + base.getImageNo() +".png";
|
||||
JmImage base = getBaseImage(airlineExecId, currImage.width(), frameNo);
|
||||
String imageName = airlineExecId + "-" + base.getImageNo() + ".png";
|
||||
String[] imagePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, imageName);
|
||||
String currPath = imagePath[0];
|
||||
|
||||
System.out.println("帧:" + frameNo);
|
||||
|
||||
// 4.保存基准图(同步),用于下次拼接
|
||||
Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO);
|
||||
Integer baseNo = imageCache.get("jmImgJoin-" + airlineExecId + ":" + CACHE_FIELD_CURR_FRAME_NO, Integer.class);
|
||||
boolean lostImage = baseNo != null && (frameNo - baseNo > 1); // 判断是否丢图
|
||||
String basePath = sysFileTypeService.getAbsolutePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId,
|
||||
airlineExecId + "-" + base.getImageNo() +"-base.png");
|
||||
airlineExecId + "-" + base.getImageNo() + "-base.png");
|
||||
Mat baseMat = generateBaseMat(base, currImage, frameNo, imageFrame.getMax(), basePath, baseNo);
|
||||
if (baseMat == null) { // 拼接失败 或 基准图生成失败则跳过,按丢图处理
|
||||
return null;
|
||||
}
|
||||
if (lostImage) {
|
||||
log.warn("丢图"+(frameNo - baseNo)+"张!当前帧" + frameNo + ",前帧" + baseNo);
|
||||
log.warn("丢图{}张!当前帧{},前帧{}", (frameNo - baseNo), frameNo, baseNo);
|
||||
}
|
||||
|
||||
//modCoord(imageFrame, lostImage, currAirline);
|
||||
redisUtil.hset("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);// 更新帧号
|
||||
imageCache.put("jmImgJoin-" + airlineExecId + ":" + CACHE_FIELD_CURR_FRAME_NO, frameNo);
|
||||
// ### 亮度调整,用于可靠udp版本图像,固定使用系数0.5
|
||||
// 拆分多张图片,去掉自适应调整
|
||||
if (IMG_MAX_WITH > 20000) {
|
||||
@ -224,15 +188,15 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
// 6.更新基准图坐标和帧号
|
||||
JmImage imageInfo = saveImage(uav, airlineExecId, base, imagePath, imageFrame, frameNo, lostImage);
|
||||
long end = System.currentTimeMillis();
|
||||
log.info("生成" + imageFrame.getImageBitDeep()+"位雷达回传图像:帧序号" + frameNo + "," +
|
||||
imageInfo.getRelativePath() + ",耗时" + (end - start)/1000 + "秒");
|
||||
log.info("生成{}位雷达回传图像:帧序号{},{},耗时{}秒",
|
||||
imageFrame.getImageBitDeep(), frameNo, imageInfo.getRelativePath(), (end - start) / 1000);
|
||||
return imageInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载回传图像,先转置再转为Mat
|
||||
*
|
||||
* @param frameData 图像帧数据,包含参数信息和未转置的8位灰度图像数据
|
||||
* @param frameData 图像帧数据,包含参数信息和未转置的8位灰度图像数据
|
||||
* @param imageFrame 图像帧对象
|
||||
* @return 转置后的4通道图像Mat
|
||||
*/
|
||||
@ -265,10 +229,10 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
/**
|
||||
* 生成基准图,将原有基准图与新图拼接
|
||||
*
|
||||
* @param base 基准图信息
|
||||
* @param base 基准图信息
|
||||
* @param currImage 当前图
|
||||
* @param currNo 当前图的帧号
|
||||
* @param currMax 当前图最大值
|
||||
* @param currNo 当前图的帧号
|
||||
* @param currMax 当前图最大值
|
||||
* @param imagePath 基准图路径
|
||||
* @return
|
||||
*/
|
||||
@ -281,7 +245,7 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
if (baseMax < currMax) {
|
||||
OpenCVUtil.multiply(baseImage, baseMax / currMax);
|
||||
} else if (baseMax > currMax) {
|
||||
OpenCVUtil.multiply(currImage , currMax / baseMax);
|
||||
OpenCVUtil.multiply(currImage, currMax / baseMax);
|
||||
}
|
||||
}
|
||||
Mat baseMat = ImageUtil.join(baseImage, baseNo, currImage, currNo); // 会释放 currImage baseImage 资源
|
||||
@ -295,9 +259,9 @@ public class SarImageServiceImpl implements ISarImageService {
|
||||
* 生成后处理图,将拼接好的基准图转置并调整对比度
|
||||
*
|
||||
* @param currAirline 当前航线信息
|
||||
* @param baseMat 原基准图,处理后释放资源
|
||||
* @param imagePath 后处理图路径,每条航线对应一张图,每次生成会覆盖
|
||||
* @param imageLight 图像亮度倍数,0是不调整
|
||||
* @param baseMat 原基准图,处理后释放资源
|
||||
* @param imagePath 后处理图路径,每条航线对应一张图,每次生成会覆盖
|
||||
* @param imageLight 图像亮度倍数,0是不调整
|
||||
*/
|
||||
private void generateAfterMat(JmAirlineStatusDTO currAirline, Mat baseMat, String imagePath, int imageLight) {
|
||||
// 后处理参数
|
||||
|
||||
@ -49,11 +49,11 @@ spring:
|
||||
date-format: yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
|
||||
#redis 配置
|
||||
redis:
|
||||
database: 0
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 'P@ssw0rd'
|
||||
#redis:
|
||||
#database: 0
|
||||
#host: 127.0.0.1
|
||||
#port: 6379
|
||||
#password: 'P@ssw0rd'
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:mapping/**/*Mapping.xml
|
||||
|
||||
@ -22,10 +22,10 @@
|
||||
</dependency>
|
||||
|
||||
<!-- redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,55 +1,55 @@
|
||||
package com.zhangy.skyeye.redis.config;
|
||||
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 将redis key序列化为字符串
|
||||
* spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
|
||||
*/
|
||||
public class RedisKeySerializer implements RedisSerializer<Object> {
|
||||
private final Charset charset;
|
||||
private final ConversionService converter;
|
||||
|
||||
public RedisKeySerializer() {
|
||||
this(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public RedisKeySerializer(Charset charset) {
|
||||
Objects.requireNonNull(charset, "Charset must not be null");
|
||||
this.charset = charset;
|
||||
this.converter = DefaultConversionService.getSharedInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(byte[] bytes) {
|
||||
// redis keys 会用到反序列化
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] serialize(Object object) {
|
||||
Objects.requireNonNull(object, "redis key is null");
|
||||
String key;
|
||||
if (object instanceof SimpleKey) {
|
||||
key = "";
|
||||
} else if (object instanceof String) {
|
||||
key = (String) object;
|
||||
} else {
|
||||
key = converter.convert(object, String.class);
|
||||
}
|
||||
return key.getBytes(this.charset);
|
||||
}
|
||||
|
||||
}
|
||||
//package com.zhangy.skyeye.redis.config;
|
||||
//
|
||||
//import org.springframework.cache.interceptor.SimpleKey;
|
||||
//import org.springframework.core.convert.ConversionService;
|
||||
//import org.springframework.core.convert.support.DefaultConversionService;
|
||||
//import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
//import org.springframework.lang.Nullable;
|
||||
//
|
||||
//import java.nio.charset.Charset;
|
||||
//import java.nio.charset.StandardCharsets;
|
||||
//import java.util.Objects;
|
||||
//
|
||||
///**
|
||||
// * 将redis key序列化为字符串
|
||||
// * spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
|
||||
// */
|
||||
//public class RedisKeySerializer implements RedisSerializer<Object> {
|
||||
// private final Charset charset;
|
||||
// private final ConversionService converter;
|
||||
//
|
||||
// public RedisKeySerializer() {
|
||||
// this(StandardCharsets.UTF_8);
|
||||
// }
|
||||
//
|
||||
// public RedisKeySerializer(Charset charset) {
|
||||
// Objects.requireNonNull(charset, "Charset must not be null");
|
||||
// this.charset = charset;
|
||||
// this.converter = DefaultConversionService.getSharedInstance();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Object deserialize(byte[] bytes) {
|
||||
// // redis keys 会用到反序列化
|
||||
// if (bytes == null) {
|
||||
// return null;
|
||||
// }
|
||||
// return new String(bytes, charset);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// @Nullable
|
||||
// public byte[] serialize(Object object) {
|
||||
// Objects.requireNonNull(object, "redis key is null");
|
||||
// String key;
|
||||
// if (object instanceof SimpleKey) {
|
||||
// key = "";
|
||||
// } else if (object instanceof String) {
|
||||
// key = (String) object;
|
||||
// } else {
|
||||
// key = converter.convert(object, String.class);
|
||||
// }
|
||||
// return key.getBytes(this.charset);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,2 +1 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.zhangy.skyeye.redis.config.DTRedisTemplateConfiguration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
|
||||
|
||||
Loading…
Reference in New Issue
Block a user