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.
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +79 -32
- package/.claude/skills/api-development/SKILL.md +83 -377
- package/.claude/skills/architecture-design/SKILL.md +138 -632
- package/.claude/skills/backend-annotations/SKILL.md +134 -506
- package/.claude/skills/banana-image/SKILL.md +10 -3
- package/.claude/skills/brainstorm/SKILL.md +103 -535
- package/.claude/skills/bug-detective/SKILL.md +147 -1097
- package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
- package/.claude/skills/code-patterns/SKILL.md +116 -426
- package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.claude/skills/crud-development/SKILL.md +64 -304
- package/.claude/skills/data-permission/SKILL.md +105 -412
- package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.claude/skills/file-oss-management/SKILL.md +106 -714
- package/.claude/skills/file-oss-management/references/entities.md +105 -0
- package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
- package/.claude/skills/leniu-api-development/SKILL.md +142 -626
- package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
- package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
- package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
- package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
- package/.claude/skills/leniu-java-export/SKILL.md +94 -379
- package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
- package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +111 -365
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
- package/.claude/skills/mysql-debug/SKILL.md +364 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
- package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
- package/.claude/skills/openspec-explore/SKILL.md +10 -1
- package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
- package/.claude/skills/openspec-new-change/SKILL.md +9 -1
- package/.claude/skills/openspec-onboard/SKILL.md +15 -130
- package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
- package/.claude/skills/performance-doctor/SKILL.md +110 -434
- package/.claude/skills/redis-cache/SKILL.md +89 -595
- package/.claude/skills/redis-cache/references/listeners.md +23 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
- package/.claude/skills/security-guard/SKILL.md +137 -532
- package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
- package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.claude/skills/sms-mail/SKILL.md +116 -574
- package/.claude/skills/sms-mail/references/mail-config.md +88 -0
- package/.claude/skills/sms-mail/references/sms-config.md +74 -0
- package/.claude/skills/social-login/SKILL.md +112 -514
- package/.claude/skills/social-login/references/provider-configs.md +118 -0
- package/.claude/skills/tenant-management/SKILL.md +129 -444
- package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.claude/skills/test-development/SKILL.md +86 -540
- package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
- package/.claude/skills/utils-toolkit/SKILL.md +52 -305
- package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.claude/skills/websocket-sse/SKILL.md +105 -550
- package/.claude/skills/workflow-engine/SKILL.md +147 -502
- package/.codex/skills/add-skill/SKILL.md +79 -32
- package/.codex/skills/api-development/SKILL.md +172 -599
- package/.codex/skills/architecture-design/SKILL.md +138 -504
- package/.codex/skills/backend-annotations/SKILL.md +134 -496
- package/.codex/skills/banana-image/SKILL.md +10 -3
- package/.codex/skills/brainstorm/SKILL.md +103 -535
- package/.codex/skills/bug-detective/SKILL.md +147 -1097
- package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
- package/.codex/skills/code-patterns/SKILL.md +120 -282
- package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.codex/skills/crud-development/SKILL.md +64 -292
- package/.codex/skills/data-permission/SKILL.md +108 -407
- package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.codex/skills/database-ops/SKILL.md +8 -154
- package/.codex/skills/error-handler/SKILL.md +10 -0
- package/.codex/skills/file-oss-management/SKILL.md +106 -714
- package/.codex/skills/file-oss-management/references/entities.md +105 -0
- package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
- package/.codex/skills/git-workflow/SKILL.md +27 -5
- package/.codex/skills/leniu-api-development/SKILL.md +142 -626
- package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
- package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
- package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
- package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
- package/.codex/skills/leniu-java-export/SKILL.md +94 -379
- package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
- package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +111 -365
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
- package/.codex/skills/mysql-debug/SKILL.md +364 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
- package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
- package/.codex/skills/openspec-explore/SKILL.md +10 -1
- package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
- package/.codex/skills/openspec-new-change/SKILL.md +9 -1
- package/.codex/skills/openspec-onboard/SKILL.md +15 -130
- package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
- package/.codex/skills/performance-doctor/SKILL.md +110 -434
- package/.codex/skills/project-navigator/SKILL.md +20 -1
- package/.codex/skills/redis-cache/SKILL.md +93 -589
- package/.codex/skills/redis-cache/references/listeners.md +23 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
- package/.codex/skills/security-guard/SKILL.md +141 -527
- package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
- package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.codex/skills/sms-mail/SKILL.md +116 -574
- package/.codex/skills/sms-mail/references/mail-config.md +88 -0
- package/.codex/skills/sms-mail/references/sms-config.md +74 -0
- package/.codex/skills/social-login/SKILL.md +112 -514
- package/.codex/skills/social-login/references/provider-configs.md +118 -0
- package/.codex/skills/store-pc/SKILL.md +258 -383
- package/.codex/skills/tenant-management/SKILL.md +129 -444
- package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.codex/skills/test-development/SKILL.md +86 -540
- package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
- package/.codex/skills/ui-pc/SKILL.md +350 -387
- package/.codex/skills/utils-toolkit/SKILL.md +52 -283
- package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.codex/skills/websocket-sse/SKILL.md +105 -550
- package/.codex/skills/workflow-engine/SKILL.md +147 -502
- package/.cursor/hooks.json +3 -3
- package/.cursor/skills/add-skill/SKILL.md +79 -32
- package/.cursor/skills/api-development/SKILL.md +83 -377
- package/.cursor/skills/architecture-design/SKILL.md +138 -632
- package/.cursor/skills/backend-annotations/SKILL.md +134 -506
- package/.cursor/skills/banana-image/SKILL.md +10 -3
- package/.cursor/skills/brainstorm/SKILL.md +103 -535
- package/.cursor/skills/bug-detective/SKILL.md +147 -1097
- package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
- package/.cursor/skills/code-patterns/SKILL.md +116 -426
- package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.cursor/skills/crud-development/SKILL.md +64 -304
- package/.cursor/skills/data-permission/SKILL.md +105 -412
- package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.cursor/skills/file-oss-management/SKILL.md +106 -714
- package/.cursor/skills/file-oss-management/references/entities.md +105 -0
- package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
- package/.cursor/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
- package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
- package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
- package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
- package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
- package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
- package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.cursor/skills/leniu-report-customization/SKILL.md +111 -365
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
- package/.cursor/skills/mysql-debug/SKILL.md +364 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
- package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-explore/SKILL.md +10 -1
- package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
- package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
- package/.cursor/skills/performance-doctor/SKILL.md +110 -434
- package/.cursor/skills/redis-cache/SKILL.md +89 -595
- package/.cursor/skills/redis-cache/references/listeners.md +23 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
- package/.cursor/skills/security-guard/SKILL.md +137 -532
- package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
- package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.cursor/skills/sms-mail/SKILL.md +116 -574
- package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
- package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
- package/.cursor/skills/social-login/SKILL.md +112 -514
- package/.cursor/skills/social-login/references/provider-configs.md +118 -0
- package/.cursor/skills/tenant-management/SKILL.md +129 -444
- package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.cursor/skills/test-development/SKILL.md +86 -540
- package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
- package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.cursor/skills/websocket-sse/SKILL.md +105 -550
- package/.cursor/skills/workflow-engine/SKILL.md +147 -502
- 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
|
|
5
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
25
|
+
## 项目路径
|
|
28
26
|
|
|
29
27
|
| 项目 | 路径 |
|
|
30
28
|
|------|------|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
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
|
-
|
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
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
|
-
##
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
private String name;
|
|
85
|
+
### Controller 路由前缀
|
|
225
86
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
##
|
|
96
|
+
## 核心代码片段
|
|
97
|
+
|
|
98
|
+
### Entity 审计字段
|
|
245
99
|
|
|
246
100
|
```java
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
###
|
|
677
|
-
|
|
678
|
-
当 Service/Business 中的方法 A 需要调用同类的带 `@Transactional` 方法 B 时,必须通过 `self` 代理调用,否则事务不生效:
|
|
160
|
+
### 对象转换与空值防护
|
|
679
161
|
|
|
680
162
|
```java
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
163
|
+
// 新增
|
|
164
|
+
XxxEntity entity = BeanUtil.copyProperties(dto, XxxEntity.class);
|
|
165
|
+
entity.setDelFlag(2);
|
|
166
|
+
xxxMapper.insert(entity);
|
|
684
167
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
183
|
+
## 分页查询
|
|
715
184
|
|
|
716
|
-
###
|
|
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
|
|
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,
|
|
770
|
-
XxxVO totalLine = Optional.ofNullable(xxxMapper.getSummaryTotal(param, authPO,
|
|
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
|
-
|
|
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
|
-
##
|
|
238
|
+
## 事务管理
|
|
829
239
|
|
|
830
|
-
###
|
|
240
|
+
### 多表操作必须加事务
|
|
831
241
|
|
|
832
242
|
```java
|
|
833
|
-
@
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
-
###
|
|
251
|
+
### Self 自注入(同类事务调用)
|
|
858
252
|
|
|
859
253
|
```java
|
|
860
|
-
@
|
|
861
|
-
|
|
254
|
+
@Slf4j
|
|
255
|
+
@Service
|
|
256
|
+
public class OrderPlaceBusiness {
|
|
257
|
+
@Autowired @Lazy
|
|
258
|
+
private OrderPlaceBusiness self; // 自注入,触发 AOP 代理
|
|
862
259
|
|
|
863
|
-
public void
|
|
864
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
##
|
|
891
|
-
|
|
892
|
-
### 空指针防护
|
|
275
|
+
## 报表 Service 模式
|
|
893
276
|
|
|
894
277
|
```java
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
-
|
|
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
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
.
|
|
955
|
-
|
|
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
|
-
###
|
|
317
|
+
### Redisson 分布式锁
|
|
960
318
|
|
|
961
319
|
```java
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
##
|
|
985
|
-
|
|
986
|
-
```java
|
|
987
|
-
// InsertGroup.java
|
|
988
|
-
public interface InsertGroup {}
|
|
335
|
+
## 建表 SQL 模板
|
|
989
336
|
|
|
990
|
-
|
|
991
|
-
|
|
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
|
-
##
|
|
997
|
-
|
|
998
|
-
### 不要做
|
|
358
|
+
## 禁止项速查
|
|
999
359
|
|
|
1000
360
|
```java
|
|
1001
|
-
// 错误
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
//
|
|
1012
|
-
|
|
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
|
-
- [ ]
|
|
1076
|
-
- [ ]
|
|
1077
|
-
- [ ]
|
|
1078
|
-
- [ ]
|
|
1079
|
-
- [ ]
|
|
1080
|
-
- [ ]
|
|
1081
|
-
- [ ]
|
|
1082
|
-
- [ ]
|
|
1083
|
-
- [ ]
|
|
1084
|
-
- [ ]
|
|
1085
|
-
- [ ]
|
|
1086
|
-
- [ ]
|
|
1087
|
-
- [ ]
|
|
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
|
|
1103
|
-
| Service
|
|
1104
|
-
| Mapper
|
|
1105
|
-
| Entity
|
|
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` |
|