对降雨栅格添加websocket响应
This commit is contained in:
@@ -5,12 +5,14 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 后台启动入口
|
||||
* 启动时过滤DataSourceAutoConfiguration,避免数据源自动配置
|
||||
*/
|
||||
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
|
||||
@EnableScheduling // 启用定时任务支持
|
||||
@MapperScan("com.gis.xian.mapper") // 扫描MyBatis的Mapper接口
|
||||
public class XianApplication {
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.gis.xian.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 降雨网格配置属性类
|
||||
* 从application.yml中读取rainfall.grid配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "rainfall.grid")
|
||||
public class RainfallGridProperties {
|
||||
|
||||
/**
|
||||
* 降雨站点网格数据 Redis Key
|
||||
*/
|
||||
private String stationGrid;
|
||||
|
||||
/**
|
||||
* 降雨站点标识符 Redis Key
|
||||
*/
|
||||
private String stationIdentifier;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.gis.xian.controller;
|
||||
|
||||
import com.gis.xian.domain.ApiResponse;
|
||||
import com.gis.xian.service.RainfallGridService;
|
||||
import com.gis.xian.vo.RainfallGridVo;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 降雨网格控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class RainfallGridController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@Resource
|
||||
private RainfallGridService rainfallGridService;
|
||||
|
||||
// 存储上一次的 ID,用于检测变化
|
||||
private final AtomicReference<String> lastIdentifier = new AtomicReference<>(null);
|
||||
|
||||
/**
|
||||
* 处理客户端订阅请求,首次订阅时返回当前数据
|
||||
* 客户端发送任意消息到 /app/rainfall/grid 即可触发
|
||||
*/
|
||||
@MessageMapping("/rainfall/grid")
|
||||
@SendTo("/topic/rainfall/grid/messages")
|
||||
public ApiResponse<RainfallGridVo> handleSubscription() {
|
||||
RainfallGridVo data = rainfallGridService.getRainfallGridData();
|
||||
|
||||
// 记录当前 identifier,用于后续变化检测
|
||||
String currentIdentifier = rainfallGridService.getCurrentIdentifier();
|
||||
if (currentIdentifier != null) {
|
||||
lastIdentifier.set(currentIdentifier);
|
||||
log.info("客户端订阅,记录初始 identifier: {}", currentIdentifier);
|
||||
}
|
||||
|
||||
log.info("响应客户端订阅请求: id={}", data.getId());
|
||||
return ApiResponse.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每秒检查 Redis 中的 identifier 是否变化,如果变化则推送新数据
|
||||
*/
|
||||
@Scheduled(fixedRate = 1000)
|
||||
public void checkAndPushIfChanged() {
|
||||
try {
|
||||
String currentIdentifier = rainfallGridService.getCurrentIdentifier();
|
||||
|
||||
// 如果 identifier 为空,跳过
|
||||
if (currentIdentifier == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 identifier 是否发生变化
|
||||
String previousIdentifier = lastIdentifier.get();
|
||||
if (previousIdentifier == null || !previousIdentifier.equals(currentIdentifier)) {
|
||||
log.info("检测到 identifier 变化: {} -> {}", previousIdentifier, currentIdentifier);
|
||||
|
||||
// 获取最新数据并推送
|
||||
RainfallGridVo data = rainfallGridService.getRainfallGridData();
|
||||
messagingTemplate.convertAndSend("/topic/rainfall/grid/messages", ApiResponse.ok(data));
|
||||
log.info("推送更新数据: id={}", data.getId());
|
||||
|
||||
// 更新记录的 identifier
|
||||
lastIdentifier.set(currentIdentifier);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("检查并推送数据失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gis.xian.service;
|
||||
|
||||
import com.gis.xian.vo.RainfallGridVo;
|
||||
|
||||
/**
|
||||
* 降雨网格服务接口
|
||||
*/
|
||||
public interface RainfallGridService {
|
||||
|
||||
/**
|
||||
* 从 Redis 获取降雨网格数据
|
||||
* @return 降雨网格数据
|
||||
*/
|
||||
RainfallGridVo getRainfallGridData();
|
||||
|
||||
/**
|
||||
* 获取当前的降雨站点标识符
|
||||
* @return 标识符
|
||||
*/
|
||||
String getCurrentIdentifier();
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.gis.xian.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.gis.xian.config.RainfallGridProperties;
|
||||
import com.gis.xian.service.RainfallGridService;
|
||||
import com.gis.xian.service.ex.ServiceException;
|
||||
import com.gis.xian.vo.RainfallGridVo;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 降雨网格服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class RainfallGridServiceImpl implements RainfallGridService {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private RainfallGridProperties rainfallGridProperties;
|
||||
|
||||
@Override
|
||||
public RainfallGridVo getRainfallGridData() {
|
||||
try {
|
||||
String redisKey = rainfallGridProperties.getStationGrid();
|
||||
Object gridDataObj = redisTemplate.opsForValue().get(redisKey);
|
||||
|
||||
if (gridDataObj == null) {
|
||||
throw new ServiceException("降雨网格数据不存在,Redis Key: " + redisKey);
|
||||
}
|
||||
|
||||
// 将 Redis 中的 JSON 字符串解析为 Map
|
||||
String gridDataJson;
|
||||
if (gridDataObj instanceof String) {
|
||||
gridDataJson = (String) gridDataObj;
|
||||
} else {
|
||||
gridDataJson = JSON.toJSONString(gridDataObj);
|
||||
}
|
||||
|
||||
// 解析 JSON 为 Map(下划线命名)
|
||||
Map<String, Object> gridDataMap = JSON.parseObject(gridDataJson, Map.class);
|
||||
|
||||
// 转换为 RainfallGridVo(小驼峰命名)
|
||||
return convertToVo(gridDataMap);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("解析降雨网格数据失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentIdentifier() {
|
||||
try {
|
||||
String redisKey = rainfallGridProperties.getStationIdentifier();
|
||||
Object identifierObj = redisTemplate.opsForValue().get(redisKey);
|
||||
|
||||
if (identifierObj == null) {
|
||||
return null;
|
||||
}
|
||||
return identifierObj.toString();
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("获取降雨站点标识符失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Map 转换为 RainfallGridVo
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private RainfallGridVo convertToVo(Map<String, Object> gridDataMap) {
|
||||
RainfallGridVo vo = new RainfallGridVo();
|
||||
|
||||
// 基本字段
|
||||
vo.setId(getLongValue(gridDataMap, "id"));
|
||||
vo.setPngPath((String) gridDataMap.get("png_path"));
|
||||
vo.setResolution(getDoubleValue(gridDataMap, "resolution"));
|
||||
vo.setStationCount(getIntValue(gridDataMap, "station_count"));
|
||||
|
||||
// 解析 query_time
|
||||
String queryTimeStr = (String) gridDataMap.get("query_time");
|
||||
if (queryTimeStr != null) {
|
||||
vo.setQueryTime(LocalDateTime.parse(
|
||||
queryTimeStr.replace(" ", "T"),
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
|
||||
));
|
||||
}
|
||||
|
||||
// 解析 cesium_config
|
||||
Map<String, Object> cesiumConfigMap = (Map<String, Object>) gridDataMap.get("cesium_config");
|
||||
if (cesiumConfigMap != null) {
|
||||
vo.setCesiumConfig(parseCesiumConfig(cesiumConfigMap));
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Cesium 配置
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private RainfallGridVo.CesiumConfig parseCesiumConfig(Map<String, Object> cesiumConfigMap) {
|
||||
RainfallGridVo.CesiumConfig cesiumConfig = new RainfallGridVo.CesiumConfig();
|
||||
cesiumConfig.setWidth(getDoubleValue(cesiumConfigMap, "width"));
|
||||
cesiumConfig.setHeight(getDoubleValue(cesiumConfigMap, "height"));
|
||||
|
||||
// 解析 rectangle
|
||||
Map<String, Object> rectangleMap = (Map<String, Object>) cesiumConfigMap.get("rectangle");
|
||||
if (rectangleMap != null) {
|
||||
cesiumConfig.setRectangle(parseRectangle(rectangleMap));
|
||||
}
|
||||
|
||||
return cesiumConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析矩形区域
|
||||
*/
|
||||
private RainfallGridVo.CesiumConfig.Rectangle parseRectangle(Map<String, Object> rectangleMap) {
|
||||
RainfallGridVo.CesiumConfig.Rectangle rectangle = new RainfallGridVo.CesiumConfig.Rectangle();
|
||||
rectangle.setWest(getDoubleValue(rectangleMap, "west"));
|
||||
rectangle.setSouth(getDoubleValue(rectangleMap, "south"));
|
||||
rectangle.setEast(getDoubleValue(rectangleMap, "east"));
|
||||
rectangle.setNorth(getDoubleValue(rectangleMap, "north"));
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:从 Map 中获取 Long 值
|
||||
*/
|
||||
private Long getLongValue(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
}
|
||||
return Long.parseLong(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:从 Map 中获取 Double 值
|
||||
*/
|
||||
private Double getDoubleValue(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
return Double.parseDouble(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:从 Map 中获取 Integer 值
|
||||
*/
|
||||
private Integer getIntValue(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return Integer.parseInt(value.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.gis.xian.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class RainfallGridVo {
|
||||
public RainfallGridVo() {
|
||||
this.cesiumConfig = new CesiumConfig();
|
||||
}
|
||||
|
||||
private Long id;
|
||||
private String pngPath;
|
||||
private LocalDateTime queryTime;
|
||||
private Double resolution;
|
||||
private Integer stationCount;
|
||||
private CesiumConfig cesiumConfig;
|
||||
|
||||
@Data
|
||||
public static class CesiumConfig {
|
||||
public CesiumConfig() {
|
||||
this.rectangle = new Rectangle();
|
||||
}
|
||||
|
||||
private Rectangle rectangle;
|
||||
private Double width;
|
||||
private Double height;
|
||||
|
||||
@Data
|
||||
public static class Rectangle {
|
||||
public Rectangle() {
|
||||
this.west = 0.0;
|
||||
this.south = 0.0;
|
||||
this.east = 0.0;
|
||||
this.north = 0.0;
|
||||
}
|
||||
private Double west;
|
||||
private Double south;
|
||||
private Double east;
|
||||
private Double north;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,13 +41,13 @@ safety:
|
||||
no-encrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /druid
|
||||
- /algorithm-api/rainfall/grid
|
||||
- /websocket/info
|
||||
- /websocket/**
|
||||
# 请求无需解密的路径
|
||||
no-decrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /druid
|
||||
- /algorithm-api/rainfall/grid
|
||||
- /websocket/info
|
||||
- /websocket/**
|
||||
|
||||
# 算法服务器配置
|
||||
|
||||
@@ -41,12 +41,10 @@ safety:
|
||||
# 响应无需加密的路径
|
||||
no-encrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /algorithm-api/rainfall/grid
|
||||
- /websocket/**
|
||||
# 请求无需解密的路径
|
||||
no-decrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /algorithm-api/rainfall/grid
|
||||
- /websocket/**
|
||||
|
||||
# 算法服务器配置
|
||||
|
||||
@@ -5,6 +5,15 @@ safety:
|
||||
global: 'xian:sm2:keypair:global'
|
||||
|
||||
|
||||
# 降雨网格数据
|
||||
rainfall:
|
||||
grid:
|
||||
# 降雨站点网格数据
|
||||
station-grid: 'xian:rainfall:rain_station_grid'
|
||||
# 降雨站点标识符
|
||||
station-identifier: 'xian:rainfall:rain_station_identifier'
|
||||
|
||||
|
||||
# 初始化数据存储
|
||||
init:
|
||||
data:
|
||||
|
||||
Reference in New Issue
Block a user