From a69695bafcd80da84f954b2f0992c6b19ede83e2 Mon Sep 17 00:00:00 2001 From: tangwei Date: Wed, 9 Sep 2026 11:13:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=A5=E6=94=B6AI=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/common/enums/AicomTypeEnum.java | 56 ++++++++ .../ime/entity/vo/ImeVmsstbprptVo.java | 3 + .../ime/service/impl/ImeServiceImpl.java | 2 + .../warn/controller/WarnDataController.java | 15 ++- .../qgc_env/warn/entity/ao/AiDataAo.java | 41 ++++++ .../qgc_env/warn/entity/ao/AiFlowAo.java | 17 +++ .../warn/entity/ao/ReceiveAiDataAo.java | 39 ++++++ .../qgc_env/warn/service/WarnDataService.java | 8 ++ .../service/impl/WarnDataServiceImpl.java | 127 ++++++++++++++++++ .../qgc_env/wte/entity/vo/WeVmsstbprptVo.java | 3 + .../wte/service/impl/WeFishServiceImpl.java | 2 + 11 files changed, 309 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/com/yfd/platform/common/enums/AicomTypeEnum.java create mode 100644 backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiDataAo.java create mode 100644 backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiFlowAo.java create mode 100644 backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/ReceiveAiDataAo.java diff --git a/backend/src/main/java/com/yfd/platform/common/enums/AicomTypeEnum.java b/backend/src/main/java/com/yfd/platform/common/enums/AicomTypeEnum.java new file mode 100644 index 00000000..1d9c29aa --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/common/enums/AicomTypeEnum.java @@ -0,0 +1,56 @@ +package com.yfd.platform.common.enums; + +/** + * @author xyx + * @date 2023-11-27 15:53 + */ +public enum AicomTypeEnum { + PFW("AI_5001","漂浮物"), + YYCYX("AI_5002","运鱼车运行"), + YYCCG("AI_5003","运鱼船出港"), + JYCJY("AI_5004","集鱼船集鱼"), + DLMYX("AI_5005","叠梁门运行"), + GYSYX("AI_5006","赶鱼栅运行"), + SYJYX("AI_5007","升鱼机运行"), + AGVXC("AI_5008","AGV小车"), + YLZY("AI_5009","鱼类暂养"), + BASYJ("AI_5010","坝上升鱼机"), + PFWQL("AI_5011","漂浮物清理"), + YDFL("AI_5012","鱼道放流"); + + private String code; + private String name; + + AicomTypeEnum(String code, String name){ + this.code = code; + this.name = name; + } + + public String getCode(){ + return this.code; + } + + public String getName(){ + return this.name; + } + public enum DlmValueEnum { + CLOSE("close","关闭"), + OPEN("open","开启"); + + private String code; + private String name; + + DlmValueEnum(String code, String name){ + this.code = code; + this.name = name; + } + + public String getCode(){ + return this.code; + } + + public String getName(){ + return this.name; + } + } +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/ime/entity/vo/ImeVmsstbprptVo.java b/backend/src/main/java/com/yfd/platform/qgc_env/ime/entity/vo/ImeVmsstbprptVo.java index 66e706e9..58e4a006 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/ime/entity/vo/ImeVmsstbprptVo.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/ime/entity/vo/ImeVmsstbprptVo.java @@ -15,6 +15,9 @@ public class ImeVmsstbprptVo { @Schema(description = "站类编码") private String sttpCode; + @Schema(description = "站类编码") + private String sttp; + @Schema(description = "描点状态") private String anchoPointState; diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/ime/service/impl/ImeServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/ime/service/impl/ImeServiceImpl.java index 02134c04..4b0b44f9 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/ime/service/impl/ImeServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/ime/service/impl/ImeServiceImpl.java @@ -433,6 +433,7 @@ public class ImeServiceImpl implements ImeService { return "SELECT " + "src.STCD AS id, " + "src.STTP_CODE AS sttpCode, " + + "src.STTP AS sttp, " + "CONCAT('ZHZB_', src.STTP_CODE) AS anchoPointState, " + "src.STLC AS stlc, " + "src.STCD AS stcd, " + @@ -477,6 +478,7 @@ public class ImeServiceImpl implements ImeService { Map columns = new LinkedHashMap<>(); columns.put("id", "t.id AS id"); columns.put("sttpCode", "t.sttpCode AS sttpCode"); + columns.put("sttp", "t.sttp AS sttp"); columns.put("stcd", "t.stcd AS stcd"); columns.put("stnm", "t.stnm AS stnm"); columns.put("rstcd", "t.rstcd AS rstcd"); diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/warn/controller/WarnDataController.java b/backend/src/main/java/com/yfd/platform/qgc_env/warn/controller/WarnDataController.java index ebb0f33c..e3fbe51a 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/warn/controller/WarnDataController.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/warn/controller/WarnDataController.java @@ -2,15 +2,15 @@ package com.yfd.platform.qgc_env.warn.controller; import com.yfd.platform.common.DataSourceRequest; import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.qgc_env.warn.entity.ao.ReceiveAiDataAo; import com.yfd.platform.qgc_env.warn.service.WarnDataService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequestMapping("/warn") @@ -80,4 +80,11 @@ public class WarnDataController { public ResponseResult getAiFileKendoListCust(@RequestBody DataSourceRequest dataSourceRequest) { return ResponseResult.successData(warnDataService.getAiFileKendoListCust(dataSourceRequest)); } + + @PostMapping("/receiveAiData") + @Operation(summary = "接收AI数据") + public ResponseResult receiveAiData(@RequestBody List aoList) { + warnDataService.receiveAiData(aoList); + return ResponseResult.success(); + } } diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiDataAo.java b/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiDataAo.java new file mode 100644 index 00000000..83a9091a --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiDataAo.java @@ -0,0 +1,41 @@ +package com.yfd.platform.qgc_env.warn.entity.ao; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Map; + +/** + * AI识别数据(AI设备上抛识别的 aidata 载荷) + */ +@Data +@Schema(description = "AI识别数据") +public class AiDataAo { + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "识别时间") + private Date time; + + @Schema(description = "漂浮物面积(平方米)") + private BigDecimal area; + + @Schema(description = "视频流量") + private BigDecimal flow; + + @Schema(description = "水位") + private BigDecimal water_level; + + @Schema(description = "断面流速分布") + private Map flow_info; + + /** + * 公共识别数据字段:各类型通用取数,写入 SD_AICOM_R.AI_VAL + */ + @Schema(description = "公共识别数据(各类型通用,写入AI_VAL)") + private String value; +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiFlowAo.java b/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiFlowAo.java new file mode 100644 index 00000000..320f5717 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/AiFlowAo.java @@ -0,0 +1,17 @@ +package com.yfd.platform.qgc_env.warn.entity.ao; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Schema(description = "AI视频流量数据") +public class AiFlowAo { + + @Schema(description = "流速") + private BigDecimal v; + + @Schema(description = "面积") + private BigDecimal area; +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/ReceiveAiDataAo.java b/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/ReceiveAiDataAo.java new file mode 100644 index 00000000..a968764f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_env/warn/entity/ao/ReceiveAiDataAo.java @@ -0,0 +1,39 @@ +package com.yfd.platform.qgc_env.warn.entity.ao; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * 接收AI数据入参 + */ +@Data +@Schema(description = "接收AI数据入参") +public class ReceiveAiDataAo { + + @Schema(description = "设备编号") + private String dvcd; + + @Schema(description = "事件类型:0000=心跳 5001=漂浮物 5011=漂浮物清理 5021=视频流量 AI_RIV=河道识别 AI_FISHING=捕鱼识别") + private String type; + + @Schema(description = "AI识别数据") + private AiDataAo aidata; + + @Schema(description = "文件id,多个逗号分隔") + private String files; + + @Schema(description = "AI识别发生时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date aitime; + + @Schema(description = "状态") + private Boolean status; + + @Schema(description = "消息") + private String message; +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/WarnDataService.java b/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/WarnDataService.java index 66f942ff..bf673ced 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/WarnDataService.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/WarnDataService.java @@ -2,6 +2,7 @@ package com.yfd.platform.qgc_env.warn.service; import com.yfd.platform.common.DataSourceRequest; import com.yfd.platform.common.DataSourceResult; +import com.yfd.platform.qgc_env.warn.entity.ao.ReceiveAiDataAo; import com.yfd.platform.qgc_env.warn.entity.vo.AlarmPointVo; import com.yfd.platform.qgc_env.warn.entity.vo.AiRecordVo; import com.yfd.platform.qgc_env.warn.entity.vo.AiProtectVo; @@ -35,4 +36,11 @@ public interface WarnDataService { DataSourceResult getWarnStcdKendoListCust(DataSourceRequest dataSourceRequest); DataSourceResult getAiFileKendoListCust(DataSourceRequest dataSourceRequest); + + /** + * 接收AI设备上抛的识别数据,合并写入 SD_AICOM_R / SD_AIHEARTBEAT_R + * + * @param aoList AI数据列表 + */ + void receiveAiData(List aoList); } diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/impl/WarnDataServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/impl/WarnDataServiceImpl.java index cd11a195..fc896698 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/impl/WarnDataServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/warn/service/impl/WarnDataServiceImpl.java @@ -11,6 +11,8 @@ import com.yfd.platform.common.DataSourceResult; import com.yfd.platform.common.GroupHelper; import com.yfd.platform.common.GroupingInfo; import com.yfd.platform.common.MicroservicDynamicSQLMapper; +import com.yfd.platform.common.exception.BizException; +import com.yfd.platform.qgc_env.warn.entity.ao.ReceiveAiDataAo; import com.yfd.platform.qgc_env.warn.entity.vo.AlarmPointVo; import com.yfd.platform.qgc_env.warn.entity.vo.AiComListVo; import com.yfd.platform.qgc_env.warn.entity.vo.AiDmRunVo; @@ -22,9 +24,12 @@ import com.yfd.platform.qgc_env.warn.entity.vo.EngWarnCountVo; import com.yfd.platform.qgc_env.warn.entity.vo.StcdWarnStateVo; import com.yfd.platform.qgc_env.warn.service.WarnDataService; import com.yfd.platform.utils.QgcQueryWrapperUtil; +import cn.hutool.json.JSONUtil; import jakarta.annotation.Resource; import org.springframework.cache.annotation.Cacheable; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.ArrayList; @@ -43,6 +48,9 @@ public class WarnDataServiceImpl implements WarnDataService { @Resource private MicroservicDynamicSQLMapper microservicDynamicSQLMapper; + @Resource + private JdbcTemplate jdbcTemplate; + @Override public DataSourceResult getEngWarnCount(DataSourceRequest dataSourceRequest) { DataSourceLoadOptionsBase loadOptions = dataSourceRequest == null ? null : dataSourceRequest.toDevRequest(); @@ -2586,4 +2594,123 @@ public class WarnDataServiceImpl implements WarnDataService { default -> null; }; } + + // ==================== 接收AI数据(SD_AICOM_R / SD_AIHEARTBEAT_R) ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public void receiveAiData(List aoList) { + if (CollUtil.isEmpty(aoList)) { + throw new BizException("数据不能为空"); + } + List aicomRows = new ArrayList<>(); + List heartbeatRows = new ArrayList<>(); + for (ReceiveAiDataAo ao : aoList) { + if (ao == null) { + continue; + } + if (StrUtil.isBlank(ao.getDvcd())) { + throw new BizException("站码不能为空"); + } + if (StrUtil.isBlank(ao.getType())) { + throw new BizException("AI数据类型不能为空"); + } + if (ao.getAitime() == null) { + throw new BizException("AI数据发生时间不能为空"); + } + String stcd = ao.getDvcd().trim(); + String tm = DateUtil.formatDateTime(ao.getAitime()); + String remarkJson = JSONUtil.toJsonStr(ao.getAidata()); + switch (ao.getType()) { + case "0000": + // ai心跳数据 + heartbeatRows.add(new Object[]{stcd, tm, ao.getFiles()}); + break; + case "5001": + // 漂浮物识别 + if (ao.getAidata() == null || ao.getAidata().getArea() == null) { + throw new BizException("漂浮物面积不能为空"); + } + aicomRows.add(new Object[]{ + stcd, tm, "AI_5001", + ao.getAidata().getArea().toPlainString(), + ao.getFiles(), remarkJson + }); + break; + case "5011": + // 漂浮物清理 + aicomRows.add(new Object[]{stcd, tm, "AI_5011", null, ao.getFiles(), remarkJson}); + break; + case "5021": + // 视频流量 + aicomRows.add(new Object[]{ + stcd, tm, "AI_5014", + ao.getAidata() != null && ao.getAidata().getFlow() != null + ? ao.getAidata().getFlow().toPlainString() : null, + ao.getFiles(), remarkJson + }); + break; + case "AI_RIV": + case "AI_FISHING": + // 新增类型:AI_VAL 取自 aidata 公共识别字段 value + aicomRows.add(new Object[]{ + stcd, tm, ao.getType(), + ao.getAidata() != null ? ao.getAidata().getValue() : null, + ao.getFiles(), remarkJson + }); + break; + default: + throw new BizException("未知的AI数据类型"); + } + } + if (!aicomRows.isEmpty()) { + mergeAiCom(aicomRows); + } + if (!heartbeatRows.isEmpty()) { + mergeAiHeartbeat(heartbeatRows); + } + } + + private void mergeAiCom(List rows) { + StringBuilder sql = new StringBuilder("MERGE INTO SD_AICOM_R target USING ("); + for (int i = 0; i < rows.size(); i++) { + if (i > 0) { + sql.append(" UNION ALL "); + } + sql.append("SELECT ? AS STCD, TO_DATE(?, 'YYYY-MM-DD HH24:MI:SS') AS TM, ") + .append("? AS TYPE, ? AS AI_VAL, ? AS FID, ? AS REMARK FROM DUAL"); + } + sql.append(") source ON (target.STCD = source.STCD AND target.TM = source.TM AND target.TYPE = source.TYPE) ") + .append("WHEN NOT MATCHED THEN INSERT (STCD, TM, TYPE, AI_VAL, FID, REMARK, RECORD_TIME, IS_DELETED) ") + .append("VALUES (source.STCD, source.TM, source.TYPE, source.AI_VAL, source.FID, source.REMARK, SYSDATE, 0) ") + .append("WHEN MATCHED THEN UPDATE SET ") + .append("target.AI_VAL = source.AI_VAL, ") + .append("target.FID = source.FID, ") + .append("target.REMARK = source.REMARK, ") + .append("target.MODIFY_TIME = SYSDATE"); + List params = new ArrayList<>(); + for (Object[] row : rows) { + params.addAll(Arrays.asList(row)); + } + jdbcTemplate.update(sql.toString(), params.toArray()); + } + + private void mergeAiHeartbeat(List rows) { + StringBuilder sql = new StringBuilder("MERGE INTO SD_AIHEARTBEAT_R target USING ("); + for (int i = 0; i < rows.size(); i++) { + if (i > 0) { + sql.append(" UNION ALL "); + } + sql.append("SELECT ? AS STCD, TO_DATE(?, 'YYYY-MM-DD HH24:MI:SS') AS TM, ? AS FID FROM DUAL"); + } + sql.append(") source ON (target.STCD = source.STCD AND target.TM = source.TM) ") + .append("WHEN NOT MATCHED THEN INSERT (STCD, TM, FID, RECORD_TIME, IS_DELETED) ") + .append("VALUES (source.STCD, source.TM, source.FID, SYSDATE, 0) ") + .append("WHEN MATCHED THEN UPDATE SET target.FID = source.FID, target.MODIFY_TIME = SYSDATE"); + List params = new ArrayList<>(); + for (Object[] row : rows) { + params.addAll(Arrays.asList(row)); + } + jdbcTemplate.update(sql.toString(), params.toArray()); + } } diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/wte/entity/vo/WeVmsstbprptVo.java b/backend/src/main/java/com/yfd/platform/qgc_env/wte/entity/vo/WeVmsstbprptVo.java index ac598f1f..a891b078 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/wte/entity/vo/WeVmsstbprptVo.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/wte/entity/vo/WeVmsstbprptVo.java @@ -12,6 +12,9 @@ public class WeVmsstbprptVo { @Schema(description = "主键") private String id; + @Schema(description = "站类编码") + private String sttp; + @Schema(description = "站类编码") private String sttpCode; diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/wte/service/impl/WeFishServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/wte/service/impl/WeFishServiceImpl.java index 9e2713b4..c01ca037 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/wte/service/impl/WeFishServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/wte/service/impl/WeFishServiceImpl.java @@ -4232,6 +4232,7 @@ public class WeFishServiceImpl implements WeFishService { private String buildWeVmsstbprptViewSql() { return "SELECT " + "src.STCD AS id, " + + "src.STTP AS sttp, " + "src.STTP_CODE AS sttpCode, " + "src.STLC AS stlc, " + "src.STCD AS stcd, " + @@ -4370,6 +4371,7 @@ public class WeFishServiceImpl implements WeFishService { Map columns = new LinkedHashMap<>(); columns.put("id", "t.id AS id"); columns.put("sttpCode", "t.sttpCode AS sttpCode"); + columns.put("sttp", "t.sttp AS sttp"); columns.put("stcd", "t.stcd AS stcd"); columns.put("stnm", "t.stnm AS stnm"); columns.put("rstcd", "t.rstcd AS rstcd");