fix: 优化AI设备识别数据上报(支持批量)

This commit is contained in:
tangwei 2026-07-30 18:44:09 +08:00
parent 2dcde94d4b
commit 4bfe058765
6 changed files with 258 additions and 11 deletions

View File

@ -1,13 +1,14 @@
package com.yfd.platform.qgc_base.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yfd.platform.annotation.Log;
import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.qgc_base.domain.SdEngInfoBHOperateRequest;
import com.yfd.platform.qgc_base.domain.SdFpssrlR;
import com.yfd.platform.qgc_base.domain.SdFpssrlRAiRequest;
import com.yfd.platform.qgc_base.domain.*;
import com.yfd.platform.qgc_base.service.ISdFishDictoryBService;
import com.yfd.platform.qgc_base.service.ISdFpssrlRService;
import com.yfd.platform.qgc_data.service.AttachmentUploadService;
import io.swagger.v3.oas.annotations.Operation;
@ -19,6 +20,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -41,6 +43,9 @@ public class SdFpssrlRController {
@Resource
private AttachmentUploadService attachmentUploadService;
@Resource
private ISdFishDictoryBService sdFishDictoryBService;
// ==================== CRUD 方法 ====================
@PostMapping("/queryPageList")
@ -131,7 +136,14 @@ public class SdFpssrlRController {
if (requests == null || requests.isEmpty()) {
return ResponseResult.error("请求数据不能为空");
}
List<SdFishDictoryB> list = sdFishDictoryBService.list(new LambdaQueryWrapper<SdFishDictoryB>().eq(SdFishDictoryB::getIsDeleted, "0").select(SdFishDictoryB::getId, SdFishDictoryB::getName));
Map<String, String> idNameMap = list.stream()
.filter(item -> item.getId() != null && item.getName() != null)
.collect(Collectors.toMap(
SdFishDictoryB::getName,
SdFishDictoryB::getId,
(oldValue, newValue) -> oldValue // 如果有重复 key 保留旧值实际不会重复
));
// 逐条校验
for (int i = 0; i < requests.size(); i++) {
SdFpssrlRAiRequest req = requests.get(i);
@ -150,10 +162,13 @@ public class SdFpssrlRController {
if (req.getFtp() == null || req.getFtp().isEmpty()) {
return ResponseResult.error("" + (i + 1) + "条鱼种类(ftp)不能为空");
}
if (StrUtil.isNotBlank(req.getFtp())) {
req.setFtp(idNameMap.getOrDefault(req.getFtp(), req.getFtp()));
}
}
try {
List<SdFpssrlR> result = sdFpssrlRService.processAiReportBatch(requests);
List<SdFpssrlAiR> result = sdFpssrlRService.processAiReportBatch(requests);
// List<String> ids = result.stream().map(SdFpssrlR::getId).collect(Collectors.toList());
log.info("AI批量上报成功共{}条", result.size());
return ResponseResult.success();

View File

@ -0,0 +1,121 @@
package com.yfd.platform.qgc_base.domain;
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 lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* <p>
* 过鱼设施AI盒子自动数据表
* </p>
*/
@Data
@TableName("SD_FPSSRL_AI_R")
public class SdFpssrlAiR implements Serializable {
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/** 过鱼设施编码 */
private String stcd;
/** 时间 */
private Date tm;
/** 鱼种类 */
private String ftp;
/** 过鱼数量 */
private Integer fcnt;
/** 鱼尺寸:大/中/小 */
private String fsz;
/** 鱼长度 */
private BigDecimal length;
/** 鱼宽度 */
private BigDecimal width;
/** 鱼速度 */
private String fishspeed;
/** 游向0=上行 1=下行 */
private Integer direction;
/** 鱼位置 */
private Long fishposition;
/** 鱼截图主图片url */
private String firstImgUrl;
/** 鱼截图副图片url */
private String secondImgUrl;
/** 视频url */
private String videoUrl;
/** 水温:单位:℃ */
private BigDecimal temperature;
/** 水位单位m */
private BigDecimal waterlevel;
/** 流速单位m/s */
private BigDecimal speed;
/** 流量单位m3/s */
private BigDecimal q;
/** 溶氧单位mg/L */
private BigDecimal dox;
/** 浊度单位NTU */
private Integer tu;
/** 过鱼通道 */
private String channelno;
/** AI盒子编码 */
private String aiBoxCode;
/** 创建人 */
private String recordUser;
/** 创建时间 */
private Date recordTime;
/** 更新人 */
private String modifyUser;
/** 更新时间 */
private Date modifyTime;
/** 是否已删除0=未删除 1=已删除 */
private Integer isDeleted;
/** 删除人 */
private String deleteUser;
/** 删除时间 */
private Date deleteTime;
/** 附件ID */
private String fid;
/** 备注 */
private String remark;
/** 过鱼设施名称(非表字段,用于列表展示) */
@TableField(exist = false)
private String stnm;
}

View File

@ -0,0 +1,22 @@
package com.yfd.platform.qgc_base.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.qgc_base.domain.SdFpssrlAiR;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* <p>
* 过鱼设施AI盒子自动数据表 Mapper 接口
* </p>
*/
@Mapper
public interface SdFpssrlAiRMapper extends BaseMapper<SdFpssrlAiR> {
/**
* 批量合并过鱼AI自动数据基于 STCD + TM + FTP 唯一
* 存在则更新不存在则新增
*/
int mergeFishRecords(List<SdFpssrlAiR> list);
}

View File

@ -3,6 +3,7 @@ package com.yfd.platform.qgc_base.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.qgc_base.domain.SdFpssrlAiR;
import com.yfd.platform.qgc_base.domain.SdFpssrlR;
import com.yfd.platform.qgc_base.domain.SdFpssrlRAiRequest;
@ -48,5 +49,5 @@ public interface ISdFpssrlRService extends IService<SdFpssrlR> {
/**
* 批量处理AI设备上报的识别数据
*/
List<SdFpssrlR> processAiReportBatch(List<SdFpssrlRAiRequest> requests);
List<SdFpssrlAiR> processAiReportBatch(List<SdFpssrlRAiRequest> requests);
}

View File

@ -6,8 +6,10 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yfd.platform.common.DataSourceRequest;
import com.yfd.platform.qgc_base.domain.SdFpssrlAiR;
import com.yfd.platform.qgc_base.domain.SdFpssrlR;
import com.yfd.platform.qgc_base.domain.SdFpssrlRAiRequest;
import com.yfd.platform.qgc_base.mapper.SdFpssrlAiRMapper;
import com.yfd.platform.qgc_base.mapper.SdFpssrlRMapper;
import com.yfd.platform.qgc_base.service.IMsOperationLogService;
import com.yfd.platform.qgc_base.service.ISdFpssrlRService;
@ -35,6 +37,8 @@ public class SdFpssrlRServiceImpl extends ServiceImpl<SdFpssrlRMapper, SdFpssrlR
@Resource
private IMsOperationLogService msOperationLogService;
@Resource
private SdFpssrlAiRMapper fpssrlAiRMapper;
@Override
public Page<SdFpssrlR> queryPageList(DataSourceRequest request) {
@ -130,12 +134,13 @@ public class SdFpssrlRServiceImpl extends ServiceImpl<SdFpssrlRMapper, SdFpssrlR
@Override
@Transactional(rollbackFor = Exception.class)
public List<SdFpssrlR> processAiReportBatch(List<SdFpssrlRAiRequest> requests) {
List<SdFpssrlR> entities = new ArrayList<>();
public List<SdFpssrlAiR> processAiReportBatch(List<SdFpssrlRAiRequest> requests) {
List<SdFpssrlAiR> entities = new ArrayList<>();
Date now = new Date();
for (SdFpssrlRAiRequest req : requests) {
SdFpssrlR entity = new SdFpssrlR();
entity.setStcd(req.getStcd());
SdFpssrlAiR entity = new SdFpssrlAiR();
entity.setStcd(req.getFwdx());
entity.setAiBoxCode(req.getStcd());
entity.setTm(req.getTm());
entity.setFtp(req.getFtp());
entity.setFcnt(req.getFcnt() != null ? req.getFcnt() : 1);
@ -164,7 +169,7 @@ public class SdFpssrlRServiceImpl extends ServiceImpl<SdFpssrlRMapper, SdFpssrlR
entity.setIsDeleted(0);
entities.add(entity);
}
baseMapper.mergeFishRecords(entities);
fpssrlAiRMapper.mergeFishRecords(entities);
return entities;
}
}

View File

@ -0,0 +1,83 @@
<?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.yfd.platform.qgc_base.mapper.SdFpssrlAiRMapper">
<!-- 批量合并过鱼AI自动数据基于 STCD + TM + FTP + AI_BOX_CODE 唯一) -->
<insert id="mergeFishRecords" parameterType="java.util.List">
MERGE INTO SD_FPSSRL_AI_R T
USING (
<foreach collection="list" item="obj" index="index" separator="UNION ALL">
SELECT
#{obj.stcd, jdbcType=VARCHAR} AS STCD,
#{obj.tm, jdbcType=TIMESTAMP} AS TM,
#{obj.ftp, jdbcType=VARCHAR} AS FTP,
#{obj.fcnt, jdbcType=INTEGER} AS FCNT,
#{obj.fsz, jdbcType=VARCHAR} AS FSZ,
#{obj.length, jdbcType=DECIMAL} AS LENGTH,
#{obj.width, jdbcType=DECIMAL} AS WIDTH,
#{obj.fishspeed, jdbcType=VARCHAR} AS FISHSPEED,
#{obj.direction, jdbcType=INTEGER} AS DIRECTION,
#{obj.fishposition, jdbcType=DECIMAL} AS FISHPOSITION,
#{obj.firstImgUrl, jdbcType=VARCHAR} AS FIRSTIMGURL,
#{obj.secondImgUrl, jdbcType=VARCHAR} AS SECONDIMGURL,
#{obj.videoUrl, jdbcType=VARCHAR} AS VIDEOURL,
#{obj.temperature, jdbcType=DECIMAL} AS TEMPERATURE,
#{obj.waterlevel, jdbcType=DECIMAL} AS WATERLEVEL,
#{obj.speed, jdbcType=DECIMAL} AS SPEED,
#{obj.q, jdbcType=DECIMAL} AS Q,
#{obj.dox, jdbcType=DECIMAL} AS DOX,
#{obj.tu, jdbcType=INTEGER} AS TU,
#{obj.channelno, jdbcType=VARCHAR} AS CHANNELNO,
#{obj.aiBoxCode, jdbcType=VARCHAR} AS AI_BOX_CODE,
#{obj.fid, jdbcType=VARCHAR} AS FID,
#{obj.remark, jdbcType=VARCHAR} AS REMARK,
#{obj.recordUser, jdbcType=VARCHAR} AS RECORD_USER,
#{obj.modifyUser, jdbcType=VARCHAR} AS MODIFY_USER
FROM DUAL
</foreach>
) S
ON (T.STCD = S.STCD AND T.TM = S.TM AND T.FTP = S.FTP
AND T.AI_BOX_CODE = S.AI_BOX_CODE AND T.IS_DELETED = 0)
WHEN MATCHED THEN
UPDATE SET
T.FCNT = S.FCNT,
T.FSZ = S.FSZ,
T.LENGTH = S.LENGTH,
T.WIDTH = S.WIDTH,
T.FISHSPEED = S.FISHSPEED,
T.DIRECTION = S.DIRECTION,
T.FISHPOSITION = S.FISHPOSITION,
T.FIRSTIMGURL = S.FIRSTIMGURL,
T.SECONDIMGURL = S.SECONDIMGURL,
T.VIDEOURL = S.VIDEOURL,
T.TEMPERATURE = S.TEMPERATURE,
T.WATERLEVEL = S.WATERLEVEL,
T.SPEED = S.SPEED,
T.Q = S.Q,
T.DOX = S.DOX,
T.TU = S.TU,
T.CHANNELNO = S.CHANNELNO,
T.FID = S.FID,
T.REMARK = S.REMARK,
T.MODIFY_USER = S.MODIFY_USER,
T.MODIFY_TIME = SYSDATE
WHEN NOT MATCHED THEN
INSERT (
ID, STCD, TM, FTP, FCNT, FSZ, LENGTH, WIDTH, FISHSPEED,
DIRECTION, FISHPOSITION, FIRSTIMGURL, SECONDIMGURL, VIDEOURL,
TEMPERATURE, WATERLEVEL, SPEED, Q, DOX, TU, CHANNELNO, AI_BOX_CODE,
FID, REMARK, RECORD_USER, RECORD_TIME, MODIFY_USER, MODIFY_TIME,
IS_DELETED
) VALUES (
SYS_GUID(),
S.STCD, S.TM, S.FTP, S.FCNT, S.FSZ, S.LENGTH, S.WIDTH, S.FISHSPEED,
S.DIRECTION, S.FISHPOSITION, S.FIRSTIMGURL, S.SECONDIMGURL, S.VIDEOURL,
S.TEMPERATURE, S.WATERLEVEL, S.SPEED, S.Q, S.DOX, S.TU, S.CHANNELNO,
NVL(S.AI_BOX_CODE, '未知'),
S.FID, S.REMARK, S.RECORD_USER, SYSDATE, S.MODIFY_USER, SYSDATE,
0
)
</insert>
</mapper>