From d1480fd590ac450deca2d7efe2e2f325918d07ed Mon Sep 17 00:00:00 2001 From: tangwei Date: Thu, 21 May 2026 11:35:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=B1=BC=E7=B1=BB=E9=80=82=E5=BA=94?= =?UTF-8?q?=E6=80=A7=E5=88=86=E6=9E=90=E6=8E=A5=E5=8F=A3=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/docs/业务模块开发技术规范.md | 579 ++++++++++++++++++ .../service/impl/SdWtMonitorServiceImpl.java | 37 +- 2 files changed, 595 insertions(+), 21 deletions(-) create mode 100644 backend/docs/业务模块开发技术规范.md diff --git a/backend/docs/业务模块开发技术规范.md b/backend/docs/业务模块开发技术规范.md new file mode 100644 index 0000000..3037fe0 --- /dev/null +++ b/backend/docs/业务模块开发技术规范.md @@ -0,0 +1,579 @@ +# WholeProcessPlatform Backend 业务模块开发技术规范 + +## 1. 文档目的 + +本文档用于统一 `d:\shuili\WholeProcessPlatform\backend` 项目中业务模块的开发方式,重点覆盖: + +- 模块目录与分层设计规范 +- Controller、Service、Mapper、VO、Domain 的职责边界 +- 基于 `Spring Boot 4.0.3 + Java 21 + MyBatis/MyBatis-Plus` 的编码约束 +- 当前项目已形成的事实标准 +- 新增业务模块、旧系统迁移模块、字典/基础数据模块的推荐实现方式 +- SQL、异常、接口返回、分页、文档、测试、性能与安全要求 + +本文档适用于本项目所有新增或改造的业务模块,尤其适用于 `env` 目录下的生态环境类业务。 + +## 2. 技术基线 + +根据项目当前 `pom.xml`,本项目业务开发技术基线如下: + +- JDK:21 +- Spring Boot:4.0.3 +- Spring Web:MVC +- Spring Security:已引入 +- Springdoc OpenAPI:3.0.2 +- MyBatis:3.5.16 +- MyBatis Spring Boot Starter:4.0.1 +- MyBatis-Plus:3.5.16,使用 `mybatis-plus-spring-boot4-starter` +- 数据库驱动:MySQL、Oracle、达梦 +- 工具库:Hutool、Lombok、Guava、POI + +## 3. 外部参考原则 + +本文档参考了当前主流的 Spring Boot 4 / 现代 Spring 开发建议,并结合项目现状做了落地约束,核心依据如下: + +- Spring Boot 官方文档强调代码结构、配置类组织、依赖注入、外部化配置、日志、Web、Data、Validation、Actuator 等能力应作为应用开发基础。 +- Springdoc 官方文档建议使用 `springdoc-openapi-starter-webmvc-ui` 作为 OpenAPI/Swagger UI 集成方式,并通过注解与配置文件维持接口文档。 +- MyBatis Spring Boot Starter 官方文档强调在 Boot 4 体系中使用官方 starter、自动扫描 Mapper、使用配置项统一管理 MyBatis 行为。 +- MyBatis-Plus 官方文档明确支持 Spring Boot 4,推荐使用 Boot 4 对应 starter,并采用 `BaseMapper`、`ServiceImpl`、分页对象等标准方式实现常规 CRUD。 + +说明: + +- 本规范不是照搬互联网通用模板,而是“以本项目实际代码为准,吸收外部最佳实践后形成的可执行规范”。 +- 若通用建议与现有项目框架存在冲突,优先保证现有项目兼容性,再逐步演进。 + +## 4. 当前项目模块结构总结 + +`env` 模块已经形成两类主流实现风格。 + +### 4.1 基础资料型模块 + +典型文件: + +- `controller/SdCountryBController.java` +- `service/ISdCountryBService.java` +- `service/impl/SdCountryBServiceImpl.java` +- `mapper/SdCountryBMapper.java` +- `domain/SdCountryB.java` + +特征: + +- 面向单表或轻量多表的增删改查 +- Service 通常继承 `ServiceImpl` +- 使用 `BaseMapper` 和 MyBatis-Plus `lambdaQuery()` +- Controller 以分页查询、列表查询、详情查询为主 + +适用场景: + +- 字典表 +- 基础资料表 +- 标准后台管理页面 + +### 4.2 业务查询/迁移型模块 + +典型文件: + +- `controller/SdWTMonitorController.java` +- `service/SdWtMonitorService.java` +- `service/impl/SdWtMonitorServiceImpl.java` +- `service/AlongListService.java` +- `service/impl/AlongListServiceImpl.java` +- `mapper/AlongDetailMapper.java` +- `mapper/SdWtMonitorMapper.java` +- `entity/vo/*` + +特征: + +- 来自旧系统迁移的复杂业务查询较多 +- Controller 聚合多个业务接口 +- Service 接口与实现手写,通常不继承 `ServiceImpl` +- 同时存在注解 SQL、动态 SQL、MyBatis-Plus 分页 +- 使用 `DataSourceRequest`、`DataSourceResult` 适配旧平台 Kendo/DevExtreme 风格 + +适用场景: + +- 旧系统模块迁移 +- 复杂统计、图表、对比分析、联表明细 +- 需要兼容旧平台入参与返回结构的接口 + +## 5. 模块分层规范 + +### 5.1 Controller 层职责 + +Controller 仅负责: + +- 暴露路由 +- 参数接收 +- 基础空值校验 +- 调用 Service +- 统一包装 `ResponseResult` + +Controller 禁止: + +- 拼接 SQL +- 大段业务判断 +- 循环组装复杂结果 +- 直接操作 Mapper + +当前项目推荐写法: + +- 类上使用 `@RestController` +- 类上使用 `@RequestMapping` +- 类上使用 `@Tag` +- 方法上使用 `@Operation` +- 返回值统一为 `ResponseResult` + +示例风格: + +- 业务聚合 Controller:`SdWTMonitorController` +- 管理型 Controller:`SdCountryBController` + +### 5.2 Service 层职责 + +Service 层负责: + +- 业务编排 +- 参数解析 +- 分页对象生成 +- 调用 Mapper +- Java 层补充计算 +- 返回对象组装 + +Service 层应根据模块类型选择实现方式: + +- 基础 CRUD:优先 `IxxxService + ServiceImpl` +- 复杂业务查询:优先 `XxxService + XxxServiceImpl` +- 旧系统迁移模块:优先显式定义业务接口,避免泛化成通用 CRUD Service + +### 5.3 Mapper 层职责 + +Mapper 层负责: + +- 数据查询与数据写入 +- 注解 SQL 或 XML SQL +- 复杂 join、exists、聚合、分页 SQL + +Mapper 层禁止: + +- 放业务语义不清的万能 SQL +- 将参数语义不明确地混在同一个方法中 +- 为了省事直接将前端字段名等同于数据库字段名而不做映射说明 + +### 5.4 VO / DTO / Domain 职责 + +当前项目已存在三类对象: + +- `domain`:数据库实体,主要服务于 MyBatis-Plus CRUD +- `entity/vo`:前端返回对象、业务查询对象 +- `common`:分页、过滤、排序、结果包装对象 + +规范要求: + +- 单表管理页优先使用 `domain` +- 复杂查询、聚合结果、旧接口兼容结构必须使用独立 VO +- 不允许复用 `domain` 直接承接复杂联表查询结果 +- 前端字段名与数据库字段名不一致时,SQL 必须用旧字段名作为别名返回 + +## 6. 推荐目录规范 + +新增业务模块建议按如下目录落位: + +```text +src/main/java/com/yfd/platform// + controller/ + service/ + service/impl/ + mapper/ + domain/ + entity/vo/ +``` + +命名建议: + +- Controller:`XxxController` +- Service:`XxxService` +- ServiceImpl:`XxxServiceImpl` +- Mapper:`XxxMapper` +- 管理类 Service:`IXxxService` +- 管理类 ServiceImpl:`XxxServiceImpl extends ServiceImpl` +- Domain:与表/业务实体对应 +- VO:以业务含义命名,如 `SdYearListVO`、`WtFishVo` + +## 7. 接口设计规范 + +### 7.1 返回结构规范 + +本项目统一返回对象为 `ResponseResult`,必须使用: + +- `ResponseResult.success()` +- `ResponseResult.successData(data)` +- `ResponseResult.error(msg)` + +禁止: + +- Controller 直接返回裸对象 +- 不同模块返回结构不一致 +- 同类接口同时混用 `Map`、实体、裸数组作为顶层响应 + +### 7.2 异常规范 + +业务异常统一使用 `BizException`。 + +适用场景: + +- 必填参数缺失 +- 业务前置条件不满足 +- 站点、工程、字典等基础数据不存在 +- 旧接口兼容校验失败 + +推荐写法: + +```java +if (StrUtil.isBlank(stcd)) { + throw new BizException("站点编码不能为空."); +} +``` + +禁止: + +- `throw new RuntimeException(...)` +- Controller 静默吞异常 +- Service 返回 `null` 表示明确错误 + +### 7.3 参数接收规范 + +当前项目主要有两类接口: + +- Kendo/DevExtreme 风格:`@RequestBody DataSourceRequest` +- 标准后台管理风格:`@RequestParam` + 分页参数 + +规范要求: + +- 旧平台迁移接口优先保留 `DataSourceRequest` +- 后台管理接口优先使用显式参数 +- 不允许将大量业务字段塞入 `Map` 作为常规入参 +- `Map` 仅用于动态字段更新、非结构化补丁写入等少数场景 + +## 8. 分页与筛选规范 + +### 8.1 `DataSourceRequest` 适用范围 + +`DataSourceRequest` 是本项目重要的兼容层对象,适用于: + +- 迁移旧平台 Kendo Grid 接口 +- 需要复杂 filter/group/sort/select 的业务页面 +- 旧接口需要兼容前端请求结构时 + +规范要求: + +- 迁移旧接口时优先从 `DataSourceRequest` 中提取明确字段 +- 尽量封装公共提取逻辑,如使用 `QgcQueryWrapperUtil.getFilterFieldValue(...)` +- 对必要参数要先做空值校验 + +### 8.2 `DataSourceResult` 规范 + +使用 `DataSourceResult` 时应设置: + +- `data` +- `total` +- `aggregates` + +推荐: + +- 无聚合时设置空 `HashMap<>` +- 无数据时返回空列表而不是 `null` + +## 9. 依赖注入规范 + +现状: + +- 项目中大量使用 `@Resource` 字段注入 + +建议: + +- 存量代码允许继续使用 `@Resource` +- 新增业务模块优先使用构造器注入 +- 若为了保持模块风格一致,可在同一文件中延续现有注入方式 + +统一要求: + +- 不允许通过 `SpringContextHolder` 主动取 Bean 代替正常依赖注入,除非是历史兼容场景且无法重构 + +## 10. SQL 与数据访问规范 + +### 10.1 选型原则 + +优先级如下: + +1. 单表 CRUD、简单条件分页:MyBatis-Plus +2. 明确的小型查询:Mapper 注解 SQL +3. 复杂迁移查询、动态列、旧 SQL 兼容:`MicroservicDynamicSQLMapper` +4. 复杂 XML 已有现成资产且迁移成本高:可保留 XML + +### 10.2 MyBatis-Plus 适用规范 + +适用于: + +- 基础字典 +- 台账管理 +- 后台标准分页列表 + +要求: + +- 使用 `lambdaQuery()`、`lambdaUpdate()` +- 条件判断写在链式条件中 +- 避免大量手写 SQL 替代简单 CRUD + +### 10.3 动态 SQL 适用规范 + +当前项目已有 `MicroservicDynamicSQLMapper`,适用于: + +- 旧系统 SQL 平移 +- 表结构拆分后的复杂重写 +- 返回 VO 列较多且结构动态 + +使用规范: + +- 动态 SQL 必须先在 Service 中拼装完整语义,不允许把参数语义留给前端猜测 +- SQL 返回字段必须使用 VO 字段名别名 +- 参数统一通过 `Map` 绑定,禁止字符串直接拼接用户输入 +- 非必要不要在 Service 中写多个层层嵌套的查询,优先压缩为可维护的单条 SQL 或小规模明确查询 + +### 10.4 SQL 编写要求 + +必须遵循: + +- 过滤条件尽量前置 +- 明确 `IS_DELETED = 0` +- 与旧系统兼容时保留 `USFL`、`MWAY`、`DTIN_TYPE`、`STTP` 等业务条件 +- 联表字段语义必须先确认再关联 +- 能用 `EXISTS` 的场景优先评估是否优于无谓 join +- 排序字段应稳定,避免前端列表顺序漂移 + +禁止: + +- 用错误语义字段强行 join +- 在 SQL 中随意猜测旧字段映射 +- 使用无法参数化的用户输入拼接 SQL + +### 10.5 旧系统迁移专项规则 + +迁移模块时必须先做三件事: + +1. 找到旧 Controller/Service/Mapper/XML 主入口 +2. 梳理旧表关系和字段语义 +3. 建立旧表到新表的映射说明 + +禁止: + +- 未分析旧 SQL 语义就直接按字段名猜测重写 +- 把旧“横表字段”误当成新“关系表字段” +- 为了快速实现,破坏旧接口请求和返回结构 + +## 11. 业务模块编码规范 + +### 11.1 Controller 编码规范 + +- 一个 Controller 只承载同一业务域或同一前端页面聚合能力 +- 聚合型 Controller 可以包含多个子接口,但应保持前缀一致 +- 方法名与 `@Operation(summary = "...")` 应表达真实业务语义 +- 空值校验放在 Controller 或 Service 入口,不能完全依赖底层报错 + +### 11.2 Service 编码规范 + +- 一个方法只表达一个明确业务能力 +- 复杂逻辑可拆为少量私有方法,但不要抽象出与旧业务语义脱节的“工具方法泛滥” +- 允许在 Service 层做二次排序、月份格式化、标志位组装、列头组装等 Java 后处理 +- 数据库语义判断优先放 SQL,纯展示逻辑优先放 Java + +### 11.3 VO 编码规范 + +- VO 字段必须服务于前端返回,不要无意义堆字段 +- 对旧接口兼容字段要保持命名稳定 +- 使用 Lombok 时优先 `@Data` 或 `@Getter/@Setter` +- 对外 VO 建议加 `@Schema` 注释 + +### 11.4 Domain 编码规范 + +- Domain 应与真实表结构对齐 +- 建议保持字段名、注释、表映射一致 +- 不要将 VO 字段、临时业务字段塞进 Domain + +## 12. 文档与 OpenAPI 规范 + +项目当前已引入 `springdoc-openapi-starter-webmvc-ui`,因此新增接口必须同时维护接口文档。 + +要求: + +- Controller 类使用 `@Tag` +- 方法使用 `@Operation` +- 复杂 VO 使用 `@Schema` +- 对外接口要有清晰的 `summary` + +推荐: + +- 对关键参数补充 `description` +- 对复杂返回结构单独定义 VO,不返回匿名 `Map` +- 将生产环境的 OpenAPI/Swagger UI 访问权限纳入安全控制 + +## 13. 配置管理规范 + +根据 Spring Boot 官方建议,配置应外部化管理。 + +项目要求: + +- 数据源、缓存、安全、文档开关、日志级别等必须放配置文件 +- 多环境配置使用 profile 管理 +- 不允许将数据库地址、账号、密码、第三方密钥硬编码进 Java 代码 +- 与数据库方言相关的行为尽量通过配置或基础层封装统一管理 + +## 14. 日志与可观测性规范 + +项目已引入 `spring-boot-starter-actuator`,因此新增模块应具备基本可观测性意识。 + +要求: + +- 业务异常使用统一异常体系,不在正常分支打 error 日志 +- 大查询、长耗时任务、批量处理建议记录 info/debug 级别日志 +- 不记录敏感字段原文 +- 生产问题定位优先结合 Actuator、日志、SQL 条件进行 + +建议: + +- 对复杂迁移接口记录关键参数与结果数量 +- 对导入、导出、批处理记录耗时 + +## 15. 安全规范 + +由于项目已引入 Spring Security,新增模块应遵循最小暴露原则。 + +要求: + +- Swagger/OpenAPI 文档在生产环境应受控 +- 任何写接口必须考虑权限控制与审计要求 +- 参数校验必须前置,避免越权探测和异常信息泄露 +- 对动态 SQL 特别关注注入风险,所有用户输入都必须通过参数绑定 + +## 16. 测试规范 + +### 16.1 必测场景 + +新增业务模块至少要验证: + +- 正常查询 +- 必填参数缺失 +- 空数据返回 +- 关键筛选条件命中 +- 排序/分页正确 + +### 16.2 测试建议 + +- 单表 CRUD 模块:优先补 Service/Mapper 单元或集成测试 +- 迁移型查询模块:至少保留典型请求体样例和 SQL 验证说明 +- 高风险接口:建议补接口级集成测试 + +### 16.3 不建议 + +- 为了补测试而写无价值的样板测试 +- 完全重复实现逻辑本身的测试 + +## 17. 性能规范 + +### 17.1 查询性能 + +- 优先明确主表与过滤条件 +- 少用无必要的多层嵌套子查询 +- 大表统计优先关注时间条件、站点条件、逻辑删除条件是否下推 +- `ROWNUM = 1`、`FETCH FIRST 1 ROWS ONLY` 等默认值查询必须确保排序稳定 + +### 17.2 Java 21 使用建议 + +本项目使用 Java 21,但业务模块默认仍以同步 MVC 为主。 + +建议: + +- 普通查询接口保持同步风格,避免过早引入响应式复杂度 +- CPU 密集或 I/O 密集的异步能力需经过明确评估后再引入 +- 语言特性应以可读性优先,避免为了“新语法”牺牲团队可维护性 + +## 18. 新增业务模块推荐模板 + +### 18.1 基础管理模块模板 + +- `domain/Xxx.java` +- `mapper/XxxMapper.java` +- `service/IXxxService.java` +- `service/impl/XxxServiceImpl.java` +- `controller/XxxController.java` + +适用: + +- 国家、流域、字典、基础表管理 + +### 18.2 复杂查询模块模板 + +- `entity/vo/XxxVO.java` +- `mapper/XxxMapper.java` +- `service/XxxService.java` +- `service/impl/XxxServiceImpl.java` +- `controller/XxxController.java` + +适用: + +- 旧平台迁移 +- 统计分析 +- 图表联查 +- 多表业务判断 + +## 19. 开发禁止项 + +以下行为在新增业务模块中原则上禁止: + +- Controller 直接操作 Mapper +- 直接返回裸 `Map` 作为通用接口响应 +- 大量复制旧 SQL 但不校验字段语义 +- 将前端字段名直接拼成 SQL 片段 +- 业务异常使用 `RuntimeException` +- 多个接口返回结构风格不统一 +- 在 Service 中堆积无法复用、语义不清的“万能工具方法” +- 未确认表语义就擅自把旧表字段映射到新表字段 +- 对外接口无 `@Operation` 文档说明 + +## 20. 迁移模块专项检查清单 + +新增或改造迁移模块时,提交前必须自检: + +- 是否定位了旧入口 Controller/Service/Mapper/XML +- 是否梳理了旧表职责与表关系 +- 是否建立了旧表到新表的映射说明 +- 是否保持了旧接口路径、入参名、返回字段的兼容性 +- 是否校验了关键 SQL 的过滤条件、排序条件和时间口径 +- 是否补充了空值、默认值、无数据场景处理 +- 是否检查了 `IS_DELETED`、`USFL`、`MWAY`、`DTIN_TYPE` 等业务条件 +- 是否校验了 `ResponseResult`、`DataSourceResult`、VO 返回结构 +- 是否完成最基本的诊断或测试验证 + +## 21. 推荐落地原则 + +本项目业务模块开发建议遵循以下顺序: + +1. 先确认业务语义与表关系 +2. 再确定模块属于 CRUD 型还是迁移查询型 +3. 再选择 MyBatis-Plus、注解 SQL 或动态 SQL +4. 再定义 VO/Domain/Service/Controller 落位 +5. 最后补异常、文档、校验、测试与性能检查 + +一句话原则: + +以项目现有架构为主线,以 Spring Boot 4.0 现代实践为约束,以“业务语义正确、接口兼容、SQL 可维护、结构可复用”为最终标准。 + +## 22. 附录:外部参考资料 + +- Spring Boot Documentation Overview + - https://docs.spring.io/spring-boot/documentation.html +- springdoc OpenAPI Getting Started + - https://springdoc.org/getting-started.html +- MyBatis-Plus Quick Start + - https://baomidou.com/en/getting-started/ +- MyBatis Spring Boot Starter Reference + - https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ diff --git a/backend/src/main/java/com/yfd/platform/env/service/impl/SdWtMonitorServiceImpl.java b/backend/src/main/java/com/yfd/platform/env/service/impl/SdWtMonitorServiceImpl.java index b0faab2..5313d49 100644 --- a/backend/src/main/java/com/yfd/platform/env/service/impl/SdWtMonitorServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/env/service/impl/SdWtMonitorServiceImpl.java @@ -74,22 +74,10 @@ public class SdWtMonitorServiceImpl implements SdWtMonitorService { @Override public DataSourceResult getWtFishAnalysis(DataSourceRequest dataSourceRequest) { - List filters = new ArrayList<>(); - collectFilters(dataSourceRequest.getFilter(), filters); - - String stcd = null; - String startTime = null; - String endTime = null; - for (DataSourceRequest.FilterDescriptor filter : filters) { - if ("stcd".equals(filter.getField())) { - stcd = toStringValue(filter.getValue()); - } else if ("startTime".equals(filter.getField())) { - startTime = toStringValue(filter.getValue()); - } else if ("endTime".equals(filter.getField())) { - endTime = toStringValue(filter.getValue()); - } - } - + DataSourceLoadOptionsBase loadOptions = dataSourceRequest.toDevRequest(); + String startTime = QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "startTime"); + String endTime = QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "endTime"); + String stcd = QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "stcd"); DataSourceResult result = new DataSourceResult<>(); if (StrUtil.isBlank(stcd) || StrUtil.isBlank(startTime) || StrUtil.isBlank(endTime)) { result.setData(new ArrayList<>()); @@ -135,11 +123,18 @@ public class SdWtMonitorServiceImpl implements SdWtMonitorService { String hbrvcd = wtFishVoList.isEmpty() ? null : wtFishVoList.get(0).getHbrvcd(); if (StrUtil.isNotBlank(hbrvcd)) { - String fishSql = "SELECT ID, NAME, PRETEMP AS pretempStr, SPAWN_MONTH AS spawnMonthStr " + - "FROM SD_FISHDICTORY_B " + - "WHERE IS_DELETED = 0 AND ENABLE = 1 AND PRETEMP IS NOT NULL AND SPAWN_MONTH IS NOT NULL " + - "AND RVCD = #{map.hbrvcd} " + - "ORDER BY NVL(ORDER_INDEX, 999999), NAME"; + String fishSql = "SELECT t1.ID AS id, " + + "t1.NAME AS name, " + + "t1.PRETEMP AS pretempStr, " + + "t1.SPAWN_MONTH AS spawnMonthStr " + + "FROM SD_FISHDICTORY_B t1 " + + "INNER JOIN SD_FISHDICTORY_RLTN_B t2 ON t1.ID = t2.ZY_FISH_ID " + + "WHERE t1.IS_DELETED = 0 " + + " AND t2.IS_DELETED = 0 " + + " AND t2.RVCD = #{map.hbrvcd} " + + " AND t1.PRETEMP IS NOT NULL " + + " AND t1.SPAWN_MONTH IS NOT NULL " + + "ORDER BY NVL(t2.ORDER_INDEX, 999999), NVL(t1.ORDER_INDEX, 999999), t1.NAME"; Map fishParamMap = new HashMap<>(); fishParamMap.put("hbrvcd", hbrvcd); List fishSpawnVoList = microservicDynamicSQLMapper.getAllListWithResultType(fishSql, fishParamMap, FishSpawnVo.class);