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.
Files changed (54) hide show
  1. package/.claude/skills/code-patterns/SKILL.md +0 -119
  2. package/.claude/skills/codex-code-review/SKILL.md +0 -39
  3. package/.claude/skills/fix-bug/SKILL.md +57 -0
  4. package/.claude/skills/leniu-code-patterns/SKILL.md +2 -179
  5. package/.claude/skills/leniu-crud-development/SKILL.md +7 -26
  6. package/.claude/skills/leniu-java-mybatis/SKILL.md +16 -25
  7. package/.claude/skills/leniu-report-scenario/references/amount-handling.md +448 -0
  8. package/.claude/skills/leniu-report-scenario/references/analysis-module.md +64 -0
  9. package/.claude/skills/leniu-report-scenario/references/customization-table-fields.md +93 -0
  10. package/.claude/skills/leniu-report-scenario/references/export.md +553 -0
  11. package/.claude/skills/leniu-report-scenario/references/mealtime.md +197 -0
  12. package/.claude/skills/leniu-report-scenario/references/query-param.md +274 -0
  13. package/.claude/skills/leniu-report-scenario/references/standard-customization.md +112 -0
  14. package/.claude/skills/leniu-report-scenario/references/standard-table-fields.md +113 -0
  15. package/.claude/skills/leniu-report-scenario/references/total-line.md +179 -0
  16. package/.claude/skills/loki-log-query/SKILL.md +30 -33
  17. package/.claude/skills/mysql-debug/SKILL.md +17 -35
  18. package/.codex/skills/code-patterns/SKILL.md +0 -119
  19. package/.codex/skills/fix-bug/SKILL.md +57 -0
  20. package/.codex/skills/leniu-code-patterns/SKILL.md +2 -179
  21. package/.codex/skills/leniu-crud-development/SKILL.md +7 -26
  22. package/.codex/skills/leniu-java-amount-handling/SKILL.md +461 -0
  23. package/.codex/skills/leniu-java-mybatis/SKILL.md +16 -25
  24. package/.codex/skills/leniu-report-scenario/references/amount-handling.md +448 -0
  25. package/.codex/skills/leniu-report-scenario/references/analysis-module.md +64 -0
  26. package/.codex/skills/leniu-report-scenario/references/customization-table-fields.md +93 -0
  27. package/.codex/skills/leniu-report-scenario/references/export.md +553 -0
  28. package/.codex/skills/leniu-report-scenario/references/mealtime.md +197 -0
  29. package/.codex/skills/leniu-report-scenario/references/query-param.md +274 -0
  30. package/.codex/skills/leniu-report-scenario/references/standard-customization.md +112 -0
  31. package/.codex/skills/leniu-report-scenario/references/standard-table-fields.md +113 -0
  32. package/.codex/skills/leniu-report-scenario/references/total-line.md +179 -0
  33. package/.codex/skills/loki-log-query/SKILL.md +55 -25
  34. package/.codex/skills/mysql-debug/SKILL.md +12 -6
  35. package/.cursor/skills/code-patterns/SKILL.md +0 -119
  36. package/.cursor/skills/fix-bug/SKILL.md +57 -0
  37. package/.cursor/skills/leniu-code-patterns/SKILL.md +2 -179
  38. package/.cursor/skills/leniu-crud-development/SKILL.md +7 -26
  39. package/.cursor/skills/leniu-java-amount-handling/SKILL.md +461 -0
  40. package/.cursor/skills/leniu-java-mybatis/SKILL.md +16 -25
  41. package/.cursor/skills/leniu-report-scenario/references/amount-handling.md +448 -0
  42. package/.cursor/skills/leniu-report-scenario/references/analysis-module.md +64 -0
  43. package/.cursor/skills/leniu-report-scenario/references/customization-table-fields.md +93 -0
  44. package/.cursor/skills/leniu-report-scenario/references/export.md +553 -0
  45. package/.cursor/skills/leniu-report-scenario/references/mealtime.md +197 -0
  46. package/.cursor/skills/leniu-report-scenario/references/query-param.md +274 -0
  47. package/.cursor/skills/leniu-report-scenario/references/standard-customization.md +112 -0
  48. package/.cursor/skills/leniu-report-scenario/references/standard-table-fields.md +113 -0
  49. package/.cursor/skills/leniu-report-scenario/references/total-line.md +179 -0
  50. package/.cursor/skills/loki-log-query/SKILL.md +30 -33
  51. package/.cursor/skills/mysql-debug/SKILL.md +20 -36
  52. package/CLAUDE.md +34 -0
  53. package/package.json +1 -1
  54. package/src/skills/fix-bug/SKILL.md +57 -0
@@ -0,0 +1,179 @@
1
+
2
+ # leniu-tengyun-core 合计行(Total Line)规范
3
+
4
+ ## 项目特征
5
+
6
+ | 特征 | 说明 |
7
+ |------|------|
8
+ | **包名前缀** | `net.xnzn.core.*` |
9
+ | **JDK 版本** | 21 |
10
+ | **双库切换** | 默认商户库,`Executors.doInSystem()` 切换系统库 |
11
+ | **分页工具** | `PageMethod.startPage()` |
12
+ | **结果封装** | `ReportBaseTotalVO<T>` |
13
+ | **分页封装** | `PageVO.of()` |
14
+
15
+ ## 核心原则
16
+
17
+ **合计行SQL只返回需要合计的数值字段**,不返回非数值字段(如日期、名称、ID等)。
18
+
19
+ ## Service层实现
20
+
21
+ ### 带合计行的分页查询
22
+
23
+ ```java
24
+ import net.xnzn.core.common.page.PageMethod;
25
+ import net.xnzn.core.common.page.PageVO;
26
+ import net.xnzn.core.common.vo.ReportBaseTotalVO;
27
+ import cn.hutool.core.collection.CollUtil;
28
+
29
+ // ⚠️ 系统默认在商户库执行,业务查询无需 Executors.readInSystem()
30
+ // Executors.readInSystem() 仅用于需要访问系统库的场景(如全局配置、商户管理)
31
+
32
+ public ReportBaseTotalVO<XxxVO> pageWithTotal(XxxPageParam param) {
33
+ ReportBaseTotalVO<XxxVO> result = new ReportBaseTotalVO<>();
34
+
35
+ // 1. 导出时不查询合计行(避免不必要的性能开销)
36
+ if (CollUtil.isEmpty(param.getExportCols())) {
37
+ XxxVO totalLine = mapper.getSummaryTotal(param);
38
+ result.setTotalLine(totalLine);
39
+ }
40
+
41
+ // 2. 开启分页
42
+ PageMethod.startPage(param.getPage());
43
+
44
+ // 3. 查询数据
45
+ List<XxxVO> list = mapper.getSummaryList(param);
46
+
47
+ // 4. 封装分页结果
48
+ result.setResultPage(PageVO.of(list));
49
+ return result;
50
+ }
51
+ ```
52
+
53
+ ### 单独的合计行查询方法
54
+
55
+ ```java
56
+ public XxxVO getSummaryTotal(XxxPageParam param) {
57
+ // 默认在商户库执行,无需 Executors 包装
58
+ return mapper.getSummaryTotal(param);
59
+ }
60
+ ```
61
+
62
+ ## Mapper XML规范
63
+
64
+ ### 错误示例:返回非数值字段
65
+
66
+ ```xml
67
+ <!-- ❌ 不要这样做 -->
68
+ <select id="getSummaryTotal" resultType="XxxVO">
69
+ SELECT
70
+ '合计' AS dateMonth, <!-- ❌ 不要返回字符串 -->
71
+ NULL AS schoolId, <!-- ❌ 不要返回ID -->
72
+ NULL AS schoolName, <!-- ❌ 不要返回名称 -->
73
+ SUM(staffCount) AS staffCount,
74
+ SUM(amount) AS amount
75
+ FROM xxx_table
76
+ </select>
77
+ ```
78
+
79
+ ### 正确示例:只返回数值字段
80
+
81
+ ```xml
82
+ <!-- ✅ 正确做法 -->
83
+ <select id="getSummaryTotal" resultType="XxxVO">
84
+ SELECT
85
+ SUM(staff_count) AS staffCount,
86
+ SUM(basic_salary) AS basicSalary,
87
+ SUM(overtime_salary) AS overtimeSalary,
88
+ SUM(personal_actual_amount) AS personalActualAmount,
89
+ CASE
90
+ WHEN SUM(staff_count) = 0 THEN 0
91
+ ELSE SUM(avg_salary) / COUNT(DISTINCT tenant_id)
92
+ END AS avgSalary
93
+ FROM xxx_table
94
+ <where>
95
+ del_flag = 2
96
+ <if test="startDate != null">
97
+ AND crtime >= #{startDate}
98
+ </if>
99
+ <if test="endDate != null">
100
+ AND crtime &lt;= #{endDate}
101
+ </if>
102
+ </where>
103
+ </select>
104
+ ```
105
+
106
+ ## 常见合计字段类型
107
+
108
+ | 字段类型 | 示例 | 合计方式 |
109
+ |---------|------|---------|
110
+ | 数量 | `staff_count`, `order_count` | `SUM()` |
111
+ | 金额 | `amount`, `salary` | `SUM()` |
112
+ | 百分比 | `discount_rate` | `AVG()` 或 `SUM() / COUNT()` |
113
+ | 计数 | `COUNT(DISTINCT id)` | 直接使用 |
114
+
115
+ ## 特殊处理:平均值
116
+
117
+ ```xml
118
+ <!-- 简单平均值 -->
119
+ SELECT AVG(amount) AS avgAmount
120
+
121
+ <!-- 加权平均值 -->
122
+ SELECT
123
+ CASE
124
+ WHEN SUM(count) = 0 THEN 0
125
+ ELSE SUM(amount * count) / SUM(count)
126
+ END AS weightedAvgAmount
127
+
128
+ <!-- 按维度平均 -->
129
+ SELECT
130
+ CASE
131
+ WHEN COUNT(DISTINCT tenant_id) = 0 THEN 0
132
+ ELSE SUM(total_amount) / COUNT(DISTINCT tenant_id)
133
+ END AS avgByTenant
134
+ ```
135
+
136
+ ## Controller层实现
137
+
138
+ ```java
139
+ @PostMapping("/page")
140
+ @ApiOperation("分页查询(带合计)")
141
+ @RequiresAuthentication
142
+ public ReportBaseTotalVO<XxxVO> page(@RequestBody LeRequest<XxxParam> request) {
143
+ XxxParam param = request.getContent();
144
+ return xxxService.pageWithTotal(param);
145
+ }
146
+
147
+ @GetMapping("/total")
148
+ @ApiOperation("单独查询合计行")
149
+ @RequiresAuthentication
150
+ public XxxVO getTotal(XxxParam param) {
151
+ return xxxService.getSummaryTotal(param);
152
+ }
153
+ ```
154
+
155
+ ## 导出时合计行处理
156
+
157
+ ```java
158
+ public void exportExcel(XxxParam param, HttpServletResponse response) {
159
+ // 导出时需要查询合计行
160
+ XxxVO totalLine = mapper.getSummaryTotal(param);
161
+
162
+ // 分页查询所有数据(不分页)
163
+ param.getPage().setSize(Integer.MAX_VALUE);
164
+ List<XxxVO> list = mapper.getSummaryList(param);
165
+
166
+ // 将合计行添加到列表末尾
167
+ list.add(totalLine);
168
+
169
+ // 导出
170
+ exportService.export(list, response);
171
+ }
172
+ ```
173
+
174
+ ## 注意事项
175
+
176
+ - 合计SQL只返回数值字段,不返回字符串、ID、名称等
177
+ - 业务查询默认在商户库执行,无需 `Executors` 包装;仅访问系统库数据时才使用 `Executors.doInSystem()`
178
+ - 导出时通过 `exportCols` 判断是否需要合计行
179
+ - 金额字段类型与 Entity 保持一致:订单模块用 `BigDecimal`,钱包模块用 `Long`(详见 leniu-java-amount-handling)
@@ -30,10 +30,10 @@ description: |
30
30
  ```
31
31
  1. 本地项目:.claude/loki-config.json(新格式)
32
32
  2. 本地项目:.claude/skills/loki-log-query/environments.json(旧格式,兼容)
33
- 3. 全局配置:~/.claude/loki-config.json
33
+ 3. 全局配置:~/.claude/loki-config.json(或 ~/.cursor/loki-config.json)
34
34
  ```
35
35
 
36
- 本地配置优先。全局配置推荐用 `npx ai-engineering-init config --type loki --scope global` 创建。
36
+ 本地优先。全局配置用 `npx ai-engineering-init config --type loki --scope global` 创建。
37
37
 
38
38
  ### 配置结构(支持 range 范围匹配)
39
39
 
@@ -47,32 +47,32 @@ description: |
47
47
  "token": "glsa_xxx",
48
48
  "aliases": ["mdev", "dev"],
49
49
  "range": "dev1~15",
50
- "projects": ["dev01","dev02","dev03","dev04","dev05","dev06","dev07","dev08","dev09","dev10","dev11","dev12","dev13","dev14","dev15"]
50
+ "projects": ["dev01","dev02","...","dev15"]
51
51
  }
52
52
  }
53
53
  }
54
54
  ```
55
55
 
56
- ### 环境匹配规则(含 range 范围匹配)
56
+ ### 环境列表(默认)
57
57
 
58
- 用户说的话 匹配逻辑:
58
+ | 环境别名 | 名称 | URL | 快捷词 |
59
+ |----------|------|-----|--------|
60
+ | `test13` | 测试13(主测试环境) | `https://test13.xnzn.net/grafana` | test13, 13 |
61
+ | `monitor-test` | Monitor 测试环境 | `https://monitor-test.xnzn.net/grafana` | mtest |
62
+ | `monitor-dev` | Monitor 开发环境 | `https://monitor-dev.xnzn.net/grafana` | mdev, dev |
63
+ | `monitor02-dev` | Monitor02 开发环境 | `https://monitor02-dev.xnzn.net/grafana` | m02, monitor02 |
64
+ | `monitor-tyy-dev` | Monitor 体验园开发环境 | `https://monitor-tyy-dev.xnzn.net/grafana` | tyy, 体验园 |
65
+
66
+ ### 环境匹配规则(含 range)
59
67
 
60
68
  | 用户说法 | 匹配方式 | 结果 |
61
69
  |---------|---------|------|
62
- | "查 test13 的日志" | 精确匹配 keyaliases | `test13` 环境 |
63
- | "去 dev10 查" | **range 匹配**:dev10 在 monitor-dev 的 projects 中 | `monitor-dev` 环境,`project="dev10"` |
70
+ | "查 test13 的日志" | 精确匹配 key/aliases | `test13` 环境 |
71
+ | "去 dev10 查" | **range 匹配**:dev10 在 monitor-dev 的 projects 中 | `monitor-dev`,`project="dev10"` |
64
72
  | "去 monitor-dev 查" | 精确匹配 key | `monitor-dev` 环境 |
65
73
  | "切到体验园" | aliases 匹配 | `monitor-tyy-dev` 环境 |
66
74
  | 未指定环境 | 使用 `active` 字段 | 默认活跃环境 |
67
75
 
68
- **range 匹配算法**:
69
- ```
70
- 1. 用户输入 "dev10"
71
- 2. 先精确匹配 key / aliases → 未命中
72
- 3. 遍历所有环境的 projects 列表,检查是否包含 "dev10"
73
- 4. 命中 → 使用该环境,并自动添加 project="dev10" 标签过滤
74
- ```
75
-
76
76
  ### 读取配置(含全局降级 + range 匹配)
77
77
 
78
78
  ```bash
@@ -82,10 +82,10 @@ find_config() {
82
82
  for f in \
83
83
  "${PROJECT_DIR}/.claude/loki-config.json" \
84
84
  "${PROJECT_DIR}/.claude/skills/loki-log-query/environments.json" \
85
- "${HOME}/.claude/loki-config.json"; do
85
+ "${HOME}/.claude/loki-config.json" \
86
+ "${HOME}/.cursor/loki-config.json"; do
86
87
  [ -f "$f" ] && echo "$f" && return
87
88
  done
88
- echo ""
89
89
  }
90
90
 
91
91
  ENV_FILE=$(find_config)
@@ -93,46 +93,43 @@ ENV_FILE=$(find_config)
93
93
  # 通过别名或 range 查找环境 key + project
94
94
  find_env() {
95
95
  python3 -c "
96
- import json, re
96
+ import json
97
97
  data = json.load(open('${ENV_FILE}'))
98
98
  alias = '${1}'.lower()
99
- # 1. 精确匹配 key 或 aliases
100
99
  for key, env in data['environments'].items():
101
100
  if alias == key or alias in env.get('aliases', []):
102
- print(f'{key}|') # key|project(project 为空)
103
- exit()
104
- # 2. range 匹配:在 projects 列表中查找
101
+ print(f'{key}|'); exit()
105
102
  for key, env in data['environments'].items():
106
103
  if alias in env.get('projects', []):
107
- print(f'{key}|{alias}')
108
- exit()
109
- # 3. 未命中,返回默认
104
+ print(f'{key}|{alias}'); exit()
110
105
  print(f'{data[\"active\"]}|')
111
106
  "
112
107
  }
113
-
114
- # 使用示例:
115
- # result=$(find_env "dev10")
116
- # ENV_KEY=$(echo "$result" | cut -d'|' -f1) # monitor-dev
117
- # PROJECT=$(echo "$result" | cut -d'|' -f2) # dev10
118
- # 查询时:{app="yunshitang",project="${PROJECT}"}
119
108
  ```
120
109
 
121
- ### 切换活跃环境 / 更新 Token
110
+ ### 切换活跃环境
122
111
 
123
112
  ```bash
113
+ # 切换默认环境为 monitor-dev
124
114
  python3 -c "
125
115
  import json
126
116
  data = json.load(open('${ENV_FILE}'))
127
117
  data['active'] = 'monitor-dev'
128
118
  json.dump(data, open('${ENV_FILE}', 'w'), indent=2, ensure_ascii=False)
119
+ print('Switched to:', data['active'])
129
120
  "
121
+ ```
130
122
 
123
+ ### 更新 Token
124
+
125
+ ```bash
126
+ # 为某个环境设置 Token
131
127
  python3 -c "
132
128
  import json
133
129
  data = json.load(open('${ENV_FILE}'))
134
- data['environments']['monitor-dev']['token'] = 'glsa_token'
130
+ data['environments']['monitor-dev']['token'] = 'glsa_新的token'
135
131
  json.dump(data, open('${ENV_FILE}', 'w'), indent=2, ensure_ascii=False)
132
+ print('Token updated for monitor-dev')
136
133
  "
137
134
  ```
138
135
 
@@ -113,61 +113,43 @@ brew install mysql-client
113
113
 
114
114
  ## 多环境支持
115
115
 
116
- ### 配置文件查找顺序
116
+ ### 配置文件结构
117
117
 
118
- ```
119
- 1. 本地项目:.claude/mysql-config.json(或 .cursor/mysql-config.json)
120
- 2. 全局配置:~/.claude/mysql-config.json(或 ~/.cursor/mysql-config.json)
121
- ```
122
-
123
- 本地配置优先。如果本地无配置,使用全局。两者都存在时,本地覆盖全局(同名环境取本地值)。
124
- `config --scope global` 会同时写入 `~/.claude/` 和 `~/.cursor/`(如果目录存在)。
125
-
126
- ### 配置文件结构(支持 range 范围匹配)
118
+ `.claude/mysql-config.json` 支持多环境配置:
127
119
 
128
120
  ```json
129
121
  {
130
122
  "environments": {
131
- "local": { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "xxx", "_desc": "本地环境" },
132
- "dev": { "host": "dev-db.example.com", "port": 3306, "user": "dev_user", "password": "xxx", "range": "1~15", "_desc": "覆盖 dev1→dev15" },
133
- "test": { "host": "test-db.example.com", "port": 3306, "user": "test_user", "password": "xxx", "range": "1~30", "_desc": "覆盖 test1→test30" },
123
+ "local": { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "xxx" },
124
+ "dev": { "host": "dev-db.example.com", "port": 3306, "user": "dev_user", "password": "xxx" },
134
125
  "prod": { "host": "prod-db.example.com", "port": 3306, "user": "readonly", "password": "xxx" }
135
126
  },
136
127
  "default": "local"
137
128
  }
138
129
  ```
139
130
 
140
- ### 环境范围匹配(range)
141
-
142
- `range` 字段支持 `起始~结束` 格式,用于一个配置覆盖多个编号环境:
143
-
144
- | 用户说法 | 匹配逻辑 | 结果 |
145
- |---------|---------|------|
146
- | "去 dev10 查" | 提取前缀 `dev`、编号 `10`,检查 dev 环境 range "1~15",10 在范围内 | 使用 dev 配置 |
147
- | "连 test25" | 提取前缀 `test`、编号 `25`,检查 test 环境 range "1~30",25 在范围内 | 使用 test 配置 |
148
- | "连 prod" | 精确匹配 prod 环境 | 使用 prod 配置 |
149
-
150
- **匹配算法**:
151
- ```
152
- 1. 用户输入的环境名(如 "dev10")
153
- 2. 先精确匹配环境 key(environments["dev10"])
154
- 3. 未命中 → 拆分为前缀+编号("dev" + 10)
155
- 4. 查找有 range 字段的环境,前缀匹配 + 编号在范围内
156
- 5. 命中 → 使用该环境的 host/port/user/password
157
- ```
158
-
159
131
  ### 环境选择规则
160
132
 
161
133
  | 优先级 | 来源 | 示例 |
162
134
  |--------|------|------|
163
- | 1(最高) | 用户对话中指定 | "连 dev10 环境查一下" |
135
+ | 1(最高) | 用户对话中指定 | "连 dev 环境查一下"、"用生产库看看" |
164
136
  | 2 | 配置文件 `default` 字段 | `"default": "local"` |
165
137
 
138
+ ### 环境关键词映射
139
+
140
+ 用户说的话 → 对应环境名:
141
+
142
+ | 用户说法 | 环境 |
143
+ |---------|------|
144
+ | "本地"、"local"、"本地环境" | `local` |
145
+ | "开发"、"dev"、"测试环境"、"开发环境" | `dev` |
146
+ | "生产"、"prod"、"线上"、"正式环境" | `prod` |
147
+
166
148
  ### 连接示例
167
149
 
168
150
  ```bash
169
- # 用户说"连 dev10 环境查一下 order_info"
170
- # → range 匹配到 dev 环境的连接信息 + 日志提取的数据库名
151
+ # 用户说"连 dev 环境查一下 order_info"
152
+ # → 读取 environments.dev 的连接信息 + 日志提取的数据库名
171
153
  mysql -h dev-db.example.com -P 3306 -u dev_user -p'xxx' 546198574447230976 -e "SELECT ..."
172
154
  ```
173
155
 
@@ -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` 标注合法值或直接用枚举类型 |
@@ -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 修复后**必须执行交叉验证**,不能只看"能跑"就提交