广东水文项目实战:大断面接口优化与水库站超限判断的深度实践

在广东水文监测系统项目中,我们针对大断面数据接口和水库站超限判断逻辑进行了多项优化。本文将详细分享这些实战经验,包括测站沿革排序优化、断面数据岸别标识、实时水位集成,以及水库站超限判断的业务逻辑修正。这些改动不仅提升了系统的实用性,也解决了长期存在的业务概念不匹配问题。

前言

广东水文监测系统是一个综合性的水文数据管理平台,涵盖河道水位、水库库容、断面测量等多维度数据。在项目迭代过程中,我们发现原有系统存在几个关键痛点:大断面数据接口缺少实时水位信息、测站沿革数据排序混乱、水库站超限判断功能失效等。本文将记录我们如何逐一分析和解决这些问题的完整过程。

大断面数据处理流程
图1:大断面数据处理流程

大断面接口完善

大断面数据是水文监测中的重要组成部分,它记录了河道横断面的地形变化。原有的 /basic/station/archive/stXsDataByStcd 接口功能较为基础,无法满足前端展示的丰富需求。我们对其进行了三项关键完善。

测站沿革排序优化

测站沿革接口 /basic/station/archive/stationEvolution 返回的是测站的历史变更记录。原实现中,数据按照数据库默认顺序返回,导致用户查看时需要自行寻找最新的变更记录。

解决方案:从外部 API 获取沿革列表后,按 chgdt(变更日期)降序排序,确保最新的变更排在最前面。

1
2
3
4
5
6
7
List<StationEvolutionDTO> list = rows.toJavaList(StationEvolutionDTO.class);
// 按变更日期降序排序,最新的变更排在最前面
list.sort(Comparator.comparing(
(StationEvolutionDTO dto) -> DateUtils.parseDate(dto.getChgdt()),
Comparator.nullsLast(Comparator.reverseOrder())
));
return list;

测试结果显示,222 条数据正确按日期降序排列,从 2026-03 到 1915-06,覆盖了近百年的历史变迁。

大断面数据增加 position 岸别标识

岸别标识示意图
图2:河道断面岸别标识示意图

在断面图上,前端需要标注每个测量点属于左堤还是右堤。这需要结合断面数据(外部 API)和堤防高程(本地 StRvfcchB 表)进行判断。

判断逻辑

  • rvbdel == LDKEL → 标注为 "left"(左堤)
  • rvbdel == RDKEL → 标注为 "right"(右堤)
  • 其他情况 → null(普通测量点)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
StRvfcchB rvfcchB = stRvfcchBService.selectStRvfcchBBySTCD(stcd);
if (rvfcchB != null) {
BigDecimal ldkel = rvfcchB.getLDKEL();
BigDecimal rdkel = rvfcchB.getRDKEL();
for (OpenCrossSectionDataDTO point : dataList) {
BigDecimal rvbdel = point.getRvbdel();
if (rvbdel != null) {
if (ldkel != null && rvbdel.compareTo(ldkel) == 0) {
point.setPosition("left");
} else if (rdkel != null && rvbdel.compareTo(rdkel) == 0) {
point.setPosition("right");
}
}
}
}

注意:当前使用精确匹配,实测外部 API 与本地数据精度不一致(差值约 0.7m),后续可能需要改为最近值匹配。

大断面接口增加实时水位

为了在断面图上同时展示当前水位线和水位过程线,我们需要在接口中集成实时水位数据。数据来源是河道分钟数据表 StRiverRMin

实现要点

  • 支持可选的 startTime/endTime 参数,默认查询最近 3 天
  • 返回实时水位值和水位过程线列表
  • 参考水库剖面图接口 getReservoirProfile 的实现模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Map<String, Object> getLatestOpenCrossSectionData(String stcd, String startTime, String endTime) {
// 1. 获取断面数据 + position 标注
// 2. 查询实时水位
// 3. 查询水位过程线(默认3天)
// 4. 组装返回结果
Map<String, Object> result = new HashMap<>();
result.put("stcd", stcd);
result.put("stationName", station != null ? station.getSTNM() : "");
result.put("realtimeWaterLevel", realtimeWaterLevel);
result.put("dataTime", dataTime);
result.put("startTime", actualStartTime);
result.put("endTime", actualEndTime);
result.put("waterLevelList", waterLevelList);
result.put("crossSectionData", dataList);
return result;
}

测试结果:默认 3 天返回 864 条水位数据,自定义 1 天返回 289 条,实时水位 1.19m。

API 返回结构变更

API 返回结构
图3:API 返回结构示意图

完善后的接口返回结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"stcd": "81001150",
"stationName": "某水文站",
"realtimeWaterLevel": 1.19,
"dataTime": "2026-04-29 10:00:00",
"startTime": "2026-04-26 10:00:00",
"endTime": "2026-04-29 10:00:00",
"waterLevelList": [
{ "tm": "2026-04-26 10:05:00", "z": 1.15 },
{ "tm": "2026-04-26 10:10:00", "z": 1.16 }
],
"crossSectionData": [
{ "id": 1, "xsid": 100, "vtno": 1, "di": 0, "rvbdel": 5.2, "position": "left" },
{ "id": 2, "xsid": 100, "vtno": 2, "di": 10, "rvbdel": 3.8, "position": null }
]
}

水库站超限判断优化

水库站超限判断流程
图4:水库站超限判断流程图

