1.完成天气预报计算任务执行接口

2.修改系统全局form-data请求编码格式
3.完成输运模拟贡献分析功能
This commit is contained in:
panbaolin 2025-11-24 11:55:22 +08:00
parent 259604d42a
commit 27e24880b8
31 changed files with 1527 additions and 66 deletions

View File

@ -270,7 +270,7 @@ public interface CommonConstant {
/**
* 报表允许设计开发的角色
*/
public static String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
/**
* 缓存用户最后一次收到消息通知的时间 KEY
@ -286,4 +286,14 @@ public interface CommonConstant {
* 缓存所有核设施key
*/
String ALL_NUCLEARFACILITY = "nuclearfacility";
/**
* 输运模拟贡献分析数据KEY
*/
String TRANSPORT_CONTRIBUTION_ANALYSIS = "transport:contribution_analysis:";
/**
* 输运模拟时序分析数据KEY
*/
String TRANSPORT_TIMING_ANALYSIS = "transport:timing_analysis:";
}

View File

@ -8,7 +8,7 @@ public enum TransportTaskStatusEnum {
/**
* 执行失败
*/
ERROR(-1),
FAILURE(-1),
/**
* 未开始
*/

View File

@ -0,0 +1,31 @@
package org.jeecg.common.constant.enums;
public enum TransportTimingAnalysisEnum {
/**
* 3小时
*/
THREE_HOURS(3),
/**
* 6小时
*/
SIX_HOURS(-1),
/**
* 12小时
*/
TWELVE_HOURS(-1),
/**
* 24小时
*/
TWENTY_FOUR_HOURS(-1);
private Integer key;
TransportTimingAnalysisEnum(Integer key) {
this.key = key;
}
public Integer getKey(){
return this.key;
}
}

View File

@ -0,0 +1,22 @@
package org.jeecg.common.constant.enums;
/**
* 盘古和Graphcast气象预测模型使用的数据源
*/
public enum WeatherForecastDatasourceEnum {
CDS(1),
LOCATION_FILE(2);
private Integer key;
WeatherForecastDatasourceEnum(Integer key) {
this.key = key;
}
public Integer getKey(){
return this.key;
}
}

View File

@ -1,26 +0,0 @@
package org.jeecg.common.constant.enums;
/**
* 预测模型说明枚举
*/
public enum WeatherModelEnum {
/**
* 盘古模型
*/
PANGU(1),
/**
* Graphcast
*/
GRAPHCAST(2);
private Integer key;
WeatherModelEnum(Integer key) {
this.key = key;
}
public Integer getKey(){
return this.key;
}
}

View File

@ -5,6 +5,10 @@ package org.jeecg.common.constant.enums;
*/
public enum WeatherTaskStatusEnum {
/**
* 执行失败
*/
FAILURE(-1),
/**
* 未开始
*/

View File

@ -53,4 +53,19 @@ public class SystemStorageProperties {
* CMAQ数据存储路径
*/
private String cmaqPath;
/**
* ai-models 安装地址
*/
private String aiModelsPath;
/**
* 盘古模型执行路径
*/
private String panguModelExecPath;
/**
* graphcast模型执行路径
*/
private String graphcastModelExecPath;
}

View File

@ -23,6 +23,7 @@ import java.io.IOException;
public class CopyTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
// 以下为undertow定制代码如切换其它servlet容器需要同步更换
HttpServletRequestImpl undertowRequest = (HttpServletRequestImpl) request;
String token = request.getHeader("Authorization");

View File

@ -51,7 +51,7 @@ public class WeatherData implements Serializable {
private LocalDateTime dataStartTime;
/**
* 数据来源1-盘古模型2-graphcast3-cra404-ncep,5-t1h,6-fnl
* 数据来源1-盘古模型2-graphcast3-cra404-ncep,5-fnl,6-t1h
*/
@TableField(value = "data_source")
private Integer dataSource;
@ -62,12 +62,6 @@ public class WeatherData implements Serializable {
@TableField(value = "file_path")
private String filePath;
/**
* 创建人
*/
@TableField(value = "create_by")
private String createBy;
/**
* 创建时间
*/

View File

@ -1,23 +1,37 @@
package org.jeecg.modules.base.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 com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import org.jeecg.common.system.base.entity.BaseEntity;
import org.jeecg.common.validgroup.InsertGroup;
import org.jeecg.common.validgroup.UpdateGroup;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.util.Date;
/**
* 天气预测任务表
*/
@Data
@TableName("stas_weather_task")
public class WeatherTask extends BaseEntity {
public class WeatherTask{
/**
* ID
*/
@Null(message = "ID必须为空",groups = { InsertGroup.class})
@NotNull(message = "ID不能为空",groups = { UpdateGroup.class})
@TableId(type = IdType.INPUT)
@Schema(description = "ID")
private java.lang.String id;
/**
* 任务名称
@ -27,18 +41,18 @@ public class WeatherTask extends BaseEntity {
private String taskName;
/**
* 任务状态0-未开始1-运行中2-已完成
* 任务状态-1失败0-未开始1-运行中2-已完成
*/
@Null(message = "任务状态必须为空",groups = {InsertGroup.class, UpdateGroup.class})
@TableField(value = "task_status")
private Integer taskStatus;
/**
* 耗时小时默认0
* 耗时分钟默认0
*/
@Null(message = "耗时参数必须为空",groups = {InsertGroup.class, UpdateGroup.class})
@TableField(value = "time_consuming")
private Integer timeConsuming;
private Double timeConsuming;
/**
* 预测模型1-盘古2-Graphcast
@ -79,7 +93,38 @@ public class WeatherTask extends BaseEntity {
/**
* 如果data_sources为2则存储本地文件路径
*/
@Null(message = "预测文件本地路径必须为空",groups = {InsertGroup.class, UpdateGroup.class})
@TableField(value = "input_file")
private String inputFile;
/**
* 预测文件
*/
@TableField(exist = false)
private MultipartFile file;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@ -7,9 +7,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 天气预测任务日志表
@ -41,6 +40,6 @@ public class WeatherTaskLog implements Serializable {
* 创建时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
private Date createTime;
}

View File

@ -25,7 +25,7 @@ public class StationDataController {
return Result.OK(stationDataService.getAllStations());
}
@AutoLog(value = "查询所有台站")
@AutoLog(value = "查询所有设施")
@Operation(summary = "查询所有设施")
@GetMapping("getAllNuclearfacility")
public Result<?> getAllNuclearfacility(){

View File

@ -1,6 +1,7 @@
package org.jeecg.controller;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.jeecg.common.api.vo.Result;
@ -43,6 +44,40 @@ public class TransportResultDataController {
return Result.OK(transportResultDataService.getConformityAnalysis(taskId,stationId,nuclideName,facilityName));
}
@AutoLog(value = "查询贡献分析数据")
@Operation(summary = "查询贡献分析数据")
@GetMapping("getContributionAnalysis")
public Result<?> getContributionAnalysis(@NotNull(message = "任务id不能为空") Integer taskId,
@NotBlank(message = "台站编码不能为空") String stationCode) {
return Result.OK(transportResultDataService.getContributionAnalysis(taskId,stationCode));
}
@AutoLog(value = "处理贡献分析数据-测试接口")
@Operation(summary = "处理贡献分析数据-测试接口")
@GetMapping("handleContributionAnalysis")
public Result<?> handleContributionAnalysis(@NotNull(message = "任务id不能为空") Integer taskId) {
transportResultDataService.handleContributionAnalysis(taskId);
return Result.OK();
}
@AutoLog(value = "查询反演核设施时序数据接口")
@Operation(summary = "查询反演核设施时序数据接口")
@GetMapping("getTimingAnalysis")
public Result<?> getTimingAnalysis(@NotNull(message = "任务id不能为空") Integer taskId,
@NotBlank(message = "台站编码不能为空") String stationCode,
@NotBlank(message = "核设施名称不能为空") String facilityName,
@NotNull(message = "时间序号不能为空") Integer timeNum) {
return Result.OK(transportResultDataService.getTimingAnalysis(taskId,stationCode,facilityName,timeNum));
}
@AutoLog(value = "处理反演核设施时序数据-测试接口")
@Operation(summary = "处理反演核设施时序数据-测试接口")
@GetMapping("handleTimingAnalysis")
public Result<?> handleTimingAnalysis(@NotNull(message = "任务id不能为空") Integer taskId) {
transportResultDataService.handleTimingAnalysis(taskId);
return Result.OK();
}
@AutoLog(value = "查询任务所属核设施数据")
@Operation(summary = "查询任务所属核设施数据")
@GetMapping("getTaskFacility")
@ -50,4 +85,18 @@ public class TransportResultDataController {
return Result.OK(transportResultDataService.getTaskFacility(taskId));
}
@AutoLog(value = "查询任务所属台站数据")
@Operation(summary = "查询任务所属台站数据")
@GetMapping("getTaskStations")
public Result<?> getTaskStations(@NotNull(message = "任务id不能为空") Integer taskId) {
return Result.OK(transportResultDataService.getTaskStations(taskId));
}
@AutoLog(value = "查询任务所属NC层级数据")
@Operation(summary = "查询任务所属NC层级数据")
@GetMapping("getTaskNCHeightLevel")
public Result<?> getTaskNCHeightLevel(@NotNull(message = "任务id不能为空") Integer taskId) {
return Result.OK(transportResultDataService.getTaskNCHeightLevel(taskId));
}
}

View File

@ -1,6 +1,8 @@
package org.jeecg.service;
import org.jeecg.vo.ContributionAnalysisVO;
import org.jeecg.vo.QueryDiffusionVO;
import org.jeecg.vo.TaskStationsVO;
import java.util.List;
import java.util.Map;
@ -37,4 +39,49 @@ public interface TransportResultDataService {
* @return
*/
List<String> getTaskFacility(Integer taskId);
/**
* 获取贡献分析数据
* @param taskId
* @param stationCode
* @return
*/
ContributionAnalysisVO getContributionAnalysis(Integer taskId, String stationCode);
/**
* 处理贡献分析数据存入缓存
* @param taskId
* @return
*/
void handleContributionAnalysis(Integer taskId);
/**
* 查询任务所属台站数据
* @param taskId
* @return
*/
List<TaskStationsVO> getTaskStations(Integer taskId);
/**
* 查询任务所属NC层级数据
* @param taskId
* @return
*/
Map<Integer, Object> getTaskNCHeightLevel(Integer taskId);
/**
* 查询反演核设施时序数据接口
* @param taskId
* @param stationCode
* @param facilityName
* @param timeNum
*/
Map<String,Double> getTimingAnalysis(Integer taskId,String stationCode,String facilityName,Integer timeNum);
/**
* 处理反演核设施时序数据存入缓存
* @param taskId
* @return
*/
void handleTimingAnalysis(Integer taskId);
}

View File

@ -77,7 +77,7 @@ public interface TransportTaskService extends IService<TransportTask> {
* @param taskId
* @param minute
*/
void updateTaskTimeConsuming(Integer taskId, Double minute);
void updateTaskStatusToCompleted(Integer taskId, Double minute);
/**
* 删除任务日志

View File

@ -2,22 +2,31 @@ package org.jeecg.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.enums.TransportTaskModeEnum;
import org.jeecg.common.constant.enums.TransportTimingAnalysisEnum;
import org.jeecg.common.properties.TransportSimulationProperties;
import org.jeecg.common.util.NcUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.base.entity.TransportTask;
import org.jeecg.modules.base.entity.TransportTaskChild;
import org.jeecg.modules.base.entity.configuration.GardsNuclearfacility;
import org.jeecg.modules.base.entity.configuration.GardsStations;
import org.jeecg.modules.base.mapper.TransportTaskChildMapper;
import org.jeecg.modules.base.mapper.TransportTaskMapper;
import org.jeecg.service.StationDataService;
import org.jeecg.service.TransportResultDataService;
import org.jeecg.util.BilinearInterpolatorWithMath;
import org.jeecg.vo.ConcModValVo;
import org.jeecg.vo.ContributionAnalysisVO;
import org.jeecg.vo.QueryDiffusionVO;
import org.jeecg.vo.TaskStationsVO;
import org.springframework.stereotype.Service;
import ucar.ma2.Array;
import ucar.ma2.DataType;
@ -36,6 +45,7 @@ import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class TransportResultDataServiceImpl implements TransportResultDataService {
@ -44,6 +54,7 @@ public class TransportResultDataServiceImpl implements TransportResultDataServic
private final TransportSimulationProperties simulationProperties;
private final TransportTaskChildMapper transportTaskChildMapper;
private final StationDataService stationDataService;
private final RedisUtil redisUtil;
private final static String FORWARD="forward";
private final static String BACK_FORWARD="backward";
@ -236,7 +247,6 @@ public class TransportResultDataServiceImpl implements TransportResultDataServic
}
List<Map<String, Object>> xeResults = stationDataService.getXeResults(stationId, transportTask.getStartTime(), transportTask.getEndTime(), nuclideName);
System.out.println(xeResults);
//获取nc文件路径
String path = this.getForwardTaskNCPath(transportTask);
try (NetcdfFile ncFile = NetcdfFile.open(path.toString())) {
@ -273,6 +283,7 @@ public class TransportResultDataServiceImpl implements TransportResultDataServic
List<ConcModValVo> modValList = new ArrayList<>();
for(int i=pointNum;i<=maxPointNum;i++){
for(int k=0;k<timeData.size();k++){
//nageclass=1, pointspec=1, time=30, height=6, latitude=710, longitude=1430
int[] origin = {0, i,k,0, latBestIndex,lonBestIndex};
int[] section = {1, 1,1,1,1,1};
Array levelData = spec001Mr.read(origin,section);
@ -341,6 +352,326 @@ public class TransportResultDataServiceImpl implements TransportResultDataServic
return transportTaskChildren.stream().map(TransportTaskChild::getStationCode).collect(Collectors.toList());
}
/**
* 获取贡献分析数据
* @param taskId
* @param stationCode
* @return
*/
@Override
public ContributionAnalysisVO getContributionAnalysis(Integer taskId,String stationCode) {
TransportTask transportTask = transportTaskMapper.selectById(taskId);
if(Objects.isNull(transportTask)){
throw new RuntimeException("此任务不存在");
}
//包含从缓存中拿
if(redisUtil.hasKey(CommonConstant.TRANSPORT_CONTRIBUTION_ANALYSIS+transportTask.getId()+":"+stationCode)){
return (ContributionAnalysisVO) redisUtil.get(CommonConstant.TRANSPORT_CONTRIBUTION_ANALYSIS+transportTask.getId()+":"+stationCode);
}
throw new RuntimeException("此站点贡献分析数据不存在或未处理");
}
/**
* 处理贡献分析数据存入缓存
* @param taskId
* @return
*/
@Override
public void handleContributionAnalysis(Integer taskId) {
//查询任务数据
TransportTask transportTask = transportTaskMapper.selectById(taskId);
//查询需要处理贡献分析数据的台站
LambdaQueryWrapper<TransportTaskChild> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TransportTaskChild::getTaskId,taskId);
List<TransportTaskChild> stationInfos = this.transportTaskChildMapper.selectList(queryWrapper);
//所以核设施数据
List<GardsNuclearfacility> facilitys = stationDataService.getAllNuclearfacility();
NetcdfFile ncFile = null;
try {
for(TransportTaskChild stationInfo :stationInfos) {
//每个台站的结果数据
ContributionAnalysisVO contributionAnalysisVO = new ContributionAnalysisVO();
//存储台站每天的浓度值数据
Map<String,Double> stationEveryDayConcDatas = new LinkedHashMap<>();
//存储核设施每天的浓度值数据
Map<String,Map<String,Double>> facilityEveryDayConcDatas = new HashMap<>();
//饼图数据
Map<String,Double> pipeChartData = new HashMap<>();
//总浓度值
Double totalConc = 0D;
//获取nc文件路径
String path = this.getBackForwardTaskNCPath(transportTask,stationInfo.getStationCode());
ncFile = NetcdfFile.open(path.toString());
List<Double> lonData = NcUtil.getNCList(ncFile, "longitude");
List<Double> latData = NcUtil.getNCList(ncFile, "latitude");
List<Double> timeData = NcUtil.getNCList(ncFile, "time");
Variable spec001Mr = ncFile.findVariable("spec001_mr");
for(int k=0;k<timeData.size();k++){
System.out.println(stationInfo.getStationCode()+"循环:"+k+",共"+timeData.size()+"");
//处理日期数据
Instant instant = transportTask.getEndTime().toInstant();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
localDateTime = localDateTime.plusSeconds(timeData.get(k).intValue());
String dayStr = LocalDateTimeUtil.format(localDateTime, "yyyy-MM-dd");
if (!stationEveryDayConcDatas.containsKey(dayStr)) {
stationEveryDayConcDatas.put(dayStr,0D);
}
//获取台站点位取整后±1.5度内的点坐标及数据使用插值算法求台站点位的值
Double stationConc = this.getTargetSiteConc(lonData,latData,stationInfo.getLon(),stationInfo.getLat(),k,spec001Mr);
//累加台站位置当前天的浓度数据
stationEveryDayConcDatas.put(dayStr,stationEveryDayConcDatas.get(dayStr)+stationConc);
for (GardsNuclearfacility facility : facilitys){
Double facilityConc = this.getTargetSiteConc(lonData,latData,facility.getLonValue(),facility.getLatValue(),k,spec001Mr);
if (facilityConc>0){
if (!facilityEveryDayConcDatas.containsKey(dayStr)) {
Map<String,Double> facilityConcMap = new HashMap<>();
facilityConcMap.put(facility.getFacilityName(),facilityConc);
facilityEveryDayConcDatas.put(dayStr,facilityConcMap);
}else {
Map<String,Double> facilityConcMap = facilityEveryDayConcDatas.get(dayStr);
if (!facilityConcMap.containsKey(facility.getFacilityName())) {
facilityConcMap.put(facility.getFacilityName(),facilityConc);
}else {
facilityConcMap.put(facility.getFacilityName(),facilityConcMap.get(facility.getFacilityName())+facilityConc);
}
}
}
}
}
//计算本模拟时间内总浓度数据
totalConc = stationEveryDayConcDatas.values().stream().mapToDouble(Double::doubleValue).sum();
//处理饼图数据
if(CollUtil.isNotEmpty(facilityEveryDayConcDatas)){
for (Map<String,Double> facilityConcMap : facilityEveryDayConcDatas.values()) {
Set<Map.Entry<String, Double>> entries = facilityConcMap.entrySet();
for (Map.Entry<String, Double> entry : entries) {
if (!pipeChartData.containsKey(entry.getKey())) {
pipeChartData.put(entry.getKey(),entry.getValue());
}else {
pipeChartData.put(entry.getKey(),pipeChartData.get(entry.getKey())+entry.getValue());
}
}
}
}
//处理返回值
contributionAnalysisVO.setTotalConc(totalConc);
contributionAnalysisVO.setStationEveryDayConcDatas(stationEveryDayConcDatas);
contributionAnalysisVO.setFacilityEveryDayConcDatas(facilityEveryDayConcDatas);
contributionAnalysisVO.setNuclideNames(new ArrayList<>(pipeChartData.keySet()));
contributionAnalysisVO.setPipeChartData(pipeChartData);
redisUtil.set(CommonConstant.TRANSPORT_CONTRIBUTION_ANALYSIS+transportTask.getId()+":"+stationInfo.getStationCode(), contributionAnalysisVO);
ncFile.close();
}
}catch (IOException | InvalidRangeException e) {
throw new RuntimeException(e);
}finally {
try {
if(ncFile !=null){
ncFile.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 查询任务所属台站数据
* @param taskId
* @return
*/
@Override
public List<TaskStationsVO> getTaskStations(Integer taskId) {
TransportTask transportTask = this.transportTaskMapper.selectById(taskId);
if(Objects.isNull(transportTask)){
throw new RuntimeException("此任务不存在");
}
LambdaQueryWrapper<TransportTaskChild> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TransportTaskChild::getTaskId,taskId);
queryWrapper.select(TransportTaskChild::getId,TransportTaskChild::getStationCode);
queryWrapper.orderByAsc(TransportTaskChild::getId);
List<TransportTaskChild> transportTaskChildren = transportTaskChildMapper.selectList(queryWrapper);
if(CollUtil.isEmpty(transportTaskChildren)){
throw new RuntimeException("此任务站点信息不存在,请确认任务配置信息");
}
//本任务模拟的台站数据
List<TaskStationsVO> taskStationsVOList = new ArrayList<>();
for (int i = 0; i<transportTaskChildren.size();i++) {
TaskStationsVO taskStationsVO = new TaskStationsVO();
taskStationsVO.setStationNum(i+1);
taskStationsVO.setStationCode(transportTaskChildren.get(i).getStationCode());
taskStationsVOList.add(taskStationsVO);
}
return taskStationsVOList;
}
/**
* 查询任务所属NC层级数据
*
* @param taskId
* @return
*/
@Override
public Map<Integer, Object> getTaskNCHeightLevel(Integer taskId) {
Map<Integer,Object> resultMap = new HashMap<>();
TransportTask transportTask = this.transportTaskMapper.selectById(taskId);
if(Objects.isNull(transportTask)){
throw new RuntimeException("此任务不存在");
}
LambdaQueryWrapper<TransportTaskChild> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TransportTaskChild::getTaskId,taskId);
queryWrapper.select(TransportTaskChild::getId,TransportTaskChild::getStationCode);
queryWrapper.orderByAsc(TransportTaskChild::getId);
List<TransportTaskChild> transportTaskChildren = transportTaskChildMapper.selectList(queryWrapper);
if(CollUtil.isEmpty(transportTaskChildren)){
throw new RuntimeException("此任务站点信息不存在,请确认任务配置信息");
}
//获取nc文件路径
String path = "";
if(TransportTaskModeEnum.FORWARD.getKey().equals(transportTask.getTaskMode())){
path = this.getForwardTaskNCPath(transportTask);
}else if(TransportTaskModeEnum.BACK_FORWARD.getKey().equals(transportTask.getTaskMode())){
path = this.getBackForwardTaskNCPath(transportTask,transportTaskChildren.get(0).getStationCode());
}
if(!FileUtil.exist(path)){
throw new RuntimeException("此任务模拟结果不存在,请确认任务运行状态");
}
//全局属性
try (NetcdfFile ncFile = NetcdfFile.open(path.toString())) {
Variable heightVar = ncFile.findVariable("height");
Array data = heightVar.read();
int[] shape = data.getShape();
Index index = data.getIndex();
for(int i=0;i<shape[0];i++){
resultMap.put((i),data.getDouble(index.set(i)));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return resultMap;
}
/**
* 查询反演核设施时序数据接口
* @param taskId
* @param stationCode
* @param facilityName
* @param timeNum
*/
@Override
public Map<String,Double> getTimingAnalysis(Integer taskId,String stationCode,String facilityName,Integer timeNum) {
TransportTask transportTask = transportTaskMapper.selectById(taskId);
if(Objects.isNull(transportTask)){
throw new RuntimeException("此任务不存在");
}
//包含从缓存中拿
String key = CommonConstant.TRANSPORT_TIMING_ANALYSIS+transportTask.getId()+":"+stationCode+":"+facilityName;
if(redisUtil.hasKey(key)){
Map<String,Double> concValMap = (LinkedHashMap)redisUtil.get(key);
//如果是3小时纬度直接范围因为最小是3小时
if (TransportTimingAnalysisEnum.THREE_HOURS.getKey().equals(timeNum)) {
concValMap.forEach((k,v)->{
//纳克转换为毫克需除以1000000
BigDecimal concValue = new BigDecimal(v);
BigDecimal finalValue = concValue.divide(new BigDecimal(1000000)).setScale(5,BigDecimal.ROUND_HALF_UP);
concValMap.put(k,finalValue.doubleValue());
});
return concValMap;
}else{
Map<String,Double> resultMap = new LinkedHashMap<>();
Instant endTimeInstant = transportTask.getEndTime().toInstant();
LocalDateTime endTime = LocalDateTime.ofInstant(endTimeInstant, ZoneId.systemDefault());
Instant startTimeInstant = transportTask.getStartTime().toInstant();
LocalDateTime startTime = LocalDateTime.ofInstant(startTimeInstant, ZoneId.systemDefault());
boolean flag = true;
while(flag){
String dayTimeStr = LocalDateTimeUtil.format(startTime, "yyyy-MM-dd HH:mm:ss");
//纳克转换为毫克需除以1000000
BigDecimal concValue = new BigDecimal(concValMap.get(dayTimeStr));
BigDecimal finalValue = concValue.divide(new BigDecimal(1000000)).setScale(5,BigDecimal.ROUND_HALF_UP);
resultMap.put(dayTimeStr,finalValue.doubleValue());
startTime = startTime.plusHours(timeNum);
if(startTime.isEqual(endTime)){
flag = false;
}
}
return resultMap;
}
}
return Map.of();
}
/**
* 处理反演核设施时序数据存入缓存
* @param taskId
* @return
*/
@Override
public void handleTimingAnalysis(Integer taskId) {
TransportTask transportTask = this.transportTaskMapper.selectById(taskId);
//查询需要处理数据的台站
LambdaQueryWrapper<TransportTaskChild> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TransportTaskChild::getTaskId,taskId);
List<TransportTaskChild> stationInfos = this.transportTaskChildMapper.selectList(queryWrapper);
//所以核设施数据
List<GardsNuclearfacility> facilitys = stationDataService.getAllNuclearfacility();
NetcdfFile ncFile = null;
try {
for(TransportTaskChild stationInfo :stationInfos) {
log.info("处理"+stationInfo.getStationCode()+"台站数据");
Map<String,Map<String,Double>> everyFacilityConcDatas = new HashMap<>();
//获取nc文件路径
String path = this.getBackForwardTaskNCPath(transportTask,stationInfo.getStationCode());
ncFile = NetcdfFile.open(path.toString());
List<Double> lonData = NcUtil.getNCList(ncFile, "longitude");
List<Double> latData = NcUtil.getNCList(ncFile, "latitude");
List<Double> timeData = NcUtil.getNCList(ncFile, "time");
Variable spec001Mr = ncFile.findVariable("spec001_mr");
for (GardsNuclearfacility facility : facilitys){
//存储台站每步的浓度值数据
Map<String,Double> everyStepConcDatas = new LinkedHashMap<>();
for(int k=0;k<timeData.size();k++){
//处理日期数据
Instant instant = transportTask.getEndTime().toInstant();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
localDateTime = localDateTime.plusSeconds(timeData.get(k).intValue());
String dayTimeStr = LocalDateTimeUtil.format(localDateTime, "yyyy-MM-dd HH:mm:ss");
//获取台站点位取整后±1.5度内的点坐标及数据使用插值算法求台站点位的值
Double facilityConc = this.getTargetSiteConc(lonData,latData,facility.getLonValue(),facility.getLatValue(),k,spec001Mr);
everyStepConcDatas.put(dayTimeStr,facilityConc);
}
everyFacilityConcDatas.put(facility.getFacilityName(),everyStepConcDatas);
}
if(CollUtil.isNotEmpty(everyFacilityConcDatas)){
everyFacilityConcDatas.forEach((facilityName,facilityConcData)->{
String key = CommonConstant.TRANSPORT_TIMING_ANALYSIS+transportTask.getId()+":"+stationInfo.getStationCode()+":"+facilityName;
redisUtil.set(key, facilityConcData);
});
}
ncFile.close();
}
}catch (IOException | InvalidRangeException e) {
throw new RuntimeException(e);
}finally {
try {
if(ncFile !=null){
ncFile.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 找到集合中里最接近目标值的值
* @param data
@ -406,4 +737,63 @@ public class TransportResultDataServiceImpl implements TransportResultDataServic
path.append("grid_time_"+DateUtil.format(transportTask.getEndTime(),"yyyyMMddHHmmss")+".nc");
return path.toString();
}
/**
* 在步长为 0.25° 的递增经纬度数组中查找指定位置的索引
* - lons[0] 是起始经度
* - 步长严格为 0.25
* - target 一定存在
*/
public int findTargetIndex(List<Double> datas, double target) {
// 计算偏移量单位0.25°
double offset = (target - datas.get(0)) / 0.25;
// 四舍五入取整抵抗浮点误差 166.5 - 166.0 = 0.5000000001 2.000...
int index = (int) Math.round(offset);
// 安全检查可选
if (index < 0 || index >= datas.size() || Math.abs(datas.get(index) - target) > 1e-5) {
System.out.println(datas);
throw new IllegalArgumentException("经度 " + target + " 不在数组中");
}
return index;
}
/**
* 获取指定位置的浓度数据
* @param lonData nc文件的经度数据
* @param latData nc文件的纬度数据
* @param targetLon 目标位置的经度
* @param targetLat 目标位置的纬度
* @param k 时间索引
* @param spec001Mr nc文件浓度数据变量
* @return
* @throws InvalidRangeException
* @throws IOException
*/
private Double getTargetSiteConc(List<Double> lonData,List<Double> latData,Double targetLon,Double targetLat,int k,Variable spec001Mr) throws InvalidRangeException, IOException {
int lonIndex = this.findTargetIndex(lonData, targetLon.intValue()-0.125);
int latIndex = this.findTargetIndex(latData, targetLat.intValue()-0.125);
//nageclass=1, pointspec=1, time=30, height=6, latitude=710, longitude=1430
int[] origin = {0,0,k,0, (latIndex-6),(lonIndex-6)};
int[] section = {1, 1,1,1,12,12};
Array levelData = spec001Mr.read(origin,section);
double[] pointData = (double[]) levelData.get1DJavaArray(DataType.DOUBLE);
Map<BilinearInterpolatorWithMath.Point,Double> siteData = new LinkedHashMap<>();
int nlon_patch = 12,patchLatStart = latIndex - 6,patchLonStart = lonIndex - 6;
for(int i=0;i<pointData.length;i++){
int iy_patch = i / nlon_patch; // 在子区域内的纬度偏移0~11
int ix_patch = i % nlon_patch; // 在子区域内的经度偏移0~11
// 转为全局索引用于查 lonData/latData
int global_iy = patchLatStart + iy_patch;
int global_ix = patchLonStart + ix_patch;
// 获取真实经纬度
double lon = lonData.get(global_ix);
double lat = latData.get(global_iy);
BilinearInterpolatorWithMath.Point point = new BilinearInterpolatorWithMath.Point(lon,lat);
siteData.put(point,pointData[i]);
}
BilinearInterpolatorWithMath.GridData grid = new BilinearInterpolatorWithMath.GridData(siteData, 0.25, 0.125);
return grid.interpolate(targetLon,targetLat);
}
}

View File

@ -7,11 +7,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.enums.TransportTaskStatusEnum;
import org.jeecg.common.constant.enums.TransportTaskTypeEnum;
import org.jeecg.common.properties.SystemStorageProperties;
import org.jeecg.common.properties.TransportSimulationProperties;
import org.jeecg.common.system.query.PageRequest;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.base.entity.TransportTask;
import org.jeecg.modules.base.entity.TransportTaskChild;
import org.jeecg.modules.base.entity.TransportTaskLog;
@ -40,6 +42,7 @@ public class TransportTaskServiceImpl extends ServiceImpl<TransportTaskMapper,Tr
private final WeatherDataMapper weatherDataMapper;
private final TransportSimulationProperties simulationProperties;
private final SystemStorageProperties systemStorageProperties;
private final RedisUtil redisUtil;
/**
* 分页查询任务列表
@ -169,6 +172,9 @@ public class TransportTaskServiceImpl extends ServiceImpl<TransportTaskMapper,Tr
queryWrapper.eq(TransportTaskChild::getTaskId,id);
transportTaskChildMapper.delete(queryWrapper);
this.baseMapper.deleteById(id);
//删除存储的贡献分析数据和时序分析数据
redisUtil.del(CommonConstant.TRANSPORT_CONTRIBUTION_ANALYSIS+id);
redisUtil.del(CommonConstant.TRANSPORT_TIMING_ANALYSIS+id);
}
/**
@ -231,10 +237,11 @@ public class TransportTaskServiceImpl extends ServiceImpl<TransportTaskMapper,Tr
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void updateTaskTimeConsuming(Integer taskId, Double minute) {
public void updateTaskStatusToCompleted(Integer taskId, Double minute) {
TransportTask transportTask = this.baseMapper.selectById(taskId);
transportTask.setTaskStatus(TransportTaskStatusEnum.FAILURE.getValue());
transportTask.setTimeConsuming(minute);
this.baseMapper.updateById(transportTask);
}
/**

View File

@ -1,8 +1,6 @@
package org.jeecg.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -74,14 +72,15 @@ public class TransportTaskExec extends Thread{
}catch (Exception e){
String taskErrorLog = "任务执行失败,原因:"+e.getMessage();
ProgressQueue.getInstance().offer(new ProgressEvent(this.transportTask.getId(),taskErrorLog));
e.printStackTrace();
throw e;
}finally {
//添加任务耗时
stopWatch.stop();
long seconds = stopWatch.getTime(TimeUnit.SECONDS);
Double min = seconds/60D;
this.transportTaskService.updateTaskTimeConsuming(this.transportTask.getId(),min);
this.transportTaskService.updateTaskStatusToCompleted(this.transportTask.getId(),min);
String taskCompletedLog = "任务执行完成,耗时:"+min+"分钟";
ProgressQueue.getInstance().offer(new ProgressEvent(this.transportTask.getId(),taskCompletedLog));
}
}

View File

@ -0,0 +1,175 @@
package org.jeecg.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class BilinearInterpolatorWithMath {
public static class Point {
public final double lon;
public final double lat;
public Point(double lon, double lat) {
this.lon = lon;
this.lat = lat;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point p = (Point) o;
// 使用 Math.abs 做浮点容差比较
double tol = 1e-9;
return Math.abs(p.lon - lon) < tol && Math.abs(p.lat - lat) < tol;
}
@Override
public int hashCode() {
// 使用 Math.floor + longBitsToDouble 生成稳定哈希可选优化
return java.util.Objects.hash(
Double.doubleToLongBits(Math.floor(lon * 1e6 + 0.5) / 1e6),
Double.doubleToLongBits(Math.floor(lat * 1e6 + 0.5) / 1e6)
);
}
}
public static class GridData {
private final Map<Point, Double> data;
private final double delta;
private final double offset;
public GridData(Map<Point, Double> data, double delta, double offset) {
this.data = data;
this.delta = delta;
this.offset = offset;
}
/**
* 计算双线性插值
*/
public Double interpolate(double lon, double lat) {
//精确匹配格点避免边界问题
Point target = new Point(lon, lat);
Double exactValue = data.get(target);
if (exactValue != null && !Double.isNaN(exactValue)) {
return exactValue; // 直接返回不插值
}
//1. 定位网格单元使用 Math.floor 确保负数正确处理
// 关键避免 (int) 强转的 truncation 问题
int i = (int) Math.floor((lon - offset) / delta);
int j = (int) Math.floor((lat - offset) / delta);
// 计算四个角点坐标
double lon0 = i * delta + offset;
double lon1 = lon0 + delta;
double lat0 = j * delta + offset;
double lat1 = lat0 + delta;
//2. 防御性检查目标点是否真的在 [lon0, lon1) × [lat0, lat1)
// 使用 Math.abs + 容差避免浮点误差导致误判
final double TOL = 1e-10;
if (lon < lon0 - TOL || lon > lon1 + TOL ||
lat < lat0 - TOL || lat > lat1 + TOL) {
System.err.printf("Warning: (%.6f, %.6f) is outside computed cell [%.3f~%.3f]×[%.3f~%.3f]%n",
lon, lat, lon0, lon1, lat0, lat1);
// 可选自动修正索引
i = (int) Math.floor((lon - offset) / delta); // 再算一次
j = (int) Math.floor((lat - offset) / delta);
lon0 = i * delta + offset;
lon1 = lon0 + delta;
lat0 = j * delta + offset;
lat1 = lat0 + delta;
}
//3. 构造四邻点并查值
Point[] points = {
new Point(lon0, lat0), // SW
new Point(lon1, lat0), // SE
new Point(lon0, lat1), // NW
new Point(lon1, lat1) // NE
};
Double[] values = new Double[4];
boolean hasMissing = false;
for (int k = 0; k < 4; k++) {
values[k] = data.get(points[k]);
if (values[k] == null || Double.isNaN(values[k])) {
hasMissing = true;
System.err.println("Missing/NaN at: " + points[k].lon + ", " + points[k].lat);
}
}
if (hasMissing) return null;
//4. 计算权重使用 Math.max/Math.min 裁剪到 [0,1]防浮点误差溢出
double wx = (lon - lon0) / delta;
double wy = (lat - lat0) / delta;
// 关键增强防止 wx/wy 因浮点误差略超 [0,1]
wx = Math.max(0.0, Math.min(1.0, wx)); // 等价于 clamp(wx, 0, 1)
wy = Math.max(0.0, Math.min(1.0, wy));
//5. 双线性插值公式核心无需 Math 复杂函数
double v00 = values[0], v10 = values[1], v01 = values[2], v11 = values[3];
double val = (1 - wx) * (1 - wy) * v00 +
wx * (1 - wy) * v10 +
(1 - wx) * wy * v01 +
wx * wy * v11;
//6. 最终检查结果是否合理
// 使用 Math.abs + isFinite NaN/Inf
if (!Double.isFinite(val)) {
System.err.println("Interpolation produced invalid value: " + val);
return null;
}
return val;
}
/**
* 批量插值高效处理多个点
* 展示 Math.copySign / Math.IEEEremainder 等高级用法可选场景
*/
public double[] interpolateBatch(double[] lons, double[] lats) {
if (lons.length != lats.length)
throw new IllegalArgumentException("Length mismatch");
double[] results = new double[lons.length];
for (int idx = 0; idx < lons.length; idx++) {
Double val = interpolate(lons[idx], lats[idx]);
results[idx] = (val != null) ? val : Double.NaN;
// 可选对经度做环绕处理全球数据常见
// 例如 -181° 映射到 179°
double adjustedLon = lons[idx];
if (Math.abs(adjustedLon) > 180.0) {
// 使用 IEEE 754 标准取余 % 更可靠
adjustedLon = Math.IEEEremainder(adjustedLon + 180.0, 360.0) - 180.0;
// 保持符号一致性-0.0 0.0
adjustedLon = Math.copySign(adjustedLon, adjustedLon);
}
// 然后用 adjustedLon 重新插值...
}
return results;
}
}
// ===== 演示Math 库增强版用法 =====
public static void main(String[] args) {
Map<Point, Double> data = new LinkedHashMap<>();
data.put(new Point(7.875, 47.875), 195.53140258789062);
data.put(new Point(8.125, 47.875), 9.2166166305542);
data.put(new Point(7.875, 48.125), 40.02995681762695);
data.put(new Point(8.125, 48.125), 11.597820281982422);
GridData grid = new GridData(data, 0.25, 0.125);
// 测试正常点
System.out.printf("Normal: %.8f%n",
grid.interpolate(7.9, 47.9));
// 测试边界点wx/wy 接近 1
// System.out.printf("Edge case: %.8f%n",
// grid.interpolate(-53.625, -36.375)); // 应等于 NE 点值
}
}

View File

@ -0,0 +1,39 @@
package org.jeecg.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 贡献分析数据VO
*/
@Data
public class ContributionAnalysisVO {
/**
* X轴台站每天的浓度值数据
*/
private Map<String,Double> stationEveryDayConcDatas;
/**
* 核设施每天的浓度值数据
*/
private Map<String,Map<String,Double>> facilityEveryDayConcDatas;
/**
* 涉及的核设施名称
*/
private List<String> nuclideNames;
/**
* 饼图数据
*/
private Map<String,Double> pipeChartData;
/**
* 总浓度值
*/
private Double totalConc;
}

View File

@ -0,0 +1,17 @@
package org.jeecg.vo;
import lombok.Data;
@Data
public class TaskStationsVO {
private Integer stationId;
private Integer stationNum;
private String stationCode;
private Double lon;
private Double lat;
}

View File

@ -12,6 +12,7 @@ import org.jeecg.common.validgroup.InsertGroup;
import org.jeecg.common.validgroup.UpdateGroup;
import org.jeecg.modules.base.entity.WeatherTask;
import org.jeecg.service.WeatherTaskService;
import org.jeecg.vo.ForecastFileVO;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -44,7 +45,8 @@ public class WeatherTaskController {
@AutoLog(value = "新增天气预测任务")
@Operation(summary = "新增天气预测任务")
@PostMapping("create")
public Result<?> create(@RequestBody @Validated(value = InsertGroup.class) WeatherTask weatherTask){
public Result<?> create(@Validated(value = InsertGroup.class) WeatherTask weatherTask){
System.out.println(weatherTask);
weatherTaskService.cteate(weatherTask);
return Result.OK();
}
@ -59,7 +61,7 @@ public class WeatherTaskController {
@AutoLog(value = "修改天气预测任务")
@Operation(summary = "修改天气预测任务")
@PutMapping("update")
public Result<?> update(@RequestBody @Validated(value = UpdateGroup.class) WeatherTask weatherTask){
public Result<?> update(@Validated(value = UpdateGroup.class) WeatherTask weatherTask){
weatherTaskService.update(weatherTask);
return Result.OK();
}
@ -72,6 +74,14 @@ public class WeatherTaskController {
return Result.OK();
}
@AutoLog(value = "启动任务")
@Operation(summary = "启动任务")
@PutMapping("runTask")
public Result<?> runTask(@NotBlank(message = "任务ID不能为空") String taskId){
weatherTaskService.runTask(taskId);
return Result.OK();
}
@AutoLog(value = "获取天气预测任务过程日志")
@Operation(summary = "获取天气预测任务过程日志")
@GetMapping("getTaskLog")

View File

@ -3,15 +3,11 @@ package org.jeecg.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.system.query.PageRequest;
import org.jeecg.modules.base.entity.StasDataSource;
import org.jeecg.modules.base.entity.WeatherData;
import org.jeecg.vo.*;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
public interface WeatherDataService extends IService<WeatherData> {
@ -54,4 +50,10 @@ public interface WeatherDataService extends IService<WeatherData> {
* 处理静态气象数据入库接口比上传快
*/
void handleStaticDataToDB(String path,Integer dataSource);
/**
* 保存模型预测好的文件信息到WeatherData
* @param weatherData
*/
void saveFileInfo(WeatherData weatherData);
}

View File

@ -53,7 +53,7 @@ public interface WeatherTaskService extends IService<WeatherTask> {
* 运行任务
* @param id
*/
void runTask(Integer id);
void runTask(String id);
/**
* 获取任务运行日志
@ -61,4 +61,31 @@ public interface WeatherTaskService extends IService<WeatherTask> {
* @return
*/
List<WeatherTaskLog> getTaskLog(String taskId);
/**
* 保存任务过程日志
* @param log
*/
void saveLog(WeatherTaskLog log);
/**
* 修改任务运行状态
* @param taskId
* @param taskStatus
*/
void updateTaskStatus(String taskId,Integer taskStatus);
/**
* 根据任务id删除任务日志
* @param taskId
*/
void deleteTaskLog(String taskId);
/**
* 修改任务状态为结束
* @param taskId
* @param timeConsuming
*/
void updateTaskStatusToCompleted(String taskId, Double timeConsuming);
}

View File

@ -2,6 +2,7 @@ package org.jeecg.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
@ -435,8 +436,7 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
BigDecimal fileSize = bg.divide(divideVal).divide(divideVal).setScale(2, RoundingMode.HALF_UP);
queryResult.setFileSize(fileSize.doubleValue());
//把文件移入新路径
int year = utcDateTime.getYear();
String newFileDirPath = dataFile.getParentFile().getParent()+File.separator+year+File.separator;
String newFileDirPath = dataFile.getParentFile().getParent()+File.separator+File.separator;
String newFilePath = newFileDirPath+File.separator+dataFile.getName();
FileUtil.mkdir(newFileDirPath);
File parentDir = dataFile.getParentFile();
@ -557,6 +557,17 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
}
}
/**
* 保存模型预测好的文件信息到WeatherData
*
* @param weatherData
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void saveFileInfo(WeatherData weatherData) {
this.save(weatherData);
}
/**
* 文件合并
* @param fileVo

View File

@ -2,20 +2,29 @@ package org.jeecg.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.jeecg.common.constant.enums.WeatherFileSuffixEnum;
import org.jeecg.common.constant.enums.WeatherForecastDatasourceEnum;
import org.jeecg.common.constant.enums.WeatherTaskStatusEnum;
import org.jeecg.common.properties.SystemStorageProperties;
import org.jeecg.common.system.query.PageRequest;
import org.jeecg.modules.base.entity.WeatherTask;
import org.jeecg.modules.base.entity.WeatherTaskLog;
import org.jeecg.modules.base.mapper.WeatherDataMapper;
import org.jeecg.modules.base.mapper.WeatherTaskLogMapper;
import org.jeecg.modules.base.mapper.WeatherTaskMapper;
import org.jeecg.service.WeatherTaskService;
import org.jeecg.task.WeatherTaskExec;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@ -29,6 +38,8 @@ import java.util.Objects;
public class WeatherTaskServiceImpl extends ServiceImpl<WeatherTaskMapper, WeatherTask> implements WeatherTaskService {
private final WeatherTaskLogMapper weatherTaskLogMapper;
private final SystemStorageProperties systemStorageProperties;
private final WeatherDataMapper weatherDataMapper;
/**
* 分页查询任务列表
@ -73,11 +84,40 @@ public class WeatherTaskServiceImpl extends ServiceImpl<WeatherTaskMapper, Weath
LambdaQueryWrapper<WeatherTask> checkDateWrapper = new LambdaQueryWrapper<>();
checkDateWrapper.eq(WeatherTask::getStartDate,weatherTask.getStartDate());
checkDateWrapper.eq(WeatherTask::getStartTime,weatherTask.getStartTime());
checkDateWrapper.eq(WeatherTask::getPredictionModel,weatherTask.getPredictionModel());
WeatherTask checkDateResult = this.getOne(checkDateWrapper);
if(Objects.nonNull(checkDateResult)){
throw new RuntimeException("此当前预测时间为参数的任务已存在");
}
//手动获取id
String id = IdWorker.getIdStr();
weatherTask.setId(id);
weatherTask.setTaskStatus(WeatherTaskStatusEnum.NOT_STARTED.getKey());
if (WeatherForecastDatasourceEnum.LOCATION_FILE.getKey().equals(weatherTask.getDataSources())){
try {
MultipartFile file = weatherTask.getFile();
//构造文件名称
StringBuilder fileName = new StringBuilder();
fileName.append(file.getOriginalFilename().substring(0,file.getOriginalFilename().lastIndexOf(".")));
fileName.append("_"+id+".");
fileName.append(WeatherFileSuffixEnum.GRIB.getValue());
//文件保存路径
StringBuilder filePath = new StringBuilder();
filePath.append(this.systemStorageProperties.getPanguModelExecPath());
filePath.append(File.separator);
filePath.append(fileName);
File storageFile = new File(filePath.toString());
if (storageFile.exists()){
storageFile.delete();
}
file.transferTo(storageFile);
weatherTask.setInputFile(fileName.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
this.save(weatherTask);
}
@ -115,8 +155,9 @@ public class WeatherTaskServiceImpl extends ServiceImpl<WeatherTaskMapper, Weath
LambdaQueryWrapper<WeatherTask> checkDateWrapper = new LambdaQueryWrapper<>();
checkDateWrapper.eq(WeatherTask::getStartDate,weatherTask.getStartDate());
checkDateWrapper.eq(WeatherTask::getStartTime,weatherTask.getStartTime());
checkDateWrapper.eq(WeatherTask::getPredictionModel,weatherTask.getPredictionModel());
WeatherTask checkDateResult = this.getOne(checkDateWrapper);
if(Objects.nonNull(checkDateResult) && !weatherTask.getId().equals(checkNameResult.getId())){
if(Objects.nonNull(checkDateResult) && Objects.nonNull(checkNameResult) && !weatherTask.getId().equals(checkNameResult.getId())){
throw new RuntimeException("此当前预测时间为参数的任务已存在");
}
queryResult.setTaskName(weatherTask.getTaskName());
@ -125,7 +166,35 @@ public class WeatherTaskServiceImpl extends ServiceImpl<WeatherTaskMapper, Weath
queryResult.setStartTime(weatherTask.getStartTime());
queryResult.setLeadTime(weatherTask.getLeadTime());
queryResult.setDataSources(weatherTask.getDataSources());
queryResult.setInputFile(weatherTask.getInputFile());
if (WeatherForecastDatasourceEnum.LOCATION_FILE.getKey().equals(weatherTask.getDataSources())){
if (Objects.nonNull(weatherTask.getFile())){
try {
MultipartFile file = weatherTask.getFile();
//构造文件名称
StringBuilder fileName = new StringBuilder();
fileName.append(file.getOriginalFilename().substring(0,file.getOriginalFilename().lastIndexOf(".")));
fileName.append("_"+queryResult.getId()+".");
fileName.append(WeatherFileSuffixEnum.GRIB.getValue());
//文件保存路径
StringBuilder filePath = new StringBuilder();
filePath.append(this.systemStorageProperties.getPanguModelExecPath());
filePath.append(File.separator);
filePath.append(fileName);
File storageFile = new File(filePath.toString());
if (storageFile.exists()){
storageFile.delete();
}
file.transferTo(storageFile);
queryResult.setInputFile(fileName.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}else{
queryResult.setInputFile(Strings.EMPTY);
}
this.updateById(queryResult);
}
@ -145,8 +214,15 @@ public class WeatherTaskServiceImpl extends ServiceImpl<WeatherTaskMapper, Weath
* @param id
*/
@Override
public void runTask(Integer id) {
public void runTask(String id) {
WeatherTask task = this.baseMapper.selectById(id);
if(Objects.isNull(task)){
throw new RuntimeException("此任务不存在");
}
WeatherTaskExec exec = new WeatherTaskExec();
exec.init(task,this,systemStorageProperties,weatherDataMapper);
exec.setName("天气预测任务线程");
exec.start();
}
/**
@ -163,4 +239,57 @@ public class WeatherTaskServiceImpl extends ServiceImpl<WeatherTaskMapper, Weath
queryWrapper.orderByAsc(WeatherTaskLog::getCreateTime);
return this.weatherTaskLogMapper.selectList(queryWrapper);
}
/**
* 保存任务过程日志
*
* @param log
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void saveLog(WeatherTaskLog log) {
this.weatherTaskLogMapper.insert(log);
}
/**
* 修改任务运行状态
*
* @param taskId
* @param taskStatus
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void updateTaskStatus(String taskId, Integer taskStatus) {
WeatherTask weatherTask = this.baseMapper.selectById(taskId);
weatherTask.setTaskStatus(taskStatus);
this.updateById(weatherTask);
}
/**
* 根据任务id删除任务日志
*
* @param taskId
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void deleteTaskLog(String taskId) {
LambdaQueryWrapper<WeatherTaskLog> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WeatherTaskLog::getTaskId,taskId);
this.weatherTaskLogMapper.delete(queryWrapper);
}
/**
* 修改任务状态为结束
*
* @param taskId
* @param timeConsuming
*/
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void updateTaskStatusToCompleted(String taskId, Double timeConsuming) {
WeatherTask weatherTask = this.baseMapper.selectById(taskId);
weatherTask.setTaskStatus(WeatherTaskStatusEnum.COMPLETED.getKey());
weatherTask.setTimeConsuming(timeConsuming);
this.updateById(weatherTask);
}
}

View File

@ -0,0 +1,28 @@
package org.jeecg.task;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 日志事件
*/
@Data
@NoArgsConstructor
public class ProgressEvent implements Serializable{
private static final long serialVersionUID = 1L;
private String taskId;
/**
* 过程日志
*/
private String content;
public ProgressEvent(String taskId, String content) {
this.taskId = taskId;
this.content = content;
}
}

View File

@ -0,0 +1,50 @@
package org.jeecg.task;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.base.entity.WeatherTaskLog;
import org.jeecg.service.WeatherTaskService;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 日志监测线程
* @author 86187
*
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class ProgressMonitor{
private final WeatherTaskService weatherTaskService;
@PostConstruct
public void start() {
ProgressMonitorThread monitor = new ProgressMonitorThread();
monitor.setName("气象预测日志监测线程");
monitor.start();
}
private class ProgressMonitorThread extends Thread{
@Override
public void run() {
for(;;) {
try {
ProgressEvent event = ProgressQueue.getInstance().take();
if(Objects.nonNull(event)) {
WeatherTaskLog log = new WeatherTaskLog();
log.setTaskId(event.getTaskId());
log.setLogContent(event.getContent());
weatherTaskService.saveLog(log);
}
}catch (Exception e){
log.error("气象预测日志存储线程异常,日志存储失败,原因为:{}",e.getMessage());
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,39 @@
package org.jeecg.task;
import java.util.LinkedList;
/**
* 日志队列
*/
public class ProgressQueue {
private final LinkedList<ProgressEvent> queue = new LinkedList<>();
private static ProgressQueue progressQueue = new ProgressQueue();
public static ProgressQueue getInstance() {
return progressQueue;
}
public void offer(ProgressEvent event) {
synchronized (queue) {
queue.addLast(event);
queue.notify();
}
}
public ProgressEvent take(){
synchronized (queue) {
if(queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ProgressEvent event = queue.removeFirst();
return event;
}
}
}

View File

@ -0,0 +1,321 @@
package org.jeecg.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.jeecg.common.constant.enums.WeatherDataSourceEnum;
import org.jeecg.common.constant.enums.WeatherForecastDatasourceEnum;
import org.jeecg.common.constant.enums.WeatherTaskStatusEnum;
import org.jeecg.common.exception.JeecgFileUploadException;
import org.jeecg.common.properties.SystemStorageProperties;
import org.jeecg.common.util.NcUtil;
import org.jeecg.modules.base.entity.WeatherData;
import org.jeecg.modules.base.entity.WeatherTask;
import org.jeecg.modules.base.entity.WeatherTaskLog;
import org.jeecg.modules.base.mapper.WeatherDataMapper;
import org.jeecg.service.WeatherDataService;
import org.jeecg.service.WeatherTaskService;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* 气象预测任务执行线程
*/
public class WeatherTaskExec extends Thread{
private WeatherTask weatherTask;
private WeatherTaskService weatherTaskService;
private SystemStorageProperties systemStorageProperties;
private WeatherDataMapper weatherDataMapper;
/**
* 初始化
*/
public void init(WeatherTask weatherTask,
WeatherTaskService weatherTaskService,
SystemStorageProperties systemStorageProperties,
WeatherDataMapper weatherDataMapper){
this.weatherTask = weatherTask;
this.weatherTaskService = weatherTaskService;
this.systemStorageProperties = systemStorageProperties;
this.weatherDataMapper = weatherDataMapper;
}
@Override
public void run() {
this.execute();
}
/**
* 执行任务
*/
public void execute() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
//修改任务状态为执行中
this.weatherTaskService.updateTaskStatus(this.weatherTask.getId(), WeatherTaskStatusEnum.IN_OPERATION.getKey());
//如果此任务已存在历史日志先清除
this.weatherTaskService.deleteTaskLog(this.weatherTask.getId());
//执行模拟
this.execSimulation();
}catch (Exception e){
String taskErrorLog = "任务执行失败,原因:"+e.getMessage();
ProgressQueue.getInstance().offer(new ProgressEvent(this.weatherTask.getId(),taskErrorLog));
throw e;
}finally {
//添加任务耗时
stopWatch.stop();
long seconds = stopWatch.getTime(TimeUnit.SECONDS);
double min = seconds/60D;
BigDecimal bgMin = new BigDecimal(min);
BigDecimal result = bgMin.setScale(2, RoundingMode.HALF_UP);
this.weatherTaskService.updateTaskStatusToCompleted(this.weatherTask.getId(),result.doubleValue());
String taskCompletedLog = "任务执行完成,耗时:"+result+"分钟";
ProgressQueue.getInstance().offer(new ProgressEvent(this.weatherTask.getId(),taskCompletedLog));
}
}
/**
* 执行模拟
*/
private void execSimulation(){
try {
//处理开始日志
String forecastModel = "";
String startLogFormat = "";
String startTimeFormat = LocalDateTimeUtil.format(this.weatherTask.getStartDate(),"yyyy-MM-dd HH:mm:ss");
String forecastTime = this.weatherTask.getLeadTime().toString();
if (WeatherDataSourceEnum.GRAPHCAST.getKey().equals(this.weatherTask.getPredictionModel())){
forecastModel = WeatherDataSourceEnum.GRAPHCAST.getValue();
}else if(WeatherDataSourceEnum.PANGU.getKey().equals(this.weatherTask.getPredictionModel())){
forecastModel = WeatherDataSourceEnum.PANGU.getValue();
}
if(this.weatherTask.getDataSources().equals(WeatherForecastDatasourceEnum.CDS.getKey())){
startLogFormat = "任务开始,本次使用预测模型为:%s预测开始时间为%s预测时长为%s前置数据采用CDS在线数据";
}else {
startLogFormat = "任务开始,本次使用预测模型为:%s预测开始时间为%s预测时长为%s前置数据采用离线数据"+this.weatherTask.getInputFile();
}
String startLog = String.format(startLogFormat,forecastModel,startTimeFormat,forecastTime);
ProgressQueue.getInstance().offer(new ProgressEvent(this.weatherTask.getId(),startLog));
//处理运行命令
List<String> command = new ArrayList<>();
String aiModelsPath = systemStorageProperties.getAiModelsPath()+File.separator+"ai-models";
String fileName = "panguweather_output_"+this.weatherTask.getId()+".grib";
if(this.weatherTask.getDataSources().equals(WeatherForecastDatasourceEnum.CDS.getKey())){
command.add(aiModelsPath);
command.add("--input");
command.add("cds");
command.add("--date");
command.add(LocalDateTimeUtil.format(this.weatherTask.getStartDate(),"yyyyMMdd"));
command.add("--time");
command.add(this.weatherTask.getStartTime().toString());
command.add("--lead-time");
command.add(this.weatherTask.getLeadTime().toString());
command.add("--path");
command.add(fileName);
command.add("--assets");
command.add("assets-panguweather");
command.add("panguweather");
}else if (this.weatherTask.getDataSources().equals(WeatherForecastDatasourceEnum.LOCATION_FILE.getKey())){
//离线文件还需处理
command.add(aiModelsPath);
command.add("--file");
command.add(this.weatherTask.getInputFile());
command.add("--lead-time");
command.add(this.weatherTask.getLeadTime().toString());
command.add("--path");
command.add(fileName);
command.add("--assets");
command.add("assets-panguweather");
command.add("panguweather");
}
String execLog = "执行任务命令,开始模拟,命令为:"+command;
ProgressQueue.getInstance().offer(new ProgressEvent(this.weatherTask.getId(),execLog));
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.directory(new File(systemStorageProperties.getPanguModelExecPath()));
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
//读取输出日志
String line;
while ((line = reader.readLine()) != null) {
if(StrUtil.isNotBlank(line)){
ProgressQueue.getInstance().offer(new ProgressEvent(this.weatherTask.getId(),line));
}
}
//等待进程结束
process.waitFor();
String handleGribLog = "预测结束开始处理grib文件";
ProgressQueue.getInstance().offer(new ProgressEvent(this.weatherTask.getId(),handleGribLog));
LocalDateTime startTime = this.weatherTask.getStartDate().atTime(this.weatherTask.getStartTime(), 0, 0);
//grib_copy命令
String gribCopyCommandPath = systemStorageProperties.getAiModelsPath()+File.separator+"grib_copy";
//grib_set命令
String gribSetCommandPath = systemStorageProperties.getAiModelsPath()+File.separator+"grib_set";
//需删除文件
List<String> delFiles = new ArrayList<>();
//需移动文件
List<String> moveFiles = new ArrayList<>();
//公用gribProcessBuilder
ProcessBuilder gribProcessBuilder = new ProcessBuilder();
gribProcessBuilder.directory(new File(systemStorageProperties.getPanguModelExecPath()));
//定义名称后缀,执行grib_set命令时去除否则命名会冲突
String gribFileSuffix = "_not_grib_set";
//把grib文件切割成每6小时一份
int step = 6;
int i=this.weatherTask.getStartTime();
while(i <= this.weatherTask.getLeadTime()){
String gribCopyFileName = "panguweather_"+LocalDateTimeUtil.format(startTime,"yyyyMMddHH")+gribFileSuffix+".grib";
delFiles.add(gribCopyFileName);
//切割grib文件命令
List<String> gribCopyCommand = new ArrayList<>();
gribCopyCommand.add(gribCopyCommandPath);
gribCopyCommand.add("-w");
gribCopyCommand.add("step="+i);
gribCopyCommand.add(fileName);
gribCopyCommand.add(gribCopyFileName);
gribProcessBuilder.command(gribCopyCommand);
Process gribCopyProcess = gribProcessBuilder.start();
gribCopyProcess.waitFor();
//重新设置reftime信息
String gribSetFileName = "panguweather_"+LocalDateTimeUtil.format(startTime,"yyyyMMddHH")+".grib";
moveFiles.add(gribSetFileName);
String date = LocalDateTimeUtil.format(startTime,"yyyyMMdd");
String time = LocalDateTimeUtil.format(startTime,"HHmm");
List<String> gribSetCommand = new ArrayList<>();
gribSetCommand.add(gribSetCommandPath);
gribSetCommand.add("-s");
gribSetCommand.add("dataDate="+date+",dataTime="+time+",endStep="+0);
gribSetCommand.add(gribCopyFileName);
gribSetCommand.add(gribSetFileName);
gribProcessBuilder.command(gribSetCommand);
Process gribSetProcess = gribProcessBuilder.start();
gribSetProcess.waitFor();
i+=step;
startTime = startTime.plusHours(step);
}
if (CollUtil.isNotEmpty(moveFiles)) {
for (String moveFile : moveFiles) {
File srcFile = new File(systemStorageProperties.getPanguModelExecPath()+File.separator+moveFile);
if (srcFile.exists()) {
File targetFile = new File(this.getPanguWeatherPath()+File.separator+File.separator+moveFile);
FileUtil.move(srcFile,targetFile,true);
//保存生成的气象数据存储到数据库
this.saveGribInfoToDB(targetFile);
}
}
}
//删除gribCopy产生的文件
for (String delFile : delFiles) {
File srcFile = new File(systemStorageProperties.getPanguModelExecPath()+File.separator+delFile);
if (srcFile.exists()) {
srcFile.delete();
}
}
//删除预测的结果文件
String outputFilePath = systemStorageProperties.getPanguModelExecPath()+File.separator+fileName;
File outputFile = new File(outputFilePath);
if(outputFile.exists()){
outputFile.delete();
}
//如果是本地文件进行预测预测结束后删除文件
if(this.weatherTask.getDataSources().equals(WeatherForecastDatasourceEnum.LOCATION_FILE.getKey())){
String inputFilePath = systemStorageProperties.getPanguModelExecPath()+File.separator+this.weatherTask.getInputFile();
File inputFile = new File(inputFilePath);
if(inputFile.exists()){
inputFile.delete();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 保存生成的气象数据存储到数据库
* @param file
*/
private void saveGribInfoToDB(File file){
//获取文件数据开始日期
String reftime = NcUtil.getReftime(file.getAbsolutePath());
if(StringUtils.isBlank(reftime)) {
throw new JeecgFileUploadException("解析气象文件起始时间数据异常,此文件可能损坏");
}
//计算文件大小M
BigDecimal divideVal = new BigDecimal("1024");
BigDecimal bg = new BigDecimal(file.length());
BigDecimal fileSize = bg.divide(divideVal).divide(divideVal).setScale(2, RoundingMode.HALF_UP);
//处理文件数据开始时间
Instant instant = Instant.parse(reftime);
LocalDateTime utcDateTime = LocalDateTime.ofInstant(instant, ZoneId.of("UTC"));
//计算文件MD5值
String md5Val = "";
try (InputStream is = new FileInputStream(file.getAbsolutePath())) {
md5Val = DigestUtils.md5Hex(is);
}catch (Exception e){
throw new RuntimeException(file.getName()+"MD5值计算失败");
}
//校验文件是否存在存在删除从新新增
LambdaQueryWrapper<WeatherData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WeatherData::getFileName,file.getName());
WeatherData queryResult = weatherDataMapper.selectOne(queryWrapper);
if(Objects.nonNull(queryResult)){
weatherDataMapper.deleteById(queryResult.getId());
}
//构建文件信息
WeatherData weatherData = new WeatherData();
weatherData.setFileName(file.getName());
weatherData.setFileSize(fileSize.doubleValue());
weatherData.setFileExt(file.getName().substring(file.getName().lastIndexOf(".")+1));
weatherData.setDataStartTime(utcDateTime);
weatherData.setDataSource(weatherTask.getPredictionModel());
weatherData.setFilePath(file.getAbsolutePath());
weatherData.setMd5Value(md5Val);
weatherData.setShareTotal(1);
weatherDataMapper.insert(weatherData);
}
/**
* 获取盘古数据存储路径
* @return
*/
private String getPanguWeatherPath(){
StringBuilder sb = new StringBuilder();
sb.append(this.systemStorageProperties.getRootPath());
sb.append(File.separator);
sb.append(this.systemStorageProperties.getPangu());
return sb.toString();
}
/**
* 获取盘古数据存储路径
* @return
*/
private String getGraphcastWeatherPath(){
StringBuilder sb = new StringBuilder();
sb.append(this.systemStorageProperties.getRootPath());
sb.append(File.separator);
sb.append(this.systemStorageProperties.getGraphcast());
return sb.toString();
}
}

View File

@ -0,0 +1,26 @@
package org.jeecg.vo;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.io.Serializable;
/**
* 预测文件上传VO
*/
@Data
public class ForecastFileVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务id
*/
private String taskId;
/**
* 上传文件
*/
@JSONField(serialize = false)
private MultipartFile file;
}