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

21 KiB
Raw Permalink Blame 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. 数据源

项目使用苞米豆动态数据源,两个数据源

数据源 用途 数据库名
masterprimary 业务库 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 设为 devprod
  • 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. 编译命令

# 开发阶段编译(跳过 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

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

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 BooleanInteger 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 模板

@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 模板

@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 模板

@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)
  • 查询用 QueryWrapperLambdaQueryWrapper

C7. Mapper 模板

@Mapper
public interface XxxMapper extends BaseMapper<XxxEntity> {
}

无需 XMLMyBatis-Plus 自动提供 CRUD。新模块 mapper 放在 com.chinaweal.youfool.prj.**.mapper 包下自动注册。

C8. Query 模板

@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-001CW-EVI-001
  • 审计字段默认值
    • create_time / update_timeTO_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. 编译验证

# 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 + 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 精度溢出错误 改用 TINYINTINT
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 compileFailed 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 并行开发