ai-engineering-init 1.4.3 → 1.6.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/.cursor/skills/bug-detective/SKILL.md +19 -19
- package/.cursor/skills/project-navigator/SKILL.md +164 -258
- package/README.md +20 -236
- package/bin/index.js +437 -7
- package/package.json +7 -1
- package/scripts/build-skills.js +180 -0
- package/src/platform-map.json +56 -0
- package/src/skills/add-skill/SKILL.md +488 -0
- package/src/skills/add-todo/SKILL.md +269 -0
- package/src/skills/api-development/SKILL.md +266 -0
- package/src/skills/architecture-design/SKILL.md +262 -0
- package/src/skills/backend-annotations/SKILL.md +302 -0
- package/src/skills/banana-image/CHANGELOG.md +37 -0
- package/src/skills/banana-image/README.md +146 -0
- package/src/skills/banana-image/SKILL.md +171 -0
- package/src/skills/banana-image/assets/logo.png +0 -0
- package/src/skills/banana-image/references/advanced-usage.md +189 -0
- package/src/skills/banana-image/scripts/apply_template.py +125 -0
- package/src/skills/banana-image/scripts/banana_image_exec.ts +412 -0
- package/src/skills/banana-image/scripts/batch_prep.py +82 -0
- package/src/skills/banana-image/scripts/package-lock.json +1437 -0
- package/src/skills/banana-image/scripts/package.json +18 -0
- package/src/skills/banana-image/scripts/requirements.txt +10 -0
- package/src/skills/banana-image/templates/poster.json +22 -0
- package/src/skills/banana-image/templates/product.json +17 -0
- package/src/skills/banana-image/templates/social.json +22 -0
- package/src/skills/banana-image/templates/thumbnail.json +17 -0
- package/src/skills/brainstorm/SKILL.md +216 -0
- package/src/skills/bug-detective/SKILL.md +256 -0
- package/src/skills/bug-detective/references/error-patterns.md +242 -0
- package/src/skills/check/SKILL.md +367 -0
- package/src/skills/code-patterns/SKILL.md +280 -0
- package/src/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/src/skills/codex-code-review/SKILL.md +135 -0
- package/src/skills/collaborating-with-codex/SKILL.md +174 -0
- package/src/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
- package/src/skills/collaborating-with-gemini/SKILL.md +194 -0
- package/src/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
- package/src/skills/crud/SKILL.md +265 -0
- package/src/skills/crud-development/SKILL.md +409 -0
- package/src/skills/data-permission/SKILL.md +292 -0
- package/src/skills/data-permission/references/custom-data-scope.md +90 -0
- package/src/skills/database-ops/SKILL.md +407 -0
- package/src/skills/dev/SKILL.md +187 -0
- package/src/skills/error-handler/SKILL.md +371 -0
- package/src/skills/file-oss-management/SKILL.md +255 -0
- package/src/skills/file-oss-management/references/entities.md +105 -0
- package/src/skills/file-oss-management/references/service-impl.md +104 -0
- package/src/skills/git-workflow/SKILL.md +397 -0
- package/src/skills/init-docs/SKILL.md +194 -0
- package/src/skills/json-serialization/SKILL.md +357 -0
- package/src/skills/leniu-api-development/SKILL.md +319 -0
- package/src/skills/leniu-api-development/references/real-examples.md +273 -0
- package/src/skills/leniu-architecture-design/SKILL.md +383 -0
- package/src/skills/leniu-backend-annotations/SKILL.md +277 -0
- package/src/skills/leniu-brainstorm/SKILL.md +242 -0
- package/src/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/src/skills/leniu-code-patterns/SKILL.md +411 -0
- package/src/skills/leniu-crud-development/SKILL.md +404 -0
- package/src/skills/leniu-crud-development/references/templates.md +597 -0
- package/src/skills/leniu-customization-location/SKILL.md +410 -0
- package/src/skills/leniu-data-permission/SKILL.md +341 -0
- package/src/skills/leniu-database-ops/SKILL.md +426 -0
- package/src/skills/leniu-error-handler/SKILL.md +462 -0
- package/src/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/src/skills/leniu-java-code-style/SKILL.md +510 -0
- package/src/skills/leniu-java-concurrent/SKILL.md +400 -0
- package/src/skills/leniu-java-entity/SKILL.md +237 -0
- package/src/skills/leniu-java-entity/references/templates.md +237 -0
- package/src/skills/leniu-java-export/SKILL.md +570 -0
- package/src/skills/leniu-java-logging/SKILL.md +229 -0
- package/src/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/src/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/src/skills/leniu-java-mq/SKILL.md +338 -0
- package/src/skills/leniu-java-mybatis/SKILL.md +267 -0
- package/src/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/src/skills/leniu-java-report-query-param/SKILL.md +291 -0
- package/src/skills/leniu-java-task/SKILL.md +367 -0
- package/src/skills/leniu-java-total-line/SKILL.md +196 -0
- package/src/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
- package/src/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
- package/src/skills/leniu-mealtime/SKILL.md +215 -0
- package/src/skills/leniu-redis-cache/SKILL.md +331 -0
- package/src/skills/leniu-report-customization/SKILL.md +335 -0
- package/src/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/src/skills/leniu-report-standard-customization/SKILL.md +328 -0
- package/src/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/src/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/src/skills/leniu-security-guard/SKILL.md +306 -0
- package/src/skills/leniu-utils-toolkit/SKILL.md +380 -0
- package/src/skills/mysql-debug/SKILL.md +364 -0
- package/src/skills/next/SKILL.md +137 -0
- package/src/skills/openspec-apply-change/SKILL.md +165 -0
- package/src/skills/openspec-archive-change/SKILL.md +122 -0
- package/src/skills/openspec-bulk-archive-change/SKILL.md +254 -0
- package/src/skills/openspec-continue-change/SKILL.md +126 -0
- package/src/skills/openspec-explore/SKILL.md +299 -0
- package/src/skills/openspec-ff-change/SKILL.md +109 -0
- package/src/skills/openspec-new-change/SKILL.md +82 -0
- package/src/skills/openspec-onboard/SKILL.md +414 -0
- package/src/skills/openspec-sync-specs/SKILL.md +146 -0
- package/src/skills/openspec-verify-change/SKILL.md +176 -0
- package/src/skills/performance-doctor/SKILL.md +303 -0
- package/src/skills/progress/SKILL.md +193 -0
- package/src/skills/project-navigator/SKILL.md +211 -0
- package/src/skills/redis-cache/SKILL.md +333 -0
- package/src/skills/redis-cache/references/listeners.md +23 -0
- package/src/skills/scheduled-jobs/SKILL.md +314 -0
- package/src/skills/security-guard/SKILL.md +353 -0
- package/src/skills/security-guard/references/encrypt-config.md +103 -0
- package/src/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/src/skills/sms-mail/SKILL.md +308 -0
- package/src/skills/sms-mail/references/mail-config.md +88 -0
- package/src/skills/sms-mail/references/sms-config.md +74 -0
- package/src/skills/social-login/SKILL.md +266 -0
- package/src/skills/social-login/references/provider-configs.md +118 -0
- package/src/skills/start/SKILL.md +154 -0
- package/src/skills/store-pc/SKILL.md +366 -0
- package/src/skills/sync/SKILL.md +149 -0
- package/src/skills/task-tracker/SKILL.md +307 -0
- package/src/skills/tech-decision/SKILL.md +393 -0
- package/src/skills/tenant-management/SKILL.md +288 -0
- package/src/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/src/skills/test-development/SKILL.md +301 -0
- package/src/skills/test-development/references/parameterized-examples.md +119 -0
- package/src/skills/ui-pc/SKILL.md +438 -0
- package/src/skills/update-status/SKILL.md +159 -0
- package/src/skills/utils-toolkit/SKILL.md +362 -0
- package/src/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/src/skills/websocket-sse/SKILL.md +271 -0
- package/src/skills/workflow-engine/SKILL.md +321 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: leniu-java-concurrent
|
|
3
|
+
description: |
|
|
4
|
+
leniu-tengyun-core / leniu-yunshitang 项目并发处理规范。当编写并发代码时使用此skill,包括线程安全、异步处理、分布式锁规范。
|
|
5
|
+
|
|
6
|
+
触发场景:
|
|
7
|
+
- 使用CompletableFuture进行异步处理
|
|
8
|
+
- 配置和使用线程池(ThreadPoolExecutor)
|
|
9
|
+
- 处理并发安全问题(Redis分布式锁、数据库唯一索引)
|
|
10
|
+
- 实现异步通知或异步日志(不等待结果)
|
|
11
|
+
|
|
12
|
+
适用项目:
|
|
13
|
+
- leniu-tengyun-core:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core
|
|
14
|
+
- leniu-yunshitang:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang
|
|
15
|
+
|
|
16
|
+
触发词:并发、CompletableFuture、线程池、ThreadPool、并发安全、异步处理、synchronized
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# leniu-tengyun-core 并发处理规范
|
|
20
|
+
|
|
21
|
+
## 项目特征
|
|
22
|
+
|
|
23
|
+
| 特征 | 说明 |
|
|
24
|
+
|------|------|
|
|
25
|
+
| 包名 | `net.xnzn.*` |
|
|
26
|
+
| 异常类 | `LeException`(禁止使用 ServiceException) |
|
|
27
|
+
| 工具类 | Hutool(CollUtil、StrUtil 等) |
|
|
28
|
+
| 分布式锁 | `RedisUtil.getLock()` / `RedisUtil.setNx()` |
|
|
29
|
+
| JDK | 21(支持虚拟线程,推荐使用标准线程池) |
|
|
30
|
+
|
|
31
|
+
## 并行查询
|
|
32
|
+
|
|
33
|
+
### 使用CompletableFuture
|
|
34
|
+
|
|
35
|
+
```java
|
|
36
|
+
// 并行查询多个数据源
|
|
37
|
+
public XxxVO getData(Long id) {
|
|
38
|
+
// 创建异步任务
|
|
39
|
+
CompletableFuture<TypeA> futureA = CompletableFuture
|
|
40
|
+
.supplyAsync(() -> mapperA.selectById(id));
|
|
41
|
+
|
|
42
|
+
CompletableFuture<TypeB> futureB = CompletableFuture
|
|
43
|
+
.supplyAsync(() -> mapperB.selectById(id));
|
|
44
|
+
|
|
45
|
+
CompletableFuture<TypeC> futureC = CompletableFuture
|
|
46
|
+
.supplyAsync(() -> mapperC.selectById(id));
|
|
47
|
+
|
|
48
|
+
// 等待所有任务完成
|
|
49
|
+
CompletableFuture.allOf(futureA, futureB, futureC).join();
|
|
50
|
+
|
|
51
|
+
// 获取结果
|
|
52
|
+
TypeA resultA = futureA.join();
|
|
53
|
+
TypeB resultB = futureB.join();
|
|
54
|
+
TypeC resultC = futureC.join();
|
|
55
|
+
|
|
56
|
+
// 组装结果(使用 BeanUtil.copyProperties)
|
|
57
|
+
XxxVO vo = new XxxVO();
|
|
58
|
+
BeanUtil.copyProperties(resultA, vo);
|
|
59
|
+
vo.setDataB(resultB);
|
|
60
|
+
vo.setDataC(resultC);
|
|
61
|
+
|
|
62
|
+
return vo;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 指定线程池
|
|
67
|
+
|
|
68
|
+
```java
|
|
69
|
+
@Autowired
|
|
70
|
+
private Executor asyncTaskExecutor;
|
|
71
|
+
|
|
72
|
+
public XxxVO getData(Long id) {
|
|
73
|
+
// 使用指定的线程池
|
|
74
|
+
CompletableFuture<TypeA> futureA = CompletableFuture
|
|
75
|
+
.supplyAsync(() -> mapperA.selectById(id), asyncTaskExecutor);
|
|
76
|
+
|
|
77
|
+
CompletableFuture<TypeB> futureB = CompletableFuture
|
|
78
|
+
.supplyAsync(() -> mapperB.selectById(id), asyncTaskExecutor);
|
|
79
|
+
|
|
80
|
+
// 等待并获取结果
|
|
81
|
+
CompletableFuture.allOf(futureA, futureB).join();
|
|
82
|
+
|
|
83
|
+
TypeA resultA = futureA.join();
|
|
84
|
+
TypeB resultB = futureB.join();
|
|
85
|
+
|
|
86
|
+
return buildVO(resultA, resultB);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 报表模块专用线程池(yunshitangTaskExecutor)
|
|
91
|
+
|
|
92
|
+
云食堂报表模块使用专项线程池,通过 `@Resource(name=...)` 按名称注入:
|
|
93
|
+
|
|
94
|
+
```java
|
|
95
|
+
@Service
|
|
96
|
+
@Slf4j
|
|
97
|
+
public class ReportSumXxxService {
|
|
98
|
+
|
|
99
|
+
// ✅ 按名称注入报表专用线程池
|
|
100
|
+
@Resource(name = "yunshitangTaskExecutor")
|
|
101
|
+
private AsyncTaskExecutor asyncTaskExecutor;
|
|
102
|
+
|
|
103
|
+
// ✅ 报表查询标准三并发模式:COUNT + LIST + TOTAL
|
|
104
|
+
public ReportBaseTotalVO<XxxVO> pageSummary(XxxParam param) {
|
|
105
|
+
// 1. 获取认证信息和数据权限
|
|
106
|
+
MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
|
|
107
|
+
ReportDataPermissionParam dataPermission =
|
|
108
|
+
reportDataPermissionService.getDataPermission(authPO);
|
|
109
|
+
|
|
110
|
+
// 2. 三并发:count、list、total 同时执行
|
|
111
|
+
CompletableFuture<Long> countFuture = CompletableFuture.supplyAsync(
|
|
112
|
+
() -> reportMapper.listSummary_COUNT(param, authPO, dataPermission),
|
|
113
|
+
asyncTaskExecutor
|
|
114
|
+
);
|
|
115
|
+
CompletableFuture<List<XxxVO>> listFuture = CompletableFuture.supplyAsync(() -> {
|
|
116
|
+
PageMethod.startPage(param.getPage());
|
|
117
|
+
return reportMapper.listSummary(param, authPO, dataPermission);
|
|
118
|
+
}, asyncTaskExecutor);
|
|
119
|
+
CompletableFuture<XxxVO> totalFuture = CompletableFuture.supplyAsync(
|
|
120
|
+
() -> reportMapper.getSummaryTotal(param, authPO, dataPermission),
|
|
121
|
+
asyncTaskExecutor
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// 3. 等待所有任务完成
|
|
125
|
+
CompletableFuture.allOf(countFuture, listFuture, totalFuture).join();
|
|
126
|
+
|
|
127
|
+
// 4. 组装结果
|
|
128
|
+
PageVO<XxxVO> pageVO = PageVO.of(listFuture.join());
|
|
129
|
+
return new ReportBaseTotalVO<XxxVO>()
|
|
130
|
+
.setResultPage(pageVO)
|
|
131
|
+
.setTotalLine(totalFuture.join());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**注意**:
|
|
137
|
+
- `listSummary_COUNT` 为 Mapper 中专门的 COUNT 方法(命名规范:`listXxx_COUNT`)
|
|
138
|
+
- `PageMethod.startPage()` 必须在 LIST Future 内部调用(同线程)
|
|
139
|
+
- COUNT Future 使用 Mapper 的 COUNT 方法,不走 PageHelper
|
|
140
|
+
- TOTAL Future 查询合计行,只返回数值字段
|
|
141
|
+
|
|
142
|
+
## 异步执行
|
|
143
|
+
|
|
144
|
+
### 不等待结果
|
|
145
|
+
|
|
146
|
+
```java
|
|
147
|
+
// 异步执行,不等待结果
|
|
148
|
+
CompletableFuture.runAsync(() -> {
|
|
149
|
+
// 异步操作
|
|
150
|
+
logService.saveLog(log);
|
|
151
|
+
}, asyncTaskExecutor);
|
|
152
|
+
|
|
153
|
+
// 主线程继续执行
|
|
154
|
+
return result;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 异步执行并处理异常
|
|
158
|
+
|
|
159
|
+
```java
|
|
160
|
+
CompletableFuture.runAsync(() -> {
|
|
161
|
+
try {
|
|
162
|
+
// 异步操作
|
|
163
|
+
notificationService.send(message);
|
|
164
|
+
} catch (Exception e) {
|
|
165
|
+
log.error("异步发送通知失败", e);
|
|
166
|
+
}
|
|
167
|
+
}, asyncTaskExecutor);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 线程池配置
|
|
171
|
+
|
|
172
|
+
### 自定义线程池
|
|
173
|
+
|
|
174
|
+
```java
|
|
175
|
+
@Configuration
|
|
176
|
+
public class ThreadPoolConfig {
|
|
177
|
+
|
|
178
|
+
@Bean("asyncTaskExecutor")
|
|
179
|
+
public Executor asyncTaskExecutor() {
|
|
180
|
+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
|
181
|
+
// 核心线程数
|
|
182
|
+
executor.setCorePoolSize(10);
|
|
183
|
+
// 最大线程数
|
|
184
|
+
executor.setMaxPoolSize(20);
|
|
185
|
+
// 队列容量
|
|
186
|
+
executor.setQueueCapacity(200);
|
|
187
|
+
// 线程名称前缀
|
|
188
|
+
executor.setThreadNamePrefix("async-task-");
|
|
189
|
+
// 拒绝策略
|
|
190
|
+
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
|
191
|
+
// 等待所有任务完成后关闭线程池
|
|
192
|
+
executor.setWaitForTasksToCompleteOnShutdown(true);
|
|
193
|
+
// 等待时间
|
|
194
|
+
executor.setAwaitTerminationSeconds(60);
|
|
195
|
+
executor.initialize();
|
|
196
|
+
return executor;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 并发安全
|
|
202
|
+
|
|
203
|
+
### 使用分布式锁(Redisson)
|
|
204
|
+
|
|
205
|
+
```java
|
|
206
|
+
public void processOrder(Long orderId) {
|
|
207
|
+
String lockKey = "order:process:" + orderId;
|
|
208
|
+
|
|
209
|
+
// 获取Redisson分布式锁
|
|
210
|
+
RLock lock = RedisUtil.getLock(lockKey);
|
|
211
|
+
if (!lock.tryLock()) {
|
|
212
|
+
throw new LeException("订单正在处理中,请稍后重试");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
// 业务处理
|
|
217
|
+
doProcess(orderId);
|
|
218
|
+
} finally {
|
|
219
|
+
// 安全释放锁
|
|
220
|
+
if (lock.isHeldByCurrentThread() && lock.isLocked()) {
|
|
221
|
+
lock.unlock();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 使用Redis setNx 轻量锁
|
|
228
|
+
|
|
229
|
+
```java
|
|
230
|
+
public void processOnce(Long tenantId) {
|
|
231
|
+
String key = "task:process:" + tenantId;
|
|
232
|
+
// setNx:设置成功才继续执行,防重入
|
|
233
|
+
boolean ifFirst = RedisUtil.setNx(key, "1", 6);
|
|
234
|
+
if (!ifFirst) {
|
|
235
|
+
throw new LeException("任务正在执行中");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
doBusiness();
|
|
240
|
+
} finally {
|
|
241
|
+
RedisUtil.del(key);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 使用数据库唯一索引
|
|
247
|
+
|
|
248
|
+
```java
|
|
249
|
+
@Transactional(rollbackFor = Exception.class)
|
|
250
|
+
public void createOrder(OrderParam param) {
|
|
251
|
+
try {
|
|
252
|
+
// 插入订单(依赖唯一索引防止重复)
|
|
253
|
+
Order order = buildOrder(param);
|
|
254
|
+
orderMapper.insert(order);
|
|
255
|
+
} catch (DuplicateKeyException e) {
|
|
256
|
+
throw new LeException("订单已存在");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## 常见场景
|
|
262
|
+
|
|
263
|
+
### 场景1:并行查询多个接口
|
|
264
|
+
|
|
265
|
+
```java
|
|
266
|
+
public DashboardVO getDashboard(Long userId) {
|
|
267
|
+
// 并行查询多个数据
|
|
268
|
+
CompletableFuture<OrderSummary> orderFuture = CompletableFuture
|
|
269
|
+
.supplyAsync(() -> orderService.getSummary(userId), asyncTaskExecutor);
|
|
270
|
+
|
|
271
|
+
CompletableFuture<PaymentSummary> paymentFuture = CompletableFuture
|
|
272
|
+
.supplyAsync(() -> paymentService.getSummary(userId), asyncTaskExecutor);
|
|
273
|
+
|
|
274
|
+
CompletableFuture<InventorySummary> inventoryFuture = CompletableFuture
|
|
275
|
+
.supplyAsync(() -> inventoryService.getSummary(userId), asyncTaskExecutor);
|
|
276
|
+
|
|
277
|
+
// 等待所有查询完成
|
|
278
|
+
CompletableFuture.allOf(orderFuture, paymentFuture, inventoryFuture).join();
|
|
279
|
+
|
|
280
|
+
// 组装结果
|
|
281
|
+
DashboardVO dashboard = new DashboardVO();
|
|
282
|
+
dashboard.setOrderSummary(orderFuture.join());
|
|
283
|
+
dashboard.setPaymentSummary(paymentFuture.join());
|
|
284
|
+
dashboard.setInventorySummary(inventoryFuture.join());
|
|
285
|
+
|
|
286
|
+
return dashboard;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 场景2:批量并行处理
|
|
291
|
+
|
|
292
|
+
```java
|
|
293
|
+
public void batchProcess(List<Long> ids) {
|
|
294
|
+
// 分批处理,每批100个(使用 Hutool CollUtil)
|
|
295
|
+
int batchSize = 100;
|
|
296
|
+
List<List<Long>> batches = CollUtil.split(ids, batchSize);
|
|
297
|
+
|
|
298
|
+
// 并行处理每批数据
|
|
299
|
+
List<CompletableFuture<Void>> futures = batches.stream()
|
|
300
|
+
.map(batch -> CompletableFuture.runAsync(() -> {
|
|
301
|
+
processBatch(batch);
|
|
302
|
+
}, asyncTaskExecutor))
|
|
303
|
+
.collect(Collectors.toList());
|
|
304
|
+
|
|
305
|
+
// 等待所有批次完成
|
|
306
|
+
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|
307
|
+
|
|
308
|
+
log.info("批量处理完成,总数:{}", ids.size());
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private void processBatch(List<Long> batch) {
|
|
312
|
+
for (Long id : batch) {
|
|
313
|
+
try {
|
|
314
|
+
processOne(id);
|
|
315
|
+
} catch (Exception e) {
|
|
316
|
+
log.error("处理失败,id:{}", id, e);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 场景3:异步通知(不影响主流程)
|
|
323
|
+
|
|
324
|
+
```java
|
|
325
|
+
@Transactional(rollbackFor = Exception.class)
|
|
326
|
+
public void createOrder(OrderParam param) {
|
|
327
|
+
// 创建订单
|
|
328
|
+
Order order = buildOrder(param);
|
|
329
|
+
orderMapper.insert(order);
|
|
330
|
+
|
|
331
|
+
// 异步发送通知(不影响主流程)
|
|
332
|
+
CompletableFuture.runAsync(() -> {
|
|
333
|
+
try {
|
|
334
|
+
// 发送短信
|
|
335
|
+
smsService.sendOrderCreated(order);
|
|
336
|
+
// 发送邮件
|
|
337
|
+
emailService.sendOrderCreated(order);
|
|
338
|
+
} catch (Exception e) {
|
|
339
|
+
log.error("发送通知失败,orderId:{}", order.getId(), e);
|
|
340
|
+
}
|
|
341
|
+
}, asyncTaskExecutor);
|
|
342
|
+
|
|
343
|
+
log.info("订单创建成功,orderId:{}", order.getId());
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## 最佳实践
|
|
348
|
+
|
|
349
|
+
### 1. 指定线程池
|
|
350
|
+
|
|
351
|
+
```java
|
|
352
|
+
// ✅ 推荐:使用自定义线程池
|
|
353
|
+
CompletableFuture.supplyAsync(() -> doSomething(), asyncTaskExecutor);
|
|
354
|
+
|
|
355
|
+
// ❌ 避免:使用默认ForkJoinPool(可能影响其他业务)
|
|
356
|
+
CompletableFuture.supplyAsync(() -> doSomething());
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 2. 异常处理
|
|
360
|
+
|
|
361
|
+
```java
|
|
362
|
+
CompletableFuture<Result> future = CompletableFuture
|
|
363
|
+
.supplyAsync(() -> {
|
|
364
|
+
try {
|
|
365
|
+
return doSomething();
|
|
366
|
+
} catch (Exception e) {
|
|
367
|
+
log.error("异步任务执行失败", e);
|
|
368
|
+
throw new CompletionException(e);
|
|
369
|
+
}
|
|
370
|
+
}, asyncTaskExecutor);
|
|
371
|
+
|
|
372
|
+
// 处理异常(leniu 项目使用 LeException)
|
|
373
|
+
try {
|
|
374
|
+
Result result = future.join();
|
|
375
|
+
} catch (CompletionException e) {
|
|
376
|
+
log.error("获取异步结果失败", e);
|
|
377
|
+
throw new LeException("处理失败");
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 3. 超时控制
|
|
382
|
+
|
|
383
|
+
```java
|
|
384
|
+
try {
|
|
385
|
+
// 设置超时时间
|
|
386
|
+
Result result = future.get(5, TimeUnit.SECONDS);
|
|
387
|
+
} catch (TimeoutException e) {
|
|
388
|
+
log.error("异步任务超时", e);
|
|
389
|
+
throw new LeException("处理超时");
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## 常见错误
|
|
394
|
+
|
|
395
|
+
| 错误写法 | 正确写法 | 说明 |
|
|
396
|
+
|---------|---------|------|
|
|
397
|
+
| `throw new ServiceException("msg")` | `throw new LeException("msg")` | leniu 项目异常类不同 |
|
|
398
|
+
| `Lists.partition(ids, batchSize)` | `CollUtil.split(ids, batchSize)` | 使用 Hutool 工具类 |
|
|
399
|
+
| `MapstructUtils.convert(a, B.class)` | `BeanUtil.copyProperties(a, b)` | leniu 使用 Hutool 转换 |
|
|
400
|
+
| `import org.dromara.*` | `import net.xnzn.*` | 包名不同 |
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: leniu-java-entity
|
|
3
|
+
description: |
|
|
4
|
+
leniu-tengyun-core 项目 Entity/VO/DTO/Param/Enum 数据类规范。
|
|
5
|
+
|
|
6
|
+
触发场景:
|
|
7
|
+
- 创建 Entity 实体类(@TableName、审计字段 crby/crtime/upby/uptime)
|
|
8
|
+
- 创建 VO/DTO/Param 数据传输对象
|
|
9
|
+
- 使用 Jakarta Validation 参数校验
|
|
10
|
+
- 创建枚举类(含国际化 I18n)
|
|
11
|
+
- Excel 导出 VO(EasyExcel)
|
|
12
|
+
|
|
13
|
+
触发词:Entity实体类、VO视图对象、DTO数据传输、Param参数类、@TableName、@TableField、审计字段、枚举类、Excel导出、Jakarta校验
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# leniu Entity/VO/DTO 规范
|
|
17
|
+
|
|
18
|
+
## 项目特征速查
|
|
19
|
+
|
|
20
|
+
| 项 | 值 |
|
|
21
|
+
|---|---|
|
|
22
|
+
| 包名 | `net.xnzn.core.*` |
|
|
23
|
+
| JDK | 21 → `jakarta.validation.*`(禁用 javax) |
|
|
24
|
+
| 工具库 | Hutool(BeanUtil / CollUtil / ObjectUtil / StrUtil) |
|
|
25
|
+
| 金额 | `Long`(分)或 `BigDecimal`(分),前端用 `money()` 转元 |
|
|
26
|
+
| 审计字段 | crby / crtime / upby / uptime |
|
|
27
|
+
| 逻辑删除 | **1=删除,2=正常**(与 RuoYi 相反) |
|
|
28
|
+
| Entity 特点 | 无基类、无 Serializable |
|
|
29
|
+
|
|
30
|
+
## Entity 模板(核心)
|
|
31
|
+
|
|
32
|
+
```java
|
|
33
|
+
@Data
|
|
34
|
+
@Accessors(chain = true)
|
|
35
|
+
@TableName(value = "table_name", autoResultMap = true)
|
|
36
|
+
public class XxxEntity {
|
|
37
|
+
|
|
38
|
+
@TableId
|
|
39
|
+
@ApiModelProperty("主键ID")
|
|
40
|
+
private Long id;
|
|
41
|
+
|
|
42
|
+
// --- 业务字段 ---
|
|
43
|
+
@ApiModelProperty("名称")
|
|
44
|
+
private String name;
|
|
45
|
+
|
|
46
|
+
// --- 审计字段 ---
|
|
47
|
+
@ApiModelProperty("删除标识(1-删除,2-正常)")
|
|
48
|
+
private Integer delFlag;
|
|
49
|
+
|
|
50
|
+
@TableField(fill = FieldFill.INSERT)
|
|
51
|
+
private String crby;
|
|
52
|
+
|
|
53
|
+
@TableField(fill = FieldFill.INSERT)
|
|
54
|
+
private LocalDateTime crtime;
|
|
55
|
+
|
|
56
|
+
@TableField(fill = FieldFill.INSERT_UPDATE)
|
|
57
|
+
private String upby;
|
|
58
|
+
|
|
59
|
+
@TableField(fill = FieldFill.INSERT_UPDATE)
|
|
60
|
+
private LocalDateTime uptime;
|
|
61
|
+
|
|
62
|
+
// --- 静态工厂(预设默认值) ---
|
|
63
|
+
public static XxxEntity newDefaultInstance() {
|
|
64
|
+
XxxEntity e = new XxxEntity();
|
|
65
|
+
e.setDelFlag(2); // 2=正常
|
|
66
|
+
return e;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --- 领域业务方法(可选) ---
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**要点**:
|
|
74
|
+
- 无基类,审计字段直接定义
|
|
75
|
+
- `@TableName` 一般带 `autoResultMap = true`;报表 Model 不带(无 BaseMapper)
|
|
76
|
+
- 可含 `newDefaultInstance()` 工厂方法和领域业务方法
|
|
77
|
+
|
|
78
|
+
## VO 模板
|
|
79
|
+
|
|
80
|
+
```java
|
|
81
|
+
@Data
|
|
82
|
+
@Accessors(chain = true)
|
|
83
|
+
@ApiModel("订单信息")
|
|
84
|
+
public class OrderVO {
|
|
85
|
+
|
|
86
|
+
@ApiModelProperty("订单ID")
|
|
87
|
+
private Long orderId;
|
|
88
|
+
|
|
89
|
+
@ApiModelProperty("订单金额(元)")
|
|
90
|
+
private BigDecimal orderAmount;
|
|
91
|
+
|
|
92
|
+
@ApiModelProperty("创建时间")
|
|
93
|
+
private LocalDateTime crtime;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
带校验的 VO:
|
|
98
|
+
|
|
99
|
+
```java
|
|
100
|
+
@Data
|
|
101
|
+
@Accessors(chain = true)
|
|
102
|
+
@ApiModel("订单信息")
|
|
103
|
+
public class OrderVO {
|
|
104
|
+
|
|
105
|
+
@NotNull(message = "用户ID不能为空")
|
|
106
|
+
@ApiModelProperty("用户ID")
|
|
107
|
+
private Long userId;
|
|
108
|
+
|
|
109
|
+
@NotNull(message = "订单金额不能为空")
|
|
110
|
+
@DecimalMin(value = "0.01", message = "订单金额必须大于0")
|
|
111
|
+
@ApiModelProperty("订单金额(元)")
|
|
112
|
+
private BigDecimal orderAmount;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Param 模板(分页查询)
|
|
117
|
+
|
|
118
|
+
```java
|
|
119
|
+
@Data
|
|
120
|
+
@ApiModel("订单查询参数")
|
|
121
|
+
public class OrderQueryParam implements Serializable {
|
|
122
|
+
|
|
123
|
+
@NotNull(message = "分页参数不能为空")
|
|
124
|
+
@ApiModelProperty(value = "分页参数", required = true)
|
|
125
|
+
private PageDTO page;
|
|
126
|
+
|
|
127
|
+
@ApiModelProperty("关键字")
|
|
128
|
+
private String keyword;
|
|
129
|
+
|
|
130
|
+
@ApiModelProperty("状态")
|
|
131
|
+
private Integer status;
|
|
132
|
+
|
|
133
|
+
@ApiModelProperty("开始日期")
|
|
134
|
+
private LocalDate startDate;
|
|
135
|
+
|
|
136
|
+
@ApiModelProperty("结束日期")
|
|
137
|
+
private LocalDate endDate;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## DTO 模板
|
|
142
|
+
|
|
143
|
+
```java
|
|
144
|
+
@Data
|
|
145
|
+
@ApiModel("订单数据传输对象")
|
|
146
|
+
public class OrderDTO {
|
|
147
|
+
|
|
148
|
+
@ApiModelProperty("订单ID")
|
|
149
|
+
private Long orderId;
|
|
150
|
+
|
|
151
|
+
@ApiModelProperty("用户ID")
|
|
152
|
+
private Long userId;
|
|
153
|
+
|
|
154
|
+
@ApiModelProperty("订单状态")
|
|
155
|
+
private Integer status;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 枚举模板
|
|
160
|
+
|
|
161
|
+
```java
|
|
162
|
+
@Getter
|
|
163
|
+
@AllArgsConstructor
|
|
164
|
+
public enum OrderStatusEnum {
|
|
165
|
+
|
|
166
|
+
CREATED(1, "已创建"),
|
|
167
|
+
PAID(2, "已支付"),
|
|
168
|
+
COMPLETED(3, "已完成"),
|
|
169
|
+
CANCELLED(4, "已取消");
|
|
170
|
+
|
|
171
|
+
private final Integer key;
|
|
172
|
+
private final String desc;
|
|
173
|
+
|
|
174
|
+
public static OrderStatusEnum getByKey(Integer key) {
|
|
175
|
+
if (key == null) return null;
|
|
176
|
+
for (OrderStatusEnum e : values()) {
|
|
177
|
+
if (e.getKey().equals(key)) return e;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public static String getDescByKey(Integer key) {
|
|
183
|
+
OrderStatusEnum e = getByKey(key);
|
|
184
|
+
return e != null ? e.getDesc() : "";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
含国际化时,`desc` 用占位符 `"{order.status.created}"`,重写 `getDesc()` 调用 `I18n.getMessage(desc)`。
|
|
190
|
+
|
|
191
|
+
## 时间格式化
|
|
192
|
+
|
|
193
|
+
```java
|
|
194
|
+
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
195
|
+
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
|
196
|
+
private LocalDateTime createTime;
|
|
197
|
+
|
|
198
|
+
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
|
199
|
+
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
|
200
|
+
private LocalDate date;
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 对象转换
|
|
204
|
+
|
|
205
|
+
```java
|
|
206
|
+
// 单对象
|
|
207
|
+
Target t = BeanUtil.copyProperties(source, Target.class);
|
|
208
|
+
// 列表
|
|
209
|
+
List<Target> list = BeanUtil.copyToList(sources, Target.class);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 禁止项
|
|
213
|
+
|
|
214
|
+
```java
|
|
215
|
+
// ❌ 继承 RuoYi TenantEntity / BaseEntity
|
|
216
|
+
import org.dromara.common.mybatis.core.domain.TenantEntity;
|
|
217
|
+
|
|
218
|
+
// ❌ javax.validation(JDK 21 必须用 jakarta)
|
|
219
|
+
import javax.validation.constraints.NotNull;
|
|
220
|
+
|
|
221
|
+
// ❌ MapstructUtils(用 BeanUtil.copyProperties)
|
|
222
|
+
MapstructUtils.convert(source, Target.class);
|
|
223
|
+
|
|
224
|
+
// ❌ delFlag: 0=正常(leniu 是 2=正常)
|
|
225
|
+
wrapper.eq(XxxEntity::getDelFlag, 0);
|
|
226
|
+
|
|
227
|
+
// ❌ 错误审计字段名
|
|
228
|
+
private String createBy; // 必须 crby
|
|
229
|
+
private LocalDateTime createTime; // 必须 crtime
|
|
230
|
+
|
|
231
|
+
// ❌ Entity 加 tenant_id(双库物理隔离,不需要)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## 参考文档
|
|
235
|
+
|
|
236
|
+
- 完整模板示例(Excel导出VO、报表Param等):详见 `references/templates.md`
|
|
237
|
+
- 生产代码参考:`sys-canteen/.../order/common/model/OrderInfo.java`
|