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

590 lines
21 KiB
Markdown
Raw Normal View History

# 后端模块开发指南
从广州广告监管系统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 并行开发