gz-oarms/docs/backend-module-dev-guide.md

590 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 后端模块开发指南
从广州广告监管系统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小写表名 OKDM8 自动转大写)
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` | 主键 IDASSIGN_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<IPage<Xxx>> queryList(@RequestBody XxxQuery query) {
log.info("[OK] 查询列表: key={}", query.getKey());
return RestResult.ok(xxxService.queryList(query));
}
@GetMapping("/detail")
@Operation(summary = "查询详情")
public RestResult<XxxDetailVO> getDetail(@RequestParam String id) {
log.info("[OK] 查询详情: id={}", id);
return RestResult.ok(xxxService.getDetail(id));
}
@PostMapping("/save")
@Operation(summary = "新增")
public RestResult<String> save(@RequestBody XxxSaveReq req) {
return RestResult.ok(xxxService.save(req));
}
@PostMapping("/update")
@Operation(summary = "修改")
public RestResult<Void> update(@RequestBody XxxSaveReq req) {
xxxService.update(req);
return RestResult.ok();
}
@PostMapping("/remove")
@Operation(summary = "删除")
public RestResult<Void> remove(@RequestBody Map<String, String> params) {
xxxService.remove(params.get("id"));
return RestResult.ok();
}
@GetMapping("/xxx/list")
@Operation(summary = "选项列表")
public RestResult<List<Map<String, String>>> listXxxOptions() {
return RestResult.ok(xxxService.getXxxOptions());
}
}
```
- DI`@AllArgsConstructor` + `private final`
- 返回值:`RestResult.ok(data)` / `RestResult.ok()`
- 日志:`log.info("[OK] 操作描述: key={}", value)`
- 删除用 `Map<String, String>` 接收 id
- 选项接口返回 `List<Map<String, String>>`label + value
## C6. Service 模板
```java
@Slf4j
@Service
public class XxxServiceImpl extends ServiceImpl<XxxMapper, XxxEntity>
implements IXxxService {
@Override
public IPage<XxxEntity> queryList(XxxQuery query) {
Page<XxxEntity> page = new Page<>(query.getPageNum(), query.getPageSize());
QueryWrapper<XxxEntity> 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<XxxEntity> {
}
```
无需 XMLMyBatis-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<String> orderFields;
@Schema(description = "排序规则")
private List<String> 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 并行开发