Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d343b3e64f | |||
| f95ff8c293 | |||
| ad05e64685 | |||
| 9390c5dc85 | |||
| c9cad1e366 | |||
| 4262ec24ad | |||
| 1264070023 | |||
| 30acf435ee | |||
| 0c59afa490 | |||
| 2ef96f812f | |||
| 20bfead821 | |||
| 0d952e0c98 | |||
| d2950ffdd3 | |||
| 71ae772ede | |||
| 8d9cbc3912 | |||
| c04b985ce6 | |||
| bc5b6fe50a | |||
| ec1549330a | |||
| b6bdc913ec | |||
| a7c4615227 | |||
| 175cbc0b43 | |||
| 16b0206318 | |||
| 12a134fbfe | |||
| a312cb4568 | |||
| 51960eaad4 | |||
| 9d3e51be31 | |||
| 60443470b1 | |||
| a3fae6630a | |||
| b004b5beae | |||
| e2c013c86b | |||
| 6bb5cf46f7 |
@@ -7,12 +7,13 @@
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<mybatis.spring.version>3.0.5</mybatis.spring.version>
|
||||
<druid.version>1.2.27</druid.version>
|
||||
<postgresql.version>42.7.8</postgresql.version>
|
||||
<lombok.version>1.18.42</lombok.version>
|
||||
<bcprov-jdk15to18.version>1.82</bcprov-jdk15to18.version>
|
||||
<fastjson.version>2.0.60</fastjson.version>
|
||||
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||||
<poi-tl-version>1.12.2</poi-tl-version>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
@@ -53,11 +54,11 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis 整合 Spring Boot -->
|
||||
<!-- mybatis-plus 整合 Spring Boot -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>${mybatis.spring.version}</version>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库连接池 Druid -->
|
||||
@@ -72,7 +73,7 @@
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
<scope>runtime</scope>
|
||||
<!-- <scope>runtime</scope>-->
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket 支持 -->
|
||||
@@ -120,12 +121,20 @@
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- word模板 -->
|
||||
<dependency>
|
||||
<groupId>com.deepoove</groupId>
|
||||
<artifactId>poi-tl</artifactId>
|
||||
<version>${poi-tl-version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试框架 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.gis.xian.config;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.gis.xian.domain.ApiResponse;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 算法服务 HTTP 客户端
|
||||
*
|
||||
* 两种使用方式:
|
||||
* 1. Controller 透传前端请求:{@link #proxyRequest(HttpServletRequest, HttpMethod)}
|
||||
* 2. Service 直接调用:{@link #get(String, Class)} / {@link #post(String, Object, Class)}
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlgorithmClient {
|
||||
|
||||
@Resource
|
||||
private AlgorithmServerProperties props;
|
||||
|
||||
private RestClient restClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(props.getConnectTimeout() * 1000);
|
||||
factory.setReadTimeout(props.getReadTimeout() * 1000);
|
||||
this.restClient = RestClient.builder().requestFactory(factory).build();
|
||||
log.info("AlgorithmClient 初始化: url={}, connectTimeout={}s, readTimeout={}s",
|
||||
props.getUrl(), props.getConnectTimeout(), props.getReadTimeout());
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 前端透传:完整代理 HTTP 请求
|
||||
// ================================================================
|
||||
|
||||
public ApiResponse<Object> proxyGet(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.GET);
|
||||
}
|
||||
|
||||
public ApiResponse<Object> proxyPost(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.POST);
|
||||
}
|
||||
|
||||
public ApiResponse<Object> proxyPut(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.PUT);
|
||||
}
|
||||
|
||||
public ApiResponse<Object> proxyDelete(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
public ApiResponse<Object> proxyPatch(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.PATCH);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ApiResponse<Object> proxyRequest(HttpServletRequest request, HttpMethod httpMethod) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
String targetUrl = buildTargetUrl(request);
|
||||
log.info("代理请求: {} -> {}", request.getRequestURI(), targetUrl);
|
||||
|
||||
RestClient.RequestBodySpec spec = restClient
|
||||
.method(httpMethod)
|
||||
.uri(targetUrl)
|
||||
.headers(h -> copyRequestHeaders(request, h));
|
||||
|
||||
if (httpMethod == HttpMethod.POST || httpMethod == HttpMethod.PUT || httpMethod == HttpMethod.PATCH) {
|
||||
String contentType = request.getContentType();
|
||||
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
if (bodyBytes.length > 0) {
|
||||
if (contentType != null) {
|
||||
spec.contentType(MediaType.parseMediaType(contentType));
|
||||
}
|
||||
spec.body(bodyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
ResponseEntity<String> response = spec.retrieve().toEntity(String.class);
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
log.info("代理完成: {} -> {}, {}ms, HTTP {}", request.getRequestURI(), targetUrl, elapsed, response.getStatusCode().value());
|
||||
|
||||
return parseResponseBody(response.getBody());
|
||||
|
||||
} catch (Exception e) {
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
log.error("代理失败: {}, {}ms, {}", request.getRequestURI(), elapsed, e.getMessage(), e);
|
||||
return ApiResponse.error(502, "算法服务调用失败: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 后端直接调用(类型安全)
|
||||
// ================================================================
|
||||
|
||||
public <T> T get(String path, Class<T> responseType) {
|
||||
String url = props.getUrl() + path;
|
||||
log.info("GET {}", url);
|
||||
return restClient.get().uri(url).retrieve().body(responseType);
|
||||
}
|
||||
|
||||
public <T> T get(String path, ParameterizedTypeReference<T> responseType) {
|
||||
String url = props.getUrl() + path;
|
||||
log.info("GET {}", url);
|
||||
return restClient.get().uri(url).retrieve().body(responseType);
|
||||
}
|
||||
|
||||
public <T> T post(String path, Object body, Class<T> responseType) {
|
||||
String url = props.getUrl() + path;
|
||||
log.info("POST {} body={}", url, body);
|
||||
return restClient.post().uri(url).body(body).retrieve().body(responseType);
|
||||
}
|
||||
|
||||
public <T> T post(String path, Object body, ParameterizedTypeReference<T> responseType) {
|
||||
String url = props.getUrl() + path;
|
||||
log.info("POST {} body={}", url, body);
|
||||
return restClient.post().uri(url).body(body).retrieve().body(responseType);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 内部工具方法
|
||||
// ================================================================
|
||||
|
||||
private String buildTargetUrl(HttpServletRequest request) {
|
||||
String path = request.getRequestURI().substring("/algorithm-api".length());
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
String baseUrl = props.getUrl();
|
||||
if (baseUrl.endsWith("/") && path.startsWith("/")) {
|
||||
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||
}
|
||||
String queryString = request.getQueryString();
|
||||
return queryString != null ? baseUrl + path + "?" + queryString : baseUrl + path;
|
||||
}
|
||||
|
||||
private void copyRequestHeaders(HttpServletRequest request, HttpHeaders headers) {
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
if (shouldExcludeHeader(name)) continue;
|
||||
headers.addAll(name, Collections.list(request.getHeaders(name)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldExcludeHeader(String name) {
|
||||
String lower = name.toLowerCase();
|
||||
return lower.equals("host") || lower.equals("content-length") || lower.equals("transfer-encoding");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ApiResponse<Object> parseResponseBody(String body) {
|
||||
if (body == null || body.isEmpty()) {
|
||||
return ApiResponse.ok(null);
|
||||
}
|
||||
try {
|
||||
Map<String, Object> map = JSON.parseObject(body, Map.class);
|
||||
if (map.containsKey("code") && map.containsKey("message")) {
|
||||
return new ApiResponse<>((Integer) map.get("code"), (String) map.get("message"), map.get("data"));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return ApiResponse.ok((Object) body);
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,14 @@ public class AlgorithmServerProperties {
|
||||
* 算法服务器地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 连接超时(秒)
|
||||
*/
|
||||
private int connectTimeout;
|
||||
|
||||
/**
|
||||
* 读取超时(秒)
|
||||
*/
|
||||
private int readTimeout;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.gis.xian.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 基础信息点 Redis Key 配置属性
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "init.data.base-points")
|
||||
public class BasePointsRedisProperties {
|
||||
|
||||
private HiddenDanger hiddenDanger;
|
||||
private String risk;
|
||||
private String hospitals;
|
||||
private String dangerousSource;
|
||||
private String emergencyShelter;
|
||||
private String firefighter;
|
||||
private String storePoints;
|
||||
private String school;
|
||||
private String bridge;
|
||||
private String reservoir;
|
||||
private String subway;
|
||||
|
||||
@Data
|
||||
public static class HiddenDanger {
|
||||
private String all;
|
||||
private String landslide;
|
||||
private String collapse;
|
||||
private String debrisFlow;
|
||||
private String flashFlood;
|
||||
private String waterLogging;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ public class DataSourceAspect {
|
||||
|
||||
@Around("@annotation(dataSource) || @within(dataSource)")
|
||||
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
|
||||
if (dataSource == null) {
|
||||
return point.proceed();
|
||||
}
|
||||
try {
|
||||
String dsName = dataSource.value();
|
||||
log.debug("切换数据源: {}", dsName);
|
||||
|
||||
@@ -25,6 +25,12 @@ public class DataSourceConfig {
|
||||
return DruidDataSourceBuilder.create().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.slave1")
|
||||
public DataSource slave1() {
|
||||
return DruidDataSourceBuilder.create().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource dataSource() {
|
||||
@@ -32,6 +38,7 @@ public class DataSourceConfig {
|
||||
Map<Object, Object> map = new HashMap<>();
|
||||
map.put("master", master());
|
||||
map.put("slave", slave());
|
||||
map.put("slave1", slave1());
|
||||
ds.setTargetDataSources(map);
|
||||
ds.setDefaultTargetDataSource(master());
|
||||
return ds;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gis.xian.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "report")
|
||||
public class ReportProperties {
|
||||
private DisasterCausingFactors disasterCausingFactors;
|
||||
private ReportFiles files;
|
||||
private Map<String, String> disasterTypes = new LinkedHashMap<>();
|
||||
|
||||
@Data
|
||||
public static class ReportFiles {
|
||||
/**
|
||||
* 文件服务器前缀
|
||||
*/
|
||||
private String serverUrl;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DisasterCausingFactors {
|
||||
private FactorConfig rainfall;
|
||||
private FactorConfig earthquake;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FactorConfig {
|
||||
private List<String> type;
|
||||
private Integer number;
|
||||
private String templatePath;
|
||||
private String outputPath;
|
||||
private String outputName;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,14 @@
|
||||
package com.gis.xian.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.gis.xian.config.AlgorithmServerProperties;
|
||||
import com.gis.xian.domain.ApiResponse;
|
||||
import com.gis.xian.config.AlgorithmClient;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 算法代理控制器
|
||||
* 所有以 /algorithm-api 开头的请求都会转发到算法服务器
|
||||
* 算法代理控制器 — 前端请求透传到算法服务器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/algorithm-api")
|
||||
@@ -26,197 +16,30 @@ import java.util.Map;
|
||||
public class AlgorithmProxyController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private AlgorithmServerProperties algorithmServerProperties;
|
||||
private AlgorithmClient algorithmClient;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
/**
|
||||
* 处理所有 GET 请求
|
||||
*/
|
||||
@GetMapping("/**")
|
||||
public ApiResponse<Object> proxyGet(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.GET);
|
||||
return algorithmClient.proxyGet(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有 POST 请求
|
||||
*/
|
||||
@PostMapping("/**")
|
||||
public ApiResponse<Object> proxyPost(HttpServletRequest request) throws IOException {
|
||||
return proxyRequest(request, HttpMethod.POST);
|
||||
public ApiResponse<Object> proxyPost(HttpServletRequest request) {
|
||||
return algorithmClient.proxyPost(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有 PUT 请求
|
||||
*/
|
||||
@PutMapping("/**")
|
||||
public ApiResponse<Object> proxyPut(HttpServletRequest request) throws IOException {
|
||||
return proxyRequest(request, HttpMethod.PUT);
|
||||
public ApiResponse<Object> proxyPut(HttpServletRequest request) {
|
||||
return algorithmClient.proxyPut(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有 DELETE 请求
|
||||
*/
|
||||
@DeleteMapping("/**")
|
||||
public ApiResponse<Object> proxyDelete(HttpServletRequest request) {
|
||||
return proxyRequest(request, HttpMethod.DELETE);
|
||||
return algorithmClient.proxyDelete(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有 PATCH 请求
|
||||
*/
|
||||
@PatchMapping("/**")
|
||||
public ApiResponse<Object> proxyPatch(HttpServletRequest request) throws IOException {
|
||||
return proxyRequest(request, HttpMethod.PATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的请求代理方法
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private ApiResponse<Object> proxyRequest(HttpServletRequest request, HttpMethod httpMethod) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// 构建目标 URL
|
||||
String targetUrl = buildTargetUrl(request);
|
||||
log.info("代理请求: {} -> {}", request.getRequestURI(), targetUrl);
|
||||
|
||||
// 构建请求头
|
||||
HttpHeaders headers = buildRequestHeaders(request);
|
||||
|
||||
// 构建请求体
|
||||
Object requestBody = null;
|
||||
if (httpMethod == HttpMethod.POST || httpMethod == HttpMethod.PUT || httpMethod == HttpMethod.PATCH) {
|
||||
String contentType = request.getContentType();
|
||||
if (contentType != null && !contentType.isEmpty()) {
|
||||
headers.setContentType(MediaType.parseMediaType(contentType));
|
||||
|
||||
// 读取请求体
|
||||
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
if (bodyBytes.length > 0) {
|
||||
// 尝试解析为 JSON,如果不是 JSON 则直接使用字节数组
|
||||
if (contentType.contains("application/json")) {
|
||||
String jsonBody = new String(bodyBytes, StandardCharsets.UTF_8);
|
||||
try {
|
||||
requestBody = JSON.parse(jsonBody);
|
||||
} catch (Exception e) {
|
||||
requestBody = jsonBody;
|
||||
}
|
||||
} else {
|
||||
requestBody = bodyBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 HTTP 实体
|
||||
HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
// 发送请求到算法服务器
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
targetUrl,
|
||||
httpMethod,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
// 解析响应
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("代理请求完成: {} -> {}, 耗时: {}ms, 状态码: {}",
|
||||
request.getRequestURI(), targetUrl, (endTime - startTime), response.getStatusCode());
|
||||
|
||||
// 尝试将响应解析为 ApiResponse
|
||||
String responseBody = response.getBody();
|
||||
if (responseBody != null && !responseBody.isEmpty()) {
|
||||
try {
|
||||
// 尝试解析为 ApiResponse 格式
|
||||
Map<String, Object> responseMap = JSON.parseObject(responseBody, Map.class);
|
||||
if (responseMap.containsKey("code") && responseMap.containsKey("message")) {
|
||||
// 如果已经是 ApiResponse 格式,直接返回
|
||||
Integer code = (Integer) responseMap.get("code");
|
||||
String message = (String) responseMap.get("message");
|
||||
Object data = responseMap.get("data");
|
||||
return new ApiResponse<>(code, message, data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("响应不是标准 ApiResponse 格式,将作为数据返回");
|
||||
}
|
||||
|
||||
// 如果不是 ApiResponse 格式,将整个响应作为 data 返回
|
||||
return ApiResponse.ok((Object) responseBody);
|
||||
}
|
||||
|
||||
return ApiResponse.ok((Object) null);
|
||||
|
||||
} catch (Exception e) {
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.error("代理请求失败: {} , 耗时: {}ms, 错误: {}",
|
||||
request.getRequestURI(), (endTime - startTime), e.getMessage(), e);
|
||||
return ApiResponse.error(502, "算法服务调用失败: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目标 URL
|
||||
*/
|
||||
private String buildTargetUrl(HttpServletRequest request) {
|
||||
// 获取原始请求路径,去掉 /algorithm-api 前缀
|
||||
String requestUri = request.getRequestURI();
|
||||
String path = requestUri.substring("/algorithm-api".length());
|
||||
|
||||
// 确保路径以 / 开头
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
|
||||
// 拼接算法服务器地址和路径
|
||||
String baseUrl = algorithmServerProperties.getUrl();
|
||||
if (baseUrl.endsWith("/") && path.startsWith("/")) {
|
||||
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||
}
|
||||
|
||||
String targetUrl = baseUrl + path;
|
||||
|
||||
// 添加查询参数
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null && !queryString.isEmpty()) {
|
||||
targetUrl += "?" + queryString;
|
||||
}
|
||||
|
||||
return targetUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求头
|
||||
*/
|
||||
private HttpHeaders buildRequestHeaders(HttpServletRequest request) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
// 复制所有请求头(排除一些特定的头)
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
// 排除一些不应该转发的头
|
||||
if (!shouldExcludeHeader(headerName)) {
|
||||
Enumeration<String> headerValues = request.getHeaders(headerName);
|
||||
while (headerValues.hasMoreElements()) {
|
||||
headers.add(headerName, headerValues.nextElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该排除该请求头
|
||||
*/
|
||||
private boolean shouldExcludeHeader(String headerName) {
|
||||
String lowerName = headerName.toLowerCase();
|
||||
// 排除 Host、Content-Length 等头
|
||||
return lowerName.equals("host") ||
|
||||
lowerName.equals("content-length") ||
|
||||
lowerName.equals("transfer-encoding");
|
||||
public ApiResponse<Object> proxyPatch(HttpServletRequest request) {
|
||||
return algorithmClient.proxyPatch(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gis.xian.controller;
|
||||
|
||||
import com.gis.xian.domain.ApiResponse;
|
||||
import com.gis.xian.service.ReportOutputService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/report/output")
|
||||
public class ReportOutputController {
|
||||
|
||||
@Resource
|
||||
private ReportOutputService reportOutputService;
|
||||
|
||||
/**
|
||||
* 输出暴雨报告
|
||||
* @param id 模拟id
|
||||
* @return 暴雨报告链接
|
||||
*/
|
||||
@PostMapping("/rainfall/{id}")
|
||||
public ApiResponse<String> rainfall(@PathVariable String id) {
|
||||
return ApiResponse.ok(reportOutputService.outputRainReport(Long.parseLong(id)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gis.xian.dto;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 预测条件基类 — 对应 xian_inference_result.condition JSONB 字段
|
||||
*/
|
||||
@Data
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class BaseCondition {
|
||||
|
||||
/**
|
||||
* 点位ID列表
|
||||
*/
|
||||
@JSONField(name = "point_ids")
|
||||
private List<Integer> pointIds;
|
||||
|
||||
/**
|
||||
* 行政区划代码
|
||||
*/
|
||||
@JSONField(name = "region_code")
|
||||
private String regionCode;
|
||||
|
||||
/**
|
||||
* 事件发生时间
|
||||
*/
|
||||
@JSONField(name = "occurred_time", format = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime occurredTime;
|
||||
|
||||
/**
|
||||
* 震级 (Richter)
|
||||
*/
|
||||
private Float magnitude;
|
||||
|
||||
/**
|
||||
* 震源深度 (km)
|
||||
*/
|
||||
private Float depth;
|
||||
|
||||
/**
|
||||
* 震中经度
|
||||
*/
|
||||
@JSONField(name = "epicenter_lon")
|
||||
private Float epicenterLon;
|
||||
|
||||
/**
|
||||
* 震中纬度
|
||||
*/
|
||||
@JSONField(name = "epicenter_lat")
|
||||
private Float epicenterLat;
|
||||
|
||||
/**
|
||||
* 累计降雨量(mm)
|
||||
*/
|
||||
private Float rainfall;
|
||||
|
||||
/**
|
||||
* 降雨持续时间(h)
|
||||
*/
|
||||
private Float duration;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.gis.xian.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 灾害风险数据——按灾害类型聚合,用于填充报告模板
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DisasterRiskData {
|
||||
|
||||
/** 灾害类型中文名(滑坡、泥石流、山洪、内涝、崩塌) */
|
||||
private String disasterType;
|
||||
|
||||
/** 模板条件变量后缀(Landslide、DebrisFlow、TorrentialFlood、WaterLogging、Collapse) */
|
||||
private String conditionSuffix;
|
||||
|
||||
/** 中高风险街道(逗号分隔) */
|
||||
private String highRiskStreets;
|
||||
|
||||
/** 影响人数下限(暂空,待公式计算) */
|
||||
private String influenceLower = "";
|
||||
|
||||
/** 影响人数上限(暂空,待公式计算) */
|
||||
private String influenceUpper = "";
|
||||
|
||||
/** 隐患点人口分布图完整 URL */
|
||||
private String imageUrl;
|
||||
|
||||
/** 风险明细列表 */
|
||||
private List<SpotRisk> spots = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 是否有该类型的数据
|
||||
*/
|
||||
public boolean hasData() {
|
||||
return spots != null && !spots.isEmpty();
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class SpotRisk {
|
||||
/** 序号 */
|
||||
private int index;
|
||||
/** 位置 */
|
||||
private String position;
|
||||
/** 灾害点名称 */
|
||||
private String disasterName;
|
||||
/** 乡镇/街道 */
|
||||
private String village;
|
||||
/** 发生概率(小数 0-1) */
|
||||
private Double probability;
|
||||
/** 灾害等级:高(>=70%) / 中(50%-70%) */
|
||||
private String riskLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.gis.xian.dto;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class RainfallDistrictSummaryResponseDTO {
|
||||
/**
|
||||
* 行政区名称
|
||||
*/
|
||||
@JSONField(name = "district_name")
|
||||
private String districtName;
|
||||
/**
|
||||
* 行政区编码
|
||||
*/
|
||||
@JSONField(name = "district_code")
|
||||
private String districtCode;
|
||||
/**
|
||||
* 雨量
|
||||
*/
|
||||
private String rainfall;
|
||||
/**
|
||||
* 持续时间
|
||||
*/
|
||||
@JSONField(name = "duration_hours")
|
||||
private String durationHours;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.gis.xian.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RiskAndHiddenSpotDTO {
|
||||
private String districtName;
|
||||
private String number;
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.gis.xian.entity;
|
||||
|
||||
import java.util.Date;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 推理结果表
|
||||
* @TableName xian_inference_result
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "xian_inference_result", autoResultMap = true)
|
||||
public class XianInferenceResult {
|
||||
|
||||
/**
|
||||
@@ -19,14 +23,14 @@ public class XianInferenceResult {
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 事件类型
|
||||
* 事件类型:rainfall / earthquake
|
||||
*/
|
||||
private String eventType;
|
||||
|
||||
/**
|
||||
* 发生时间
|
||||
*/
|
||||
private Date occurredTime;
|
||||
private LocalDateTime occurredTime;
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
@@ -36,186 +40,20 @@ public class XianInferenceResult {
|
||||
/**
|
||||
* 推理结果(JSONB)
|
||||
*/
|
||||
private Object result;
|
||||
private String result;
|
||||
|
||||
/**
|
||||
* 条件(JSONB)
|
||||
* 预测条件(JSONB)
|
||||
*/
|
||||
private Object condition;
|
||||
|
||||
/**
|
||||
* 文件路径(JSONB)
|
||||
*/
|
||||
private Object filePath;
|
||||
private String condition;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 是否删除(0: 未删除, 1: 已删除)
|
||||
* 是否删除(0:未删除, 1:已删除)
|
||||
*/
|
||||
private Integer isDelete;
|
||||
|
||||
// 构造方法
|
||||
public XianInferenceResult() {
|
||||
}
|
||||
|
||||
public XianInferenceResult(Long id, String name, String eventType, Date occurredTime,
|
||||
String operationType, Object result, Object condition,
|
||||
Object filePath, Date createTime, Integer isDelete) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.eventType = eventType;
|
||||
this.occurredTime = occurredTime;
|
||||
this.operationType = operationType;
|
||||
this.result = result;
|
||||
this.condition = condition;
|
||||
this.filePath = filePath;
|
||||
this.createTime = createTime;
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
// Getter 和 Setter 方法
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public Date getOccurredTime() {
|
||||
return occurredTime;
|
||||
}
|
||||
|
||||
public void setOccurredTime(Date occurredTime) {
|
||||
this.occurredTime = occurredTime;
|
||||
}
|
||||
|
||||
public String getOperationType() {
|
||||
return operationType;
|
||||
}
|
||||
|
||||
public void setOperationType(String operationType) {
|
||||
this.operationType = operationType;
|
||||
}
|
||||
|
||||
public Object getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(Object result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Object getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public void setCondition(Object condition) {
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
public Object getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(Object filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Integer getIsDelete() {
|
||||
return isDelete;
|
||||
}
|
||||
|
||||
public void setIsDelete(Integer isDelete) {
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
if (this == that) {
|
||||
return true;
|
||||
}
|
||||
if (that == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != that.getClass()) {
|
||||
return false;
|
||||
}
|
||||
XianInferenceResult other = (XianInferenceResult) that;
|
||||
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
|
||||
&& (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
|
||||
&& (this.getEventType() == null ? other.getEventType() == null : this.getEventType().equals(other.getEventType()))
|
||||
&& (this.getOccurredTime() == null ? other.getOccurredTime() == null : this.getOccurredTime().equals(other.getOccurredTime()))
|
||||
&& (this.getOperationType() == null ? other.getOperationType() == null : this.getOperationType().equals(other.getOperationType()))
|
||||
&& (this.getResult() == null ? other.getResult() == null : this.getResult().equals(other.getResult()))
|
||||
&& (this.getCondition() == null ? other.getCondition() == null : this.getCondition().equals(other.getCondition()))
|
||||
&& (this.getFilePath() == null ? other.getFilePath() == null : this.getFilePath().equals(other.getFilePath()))
|
||||
&& (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
|
||||
&& (this.getIsDelete() == null ? other.getIsDelete() == null : this.getIsDelete().equals(other.getIsDelete()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
|
||||
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
|
||||
result = prime * result + ((getEventType() == null) ? 0 : getEventType().hashCode());
|
||||
result = prime * result + ((getOccurredTime() == null) ? 0 : getOccurredTime().hashCode());
|
||||
result = prime * result + ((getOperationType() == null) ? 0 : getOperationType().hashCode());
|
||||
result = prime * result + ((getResult() == null) ? 0 : getResult().hashCode());
|
||||
result = prime * result + ((getCondition() == null) ? 0 : getCondition().hashCode());
|
||||
result = prime * result + ((getFilePath() == null) ? 0 : getFilePath().hashCode());
|
||||
result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
|
||||
result = prime * result + ((getIsDelete() == null) ? 0 : getIsDelete().hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [");
|
||||
sb.append("Hash = ").append(hashCode());
|
||||
sb.append(", id=").append(id);
|
||||
sb.append(", name=").append(name);
|
||||
sb.append(", eventType=").append(eventType);
|
||||
sb.append(", occurredTime=").append(occurredTime);
|
||||
sb.append(", operationType=").append(operationType);
|
||||
sb.append(", result=").append(result);
|
||||
sb.append(", condition=").append(condition);
|
||||
sb.append(", filePath=").append(filePath);
|
||||
sb.append(", createTime=").append(createTime);
|
||||
sb.append(", isDelete=").append(isDelete);
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gis.xian.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 推理结果文件表
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "xian_inference_result_file", autoResultMap = true)
|
||||
public class XianInferenceResultFile {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long inferenceId;
|
||||
|
||||
private String filePath;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private Integer isDelete;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.gis.xian.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 西安市气象局-实况-小时实况
|
||||
* @TableName xian_meteorology
|
||||
*/
|
||||
@TableName(value ="xian_meteorology")
|
||||
@Data
|
||||
public class XianMeteorology implements Serializable {
|
||||
/**
|
||||
* 主键ID,自增
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 区站名称
|
||||
*/
|
||||
@TableField(value = "station_name")
|
||||
private String stationName;
|
||||
|
||||
/**
|
||||
* 水平能见度(人工)
|
||||
*/
|
||||
@TableField(value = "visibility")
|
||||
private Double visibility;
|
||||
|
||||
/**
|
||||
* 区域编码
|
||||
*/
|
||||
@TableField(value = "area_code")
|
||||
private String areaCode;
|
||||
|
||||
/**
|
||||
* 最大风速
|
||||
*/
|
||||
@TableField(value = "max_wind_speed")
|
||||
private Double maxWindSpeed;
|
||||
|
||||
/**
|
||||
* 过去1小时降水量,单位:毫米
|
||||
*/
|
||||
@TableField(value = "rainfall_1h")
|
||||
private Double rainfall1h;
|
||||
|
||||
/**
|
||||
* 气压
|
||||
*/
|
||||
@TableField(value = "pressure")
|
||||
private Double pressure;
|
||||
|
||||
/**
|
||||
* 最大风速的风向
|
||||
*/
|
||||
@TableField(value = "max_wind_direction")
|
||||
private Double maxWindDirection;
|
||||
|
||||
/**
|
||||
* 极大风速的风向
|
||||
*/
|
||||
@TableField(value = "inst_max_wind_direction")
|
||||
private Double instMaxWindDirection;
|
||||
|
||||
/**
|
||||
* 区站号
|
||||
*/
|
||||
@TableField(value = "station_id")
|
||||
private String stationId;
|
||||
|
||||
/**
|
||||
* 温度/气温,单位:摄氏度
|
||||
*/
|
||||
@TableField(value = "temperature")
|
||||
private Double temperature;
|
||||
|
||||
/**
|
||||
* 相对湿度
|
||||
*/
|
||||
@TableField(value = "relative_humidity")
|
||||
private Double relativeHumidity;
|
||||
|
||||
/**
|
||||
* 日期(加8小时为数据对应时间)
|
||||
*/
|
||||
@TableField(value = "datetime")
|
||||
private Long datetime;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
@TableField(value = "lon")
|
||||
private Double lon;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
@TableField(value = "lat")
|
||||
private Double lat;
|
||||
|
||||
/**
|
||||
* 地理位置点
|
||||
*/
|
||||
@TableField(value = "geom")
|
||||
private Object geom;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除标识,0未删除,1已删除
|
||||
*/
|
||||
@TableField(value = "is_delete")
|
||||
private Integer isDelete;
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gis.xian.mapper;
|
||||
|
||||
import com.gis.xian.dto.RiskAndHiddenSpotDTO;
|
||||
import com.gis.xian.entity.XianHiddenDangerSpots;
|
||||
|
||||
import java.util.List;
|
||||
@@ -13,6 +14,7 @@ import java.util.List;
|
||||
public interface XianHiddenDangerSpotsMapper {
|
||||
/**
|
||||
* 获取所有基础点:滑坡、泥石流、山洪、内涝
|
||||
*
|
||||
* @param disasterType 具体灾害类型(landslide-滑坡, debris_flow-泥石流, flash_flood-山洪, water_logging-内涝),可选
|
||||
* @return 基础点列表
|
||||
*/
|
||||
@@ -20,10 +22,27 @@ public interface XianHiddenDangerSpotsMapper {
|
||||
|
||||
/**
|
||||
* 根据id获取隐患点详情
|
||||
*
|
||||
* @param id 隐患点id
|
||||
* @return 隐患点详情
|
||||
*/
|
||||
XianHiddenDangerSpots getPointDetailById(Long id);
|
||||
|
||||
/**
|
||||
* 根据行政区获取隐患点数量
|
||||
*
|
||||
* @param districtNames 行政名称列表
|
||||
* @return 隐患点数量
|
||||
*/
|
||||
List<RiskAndHiddenSpotDTO> queryHiddenDangerNumberByDistrictName(List<String> districtNames);
|
||||
|
||||
/**
|
||||
* 根据id获取隐患点详情
|
||||
*
|
||||
* @param ids 隐患点id列表
|
||||
* @return 隐患点详情
|
||||
*/
|
||||
List<XianHiddenDangerSpots> getPointDetailByIds(List<Long> ids);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gis.xian.mapper;
|
||||
|
||||
import com.gis.xian.entity.XianInferenceResultFile;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 针对表【xian_inference_result_file(推理结果文件表)】的数据库操作Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface XianInferenceResultFileMapper {
|
||||
|
||||
/**
|
||||
* 根据推理结果id获取文件列表
|
||||
*/
|
||||
List<XianInferenceResultFile> selectByInferenceId(@Param("inferenceId") Long inferenceId);
|
||||
|
||||
/**
|
||||
* 插入或更新(按 inference_id + file_name 唯一约束 upsert)
|
||||
*/
|
||||
int upsert(@Param("inferenceId") Long inferenceId,
|
||||
@Param("filePath") String filePath,
|
||||
@Param("fileName") String fileName);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gis.xian.mapper;
|
||||
|
||||
import com.gis.xian.entity.XianInferenceResult;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@@ -13,9 +14,25 @@ import org.apache.ibatis.annotations.Param;
|
||||
public interface XianInferenceResultMapper {
|
||||
/**
|
||||
* 根据id和pointId获取概率
|
||||
* @param id 预测结果id
|
||||
*
|
||||
* @param id 预测结果id
|
||||
* @param pointId 隐患点/风险点id和类型
|
||||
* @return 预测概率
|
||||
*/
|
||||
String getProbabilityByIdAndPointId(@Param("id") Long id, @Param("pointId") String pointId);
|
||||
|
||||
/**
|
||||
* 根据id获取预测结果
|
||||
*
|
||||
* @param id 预测结果id
|
||||
* @return 预测结果
|
||||
*/
|
||||
XianInferenceResult selectById(Long id);
|
||||
|
||||
/**
|
||||
* 获取预测结果数量
|
||||
*
|
||||
* @return 预测结果数量
|
||||
*/
|
||||
Integer getTheNumberOfPredictedPoints();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.gis.xian.mapper;
|
||||
|
||||
/**
|
||||
* @author wzy
|
||||
* @description 针对表【xian_meteorology(西安市气象局-实况-小时实况)】的数据库操作Mapper
|
||||
* @createDate 2026-06-27 10:28:34
|
||||
* @Entity com.gis.xian.entity.XianMeteorology
|
||||
*/
|
||||
public interface XianMeteorologyMapper {
|
||||
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
package com.gis.xian.mapper;
|
||||
|
||||
import com.gis.xian.dto.RiskAndHiddenSpotDTO;
|
||||
import com.gis.xian.entity.XianRiskSpots;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wzy
|
||||
* @description 针对表【xian_risk_spots(地质灾害风险区)】的数据库操作Mapper
|
||||
* @createDate 2026-04-11 10:38:29
|
||||
* @Entity com.gis.xian.entity.XianRiskSpots
|
||||
*/
|
||||
* @author wzy
|
||||
* @description 针对表【xian_risk_spots(地质灾害风险区)】的数据库操作Mapper
|
||||
* @createDate 2026-04-11 10:38:29
|
||||
* @Entity com.gis.xian.entity.XianRiskSpots
|
||||
*/
|
||||
public interface XianRiskSpotsMapper {
|
||||
/**
|
||||
* 获取所有风险点基础信息
|
||||
*
|
||||
* @return 风险点基础列表
|
||||
*/
|
||||
List<XianRiskSpots> getBasePoints();
|
||||
@@ -20,10 +22,19 @@ public interface XianRiskSpotsMapper {
|
||||
|
||||
/**
|
||||
* 根据id获取风险点详情
|
||||
*
|
||||
* @param id 风险点id
|
||||
* @return 风险点详情
|
||||
*/
|
||||
XianRiskSpots getPointDetailById(Long id);
|
||||
|
||||
/**
|
||||
* 根据行政区获取风险点数量
|
||||
*
|
||||
* @param districtNames 行政名称列表
|
||||
* @return 风险点数量
|
||||
*/
|
||||
List<RiskAndHiddenSpotDTO> queryRiskNumberByDistrictName(List<String> districtNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gis.xian.service;
|
||||
|
||||
public interface ReportOutputService {
|
||||
/**
|
||||
* 产出暴雨报告
|
||||
* @param id 模拟id
|
||||
* @return 报告链接
|
||||
*/
|
||||
String outputRainReport(Long id);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.gis.xian.service.ex;
|
||||
|
||||
public class ReportParameterException extends ServiceException {
|
||||
public ReportParameterException() {
|
||||
}
|
||||
|
||||
public ReportParameterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ReportParameterException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ReportParameterException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ReportParameterException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
package com.gis.xian.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.data.PictureRenderData;
|
||||
import com.deepoove.poi.data.Pictures;
|
||||
import com.deepoove.poi.policy.TableRenderPolicy;
|
||||
import com.gis.xian.config.AlgorithmClient;
|
||||
import com.gis.xian.config.ReportProperties;
|
||||
import com.gis.xian.domain.ApiResponse;
|
||||
import com.gis.xian.dto.BaseCondition;
|
||||
import com.gis.xian.dto.DisasterRiskData;
|
||||
import com.gis.xian.dto.RainfallDistrictSummaryResponseDTO;
|
||||
import com.gis.xian.dto.RiskAndHiddenSpotDTO;
|
||||
import com.gis.xian.entity.XianHiddenDangerSpots;
|
||||
import com.gis.xian.entity.XianInferenceResult;
|
||||
import com.gis.xian.entity.XianInferenceResultFile;
|
||||
import com.gis.xian.mapper.XianHiddenDangerSpotsMapper;
|
||||
import com.gis.xian.mapper.XianInferenceResultFileMapper;
|
||||
import com.gis.xian.mapper.XianInferenceResultMapper;
|
||||
import com.gis.xian.mapper.XianRiskSpotsMapper;
|
||||
import com.gis.xian.service.ReportOutputService;
|
||||
import com.gis.xian.utils.DateTimeUtils;
|
||||
import com.gis.xian.utils.ReportTableBuilder;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class IReportOutputServiceImpl implements ReportOutputService {
|
||||
|
||||
/**
|
||||
* 暴雨灾害类型 → 模板变量后缀
|
||||
*/
|
||||
private static final Map<String, String> DISASTER_CONDITION_MAP = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
DISASTER_CONDITION_MAP.put("滑坡", "Landslide");
|
||||
DISASTER_CONDITION_MAP.put("泥石流", "DebrisFlow");
|
||||
DISASTER_CONDITION_MAP.put("山洪", "TorrentialFlood");
|
||||
DISASTER_CONDITION_MAP.put("内涝", "WaterLogging");
|
||||
DISASTER_CONDITION_MAP.put("崩塌", "Collapse");
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片命名模式
|
||||
*/
|
||||
private static final String IMAGE_NAME_PATTERN = "暴雨%s潜在隐患点及人口分布图.jpg";
|
||||
|
||||
@Value("${files.local.path}")
|
||||
private String localPath;
|
||||
|
||||
@Value("${files.server.url}")
|
||||
private String fileServerPath;
|
||||
|
||||
@Resource
|
||||
private XianInferenceResultMapper inferenceResultMapper;
|
||||
|
||||
@Resource
|
||||
private XianHiddenDangerSpotsMapper hiddenDangerSpotsMapper;
|
||||
|
||||
@Resource
|
||||
private XianRiskSpotsMapper riskSpotsMapper;
|
||||
|
||||
@Resource
|
||||
private XianInferenceResultFileMapper inferenceResultFileMapper;
|
||||
|
||||
@Resource
|
||||
private AlgorithmClient algorithmClient;
|
||||
|
||||
@Resource
|
||||
private ReportProperties reportProperties;
|
||||
|
||||
@Override
|
||||
public String outputRainReport(Long id) {
|
||||
// 获取模拟数据
|
||||
XianInferenceResult sim = inferenceResultMapper.selectById(id);
|
||||
Map<String, Double> probabilities = JSON.parseObject(sim.getResult(), new TypeReference<>() {
|
||||
});
|
||||
|
||||
// 获取所有隐患点详情
|
||||
List<Long> pointIds = extractPointIds(probabilities.keySet());
|
||||
List<XianHiddenDangerSpots> allSpots = pointIds.isEmpty()
|
||||
? Collections.emptyList()
|
||||
: hiddenDangerSpotsMapper.getPointDetailByIds(pointIds);
|
||||
|
||||
// 按灾害类型分组并构建风险数据
|
||||
List<XianInferenceResultFile> files = inferenceResultFileMapper.selectByInferenceId(id);
|
||||
List<DisasterRiskData> disasterDataList = buildDisasterDataList(allSpots, probabilities, files);
|
||||
|
||||
// 获取降雨概况
|
||||
List<RainfallDistrictSummaryResponseDTO> rainData = fetchRainData();
|
||||
List<String> allDistricts = allDistrictNames(rainData);
|
||||
List<String> top3Districts = top3DistrictNames(rainData);
|
||||
|
||||
// 获取风险统计
|
||||
int riskCount = sumCount(riskSpotsMapper.queryRiskNumberByDistrictName(top3Districts));
|
||||
int hiddenCount = sumCount(hiddenDangerSpotsMapper.queryHiddenDangerNumberByDistrictName(top3Districts));
|
||||
int predictPointNum = sumCount(riskSpotsMapper.queryRiskNumberByDistrictName(allDistricts))
|
||||
+ sumCount(hiddenDangerSpotsMapper.queryHiddenDangerNumberByDistrictName(allDistricts));
|
||||
|
||||
// 构建模板数据模型
|
||||
Map<String, Object> model = buildTemplateModel(sim, disasterDataList, rainData,
|
||||
allDistricts, top3Districts, riskCount, hiddenCount, predictPointNum);
|
||||
|
||||
// 渲染输出
|
||||
String templatePath = reportProperties.getDisasterCausingFactors().getRainfall().getTemplatePath();
|
||||
String outputDir = Path.of(localPath,
|
||||
reportProperties.getDisasterCausingFactors().getRainfall().getOutputPath()
|
||||
.replace("{id}", id.toString())).toString();
|
||||
String outputName = reportProperties.getDisasterCausingFactors().getRainfall().getOutputName()
|
||||
.replace("{currentTime}", String.valueOf(System.currentTimeMillis()));
|
||||
String fullPath = renderReport(templatePath, outputDir, outputName, model);
|
||||
|
||||
// 写入文件记录
|
||||
String baseName = outputName.replaceAll("_\\d+(\\.\\w+)$", "$1");
|
||||
String relativePath = fullPath.substring(localPath.length()).replace("\\", "/").replaceAll("^/+", "");
|
||||
inferenceResultFileMapper.upsert(id, relativePath, baseName);
|
||||
log.info("文件记录已写入: inference_id={}, file_name={}, file_path={}", id, baseName, relativePath);
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 JSONB key 集合中提取隐患点 ID
|
||||
*/
|
||||
private List<Long> extractPointIds(Set<String> keys) {
|
||||
return keys.stream()
|
||||
.map(k -> Long.parseLong(k.split("_")[0]))
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用算法服务获取各区降雨概况
|
||||
*/
|
||||
private List<RainfallDistrictSummaryResponseDTO> fetchRainData() {
|
||||
ApiResponse<List<RainfallDistrictSummaryResponseDTO>> resp = algorithmClient.post(
|
||||
"/rainfall/district-summary",
|
||||
Map.of("inference_id", 1),
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
return resp != null ? resp.getData() : Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部行政区名称(按降雨量降序)
|
||||
*/
|
||||
private List<String> allDistrictNames(List<RainfallDistrictSummaryResponseDTO> rainData) {
|
||||
return rainData.stream()
|
||||
.filter(d -> d.getDistrictName() != null && !d.getDistrictName().isEmpty())
|
||||
.filter(d -> d.getRainfall() != null && !d.getRainfall().isEmpty())
|
||||
.sorted((a, b) -> Double.compare(
|
||||
Double.parseDouble(b.getRainfall()),
|
||||
Double.parseDouble(a.getRainfall())))
|
||||
.map(RainfallDistrictSummaryResponseDTO::getDistrictName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 降雨量前3行政区
|
||||
*/
|
||||
private List<String> top3DistrictNames(List<RainfallDistrictSummaryResponseDTO> rainData) {
|
||||
return rainData.stream()
|
||||
.filter(d -> d.getRainfall() != null && !d.getRainfall().isEmpty())
|
||||
.sorted((a, b) -> Double.compare(
|
||||
Double.parseDouble(b.getRainfall()),
|
||||
Double.parseDouble(a.getRainfall())))
|
||||
.limit(3)
|
||||
.map(RainfallDistrictSummaryResponseDTO::getDistrictName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对DTO列表中的number字段求和
|
||||
*/
|
||||
private int sumCount(List<RiskAndHiddenSpotDTO> list) {
|
||||
return list.stream().mapToInt(d -> Integer.parseInt(d.getNumber())).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将隐患点按disasterType分组,构建各类型的DisasterRiskData
|
||||
*/
|
||||
private List<DisasterRiskData> buildDisasterDataList(
|
||||
List<XianHiddenDangerSpots> allSpots,
|
||||
Map<String, Double> probabilities,
|
||||
List<XianInferenceResultFile> files) {
|
||||
|
||||
// 按灾害类型分组
|
||||
Map<String, List<XianHiddenDangerSpots>> grouped = allSpots.stream()
|
||||
.filter(s -> s.getDisasterType() != null)
|
||||
.collect(Collectors.groupingBy(XianHiddenDangerSpots::getDisasterType, LinkedHashMap::new, Collectors.toList()));
|
||||
|
||||
List<DisasterRiskData> result = new ArrayList<>();
|
||||
for (Map.Entry<String, List<XianHiddenDangerSpots>> entry : grouped.entrySet()) {
|
||||
String disasterType = entry.getKey();
|
||||
String suffix = DISASTER_CONDITION_MAP.get(disasterType);
|
||||
if (suffix == null) continue;
|
||||
|
||||
DisasterRiskData data = new DisasterRiskData()
|
||||
.setDisasterType(disasterType)
|
||||
.setConditionSuffix(suffix)
|
||||
.setInfluenceLower("")
|
||||
.setInfluenceUpper("");
|
||||
|
||||
// 构建风险明细(按概率降序)
|
||||
List<DisasterRiskData.SpotRisk> spots = entry.getValue().stream()
|
||||
.map(spot -> {
|
||||
Double prob = probabilities.get(spot.getId() + "_1");
|
||||
if (prob == null) return null;
|
||||
String level = prob >= 70 ? "高" : "中";
|
||||
return new DisasterRiskData.SpotRisk()
|
||||
.setPosition(spot.getPosition())
|
||||
.setDisasterName(spot.getDisasterName())
|
||||
.setVillage(spot.getVillage())
|
||||
.setProbability(prob)
|
||||
.setRiskLevel(level);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(DisasterRiskData.SpotRisk::getProbability).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 重新编号
|
||||
AtomicInteger reIdx = new AtomicInteger(1);
|
||||
spots.forEach(s -> s.setIndex(reIdx.getAndIncrement()));
|
||||
|
||||
data.setSpots(spots);
|
||||
|
||||
// 中高风险街道(village 去重)
|
||||
data.setHighRiskStreets(spots.stream()
|
||||
.map(DisasterRiskData.SpotRisk::getVillage)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、")));
|
||||
|
||||
// 图片
|
||||
data.setImageUrl(findImage(files, disasterType));
|
||||
|
||||
result.add(data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件列表中匹配图片
|
||||
*/
|
||||
private String findImage(List<XianInferenceResultFile> files, String disasterType) {
|
||||
String expectedName = String.format(IMAGE_NAME_PATTERN, disasterType);
|
||||
return files.stream()
|
||||
.filter(f -> expectedName.equals(f.getFileName()))
|
||||
.findFirst()
|
||||
.map(f -> {
|
||||
String server = reportProperties.getFiles().getServerUrl().replaceAll("/+$", "");
|
||||
String path = f.getFilePath().replace("\\", "/").replaceAll("^/+", "");
|
||||
return server + "/" + path;
|
||||
})
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建模板数据模型
|
||||
*
|
||||
* @param sim 模拟结果
|
||||
* @param disasterDataList 灾害数据
|
||||
* @param rainData 降雨数据
|
||||
* @param allDistricts 所有灾害名称
|
||||
* @param top3Districts top3灾害名称
|
||||
* @param riskCount 灾害数量
|
||||
* @param hiddenCount 隐患数量
|
||||
* @param predictPointNum 预测点数
|
||||
* @return 模板数据模型
|
||||
*/
|
||||
private Map<String, Object> buildTemplateModel(
|
||||
XianInferenceResult sim,
|
||||
List<DisasterRiskData> disasterDataList,
|
||||
List<RainfallDistrictSummaryResponseDTO> rainData,
|
||||
List<String> allDistricts,
|
||||
List<String> top3Districts,
|
||||
int riskCount,
|
||||
int hiddenCount,
|
||||
int predictPointNum) {
|
||||
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
BaseCondition cond = JSON.parseObject(sim.getCondition(), BaseCondition.class);
|
||||
|
||||
// 基础文本
|
||||
m.put("报告时间", DateTimeUtils.datetimeFormat(sim.getOccurredTime(), "MM月dd日HH时mm分"));
|
||||
m.put("降雨时间", DateTimeUtils.datetimeFormat(sim.getOccurredTime(), "yyyy年MM月dd日HH时"));
|
||||
m.put("降雨地区", String.join("、", allDistricts));
|
||||
m.put("持续时间", cond.getDuration() == null ? 24 : cond.getDuration());
|
||||
m.put("降雨量", buildRainfallStr(rainData, allDistricts));
|
||||
m.put("降雨集中区域", String.join("、", top3Districts));
|
||||
m.put("风险区数量", riskCount);
|
||||
m.put("隐患点数量", hiddenCount);
|
||||
m.put("致灾因子", String.join("、", reportProperties.getDisasterCausingFactors().getRainfall().getType()) + "等");
|
||||
m.put("致灾因子数量", reportProperties.getDisasterCausingFactors().getRainfall().getNumber());
|
||||
m.put("预测点数量", predictPointNum);
|
||||
m.put("灾害链", buildDisasterChain("暴雨", disasterDataList));
|
||||
m.put("具体风险地区", buildRiskPosition(disasterDataList));
|
||||
m.put("高风险区域数量", countHighRisk(disasterDataList));
|
||||
m.put("次生灾害类型", buildSecondaryTypes(disasterDataList));
|
||||
|
||||
// 所有灾害类型默认值
|
||||
for (String typeName : DISASTER_CONDITION_MAP.keySet()) {
|
||||
String suffix = DISASTER_CONDITION_MAP.get(typeName);
|
||||
m.put("has" + suffix, false);
|
||||
}
|
||||
|
||||
// 各灾害类型(覆盖有数据的)
|
||||
for (DisasterRiskData data : disasterDataList) {
|
||||
String suffix = data.getConditionSuffix();
|
||||
boolean has = data.hasData();
|
||||
m.put("has" + suffix, has);
|
||||
|
||||
if (has) {
|
||||
m.put(data.getDisasterType() + "中高风险街道", data.getHighRiskStreets());
|
||||
m.put(data.getDisasterType() + "风险表",
|
||||
ReportTableBuilder.buildRiskTable(data.getDisasterType(), data.getSpots()));
|
||||
m.put(data.getDisasterType() + "隐患点人口分布图",
|
||||
downloadImage(data.getImageUrl()));
|
||||
m.put(data.getDisasterType() + "影响人数下限", data.getInfluenceLower());
|
||||
m.put(data.getDisasterType() + "影响人数上限", data.getInfluenceUpper());
|
||||
}
|
||||
}
|
||||
|
||||
// 全局条件
|
||||
boolean hasAny = disasterDataList.stream().anyMatch(DisasterRiskData::hasData);
|
||||
m.put("hasDisasters", hasAny);
|
||||
|
||||
// 应急处置建议
|
||||
m.put("高风险街道", buildAllHighRiskPosition(disasterDataList));
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建降雨量字符串
|
||||
*
|
||||
* @param rainData 降雨数据
|
||||
* @param names 位置
|
||||
* @return 降雨量字符串
|
||||
*/
|
||||
private String buildRainfallStr(List<RainfallDistrictSummaryResponseDTO> rainData, List<String> names) {
|
||||
Map<String, String> map = rainData.stream().collect(Collectors.toMap(
|
||||
RainfallDistrictSummaryResponseDTO::getDistrictName,
|
||||
RainfallDistrictSummaryResponseDTO::getRainfall,
|
||||
(a, b) -> a));
|
||||
return names.stream().map(map::get).filter(Objects::nonNull)
|
||||
.collect(Collectors.joining("mm、")) + "mm";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建灾害链字符串
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 灾害链
|
||||
*/
|
||||
private String buildDisasterChain(String type, List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.map(d -> type + "-" + d.getDisasterType())
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建高风险位置字符串
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 高风险位置
|
||||
*/
|
||||
private String buildRiskPosition(List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.map(DisasterRiskData::getHighRiskStreets)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计高风险数量
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 高风险数量
|
||||
*/
|
||||
private int countHighRisk(List<DisasterRiskData> list) {
|
||||
return (int) list.stream().filter(DisasterRiskData::hasData)
|
||||
.flatMap(d -> d.getSpots().stream())
|
||||
.filter(s -> "高".equals(s.getRiskLevel()))
|
||||
.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建次生灾害类型字符串
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 次生灾害类型
|
||||
*/
|
||||
private String buildSecondaryTypes(List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.map(DisasterRiskData::getDisasterType)
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建所有高风险位置字符串
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 所有高风险位置
|
||||
*/
|
||||
private String buildAllHighRiskPosition(List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.flatMap(d -> d.getSpots().stream())
|
||||
.map(DisasterRiskData.SpotRisk::getVillage)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染报告
|
||||
*
|
||||
* @param templatePath 模板路径
|
||||
* @param outputPath 输出路径
|
||||
* @param outputName 输出文件名
|
||||
* @param model 模板数据
|
||||
* @return 输出文件路径
|
||||
*/
|
||||
private String renderReport(String templatePath, String outputPath, String outputName, Map<String, Object> model) {
|
||||
|
||||
// 注册表格策略
|
||||
Configure config = Configure.builder()
|
||||
.bind("滑坡风险表", new TableRenderPolicy())
|
||||
.bind("泥石流风险表", new TableRenderPolicy())
|
||||
.bind("山洪风险表", new TableRenderPolicy())
|
||||
.bind("内涝风险表", new TableRenderPolicy())
|
||||
.bind("崩塌风险表", new TableRenderPolicy())
|
||||
.build();
|
||||
|
||||
try {
|
||||
Path dir = Path.of(outputPath);
|
||||
if (!Files.exists(dir)) Files.createDirectories(dir);
|
||||
Path output = dir.resolve(outputName);
|
||||
|
||||
XWPFTemplate template = XWPFTemplate.compile(Path.of(templatePath).toFile(), config)
|
||||
.render(model);
|
||||
try (FileOutputStream fos = new FileOutputStream(output.toFile())) {
|
||||
template.write(fos);
|
||||
}
|
||||
template.close();
|
||||
|
||||
log.info("报告生成成功: {}", Path.of(outputPath, outputName).toString());
|
||||
return output.toString();
|
||||
} catch (Exception e) {
|
||||
log.error("生成暴雨报告失败", e);
|
||||
throw new RuntimeException("生成暴雨报告失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 URL 下载图片
|
||||
*/
|
||||
private PictureRenderData downloadImage(String imageUrl) {
|
||||
if (imageUrl == null || imageUrl.isEmpty()) return null;
|
||||
try {
|
||||
return Pictures.ofUrl(imageUrl).size(500, 375).create();
|
||||
} catch (Exception e) {
|
||||
log.warn("下载图片失败, 使用占位: {}", imageUrl, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import com.gis.xian.mapper.*;
|
||||
import com.gis.xian.vo.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import com.gis.xian.config.BasePointsRedisProperties;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -13,6 +14,7 @@ import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
@@ -57,53 +59,12 @@ public class InitializeData {
|
||||
@Resource
|
||||
RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${init.data.base-points.hidden-danger.all}")
|
||||
private String allBasePointsKey;
|
||||
@Resource
|
||||
@Qualifier("xianPool")
|
||||
private Executor xianPoolExecutor;
|
||||
|
||||
@Value("${init.data.base-points.hidden-danger.landslide}")
|
||||
private String landslideKey;
|
||||
|
||||
@Value("${init.data.base-points.hidden-danger.collapse}")
|
||||
private String collapseKey;
|
||||
|
||||
@Value("${init.data.base-points.hidden-danger.debris-flow}")
|
||||
private String debrisFlowKey;
|
||||
|
||||
@Value("${init.data.base-points.hidden-danger.flash-flood}")
|
||||
private String flashFloodKey;
|
||||
|
||||
@Value("${init.data.base-points.hidden-danger.water-logging}")
|
||||
private String waterLoggingKey;
|
||||
|
||||
@Value("${init.data.base-points.risk}")
|
||||
private String riskBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.hospitals}")
|
||||
private String hospitalsBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.dangerous-source}")
|
||||
private String dangerousSourceBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.emergency-shelter}")
|
||||
private String emergencyShelterBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.firefighter}")
|
||||
private String firefighterBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.store-points}")
|
||||
private String storePointsBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.school}")
|
||||
private String schoolBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.bridge}")
|
||||
private String bridgeBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.reservoir}")
|
||||
private String reservoirBasePointsKey;
|
||||
|
||||
@Value("${init.data.base-points.subway}")
|
||||
private String subwayBasePointsKey;
|
||||
@Resource
|
||||
private BasePointsRedisProperties basePoints;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
@Async("xianPool")
|
||||
@@ -113,153 +74,154 @@ public class InitializeData {
|
||||
|
||||
// 隐患点 - 总体
|
||||
CompletableFuture<Void> allFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(allBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHiddenDanger().getAll(), JSON.toJSONString(
|
||||
XianHiddenDangerSpotsBasePointVo.entity2Vo(
|
||||
xianHiddenDangerSpotsMapper.getBasePoints(null))
|
||||
)
|
||||
);
|
||||
log.info("加载隐患点信息(总体)并写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
// 隐患点 - 滑坡
|
||||
CompletableFuture<Void> landslideFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(landslideKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHiddenDanger().getLandslide(), JSON.toJSONString(
|
||||
XianHiddenDangerSpotsBasePointVo.entity2Vo(
|
||||
xianHiddenDangerSpotsMapper.getBasePoints("landslide"))
|
||||
)
|
||||
);
|
||||
log.info("加载隐患点信息(滑坡)并写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
// 隐患点 - 崩塌
|
||||
CompletableFuture<Void> collapseFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(collapseKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHiddenDanger().getCollapse(), JSON.toJSONString(
|
||||
XianHiddenDangerSpotsBasePointVo.entity2Vo(
|
||||
xianHiddenDangerSpotsMapper.getBasePoints("collapse"))
|
||||
)
|
||||
);
|
||||
log.info("加载隐患点信息(崩塌)并写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
|
||||
// 隐患点 - 泥石流
|
||||
CompletableFuture<Void> debrisFlowFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(debrisFlowKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHiddenDanger().getDebrisFlow(), JSON.toJSONString(
|
||||
XianHiddenDangerSpotsBasePointVo.entity2Vo(
|
||||
xianHiddenDangerSpotsMapper.getBasePoints("debris_flow"))
|
||||
)
|
||||
);
|
||||
log.info("加载隐患点信息(泥石流)并写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
// 隐患点 - 山洪
|
||||
CompletableFuture<Void> flashFloodFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(flashFloodKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHiddenDanger().getFlashFlood(), JSON.toJSONString(
|
||||
XianHiddenDangerSpotsBasePointVo.entity2Vo(
|
||||
xianHiddenDangerSpotsMapper.getBasePoints("flash_flood"))
|
||||
)
|
||||
);
|
||||
log.info("加载隐患点信息(山洪)并写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
// 隐患点 - 内涝
|
||||
CompletableFuture<Void> waterLoggingFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(waterLoggingKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHiddenDanger().getWaterLogging(), JSON.toJSONString(
|
||||
XianHiddenDangerSpotsBasePointVo.entity2Vo(
|
||||
xianHiddenDangerSpotsMapper.getBasePoints("water_logging"))
|
||||
)
|
||||
);
|
||||
log.info("加载隐患点信息(内涝)并写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> riskFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(riskBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getRisk(), JSON.toJSONString(
|
||||
XianRiskSpotsBasePointVo.entity2Vo(
|
||||
xianRiskSpotsMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载风险点基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> hospitalsFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(hospitalsBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getHospitals(), JSON.toJSONString(
|
||||
XianHospitalsBasePointVo.entity2Vo(
|
||||
xianHospitalsMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载医院基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> dangerousSourceFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(dangerousSourceBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getDangerousSource(), JSON.toJSONString(
|
||||
XianDangerousSourceBasePointVo.entity2Vo(
|
||||
xianDangerousSourceMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载危险源基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> emergencyShelterFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(emergencyShelterBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getEmergencyShelter(), JSON.toJSONString(
|
||||
XianEmergencyShelterBasePointVo.entity2Vo(
|
||||
xianEmergencyShelterMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载应急避难所基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> firefighterFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(firefighterBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getFirefighter(), JSON.toJSONString(
|
||||
XianFirefighterBasePointVo.entity2Vo(
|
||||
xianFirefighterMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载消防站基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> storePointsFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(storePointsBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getStorePoints(), JSON.toJSONString(
|
||||
XianStorePointsBasePointVo.entity2Vo(
|
||||
xianStorePointsMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载物资储备点基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> schoolFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(schoolBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getSchool(), JSON.toJSONString(
|
||||
XianSchoolBasePointVo.entity2Vo(
|
||||
xianSchoolMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载学校基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> bridgeFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(bridgeBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getBridge(), JSON.toJSONString(
|
||||
XianBridgeBasePointVo.entity2Vo(
|
||||
xianBridgeMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载桥梁基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> reservoirFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(reservoirBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getReservoir(), JSON.toJSONString(
|
||||
XianReservoirListBasePointVo.entity2Vo(
|
||||
xianReservoirListMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载水库基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
CompletableFuture<Void> subwayFuture = CompletableFuture.runAsync(() -> {
|
||||
redisTemplate.opsForValue().set(subwayBasePointsKey, JSON.toJSONString(
|
||||
redisTemplate.opsForValue().set(basePoints.getSubway(), JSON.toJSONString(
|
||||
XianSubwayStationsBasePointVo.entity2Vo(
|
||||
xianSubwayStationsMapper.getBasePoints())
|
||||
)
|
||||
);
|
||||
log.info("加载地铁站点基本信息写入redis完成");
|
||||
});
|
||||
}, xianPoolExecutor);
|
||||
|
||||
// 等待所有任务完成
|
||||
CompletableFuture.allOf(
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.gis.xian.utils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class DateTimeUtils {
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*
|
||||
* @param datetime 日期时间
|
||||
* @param format 格式
|
||||
* @return 格式化后的日期时间
|
||||
*/
|
||||
public static String datetimeFormat(LocalDateTime datetime, String format) {
|
||||
return datetime.format(DateTimeFormatter.ofPattern(format));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gis.xian.utils;
|
||||
|
||||
import com.deepoove.poi.data.RowRenderData;
|
||||
import com.deepoove.poi.data.Rows;
|
||||
import com.deepoove.poi.data.TableRenderData;
|
||||
import com.deepoove.poi.data.Tables;
|
||||
import com.deepoove.poi.data.style.TableStyle;
|
||||
import com.gis.xian.dto.DisasterRiskData;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* poi-tl 报告表格构建工具
|
||||
*/
|
||||
public class ReportTableBuilder {
|
||||
|
||||
private static final DecimalFormat PCT_FMT = new DecimalFormat("0.00");
|
||||
/**
|
||||
* 小四号 = 12pt = 24 half-pts
|
||||
*/
|
||||
private static final int XIAO_SI_FONT = 10;
|
||||
/**
|
||||
* 列宽(twips):序号窄 / 位置最宽 / 概率中等 / 等级中等
|
||||
*/
|
||||
private static final int[] COL_WIDTHS = {700, 5000, 1500, 1500};
|
||||
/**
|
||||
* 表格总宽 = 列宽之和
|
||||
*/
|
||||
private static final String TABLE_WIDTH = "8700";
|
||||
|
||||
/**
|
||||
* 构建灾害风险表(四列:序号、位置、发生概率、灾害等级)
|
||||
*/
|
||||
public static TableRenderData buildRiskTable(String disasterType, List<DisasterRiskData.SpotRisk> spots) {
|
||||
// 山洪第二列用灾害点名称,其他用位置
|
||||
boolean useDisasterName = "山洪".equals(disasterType);
|
||||
String col2Header = useDisasterName ? "灾害名称" : "位置";
|
||||
|
||||
RowRenderData header = Rows.of(
|
||||
"序号",
|
||||
col2Header,
|
||||
disasterType + "发生概率",
|
||||
"灾害等级"
|
||||
).textFontFamily("黑体").textFontSize(XIAO_SI_FONT).textBold().center().verticalCenter().create();
|
||||
|
||||
Tables.TableBuilder builder = Tables.of(header);
|
||||
AtomicInteger idx = new AtomicInteger(1);
|
||||
for (DisasterRiskData.SpotRisk spot : spots) {
|
||||
String col2 = useDisasterName ? spot.getDisasterName() : spot.getPosition();
|
||||
builder.addRow(Rows.of(
|
||||
String.valueOf(idx.getAndIncrement()),
|
||||
col2,
|
||||
PCT_FMT.format(spot.getProbability()) + "%",
|
||||
spot.getRiskLevel()
|
||||
).textFontFamily("仿宋_GB2312").textFontSize(XIAO_SI_FONT).center().verticalCenter().create());
|
||||
}
|
||||
|
||||
TableRenderData table = builder.create();
|
||||
TableStyle style = new TableStyle();
|
||||
style.setWidth(TABLE_WIDTH);
|
||||
style.setColWidths(COL_WIDTHS);
|
||||
table.setTableStyle(style);
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
# 开发环境配置
|
||||
spring:
|
||||
config:
|
||||
import: classpath:config/database/application-database-dev.yml
|
||||
import:
|
||||
- classpath:config/database/application-database-dev.yml
|
||||
- classpath:config/customize/application-customize-dev.yml
|
||||
|
||||
|
||||
# redis
|
||||
data:
|
||||
@@ -15,7 +14,6 @@ spring:
|
||||
password: zhangsan
|
||||
database: 0
|
||||
connect-timeout: 3000ms
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
@@ -43,15 +41,13 @@ safety:
|
||||
- /druid
|
||||
- /websocket/info
|
||||
- /websocket/**
|
||||
- /report/output/**
|
||||
|
||||
# 请求无需解密的路径
|
||||
no-decrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /druid
|
||||
- /websocket/info
|
||||
- /websocket/**
|
||||
- /report/output/**
|
||||
|
||||
# 算法服务器配置
|
||||
algorithm:
|
||||
server:
|
||||
# 开发环境算法服务器地址
|
||||
url: http://localhost:8082
|
||||
@@ -1,11 +1,9 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
# 生产环境配置
|
||||
spring:
|
||||
config:
|
||||
import: classpath:config/database/application-database-prod.yml
|
||||
import:
|
||||
- classpath:config/database/application-database-prod.yml
|
||||
- classpath:config/customize/application-customize-prod.yml
|
||||
|
||||
# redis
|
||||
data:
|
||||
@@ -46,9 +44,3 @@ safety:
|
||||
no-decrypt-paths:
|
||||
- /crypto/sm2/public-key
|
||||
- /websocket/**
|
||||
|
||||
# 算法服务器配置
|
||||
algorithm:
|
||||
server:
|
||||
# 生产环境算法服务器地址
|
||||
url: http://localhost:8081
|
||||
@@ -5,9 +5,35 @@ spring:
|
||||
config:
|
||||
import: classpath:config/redis/redis-key.yml
|
||||
|
||||
# MyBatis 配置
|
||||
mybatis:
|
||||
# MyBatis-plus 配置
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:com/gis/xian/mapper/*.xml
|
||||
type-aliases-package: com.gis.xian.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
# 添加 TypeHandler 扫描包路径
|
||||
type-handlers-package: com.gis.xian.handler
|
||||
|
||||
# 报告配置
|
||||
report:
|
||||
files:
|
||||
server-url: ${files.server.url:http://localhost:8082}
|
||||
disaster-causing-factors:
|
||||
rainfall:
|
||||
type: [ "降雨强度", "持续时间", "累计降雨量", "高程", "坡度", "坡向", "土壤分类", "岩性", "土地利用类型", "不透水率", "植被覆盖指数", "距河道距离", "管网密度" ]
|
||||
number: 19
|
||||
template-path: "src/main/resources/template/rainfall/rainfall-template.docx"
|
||||
output-path: "/xian/report/rainfall/{id}"
|
||||
output-name: "暴雨应急预评估报告_{currentTime}.docx"
|
||||
earthquake:
|
||||
type: [ "震度", "震源深度", "距离震中位置", "高程", "坡度", "坡向", "土壤分类", "岩性", "土地利用类型", "植被覆盖指数", "距离断裂带距离" ]
|
||||
number: 17
|
||||
template-path: "src/main/resources/template/earthquake/earthquake-template.docx"
|
||||
output-path: "/xian/report/earthquake/{id}"
|
||||
output-name: "地震应急预评估报告_{currentTime}.docx"
|
||||
disaster-types:
|
||||
landslide: 滑坡
|
||||
debrisFlow: 泥石流
|
||||
torrentialFlood: 山洪
|
||||
waterLogging: 内涝
|
||||
collapse: 崩塌
|
||||
@@ -5,24 +5,29 @@
|
||||
<mapper namespace="com.gis.xian.mapper.XianHiddenDangerSpotsMapper">
|
||||
|
||||
<resultMap id="XianHiddenDangerSpotsResultMap" type="com.gis.xian.entity.XianHiddenDangerSpots">
|
||||
<id property="id" column="id" />
|
||||
<result property="fieldCode" column="field_code" />
|
||||
<result property="province" column="province" />
|
||||
<result property="provinceId" column="province_id" />
|
||||
<result property="city" column="city" />
|
||||
<result property="cityId" column="city_id" />
|
||||
<result property="county" column="county" />
|
||||
<result property="countyId" column="county_id" />
|
||||
<result property="village" column="village" />
|
||||
<result property="disasterName" column="disaster_name" />
|
||||
<result property="lon" column="lon" />
|
||||
<result property="lat" column="lat" />
|
||||
<result property="geom" column="geom" />
|
||||
<result property="position" column="position" />
|
||||
<result property="disasterType" column="disaster_type" />
|
||||
<result property="scaleGrade" column="scale_grade" />
|
||||
<result property="riskGrade" column="risk_grade" />
|
||||
<result property="isDelete" column="is_delete" />
|
||||
<id property="id" column="id"/>
|
||||
<result property="fieldCode" column="field_code"/>
|
||||
<result property="province" column="province"/>
|
||||
<result property="provinceId" column="province_id"/>
|
||||
<result property="city" column="city"/>
|
||||
<result property="cityId" column="city_id"/>
|
||||
<result property="county" column="county"/>
|
||||
<result property="countyId" column="county_id"/>
|
||||
<result property="village" column="village"/>
|
||||
<result property="disasterName" column="disaster_name"/>
|
||||
<result property="lon" column="lon"/>
|
||||
<result property="lat" column="lat"/>
|
||||
<result property="geom" column="geom"/>
|
||||
<result property="position" column="position"/>
|
||||
<result property="disasterType" column="disaster_type"/>
|
||||
<result property="scaleGrade" column="scale_grade"/>
|
||||
<result property="riskGrade" column="risk_grade"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="RiskAndHiddenSpotDTOMap" type="com.gis.xian.dto.RiskAndHiddenSpotDTO">
|
||||
<result property="districtName" column="county"/>
|
||||
<result property="number" column="number"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 获取所有基础点:滑坡、崩塌、泥石流、山洪、内涝 -->
|
||||
@@ -55,10 +60,42 @@
|
||||
|
||||
<!-- 根据id获取隐患点详情 -->
|
||||
<select id="getPointDetailById" resultMap="XianHiddenDangerSpotsResultMap">
|
||||
SELECT id, field_code, disaster_name, position, disaster_type, scale_grade, risk_grade FROM xian_hidden_danger_spots
|
||||
SELECT id, field_code, disaster_name, position, disaster_type, scale_grade, risk_grade FROM
|
||||
xian_hidden_danger_spots
|
||||
<where>
|
||||
id = #{id}
|
||||
AND is_delete = 0
|
||||
</where>
|
||||
AND is_delete = 0
|
||||
</select>
|
||||
|
||||
<select id="queryHiddenDangerNumberByDistrictName" resultMap="RiskAndHiddenSpotDTOMap">
|
||||
SELECT county, COUNT(*) AS number FROM xian_hidden_danger_spots
|
||||
<where>
|
||||
is_delete = 0
|
||||
<if test="districtNames != null and districtNames.size() > 0">
|
||||
AND county IN(
|
||||
<foreach collection="districtNames" item="districtName" separator=",">
|
||||
#{districtName}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
GROUP BY county
|
||||
</select>
|
||||
|
||||
<select id="getPointDetailByIds" resultMap="XianHiddenDangerSpotsResultMap">
|
||||
SELECT id, disaster_name, disaster_type, village, position, risk_grade FROM
|
||||
xian_hidden_danger_spots
|
||||
<where>
|
||||
<if test="ids != null and ids.size() > 0">
|
||||
id IN(
|
||||
<foreach collection="ids" item="id" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
AND is_delete = 0
|
||||
</where>
|
||||
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gis.xian.mapper.XianInferenceResultFileMapper">
|
||||
|
||||
<resultMap id="XianInferenceResultFileResultMap" type="com.gis.xian.entity.XianInferenceResultFile">
|
||||
<id property="id" column="id"/>
|
||||
<result property="inferenceId" column="inference_id"/>
|
||||
<result property="filePath" column="file_path"/>
|
||||
<result property="fileName" column="file_name"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByInferenceId" resultMap="XianInferenceResultFileResultMap">
|
||||
SELECT file_path, file_name
|
||||
FROM xian_inference_result_file
|
||||
WHERE inference_id = #{inferenceId}
|
||||
AND is_delete = 0
|
||||
ORDER BY create_time
|
||||
</select>
|
||||
|
||||
<insert id="upsert">
|
||||
INSERT INTO xian_inference_result_file (inference_id, file_path, file_name, create_time, is_delete)
|
||||
VALUES (#{inferenceId}, #{filePath}, #{fileName}, NOW(), 0)
|
||||
ON CONFLICT (inference_id, file_name)
|
||||
DO UPDATE SET file_path = #{filePath}, create_time = NOW()
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
@@ -5,16 +5,15 @@
|
||||
<mapper namespace="com.gis.xian.mapper.XianInferenceResultMapper">
|
||||
|
||||
<resultMap id="XianInferenceResultResultMap" type="com.gis.xian.entity.XianInferenceResult">
|
||||
<id property="id" column="id" />
|
||||
<result property="name" column="name" />
|
||||
<result property="eventType" column="event_type" />
|
||||
<result property="occurredTime" column="occurred_time" />
|
||||
<result property="operationType" column="operation_type" />
|
||||
<result property="result" column="result" />
|
||||
<result property="condition" column="condition" />
|
||||
<result property="filePath" column="file_path" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="isDelete" column="is_delete" />
|
||||
<id property="id" column="id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="eventType" column="event_type"/>
|
||||
<result property="occurredTime" column="occurred_time"/>
|
||||
<result property="operationType" column="operation_type"/>
|
||||
<result property="result" column="result"/>
|
||||
<result property="condition" column="condition"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据id和pointId获取概率 -->
|
||||
@@ -25,4 +24,28 @@
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="XianInferenceResultResultMap">
|
||||
SELECT
|
||||
id, name, event_type, occurred_time, operation_type,
|
||||
(
|
||||
SELECT jsonb_object_agg(key, value)
|
||||
FROM jsonb_each(result)
|
||||
WHERE value::numeric >= 50
|
||||
AND key LIKE '%\_1' ESCAPE '\'
|
||||
) AS result
|
||||
, condition
|
||||
FROM xian_inference_result
|
||||
<where>
|
||||
id = #{id}
|
||||
AND is_delete = 0
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="getTheNumberOfPredictedPoints" resultType="java.lang.Integer">
|
||||
SELECT COUNT(*) FROM xian_inference_result
|
||||
<where>
|
||||
is_delete = 0
|
||||
</where>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gis.xian.mapper.XianMeteorologyMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.gis.xian.entity.XianMeteorology">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="stationName" column="station_name" jdbcType="VARCHAR"/>
|
||||
<result property="visibility" column="visibility" jdbcType="DOUBLE"/>
|
||||
<result property="areaCode" column="area_code" jdbcType="VARCHAR"/>
|
||||
<result property="maxWindSpeed" column="max_wind_speed" jdbcType="DOUBLE"/>
|
||||
<result property="rainfall1h" column="rainfall_1h" jdbcType="DOUBLE"/>
|
||||
<result property="pressure" column="pressure" jdbcType="DOUBLE"/>
|
||||
<result property="maxWindDirection" column="max_wind_direction" jdbcType="DOUBLE"/>
|
||||
<result property="instMaxWindDirection" column="inst_max_wind_direction" jdbcType="DOUBLE"/>
|
||||
<result property="stationId" column="station_id" jdbcType="VARCHAR"/>
|
||||
<result property="temperature" column="temperature" jdbcType="DOUBLE"/>
|
||||
<result property="relativeHumidity" column="relative_humidity" jdbcType="DOUBLE"/>
|
||||
<result property="datetime" column="datetime" jdbcType="BIGINT"/>
|
||||
<result property="lon" column="lon" jdbcType="DOUBLE"/>
|
||||
<result property="lat" column="lat" jdbcType="DOUBLE"/>
|
||||
<result property="geom" column="geom" jdbcType="OTHER"/>
|
||||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||||
<result property="isDelete" column="is_delete" jdbcType="SMALLINT"/>
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
@@ -5,28 +5,33 @@
|
||||
<mapper namespace="com.gis.xian.mapper.XianRiskSpotsMapper">
|
||||
|
||||
<resultMap id="XianRiskSpotsResultMap" type="com.gis.xian.entity.XianRiskSpots">
|
||||
<id property="id" column="id" />
|
||||
<result property="riskName" column="risk_name" />
|
||||
<result property="unitCode" column="unit_code" />
|
||||
<result property="riskLevel" column="risk_level" />
|
||||
<result property="area" column="area" />
|
||||
<result property="province" column="province" />
|
||||
<result property="city" column="city" />
|
||||
<result property="county" column="county" />
|
||||
<result property="country" column="country" />
|
||||
<result property="village" column="village" />
|
||||
<result property="position" column="position" />
|
||||
<result property="residentCounts" column="resident_counts" />
|
||||
<result property="addressPopulation" column="address_population" />
|
||||
<result property="riskProperty" column="risk_property" />
|
||||
<result property="permanentPopulation" column="permanent_population" />
|
||||
<result property="housing" column="housing" />
|
||||
<result property="inspectorName" column="inspector_name" />
|
||||
<result property="inspectorTele" column="inspector_tele" />
|
||||
<result property="lon" column="lon" />
|
||||
<result property="lat" column="lat" />
|
||||
<result property="geom" column="geom" />
|
||||
<result property="isDelete" column="is_delete" />
|
||||
<id property="id" column="id"/>
|
||||
<result property="riskName" column="risk_name"/>
|
||||
<result property="unitCode" column="unit_code"/>
|
||||
<result property="riskLevel" column="risk_level"/>
|
||||
<result property="area" column="area"/>
|
||||
<result property="province" column="province"/>
|
||||
<result property="city" column="city"/>
|
||||
<result property="county" column="county"/>
|
||||
<result property="country" column="country"/>
|
||||
<result property="village" column="village"/>
|
||||
<result property="position" column="position"/>
|
||||
<result property="residentCounts" column="resident_counts"/>
|
||||
<result property="addressPopulation" column="address_population"/>
|
||||
<result property="riskProperty" column="risk_property"/>
|
||||
<result property="permanentPopulation" column="permanent_population"/>
|
||||
<result property="housing" column="housing"/>
|
||||
<result property="inspectorName" column="inspector_name"/>
|
||||
<result property="inspectorTele" column="inspector_tele"/>
|
||||
<result property="lon" column="lon"/>
|
||||
<result property="lat" column="lat"/>
|
||||
<result property="geom" column="geom"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="RiskAndHiddenSpotDTOMap" type="com.gis.xian.dto.RiskAndHiddenSpotDTO">
|
||||
<result property="districtName" column="county"/>
|
||||
<result property="number" column="number"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 获取所有风险点基础信息 -->
|
||||
@@ -39,11 +44,26 @@
|
||||
|
||||
<!-- 根据id获取风险点信息 -->
|
||||
<select id="getPointDetailById" resultMap="XianRiskSpotsResultMap">
|
||||
SELECT id, risk_name, unit_code, position, resident_counts, risk_property, permanent_population, housing, inspector_name, inspector_tele, lon, lat FROM xian_risk_spots
|
||||
SELECT id, risk_name, unit_code, position, resident_counts, risk_property, permanent_population, housing,
|
||||
inspector_name, inspector_tele, lon, lat FROM xian_risk_spots
|
||||
<where>
|
||||
id = #{id} AND is_delete = 0
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="queryRiskNumberByDistrictName" resultMap="RiskAndHiddenSpotDTOMap">
|
||||
SELECT county, COUNT(*) AS number FROM xian_risk_spots
|
||||
<where>
|
||||
is_delete = 0
|
||||
<if test="districtNames != null and districtNames.size() > 0">
|
||||
AND county IN(
|
||||
<foreach collection="districtNames" item="districtName" separator=",">
|
||||
#{districtName}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
GROUP BY county
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
algorithm:
|
||||
server:
|
||||
url: "http://localhost:8082"
|
||||
connect-timeout: 5
|
||||
read-timeout: 120
|
||||
|
||||
# 文件配置
|
||||
files:
|
||||
server:
|
||||
url: "http://localhost:8083"
|
||||
local:
|
||||
path: "G:/files"
|
||||
@@ -0,0 +1,17 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
# 算法服务器配置
|
||||
algorithm:
|
||||
server:
|
||||
url: "http://localhost:8081"
|
||||
connect-timeout: 5
|
||||
read-timeout: 120
|
||||
|
||||
# 文件配置
|
||||
files:
|
||||
server:
|
||||
url: "http://localhost:8082"
|
||||
local:
|
||||
path: "/data/files"
|
||||
@@ -36,3 +36,9 @@ spring:
|
||||
username: postgres
|
||||
password: zhangsan
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
slave1:
|
||||
url: jdbc:postgresql://47.92.216.173:7654/yjzyk_xian?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&postgis=true
|
||||
username: postgres
|
||||
password: zhangsan
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
@@ -33,3 +33,9 @@ spring:
|
||||
username: zaihailian
|
||||
password: XAYJ@gis2603
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
slave1:
|
||||
url: jdbc:postgresql://47.92.216.173:7654/yjzyk_xian?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&postgis=true
|
||||
username: postgres
|
||||
password: zhangsan
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user