--- name: jeecg-dev description: "JeecgBoot 开发规范(仅手动触发)。⚠️ 本技能只在用户显式输入 /jeecg-dev 或 $jeecg-dev 命令时使用,禁止自动触发——编写/修改 JeecgBoot 代码、应用 GitHub PR/issue 改动、修复 bug、新增功能、重构、代码生成等场景都不要自动调用本技能。内容涵盖 update-begin/end 痕迹注释、命名规范、实体/控制器/服务模式、API 约定、建表规则、MyBatis 多数据库兼容与修改日志实践。MANUAL ONLY: invoke ONLY when the user explicitly runs the /jeecg-dev command. Do NOT auto-trigger on any code editing, bug fix, PR/issue application, refactoring, or code generation." --- # JeecgBoot 开发规范 在 JeecgBoot 项目中编写或修改代码时,必须遵循以下规范。本 skill 是强制性的——任何代码变更都必须符合这些标准。 简单收尾直接复用当前线程结果,不重复扫描和构建。 --- # 一、必做事项(每次代码修改必须完成,缺一不可) ## 1. 代码修改痕迹注释(内联注释) **针对原有核心逻辑代码的修改,请统一增加头尾日志,并使用 update-begin / update-end 注释进行代码块标识,方便后续代码追踪、维护和差异定位。** ```java //update-begin---author:作者名 ---date:YYYYMMDD for:【bug号/需求号】修改说明----------- // 已有方法体中被修改的核心逻辑 //update-end---author:作者名 ---date:YYYYMMDD for:【bug号/需求号】修改说明----------- ``` **业务核心逻辑(必须标记)** 核心业务流程处理 状态流转逻辑 业务规则计算 权限校验 审批流程 数据转换和业务组装逻辑 **规则:** - `author` 填实际修改人,`date` 格式 `YYYYMMDD`(无横线),`for` 填 bug号/需求号 + 简要说明 - **只在修改已有方法体内的核心逻辑时包裹**:`update-begin` / `update-end` 只包裹被修改的代码段 - 只针对核心逻辑加,简单修改不要加 - 用户未提供 bug 号时,必须主动询问;如果用户明确回复“无号”或“无编号”,视为已确认没有 bug/需求号,不再询问 - **无编号时,`for` 直接填写修改说明,禁止生成 `【无号】`、`【无编号】` 或空的 `【】`**。例如:`for:图片模型测试连接使用快速参数` ### 前端轻量化规则(优先级高于通用判断) Vue、React、JavaScript、TypeScript 等前端代码默认不加 `update-begin/end`。前端变化通常可直接通过 SVN diff 识别,避免为局部交互和状态更新增加大量头尾注释。 前端只有以下关键逻辑修改需要标记: - 权限、鉴权、数据脱敏、安全校验 - 审批、支付、订单等不可逆或跨步骤的核心业务状态流转 - 直接决定提交数据或业务结果的复杂规则计算、数据转换 - 影响多个模块的公共核心逻辑,且修改原因无法从代码和 diff 直接判断 以下常见前端改动一律不加: - 模板、样式、文案、字段展示、显隐和布局调整 - `loading`、弹窗、页签、选中项、列表刷新、事件 `emit` 等页面状态或交互更新 - 简单的请求参数组装、响应字段兼容、过滤、排序、计数、格式化和默认值处理 - 普通增删改按钮事件、成功回调、错误提示、表单校验和局部 bug 修复 - 仅因方法体中出现 `if`、循环、`async/await` 或多行代码;这些语法本身不构成核心逻辑 例如,保存成功后补充 `emit('success')` 刷新列表,或通过 `filter` 统计启用项数量,均不需要 `update-begin/end`。 **⚠️ 不需要加痕迹注释的改动(以下一律不加):** 以下改动 **不要** 加 `update-begin/end` 注释,否则徒增 diff 噪音、破坏代码整洁: - **新增的类**(VO、Entity、DTO、枚举等整个新文件):类 Javadoc 必须包含 `@author 创建人`、`@since YYYY-MM-DD 原因`,有 TB号/BUG号/issue号时一并写入;无需 update-begin/end 包裹 - **新增的方法**(Mapper 接口新增方法声明、Service/Controller 新增方法):方法 Javadoc 必须包含 `@author 创建人`、`@since YYYY-MM-DD 原因`,有 TB号/BUG号/issue号时一并写入;无需 update-begin/end 包裹 - **新增的 XML SQL 块**(MyBatis Mapper XML 新增 ` SELECT * FROM sys_user WHERE id = #{id}; ``` **检查要求:** - 新增或修改 Mapper SQL 时,必须检查语句最后一个有效字符不是 `;`。 - 可使用 `rg -n ';\s*$' <模块路径> -g '*Mapper.xml'` 辅助检索,但必须人工排除 XML 实体(如 `>`)、注释等非 SQL 内容。 - 不得通过数据库连接参数、Druid/MyBatis 全局拦截器统一删除分号;全局处理可能破坏 PL/SQL/DMSQL 语句块。 - Oracle PL/SQL、达梦 DMSQL 等语法本身要求分号的语句块属于例外,必须按数据库方言隔离(如 `databaseId` 或独立 Mapper),禁止放入通用 SQL 后依赖所有数据库执行。 ## ❌ 严禁:`CONCAT` 传入三个及以上参数 Oracle 的 `CONCAT` 函数严格只接受两个参数。MySQL 等数据库允许的多参数写法放入通用 Mapper SQL 后,在 Oracle 下会触发 `ORA-00909: 参数个数无效`。 ```xml AND r.name LIKE CONCAT('%', #{keyword}, '%') AND r.name LIKE CONCAT(CONCAT('%', #{keyword}), '%') ``` 统一使用嵌套双参数 `CONCAT`,不要改成仅适用于部分数据库的字符串连接语法。前缀或后缀匹配本身只有两个参数时,可直接调用: ```xml AND org_code LIKE CONCAT(#{orgCode}, '%') ``` **检查要求:** - 新增或修改 Mapper XML、Mapper 注解 SQL、MiniDao SQL 模板时,检查每个 `CONCAT` 的顶层参数数量,确保不超过两个。 - 修复一处多参数 `CONCAT` 后,必须扫描当前模块;涉及公共功能或数据库兼容专项修复时,扫描整个项目源码,并排除 `target`、`.svn`、`node_modules`、`dist` 等生成目录。 - 可先使用 `rg -n -i 'concat\s*\(' <检查路径>` 找出候选项,再按括号层级人工确认顶层参数数量;合法的嵌套 `CONCAT(CONCAT(...), ...)` 不得误报。 - 注释示例、应用层自定义可变参数函数、明确隔离的单数据库方言 SQL 不属于通用 Mapper SQL,必须结合上下文判断,禁止机械替换。 ## ❌ 严禁:`resultType="map"` + 字符串 key 直接访问 各数据库对 `resultType="map"` 的列名/别名 key 大小写处理不同: | 数据库 | `AS procInstId` 返回的 key | |--------|--------------------------| | MySQL | `procInstId`(保持别名原样) | | Oracle | `PROCINSTID`(全部大写,丢失驼峰) | | PostgreSQL | `procinstid`(全部小写) | 因此以下写法**在 Oracle/PostgreSQL 下 `m.get(...)` 返回 `null`,必须禁止**: ```java // ❌ 禁止:Oracle 下 key 是 "PROCINSTID",取不到值 List> rows = mapper.queryXxx(); rows.stream().collect(Collectors.toMap( m -> String.valueOf(m.get("procInstId")), // Oracle 下为 null m -> String.valueOf(m.get("text")) // Oracle 下为 null )); ``` ## ✅ 正确做法:使用 DTO 类接收结果 用专门的 DTO 类替代 `Map`,MyBatis 映射到 Java 类时采用**大小写不敏感**的属性匹配,`PROCINSTID`(Oracle)/ `procinstid`(PostgreSQL)/ `procInstId`(MySQL)均能正确映射到 `procInstId` 字段: ```java // DTO 类(简单 POJO,无需注解) public class BizTitleSimpleDTO { private String procInstId; private String text; // getter / setter ... } ``` ```xml ``` ```java // Service 中通过 getter 访问,类型安全且跨数据库 List rows = mapper.getBatchHisVarinst(name, ids); Map result = rows.stream().collect(Collectors.toMap( BizTitleSimpleDTO::getProcInstId, r -> oConvertUtils.getString(r.getText()), (a, b) -> a )); ``` ## 适用 `resultType="map"` 的例外场景 以下情况可以使用 `Map`,但**必须通过固定大写 key** 访问(Oracle 风格,所有库均返回大写时一致): - 只在单一数据库环境下运行的内部工具脚本(明确标注数据库类型) - MyBatis `@Select` 注解查询且只需判断是否为空(不关心 key 名称) 其他所有情况一律使用 DTO。