ai-engineering-init 1.16.2 → 1.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/code-patterns/SKILL.md +0 -119
- package/.claude/skills/codex-code-review/SKILL.md +0 -39
- package/.claude/skills/fix-bug/SKILL.md +57 -0
- package/.claude/skills/leniu-code-patterns/SKILL.md +2 -179
- package/.claude/skills/leniu-crud-development/SKILL.md +7 -26
- package/.claude/skills/leniu-java-mybatis/SKILL.md +16 -25
- package/.claude/skills/leniu-report-scenario/references/amount-handling.md +448 -0
- package/.claude/skills/leniu-report-scenario/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-scenario/references/customization-table-fields.md +93 -0
- package/.claude/skills/leniu-report-scenario/references/export.md +553 -0
- package/.claude/skills/leniu-report-scenario/references/mealtime.md +197 -0
- package/.claude/skills/leniu-report-scenario/references/query-param.md +274 -0
- package/.claude/skills/leniu-report-scenario/references/standard-customization.md +112 -0
- package/.claude/skills/leniu-report-scenario/references/standard-table-fields.md +113 -0
- package/.claude/skills/leniu-report-scenario/references/total-line.md +179 -0
- package/.claude/skills/loki-log-query/SKILL.md +30 -33
- package/.claude/skills/mysql-debug/SKILL.md +17 -35
- package/.codex/skills/code-patterns/SKILL.md +0 -119
- package/.codex/skills/fix-bug/SKILL.md +57 -0
- package/.codex/skills/leniu-code-patterns/SKILL.md +2 -179
- package/.codex/skills/leniu-crud-development/SKILL.md +7 -26
- package/.codex/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +16 -25
- package/.codex/skills/leniu-report-scenario/references/amount-handling.md +448 -0
- package/.codex/skills/leniu-report-scenario/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-scenario/references/customization-table-fields.md +93 -0
- package/.codex/skills/leniu-report-scenario/references/export.md +553 -0
- package/.codex/skills/leniu-report-scenario/references/mealtime.md +197 -0
- package/.codex/skills/leniu-report-scenario/references/query-param.md +274 -0
- package/.codex/skills/leniu-report-scenario/references/standard-customization.md +112 -0
- package/.codex/skills/leniu-report-scenario/references/standard-table-fields.md +113 -0
- package/.codex/skills/leniu-report-scenario/references/total-line.md +179 -0
- package/.codex/skills/loki-log-query/SKILL.md +55 -25
- package/.codex/skills/mysql-debug/SKILL.md +12 -6
- package/.cursor/skills/code-patterns/SKILL.md +0 -119
- package/.cursor/skills/fix-bug/SKILL.md +57 -0
- package/.cursor/skills/leniu-code-patterns/SKILL.md +2 -179
- package/.cursor/skills/leniu-crud-development/SKILL.md +7 -26
- package/.cursor/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +16 -25
- package/.cursor/skills/leniu-report-scenario/references/amount-handling.md +448 -0
- package/.cursor/skills/leniu-report-scenario/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-scenario/references/customization-table-fields.md +93 -0
- package/.cursor/skills/leniu-report-scenario/references/export.md +553 -0
- package/.cursor/skills/leniu-report-scenario/references/mealtime.md +197 -0
- package/.cursor/skills/leniu-report-scenario/references/query-param.md +274 -0
- package/.cursor/skills/leniu-report-scenario/references/standard-customization.md +112 -0
- package/.cursor/skills/leniu-report-scenario/references/standard-table-fields.md +113 -0
- package/.cursor/skills/leniu-report-scenario/references/total-line.md +179 -0
- package/.cursor/skills/loki-log-query/SKILL.md +30 -33
- package/.cursor/skills/mysql-debug/SKILL.md +20 -36
- package/CLAUDE.md +34 -0
- package/package.json +1 -1
- package/src/skills/fix-bug/SKILL.md +57 -0
|
@@ -101,120 +101,6 @@ feat(order): 新增订单导出功能
|
|
|
101
101
|
Closes #123
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
## 数据类型规范
|
|
105
|
-
|
|
106
|
-
### 布尔语义字段必须使用 Boolean
|
|
107
|
-
|
|
108
|
-
```java
|
|
109
|
-
// ❌ 错误
|
|
110
|
-
private Integer ifNarrow;
|
|
111
|
-
private Integer isEnabled;
|
|
112
|
-
|
|
113
|
-
// ✅ 正确
|
|
114
|
-
private Boolean narrow; // getter 自动生成 isNarrow()
|
|
115
|
-
private Boolean enabled; // getter 自动生成 isEnabled()
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**规则**:
|
|
119
|
-
- 语义为"是/否"的字段,类型必须为 `Boolean`
|
|
120
|
-
- 字段名不加 `if`/`is`/`has` 前缀(JavaBean 规范中 `Boolean` 的 getter 自动生成 `isXxx()`)
|
|
121
|
-
- 数据库字段使用 `TINYINT(1)` 或 `BIT(1)`
|
|
122
|
-
|
|
123
|
-
### 枚举字段必须提供明确约束
|
|
124
|
-
|
|
125
|
-
```java
|
|
126
|
-
// ❌ 错误:调用方无法知道合法值
|
|
127
|
-
@ApiModelProperty(value = "操作类型")
|
|
128
|
-
private Integer tradeType;
|
|
129
|
-
|
|
130
|
-
// ✅ 方案一:VO/DTO 层直接用枚举(推荐)
|
|
131
|
-
@ApiModelProperty(value = "操作类型")
|
|
132
|
-
private AccTradeTypeEnum tradeType;
|
|
133
|
-
|
|
134
|
-
// ✅ 方案二:保留 Integer 但标注合法值
|
|
135
|
-
@ApiModelProperty(value = "操作类型:1-充值 2-消费 3-退款", allowableValues = "1,2,3")
|
|
136
|
-
private Integer tradeType;
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 金额字段禁止使用浮点类型
|
|
140
|
-
|
|
141
|
-
```java
|
|
142
|
-
// ❌ 错误
|
|
143
|
-
private Double amount;
|
|
144
|
-
private Float price;
|
|
145
|
-
|
|
146
|
-
// ✅ 正确:Entity/Service 层用 Long(分),VO 层展示用 BigDecimal(元)
|
|
147
|
-
private Long amountFen;
|
|
148
|
-
private BigDecimal amountYuan;
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### 原始类型 vs 包装类型
|
|
152
|
-
|
|
153
|
-
| 场景 | 用原始类型 | 用包装类型 |
|
|
154
|
-
|------|----------|----------|
|
|
155
|
-
| Entity / VO / DTO 字段 | — | ✅ 统一用包装类型 |
|
|
156
|
-
| 方法参数(不允许 null) | ✅ `int count` | — |
|
|
157
|
-
| 方法参数(允许 null) | — | ✅ `Integer count` |
|
|
158
|
-
| 局部变量 | ✅ `int i = 0` | — |
|
|
159
|
-
|
|
160
|
-
## Optional 使用规范
|
|
161
|
-
|
|
162
|
-
```java
|
|
163
|
-
// ❌ 错误:of() 不接受 null,value 为 null 直接 NPE
|
|
164
|
-
Optional.of(value).orElse(defaultValue);
|
|
165
|
-
|
|
166
|
-
// ✅ 正确:ofNullable() 安全处理 null
|
|
167
|
-
Optional.ofNullable(value).orElse(defaultValue);
|
|
168
|
-
|
|
169
|
-
// ❌ 禁止:Optional 作为方法参数或类字段
|
|
170
|
-
public void process(Optional<String> name) { ... }
|
|
171
|
-
private Optional<String> name;
|
|
172
|
-
|
|
173
|
-
// ✅ 允许:Optional 作为方法返回值、链式处理
|
|
174
|
-
public Optional<Entity> findById(Long id) { ... }
|
|
175
|
-
Optional.ofNullable(entity).map(Entity::getConfig).orElse(DEFAULT_VALUE);
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## @Transactional 规范
|
|
179
|
-
|
|
180
|
-
```java
|
|
181
|
-
// ❌ 错误:默认只回滚 RuntimeException
|
|
182
|
-
@Transactional
|
|
183
|
-
public void createOrder() { ... }
|
|
184
|
-
|
|
185
|
-
// ✅ 正确:显式指定回滚异常
|
|
186
|
-
@Transactional(rollbackFor = Exception.class)
|
|
187
|
-
public void createOrder() { ... }
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
- 所有 `@Transactional` 必须显式写 `rollbackFor = Exception.class`
|
|
191
|
-
- 只读查询不加 `@Transactional`(或用 `readOnly = true`)
|
|
192
|
-
- 事务方法不要 try-catch 吞掉异常,否则事务不回滚
|
|
193
|
-
|
|
194
|
-
## TODO 管理规范
|
|
195
|
-
|
|
196
|
-
```java
|
|
197
|
-
// ❌ 错误:无负责人、无日期、无跟踪
|
|
198
|
-
// TODO 修改一下
|
|
199
|
-
|
|
200
|
-
// ✅ 正确:完整 TODO 格式
|
|
201
|
-
// TODO(@陈沈杰, 2026-03-20, #TASK-1234): 移动端 AppId 赋值逻辑待产品确认
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
- 每个 TODO 必须有对应的任务号
|
|
205
|
-
- 超过 2 个迭代未处理的 TODO 必须清理
|
|
206
|
-
- 不用的代码直接删除,不要注释保留
|
|
207
|
-
|
|
208
|
-
## 代码格式化规范
|
|
209
|
-
|
|
210
|
-
| 项目 | 规范 |
|
|
211
|
-
|------|------|
|
|
212
|
-
| 缩进 | 4 个空格(不用 Tab) |
|
|
213
|
-
| 行宽 | 120 字符 |
|
|
214
|
-
| 大括号 | K&R 风格(同行开始) |
|
|
215
|
-
| 空行 | 方法间 1 个空行,逻辑块间 1 个空行 |
|
|
216
|
-
| import | 分组排序:java → jakarta → org → net → com,组间空行 |
|
|
217
|
-
|
|
218
104
|
## 代码示例
|
|
219
105
|
|
|
220
106
|
### 统一响应格式
|
|
@@ -275,8 +161,3 @@ public enum OrderStatusEnum {
|
|
|
275
161
|
| Git 提交信息写"fix bug" | 写清楚修了什么:`fix(order): 修复金额计算精度丢失` |
|
|
276
162
|
| Boolean 变量名:`flag` | 有意义的名字:`isActive`, `hasPermission` |
|
|
277
163
|
| 缩写命名:`usr`, `mgr` | 完整命名:`user`, `manager` |
|
|
278
|
-
| `Optional.of(可能null值)` | `Optional.ofNullable(value)` |
|
|
279
|
-
| `@Transactional` 无 rollbackFor | `@Transactional(rollbackFor = Exception.class)` |
|
|
280
|
-
| TODO 无负责人和日期 | `// TODO(@负责人, 日期, #任务号): 描述` |
|
|
281
|
-
| 布尔字段用 `Integer` | 用 `Boolean` 类型 |
|
|
282
|
-
| 枚举字段无合法值说明 | `@ApiModelProperty` 标注合法值或直接用枚举类型 |
|
|
@@ -88,45 +88,6 @@ Grep pattern: "@Transactional\((?!.*rollbackFor)" path: [命中文件]
|
|
|
88
88
|
```
|
|
89
89
|
- ❌ `@Transactional` → ✅ `@Transactional(rollbackFor = Exception.class)`
|
|
90
90
|
|
|
91
|
-
#### 🔴 A7b. selectOne 无唯一保障
|
|
92
|
-
```bash
|
|
93
|
-
Grep pattern: "selectOne(" path: [目标目录] glob: "*.java"
|
|
94
|
-
# 命中行检查:是否有 LIMIT 1 或注释说明唯一索引
|
|
95
|
-
```
|
|
96
|
-
- ❌ 无 LIMIT 且无唯一索引 → ✅ 加 `.last("LIMIT 1")` 或确保有唯一索引
|
|
97
|
-
|
|
98
|
-
#### 🟡 A7c. selectCount 用于存在性判断
|
|
99
|
-
```bash
|
|
100
|
-
Grep pattern: "selectCount" path: [目标目录] glob: "*.java"
|
|
101
|
-
# 检查命中行是否用于 > 0 判断(存在性),而非真正计数
|
|
102
|
-
```
|
|
103
|
-
- ❌ `selectCount(w) > 0` → ✅ `mapper.exists(w)` 或 `selectList(w.last("LIMIT 1"))`
|
|
104
|
-
|
|
105
|
-
#### 🔴 A7d. Redis KEYS 命令
|
|
106
|
-
```bash
|
|
107
|
-
Grep pattern: "keysByPattern|\.keys\(" path: [目标目录] glob: "*.java"
|
|
108
|
-
```
|
|
109
|
-
- ❌ `redisTemplate.keys()` / `keysByPattern()` → ✅ 使用 Redisson `deleteByPattern()`(内部 SCAN + UNLINK)
|
|
110
|
-
|
|
111
|
-
#### 🟡 A7e. Optional.of 误用
|
|
112
|
-
```bash
|
|
113
|
-
Grep pattern: "Optional\.of\(" path: [目标目录] glob: "*.java"
|
|
114
|
-
# 排除 ofNullable 的命中
|
|
115
|
-
```
|
|
116
|
-
- ❌ `Optional.of(可能为null)` → ✅ `Optional.ofNullable(value)`
|
|
117
|
-
|
|
118
|
-
#### 🟡 A7f. 布尔字段类型错误
|
|
119
|
-
```bash
|
|
120
|
-
Grep pattern: "private Integer (if|is|has)" path: [目标目录] glob: "*.java"
|
|
121
|
-
```
|
|
122
|
-
- ❌ `private Integer isEnabled` → ✅ `private Boolean enabled`
|
|
123
|
-
|
|
124
|
-
#### 🟡 A7g. Wrapper 嵌套过深
|
|
125
|
-
```bash
|
|
126
|
-
# 人工审查:Read 文件时检查 Wrapper 嵌套是否超过 2 层
|
|
127
|
-
```
|
|
128
|
-
- ❌ Wrapper 嵌套 >2 层 → ✅ 迁移到 XML 原生 SQL
|
|
129
|
-
|
|
130
91
|
#### 🟡 A8. 请求体封装
|
|
131
92
|
```bash
|
|
132
93
|
Grep pattern: "@RequestBody [^L]" path: [目标目录] glob: "*Controller.java"
|
|
@@ -197,6 +197,62 @@ Agent(mysql-runner, "根据日志发现涉及 order_info 表,查询 id=xxx 的
|
|
|
197
197
|
| Bug 描述 + DB 信息 | bug-analyzer + mysql-runner | — |
|
|
198
198
|
| Bug 描述 + traceId + DB 信息 | 全部 | 日志中有新表/ID → mysql-runner 追加查询 |
|
|
199
199
|
|
|
200
|
+
## SQL 报表 Bug 专项流程
|
|
201
|
+
|
|
202
|
+
当 Bug 涉及 **SQL 查询逻辑错误**(SUM/GROUP BY/CASE WHEN/金额计算等),启用以下验证流程:
|
|
203
|
+
|
|
204
|
+
### 修复前:建立基线
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
1. 读取当前 Mapper XML,理解现有 SQL 逻辑
|
|
208
|
+
2. 如果有已修复的参考查询(如同模块的明细查询),先读取其 SQL 逻辑作为正确参考
|
|
209
|
+
3. 查库获取当前错误结果作为"修复前基线"(可选,用户提供 DB 时执行)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 修复后:交叉验证(必须执行)
|
|
213
|
+
|
|
214
|
+
**两步验证法**(以本项目销售汇总修复为典型案例):
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
步骤 1:直接执行修改后的 SQL,验证基本逻辑
|
|
218
|
+
- 检查各记录类型是否正确处理(称重/按份、正向/逆向、入库/出库)
|
|
219
|
+
- 检查特殊值:NULL 处理、除零保护、边界条件
|
|
220
|
+
|
|
221
|
+
步骤 2:交叉对比验证
|
|
222
|
+
- 方法 A:用已修复的明细查询做 SUM 汇总
|
|
223
|
+
- 方法 B:执行修改后的汇总查询
|
|
224
|
+
- 对比 A vs B:总计必须完全一致
|
|
225
|
+
- 按维度对比(按商品/按门店等):逐行 MATCH/DIFF
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 验证 SQL 模板
|
|
229
|
+
|
|
230
|
+
```sql
|
|
231
|
+
-- 交叉验证:明细汇总 vs 汇总查询
|
|
232
|
+
SELECT '明细汇总' as source, SUM(saleNum), SUM(cost), ...
|
|
233
|
+
FROM (/* 明细查询逐行 */) detail
|
|
234
|
+
UNION ALL
|
|
235
|
+
SELECT '汇总查询' as source, ...
|
|
236
|
+
FROM /* 汇总查询 */;
|
|
237
|
+
|
|
238
|
+
-- 按维度逐行对比
|
|
239
|
+
SELECT COALESCE(d.name, s.name) as name,
|
|
240
|
+
d.detail_value, s.summary_value,
|
|
241
|
+
CASE WHEN d.detail_value = s.summary_value THEN 'MATCH' ELSE 'DIFF' END as result
|
|
242
|
+
FROM (/* 明细按维度 GROUP BY */) d
|
|
243
|
+
LEFT JOIN (/* 汇总按维度 GROUP BY */) s ON d.id = s.id;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### SQL 修改注意事项
|
|
247
|
+
|
|
248
|
+
| 规则 | 说明 |
|
|
249
|
+
|------|------|
|
|
250
|
+
| **保留 WHERE 条件** | 新增/修改查询必须包含与现有查询相同的过滤条件 |
|
|
251
|
+
| **GROUP BY 优先** | 优先添加字段到 GROUP BY,不用 ANY_VALUE() |
|
|
252
|
+
| **JOIN 一致性** | 主查询和合计查询的 JOIN 必须一致(如 drp_unit) |
|
|
253
|
+
| **IFNULL 保护** | 除法运算中的分母必须用 IFNULL 防止除零 |
|
|
254
|
+
| **业务类型分支** | CASE WHEN 必须覆盖所有业务类型(称重/按份、入库/出库等) |
|
|
255
|
+
|
|
200
256
|
## 提交规则
|
|
201
257
|
|
|
202
258
|
修复完成后,**必须调用 `Skill(git-workflow)` 再执行 git 操作**。
|
|
@@ -210,3 +266,4 @@ Agent(mysql-runner, "根据日志发现涉及 order_info 表,查询 id=xxx 的
|
|
|
210
266
|
- 简单 Bug 不要过度编排,直接修就行(但仍需先报告再修复)
|
|
211
267
|
- 如果用户没提供 DB/Loki 信息但 Bug 涉及数据问题,主动询问
|
|
212
268
|
- 与 `bug-detective` 技能的区别:`bug-detective` 是排查指南,`fix-bug` 是全流程编排(包含排查+修复+提交)
|
|
269
|
+
- SQL 报表 Bug 修复后**必须执行交叉验证**,不能只看"能跑"就提交
|
|
@@ -394,187 +394,10 @@ public class OrderService {
|
|
|
394
394
|
}
|
|
395
395
|
```
|
|
396
396
|
|
|
397
|
-
## 数据类型规范
|
|
398
|
-
|
|
399
|
-
### 布尔字段命名
|
|
400
|
-
|
|
401
|
-
```java
|
|
402
|
-
// ❌ 错误:前缀冗余、类型错误
|
|
403
|
-
private Integer ifNarrow;
|
|
404
|
-
private Integer isEnabled;
|
|
405
|
-
|
|
406
|
-
// ✅ 正确:Boolean 类型,无前缀
|
|
407
|
-
private Boolean narrow; // getter → isNarrow()
|
|
408
|
-
private Boolean enabled; // getter → isEnabled()
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 枚举字段标注
|
|
412
|
-
|
|
413
|
-
```java
|
|
414
|
-
// ❌ 错误:只靠注释 @see
|
|
415
|
-
/** @see AccTradeTypeEnum */
|
|
416
|
-
private Integer tradeType;
|
|
417
|
-
|
|
418
|
-
// ✅ 方案一:VO/DTO 用枚举类型(配合 @JsonValue)
|
|
419
|
-
private AccTradeTypeEnum tradeType;
|
|
420
|
-
|
|
421
|
-
// ✅ 方案二:@ApiModelProperty 标注合法值
|
|
422
|
-
@ApiModelProperty(value = "操作类型:1-充值 2-消费 3-退款", allowableValues = "1,2,3")
|
|
423
|
-
private Integer tradeType;
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
## MyBatis-Plus 安全规范
|
|
427
|
-
|
|
428
|
-
### selectOne 必须有唯一保障
|
|
429
|
-
|
|
430
|
-
```java
|
|
431
|
-
// ❌ 危险:多条记录时抛 TooManyResultsException
|
|
432
|
-
Entity entity = mapper.selectOne(wrapper);
|
|
433
|
-
|
|
434
|
-
// ✅ 方案一:LIMIT 1
|
|
435
|
-
Entity entity = mapper.selectOne(wrapper.last("LIMIT 1"));
|
|
436
|
-
|
|
437
|
-
// ✅ 方案二:selectList 取第一条
|
|
438
|
-
List<Entity> list = mapper.selectList(wrapper);
|
|
439
|
-
Entity entity = CollUtil.isNotEmpty(list) ? list.get(0) : null;
|
|
440
|
-
|
|
441
|
-
// ✅ 方案三:确保有唯一索引(注释说明)
|
|
442
|
-
// 唯一索引:UNIQUE KEY (order_no, del_flag)
|
|
443
|
-
Entity entity = mapper.selectOne(wrapper);
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
### 存在性判断用 EXISTS,禁用 selectCount
|
|
447
|
-
|
|
448
|
-
```java
|
|
449
|
-
// ❌ 低效:100万行表 ~200ms
|
|
450
|
-
Long count = mapper.selectCount(wrapper);
|
|
451
|
-
if (count > 0) { ... }
|
|
452
|
-
|
|
453
|
-
// ✅ 高效:~2ms(MyBatis-Plus 3.5.4+)
|
|
454
|
-
boolean exists = mapper.exists(wrapper);
|
|
455
|
-
|
|
456
|
-
// ✅ 或 selectList + LIMIT 1
|
|
457
|
-
boolean exists = CollUtil.isNotEmpty(mapper.selectList(wrapper.last("LIMIT 1")));
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### Wrapper 嵌套不超过 2 层
|
|
461
|
-
|
|
462
|
-
```java
|
|
463
|
-
// ❌ 过于复杂的 Wrapper
|
|
464
|
-
wrapper.and(w -> w.eq(A::getType, type)
|
|
465
|
-
.or(q -> q.eq(A::getType, 0))
|
|
466
|
-
.or(!PersonTypeEnum.LABOUR.getKey().equals(type),
|
|
467
|
-
q -> q.eq(A::getType, PSN_TYPE_SHARED)));
|
|
468
|
-
|
|
469
|
-
// ✅ 复杂查询写到 XML 中
|
|
470
|
-
List<Entity> list = mapper.selectByTypeCondition(type, isLabour);
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### 禁止 SELECT *
|
|
474
|
-
|
|
475
|
-
```xml
|
|
476
|
-
<!-- ❌ 禁止 -->
|
|
477
|
-
<select id="selectAll">SELECT * FROM t_order WHERE del_flag = 2</select>
|
|
478
|
-
|
|
479
|
-
<!-- ✅ 明确列出字段 -->
|
|
480
|
-
<select id="selectAll">SELECT id, order_no, amount, status, crtime FROM t_order WHERE del_flag = 2</select>
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
## Redis 使用规范
|
|
484
|
-
|
|
485
|
-
### 禁止 KEYS 命令
|
|
486
|
-
|
|
487
|
-
```java
|
|
488
|
-
// ❌ 严禁:KEYS 阻塞 Redis
|
|
489
|
-
Set<Object> keys = keysByPattern(pattern);
|
|
490
|
-
Set<Object> keys = redisTemplate.keys(pattern);
|
|
491
|
-
|
|
492
|
-
// ✅ 使用 Redisson deleteByPattern(内部 SCAN + UNLINK)
|
|
493
|
-
RedissonClient redisson = SpringUtil.getBean(RedissonClient.class);
|
|
494
|
-
redisson.getKeys().deleteByPattern(keyPattern);
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
## Optional 使用规范
|
|
498
|
-
|
|
499
|
-
```java
|
|
500
|
-
// ❌ 错误:of() 不接受 null
|
|
501
|
-
Optional.of(value).orElse(defaultValue);
|
|
502
|
-
|
|
503
|
-
// ✅ 正确:ofNullable()
|
|
504
|
-
Optional.ofNullable(value).orElse(defaultValue);
|
|
505
|
-
|
|
506
|
-
// ✅ 链式安全转换
|
|
507
|
-
Optional.ofNullable(model.getReserveRate())
|
|
508
|
-
.map(BigDecimal::new)
|
|
509
|
-
.orElse(BigDecimal.ZERO);
|
|
510
|
-
|
|
511
|
-
// ❌ 禁止作为方法参数或类字段
|
|
512
|
-
// ✅ 允许作为方法返回值
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
## @Transactional 规范
|
|
516
|
-
|
|
517
|
-
```java
|
|
518
|
-
// ❌ 默认只回滚 RuntimeException
|
|
519
|
-
@Transactional
|
|
520
|
-
public void createOrder() { ... }
|
|
521
|
-
|
|
522
|
-
// ✅ 显式指定 rollbackFor
|
|
523
|
-
@Transactional(rollbackFor = Exception.class)
|
|
524
|
-
public void createOrder() { ... }
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
- 事务方法不要 try-catch 吞掉异常
|
|
528
|
-
- 只读查询不加 `@Transactional`
|
|
529
|
-
|
|
530
|
-
## 业务逻辑分层规范
|
|
531
|
-
|
|
532
|
-
```java
|
|
533
|
-
// ❌ 错误:业务判断混在数据操作中
|
|
534
|
-
public void processOrder(Long orderId) {
|
|
535
|
-
OrderInfo order = orderMapper.selectById(orderId);
|
|
536
|
-
if (order.getStatus() == 1 && order.getPayTime() != null
|
|
537
|
-
&& ChronoUnit.HOURS.between(order.getPayTime(), LocalDateTime.now()) < 24) {
|
|
538
|
-
order.setStatus(2);
|
|
539
|
-
orderMapper.updateById(order);
|
|
540
|
-
accWalletService.deduct(order.getCustId(), order.getAmount());
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// ✅ 正确:分层清晰
|
|
545
|
-
public void processOrder(Long orderId) {
|
|
546
|
-
OrderInfo order = orderMapper.selectById(orderId);
|
|
547
|
-
if (ObjectUtil.isNull(order)) {
|
|
548
|
-
throw new LeException(I18n.getMessage("order_not_found"));
|
|
549
|
-
}
|
|
550
|
-
checkCanProcess(order); // 业务校验(独立方法)
|
|
551
|
-
order.markAsProcessed(); // 状态变更(Entity 方法封装)
|
|
552
|
-
orderMapper.updateById(order);
|
|
553
|
-
afterOrderProcessed(order); // 后续动作(独立方法)
|
|
554
|
-
}
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
| 层 | 职责 | 不应做的 |
|
|
558
|
-
|----|------|---------|
|
|
559
|
-
| Controller | 参数接收、格式转换 | 不含业务判断 |
|
|
560
|
-
| Business | 业务编排、跨 Service 协调 | 不直接操作 Mapper |
|
|
561
|
-
| Service | 单表 CRUD、单表事务 | 不含跨表业务逻辑 |
|
|
562
|
-
| Mapper | SQL 映射 | 不含业务逻辑 |
|
|
563
|
-
|
|
564
|
-
## TODO 管理规范
|
|
565
|
-
|
|
566
|
-
```java
|
|
567
|
-
// ❌ 错误
|
|
568
|
-
// TODO 修改一下
|
|
569
|
-
|
|
570
|
-
// ✅ 正确
|
|
571
|
-
// TODO(@陈沈杰, 2026-03-20, #TASK-1234): 移动端 AppId 赋值逻辑待产品确认
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
- 不用的代码直接删除,不要注释保留
|
|
575
|
-
|
|
576
397
|
## 通用代码规范
|
|
577
398
|
|
|
399
|
+
无论使用哪种项目架构,以下规范都是通用的:
|
|
400
|
+
|
|
578
401
|
1. **禁止使用 `SELECT *`**:明确指定字段
|
|
579
402
|
2. **使用参数化查询**:`#{}` 而非 `${}`
|
|
580
403
|
3. **异常必须处理**:不能吞掉异常
|
|
@@ -39,7 +39,6 @@ description: |
|
|
|
39
39
|
| 架构 | Controller -> Business -> Service -> Mapper(四层) |
|
|
40
40
|
| 无 DAO 层 | Service 直接注入 Mapper |
|
|
41
41
|
| 对象转换 | `BeanUtil.copyProperties()` (Hutool) |
|
|
42
|
-
| Service 模式 | **两种并存**:简单 CRUD 继承 `ServiceImpl`;业务聚合直接 `@Service` |
|
|
43
42
|
| Entity 基类 | 无基类,自定义审计字段 |
|
|
44
43
|
| 请求封装 | `LeRequest<T>` |
|
|
45
44
|
| 响应封装 | `Page<T>` / `LeResponse<T>` / `void` |
|
|
@@ -111,38 +110,19 @@ private LocalDateTime uptime;
|
|
|
111
110
|
private Integer delFlag; // 1=删除, 2=正常
|
|
112
111
|
```
|
|
113
112
|
|
|
114
|
-
### Service
|
|
113
|
+
### Service 注入模式
|
|
115
114
|
|
|
116
|
-
项目中存在两种 Service 模式,根据业务复杂度选择:
|
|
117
|
-
|
|
118
|
-
**模式 A:简单 CRUD Service**(适用于单表操作,利用 MyBatis-Plus 内置方法)
|
|
119
|
-
```java
|
|
120
|
-
// 接口
|
|
121
|
-
public interface XxxService extends IService<XxxEntity> { }
|
|
122
|
-
|
|
123
|
-
// 实现
|
|
124
|
-
@Slf4j
|
|
125
|
-
@Service
|
|
126
|
-
public class XxxServiceImpl extends ServiceImpl<XxxMapper, XxxEntity> implements XxxService {
|
|
127
|
-
// 继承 ServiceImpl 获得 save/updateById/removeById/page 等内置方法
|
|
128
|
-
// 通过 this.baseMapper 访问 Mapper(父类提供)
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
**模式 B:业务聚合 Service**(适用于跨表操作、复杂业务编排)
|
|
133
115
|
```java
|
|
134
116
|
@Slf4j
|
|
135
117
|
@Service
|
|
136
|
-
public class XxxService {
|
|
137
|
-
@
|
|
118
|
+
public class XxxServiceImpl implements XxxService {
|
|
119
|
+
@Resource
|
|
138
120
|
private XxxMapper xxxMapper; // 直接注入 Mapper,无 DAO 层
|
|
139
|
-
|
|
140
|
-
|
|
121
|
+
|
|
122
|
+
// 不继承 ServiceImpl,只实现接口
|
|
141
123
|
}
|
|
142
124
|
```
|
|
143
125
|
|
|
144
|
-
**选择建议**:新建简单单表 CRUD 用模式 A;涉及多表联查、报表、业务编排用模式 B。
|
|
145
|
-
|
|
146
126
|
### Controller 请求封装
|
|
147
127
|
|
|
148
128
|
```java
|
|
@@ -386,6 +366,7 @@ private String createBy; // -> crby
|
|
|
386
366
|
entity.setDelFlag(0); // -> setDelFlag(2) 表示正常
|
|
387
367
|
throw new ServiceException("..."); // -> throw new LeException("...")
|
|
388
368
|
MapstructUtils.convert(src, Dst.class); // -> BeanUtil.copyProperties(src, Dst.class)
|
|
369
|
+
extends ServiceImpl<XxxMapper, Xxx> // -> implements XxxService(不继承)
|
|
389
370
|
@Resource private XxxDao xxxDao; // -> @Resource private XxxMapper xxxMapper
|
|
390
371
|
// XML 放 resources/mapper/ // -> 与 Java 同目录
|
|
391
372
|
return null; // -> return Collections.emptyList()
|
|
@@ -396,7 +377,7 @@ return null; // -> return Collections.emptyList()
|
|
|
396
377
|
## 生成前检查清单
|
|
397
378
|
|
|
398
379
|
- [ ] 包名 `net.xnzn.core.*`
|
|
399
|
-
- [ ] Service
|
|
380
|
+
- [ ] Service 只实现接口,不继承基类
|
|
400
381
|
- [ ] Service 直接注入 Mapper(无 DAO)
|
|
401
382
|
- [ ] 审计字段 crby/crtime/upby/uptime
|
|
402
383
|
- [ ] delFlag: 1=删除, 2=正常
|
|
@@ -22,7 +22,7 @@ description: |
|
|
|
22
22
|
| XML 位置 | **与 Mapper 接口同目录**(非 `resources/mapper/`) |
|
|
23
23
|
| 分页 | PageHelper → `PageMethod.startPage(PageDTO)` → `PageVO.of(list)` |
|
|
24
24
|
| 逻辑删除 | **1=删除,2=正常**(与 RuoYi 相反) |
|
|
25
|
-
| Service |
|
|
25
|
+
| Service | 无接口,直接 `@Service` 类,Mapper 字段名统一用 `baseMapper` |
|
|
26
26
|
| 循环依赖 | 跨模块依赖用 `@Autowired @Lazy` |
|
|
27
27
|
|
|
28
28
|
## Mapper 接口模板
|
|
@@ -147,36 +147,28 @@ List<XxxEntity> list = mapper.selectList(
|
|
|
147
147
|
|
|
148
148
|
## Service 注入规范
|
|
149
149
|
|
|
150
|
-
项目中 Service 有两种模式,Mapper 字段名也不统一——参考周围已有代码的风格即可。
|
|
151
|
-
|
|
152
|
-
**模式 A:继承 ServiceImpl(简单 CRUD)**
|
|
153
|
-
```java
|
|
154
|
-
@Service
|
|
155
|
-
public class XxxServiceImpl extends ServiceImpl<XxxMapper, XxxEntity> implements XxxService {
|
|
156
|
-
// 通过 this.baseMapper 访问 Mapper(父类自动注入)
|
|
157
|
-
public boolean exists(String macOrderId) {
|
|
158
|
-
return this.baseMapper.existsOne(
|
|
159
|
-
Wrappers.lambdaQuery(XxxEntity.class)
|
|
160
|
-
.eq(XxxEntity::getMacOrderId, macOrderId)
|
|
161
|
-
.eq(XxxEntity::getDelFlag, 2)
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
**模式 B:直接 @Service(业务聚合)**
|
|
168
150
|
```java
|
|
169
151
|
@Slf4j
|
|
170
152
|
@Service
|
|
153
|
+
@Validated
|
|
171
154
|
public class XxxService {
|
|
155
|
+
|
|
172
156
|
@Autowired
|
|
173
|
-
private XxxMapper
|
|
157
|
+
private XxxMapper baseMapper; // ✅ 统一命名 baseMapper
|
|
174
158
|
|
|
175
159
|
@Autowired @Lazy
|
|
176
|
-
private
|
|
160
|
+
private XxxDetailService xxxDetailService; // ✅ 跨模块用 @Lazy
|
|
177
161
|
|
|
178
162
|
public XxxEntity getOne(Long id) {
|
|
179
|
-
return
|
|
163
|
+
return baseMapper.selectById(id);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public boolean exists(String macOrderId) {
|
|
167
|
+
return baseMapper.existsOne(
|
|
168
|
+
Wrappers.lambdaQuery(XxxEntity.class)
|
|
169
|
+
.eq(XxxEntity::getMacOrderId, macOrderId)
|
|
170
|
+
.eq(XxxEntity::getDelFlag, 2)
|
|
171
|
+
);
|
|
180
172
|
}
|
|
181
173
|
}
|
|
182
174
|
```
|
|
@@ -258,9 +250,8 @@ wrapper.eq(XxxEntity::getDelFlag, 0);
|
|
|
258
250
|
// ❌ MapstructUtils(用 BeanUtil.copyProperties)
|
|
259
251
|
MapstructUtils.convert(source, Target.class);
|
|
260
252
|
|
|
261
|
-
// ❌
|
|
262
|
-
|
|
263
|
-
// 正确:简单单表 CRUD 可以继承 ServiceImpl,复杂业务直接 @Service
|
|
253
|
+
// ❌ Service 继承 IService / ServiceImpl
|
|
254
|
+
public interface IXxxService extends IService<XxxEntity> {}
|
|
264
255
|
```
|
|
265
256
|
|
|
266
257
|
## XML 文件位置
|