# 后端模块开发指南 从广州广告监管系统(OARMS)各模块开发中提炼的完整流程、约定和踩坑记录。 适用于所有新模块(AM / BS / CW / LB / MR)的后端开发。 **开发模式**:将 mock 数据插入到数据库中,不使用 mock 数据,直连达梦 DM8 数据库读写,所有接口面向生产环境数据。 --- # A. 运行环境 ## A1. 技术栈 | 组件 | 版本 | 说明 | |------|------|------| | JDK | 21 | `JAVA_HOME="/d/Program Files/Java/jdk-21.0.11+10"` | | Spring Boot | 3.4.5 | + youfool-framework-springboot3 1.0.4(公司内部框架) | | MyBatis-Plus | - | ORM,分页插件配置在 `mybatis/mybatis-config.xml` | | Sa-Token | - | 认证鉴权,拦截规则在 `SpringMvcConfig` | | dynamic-datasource | baomidou | 多数据源切换 | | Knife4j | - | API 文档,路径 `/doc.html` | | Druid | - | 连接池 + 监控,路径 `/druid` | ## A2. 数据源 项目使用苞米豆动态数据源,**两个数据源**: | 数据源 | 用途 | 数据库名 | |--------|------|----------| | `master`(primary) | 业务库 | OARMS | | `youfool` | 框架库(restLog 等) | YOUFOOL | 实体类注解:`@DS("master")` + `@TableName(schema = "OARMS", value = "表名")` ## A3. 数据库连接信息(达梦 DM8) ``` JDBC URL: jdbc:dm://172.22.80.70:15236?schema=OARMS 用户名: SYSDBA 密码: chinaweal 驱动类: dm.jdbc.driver.DmDriver 驱动 JAR: target/libs/DmJdbcDriver18-8.1.3.62.jar ``` ## A4. Maven 配置 - **Maven 路径**:`/d/apache-maven-3.9.15/bin/mvn` - **settings.xml**:`/d/apache-maven-3.9.15/conf/settings.xml` - **本地仓库**:`D:/maven-repository` - **Profile**:`-Pcompany-nexus`(公司私有仓库 `http://121.8.152.130:8081/nexus/content/groups/public/`) ## A5. 应用配置 - **默认端口**:8080,环境变量 `PRJ_PORT` 可覆盖 - **环境切换**:`spring.profiles.active` 设为 `dev` 或 `prod` - **Mapper 扫描**:`com.chinaweal.youfool.prj.**.mapper`(新模块 mapper 放在此包下自动注册) - **XML 加载**:`classpath*:mybatis/mapper/**/*.xml` ## A6. 认证鉴权 - Sa-Token 拦截器注册在 `SpringMvcConfig`,默认所有路径需登录 - 白名单(免登录):`/user/auth/**`、`/test/**`、`/doc.html**`、`/cms/index.html` 等 --- # B. 工具 ## B1. 编译命令 ```bash # 开发阶段编译(跳过 checkstyle) JAVA_HOME="/d/Program Files/Java/jdk-21.0.11+10" \ /d/apache-maven-3.9.15/bin/mvn compile -Pcompany-nexus -Dcheckstyle.skip=true # 完整编译(含 checkstyle) JAVA_HOME="/d/Program Files/Java/jdk-21.0.11+10" \ /d/apache-maven-3.9.15/bin/mvn compile -Pcompany-nexus ``` 强制重新编译指定模块(跳过 clean): ```bash rm -rf target/classes/com/chinaweal/youfool/prj/modules/{module-name}/ JAVA_HOME="/d/Program Files/Java/jdk-21.0.11+10" \ /d/apache-maven-3.9.15/bin/mvn compile -Pcompany-nexus -Dcheckstyle.skip=true ``` ## B2. 数据库执行工具:Python + JayDeBeApi 用于执行 DDL 和初始数据到 DM8: ```python import jaydebeapi conn = jaydebeapi.connect( 'dm.jdbc.driver.DmDriver', 'jdbc:dm://172.22.80.70:15236?schema=OARMS', ['SYSDBA', 'chinaweal'], 'target/libs/DmJdbcDriver18-8.1.3.62.jar' ) cursor = conn.cursor() # CREATE TABLE(小写表名 OK,DM8 自动转大写) cursor.execute("CREATE TABLE OARMS.xxx (...)") # COMMENT / INDEX(必须大写表名列名) cursor.execute("COMMENT ON TABLE OARMS.XXX IS '注释'") cursor.execute("CREATE INDEX IDX_XXX_COL ON OARMS.XXX (COL)") # INSERT(列名小写 OK) cursor.execute("INSERT INTO OARMS.xxx (...) VALUES (...)") conn.commit() # 验证 cursor.execute("SELECT COUNT(*) FROM OARMS.XXX") print(cursor.fetchone()[0]) cursor.close() conn.close() ``` ## B3. SQL 版本号分配 DDL 文件统一放在 `docs/db/sql/`,命名规则 `V{版本号}__{模块描述}_{ddl|init_data}.sql`。 | 版本 | 用途 | 文件 | |------|------|------| | V1.0.0 | BS 大屏基础信息管理 | `V1.0.0__BS_screen_ddl.sql` + `_init_data.sql` | | V2.0.0 | LB 法律法规管理 | `V2.0.0__LB_law_ddl.sql` + `_init_data.sql` | | V3.0.0 | AM-1 广告画面录屏设置管理 | `V3.0.0__AM_recording_config_ddl.sql` | | V4.0.0 | AM-2 广告画面随机录屏 | `V4.0.0__AM_recording_task_ddl.sql` | | V5.0.0 | AM-3 广告画面监控 | `V5.0.0__AM_monitor_record_ddl.sql` | | V6.0.0 | MR-1 监测规则管理 | `V6.0.0__MR_monitoring_rule_ddl.sql` + `_init_data.sql` | | V7.0.0 | CW-1 广告监测固化取证 | `V7.0.0__CW_evidence_ddl.sql` | | V8.0.0 | CW-2 监测规则关联 | `V8.0.0__CW_evidence_rule_relation_ddl.sql` | | V9.0.0 | CW-3 监测线索生成 | `V9.0.0__CW_monitoring_clue_ddl.sql` | | V10.0.0 | CW-4 对接线索转办 | `V10.0.0__CW_clue_transfer_ddl.sql` | --- # C. 记忆 > 开发每个模块时必须遵循的模式、约定和模板。直接复制使用,不要重新设计。 ## C1. 表前缀约定 | 业务域 | 前缀 | Java 包名 | |--------|------|-----------| | 大屏基础库 | `bs_` | screen | | 法律法规库 | `lb_` | law | | 广告监控 | `am_` | monitor | | 监测规则库 | `mr_` | rule | | 内容预警 | `cw_` | evidence | ## C2. DM8 类型 → Java 映射 | DM8 类型 | Java 类型 | 场景 | |----------|-----------|------| | `VARCHAR(50)` | `String` | 主键 ID(ASSIGN_UUID) | | `VARCHAR(20~200)` | `String` | 名称、编码 | | `VARCHAR(500~1000)` | `String` | 描述、地址 | | `CLOB` | `String` | 大文本、JSON(规则配置、快照) | | `TINYINT` | `Boolean` 或 `Integer` | 0/1 标志位 | | `DECIMAL(10,6)` | `BigDecimal` | 经纬度(精度 ≤ 38) | | `INT` | `Integer` | 数量、排序号 | | `DATE` | `LocalDate` | 仅日期 | | `TIMESTAMP` | `LocalDateTime` | 日期 + 时间 | ## C3. 包结构模板 ``` modules/{module-name}/ ├── entity/ │ ├── {MainEntity}.java │ ├── {SubEntity}.java │ ├── query/ │ │ └── {Entity}Query.java # 分页 + 筛选 │ ├── req/ │ │ ├── {Entity}SaveReq.java # 新增/修改 │ │ └── {Action}Req.java # 特殊操作 │ └── vo/ │ └── {Entity}DetailVO.java # 详情聚合 ├── mapper/ │ └── {Entity}Mapper.java ├── service/ │ ├── I{Entity}Service.java │ └── impl/ │ └── {Entity}ServiceImpl.java └── controller/ └── {Entity}Controller.java ``` ## C4. Entity 模板 ```java @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) @DS("master") @TableName(schema = "OARMS", value = "bs_screen") public class ScreenEntity extends SuperEntity { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.ASSIGN_UUID) private String id; @TableField("screen_name") private String screenName; } ``` - `SuperEntity` 提供 6 个审计字段(createBy/createTime/createName/updateBy/updateTime/updateName),**不含 id** - 日期字段加 `@JsonFormat(pattern = DateUtil.DATE_DEFAULT_FORMAT, timezone = "GMT+8")` - 日期时间字段加 `@JsonFormat(pattern = DateUtil.DATETIME_DEFAULT_FORMAT, timezone = "GMT+8")` ## C5. Controller 模板 ```java @Slf4j @RestController @RequestMapping("/api/{resource-path}") @AllArgsConstructor @Tag(name = "模块名称", description = "模块描述") public class XxxController { private final IXxxService xxxService; @PostMapping("/query") @Operation(summary = "分页查询") public RestResult> queryList(@RequestBody XxxQuery query) { log.info("[OK] 查询列表: key={}", query.getKey()); return RestResult.ok(xxxService.queryList(query)); } @GetMapping("/detail") @Operation(summary = "查询详情") public RestResult getDetail(@RequestParam String id) { log.info("[OK] 查询详情: id={}", id); return RestResult.ok(xxxService.getDetail(id)); } @PostMapping("/save") @Operation(summary = "新增") public RestResult save(@RequestBody XxxSaveReq req) { return RestResult.ok(xxxService.save(req)); } @PostMapping("/update") @Operation(summary = "修改") public RestResult update(@RequestBody XxxSaveReq req) { xxxService.update(req); return RestResult.ok(); } @PostMapping("/remove") @Operation(summary = "删除") public RestResult remove(@RequestBody Map params) { xxxService.remove(params.get("id")); return RestResult.ok(); } @GetMapping("/xxx/list") @Operation(summary = "选项列表") public RestResult>> listXxxOptions() { return RestResult.ok(xxxService.getXxxOptions()); } } ``` - DI:`@AllArgsConstructor` + `private final` - 返回值:`RestResult.ok(data)` / `RestResult.ok()` - 日志:`log.info("[OK] 操作描述: key={}", value)` - 删除用 `Map` 接收 id - 选项接口返回 `List>`(label + value) ## C6. Service 模板 ```java @Slf4j @Service public class XxxServiceImpl extends ServiceImpl implements IXxxService { @Override public IPage queryList(XxxQuery query) { Page page = new Page<>(query.getPageNum(), query.getPageSize()); QueryWrapper wrapper = Wrappers.query(); if (query.getName() != null && !query.getName().isEmpty()) { wrapper.like("name", query.getName()); } wrapper.orderByAsc("name"); return baseMapper.selectPage(page, wrapper); } @Override @DSTransactional public String save(XxxSaveReq req) { XxxEntity entity = new XxxEntity(); BeanUtils.copyProperties(req, entity, "id"); save(entity); return entity.getId(); } } ``` - **事务必须用 `@DSTransactional`** - `BeanUtils.copyProperties(req, entity, "id")` 第三个参数忽略 id - 分页用 `new Page<>(pageNum, pageSize)` - 查询用 `QueryWrapper` 或 `LambdaQueryWrapper` ## C7. Mapper 模板 ```java @Mapper public interface XxxMapper extends BaseMapper { } ``` 无需 XML,MyBatis-Plus 自动提供 CRUD。新模块 mapper 放在 `com.chinaweal.youfool.prj.**.mapper` 包下自动注册。 ## C8. Query 模板 ```java @Data @Accessors(chain = true) @Schema(description = "Xxx查询参数") public class XxxQuery implements Serializable { private static final long serialVersionUID = 1L; @Schema(description = "页码") private Integer pageNum = 1; @Schema(description = "每页条数") private Integer pageSize = 10; @Schema(description = "排序字段") private List orderFields; @Schema(description = "排序规则") private List orderSorts; // ... 筛选条件 } ``` ## C9. 对象映射选择 | 方面 | MapStruct | BeanUtils | |------|-----------|-----------| | 依赖 | 需要 `convert/` 接口 | Spring 自带 | | 编译警告 | unmapped properties 警告 | 无 | | 适用场景 | 字段多、映射复杂 | 字段少、映射简单 | | **建议** | 已有模块保持 | **新模块推荐 BeanUtils**,减少样板代码 | ## C10. 初始数据约定 > 系统运行必需的种子数据(码表、默认规则等),不是 mock 测试数据。 > 文件仅用于首次部署时初始化,后续数据通过 API 接口维护。 - **文件命名**:`V{版本号}__{模块描述}_init_data.sql` - **ID 命名**:`{模块缩写}-{实体缩写}-{序号}`,如 `MR-RULE-001`、`CW-EVI-001` - **审计字段默认值**: - `create_time` / `update_time`:`TO_TIMESTAMP('2026-05-18 10:00:00', 'YYYY-MM-DD HH24:MI:SS')` - `create_by` / `update_by`:`'system'` - `create_name` / `update_name`:`'系统管理员'` - **数据原则**:贴近真实业务场景,数量以覆盖主要业务分支为准 - **标记字段**:`is_initial_data = 1` 标记初始数据,用户通过 API 创建的数据为 `0` --- # D. 评估 > 每个模块开发完成后必须执行的验证步骤。 ## D1. 编译验证 ```bash # 1. 删除目标模块编译产物(确保强制重编译) rm -rf target/classes/com/chinaweal/youfool/prj/modules/{module-name}/ # 2. 编译 JAVA_HOME="/d/Program Files/Java/jdk-21.0.11+10" \ /d/apache-maven-3.9.15/bin/mvn compile -Pcompany-nexus -Dcheckstyle.skip=true # 3. 确认输出 BUILD SUCCESS ``` ## D2. 数据库验证 ```python # 通过 Python + JayDeBeApi 连接后执行 cursor.execute("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER='OARMS' AND TABLE_NAME LIKE '{PREFIX}%'") # 确认表已创建 cursor.execute("SELECT COUNT(*) FROM OARMS.{TABLE_NAME}") # 确认初始数据行数符合预期 ``` ## D3. 模块完成检查清单 - [ ] DDL 文件已创建(`docs/db/sql/V*__*_ddl.sql`) - [ ] DDL 已执行到 DM8 数据库,表和索引存在 - [ ] 初始数据文件已创建(`docs/db/sql/V*__*_init_data.sql`,如需要) - [ ] 初始数据已插入数据库,行数正确 - [ ] Entity / Query / Req / VO 类已创建,继承 SuperEntity - [ ] Mapper 接口已创建,继承 BaseMapper - [ ] Service 接口 + ServiceImpl 已创建,事务用 `@DSTransactional` - [ ] Controller 已创建,API 路径和参数与前端 s4 文档一致 - [ ] `mvn compile` BUILD SUCCESS,无编译错误(warnings 可忽略) - [ ] 启动应用,Knife4j 文档可访问(`/doc.html`),新接口可见 - [ ] 通过 API 接口能正常读写数据库数据 --- # E. 边界 > 必须遵守的约束和必须避免的错误。违反这些会导致编译失败或运行时异常。 ## E1. 数据库禁忌 | 禁忌 | 后果 | 正确做法 | |------|------|----------| | COMMENT ON / CREATE INDEX 用小写表名 | 报"无效的表或视图" | **必须用大写**:`OARMS.BS_SCREEN` | | `DECIMAL(100,0)` 等精度 > 38 | DM8 精度溢出错误 | 改用 `TINYINT` 或 `INT` | | 用 `split(';')` 解析 SQL 文件执行 | 注释或 CLOB 中的 `;` 导致语句截断 | 按 SQL 语句逐条在 Python 中硬编码执行 | | 连接串不带 `?schema=OARMS` | 连接成功但操作错误的 schema | URL 必须包含 `?schema=OARMS` | | 用 `OARMS` / `OARMS@2026` 作为用户名密码 | 连接失败 | 用户名 `SYSDBA`,密码 `chinaweal` | ## E2. 代码禁忌 | 禁忌 | 后果 | 正确做法 | |------|------|----------| | 使用 `@Transactional` | 多数据源事务不生效 | **必须用 `@DSTransactional`** | | 实体类不加 `@DS("master")` | 默认走 youfool 数据源 | 所有业务实体加 `@DS("master")` | | 实体类不加 `schema = "OARMS"` | MyBatis-Plus 找不到表 | `@TableName(schema = "OARMS", value = "表名")` | | 主键不加 `@TableId` | MyBatis-Plus 无法识别主键 | `@TableId(value = "id", type = IdType.ASSIGN_UUID)` | | SuperEntity 已有 id 字段 | 重复字段编译错误 | SuperEntity **不含 id**,各实体自行定义 | ## E3. 编译陷阱 | 问题 | 原因 | 解决方案 | |------|------|----------| | `mvn clean compile` 报 `Failed to delete target/xxx.jar` | JAR 被运行中的 Java 进程锁定 | 先停应用,或用 `mvn compile`(跳过 clean) | | 编译输出 `Nothing to compile` | target 中已有旧 class | 先删除对应模块的 class 目录 | | MapStruct unmapped warnings | SuperEntity 审计字段未映射 | 正常现象,不影响编译 | | unchecked warnings | JSON 反序列化泛型操作 | 正常现象,不影响编译 | --- # F. Harness Engineering > 从需求到上线的完整开发管线、模块进度和任务规划。 ## F1. 全流程 ``` 信息收集 → 设计建表 → DDL 脚本 → 初始数据 → Java 代码 → 执行建表 → 插入初始数据 → 编译验证 → 接口联调 ``` ### Step 1: 信息收集(并行读取) | 输入 | 路径 | 用途 | |------|------|------| | 需求文档 s1 | `gz-oarms-web/docs/prd-llm/{域}*/{模块}*/s1-需求分析.md` | 理解业务背景、角色、流程 | | 实体定义 s2 | `gz-oarms-web/docs/prd-llm/{域}*/{模块}*/s2-实体定义.md` | **DDL 和 Entity 的主要来源** | | API 设计 s4 | `gz-oarms-web/docs/prd-llm/{域}*/{模块}*/s4-api-design.md` | Controller 接口定义 | | 编码任务 s4 | `gz-oarms-web/docs/prd-llm/{域}*/{模块}*/s4-编码任务.md` | 页面和 API 总览 | | 本开发指南 | `docs/backend-module-dev-guide.md` | 代码风格和流程规范 | | 全局实体字典 | `gz-oarms-web/docs/prd-llm/DM-全局数据模型/全局实体字典.md` | 核心实体跨域引用 | **操作建议**:并行读取 s1 + s2 + s4(api) + s4(编码任务),不要串行。 ### Step 2: 设计建表 根据 s2 实体定义,产出 DDL 脚本(参见 C 记忆部分的模板和类型映射)。 ### Step 3: 准备初始数据 系统运行必需的种子数据(码表、默认配置、示例业务数据),参见 C10 初始数据约定。 不是所有模块都需要初始数据文件——部分模块的表由 API 接口填充。 ### Step 4: 生成 Java 代码 按 C3 包结构模板,依次生成: ``` Entity → Query/Req/VO → Mapper → Service(接口+实现) → Controller ``` ### Step 5: 执行建表 + 插入初始数据 使用 B2 数据库执行工具,按 D1/D2 评估步骤验证。 ### Step 6: 编译验证 执行 D3 检查清单。 ### Step 7: 接口联调 启动应用,通过 Knife4j(`/doc.html`)或前端页面验证 API 接口能正常读写数据库。 ## F2. 模块开发进度总表 > 状态标记:✅ 已完成 | 🔨 部分完成 | ⏳ 待开发 ### 大屏基础库域(BS) | 模块 | 功能 | DDL | 初始数据 | Java 代码 | 编译 | 备注 | |------|------|:---:|:----:|:---------:|:----:|------| | BS-1 大屏基础信息管理 | 大屏 CRUD + 地图 + 导出 | ✅ | ✅ 8条 | ✅ | ⏳ | 核心实体 SCREEN | ### 法律法规库域(LB) | 模块 | 功能 | DDL | 初始数据 | Java 代码 | 编译 | 备注 | |------|------|:---:|:----:|:---------:|:----:|------| | LB-1 法律法规管理 | 法律法规 CRUD | ✅ | ✅ 5条 | ✅ | ⏳ | | ### 广告监控域(AM) | 模块 | 功能 | DDL | 初始数据 | Java 代码 | 编译 | 备注 | |------|------|:---:|:----:|:---------:|:----:|------| | AM-1 广告画面录屏设置管理 | 录屏配置 CRUD | ✅ | - | ✅ | ⏳ | | | AM-2 广告画面随机录屏 | 录屏任务管理 | ✅ | - | ✅ | ⏳ | | | AM-3 广告画面监控 | 监控审核记录 | ✅ | - | ✅ | ⏳ | | ### 监测规则库域(MR) | 模块 | 功能 | DDL | 初始数据 | Java 代码 | 编译 | 备注 | |------|------|:---:|:----:|:---------:|:----:|------| | MR-1 监测规则管理 | 监测规则/法律条款 CRUD | ✅ | ✅ 3+3+3条 | ✅ | ⏳ | 核心实体 MONITORING_RULE | ### 内容预警域(CW) | 模块 | 功能 | DDL | 初始数据 | Java 代码 | 编译 | 备注 | |------|------|:---:|:----:|:---------:|:----:|------| | CW-1 广告监测固化取证 | 取证记录管理 | ✅ | - | ✅ | ⏳ | 核心实体 EVIDENCE_RECORD | | CW-2 监测规则关联 | 证据-规则多对多 | ✅ | - | ✅ | ⏳ | | | CW-3 监测线索生成 | 线索生成与管理 | ✅ | - | ✅ | ⏳ | | | CW-4 对接线索转办 | 线索推送转办 | ✅ | - | ✅ | ⏳ | | ## F3. 核心实体跨域依赖 ``` SCREEN (BS-1) ──── 被引用于 ──── AM-1/2/3(录屏配置/任务/监控) CW-1(取证关联大屏) MONITORING_RULE (MR-1) ──── 被引用于 ──── CW-2(证据规则关联) CW-3(线索生成依据) EVIDENCE_RECORD (CW-1) ──── 被引用于 ──── CW-2(规则关联) CW-3(线索来源) CW-4(转办依据) ``` **开发建议**:先开发 BS-1(大屏)和 MR-1(规则),这两个是核心实体,被多个下游模块依赖。 ## F4. 待开发任务建议顺序 ### P1: BS-1 大屏基础信息管理 - **需求文档**:`BS-大屏基础库域/BS-1_大屏基础信息管理/` - **核心实体**:SCREEN(评分 0.956,全系统最高) - **被依赖**:AM-1/2/3、CW-1 均关联大屏 - **工作量**:中(标准 CRUD + 地图 + 导出) ### P2: MR-1 监测规则管理 - **需求文档**:`MR-监测规则库域/MR-1_监测规则管理/` - **核心实体**:MONITORING_RULE(评分 0.738) - **被依赖**:CW-2(证据规则关联)、CW-3(线索生成) - **工作量**:中(规则 + 法律条款 CRUD) ### P3: AM-1~3 广告监控(可并行) - **需求文档**:`AM-广告监控域/AM-{1,2,3}_*/` - **依赖**:BS-1(关联大屏) - **工作量**:大(3 个子模块) ### P4: CW-1~4 内容预警(顺序开发) - **需求文档**:`CW-内容预警域/CW-{1,2,3,4}_*/` - **依赖**:BS-1(关联大屏)+ MR-1(关联规则) - **工作量**:大(4 个子模块,建议 CW-1 → CW-2 → CW-3 → CW-4 顺序) ### P5: LB-1 法律法规管理 - **需求文档**:`LB-法律法规库域/LB-1_法律法规管理/` - **依赖**:无强依赖,但 MR-1 会引用法律条款 - **工作量**:小(标准 CRUD) - **建议**:可与 P1/P2 并行开发