From 9d95700787da81d778d0c072b7c44f607d5e67d9 Mon Sep 17 00:00:00 2001 From: wzy-warehouse <18135009705@163.com> Date: Mon, 18 May 2026 17:26:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E9=99=8D=E9=9B=A8=E6=A0=85=E6=A0=BC?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0websocket=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/gis/xian/XianApplication.java | 2 + .../xian/config/RainfallGridProperties.java | 25 +++ .../controller/RainfallGridController.java | 82 +++++++++ .../gis/xian/service/RainfallGridService.java | 21 +++ .../service/impl/RainfallGridServiceImpl.java | 173 ++++++++++++++++++ .../java/com/gis/xian/vo/RainfallGridVo.java | 44 +++++ src/main/resources/application-dev.yml | 4 +- src/main/resources/application-prod.yml | 2 - src/main/resources/config/redis/redis-key.yml | 9 + 9 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/gis/xian/config/RainfallGridProperties.java create mode 100644 src/main/java/com/gis/xian/controller/RainfallGridController.java create mode 100644 src/main/java/com/gis/xian/service/RainfallGridService.java create mode 100644 src/main/java/com/gis/xian/service/impl/RainfallGridServiceImpl.java create mode 100644 src/main/java/com/gis/xian/vo/RainfallGridVo.java diff --git a/src/main/java/com/gis/xian/XianApplication.java b/src/main/java/com/gis/xian/XianApplication.java index f70390c..4311011 100644 --- a/src/main/java/com/gis/xian/XianApplication.java +++ b/src/main/java/com/gis/xian/XianApplication.java @@ -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 { diff --git a/src/main/java/com/gis/xian/config/RainfallGridProperties.java b/src/main/java/com/gis/xian/config/RainfallGridProperties.java new file mode 100644 index 0000000..46ce97b --- /dev/null +++ b/src/main/java/com/gis/xian/config/RainfallGridProperties.java @@ -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; +} diff --git a/src/main/java/com/gis/xian/controller/RainfallGridController.java b/src/main/java/com/gis/xian/controller/RainfallGridController.java new file mode 100644 index 0000000..9d1006e --- /dev/null +++ b/src/main/java/com/gis/xian/controller/RainfallGridController.java @@ -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 lastIdentifier = new AtomicReference<>(null); + + /** + * 处理客户端订阅请求,首次订阅时返回当前数据 + * 客户端发送任意消息到 /app/rainfall/grid 即可触发 + */ + @MessageMapping("/rainfall/grid") + @SendTo("/topic/rainfall/grid/messages") + public ApiResponse 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); + } + } +} diff --git a/src/main/java/com/gis/xian/service/RainfallGridService.java b/src/main/java/com/gis/xian/service/RainfallGridService.java new file mode 100644 index 0000000..97b0502 --- /dev/null +++ b/src/main/java/com/gis/xian/service/RainfallGridService.java @@ -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(); +} diff --git a/src/main/java/com/gis/xian/service/impl/RainfallGridServiceImpl.java b/src/main/java/com/gis/xian/service/impl/RainfallGridServiceImpl.java new file mode 100644 index 0000000..308e9ff --- /dev/null +++ b/src/main/java/com/gis/xian/service/impl/RainfallGridServiceImpl.java @@ -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 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 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 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 cesiumConfigMap = (Map) gridDataMap.get("cesium_config"); + if (cesiumConfigMap != null) { + vo.setCesiumConfig(parseCesiumConfig(cesiumConfigMap)); + } + + return vo; + } + + /** + * 解析 Cesium 配置 + */ + @SuppressWarnings("unchecked") + private RainfallGridVo.CesiumConfig parseCesiumConfig(Map cesiumConfigMap) { + RainfallGridVo.CesiumConfig cesiumConfig = new RainfallGridVo.CesiumConfig(); + cesiumConfig.setWidth(getDoubleValue(cesiumConfigMap, "width")); + cesiumConfig.setHeight(getDoubleValue(cesiumConfigMap, "height")); + + // 解析 rectangle + Map rectangleMap = (Map) cesiumConfigMap.get("rectangle"); + if (rectangleMap != null) { + cesiumConfig.setRectangle(parseRectangle(rectangleMap)); + } + + return cesiumConfig; + } + + /** + * 解析矩形区域 + */ + private RainfallGridVo.CesiumConfig.Rectangle parseRectangle(Map 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 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 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 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()); + } +} diff --git a/src/main/java/com/gis/xian/vo/RainfallGridVo.java b/src/main/java/com/gis/xian/vo/RainfallGridVo.java new file mode 100644 index 0000000..a07819d --- /dev/null +++ b/src/main/java/com/gis/xian/vo/RainfallGridVo.java @@ -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; + } + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 26473c1..debe341 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -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/** # 算法服务器配置 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 742f050..c0d816b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -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/** # 算法服务器配置 diff --git a/src/main/resources/config/redis/redis-key.yml b/src/main/resources/config/redis/redis-key.yml index da1ac91..0fac95e 100644 --- a/src/main/resources/config/redis/redis-key.yml +++ b/src/main/resources/config/redis/redis-key.yml @@ -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: