ai-engineering-init 1.3.4 → 1.4.0

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 (226) hide show
  1. package/.claude/hooks/skill-forced-eval.js +2 -0
  2. package/.claude/settings.json +3 -3
  3. package/.claude/skills/add-skill/SKILL.md +79 -32
  4. package/.claude/skills/api-development/SKILL.md +83 -377
  5. package/.claude/skills/architecture-design/SKILL.md +138 -632
  6. package/.claude/skills/backend-annotations/SKILL.md +134 -506
  7. package/.claude/skills/banana-image/SKILL.md +10 -3
  8. package/.claude/skills/brainstorm/SKILL.md +103 -535
  9. package/.claude/skills/bug-detective/SKILL.md +147 -1097
  10. package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
  11. package/.claude/skills/code-patterns/SKILL.md +116 -426
  12. package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  13. package/.claude/skills/crud-development/SKILL.md +64 -304
  14. package/.claude/skills/data-permission/SKILL.md +105 -412
  15. package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
  16. package/.claude/skills/file-oss-management/SKILL.md +106 -714
  17. package/.claude/skills/file-oss-management/references/entities.md +105 -0
  18. package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
  19. package/.claude/skills/leniu-api-development/SKILL.md +142 -626
  20. package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
  21. package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
  22. package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
  23. package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
  24. package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  25. package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
  26. package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
  27. package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
  28. package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
  29. package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
  30. package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
  31. package/.claude/skills/leniu-java-export/SKILL.md +94 -379
  32. package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
  33. package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
  34. package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  35. package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
  36. package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  37. package/.claude/skills/leniu-report-customization/SKILL.md +111 -365
  38. package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
  39. package/.claude/skills/leniu-report-standard-customization/SKILL.md +111 -334
  40. package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  41. package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  42. package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
  43. package/.claude/skills/mysql-debug/SKILL.md +364 -0
  44. package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
  45. package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
  46. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  47. package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
  48. package/.claude/skills/openspec-explore/SKILL.md +10 -1
  49. package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
  50. package/.claude/skills/openspec-new-change/SKILL.md +9 -1
  51. package/.claude/skills/openspec-onboard/SKILL.md +15 -130
  52. package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
  53. package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
  54. package/.claude/skills/performance-doctor/SKILL.md +110 -434
  55. package/.claude/skills/redis-cache/SKILL.md +89 -595
  56. package/.claude/skills/redis-cache/references/listeners.md +23 -0
  57. package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
  58. package/.claude/skills/security-guard/SKILL.md +137 -532
  59. package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
  60. package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
  61. package/.claude/skills/sms-mail/SKILL.md +116 -574
  62. package/.claude/skills/sms-mail/references/mail-config.md +88 -0
  63. package/.claude/skills/sms-mail/references/sms-config.md +74 -0
  64. package/.claude/skills/social-login/SKILL.md +112 -514
  65. package/.claude/skills/social-login/references/provider-configs.md +118 -0
  66. package/.claude/skills/tenant-management/SKILL.md +129 -444
  67. package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
  68. package/.claude/skills/test-development/SKILL.md +86 -540
  69. package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
  70. package/.claude/skills/utils-toolkit/SKILL.md +52 -305
  71. package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  72. package/.claude/skills/websocket-sse/SKILL.md +105 -550
  73. package/.claude/skills/workflow-engine/SKILL.md +147 -502
  74. package/.codex/skills/add-skill/SKILL.md +79 -32
  75. package/.codex/skills/api-development/SKILL.md +172 -599
  76. package/.codex/skills/architecture-design/SKILL.md +138 -504
  77. package/.codex/skills/backend-annotations/SKILL.md +134 -496
  78. package/.codex/skills/banana-image/SKILL.md +10 -3
  79. package/.codex/skills/brainstorm/SKILL.md +103 -535
  80. package/.codex/skills/bug-detective/SKILL.md +147 -1097
  81. package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
  82. package/.codex/skills/code-patterns/SKILL.md +120 -282
  83. package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  84. package/.codex/skills/crud-development/SKILL.md +64 -292
  85. package/.codex/skills/data-permission/SKILL.md +108 -407
  86. package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
  87. package/.codex/skills/database-ops/SKILL.md +8 -154
  88. package/.codex/skills/error-handler/SKILL.md +10 -0
  89. package/.codex/skills/file-oss-management/SKILL.md +106 -714
  90. package/.codex/skills/file-oss-management/references/entities.md +105 -0
  91. package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
  92. package/.codex/skills/git-workflow/SKILL.md +27 -5
  93. package/.codex/skills/leniu-api-development/SKILL.md +142 -626
  94. package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
  95. package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
  96. package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
  97. package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
  98. package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  99. package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
  100. package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
  101. package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
  102. package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
  103. package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
  104. package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
  105. package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
  106. package/.codex/skills/leniu-java-export/SKILL.md +94 -379
  107. package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
  108. package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
  109. package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  110. package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
  111. package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  112. package/.codex/skills/leniu-report-customization/SKILL.md +111 -365
  113. package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
  114. package/.codex/skills/leniu-report-standard-customization/SKILL.md +111 -334
  115. package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  116. package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  117. package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
  118. package/.codex/skills/mysql-debug/SKILL.md +364 -0
  119. package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
  120. package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
  121. package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  122. package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
  123. package/.codex/skills/openspec-explore/SKILL.md +10 -1
  124. package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
  125. package/.codex/skills/openspec-new-change/SKILL.md +9 -1
  126. package/.codex/skills/openspec-onboard/SKILL.md +15 -130
  127. package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
  128. package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
  129. package/.codex/skills/performance-doctor/SKILL.md +110 -434
  130. package/.codex/skills/project-navigator/SKILL.md +20 -1
  131. package/.codex/skills/redis-cache/SKILL.md +93 -589
  132. package/.codex/skills/redis-cache/references/listeners.md +23 -0
  133. package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
  134. package/.codex/skills/security-guard/SKILL.md +141 -527
  135. package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
  136. package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
  137. package/.codex/skills/sms-mail/SKILL.md +116 -574
  138. package/.codex/skills/sms-mail/references/mail-config.md +88 -0
  139. package/.codex/skills/sms-mail/references/sms-config.md +74 -0
  140. package/.codex/skills/social-login/SKILL.md +112 -514
  141. package/.codex/skills/social-login/references/provider-configs.md +118 -0
  142. package/.codex/skills/store-pc/SKILL.md +258 -383
  143. package/.codex/skills/tenant-management/SKILL.md +129 -444
  144. package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
  145. package/.codex/skills/test-development/SKILL.md +86 -540
  146. package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
  147. package/.codex/skills/ui-pc/SKILL.md +350 -387
  148. package/.codex/skills/utils-toolkit/SKILL.md +52 -283
  149. package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  150. package/.codex/skills/websocket-sse/SKILL.md +105 -550
  151. package/.codex/skills/workflow-engine/SKILL.md +147 -502
  152. package/.cursor/hooks.json +3 -3
  153. package/.cursor/skills/add-skill/SKILL.md +79 -32
  154. package/.cursor/skills/api-development/SKILL.md +83 -377
  155. package/.cursor/skills/architecture-design/SKILL.md +138 -632
  156. package/.cursor/skills/backend-annotations/SKILL.md +134 -506
  157. package/.cursor/skills/banana-image/SKILL.md +10 -3
  158. package/.cursor/skills/brainstorm/SKILL.md +103 -535
  159. package/.cursor/skills/bug-detective/SKILL.md +147 -1097
  160. package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
  161. package/.cursor/skills/code-patterns/SKILL.md +116 -426
  162. package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  163. package/.cursor/skills/crud-development/SKILL.md +64 -304
  164. package/.cursor/skills/data-permission/SKILL.md +105 -412
  165. package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
  166. package/.cursor/skills/file-oss-management/SKILL.md +106 -714
  167. package/.cursor/skills/file-oss-management/references/entities.md +105 -0
  168. package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
  169. package/.cursor/skills/git-workflow/SKILL.md +27 -5
  170. package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
  171. package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
  172. package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
  173. package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
  174. package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
  175. package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  176. package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
  177. package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
  178. package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
  179. package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
  180. package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
  181. package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
  182. package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
  183. package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
  184. package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
  185. package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
  186. package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  187. package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
  188. package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  189. package/.cursor/skills/leniu-report-customization/SKILL.md +111 -365
  190. package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
  191. package/.cursor/skills/leniu-report-standard-customization/SKILL.md +111 -334
  192. package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  193. package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  194. package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
  195. package/.cursor/skills/mysql-debug/SKILL.md +364 -0
  196. package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
  197. package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
  198. package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
  199. package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
  200. package/.cursor/skills/openspec-explore/SKILL.md +10 -1
  201. package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
  202. package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
  203. package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
  204. package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
  205. package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
  206. package/.cursor/skills/performance-doctor/SKILL.md +110 -434
  207. package/.cursor/skills/redis-cache/SKILL.md +89 -595
  208. package/.cursor/skills/redis-cache/references/listeners.md +23 -0
  209. package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
  210. package/.cursor/skills/security-guard/SKILL.md +137 -532
  211. package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
  212. package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
  213. package/.cursor/skills/sms-mail/SKILL.md +116 -574
  214. package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
  215. package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
  216. package/.cursor/skills/social-login/SKILL.md +112 -514
  217. package/.cursor/skills/social-login/references/provider-configs.md +118 -0
  218. package/.cursor/skills/tenant-management/SKILL.md +129 -444
  219. package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
  220. package/.cursor/skills/test-development/SKILL.md +86 -540
  221. package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
  222. package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
  223. package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  224. package/.cursor/skills/websocket-sse/SKILL.md +105 -550
  225. package/.cursor/skills/workflow-engine/SKILL.md +147 -502
  226. package/package.json +1 -1
@@ -1,734 +1,198 @@
1
1
  ---
2
2
  name: leniu-crud-development
3
3
  description: |
4
- leniu 项目 CRUD 开发规范。基于 pigx-framework 三层架构(Controller Service Mapper),无独立 DAO 层。
5
- 涵盖 Service 层事务管理、分页查询模式、并发处理、代码质量要点。
4
+ leniu 项目 CRUD 开发规范。基于 pigx-framework 四层架构(Controller -> Business -> Service -> Mapper)。
5
+ 涵盖命名规范、代码模板、分页模式、事务管理、并发处理、代码质量要点。
6
6
 
7
7
  触发场景:
8
8
  - 新建 leniu 业务模块的 CRUD 功能
9
9
  - 创建 Entity、DTO、VO、Service、Mapper、Controller
10
- - 分页查询、新增、修改、删除、导出
11
- - 查询条件构建(LambdaQueryWrapper)
12
- - Service 层事务管理(多表操作、事务回滚)
13
- - 并发处理(CompletableFuture、分布式锁)
14
- - 代码质量:空指针防护、返回值兜底、集合参数防御
10
+ - 分页查询(PageHelper / MyBatis-Plus)
11
+ - 事务管理(多表操作、self 自注入)
12
+ - 报表 Service 模式(含数据权限、并发查询)
15
13
 
16
14
  适用项目:
17
15
  - leniu-tengyun-core(云食堂核心服务)
18
16
  - leniu-yunshitang(云食堂业务服务)
19
17
 
20
- 触发词:CRUD、增删改查、新建模块、Business层、Service、Mapper、Controller、分页查询、LeRequest、PageDTO、PageVO、事务管理
18
+ 触发词:CRUD、增删改查、新建模块、Business层、Service、Mapper、Controller、分页查询、LeRequest、PageDTO、PageVO、事务管理、报表Service
21
19
  ---
22
20
 
23
21
  # leniu CRUD 开发规范
24
22
 
25
- ## 项目定位
23
+ > 完整代码模板见 `references/templates.md`
26
24
 
27
- 本技能专用于 **leniu 云食堂项目** 的 CRUD 功能开发。
25
+ ## 项目路径
28
26
 
29
27
  | 项目 | 路径 |
30
28
  |------|------|
31
- | **leniu-tengyun-core** | `/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core` |
32
- | **leniu-yunshitang** | `/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang` |
33
- | **包名前缀** | `net.xnzn.core.*` |
29
+ | leniu-tengyun-core | `/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core` |
30
+ | leniu-yunshitang | `/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang` |
31
+ | 包名前缀 | `net.xnzn.core.*` |
34
32
 
35
33
  ---
36
34
 
37
- ## 核心架构差异
38
-
39
- | 对比项 | leniu-tengyun-core |
40
- |--------|-------------------|
41
- | **包名前缀** | `net.xnzn.*` |
42
- | **架构** | 三层:Controller Service Mapper |
43
- | **DAO 层** | 不存在,Service 直接注入 Mapper |
44
- | **查询构建** | Service 层 LambdaQueryWrapper |
45
- | **Mapper 继承** | `BaseMapper<Entity>` |
46
- | **对象转换** | `BeanUtil.copyProperties()` (Hutool) |
47
- | **Entity 基类** | 无基类(自定义审计字段) |
48
- | **请求封装** | `LeRequest<T>` 包装 |
49
- | **响应封装** | `Page<T>`, `LeResponse<T>`, `void` |
50
- | **分组校验** | `InsertGroup`, `UpdateGroup` |
51
- | **认证注解** | `@RequiresAuthentication`, `@RequiresGuest` |
52
- | **异常类** | `LeException` |
53
- | **审计字段** | `crby/crtime/upby/uptime` |
54
- | **逻辑删除** | `del_flag` (1=删除, 2=正常) |
55
- | **主键策略** | 雪花ID 或 自增ID |
56
- | **Mapper XML** | 与 Java 同目录(非 resources/mapper) |
35
+ ## 架构概览
36
+
37
+ | | 规范 |
38
+ |----|------|
39
+ | 架构 | Controller -> Business -> Service -> Mapper(四层) |
40
+ | DAO | Service 直接注入 Mapper |
41
+ | 对象转换 | `BeanUtil.copyProperties()` (Hutool) |
42
+ | Entity 基类 | 无基类,自定义审计字段 |
43
+ | 请求封装 | `LeRequest<T>` |
44
+ | 响应封装 | `Page<T>` / `LeResponse<T>` / `void` |
45
+ | 分组校验 | `InsertGroup` / `UpdateGroup` |
46
+ | 认证注解 | `@RequiresAuthentication` / `@RequiresGuest` |
47
+ | 异常类 | `LeException` |
48
+ | 审计字段 | crby/crtime/upby/uptime |
49
+ | 逻辑删除 | del_flag(1=删除, 2=正常) |
50
+ | 主键 | 雪花ID `Id.next()` 或自增 |
51
+ | Mapper XML | 与 Java 同目录(非 resources/mapper) |
52
+ | 验证包 | `jakarta.validation.*`(JDK 21) |
57
53
 
58
54
  ---
59
55
 
60
- ## 1. Entity 实体类
56
+ ## 标准包结构
61
57
 
62
- ```java
63
- package net.xnzn.core.xxx.model;
64
-
65
- import com.baomidou.mybatisplus.annotation.*;
66
- import io.swagger.annotations.ApiModel;
67
- import io.swagger.annotations.ApiModelProperty;
68
- import lombok.Data;
69
- import lombok.experimental.Accessors;
70
-
71
- import java.io.Serializable;
72
- import java.time.LocalDateTime;
73
-
74
- /**
75
- * XXX 对象
76
- */
77
- @Data
78
- @Accessors(chain = true)
79
- @TableName("xxx_table")
80
- @ApiModel(value = "XXX对象", description = "XXX表")
81
- public class XxxEntity implements Serializable {
82
-
83
- private static final long serialVersionUID = 1L;
84
-
85
- /**
86
- * 主键 ID
87
- */
88
- @ApiModelProperty("主键ID")
89
- @TableId(value = "id", type = IdType.AUTO)
90
- private Long id;
91
-
92
- /**
93
- * 名称
94
- */
95
- @ApiModelProperty("名称")
96
- @TableField("name")
97
- private String name;
98
-
99
- /**
100
- * 状态
101
- */
102
- @ApiModelProperty("状态")
103
- @TableField("status")
104
- private Integer status;
105
-
106
- /**
107
- * 删除标识(1删除,2正常)
108
- */
109
- @ApiModelProperty("删除标识(1删除,2正常)")
110
- @TableField("del_flag")
111
- private Integer delFlag;
112
-
113
- /**
114
- * 乐观锁
115
- */
116
- @ApiModelProperty("乐观锁")
117
- @TableField("revision")
118
- private Integer revision;
119
-
120
- /**
121
- * 创建人
122
- */
123
- @ApiModelProperty("创建人")
124
- @TableField(value = "crby", fill = FieldFill.INSERT)
125
- private String crby;
126
-
127
- /**
128
- * 创建时间
129
- */
130
- @ApiModelProperty("创建时间")
131
- @TableField(value = "crtime", fill = FieldFill.INSERT)
132
- private LocalDateTime crtime;
133
-
134
- /**
135
- * 更新人
136
- */
137
- @ApiModelProperty("更新人")
138
- @TableField(value = "upby", fill = FieldFill.INSERT_UPDATE)
139
- private String upby;
140
-
141
- /**
142
- * 更新时间
143
- */
144
- @ApiModelProperty("更新时间")
145
- @TableField(value = "uptime", fill = FieldFill.INSERT_UPDATE)
146
- private LocalDateTime uptime;
147
- }
148
58
  ```
149
-
150
- ---
151
-
152
- ## 2. DTO 数据传输对象
153
-
154
- ```java
155
- package net.xnzn.core.xxx.dto;
156
-
157
- import io.swagger.annotations.ApiModel;
158
- import io.swagger.annotations.ApiModelProperty;
159
- import lombok.Data;
160
-
161
- import jakarta.validation.constraints.*;
162
- import java.io.Serializable;
163
- import java.util.Date;
164
-
165
- /**
166
- * XXX DTO
167
- */
168
- @Data
169
- @ApiModel("XXX DTO")
170
- public class XxxDTO implements Serializable {
171
-
172
- private static final long serialVersionUID = 1L;
173
-
174
- @ApiModelProperty(value = "主键ID")
175
- @NotNull(message = "主键ID不能为空", groups = {UpdateGroup.class})
176
- private Long id;
177
-
178
- @ApiModelProperty(value = "名称", required = true)
179
- @NotBlank(message = "名称不能为空", groups = {InsertGroup.class, UpdateGroup.class})
180
- @Size(max = 100, message = "名称长度不能超过100个字符")
181
- private String name;
182
-
183
- @ApiModelProperty(value = "状态")
184
- private Integer status;
185
-
186
- @ApiModelProperty(value = "开始时间", required = true)
187
- @NotNull(message = "开始时间不能为空", groups = {InsertGroup.class, UpdateGroup.class})
188
- private Date startTime;
189
-
190
- @ApiModelProperty(value = "结束时间", required = true)
191
- @NotNull(message = "结束时间不能为空", groups = {InsertGroup.class, UpdateGroup.class})
192
- private Date endTime;
193
- }
59
+ net.xnzn.core.[module]/
60
+ +-- controller/ # 按端分:web/mobile/android
61
+ +-- business/impl/ # 业务编排(跨 Service 协调)
62
+ +-- service/impl/ # 单表 CRUD、事务
63
+ +-- mapper/ # Mapper + XML(同目录)
64
+ +-- model/ # Entity
65
+ +-- vo/ # 响应对象
66
+ +-- dto/ # 请求参数
67
+ +-- constants/ # 枚举和常量
194
68
  ```
195
69
 
196
70
  ---
197
71
 
198
- ## 3. VO 视图对象
199
-
200
- ```java
201
- package net.xnzn.core.xxx.vo;
202
-
203
- import com.fasterxml.jackson.annotation.JsonFormat;
204
- import io.swagger.annotations.ApiModel;
205
- import io.swagger.annotations.ApiModelProperty;
206
- import lombok.Data;
207
-
208
- import java.io.Serializable;
209
- import java.util.Date;
210
-
211
- /**
212
- * XXX VO
213
- */
214
- @Data
215
- @ApiModel("XXX VO")
216
- public class XxxVO implements Serializable {
217
-
218
- private static final long serialVersionUID = 1L;
72
+ ## 命名规范
219
73
 
220
- @ApiModelProperty("主键ID")
221
- private Long id;
74
+ | 类型 | 命名 | 示例 |
75
+ |------|------|------|
76
+ | Entity | `Xxx` / `XxxEntity` | `OrderInfo` |
77
+ | DTO | `XxxDTO` | `OrderInfoDTO` |
78
+ | VO | `XxxVO` | `OrderInfoVO` |
79
+ | Service 接口 | `XxxService` | `OrderInfoService` |
80
+ | Service 实现 | `XxxServiceImpl` | `OrderInfoServiceImpl` |
81
+ | Mapper | `XxxMapper` | `OrderInfoMapper` |
82
+ | Controller (Web) | `XxxWebController` | `OrderInfoWebController` |
83
+ | Business | `XxxWebBusiness` | `OrderWebBusiness` |
222
84
 
223
- @ApiModelProperty("名称")
224
- private String name;
85
+ ### Controller 路由前缀
225
86
 
226
- @ApiModelProperty("状态")
227
- private String status;
228
-
229
- @ApiModelProperty("状态描述")
230
- private String statusDesc;
231
-
232
- @ApiModelProperty(value = "创建时间")
233
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
234
- private Date crtime;
235
-
236
- @ApiModelProperty(value = "更新时间")
237
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
238
- private Date uptime;
239
- }
240
- ```
87
+ | 端 | 前缀 |
88
+ |----|------|
89
+ | Web 管理端 | `/api/v2/web/{module}` |
90
+ | 移动端 | `/api/v2/mobile/{module}` |
91
+ | 设备端 | `/api/v2/android/{module}` |
92
+ | 开放接口 | `/api/v2/open/{module}` |
241
93
 
242
94
  ---
243
95
 
244
- ## 4. Service 接口
96
+ ## 核心代码片段
97
+
98
+ ### Entity 审计字段
245
99
 
246
100
  ```java
247
- package net.xnzn.core.xxx.service;
248
-
249
- import net.xnzn.core.xxx.dto.XxxDTO;
250
- import net.xnzn.core.xxx.vo.XxxVO;
251
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
252
-
253
- import java.util.List;
254
-
255
- /**
256
- * XXX 服务接口
257
- */
258
- public interface XxxService {
259
-
260
- /**
261
- * 新增
262
- */
263
- Long add(XxxDTO dto);
264
-
265
- /**
266
- * 修改
267
- */
268
- void update(XxxDTO dto);
269
-
270
- /**
271
- * 删除
272
- */
273
- void delete(Long id);
274
-
275
- /**
276
- * 根据ID查询
277
- */
278
- XxxVO getById(Long id);
279
-
280
- /**
281
- * 分页查询
282
- */
283
- Page<XxxVO> page(XxxDTO dto);
284
-
285
- /**
286
- * 查询列表
287
- */
288
- List<XxxVO> list(XxxDTO dto);
289
- }
101
+ @TableField(value = "crby", fill = FieldFill.INSERT)
102
+ private String crby;
103
+ @TableField(value = "crtime", fill = FieldFill.INSERT)
104
+ private LocalDateTime crtime;
105
+ @TableField(value = "upby", fill = FieldFill.INSERT_UPDATE)
106
+ private String upby;
107
+ @TableField(value = "uptime", fill = FieldFill.INSERT_UPDATE)
108
+ private LocalDateTime uptime;
109
+ @TableField("del_flag")
110
+ private Integer delFlag; // 1=删除, 2=正常
290
111
  ```
291
112
 
292
- ---
293
-
294
- ## 5. Service 实现类
113
+ ### Service 注入模式
295
114
 
296
115
  ```java
297
- package net.xnzn.core.xxx.service.impl;
298
-
299
- import cn.hutool.core.bean.BeanUtil;
300
- import cn.hutool.core.collection.CollUtil;
301
- import cn.hutool.core.util.ObjectUtil;
302
- import cn.hutool.core.util.StrUtil;
303
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
304
- import com.baomidou.mybatisplus.core.toolkit.Wrappers;
305
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
306
- import com.pig4cloud.pigx.common.core.exception.LeException;
307
- import lombok.extern.slf4j.Slf4j;
308
- import net.xnzn.core.xxx.dto.XxxDTO;
309
- import net.xnzn.core.xxx.mapper.XxxMapper;
310
- import net.xnzn.core.xxx.model.XxxEntity;
311
- import net.xnzn.core.xxx.service.XxxService;
312
- import net.xnzn.core.xxx.vo.XxxVO;
313
- import org.springframework.stereotype.Service;
314
- import org.springframework.transaction.annotation.Transactional;
315
-
316
- import javax.annotation.Resource;
317
- import java.util.Collections;
318
- import java.util.List;
319
- import java.util.Optional;
320
- import java.util.stream.Collectors;
321
-
322
- /**
323
- * XXX 服务实现
324
- */
325
116
  @Slf4j
326
117
  @Service
327
118
  public class XxxServiceImpl implements XxxService {
328
-
329
119
  @Resource
330
- private XxxMapper xxxMapper;
331
-
332
- @Override
333
- @Transactional(rollbackFor = Exception.class)
334
- public Long add(XxxDTO dto) {
335
- log.info("开始新增XXX,名称: {}", dto.getName());
336
-
337
- // 参数校验
338
- if (StrUtil.isBlank(dto.getName())) {
339
- throw new LeException("名称不能为空");
340
- }
341
-
342
- // 业务校验:唯一性检查
343
- LambdaQueryWrapper<XxxEntity> wrapper = Wrappers.lambdaQuery();
344
- wrapper.eq(XxxEntity::getName, dto.getName());
345
- wrapper.eq(XxxEntity::getDelFlag, 2);
346
- Long count = xxxMapper.selectCount(wrapper);
347
- if (count > 0) {
348
- throw new LeException("名称已存在");
349
- }
350
-
351
- // 转换并保存
352
- XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
353
- entity.setDelFlag(2); // 正常状态
354
-
355
- xxxMapper.insert(entity);
356
-
357
- log.info("新增XXX成功,ID: {}", entity.getId());
358
- return entity.getId();
359
- }
360
-
361
- @Override
362
- @Transactional(rollbackFor = Exception.class)
363
- public void update(XxxDTO dto) {
364
- if (ObjectUtil.isNull(dto.getId())) {
365
- throw new LeException("ID不能为空");
366
- }
367
-
368
- // 查询并校验记录存在
369
- XxxEntity exist = Optional.ofNullable(xxxMapper.selectById(dto.getId()))
370
- .orElseThrow(() -> new LeException("记录不存在"));
371
-
372
- XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
373
- xxxMapper.updateById(entity);
374
-
375
- log.info("更新XXX成功,ID: {}", dto.getId());
376
- }
377
-
378
- @Override
379
- @Transactional(rollbackFor = Exception.class)
380
- public void delete(Long id) {
381
- if (ObjectUtil.isNull(id)) {
382
- throw new LeException("ID不能为空");
383
- }
384
-
385
- Optional.ofNullable(xxxMapper.selectById(id))
386
- .orElseThrow(() -> new LeException("记录不存在"));
387
-
388
- // 逻辑删除
389
- XxxEntity entity = new XxxEntity();
390
- entity.setId(id);
391
- entity.setDelFlag(1); // 删除状态
392
-
393
- xxxMapper.updateById(entity);
394
-
395
- log.info("删除XXX成功,ID: {}", id);
396
- }
397
-
398
- @Override
399
- public XxxVO getById(Long id) {
400
- if (ObjectUtil.isNull(id)) {
401
- throw new LeException("ID不能为空");
402
- }
403
-
404
- XxxEntity entity = Optional.ofNullable(xxxMapper.selectById(id))
405
- .orElseThrow(() -> new LeException("记录不存在"));
406
-
407
- return BeanUtil.copyProperties(entity, XxxVO.class);
408
- }
120
+ private XxxMapper xxxMapper; // 直接注入 Mapper,无 DAO 层
409
121
 
410
- @Override
411
- public Page<XxxVO> page(XxxDTO dto) {
412
- LambdaQueryWrapper<XxxEntity> wrapper = buildWrapper(dto);
413
-
414
- // 分页
415
- Page<XxxEntity> page = new Page<>(dto.getPageNum(), dto.getPageSize());
416
- Page<XxxEntity> result = xxxMapper.selectPage(page, wrapper);
417
-
418
- // 转换为 VO
419
- Page<XxxVO> voPage = new Page<>();
420
- BeanUtil.copyProperties(result, voPage, "records");
421
- List<XxxVO> voList = result.getRecords().stream()
422
- .map(entity -> BeanUtil.copyProperties(entity, XxxVO.class))
423
- .collect(Collectors.toList());
424
- voPage.setRecords(voList);
425
-
426
- return voPage;
427
- }
428
-
429
- @Override
430
- public List<XxxVO> list(XxxDTO dto) {
431
- LambdaQueryWrapper<XxxEntity> wrapper = buildWrapper(dto);
432
-
433
- List<XxxEntity> list = xxxMapper.selectList(wrapper);
434
-
435
- // 空集合兜底
436
- if (CollUtil.isEmpty(list)) {
437
- return Collections.emptyList();
438
- }
439
-
440
- return list.stream()
441
- .map(entity -> BeanUtil.copyProperties(entity, XxxVO.class))
442
- .collect(Collectors.toList());
443
- }
444
-
445
- /**
446
- * 构建查询条件
447
- */
448
- private LambdaQueryWrapper<XxxEntity> buildWrapper(XxxDTO dto) {
449
- LambdaQueryWrapper<XxxEntity> wrapper = Wrappers.lambdaQuery();
450
-
451
- // 只查询正常数据
452
- wrapper.eq(XxxEntity::getDelFlag, 2);
453
-
454
- // 名称模糊查询
455
- if (StrUtil.isNotBlank(dto.getName())) {
456
- wrapper.like(XxxEntity::getName, dto.getName());
457
- }
458
-
459
- // 状态精确查询
460
- if (ObjectUtil.isNotNull(dto.getStatus())) {
461
- wrapper.eq(XxxEntity::getStatus, dto.getStatus());
462
- }
463
-
464
- // 按创建时间倒序
465
- wrapper.orderByDesc(XxxEntity::getCrtime);
466
-
467
- return wrapper;
468
- }
122
+ // 不继承 ServiceImpl,只实现接口
469
123
  }
470
124
  ```
471
125
 
472
- ---
473
-
474
- ## 6. Mapper 接口
126
+ ### Controller 请求封装
475
127
 
476
128
  ```java
477
- package net.xnzn.core.xxx.mapper;
478
-
479
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
480
- import net.xnzn.core.xxx.model.XxxEntity;
481
- import org.apache.ibatis.annotations.Mapper;
482
-
483
- /**
484
- * XXX Mapper 接口
485
- */
486
- @Mapper
487
- public interface XxxMapper extends BaseMapper<XxxEntity> {
488
-
489
- // 继承 BaseMapper,已提供常用 CRUD 方法
490
- // 如需自定义 SQL,在此添加方法并在对应的 XML 中实现
491
- }
492
- ```
493
-
494
- ---
495
-
496
- ## 7. Mapper XML(与 Java 同目录)
497
-
498
- ```xml
499
- <?xml version="1.0" encoding="UTF-8"?>
500
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
501
- <mapper namespace="net.xnzn.core.xxx.mapper.XxxMapper">
502
-
503
- <!-- 禁止使用 SELECT *,明确指定查询字段 -->
504
- <select id="selectCustom" resultType="net.xnzn.core.xxx.model.XxxEntity">
505
- SELECT id, name, status, del_flag, crby, crtime, upby, uptime
506
- FROM xxx_table
507
- WHERE del_flag = 2
508
- </select>
509
-
510
- </mapper>
511
- ```
512
-
513
- ---
514
-
515
- ## 8. Controller 控制器
516
-
517
- ```java
518
- package net.xnzn.core.xxx.controller;
519
-
520
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
521
- import com.pig4cloud.pigx.common.core.util.LeRequest;
522
- import io.swagger.annotations.Api;
523
- import io.swagger.annotations.ApiOperation;
524
- import lombok.extern.slf4j.Slf4j;
525
- import net.xnzn.core.xxx.dto.XxxDTO;
526
- import net.xnzn.core.xxx.service.XxxService;
527
- import net.xnzn.core.xxx.vo.XxxVO;
528
- import net.xnzn.framework.secure.filter.annotation.RequiresAuthentication;
529
- import net.xnzn.framework.secure.filter.annotation.RequiresGuest;
530
- import org.springframework.validation.annotation.Validated;
531
- import org.springframework.web.bind.annotation.*;
532
-
533
- import javax.annotation.Resource;
534
- import java.util.List;
535
-
536
- /**
537
- * XXX 管理控制器
538
- */
539
- @Slf4j
540
- @RestController
541
- @RequestMapping("/api/xxx")
542
- @Api(tags = "XXX管理")
543
- public class XxxController {
544
-
545
- @Resource
546
- private XxxService xxxService;
547
-
548
- @PostMapping("/add")
549
- @ApiOperation(value = "XXX-新增")
550
- @RequiresAuthentication
551
- public Long add(@Validated(InsertGroup.class) @RequestBody LeRequest<XxxDTO> request) {
552
- return xxxService.add(request.getContent());
553
- }
554
-
555
- @PostMapping("/update")
556
- @ApiOperation(value = "XXX-修改")
557
- @RequiresAuthentication
558
- public void update(@Validated(UpdateGroup.class) @RequestBody LeRequest<XxxDTO> request) {
559
- xxxService.update(request.getContent());
560
- }
561
-
562
- @PostMapping("/delete")
563
- @ApiOperation(value = "XXX-删除")
564
- @RequiresAuthentication
565
- public void delete(@RequestBody LeRequest<Long> request) {
566
- xxxService.delete(request.getContent());
567
- }
568
-
569
- @GetMapping("/get/{id}")
570
- @ApiOperation(value = "XXX-获取详情")
571
- @RequiresGuest
572
- public XxxVO getById(@PathVariable Long id) {
573
- return xxxService.getById(id);
574
- }
575
-
576
- @PostMapping("/page")
577
- @ApiOperation(value = "XXX-分页查询")
578
- @RequiresAuthentication
579
- public Page<XxxVO> page(@Validated @RequestBody LeRequest<XxxDTO> request) {
580
- return xxxService.page(request.getContent());
581
- }
582
-
583
- @PostMapping("/list")
584
- @ApiOperation(value = "XXX-查询列表")
585
- @RequiresGuest
586
- public List<XxxVO> list(@RequestBody LeRequest<XxxDTO> request) {
587
- return xxxService.list(request.getContent());
588
- }
589
- }
590
- ```
591
-
592
- ---
593
-
594
- ## 9. 数据库建表(SQL)
595
-
596
- ```sql
597
- -- 表名:xxx_table(根据业务命名,格式: {模块}_{业务名})
598
- CREATE TABLE `xxx_table` (
599
- `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
600
-
601
- -- 业务字段
602
- `name` VARCHAR(100) NOT NULL COMMENT '名称',
603
- `status` TINYINT(1) DEFAULT 1 COMMENT '状态(0停用 1启用)',
604
-
605
- -- 删除标识(注意:1=删除,2=正常)
606
- `del_flag` TINYINT(1) DEFAULT 2 COMMENT '删除标识(1删除 2正常)',
607
- `revision` INT DEFAULT 0 COMMENT '乐观锁版本号',
608
-
609
- -- 审计字段
610
- `crby` VARCHAR(64) DEFAULT NULL COMMENT '创建人',
611
- `crtime` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
612
- `upby` VARCHAR(64) DEFAULT NULL COMMENT '更新人',
613
- `uptime` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
614
-
615
- PRIMARY KEY (`id`),
616
- KEY `idx_status` (`status`),
617
- KEY `idx_crtime` (`crtime`),
618
- KEY `idx_del_flag` (`del_flag`)
619
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='XXX表';
620
- ```
621
-
622
- ---
623
-
624
- ## 10. 事务管理规范
625
-
626
- ### 多表操作必须加事务
627
-
628
- ```java
629
- /**
630
- * 多表操作示例:创建订单同时更新库存
631
- */
632
- @Transactional(rollbackFor = Exception.class)
633
- public void createOrderWithStock(OrderDTO dto) {
634
- // 操作订单表
635
- OrderEntity order = BeanUtil.copyProperties(dto, OrderEntity.class);
636
- orderMapper.insert(order);
637
-
638
- // 操作订单明细表
639
- List<OrderDetailEntity> details = dto.getDetails().stream()
640
- .map(d -> BeanUtil.copyProperties(d, OrderDetailEntity.class))
641
- .collect(Collectors.toList());
642
- orderDetailMapper.insert(details);
643
-
644
- // 操作库存表
645
- stockMapper.deduct(dto.getStockId(), dto.getQuantity());
129
+ @PostMapping("/add")
130
+ @RequiresAuthentication
131
+ public Long add(@Validated(InsertGroup.class) @RequestBody LeRequest<XxxDTO> request) {
132
+ return xxxService.add(request.getContent());
646
133
  }
647
- ```
648
134
 
649
- ### 查询方法不加事务
650
-
651
- ```java
652
- // 查询方法不需要事务,避免不必要的事务开销
653
- public XxxVO getById(Long id) {
654
- // 无需 @Transactional
655
- XxxEntity entity = xxxMapper.selectById(id);
656
- ...
135
+ @GetMapping("/get/{id}")
136
+ @RequiresGuest
137
+ public XxxVO getById(@PathVariable Long id) {
138
+ return xxxService.getById(id);
657
139
  }
658
140
  ```
659
141
 
660
- ### 手动回滚(特殊场景)
142
+ ### 查询条件构建
661
143
 
662
144
  ```java
663
- @Transactional(rollbackFor = Exception.class)
664
- public void businessMethod() {
665
- try {
666
- // 业务逻辑
667
- } catch (Exception e) {
668
- log.error("处理失败", e);
669
- // 手动标记回滚
670
- TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
671
- throw new LeException("处理失败");
145
+ private LambdaQueryWrapper<XxxEntity> buildWrapper(XxxDTO dto) {
146
+ LambdaQueryWrapper<XxxEntity> wrapper = Wrappers.lambdaQuery();
147
+ wrapper.eq(XxxEntity::getDelFlag, 2); // 只查正常数据
148
+ // String -> like, 非 String -> eq/in/between
149
+ if (StrUtil.isNotBlank(dto.getName())) {
150
+ wrapper.like(XxxEntity::getName, dto.getName());
151
+ }
152
+ if (ObjectUtil.isNotNull(dto.getStatus())) {
153
+ wrapper.eq(XxxEntity::getStatus, dto.getStatus());
672
154
  }
155
+ wrapper.orderByDesc(XxxEntity::getCrtime);
156
+ return wrapper;
673
157
  }
674
158
  ```
675
159
 
676
- ### Self 自注入(同类事务调用)
677
-
678
- 当 Service/Business 中的方法 A 需要调用同类的带 `@Transactional` 方法 B 时,必须通过 `self` 代理调用,否则事务不生效:
160
+ ### 对象转换与空值防护
679
161
 
680
162
  ```java
681
- @Slf4j
682
- @Service
683
- public class OrderPlaceBusiness {
163
+ // 新增
164
+ XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
165
+ entity.setDelFlag(2);
166
+ xxxMapper.insert(entity);
684
167
 
685
- @Autowired
686
- @Lazy
687
- private OrderPlaceBusiness self; // ⚠️ 自注入,用于触发 Spring AOP 事务代理
688
-
689
- /**
690
- * 外部调用入口(可以不加事务)
691
- */
692
- public void doSave(OrderSavePO orderSavePO) {
693
- // ✅ 通过 self 调用,保证 @Transactional 生效
694
- self.save(orderSavePO, false, false);
695
- }
168
+ // 查询判空
169
+ XxxEntity entity = Optional.ofNullable(xxxMapper.selectById(id))
170
+ .orElseThrow(() -> new LeException("记录不存在"));
171
+ return BeanUtil.copyProperties(entity, XxxVO.class);
696
172
 
697
- /**
698
- * 实际保存逻辑
699
- */
700
- @Transactional(rollbackFor = Exception.class)
701
- public void save(OrderSavePO orderSavePO, boolean orderExists, boolean removeDetails) {
702
- // 多表操作...
703
- }
173
+ // 列表空值兜底
174
+ List<XxxEntity> list = xxxMapper.selectList(wrapper);
175
+ if (CollUtil.isEmpty(list)) {
176
+ return Collections.emptyList();
704
177
  }
178
+ return BeanUtil.copyToList(list, XxxVO.class);
705
179
  ```
706
180
 
707
- **规则**:
708
- - 同类方法互调,被调用方有 `@Transactional` → 必须用 `self.xxx()` 而非 `this.xxx()`
709
- - `self` 字段必须配合 `@Autowired @Lazy` 避免循环依赖
710
- - 纯方法重载(无 `@Transactional`)的情况无需 `self`
711
-
712
181
  ---
713
182
 
714
- ## 11. 分页查询模式
183
+ ## 分页查询
715
184
 
716
- ### 标准 MyBatis-Plus 分页
185
+ ### MyBatis-Plus 分页
717
186
 
718
187
  ```java
719
- @Override
720
188
  public Page<XxxVO> page(XxxDTO dto) {
721
189
  LambdaQueryWrapper<XxxEntity> wrapper = buildWrapper(dto);
722
-
723
- // 创建分页对象
724
190
  Page<XxxEntity> page = new Page<>(dto.getPageNum(), dto.getPageSize());
725
191
  Page<XxxEntity> result = xxxMapper.selectPage(page, wrapper);
726
192
 
727
- // 转换 VO
728
193
  Page<XxxVO> voPage = new Page<>();
729
194
  BeanUtil.copyProperties(result, voPage, "records");
730
195
  voPage.setRecords(BeanUtil.copyToList(result.getRecords(), XxxVO.class));
731
-
732
196
  return voPage;
733
197
  }
734
198
  ```
@@ -737,37 +201,26 @@ public Page<XxxVO> page(XxxDTO dto) {
737
201
 
738
202
  ```java
739
203
  public PageVO<XxxVO> pageList(XxxPageParam param) {
740
- // 1. 开启分页:必须传入 param.getPage()(PageDTO对象),不能传整个 param
741
204
  if (Objects.nonNull(param.getPage())) {
742
- PageMethod.startPage(param.getPage());
205
+ PageMethod.startPage(param.getPage()); // 传 PageDTO,紧接查询前调用
743
206
  }
744
-
745
- // 2. 执行查询
746
207
  List<XxxVO> records = xxxMapper.pageList(param);
747
-
748
- // 3. 封装分页结果(自动提取 total 等信息)
749
208
  return PageVO.of(records);
750
209
  }
751
210
  ```
752
211
 
753
- **分页关键规则**:
754
- 1. `PageMethod.startPage(param.getPage())` 必须紧接在查询前调用,传 `PageDTO` 对象
755
- 2. 调用 startPage 和查询之间不能插入其他查询,否则分页失效
756
- 3. `PageVO.of()` 自动从 Page 对象提取分页信息
757
- 4. Mapper 方法返回 `List` 即可,PageHelper 自动转换
758
-
759
- ### 带合计行的分页(报表场景)
212
+ ### 带合计行的分页
760
213
 
761
214
  ```java
762
215
  public ReportBaseTotalVO<XxxVO> pageWithTotal(XxxPageParam param) {
763
216
  MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
764
- ReportDataPermissionParam dataPermission = reportDataPermissionService.getDataPermission(authPO);
217
+ ReportDataPermissionParam dp = reportDataPermissionService.getDataPermission(authPO);
765
218
 
766
219
  if (Objects.nonNull(param.getPage())) {
767
220
  PageMethod.startPage(param.getPage());
768
221
  }
769
- List<XxxVO> list = xxxMapper.getSummaryList(param, authPO, dataPermission);
770
- XxxVO totalLine = Optional.ofNullable(xxxMapper.getSummaryTotal(param, authPO, dataPermission))
222
+ List<XxxVO> list = xxxMapper.getSummaryList(param, authPO, dp);
223
+ XxxVO totalLine = Optional.ofNullable(xxxMapper.getSummaryTotal(param, authPO, dp))
771
224
  .orElse(new XxxVO());
772
225
  return new ReportBaseTotalVO<XxxVO>()
773
226
  .setResultPage(PageVO.of(list))
@@ -775,336 +228,177 @@ public ReportBaseTotalVO<XxxVO> pageWithTotal(XxxPageParam param) {
775
228
  }
776
229
  ```
777
230
 
778
- ---
779
-
780
- ## 12. 报表 Service 模式(含数据权限)
781
-
782
- 报表模块 Service 直接作为 `@Service` 类(**无接口**),标准模式如下:
783
-
784
- ```java
785
- @Slf4j
786
- @Service
787
- public class ReportXxxService {
788
-
789
- @Autowired
790
- private ReportXxxMapper reportXxxMapper;
791
- @Autowired
792
- private MgrAuthV2Api mgrAuthApi;
793
- @Autowired
794
- private ReportDataPermissionService reportDataPermissionService;
795
- @Resource(name = "yunshitangTaskExecutor")
796
- private AsyncTaskExecutor asyncTaskExecutor;
797
-
798
- /**
799
- * 分页查询(含合计行)
800
- */
801
- public ReportBaseTotalVO<XxxVO> pageXxx(XxxParam param) {
802
- long start = System.currentTimeMillis();
803
- MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
804
- ReportDataPermissionParam dataPermission = reportDataPermissionService.getDataPermission(authPO);
805
-
806
- if (Objects.nonNull(param.getPage())) {
807
- PageMethod.startPage(param.getPage());
808
- }
809
- List<XxxVO> list = reportXxxMapper.pageXxx(param, authPO, dataPermission);
810
- XxxVO totalLine = Optional.ofNullable(reportXxxMapper.sumXxx(param, authPO, dataPermission))
811
- .orElse(new XxxVO());
812
- log.info("pageXxx耗时:{}", System.currentTimeMillis() - start);
813
- return new ReportBaseTotalVO<XxxVO>()
814
- .setResultPage(PageVO.of(list))
815
- .setTotalLine(totalLine);
816
- }
817
- }
818
- ```
819
-
820
- **报表 Service 关键规则**:
821
- - 通过 `mgrAuthApi.getUserAuthPO()` 获取当前用户权限
822
- - 通过 `reportDataPermissionService.getDataPermission(authPO)` 获取数据权限
823
- - 线程池名称为 `yunshitangTaskExecutor`(用 `@Resource(name = "yunshitangTaskExecutor")`)
824
- - 性能日志:`log.info("方法名耗时:{}", System.currentTimeMillis() - start)`
231
+ **分页关键规则**:
232
+ 1. `PageMethod.startPage(param.getPage())` 传 PageDTO 对象,紧接查询前调用
233
+ 2. startPage 与查询之间不能插入其他查询
234
+ 3. Mapper 方法返回 List 即可,PageHelper 自动转换
825
235
 
826
236
  ---
827
237
 
828
- ## 13. 并发处理
238
+ ## 事务管理
829
239
 
830
- ### CompletableFuture 并行查询(报表场景)
240
+ ### 多表操作必须加事务
831
241
 
832
242
  ```java
833
- @Resource(name = "yunshitangTaskExecutor")
834
- private AsyncTaskExecutor asyncTaskExecutor;
835
-
836
- public ReportXxxVO getXxx(XxxParam param) {
837
- long start = System.currentTimeMillis();
838
- MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
839
- ReportDataPermissionParam dataPermission = reportDataPermissionService.getDataPermission(authPO);
840
-
841
- // 并发查询多维度数据
842
- CompletableFuture<List<TypeA>> futureA = CompletableFuture
843
- .supplyAsync(() -> reportXxxMapper.getTypeA(param, authPO, dataPermission), asyncTaskExecutor);
844
- CompletableFuture<List<TypeB>> futureB = CompletableFuture
845
- .supplyAsync(() -> reportXxxMapper.getTypeB(param, authPO, dataPermission), asyncTaskExecutor);
846
- CompletableFuture<TypeC> futureC = CompletableFuture
847
- .supplyAsync(() -> reportXxxMapper.getTotal(param, authPO, dataPermission), asyncTaskExecutor);
848
-
849
- // 等待所有完成
850
- CompletableFuture.allOf(futureA, futureB, futureC).join();
851
-
852
- log.info("getXxx耗时:{}", System.currentTimeMillis() - start);
853
- return new ReportXxxVO(futureC.join(), futureA.join(), futureB.join());
243
+ @Transactional(rollbackFor = Exception.class)
244
+ public void createOrderWithStock(OrderDTO dto) {
245
+ orderMapper.insert(order);
246
+ orderDetailMapper.insert(details);
247
+ stockMapper.deduct(dto.getStockId(), dto.getQuantity());
854
248
  }
855
249
  ```
856
250
 
857
- ### Redisson 分布式锁(导入/并发写操作)
251
+ ### Self 自注入(同类事务调用)
858
252
 
859
253
  ```java
860
- @Resource
861
- private RedissonClient redissonClient;
254
+ @Slf4j
255
+ @Service
256
+ public class OrderPlaceBusiness {
257
+ @Autowired @Lazy
258
+ private OrderPlaceBusiness self; // 自注入,触发 AOP 代理
862
259
 
863
- public void importExcel(MultipartFile file) {
864
- RLock lock = redissonClient.getLock("import:lock:" + TenantContextHolder.getTenantId());
865
- if (!lock.tryLock(5, 60, TimeUnit.SECONDS)) {
866
- throw new LeException("正在处理中,请稍后再试");
260
+ public void doSave(OrderSavePO po) {
261
+ self.save(po, false, false); // 通过 self 调用,@Transactional 生效
867
262
  }
868
- try {
869
- // 业务逻辑
870
- doImport(file);
871
- } finally {
872
- // 必须在 finally 中释放锁
873
- if (lock.isLocked() && lock.isHeldByCurrentThread()) {
874
- lock.unlock();
875
- }
263
+
264
+ @Transactional(rollbackFor = Exception.class)
265
+ public void save(OrderSavePO po, boolean orderExists, boolean removeDetails) {
266
+ // 多表操作...
876
267
  }
877
268
  }
878
269
  ```
879
270
 
880
- ### 异步执行(不等待结果)
881
-
882
- ```java
883
- CompletableFuture.runAsync(() -> {
884
- // 异步操作(如发送通知、记录日志等)
885
- }, asyncTaskExecutor);
886
- ```
271
+ **规则**:同类方法互调,被调用方有 `@Transactional` -> 必须 `self.xxx()` 而非 `this.xxx()`
887
272
 
888
273
  ---
889
274
 
890
- ## 13. 代码质量要点
891
-
892
- ### 空指针防护
275
+ ## 报表 Service 模式
893
276
 
894
277
  ```java
895
- // ❌ 错误:selectOne 返回值未判空
896
- XxxEntity entity = xxxMapper.selectOne(query);
897
- entity.getName(); // 可能 NPE
898
-
899
- // 正确:ObjectUtil 判空
900
- XxxEntity entity = xxxMapper.selectOne(query);
901
- if (ObjectUtil.isNull(entity)) {
902
- throw new LeException("数据不存在");
903
- }
904
-
905
- // ✅ 推荐:Optional 链式处理
906
- XxxEntity entity = Optional.ofNullable(xxxMapper.selectById(id))
907
- .orElseThrow(() -> new LeException("数据不存在"));
908
- ```
909
-
910
- ### 返回值兜底
911
-
912
- ```java
913
- // ❌ 错误:返回 null 导致前端 NPE
914
- public List<XxxVO> listByParam(XxxDTO dto) {
915
- return xxxMapper.selectList(buildWrapper(dto)); // 可能返回 null
916
- }
278
+ @Slf4j
279
+ @Service
280
+ public class ReportXxxService { // 无接口,直接 @Service 类
281
+ @Autowired private ReportXxxMapper reportXxxMapper;
282
+ @Autowired private MgrAuthV2Api mgrAuthApi;
283
+ @Autowired private ReportDataPermissionService reportDataPermissionService;
284
+ @Resource(name = "yunshitangTaskExecutor")
285
+ private AsyncTaskExecutor asyncTaskExecutor;
917
286
 
918
- // 正确:空集合兜底
919
- public List<XxxVO> listByParam(XxxDTO dto) {
920
- List<XxxEntity> list = xxxMapper.selectList(buildWrapper(dto));
921
- if (CollUtil.isEmpty(list)) {
922
- return Collections.emptyList();
287
+ public ReportBaseTotalVO<XxxVO> pageXxx(XxxParam param) {
288
+ long start = System.currentTimeMillis();
289
+ MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
290
+ ReportDataPermissionParam dp = reportDataPermissionService.getDataPermission(authPO);
291
+ // ... 查询 + 合计行
292
+ log.info("pageXxx耗时:{}", System.currentTimeMillis() - start);
293
+ return result;
923
294
  }
924
- return BeanUtil.copyToList(list, XxxVO.class);
925
295
  }
926
296
  ```
927
297
 
928
- ### 集合参数防御
298
+ **关键点**:`mgrAuthApi.getUserAuthPO()` 获取权限、`reportDataPermissionService.getDataPermission()` 获取数据权限、线程池 `yunshitangTaskExecutor`
929
299
 
930
- ```java
931
- // ❌ 错误:空集合导致 SQL 异常 WHERE id IN ()
932
- public List<XxxVO> selectByIds(List<Long> ids) {
933
- return xxxMapper.selectByIds(ids);
934
- }
300
+ ---
935
301
 
936
- // ✅ 正确:集合判空提前返回
937
- public List<XxxVO> selectByIds(List<Long> ids) {
938
- if (CollUtil.isEmpty(ids)) {
939
- return Collections.emptyList();
940
- }
941
- return BeanUtil.copyToList(xxxMapper.selectBatchIds(ids), XxxVO.class);
942
- }
943
- ```
302
+ ## 并发处理
944
303
 
945
- ### 级联调用防护
304
+ ### CompletableFuture 并行查询
946
305
 
947
306
  ```java
948
- // 错误:级联调用易产生 NPE
949
- String city = user.getAddress().getCity().getName();
950
-
951
- // 正确:使用 Optional 链式处理
952
- String city = Optional.ofNullable(user)
953
- .map(User::getAddress)
954
- .map(Address::getCity)
955
- .map(City::getName)
956
- .orElse("未知");
307
+ @Resource(name = "yunshitangTaskExecutor")
308
+ private AsyncTaskExecutor asyncTaskExecutor;
309
+
310
+ CompletableFuture<List<A>> futureA = CompletableFuture
311
+ .supplyAsync(() -> mapper.getTypeA(param, authPO, dp), asyncTaskExecutor);
312
+ CompletableFuture<List<B>> futureB = CompletableFuture
313
+ .supplyAsync(() -> mapper.getTypeB(param, authPO, dp), asyncTaskExecutor);
314
+ CompletableFuture.allOf(futureA, futureB).join();
957
315
  ```
958
316
 
959
- ### Stream 操作规范
317
+ ### Redisson 分布式锁
960
318
 
961
319
  ```java
962
- // Java 21 使用 toList()
963
- List<Long> ids = list.stream()
964
- .map(XxxVO::getId)
965
- .distinct()
966
- .toList();
967
-
968
- // Map(注意 key 重复问题)
969
- Map<Long, XxxVO> map = list.stream()
970
- .collect(Collectors.toMap(XxxVO::getId, Function.identity()));
971
-
972
- // 分组
973
- Map<Integer, List<XxxVO>> grouped = list.stream()
974
- .collect(Collectors.groupingBy(XxxVO::getStatus));
975
-
976
- // 过滤空元素
977
- list.stream()
978
- .filter(Objects::nonNull)
979
- .collect(Collectors.toList());
320
+ RLock lock = redissonClient.getLock("import:lock:" + TenantContextHolder.getTenantId());
321
+ if (!lock.tryLock(5, 60, TimeUnit.SECONDS)) {
322
+ throw new LeException("正在处理中,请稍后再试");
323
+ }
324
+ try {
325
+ doImport(file);
326
+ } finally {
327
+ if (lock.isLocked() && lock.isHeldByCurrentThread()) {
328
+ lock.unlock();
329
+ }
330
+ }
980
331
  ```
981
332
 
982
333
  ---
983
334
 
984
- ## 14. 分组校验定义
985
-
986
- ```java
987
- // InsertGroup.java
988
- public interface InsertGroup {}
335
+ ## 建表 SQL 模板
989
336
 
990
- // UpdateGroup.java
991
- public interface UpdateGroup {}
337
+ ```sql
338
+ CREATE TABLE `xxx_table` (
339
+ `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
340
+ `name` VARCHAR(100) NOT NULL COMMENT '名称',
341
+ `status` TINYINT(1) DEFAULT 1 COMMENT '状态(0停用 1启用)',
342
+ `del_flag` TINYINT(1) DEFAULT 2 COMMENT '删除标识(1删除 2正常)',
343
+ `revision` INT DEFAULT 0 COMMENT '乐观锁版本号',
344
+ `crby` VARCHAR(64) DEFAULT NULL COMMENT '创建人',
345
+ `crtime` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
346
+ `upby` VARCHAR(64) DEFAULT NULL COMMENT '更新人',
347
+ `uptime` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
348
+ PRIMARY KEY (`id`),
349
+ KEY `idx_status` (`status`),
350
+ KEY `idx_crtime` (`crtime`),
351
+ KEY `idx_del_flag` (`del_flag`)
352
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='XXX表';
353
+ -- 无需 tenant_id(双库物理隔离)
992
354
  ```
993
355
 
994
356
  ---
995
357
 
996
- ## 15. 常见错误速查
997
-
998
- ### 不要做
358
+ ## 禁止项速查
999
359
 
1000
360
  ```java
1001
- // 错误 1: Service 层注入 DAO
1002
- @Resource
1003
- private XxxDao xxxDao; // leniu 没有 DAO 层!
1004
-
1005
- // 错误 2: 使用错误的包名
1006
- package org.dromara.xxx; // 必须是 net.xnzn.*
1007
-
1008
- // 错误 3: 使用 javax.validation(JDK 21 应用 jakarta.validation)
1009
- import javax.validation.Valid; // 错误
1010
-
1011
- // 错误 4: 使用 AddGroup/EditGroup(leniu 使用 InsertGroup/UpdateGroup)
1012
- @Validated(AddGroup.class) // 应该用 InsertGroup.class
1013
-
1014
- // 错误 5: 使用错误的审计字段
1015
- private String createBy; // 应该用 crby
1016
-
1017
- // 错误 6: 使用错误的逻辑删除值
1018
- entity.setDelFlag(0); // leniu 中 0=错误,1=删除,2=正常
1019
-
1020
- // 错误 7: Mapper XML 放在 resources/mapper 目录
1021
- // src/main/resources/mapper/XxxMapper.xml // 错误位置
1022
-
1023
- // 错误 8: 使用错误的异常类
1024
- throw new ServiceException("错误"); // 应该用 LeException
1025
-
1026
- // 错误 9: 多表操作不加事务
1027
- public void multiTableOp() { ... } // 应该加 @Transactional(rollbackFor = Exception.class)
1028
-
1029
- // 错误 10: 返回 null(应返回空集合)
1030
- return null; // 应返回 Collections.emptyList()
1031
- ```
1032
-
1033
- ### 正确做法
1034
-
1035
- ```java
1036
- // 正确 1: 直接在 Service 中注入 Mapper
1037
- @Resource
1038
- private XxxMapper xxxMapper;
1039
-
1040
- // 正确 2: 使用正确的包名
1041
- package net.xnzn.core.xxx;
1042
-
1043
- // 正确 3: 使用 Jakarta Validation
1044
- import jakarta.validation.Valid;
1045
-
1046
- // 正确 4: 使用正确的分组
1047
- @Validated(InsertGroup.class)
1048
-
1049
- // 正确 5: 使用 leniu 审计字段
1050
- private String crby;
1051
-
1052
- // 正确 6: 使用正确的逻辑删除值
1053
- entity.setDelFlag(2); // 2=正常
1054
-
1055
- // 正确 7: Mapper XML 与 Java 同目录
1056
- // src/main/java/net/xnzn/core/xxx/mapper/XxxMapper.xml
1057
-
1058
- // 正确 8: 使用 LeException
1059
- throw new LeException("错误");
1060
-
1061
- // 正确 9: 多表操作加事务
1062
- @Transactional(rollbackFor = Exception.class)
1063
- public void multiTableOp() { ... }
1064
-
1065
- // 正确 10: 返回空集合
1066
- return Collections.emptyList();
361
+ // ---- 错误 ---- | ---- 正确 ----
362
+ package org.dromara.xxx; // -> net.xnzn.core.xxx
363
+ import javax.validation.Valid; // -> jakarta.validation.Valid
364
+ @Validated(AddGroup.class) // -> InsertGroup.class
365
+ private String createBy; // -> crby
366
+ entity.setDelFlag(0); // -> setDelFlag(2) 表示正常
367
+ throw new ServiceException("..."); // -> throw new LeException("...")
368
+ MapstructUtils.convert(src, Dst.class); // -> BeanUtil.copyProperties(src, Dst.class)
369
+ extends ServiceImpl<XxxMapper, Xxx> // -> implements XxxService(不继承)
370
+ @Resource private XxxDao xxxDao; // -> @Resource private XxxMapper xxxMapper
371
+ // XML resources/mapper/ // -> 与 Java 同目录
372
+ return null; // -> return Collections.emptyList()
1067
373
  ```
1068
374
 
1069
375
  ---
1070
376
 
1071
- ## 检查清单
1072
-
1073
- 生成代码前必须检查:
1074
-
1075
- - [ ] **包名是否是 `net.xnzn.core.*`**?
1076
- - [ ] **Service 是否只实现接口,不继承任何基类**?
1077
- - [ ] **Service 是否直接注入 Mapper(无 DAO 层)**?
1078
- - [ ] **查询条件是否在 Service 层构建**?
1079
- - [ ] **Entity 是否包含审计字段**(crby/crtime/upby/uptime)?
1080
- - [ ] **delFlag 是否正确使用**(1=删除,2=正常)?
1081
- - [ ] **是否使用 `BeanUtil.copyProperties()` 转换对象**?
1082
- - [ ] **是否使用 Jakarta Validation**(jakarta.validation)?
1083
- - [ ] **是否使用正确的分组**(InsertGroup/UpdateGroup)?
1084
- - [ ] **Mapper XML 是否与 Java 文件同目录**?
1085
- - [ ] **异常是否使用 `LeException`**?
1086
- - [ ] **Controller 是否添加认证注解**(@RequiresAuthentication/@RequiresGuest)?
1087
- - [ ] **请求是否使用 `LeRequest<T>` 封装**?
1088
- - [ ] **响应是否使用 `Page<T>` 或 `LeResponse<T>`**?
1089
- - [ ] **多表操作是否添加 `@Transactional(rollbackFor = Exception.class)`**?
1090
- - [ ] **查询方法是否未添加不必要的事务**?
1091
- - [ ] **返回 List 是否有空集合兜底**?
1092
- - [ ] **集合参数是否有判空保护**?
1093
- - [ ] **selectOne/selectById 结果是否有判空处理**?
1094
- - [ ] **所有代码注释是否使用中文**?
377
+ ## 生成前检查清单
378
+
379
+ - [ ] 包名 `net.xnzn.core.*`
380
+ - [ ] Service 只实现接口,不继承基类
381
+ - [ ] Service 直接注入 Mapper(无 DAO)
382
+ - [ ] 审计字段 crby/crtime/upby/uptime
383
+ - [ ] delFlag: 1=删除, 2=正常
384
+ - [ ] `BeanUtil.copyProperties()` 转换对象
385
+ - [ ] `jakarta.validation.*` 校验
386
+ - [ ] `InsertGroup` / `UpdateGroup` 分组
387
+ - [ ] Mapper XML 与 Java 同目录
388
+ - [ ] `LeException` 抛异常
389
+ - [ ] `@RequiresAuthentication` / `@RequiresGuest` 认证
390
+ - [ ] `LeRequest<T>` 请求封装
391
+ - [ ] 多表操作加 `@Transactional(rollbackFor = Exception.class)`
392
+ - [ ] 返回 List 有空集合兜底
393
+ - [ ] selectOne/selectById 结果判空
1095
394
 
1096
395
  ---
1097
396
 
1098
- ## 参考代码位置
397
+ ## 参考代码
1099
398
 
1100
399
  | 类型 | 路径 |
1101
400
  |------|------|
1102
- | Controller 示例 | `core-attendance/.../controller/AttendanceLeaveInfoController.java` |
1103
- | Service 示例 | `core-attendance/.../service/impl/AttendanceLeaveInfoServiceImpl.java` |
1104
- | Mapper 示例 | `core-attendance/.../mapper/AttendanceLeaveInfoMapper.java` |
1105
- | Entity 示例 | `core-attendance/.../model/AttendanceLeaveInfo.java` |
1106
-
1107
- | 项目 | 路径 |
1108
- |------|------|
1109
- | **leniu-tengyun-core** | `/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core` |
1110
- | **leniu-yunshitang** | `/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang` |
401
+ | Controller | `core-attendance/.../controller/AttendanceLeaveInfoController.java` |
402
+ | Service | `core-attendance/.../service/impl/AttendanceLeaveInfoServiceImpl.java` |
403
+ | Mapper | `core-attendance/.../mapper/AttendanceLeaveInfoMapper.java` |
404
+ | Entity | `core-attendance/.../model/AttendanceLeaveInfo.java` |