From dd48776d91f612bcfed64f9df64510fc17ebc02a Mon Sep 17 00:00:00 2001 From: tangwei Date: Thu, 20 Aug 2026 11:42:04 +0800 Subject: [PATCH 01/12] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=9E=82?= =?UTF-8?q?=E5=90=91=E6=B0=B4=E6=B8=A9=E4=BF=AE=E6=94=B9=E4=B8=8D=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eq/service/impl/EngEqDataServiceImpl.java | 41 ++---------- .../wt/service/impl/SdWtvtRServiceImpl.java | 3 +- .../com/yfd/platform/utils/LogValueUtils.java | 67 +++++++++++++++++++ 3 files changed, 76 insertions(+), 35 deletions(-) create mode 100644 backend/src/main/java/com/yfd/platform/utils/LogValueUtils.java diff --git a/backend/src/main/java/com/yfd/platform/qgc_eng/eq/service/impl/EngEqDataServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_eng/eq/service/impl/EngEqDataServiceImpl.java index 4df52a05..7ea2d5cf 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_eng/eq/service/impl/EngEqDataServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_eng/eq/service/impl/EngEqDataServiceImpl.java @@ -47,6 +47,7 @@ import com.yfd.platform.qgc_base.mapper.MsOperationLogDetailMapper; import com.yfd.platform.qgc_base.mapper.MsOperationLogMapper; import com.yfd.platform.qgc_env.wq.entity.vo.MsWarnRuleDetailBVo; import com.yfd.platform.qgc_env.wt.utils.SiteAvoidanceUtils; +import com.yfd.platform.utils.LogValueUtils; import com.yfd.platform.utils.QgcQueryWrapperUtil; import com.yfd.platform.utils.SecurityUtils; import jakarta.annotation.Resource; @@ -6845,7 +6846,7 @@ public class EngEqDataServiceImpl implements EngEqDataService { Object oldValue = beforeHour == null ? null : beforeHour.get(entry.getKey()); Object newValue = updateData.get(entry.getKey()); // 数值类型按数值大小比较,忽略精度差异(133.0000 == 133.00) - if (sameLogValue(oldValue, newValue)) { + if (LogValueUtils.sameLogValue(oldValue, newValue)) { continue; } details.add(buildQecDetail(null, QEC_HOUR_TABLE_NAME, stcd, column, @@ -6865,24 +6866,6 @@ public class EngEqDataServiceImpl implements EngEqDataService { batchInsertQecLogDetails(details); } - private boolean sameLogValue(Object oldValue, Object newValue) { - if (oldValue == null && newValue == null) { - return true; - } - if (oldValue == null || newValue == null) { - return false; - } - // BigDecimal/Double 等数值类型按数值大小比较,忽略精度差异(133.0000 == 133.00) - if (oldValue instanceof Number && newValue instanceof Number) { - try { - return new BigDecimal(oldValue.toString()).compareTo(new BigDecimal(newValue.toString())) == 0; - } catch (NumberFormatException e) { - return false; - } - } - return StrUtil.equals(formatLogValue(oldValue), formatLogValue(newValue)); - } - @Override @Transactional(rollbackFor = Exception.class) public boolean removeKendoByIds(BatchDeleteAo batchDeleteAo) { @@ -7182,10 +7165,10 @@ public class EngEqDataServiceImpl implements EngEqDataService { detail.setStationCode(stationCode); detail.setFieldCode(fieldCode); detail.setFieldMeaning(QEC_FIELD_MEANING_MAP.getOrDefault(fieldCode, fieldCode)); - detail.setOldValueCode(formatLogValue(oldValueCode)); - detail.setOldValueName(formatLogValue(oldValueName)); - detail.setNewValueCode(formatLogValue(newValueCode)); - detail.setNewValueName(formatLogValue(newValueName)); + detail.setOldValueCode(LogValueUtils.formatLogValue(oldValueCode)); + detail.setOldValueName(LogValueUtils.formatLogValue(oldValueName)); + detail.setNewValueCode(LogValueUtils.formatLogValue(newValueCode)); + detail.setNewValueName(LogValueUtils.formatLogValue(newValueName)); detail.setRemark(remark); return detail; } @@ -7203,7 +7186,7 @@ public class EngEqDataServiceImpl implements EngEqDataService { } private Object resolveQecDisplayValue(String fieldCode, Object rawValue) { - String text = formatLogValue(rawValue); + String text = LogValueUtils.formatLogValue(rawValue); if (StrUtil.isBlank(text)) { return rawValue; } @@ -7232,16 +7215,6 @@ public class EngEqDataServiceImpl implements EngEqDataService { } } - private String formatLogValue(Object value) { - if (value == null) { - return null; - } - if (value instanceof Date date) { - return DateUtil.formatDateTime(date); - } - return String.valueOf(value); - } - private Integer parseInteger(Object value) { if (value == null) { return null; diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java index 810fdda9..e6631b27 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java @@ -16,6 +16,7 @@ import com.yfd.platform.qgc_base.mapper.MsOperationLogMapper; import com.yfd.platform.qgc_env.wt.entity.vo.SdWtvtYearVo; import com.yfd.platform.qgc_env.wt.mapper.SdWtvtRMapper; import com.yfd.platform.qgc_env.wt.service.SdWtvtRService; +import com.yfd.platform.utils.LogValueUtils; import com.yfd.platform.utils.QgcQueryWrapperUtil; import com.yfd.platform.utils.SecurityUtils; import jakarta.annotation.Resource; @@ -237,7 +238,7 @@ public class SdWtvtRServiceImpl extends ServiceImpl Map beforeRow = beforeRowMap.get(depthKey); Object oldValue = beforeRow == null ? null : beforeRow.get("VWT"); Object newValue = item.get("vwt"); - if (StringUtils.equals(formatWtvtLogValue(oldValue), formatWtvtLogValue(newValue))) { + if (LogValueUtils.sameLogValue(oldValue, newValue)) { return; } logDetails.add(buildWtvtDetail(null, stcd, "VWT", diff --git a/backend/src/main/java/com/yfd/platform/utils/LogValueUtils.java b/backend/src/main/java/com/yfd/platform/utils/LogValueUtils.java new file mode 100644 index 00000000..cd97a15f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/LogValueUtils.java @@ -0,0 +1,67 @@ +package com.yfd.platform.utils; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 日志值比较与格式化工具类,供各业务模块(生态流量、垂向水温等)操作日志记录时共用。 + * + * @author supeng + */ +public class LogValueUtils { + + private LogValueUtils() { + } + + /** + * 比较新旧两个值是否相同,用于判断字段值是否发生变化。 + *

+ * 规则: + *

    + *
  • 两者都为 null 视为相同
  • + *
  • 一者为 null 视为不同
  • + *
  • BigDecimal/Double 等数值类型按数值大小比较,忽略精度差异(133.0000 == 133.00)
  • + *
  • 其余类型按 {@link #formatLogValue(Object)} 格式化后字符串比较
  • + *
+ * + * @param oldValue 旧值 + * @param newValue 新值 + * @return true 表示相同 + */ + public static boolean sameLogValue(Object oldValue, Object newValue) { + if (oldValue == null && newValue == null) { + return true; + } + if (oldValue == null || newValue == null) { + return false; + } + // BigDecimal/Double 等数值类型按数值大小比较,忽略精度差异(133.0000 == 133.00) + if (oldValue instanceof Number && newValue instanceof Number) { + try { + return new BigDecimal(oldValue.toString()).compareTo(new BigDecimal(newValue.toString())) == 0; + } catch (NumberFormatException e) { + return false; + } + } + return StrUtil.equals(formatLogValue(oldValue), formatLogValue(newValue)); + } + + /** + * 格式化日志中的值:Date 格式化为日期时间字符串,其余直接转字符串。 + * + * @param value 原始值 + * @return 格式化后的字符串 + */ + public static String formatLogValue(Object value) { + if (value == null) { + return null; + } + if (value instanceof Date date) { + return DateUtil.formatDateTime(date); + } + return String.valueOf(value); + } +} From bb7ec04a388061cf797ff3ff3204a7e5b59c84f3 Mon Sep 17 00:00:00 2001 From: tangwei Date: Thu, 20 Aug 2026 12:26:38 +0800 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20=E5=9E=82=E5=90=91=E6=B0=B4?= =?UTF-8?q?=E6=B8=A9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wt/service/impl/SdWtvtRServiceImpl.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java index e6631b27..a185a842 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/wt/service/impl/SdWtvtRServiceImpl.java @@ -252,7 +252,7 @@ public class SdWtvtRServiceImpl extends ServiceImpl } batchUpdateWtvtRData(dataList); if (CollUtil.isNotEmpty(logDetails)) { - MsOperationLog mainLog = buildWtvtMainLog( stcd,"修改垂向水温数据", source); + MsOperationLog mainLog = buildWtvtMainLog( stcd,"修改垂向水温数据", source,"02"); msOperationLogMapper.insert(mainLog); for (MsOperationLogDetail detail : logDetails) { detail.setMainId(mainLog.getId()); @@ -438,12 +438,12 @@ public class SdWtvtRServiceImpl extends ServiceImpl List params = new ArrayList<>(); List conditions = new ArrayList<>(); for (DataParam item : dataParamList) { - if (item == null || StringUtils.isBlank(item.getId()) || StringUtils.isBlank(item.getDt())) { + if (item == null || StringUtils.isBlank(item.getId()) || StringUtils.isBlank(item.getTm())) { continue; } conditions.add("(STCD = ? AND TM = TO_DATE(SUBSTR(?, 1, 19), 'YYYY-MM-DD HH24:MI:SS'))"); params.add(item.getId()); - params.add(item.getDt()); + params.add(item.getTm()); } if (conditions.isEmpty()) { return new ArrayList<>(); @@ -487,12 +487,13 @@ public class SdWtvtRServiceImpl extends ServiceImpl if (CollUtil.isEmpty(rows)) { return; } - MsOperationLog mainLog = buildWtvtMainLog(null,remark, source); - msOperationLogMapper.insert(mainLog); List details = new ArrayList<>(); for (Map row : rows) { String stcd = asString(row.get("STCD")); +// String recordId = asString(row.get("ID")); Object depth = row.get("WTHG"); + MsOperationLog mainLog = buildWtvtMainLog(stcd, remark, source, "03"); + msOperationLogMapper.insert(mainLog); for (Map.Entry entry : row.entrySet()) { String fieldCode = entry.getKey(); Object oldValue = entry.getValue(); @@ -507,11 +508,12 @@ public class SdWtvtRServiceImpl extends ServiceImpl batchInsertWtvtLogDetails(details); } - private MsOperationLog buildWtvtMainLog(String recordId,String remark, String source) { + private MsOperationLog buildWtvtMainLog(String recordId,String remark, String source,String operationType) { MsOperationLog log = new MsOperationLog(); log.setOperator(resolveWtvtLogOperator()); log.setOperateTime(new Date()); log.setTableName(WT_CX_TABLE_NAME); + log.setOperationType(operationType); log.setRecordId(recordId); log.setSource(StringUtils.trimToNull(source)); log.setRemark(remark); From 4c500b1fd8a6763e9708e639d735cdef6fb55e9e Mon Sep 17 00:00:00 2001 From: tangwei Date: Thu, 20 Aug 2026 13:41:03 +0800 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E5=85=A8?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E9=B1=BC=E7=B1=BB=E6=94=BE=E9=B1=BC=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E6=95=B0=E6=8D=AE&=E5=85=A8=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=E9=B1=BC=E7=B1=BB=E6=94=BE=E9=B1=BC=E6=95=B0=E6=8D=AE=E6=98=8E?= =?UTF-8?q?=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fb/controller/FbStationController.java | 14 + .../fb/entity/vo/FbFishRunQgcDetailVo.java | 69 ++++ .../qgc_env/fb/service/FbStationService.java | 14 + .../fb/service/impl/FbStationServiceImpl.java | 337 +++++++++++++++++- 4 files changed, 426 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcDetailVo.java diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/controller/FbStationController.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/controller/FbStationController.java index 35fab7a8..79fcd23a 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/fb/controller/FbStationController.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/controller/FbStationController.java @@ -214,6 +214,12 @@ public class FbStationController { return ResponseResult.successData(fbStationService.getFishRunQgcKendoListCust(dataSourceRequest)); } + @PostMapping("/fishrun/qgc/detail/GetKendoListCust") + @Operation(summary = "全过程鱼类放鱼数据明细(细粒度,按站点+年份+鱼种下钻)") + public ResponseResult getFishRunQgcDetailKendoListCust(@RequestBody DataSourceRequest dataSourceRequest) { + return ResponseResult.successData(fbStationService.getFishRunQgcDetailKendoListCust(dataSourceRequest)); + } + @Log(module = "鱼类增殖站模块", value = "删除全过程鱼类放鱼数据") @PostMapping("/fishrun/qgc/removeFishRunQgcData") @Operation(summary = "删除全过程鱼类放鱼数据(按站点+年份+鱼种)") @@ -222,6 +228,14 @@ public class FbStationController { return ResponseResult.success(); } + @Log(module = "鱼类增殖站模块", value = "删除全过程鱼类放鱼明细数据") + @PostMapping("/fishrun/qgc/removeFishRunQgcDetailData") + @Operation(summary = "删除全过程鱼类放鱼明细数据(按明细ID批量)") + public ResponseResult removeFishRunQgcDetailData(@RequestBody List ids) { + fbStationService.removeFishRunQgcDetailData(ids); + return ResponseResult.success(); + } + @PostMapping("/fishType/GetKendoListCust") @Operation(summary = "增殖站鱼种完成情况统计") public ResponseResult getFishTypeKendoListCust(@RequestBody DataSourceRequest dataSourceRequest) { diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcDetailVo.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcDetailVo.java new file mode 100644 index 00000000..11959b6f --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcDetailVo.java @@ -0,0 +1,69 @@ +package com.yfd.platform.qgc_env.fb.entity.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 全过程鱼类放鱼数据明细(细粒度),对应 SD_RPIMNFISH_R 明细行。 + */ +@Data +@Schema(description = "全过程鱼类放鱼数据明细") +public class FbFishRunQgcDetailVo { + + @Schema(description = "明细ID (SD_RPIMNFISH_R.ID)") + private String id; + + @Schema(description = "放流主表ID (SD_ENGFR_R.ID)") + private String rpimnId; + + @Schema(description = "电站站码") + private String stcd; + + @Schema(description = "电站名称") + private String stnm; + + @Schema(description = "放流年份 yyyy") + private String flyr; + + @Schema(description = "鱼种类") + private String ftp; + + @Schema(description = "鱼种类名称") + private String ftpName; + + @Schema(description = "平均长度") + private BigDecimal fsZ; + + @Schema(description = "平均体重") + private BigDecimal fwet; + + @Schema(description = "计划放殖鱼数量") + private Long fcnt; + + @Schema(description = "标记类型") + private String tagte; + + @Schema(description = "标记数量") + private Long tagcnt; + + @Schema(description = "鱼龄") + private String fage; + + @Schema(description = "鱼类档案ID") + private String fid; + + @Schema(description = "保护等级") + private String protectlvl; + + @Schema(description = "亲本来源") + private String fa; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "放流时间") + private Date tm; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java index a4b5bc41..93aaf507 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java @@ -21,6 +21,13 @@ public interface FbStationService { DataSourceResult getFishRunQgcKendoListCust(DataSourceRequest dataSourceRequest); + /** + * 查询全过程鱼类放鱼数据明细(细粒度),用于聚合行下钻查看底层放鱼明细。 + * + * @param dataSourceRequest 支持 stcd/flyr/ftp 等过滤条件与分页排序 + */ + DataSourceResult getFishRunQgcDetailKendoListCust(DataSourceRequest dataSourceRequest); + /** * 批量删除全过程鱼类放鱼数据(按 站点+年份+鱼种 维度逻辑删除 SD_RPIMNFISH_R 明细) * @@ -28,6 +35,13 @@ public interface FbStationService { */ boolean removeFishRunQgcData(List params); + /** + * 按明细ID批量逻辑删除全过程鱼类放鱼数据,并记录操作日志。 + * + * @param ids 明细ID列表(SD_RPIMNFISH_R.ID) + */ + boolean removeFishRunQgcDetailData(List ids); + DataSourceResult getFishTypeKendoListCust(DataSourceRequest dataSourceRequest); DataSourceResult getFishTypeStcdList(DataSourceRequest dataSourceRequest); diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java index e49e5681..1e9a6304 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java @@ -195,6 +195,74 @@ public class FbStationServiceImpl implements FbStationService { return result; } + @Override + public DataSourceResult getFishRunQgcDetailKendoListCust(DataSourceRequest dataSourceRequest) { + DataSourceLoadOptionsBase loadOptions = dataSourceRequest == null ? null : dataSourceRequest.toDevRequest(); + String stcd = loadOptions == null ? null : QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "stcd"); + String flyr = loadOptions == null ? null : QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "flyr"); + String ftp = loadOptions == null ? null : QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "ftp"); + + Map paramMap = new HashMap<>(); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT det.ID AS id, ") + .append("run.ID AS rpimnId, ") + .append("run.STCD AS stcd, ") + .append("eng.ENNM AS stnm, ") + .append("TO_CHAR(run.TM, 'YYYY') AS flyr, ") + .append("det.FTP AS ftp, ") + .append(buildFishNameExpr("det.FTP")).append(" AS ftpName, ") + .append("det.FSZ AS fsZ, ") + .append("det.FWET AS fwet, ") + .append("det.FCNT AS fcnt, ") + .append("det.TAGTE AS tagte, ") + .append("det.TAGCNT AS tagcnt, ") + .append("det.FAGE AS fage, ") + .append("det.FID AS fid, ") + .append("det.PROTECTLVL AS protectlvl, ") + .append("det.FA AS fa, ") + .append("det.REMARK AS remark, ") + .append("run.TM AS tm ") + .append("FROM SD_RPIMNFISH_R det ") + .append("INNER JOIN SD_ENGFR_R run ON det.RPIMN_ID = run.ID AND NVL(run.IS_DELETED, 0) = 0 ") + .append("LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = run.STCD AND NVL(eng.IS_DELETED, 0) = 0 ") + .append(buildFishJoinSql("det.FTP", "eng.HBRVCD")) + .append("WHERE NVL(det.IS_DELETED, 0) = 0 "); + if (StrUtil.isNotBlank(stcd)) { + paramMap.put("detailStcd", stcd); + sql.append("AND run.STCD = #{map.detailStcd} "); + } + if (StrUtil.isNotBlank(flyr)) { + paramMap.put("detailFlyr", flyr); + sql.append("AND TO_CHAR(run.TM, 'YYYY') = #{map.detailFlyr} "); + } + if (StrUtil.isNotBlank(ftp)) { + paramMap.put("detailFtp", ftp); + sql.append("AND det.FTP = #{map.detailFtp} "); + } + String filterSql = buildFishRunQgcDetailFilterCondition( + dataSourceRequest == null ? null : dataSourceRequest.getFilter(), + paramMap, + new int[]{0} + ); + if (StrUtil.isNotBlank(filterSql)) { + sql.append(" AND ").append(filterSql).append(" "); + } + sql.append(buildFishRunQgcDetailOrderBySql(dataSourceRequest == null ? null : dataSourceRequest.getSort())); + + Page page = loadOptions == null ? null : QgcQueryWrapperUtil.buildPage(loadOptions, loadOptions.getSkip(), loadOptions.getTake()); + List list = microservicDynamicSQLMapper.pageAllListWithResultType( + page, + sql.toString(), + paramMap, + FbFishRunQgcDetailVo.class + ); + DataSourceResult result = new DataSourceResult<>(); + result.setData(list); + result.setTotal(page != null ? page.getTotal() : list.size()); + result.setAggregates(new HashMap<>()); + return result; + } + @Override @Transactional(rollbackFor = Exception.class) public boolean removeFishRunQgcData(List params) { @@ -224,19 +292,67 @@ public class FbStationServiceImpl implements FbStationService { " AND TO_CHAR(run.TM, 'YYYY') = ? )", resolveLogOperator(), param.getFtp(), param.getStcd(), param.getFlyr() ); - recordFishRunDeleteLog(beforeRows, param, deleteCount); + recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(站点:%s,年份:%s,删除条数:%d)", + param.getStcd(), param.getFlyr(), deleteCount), deleteCount); } return true; } + @Override + @Transactional(rollbackFor = Exception.class) + public boolean removeFishRunQgcDetailData(List ids) { + if (CollUtil.isEmpty(ids)) { + return true; + } + List distinctIds = ids.stream() + .filter(StrUtil::isNotBlank) + .distinct() + .collect(Collectors.toList()); + if (distinctIds.isEmpty()) { + return true; + } + // 1. 查询删除前的明细行(含站点等信息, 用于记录日志) + List> beforeRows = queryFishRunDetailRowsByIds(distinctIds); + // 2. 分批逻辑删除(ID IN, 防止超过数据库 IN 限制) + int deleteCount = 0; + for (List part : CollUtil.split(distinctIds, 500)) { + deleteCount += batchSoftDeleteFishRunDetail(part); + } + // 3. 记录删除日志(每条明细一条主日志, 子日志挂对应主日志) + recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(明细删除,删除条数:%d)", deleteCount), deleteCount); + return true; + } + private List> queryFishRunDetailRows(FbFishRunQgcDeleteParam param) { - String sql = "SELECT det.ID, det.RPIMN_ID, det.FTP, det.FSZ, det.FWET, det.FCNT, " + + String sql = "SELECT det.ID, det.RPIMN_ID, run.STCD, det.FTP, det.FSZ, det.FWET, det.FCNT, " + "det.TAGTE, det.TAGCNT, det.FAGE, det.FID, det.PROTECTLVL, det.FA, det.REMARK " + "FROM SD_RPIMNFISH_R det " + "INNER JOIN SD_ENGFR_R run ON det.RPIMN_ID = run.ID AND NVL(run.IS_DELETED, 0) = 0 " + "WHERE NVL(det.IS_DELETED, 0) = 0 " + " AND det.FTP = ? AND run.STCD = ? AND TO_CHAR(run.TM, 'YYYY') = ?"; - List> rows = jdbcTemplate.queryForList(sql, param.getFtp(), param.getStcd(), param.getFlyr()); + return queryFishRunDetailRowsInternal(sql, param.getFtp(), param.getStcd(), param.getFlyr()); + } + + private List> queryFishRunDetailRowsByIds(List ids) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT det.ID, det.RPIMN_ID, run.STCD, det.FTP, det.FSZ, det.FWET, det.FCNT, ") + .append("det.TAGTE, det.TAGCNT, det.FAGE, det.FID, det.PROTECTLVL, det.FA, det.REMARK ") + .append("FROM SD_RPIMNFISH_R det ") + .append("INNER JOIN SD_ENGFR_R run ON det.RPIMN_ID = run.ID AND NVL(run.IS_DELETED, 0) = 0 ") + .append("WHERE NVL(det.IS_DELETED, 0) = 0 ") + .append(" AND det.ID IN ("); + for (int i = 0; i < ids.size(); i++) { + if (i > 0) { + sql.append(", "); + } + sql.append("?"); + } + sql.append(")"); + return queryFishRunDetailRowsInternal(sql.toString(), ids.toArray()); + } + + private List> queryFishRunDetailRowsInternal(String sql, Object... args) { + List> rows = jdbcTemplate.queryForList(sql, args); List> normalized = new ArrayList<>(); for (Map row : rows) { normalized.add(normalizeFishRunLogRow(row)); @@ -244,6 +360,28 @@ public class FbStationServiceImpl implements FbStationService { return normalized; } + private int batchSoftDeleteFishRunDetail(List ids) { + if (CollUtil.isEmpty(ids)) { + return 0; + } + StringBuilder sql = new StringBuilder(); + sql.append("UPDATE SD_RPIMNFISH_R det ") + .append("SET det.IS_DELETED = 1, det.DELETE_USER = ?, det.DELETE_TIME = SYSDATE ") + .append("WHERE NVL(det.IS_DELETED, 0) = 0 ") + .append(" AND det.ID IN ("); + List params = new ArrayList<>(); + params.add(resolveLogOperator()); + for (int i = 0; i < ids.size(); i++) { + if (i > 0) { + sql.append(", "); + } + sql.append("?"); + params.add(ids.get(i)); + } + sql.append(")"); + return jdbcTemplate.update(sql.toString(), params.toArray()); + } + private Map normalizeFishRunLogRow(Map row) { Map normalized = new LinkedHashMap<>(); if (row == null || row.isEmpty()) { @@ -255,19 +393,20 @@ public class FbStationServiceImpl implements FbStationService { return normalized; } - private void recordFishRunDeleteLog(List> rows, FbFishRunQgcDeleteParam param, int deleteCount) { + private void recordFishRunDeleteLog(List> rows, String remark, int deleteCount) { if (CollUtil.isEmpty(rows)) { return; } List details = new ArrayList<>(); for (Map row : rows) { + String recordId = asString(row.get("ID")); + String stcd = asString(row.get("STCD")); MsOperationLog mainLog = new MsOperationLog(); mainLog.setOperator(resolveLogOperator()); - mainLog.setRecordId(String.valueOf(row.get("ID"))); + mainLog.setRecordId(recordId); mainLog.setOperateTime(new Date()); mainLog.setTableName(FISH_RUN_DETAIL_TABLE_NAME); - mainLog.setRemark("删除全过程鱼类放鱼数据(站点:" + param.getStcd() - + ",年份:" + param.getFlyr() + ",删除条数:" + deleteCount + ")"); + mainLog.setRemark(remark); msOperationLogMapper.insert(mainLog); for (Map.Entry entry : row.entrySet()) { if (StrUtil.isBlank(entry.getKey()) || entry.getValue() == null) { @@ -276,7 +415,7 @@ public class FbStationServiceImpl implements FbStationService { MsOperationLogDetail detail = new MsOperationLogDetail(); detail.setMainId(mainLog.getId()); detail.setTableName(FISH_RUN_DETAIL_TABLE_NAME); - detail.setStationCode(param.getStcd()); + detail.setStationCode(stcd); detail.setFieldCode(entry.getKey()); detail.setFieldMeaning(FISH_RUN_FIELD_MEANING_MAP.getOrDefault(entry.getKey(), entry.getKey())); detail.setOldValueCode(formatFishRunLogValue(entry.getValue())); @@ -290,6 +429,10 @@ public class FbStationServiceImpl implements FbStationService { } } + private String asString(Object value) { + return value == null ? null : String.valueOf(value); + } + private String resolveLogOperator() { try { return SecurityUtils.getCurrentUsername(); @@ -1412,6 +1555,184 @@ public class FbStationServiceImpl implements FbStationService { return " ORDER BY " + String.join(", ", orderByParts); } + private String mapFishRunQgcDetailColumn(String field) { + if (StrUtil.isBlank(field)) { + return null; + } + return switch (field) { + case "id" -> "det.ID"; + case "rpimnId" -> "det.RPIMN_ID"; + case "stcd" -> "run.STCD"; + case "stnm" -> "eng.ENNM"; + case "flyr" -> "TO_CHAR(run.TM, 'YYYY')"; + case "ftp" -> "det.FTP"; + case "ftpName" -> buildFishNameExpr("det.FTP"); + case "fsZ" -> "det.FSZ"; + case "fwet" -> "det.FWET"; + case "fcnt" -> "det.FCNT"; + case "tagte" -> "det.TAGTE"; + case "tagcnt" -> "det.TAGCNT"; + case "fage" -> "det.FAGE"; + case "fid" -> "det.FID"; + case "protectlvl" -> "det.PROTECTLVL"; + case "fa" -> "det.FA"; + case "remark" -> "det.REMARK"; + case "tm" -> "run.TM"; + default -> null; + }; + } + + private Object normalizeFishRunQgcDetailValue(String field, Object value) { + if (value == null) { + return null; + } + if ("fcnt".equals(field) || "tagcnt".equals(field)) { + try { + return Long.parseLong(String.valueOf(value).trim()); + } catch (Exception ex) { + return null; + } + } + return value; + } + + private String buildFishRunQgcDetailFilterCondition(DataSourceRequest.FilterDescriptor filter, + Map paramMap, + int[] indexHolder) { + if (filter == null) { + return ""; + } + if (StrUtil.isNotBlank(filter.getField())) { + return buildFishRunQgcDetailLeafCondition(filter, paramMap, indexHolder); + } + if (CollUtil.isEmpty(filter.getFilters())) { + return ""; + } + List conditions = new ArrayList<>(); + for (DataSourceRequest.FilterDescriptor child : filter.getFilters()) { + String childSql = buildFishRunQgcDetailFilterCondition(child, paramMap, indexHolder); + if (StrUtil.isNotBlank(childSql)) { + conditions.add("(" + childSql + ")"); + } + } + if (conditions.isEmpty()) { + return ""; + } + String logic = "or".equalsIgnoreCase(filter.getLogic()) ? " OR " : " AND "; + return String.join(logic, conditions); + } + + private String buildFishRunQgcDetailLeafCondition(DataSourceRequest.FilterDescriptor filter, + Map paramMap, + int[] indexHolder) { + // 这三个字段已在主 SQL 中作为下钻条件处理,避免重复拼接 + if ("stcd".equalsIgnoreCase(filter.getField()) || "flyr".equalsIgnoreCase(filter.getField()) + || "ftp".equalsIgnoreCase(filter.getField())) { + return ""; + } + String column = mapFishRunQgcDetailColumn(filter.getField()); + if (StrUtil.isBlank(column)) { + return ""; + } + String operator = StrUtil.blankToDefault(filter.getOperator(), "eq").toLowerCase(); + if ("isnull".equals(operator)) { + return column + " IS NULL"; + } + if ("isnotnull".equals(operator)) { + return column + " IS NOT NULL"; + } + if ("isempty".equals(operator)) { + return "(" + column + " IS NULL OR " + column + " = '')"; + } + if ("isnotempty".equals(operator)) { + return "(" + column + " IS NOT NULL AND " + column + " <> '')"; + } + if ("in".equals(operator)) { + List values = normalizeQgcBsmfRValues(filter.getValue()); + if (values.isEmpty()) { + return ""; + } + List placeholders = new ArrayList<>(); + for (Object value : values) { + String key = "fishRunQgcDetailP" + indexHolder[0]++; + paramMap.put(key, normalizeFishRunQgcDetailValue(filter.getField(), value)); + placeholders.add("#{map." + key + "}"); + } + return column + " IN (" + String.join(", ", placeholders) + ")"; + } + Object value = filter.getValue(); + if (value == null) { + return ""; + } + String key = "fishRunQgcDetailP" + indexHolder[0]++; + Object normalizedValue = normalizeFishRunQgcDetailValue(filter.getField(), value); + return switch (operator) { + case "eq" -> { + paramMap.put(key, normalizedValue); + yield column + " = #{map." + key + "}"; + } + case "neq" -> { + paramMap.put(key, normalizedValue); + yield column + " <> #{map." + key + "}"; + } + case "gt" -> { + paramMap.put(key, normalizedValue); + yield column + " > #{map." + key + "}"; + } + case "gte" -> { + paramMap.put(key, normalizedValue); + yield column + " >= #{map." + key + "}"; + } + case "lt" -> { + paramMap.put(key, normalizedValue); + yield column + " < #{map." + key + "}"; + } + case "lte" -> { + paramMap.put(key, normalizedValue); + yield column + " <= #{map." + key + "}"; + } + case "contains" -> { + paramMap.put(key, "%" + String.valueOf(normalizedValue).trim() + "%"); + yield column + " LIKE #{map." + key + "}"; + } + case "doesnotcontain" -> { + paramMap.put(key, "%" + String.valueOf(normalizedValue).trim() + "%"); + yield column + " NOT LIKE #{map." + key + "}"; + } + case "startswith" -> { + paramMap.put(key, String.valueOf(normalizedValue).trim() + "%"); + yield column + " LIKE #{map." + key + "}"; + } + case "endswith" -> { + paramMap.put(key, "%" + String.valueOf(normalizedValue).trim()); + yield column + " LIKE #{map." + key + "}"; + } + default -> ""; + }; + } + + private String buildFishRunQgcDetailOrderBySql(List sorts) { + if (CollUtil.isEmpty(sorts)) { + return " ORDER BY run.TM DESC, det.ID DESC"; + } + List orderByParts = new ArrayList<>(); + for (DataSourceRequest.SortDescriptor sort : sorts) { + if (sort == null || StrUtil.isBlank(sort.getField())) { + continue; + } + String column = mapFishRunQgcDetailColumn(sort.getField()); + if (StrUtil.isBlank(column)) { + continue; + } + String direction = "desc".equalsIgnoreCase(sort.getDir()) ? "DESC" : "ASC"; + orderByParts.add(column + " " + direction); + } + if (orderByParts.isEmpty()) { + return " ORDER BY run.TM DESC, det.ID DESC"; + } + return " ORDER BY " + String.join(", ", orderByParts); + } + private Date[] parseFishRunFlyrRange(String flyrFilter) { if (StrUtil.isBlank(flyrFilter)) { return new Date[]{null, null}; From 84dfc91ffe5a049f54759219cecdd5e909384665 Mon Sep 17 00:00:00 2001 From: tangwei Date: Fri, 21 Aug 2026 09:04:30 +0800 Subject: [PATCH 04/12] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=85=A8?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E9=B1=BC=E7=B1=BB=E6=94=BE=E9=B1=BC=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/vo/BatchDeleteByIdsAo.java | 23 +++ .../impl/MsOperationLogServiceImpl.java | 2 +- .../fb/controller/FbStationController.java | 17 ++- .../entity/vo/FbFishRunQgcOperateRequest.java | 24 ++++ .../qgc_env/fb/service/FbStationService.java | 13 +- .../fb/service/impl/FbStationServiceImpl.java | 132 +++++++++++++++++- 6 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/com/yfd/platform/qgc_base/entity/vo/BatchDeleteByIdsAo.java create mode 100644 backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcOperateRequest.java diff --git a/backend/src/main/java/com/yfd/platform/qgc_base/entity/vo/BatchDeleteByIdsAo.java b/backend/src/main/java/com/yfd/platform/qgc_base/entity/vo/BatchDeleteByIdsAo.java new file mode 100644 index 00000000..5f87ad38 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_base/entity/vo/BatchDeleteByIdsAo.java @@ -0,0 +1,23 @@ +package com.yfd.platform.qgc_base.entity.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +@Schema(description = "批量删除参数") +public class BatchDeleteByIdsAo implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "数据来源") + private String source; + + @Schema(description = "数据类型") + private String dataType; + + @Schema(description = "删除数据列表") + private List ids; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_base/service/impl/MsOperationLogServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_base/service/impl/MsOperationLogServiceImpl.java index 20ead32a..4dc418a3 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_base/service/impl/MsOperationLogServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_base/service/impl/MsOperationLogServiceImpl.java @@ -85,7 +85,7 @@ public class MsOperationLogServiceImpl extends ServiceImpl ids) { - fbStationService.removeFishRunQgcDetailData(ids); + public ResponseResult removeFishRunQgcDetailData(@RequestBody BatchDeleteByIdsAo batchDeleteByIdsAo) { + fbStationService.removeFishRunQgcDetailData(batchDeleteByIdsAo); + return ResponseResult.success(); + } + + @Log(module = "鱼类增殖站模块", value = "修改全过程鱼类放鱼明细数据") + @PostMapping("/fishrun/qgc/updateFishRunQgcDetailData") + @Operation(summary = "修改全过程鱼类放鱼明细数据(按明细ID)") + public ResponseResult updateFishRunQgcDetailData(@RequestBody FbFishRunQgcOperateRequest operateRequest) { + fbStationService.updateFishRunQgcDetailData( + operateRequest == null ? null : operateRequest.getUpdateData(), + operateRequest == null ? null : operateRequest.getSource() + ); return ResponseResult.success(); } diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcOperateRequest.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcOperateRequest.java new file mode 100644 index 00000000..004f2a35 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/entity/vo/FbFishRunQgcOperateRequest.java @@ -0,0 +1,24 @@ +package com.yfd.platform.qgc_env.fb.entity.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +/** + * 全过程鱼类放鱼明细数据操作请求 + *

updateData: 修改数据,其中 id 为定位条件,其余 key 对应明细表可修改字段

+ */ +@Data +@Schema(description = "全过程鱼类放鱼明细数据操作请求") +public class FbFishRunQgcOperateRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "数据来源") + private String source; + + @Schema(description = "修改数据") + private Map updateData; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java index 93aaf507..8896c6ed 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/FbStationService.java @@ -2,6 +2,7 @@ package com.yfd.platform.qgc_env.fb.service; import com.yfd.platform.common.DataSourceRequest; import com.yfd.platform.common.DataSourceResult; +import com.yfd.platform.qgc_base.entity.vo.BatchDeleteByIdsAo; import com.yfd.platform.qgc_env.fb.entity.vo.*; import java.util.List; @@ -38,9 +39,17 @@ public interface FbStationService { /** * 按明细ID批量逻辑删除全过程鱼类放鱼数据,并记录操作日志。 * - * @param ids 明细ID列表(SD_RPIMNFISH_R.ID) + * @param batchDeleteByIdsAo 删除参数(ids + source) */ - boolean removeFishRunQgcDetailData(List ids); + boolean removeFishRunQgcDetailData(BatchDeleteByIdsAo batchDeleteByIdsAo); + + /** + * 修改全过程鱼类放鱼明细数据(按明细ID定位),并记录操作日志。 + * + * @param updateData 修改数据,id 为定位条件,其余 key 为可修改字段 + * @param source 数据来源 + */ + boolean updateFishRunQgcDetailData(Map updateData, String source); DataSourceResult getFishTypeKendoListCust(DataSourceRequest dataSourceRequest); diff --git a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java index 1e9a6304..5309b35d 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_env/fb/service/impl/FbStationServiceImpl.java @@ -11,6 +11,8 @@ import com.yfd.platform.common.Group; 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_base.entity.vo.BatchDeleteByIdsAo; import com.yfd.platform.qgc_env.fb.entity.vo.*; import com.yfd.platform.qgc_env.fb.service.FbStationService; import com.yfd.platform.qgc_env.wt.utils.SiteAvoidanceUtils; @@ -18,6 +20,7 @@ import com.yfd.platform.qgc_base.domain.MsOperationLog; import com.yfd.platform.qgc_base.domain.MsOperationLogDetail; import com.yfd.platform.qgc_base.mapper.MsOperationLogDetailMapper; import com.yfd.platform.qgc_base.mapper.MsOperationLogMapper; +import com.yfd.platform.utils.LogValueUtils; import com.yfd.platform.utils.QgcQueryWrapperUtil; import com.yfd.platform.utils.SecurityUtils; import jakarta.annotation.Resource; @@ -63,6 +66,25 @@ public class FbStationServiceImpl implements FbStationService { FISH_RUN_FIELD_MEANING_MAP.put("REMARK", "备注"); } + /** + * 全过程鱼类放鱼明细可修改字段映射(前端入参 key -> 数据库列) + */ + private static final LinkedHashMap FISH_RUN_UPDATE_COLUMN_MAP = new LinkedHashMap<>(); + + static { + FISH_RUN_UPDATE_COLUMN_MAP.put("ftp", "FTP"); + FISH_RUN_UPDATE_COLUMN_MAP.put("fsZ", "FSZ"); + FISH_RUN_UPDATE_COLUMN_MAP.put("fwet", "FWET"); + FISH_RUN_UPDATE_COLUMN_MAP.put("fcnt", "FCNT"); + FISH_RUN_UPDATE_COLUMN_MAP.put("tagte", "TAGTE"); + FISH_RUN_UPDATE_COLUMN_MAP.put("tagcnt", "TAGCNT"); + FISH_RUN_UPDATE_COLUMN_MAP.put("fage", "FAGE"); + FISH_RUN_UPDATE_COLUMN_MAP.put("fid", "FID"); + FISH_RUN_UPDATE_COLUMN_MAP.put("protectlvl", "PROTECTLVL"); + FISH_RUN_UPDATE_COLUMN_MAP.put("fa", "FA"); + FISH_RUN_UPDATE_COLUMN_MAP.put("remark", "REMARK"); + } + @Override public List getYearRpStatistics(String year) { Map paramMap = new HashMap<>(); @@ -293,14 +315,15 @@ public class FbStationServiceImpl implements FbStationService { resolveLogOperator(), param.getFtp(), param.getStcd(), param.getFlyr() ); recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(站点:%s,年份:%s,删除条数:%d)", - param.getStcd(), param.getFlyr(), deleteCount), deleteCount); + param.getStcd(), param.getFlyr(), deleteCount),null, deleteCount); } return true; } @Override @Transactional(rollbackFor = Exception.class) - public boolean removeFishRunQgcDetailData(List ids) { + public boolean removeFishRunQgcDetailData(BatchDeleteByIdsAo batchDeleteByIdsAo) { + List ids = batchDeleteByIdsAo.getIds(); if (CollUtil.isEmpty(ids)) { return true; } @@ -319,10 +342,109 @@ public class FbStationServiceImpl implements FbStationService { deleteCount += batchSoftDeleteFishRunDetail(part); } // 3. 记录删除日志(每条明细一条主日志, 子日志挂对应主日志) - recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(明细删除,删除条数:%d)", deleteCount), deleteCount); + recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(明细删除,删除条数:%d)", deleteCount),batchDeleteByIdsAo.getSource(), deleteCount); return true; } + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateFishRunQgcDetailData(Map updateData, String source) { + if (updateData == null || updateData.isEmpty()) { + return true; + } + String id = asString(updateData.get("id")); + if (StrUtil.isBlank(id)) { + throw new BizException("明细ID不能为空"); + } + // 1. 查询修改前的明细行(用于记录日志) + Map beforeRow = querySingleFishRunDetailRow(id); + if (beforeRow == null || beforeRow.isEmpty()) { + throw new BizException("放鱼明细不存在或已被删除"); + } + // 2. 更新允许修改的字段 + updateFishRunDetailData(updateData, id); + // 3. 记录修改日志(只记录真正发生变化的字段) + recordFishRunUpdateLog(beforeRow, updateData, source); + return true; + } + + private Map querySingleFishRunDetailRow(String id) { + List> rows = queryFishRunDetailRowsByIds(Collections.singletonList(id)); + return rows.isEmpty() ? new HashMap<>() : rows.get(0); + } + + private void updateFishRunDetailData(Map updateData, String id) { + List setClauses = new ArrayList<>(); + List params = new ArrayList<>(); + for (Map.Entry entry : FISH_RUN_UPDATE_COLUMN_MAP.entrySet()) { + if (!updateData.containsKey(entry.getKey())) { + continue; + } + setClauses.add(entry.getValue() + " = ?"); + params.add(updateData.get(entry.getKey())); + } + if (setClauses.isEmpty()) { + return; + } + String sql = "UPDATE SD_RPIMNFISH_R SET " + String.join(", ", setClauses) + + " WHERE ID = ? AND NVL(IS_DELETED, 0) = 0"; + params.add(id); + int updated = jdbcTemplate.update(sql, params.toArray()); + if (updated == 0) { + throw new BizException("放鱼明细不存在或已被删除"); + } + } + + private void recordFishRunUpdateLog(Map beforeRow, + Map updateData, + String source) { + if (updateData == null || updateData.isEmpty()) { + return; + } + String recordId = asString(beforeRow.get("ID")); + String stcd = asString(beforeRow.get("STCD")); + List details = new ArrayList<>(); + for (Map.Entry entry : FISH_RUN_UPDATE_COLUMN_MAP.entrySet()) { + if (!updateData.containsKey(entry.getKey())) { + continue; + } + String column = entry.getValue(); + Object oldValue = beforeRow.get(column); + Object newValue = updateData.get(entry.getKey()); + // 数值类型按数值大小比较,忽略精度差异(133.0000 == 133.00) + if (LogValueUtils.sameLogValue(oldValue, newValue)) { + continue; + } + MsOperationLogDetail detail = new MsOperationLogDetail(); + detail.setTableName(FISH_RUN_DETAIL_TABLE_NAME); + detail.setStationCode(stcd); + detail.setFieldCode(column); + detail.setFieldMeaning(FISH_RUN_FIELD_MEANING_MAP.getOrDefault(column, column)); + detail.setOldValueCode(formatFishRunLogValue(oldValue)); + detail.setOldValueName(formatFishRunLogValue(oldValue)); + detail.setNewValueCode(formatFishRunLogValue(newValue)); + detail.setNewValueName(formatFishRunLogValue(newValue)); + detail.setRemark("修改字段值"); + details.add(detail); + } + if (details.isEmpty()) { + return; + } + MsOperationLog mainLog = new MsOperationLog(); + mainLog.setOperator(resolveLogOperator()); + mainLog.setOperationType("02"); + mainLog.setRecordId(recordId); + mainLog.setOperateTime(new Date()); + mainLog.setTableName(FISH_RUN_DETAIL_TABLE_NAME); + mainLog.setSource(StrUtil.trimToNull(source)); + mainLog.setRemark("修改全过程鱼类放鱼数据(明细ID:" + recordId + ")"); + msOperationLogMapper.insert(mainLog); + for (MsOperationLogDetail detail : details) { + detail.setMainId(mainLog.getId()); + msOperationLogDetailMapper.insert(detail); + } + } + private List> queryFishRunDetailRows(FbFishRunQgcDeleteParam param) { String sql = "SELECT det.ID, det.RPIMN_ID, run.STCD, det.FTP, det.FSZ, det.FWET, det.FCNT, " + "det.TAGTE, det.TAGCNT, det.FAGE, det.FID, det.PROTECTLVL, det.FA, det.REMARK " + @@ -393,7 +515,7 @@ public class FbStationServiceImpl implements FbStationService { return normalized; } - private void recordFishRunDeleteLog(List> rows, String remark, int deleteCount) { + private void recordFishRunDeleteLog(List> rows, String remark,String source, int deleteCount) { if (CollUtil.isEmpty(rows)) { return; } @@ -405,6 +527,8 @@ public class FbStationServiceImpl implements FbStationService { mainLog.setOperator(resolveLogOperator()); mainLog.setRecordId(recordId); mainLog.setOperateTime(new Date()); + mainLog.setSource(source); + mainLog.setOperationType("03"); mainLog.setTableName(FISH_RUN_DETAIL_TABLE_NAME); mainLog.setRemark(remark); msOperationLogMapper.insert(mainLog); From 83a18a31a1c6fde3cc92144f8326f47f7ffa37ef Mon Sep 17 00:00:00 2001 From: tangwei Date: Fri, 28 Aug 2026 18:08:30 +0800 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=9D=83?= =?UTF-8?q?=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/LoginController.java | 4 +- .../controller/SmsVerifyCodeController.java | 4 +- .../platform/system/mapper/SysUserMapper.java | 2 +- .../service/impl/AdminAuthServiceImpl.java | 3 +- .../service/impl/UserDetailsServiceImpl.java | 4 +- .../system/service/impl/UserServiceImpl.java | 6 +- .../com/yfd/platform/utils/JasyptUtil.java | 64 +++++++++++++++++++ .../com/yfd/platform/utils/SecurityUtils.java | 20 +++++- .../resources/mapper/system/SysUserMapper.xml | 2 +- 9 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/com/yfd/platform/utils/JasyptUtil.java diff --git a/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java b/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java index 47f1c8fa..167dd8b8 100644 --- a/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java +++ b/backend/src/main/java/com/yfd/platform/system/controller/LoginController.java @@ -24,6 +24,7 @@ import com.yfd.platform.system.service.ISysLogService; import com.yfd.platform.system.service.IUserService; import com.yfd.platform.utils.RequestHolder; import com.yfd.platform.utils.RsaUtils; +import com.yfd.platform.utils.SecurityUtils; import com.yfd.platform.utils.StringUtils; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; @@ -479,7 +480,8 @@ public class LoginController { * 判断是否为超级管理员 */ private boolean checkIsSuperAdmin(String userId) { - String maxLevel = sysUserMapper.getMaxLevel(userId); + String tenantId = SecurityUtils.getTenantId(); + String maxLevel = sysUserMapper.getMaxLevel(userId,tenantId); return "1".equals(maxLevel); } diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java b/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java index dc4dda21..93ae9f61 100644 --- a/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java +++ b/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java @@ -18,6 +18,7 @@ import com.yfd.platform.system.service.ISysLogService; import com.yfd.platform.system.service.IUserService; import com.yfd.platform.utils.RequestHolder; import com.yfd.platform.utils.RsaUtils; +import com.yfd.platform.utils.SecurityUtils; import com.yfd.platform.utils.StringUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -579,7 +580,8 @@ public class SmsVerifyCodeController { * 判断用户是否为超级管理员(拥有LEVEL=1的角色) */ private boolean isSuperAdmin(String userId) { - String maxLevel = sysUserMapper.getMaxLevel(userId); + String tenantId = SecurityUtils.getTenantId(); + String maxLevel = sysUserMapper.getMaxLevel(userId,tenantId); return "1".equals(maxLevel); } } \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java index a9a45137..a9aa29a5 100644 --- a/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java +++ b/backend/src/main/java/com/yfd/platform/system/mapper/SysUserMapper.java @@ -62,7 +62,7 @@ public interface SysUserMapper extends BaseMapper { * userid 用户id * 返回值说明: ************************************/ - String getMaxLevel(@Param("userId") String userId); + String getMaxLevel(@Param("userId") String userId,@Param("tenantId") String tenantId); /*********************************** * 用途说明:根据用户id删除所分配的角色 diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/AdminAuthServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/AdminAuthServiceImpl.java index 0afe0e2c..c3001500 100644 --- a/backend/src/main/java/com/yfd/platform/system/service/impl/AdminAuthServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/AdminAuthServiceImpl.java @@ -75,7 +75,8 @@ public class AdminAuthServiceImpl implements IAdminAuthService { @Override public boolean isCurrentManagedAdmin() { // SysUser sysUser = userMapper.selectById(SecurityUtils.getUserId()); - String maxLevel = userMapper.getMaxLevel(SecurityUtils.getUserId()); + String tenantId = SecurityUtils.getTenantId(); + String maxLevel = userMapper.getMaxLevel(SecurityUtils.getUserId(),tenantId); boolean adminRole = "1".equals(maxLevel); String currentUsername = SecurityUtils.getCurrentUsername(); return adminRole||isManagedAdminUsername(currentUsername); diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java index 45735c42..5aac70d8 100644 --- a/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/UserDetailsServiceImpl.java @@ -11,6 +11,7 @@ import com.yfd.platform.system.mapper.SysUserMapper; import com.yfd.platform.system.mapper.SysUserTenantMapper; import com.yfd.platform.system.service.IAdminAuthService; import com.yfd.platform.system.service.IUserService; +import com.yfd.platform.utils.SecurityUtils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -105,7 +106,8 @@ public class UserDetailsServiceImpl implements UserDetailsService { * 判断用户是否为超级管理员(拥有LEVEL=1的角色) */ private boolean isSuperAdmin(String userId) { - String maxLevel = sysUserMapper.getMaxLevel(userId); + String tenantId = SecurityUtils.getTenantId(); + String maxLevel = sysUserMapper.getMaxLevel(userId,tenantId); return "1".equals(maxLevel); } } diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java index 64e6220e..b41ed881 100644 --- a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java @@ -543,7 +543,8 @@ public class UserServiceImpl extends ServiceImpl impleme return true; } String currentUserId = SecurityUtils.getUserId(); - String level = sysUserMapper.getMaxLevel(currentUserId); + String tenantId = SecurityUtils.getTenantId(); + String level = sysUserMapper.getMaxLevel(currentUserId, tenantId); Integer roleLevel = parseRoleLevel(level); return roleLevel != null && roleLevel <= 2; } @@ -1030,7 +1031,8 @@ public class UserServiceImpl extends ServiceImpl impleme public boolean isSuperAdmin(String userId) { - String maxLevel = sysUserMapper.getMaxLevel(userId); + String tenantId = SecurityUtils.getTenantId(); + String maxLevel = sysUserMapper.getMaxLevel(userId,tenantId); SysUser sysUser = sysUserMapper.selectById(userId); return (sysUser!=null && sysUser.getUsertype()==0) || "1".equals(maxLevel); } diff --git a/backend/src/main/java/com/yfd/platform/utils/JasyptUtil.java b/backend/src/main/java/com/yfd/platform/utils/JasyptUtil.java new file mode 100644 index 00000000..5eaeaaf7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/JasyptUtil.java @@ -0,0 +1,64 @@ +package com.yfd.platform.utils; + +import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; +import org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig; + +/** + * Jasypt 加密解密工具 + * 配置:算法 PBEWithMD5AndTripleDES,密钥 dec,迭代次数 1000(默认) + */ +public class JasyptUtil { + + private static final String PASSWORD = "dec"; + // 建议使用标准名称(Java 内部识别),原写法也兼容但推荐此写法 + private static final String ALGORITHM = "PBEWithMD5AndTripleDES"; + private static final int ITERATIONS = 1000; + + private static StandardPBEStringEncryptor getEncryptor() { + StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); + EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig(); + config.setPassword(PASSWORD); + config.setAlgorithm(ALGORITHM); + config.setKeyObtentionIterations(ITERATIONS); + encryptor.setConfig(config); + return encryptor; + } + + /** + * 加密明文 + * @param plainText 明文 + * @return 密文(Base64 字符串) + */ + public static String encrypt(String plainText) { + StandardPBEStringEncryptor encryptor = getEncryptor(); + return encryptor.encrypt(plainText); + } + + /** + * 解密密文 + * @param cipherText 密文(Base64 字符串) + * @return 明文 + * @throws org.jasypt.exceptions.EncryptionOperationNotPossibleException 如果解密失败(密钥/算法不匹配) + */ + public static String decrypt(String cipherText) { + StandardPBEStringEncryptor encryptor = getEncryptor(); + return encryptor.decrypt(cipherText); + } + + // 测试入口 + public static void main(String[] args) { + // 1. 加密测试 + String plain = "Lcj@5678."; + String encrypted = encrypt(plain); + System.out.println("加密后的密文:" + encrypted); + + // 2. 解密测试(用你自己的密文) + String yourCipher = "w8ffsaU1GBbVeJ5wnBeCwoUDcadbD2oL"; + try { + String decrypted = decrypt(yourCipher); + System.out.println("解密结果:" + decrypted); + } catch (Exception e) { + System.err.println("解密失败,请检查密钥或算法是否正确:" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java b/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java index 8c90a931..6f07d336 100644 --- a/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java +++ b/backend/src/main/java/com/yfd/platform/utils/SecurityUtils.java @@ -15,11 +15,13 @@ */ package com.yfd.platform.utils; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.yfd.platform.exception.BadRequestException; import com.yfd.platform.system.domain.LoginUser; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; @@ -27,6 +29,8 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import java.util.Collection; import java.util.List; @@ -38,7 +42,7 @@ import java.util.List; */ @Slf4j public class SecurityUtils { - + private static final String TENANT_HEADER = "Tenant_Id"; /** * 获取当前登录的用户 * @return UserDetails @@ -142,4 +146,18 @@ public class SecurityUtils { LoginUser loginUser = (LoginUser) authentication.getPrincipal(); return loginUser.getAccessibleTenantIds(); } + + /** + * 从当前请求头中获取租户ID + * @return 租户ID,未获取到返回 null + */ + public static String getTenantId() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return null; + } + HttpServletRequest request = attributes.getRequest(); + String tenantId = request.getHeader(TENANT_HEADER); + return StrUtil.isNotBlank(tenantId) ? tenantId : null; + } } diff --git a/backend/src/main/resources/mapper/system/SysUserMapper.xml b/backend/src/main/resources/mapper/system/SysUserMapper.xml index 8ba9cf6e..df58edcf 100644 --- a/backend/src/main/resources/mapper/system/SysUserMapper.xml +++ b/backend/src/main/resources/mapper/system/SysUserMapper.xml @@ -53,7 +53,7 @@ FROM ( SELECT r."LEVEL" AS level_col FROM sys_role r - WHERE r.id IN (SELECT roleid FROM sys_role_users WHERE userid = #{userId}) + WHERE TENANT_ID = #{tenantId} AND r.id IN (SELECT roleid FROM sys_role_users WHERE userid = #{userId}) ) t