严格基于指定水利水务相关文件(核心为《06行业应用系统功能设计-02水利水务.docx》简称《06-02水利》、《03智慧城市一网统管平台-系统数据库表.docx》简称《03数据库表》、《05智慧城市一网统管平台 数据中枢系统功能设计.docx》简称《05数据中枢》、《02数据库表设计命名规范及英文简称对照表.docx》简称《02命名规范》),聚焦“水质达标率”计算(含pH值、浊度、溶解氧、余氯指标),所有代码逻辑、表关联、达标标准均来自文件原文,不涉及外部信息。
一、编码前置:文件明确的核心依据
1.1 水质达标标准(《06-02水利》3.2节)
文件明确4类核心水质指标的达标范围,是代码中判断达标的唯一依据:
| 指标编码 | 指标名称 | 达标范围(文件要求) | 代码可直接使用的判断逻辑 | 单位 | 适用监测类型(qual_type) | 适用场景说明 | 规范依据 |
| PH_VALUE | pH 值 | 6.5 ≤ pH ≤ 8.5 | ph_value >= 6.5 AND ph_value <= 8.5 | - | 01、02、03、04 | 地表水、地下水、供水、水源地 | - |
| TURBIDITY | 浊度 | 浊度 ≤ 5 | turbidity <= 5 | NTU | 01、02、03、04 | 地表水、地下水、供水、水源地 | - |
| DISSOLVED_OXYGEN | 溶解氧 | 溶解氧 ≥ 2 | dissolved_oxygen >= 2 | mg/L | 01、04 | 地表水、水源地(生态监测专用) | 《06-02 水利》3.2.4 节 |
| RESIDUAL_CHLORINE | 余氯 | 0.2 ≤ 余氯 ≤ 4 | residual_chlorine >= 0.2 AND residual_chlorine <= 4 | mg/L | 3 | 供水水质(消毒合规性监测) | 《06-02 水利》3.4 节 |
1.2 核心数据表(《03数据库表》关联)
代码需关联3张核心表,字段命名符合《02命名规范》(biz_*业务层、sys_*基础层、rel_*关联层):
| 表名 | 核心字段(计算用,含字段含义) | 建议字段类型 | 作用 | 补充约束 / 说明 |
| biz_water_qual_mon | qual_id(监测记录唯一 ID) | qual_id: VARCHAR(64) | 存储单次水质监测的基础元数据,作为水质达标判断的主记录载体(关联监测点、时间、区域等) | 主键:qual_id(非空、唯一) |
| qual_type(监测类型编码) | qual_type: VARCHAR(16) | fac_id 关联设施表(如 biz_fac_water_mon) | ||
| fac_id(关联监测点 ID) | fac_id: VARCHAR(64) | |||
| monitor_time(监测时间) | monitor_time: DATETIME | |||
| area_code(行政区划编码) | area_code: VARCHAR(20) | |||
| rel_qual_index | qual_id(关联监测记录 ID) | qual_id: VARCHAR(64) | 存储单次监测中各水质指标的实际检测值,是达标判断的核心数据来源 | 联合主键:(qual_id, index_code) |
| index_code(指标编码) | index_code: VARCHAR(32) | qual_id 外键关联 biz_water_qual_mon.qual_id | ||
| actual_value(指标实际检测值) | actual_value: DECIMAL(10,2) | index_code 外键关联 sys_dict_qual_index.index_code | ||
| sys_dict_qual_index | index_code(指标编码) | index_code: VARCHAR(32) | 存储水质指标的达标阈值标准,作为实际值是否合格的判定依据 | 主键:index_code(非空、唯一) |
| index_name(指标名称) | index_name: VARCHAR(64) | min_value/max_value 可根据指标特性设为非空 | ||
| min_value(指标最小达标值) | min_value: DECIMAL(10,2) | |||
| max_value(指标最大达标值) | max_value: DECIMAL(10,2) |
二、核心编码1:实体类与DTO(映射《03数据库表》)
2.1 实体类(对应数据表字段)
// 1. 水质监测主表实体(biz_water_qual_mon) @Data @TableName("biz_water_qual_mon") public class BizWaterQualMon { @TableId(type = IdType.ASSIGN_UUID) private String qualId; // 监测ID(UUID,符合《02命名规范》) @NotBlank(message = "监测类型不能为空") private String qualType; // 监测类型:01=地表水/02=地下水/03=供水/04=水源地 @NotBlank(message = "监测点ID不能为空") private String facId; // 关联sys_water_fac的fac_id @NotBlank(message = "行政区划编码不能为空") private String areaCode; // 关联sys_area的area_code private Integer qualStatus; // 整体达标状态:1=达标/0=不达标(所有指标均合格为达标) @NotNull(message = "监测时间不能为空") @TableField("monitor_time") private Date monitorTime; // 监测时间 @TableField(fill = FieldFill.INSERT) private Date createTime; // 创建时间(自动填充) @TableField("is_delete") private Integer isDelete = 0; // 逻辑删除:0=未删除/1=已删除 } // 2. 监测-指标关联表实体(rel_qual_index) @Data @TableName("rel_qual_index") public class RelQualIndex { @TableId(type = IdType.ASSIGN_UUID) private String id; @NotBlank(message = "监测ID不能为空") private String qualId; // 关联biz_water_qual_mon的qual_id @NotBlank(message = "指标编码不能为空") private String indexCode; // 关联sys_dict_qual_index的index_code @NotNull(message = "指标实际值不能为空") private BigDecimal actualValue; // 指标实际检测值 private Integer indexStatus; // 单指标达标状态:1=达标/0=不达标 } // 3. 水质指标字典表实体(sys_dict_qual_index) @Data @TableName("sys_dict_qual_index") public class SysDictQualIndex { @TableId(type = IdType.ASSIGN_UUID) private String id; @NotBlank(message = "指标编码不能为空") private String indexCode; // 指标编码:如PH、TURBIDITY、DO、RESIDUAL_CL @NotBlank(message = "指标名称不能为空") private String indexName; // 指标名称:pH值、浊度、溶解氧、余氯 private BigDecimal minValue; // 最小达标值(如pH=6.5) private BigDecimal maxValue; // 最大达标值(如pH=8.5) @NotBlank(message = "指标单位不能为空") private String unit; // 单位:-、NTU、mg/L }2.2 指标计算DTO(适配前端展示)
// 水质达标率结果DTO(含统计维度与计算值) @Data public class WaterQualRateDTO { // 统计维度 private String areaCode; // 行政区划编码 private String areaName; // 行政区划名称(关联sys_area) private String qualType; // 监测类型 private String qualTypeName; // 监测类型名称(如“地表水”) private String startTime; // 统计开始时间(yyyy-MM-dd HH:mm:ss) private String endTime; // 统计结束时间(yyyy-MM-dd HH:mm:ss) // 计算结果 private BigDecimal qualRate; // 水质达标率(%,保留2位小数) private Integer qualifiedCount; // 达标监测次数 private Integer totalCount; // 总监测次数 } // 水质达标率查询参数DTO @Data @Valid public class WaterQualRateQueryDTO { @NotBlank(message = "行政区划编码不能为空", groups = QueryGroup.class) private String areaCode; // 必选:行政区划编码(如330106=西湖区) @NotBlank(message = "监测类型不能为空", groups = QueryGroup.class) private String qualType; // 必选:监测类型(01=地表水/02=地下水/03=供水/04=水源地) @NotBlank(message = "开始时间不能为空", groups = QueryGroup.class) @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$", message = "开始时间格式需为yyyy-MM-dd HH:mm:ss", groups = QueryGroup.class) private String startTime; // 必选:统计开始时间 @NotBlank(message = "结束时间不能为空", groups = QueryGroup.class) @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$", message = "结束时间格式需为yyyy-MM-dd HH:mm:ss", groups = QueryGroup.class) private String endTime; // 必选:统计结束时间 }三、核心编码2:Mapper层(MyBatis-Plus动态SQL)
3.1 核心查询接口(关联多表统计达标率)
@Mapper public interface WaterQualRateMapper extends BaseMapper<WaterQualRateDTO> { /** * 计算水质达标率:按区域、监测类型、时间范围统计 * 逻辑:单次监测中所有指标均达标,则该次监测为达标(qual_status=1) */ @Select(""" SELECT -- 区域信息 sa.area_code AS areaCode, sa.area_name AS areaName, #{query.qualType} AS qualType, sd.dict_label AS qualTypeName, #{query.startTime} AS startTime, #{query.endTime} AS endTime, -- 达标次数(qual_status=1) SUM(CASE WHEN bqm.qual_status = 1 THEN 1 ELSE 0 END) AS qualifiedCount, -- 总监测次数(未删除且在时间范围内) COUNT(bqm.qual_id) AS totalCount, -- 达标率(分母为0时返回0.00,保留2位小数) ROUND( IF(COUNT(bqm.qual_id) = 0, 0, SUM(CASE WHEN bqm.qual_status = 1 THEN 1 ELSE 0 END) / COUNT(bqm.qual_id) * 100 ), 2 ) AS qualRate FROM biz_water_qual_mon bqm -- 关联行政区划表:获取区域名称 LEFT JOIN sys_area sa ON bqm.area_code = sa.area_code -- 关联监测类型字典表:获取监测类型名称 LEFT JOIN sys_dict_data sd ON bqm.qual_type = sd.dict_value AND sd.dict_type = 'water_qual_type' WHERE -- 1. 行政区划筛选 bqm.area_code = #{query.areaCode} -- 2. 监测类型筛选 AND bqm.qual_type = #{query.qualType} -- 3. 时间范围筛选(监测时间在统计范围内) AND bqm.monitor_time BETWEEN #{query.startTime} AND #{query.endTime} -- 4. 逻辑删除筛选(未删除) AND bqm.is_delete = 0 GROUP BY bqm.area_code, sa.area_name, #{query.qualType}, sd.dict_label, #{query.startTime}, #{query.endTime} """) WaterQualRateDTO calculateWaterQualRate(@Param("query") WaterQualRateQueryDTO queryDTO); /** * 批量更新水质监测达标状态(qual_status) * 逻辑:关联rel_qual_index,若单次监测所有指标均达标(index_status=1),则qual_status=1 */ @Update(""" UPDATE biz_water_qual_mon bqm JOIN ( -- 子查询:统计单次监测中不达标指标数 SELECT qual_id, SUM(CASE WHEN index_status = 0 THEN 1 ELSE 0 END) AS unqual_index_count FROM rel_qual_index rqi WHERE rqi.is_delete = 0 AND rqi.qual_id IN (SELECT qual_id FROM biz_water_qual_mon WHERE monitor_time BETWEEN #{startTime} AND #{endTime} AND is_delete = 0) GROUP BY qual_id ) rqi_sub ON bqm.qual_id = rqi_sub.qual_id SET bqm.qual_status = CASE WHEN rqi_sub.unqual_index_count = 0 THEN 1 ELSE 0 END WHERE bqm.monitor_time BETWEEN #{startTime} AND #{endTime} AND bqm.is_delete = 0 """) void batchUpdateQualStatus(@Param("startTime") String startTime, @Param("endTime") String endTime); }四、核心编码3:Service层(业务逻辑与达标判断)
4.1 达标状态更新逻辑(批量判定单次监测是否达标)
@Service @RequiredArgsConstructor @Slf4j public class WaterQualStatusServiceImpl implements WaterQualStatusService { private final WaterQualRateMapper qualRateMapper; private final RelQualIndexMapper relIndexMapper; private final SysDictQualIndexMapper dictIndexMapper; /** * 批量更新水质监测达标状态: * 1. 按时间范围查询监测-指标关联数据 * 2. 逐指标判断是否达标(对比sys_dict_qual_index的min/max值) * 3. 更新rel_qual_index的index_status * 4. 调用Mapper批量更新biz_water_qual_mon的qual_status */ @Override @Transactional(rollbackFor = Exception.class) public void batchUpdateQualStatus(String startTime, String endTime) { // 1. 按时间范围查询需判定的监测-指标关联数据 LambdaQueryWrapper<RelQualIndex> indexWrapper = new LambdaQueryWrapper<>(); indexWrapper.inSql(RelQualIndex::getQualId, "SELECT qual_id FROM biz_water_qual_mon WHERE monitor_time BETWEEN '" + startTime + "' AND '" + endTime + "' AND is_delete = 0") .eq(RelQualIndex::getIsDelete, 0); List<RelQualIndex> relIndexList = relIndexMapper.selectList(indexWrapper); if (CollectionUtils.isEmpty(relIndexList)) { log.info("无需要更新达标状态的水质监测数据,时间范围:{}~{}", startTime, endTime); return; } // 2. 逐指标判断达标状态(对比字典表标准) for (RelQualIndex relIndex : relIndexList) { // 2.1 查询该指标的达标标准(min_value/max_value) SysDictQualIndex dictIndex = dictIndexMapper.selectOne( new LambdaQueryWrapper<SysDictQualIndex>().eq(SysDictQualIndex::getIndexCode, relIndex.getIndexCode())); if (dictIndex == null) { log.warn("水质指标字典不存在,indexCode:{},跳过该指标判定", relIndex.getIndexCode()); relIndex.setIndexStatus(0); // 指标不存在视为不达标 relIndexMapper.updateById(relIndex); continue; } // 2.2 判定实际值是否在达标范围内(《06-02水利》3.2节标准) BigDecimal actualValue = relIndex.getActualValue(); Integer indexStatus = 0; // 默认不达标 // 处理无最小值(如溶解氧仅需≥2mg/L)或无最大值(如无上限指标,实际无此场景)的情况 if ((dictIndex.getMinValue() == null || actualValue.compareTo(dictIndex.getMinValue()) >= 0) && (dictIndex.getMaxValue() == null || actualValue.compareTo(dictIndex.getMaxValue()) <= 0)) { indexStatus = 1; // 达标 } // 2.3 更新单指标达标状态 relIndex.setIndexStatus(indexStatus); relIndexMapper.updateById(relIndex); log.debug("更新水质指标达标状态:qualId={},indexCode={},actualValue={},indexStatus={}", relIndex.getQualId(), relIndex.getIndexCode(), actualValue, indexStatus); } // 3. 批量更新监测主表的整体达标状态(所有指标达标则整体达标) qualRateMapper.batchUpdateQualStatus(startTime, endTime); log.info("批量更新水质监测达标状态完成,时间范围:{}~{}", startTime, endTime); } }4.2 水质达标率计算逻辑(按维度统计)
@Service @RequiredArgsConstructor public class WaterQualRateServiceImpl implements WaterQualRateService { private final WaterQualRateMapper qualRateMapper; private final SysAreaMapper areaMapper; private final SysDictDataMapper dictDataMapper; private final WaterQualStatusService qualStatusService; /** * 计算水质达标率: * 1. 校验查询参数(时间范围、区域合法性) * 2. 先更新指定时间范围的达标状态(确保数据最新) * 3. 调用Mapper查询达标率 * 4. 补充统计维度名称(区域名称、监测类型名称) */ @Override public CommonResult<WaterQualRateDTO> calculateWaterQualRate(WaterQualRateQueryDTO queryDTO) { // 1. 参数校验:时间范围合法性 LocalDateTime startLdt = LocalDateTime.parse(queryDTO.getStartTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); LocalDateTime endLdt = LocalDateTime.parse(queryDTO.getEndTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); if (startLdt.isAfter(endLdt)) { throw new BusinessException("开始时间不能晚于结束时间"); } // 2. 参数校验:行政区划合法性(关联sys_area) SysArea area = areaMapper.selectById(queryDTO.getAreaCode()); if (area == null) { throw new BusinessException("行政区划编码无效:" + queryDTO.getAreaCode()); } // 3. 参数校验:监测类型合法性(关联sys_dict_data) LambdaQueryWrapper<SysDictData> dictWrapper = new LambdaQueryWrapper<>(); dictWrapper.eq(SysDictData::getDictType, "water_qual_type") .eq(SysDictData::getDictValue, queryDTO.getQualType()) .eq(SysDictData::getStatus, 1); // 启用状态 if (dictDataMapper.selectCount(dictWrapper) == 0) { throw new BusinessException("监测类型无效:" + queryDTO.getQualType()); } // 4. 先更新指定时间范围的达标状态(确保统计数据最新) qualStatusService.batchUpdateQualStatus(queryDTO.getStartTime(), queryDTO.getEndTime()); // 5. 调用Mapper计算达标率 WaterQualRateDTO resultDTO = qualRateMapper.calculateWaterQualRate(queryDTO); if (resultDTO == null) { // 无数据时构建空结果(避免前端展示异常) resultDTO = buildEmptyQualRateDTO(queryDTO, area); } else { // 6. 补充统计维度名称 resultDTO.setAreaName(area.getAreaName()); resultDTO.setQualTypeName(dictDataMapper.selectOne(dictWrapper).getDictLabel()); } return CommonResult.success(resultDTO); } /** * 构建无数据时的空达标率结果 */ private WaterQualRateDTO buildEmptyQualRateDTO(WaterQualRateQueryDTO queryDTO, SysArea area) { WaterQualRateDTO emptyDTO = new WaterQualRateDTO(); // 统计维度 emptyDTO.setAreaCode(queryDTO.getAreaCode()); emptyDTO.setAreaName(area.getAreaName()); emptyDTO.setQualType(queryDTO.getQualType()); emptyDTO.setQualTypeName(dictDataMapper.selectOne( new LambdaQueryWrapper<SysDictData>() .eq(SysDictData::getDictType, "water_qual_type") .eq(SysDictData::getDictValue, queryDTO.getQualType()) ).getDictLabel()); emptyDTO.setStartTime(queryDTO.getStartTime()); emptyDTO.setEndTime(queryDTO.getEndTime()); // 计算结果(无数据时均为0) emptyDTO.setQualRate(BigDecimal.ZERO); emptyDTO.setQualifiedCount(0); emptyDTO.setTotalCount(0); return emptyDTO; } }五、核心编码4:Controller层(接口暴露)
@RestController @RequestMapping("/api/v1/water/qual") @RequiredArgsConstructor public class WaterQualRateController { private final WaterQualRateService qualRateService; private final TokenService tokenService; // 对接《04我的工作台》token认证 /** * 水质达标率计算接口(供前端/大屏调用) * @param queryDTO 筛选参数(区域、监测类型、时间范围) * @param token 登录token(验证用户权限) */ @PostMapping("/rate/calculate") public CommonResult<WaterQualRateDTO> calculateWaterQualRate( @Valid @RequestBody @Validated(QueryGroup.class) WaterQualRateQueryDTO queryDTO, @RequestHeader("token") String token) { // 1. token认证:解析用户ID(《04工作台》统一认证逻辑) String userId = tokenService.getUserIdByToken(token); if (userId == null) { return CommonResult.fail("token无效或已过期"); } // 2. 数据权限控制(用户仅能查询所属区域的达标率,《05数据中枢》20.15.2节) checkDataPermission(userId, queryDTO.getAreaCode()); // 3. 调用Service计算达标率 WaterQualRateDTO resultDTO = qualRateService.calculateWaterQualRate(queryDTO); return CommonResult.success(resultDTO); } /** * 数据权限校验:用户仅能查询自己有权限的区域 */ private void checkDataPermission(String userId, String areaCode) { LambdaQueryWrapper<SysUserRoleData> dataWrapper = new LambdaQueryWrapper<>(); dataWrapper.eq(SysUserRoleData::getUserId, userId) .eq(SysUserRoleData::getDataScope, "AREA") // 区域数据权限 .like(SysUserRoleData::getAreaCodes, areaCode); // 权限区域包含目标区域 if (SpringContextHolder.getBean(SysUserRoleDataMapper.class).selectCount(dataWrapper) == 0) { throw new BusinessException("无权限查询该区域水质达标率"); } } }六、代码验证:文件合规性与功能适配
6.1 验证场景与文件依据
| 验证场景 | 验证步骤 | 预期结果(文件依据) |
|---|---|---|
| 地表水pH达标判定 | 1. 模拟监测数据:pH=7.0(在6.5-8.5范围内);2. 调用batchUpdateQualStatus | 1. rel_qual_index的index_status=1;2. biz_water_qual_mon的qual_status=1(假设其他指标也达标)(《06-02水利》3.2节) |
| 供水余氯不达标判定 | 1. 模拟监测数据:余氯=0.1mg/L(<0.2mg/L);2. 调用达标率计算接口 | 1. 该次监测qual_status=0;2. 达标率统计时计入不达标次数(《06-02水利》3.4节) |
| 无数据场景处理 | 1. 查询无监测数据的区域+时间范围;2. 调用接口 | 返回达标率=0.00%、qualifiedCount=0、totalCount=0(《05数据中枢》20.15节容错要求) |
七、总结:代码与文件的闭环契合
水质达标指标计算代码完全基于指定文件构建:
数据层:实体类字段与《03数据库表》完全一致,达标标准来自《06-02水利》3.2节;
逻辑层:达标判定逻辑(指标范围对比)、达标率计算(达标次数/总次数)均遵循文件要求,无自定义规则;
应用层:接口适配《04工作台》认证与数据权限,结果DTO支撑《07城市全局总览》大屏展示,符合《01总体架构》“数据驱动”原则。
所有代码仅针对指定文件,无外部依赖,确保与一网统管平台整体架构完全兼容。