水库站超限判断是水情实时汇总服务中的核心功能,用于判断水库水位是否超过警戒线或保证水位。然而,原有逻辑存在严重的业务概念不匹配问题。

问题背景

数据库表关系
图5:数据库表关系图

原有逻辑对所有站类型统一使用 ST_RVFCCH_B 表的 WRZ(警戒水位)和 GRZ(保证水位)。但水库站业务上应使用不同的水位指标:

指标 旧逻辑数据源 水库站正确数据源 说明
超警戒判断 ST_RVFCCH_B.WRZ(警戒水位) ST_RSVRFSR_B.FSLTDZ(汛限水位) 水库站应以汛限水位为警戒标准
超保证判断 ST_RVFCCH_B.GRZ(保证水位) ST_RSVRFCCH_B.DSFLZ(设计水位) 水库站应以设计水位为保证标准

核心问题:水库站没有 GRZ(保证水位)数据,全部为 NULL,导致 IS_OVER_GUARANTEE 永远为 0,超保证判断功能完全失效。

优化方案

我们采用”优先专用、降级通用”的策略:

  1. 超警戒判断:水库站优先使用汛限水位(ST_RSVRFSR_B.FSLTDZ),无数据时降级使用警戒水位(ST_RVFCCH_B.WRZ
  2. 超保证判断:水库站优先使用设计水位(ST_RSVRFCCH_B.DSFLZ),无数据时降级使用保证水位(ST_RVFCCH_B.GRZ

实现细节

Mapper 层新增查询方法

1
2
3
4
5
// 查询水库站汛限水位
List<FloodLimitWaterLevelDTO> selectFloodLimitWaterLevels(List<String> stcds);

// 查询水库站设计水位
List<ReservoirStationAlarmDTO> selectDesignWaterLevels(List<String> stcds);

SQL 查询(按当前月份匹配汛期):

1
2
3
4
5
6
7
8
9
<select id="selectFloodLimitWaterLevels" resultType="cn.cnsci.system.domain.FloodLimitWaterLevelDTO">
SELECT r.STCD, r.FSLTDZ
FROM ST_RSVRFSR_B r
WHERE r.STCD IN
<foreach collection="list" item="stcd" open="(" separator="," close=")">
#{stcd}
</foreach>
AND MONTH(CURDATE()) BETWEEN r.BGMD AND r.EDMD
</select>

Service 层组装逻辑

1
2
3
4
5
6
7
// 水库站优先使用汛限水位(ST_RSVRFSR_B.FSLTDZ),无数据时降级使用警戒水位(ST_RVFCCH_B.WRZ)
BigDecimal fsltdz = fsltdzMap.get(base.getStcd());
BigDecimal wrz = fsltdz != null ? fsltdz : (warn != null ? warn.getWrz() : null);

// 水库站优先使用设计水位(ST_RSVRFCCH_B.DSFLZ),无数据时降级使用保证水位(ST_RVFCCH_B.GRZ)
BigDecimal dsflz = dsflzMap.get(base.getStcd());
BigDecimal grz = dsflz != null ? dsflz : (warn != null ? warn.getGrz() : null);

验证结果

以龙门水库(81705600)为例:

测站编码 站名 FSLTDZ DSFLZ summary WRZ summary GRZ 状态
81705600 龙门水库 57.000 58.850 57.000 58.850

新逻辑成功修复了水库站 GRZ 全部为 NULL 的问题,超保证判断功能恢复正常。

测站沿革接口数据过滤修复

在开发过程中,我们还发现了一个数据过滤 bug:调用 stationEvolution 接口时,返回了其他测站的数据。

问题描述

调用 GET /basic/station/archive/stationEvolution?stcd=80117000 时,返回了 8130004280901000 等其他测站的数据,而不是 80117000 的数据。

根因分析

  1. 第三方接口 /stsc/openapi/listGrouped 不支持按 stcd 参数过滤,返回所有测站数据(222 条)
  2. 代码中直接使用第三方接口的分页参数,导致只返回第一页数据(包含其他测站)
  3. 没有对返回数据按 iid 进行过滤

修复方案

1
2
3
4
5
6
Map<String, String> params = new HashMap<>();
// 不传分页参数,获取所有数据
// ...
list = list.stream()
.filter(dto -> iid.equals(dto.getIid()))
.collect(Collectors.toList());

修复后,只返回 iid=80117000 的 3 条记录,站名均为”德庆”。

经验总结

通过这次广东水文项目的优化实践,我们总结了以下几点经验:

  • 业务概念与数据模型的对齐:技术实现必须准确反映业务概念,水库站超限判断的问题根源在于数据模型设计时未充分考虑不同站类型的业务差异
  • 降级策略的重要性:在数据不完整或缺失时,提供合理的降级方案可以保证系统的基本功能不受影响
  • 第三方接口的边界处理:调用第三方接口时,不能假设其支持所有查询参数,需要在本地进行数据过滤和处理
  • 测试验证的完整性:每次改动都需要充分的测试验证,特别是涉及历史数据兼容性的场景

结语

水文监测系统的优化是一个持续迭代的过程。通过这次大断面接口完善和水库站超限判断优化,我们不仅提升了系统的实用性和准确性,也为后续的功能扩展奠定了坚实基础。未来,我们将继续关注水文业务的实际需求,不断优化系统功能。


本文配乐

点击播放背景音乐