6aspec 2.0.0-dev.10

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 (110) hide show
  1. package/.6aspec/rules/biz/api_rule.md +578 -0
  2. package/.6aspec/rules/biz/background_job_rule.md +719 -0
  3. package/.6aspec/rules/biz/c_user_system_rule.md +240 -0
  4. package/.6aspec/rules/biz/code.md +39 -0
  5. package/.6aspec/rules/biz/event_subscriber_rule.md +529 -0
  6. package/.6aspec/rules/biz/project-structure.md +90 -0
  7. package/.6aspec/rules/biz/scheduled_job_rule.md +850 -0
  8. package/.6aspec/rules/brown/brown_archive_sop.md +132 -0
  9. package/.6aspec/rules/brown/brown_constitution.md +20 -0
  10. package/.6aspec/rules/brown/brown_continue_sop.md +97 -0
  11. package/.6aspec/rules/brown/brown_design_sop.md +155 -0
  12. package/.6aspec/rules/brown/brown_ff_sop.md +194 -0
  13. package/.6aspec/rules/brown/brown_impact_sop.md +293 -0
  14. package/.6aspec/rules/brown/brown_implement_sop.md +133 -0
  15. package/.6aspec/rules/brown/brown_list_sop.md +69 -0
  16. package/.6aspec/rules/brown/brown_new_sop.md +257 -0
  17. package/.6aspec/rules/brown/brown_proposal_sop.md +160 -0
  18. package/.6aspec/rules/brown/brown_quick_sop.md +134 -0
  19. package/.6aspec/rules/brown/brown_review_sop.md +270 -0
  20. package/.6aspec/rules/brown/brown_rollback_sop.md +188 -0
  21. package/.6aspec/rules/brown/brown_specs_sop.md +228 -0
  22. package/.6aspec/rules/brown/brown_status_sop.md +135 -0
  23. package/.6aspec/rules/brown/brown_tasks_sop.md +202 -0
  24. package/.6aspec/rules/brown/brown_understand_sop.md +208 -0
  25. package/.6aspec/rules/brown/brown_verify_sop.md +360 -0
  26. package/.6aspec/rules/green/6A_archive_sop.md +301 -0
  27. package/.6aspec/rules/green/6A_clarify_sop.md +238 -0
  28. package/.6aspec/rules/green/6A_code_implementation_sop.md +110 -0
  29. package/.6aspec/rules/green/6A_constitution.md +52 -0
  30. package/.6aspec/rules/green/6A_continue_sop.md +186 -0
  31. package/.6aspec/rules/green/6A_design_sop.md +228 -0
  32. package/.6aspec/rules/green/6A_import_model_table_sop.md +120 -0
  33. package/.6aspec/rules/green/6A_init_event_list_sop.md +62 -0
  34. package/.6aspec/rules/green/6A_init_map_sop.md +79 -0
  35. package/.6aspec/rules/green/6A_model_sop.md +210 -0
  36. package/.6aspec/rules/green/6A_new_sop.md +319 -0
  37. package/.6aspec/rules/green/6A_rollback_sop.md +198 -0
  38. package/.6aspec/rules/green/6A_status_sop.md +275 -0
  39. package/.6aspec/rules/green/6A_tasks_sop.md +181 -0
  40. package/.6aspec/rules/green/6A_visual_logic_sop.md +121 -0
  41. package/.6aspec/rules/green/green_status_schema.md +293 -0
  42. package/.6aspec/script/create_entities_from_markdown.py +688 -0
  43. package/.claude/commands/6aspec/brown/archive.md +11 -0
  44. package/.claude/commands/6aspec/brown/continue.md +11 -0
  45. package/.claude/commands/6aspec/brown/design.md +11 -0
  46. package/.claude/commands/6aspec/brown/ff.md +11 -0
  47. package/.claude/commands/6aspec/brown/impact.md +11 -0
  48. package/.claude/commands/6aspec/brown/implement.md +11 -0
  49. package/.claude/commands/6aspec/brown/list.md +11 -0
  50. package/.claude/commands/6aspec/brown/new.md +11 -0
  51. package/.claude/commands/6aspec/brown/proposal.md +11 -0
  52. package/.claude/commands/6aspec/brown/quick.md +11 -0
  53. package/.claude/commands/6aspec/brown/review.md +11 -0
  54. package/.claude/commands/6aspec/brown/rollback.md +11 -0
  55. package/.claude/commands/6aspec/brown/specs.md +11 -0
  56. package/.claude/commands/6aspec/brown/status.md +11 -0
  57. package/.claude/commands/6aspec/brown/tasks.md +11 -0
  58. package/.claude/commands/6aspec/brown/understand.md +11 -0
  59. package/.claude/commands/6aspec/brown/verify.md +11 -0
  60. package/.claude/commands/6aspec/green/archive.md +8 -0
  61. package/.claude/commands/6aspec/green/clarify.md +13 -0
  62. package/.claude/commands/6aspec/green/continue.md +8 -0
  63. package/.claude/commands/6aspec/green/design.md +8 -0
  64. package/.claude/commands/6aspec/green/execute-task.md +20 -0
  65. package/.claude/commands/6aspec/green/import-model-table.md +8 -0
  66. package/.claude/commands/6aspec/green/init.md +14 -0
  67. package/.claude/commands/6aspec/green/model.md +12 -0
  68. package/.claude/commands/6aspec/green/new.md +13 -0
  69. package/.claude/commands/6aspec/green/rollback.md +8 -0
  70. package/.claude/commands/6aspec/green/status.md +8 -0
  71. package/.claude/commands/6aspec/green/tasks.md +8 -0
  72. package/.claude/commands/6aspec/green/visual-logic.md +9 -0
  73. package/.claude/settings.local.json +8 -0
  74. package/.cursor/commands/6aspec/brown/archive.md +9 -0
  75. package/.cursor/commands/6aspec/brown/continue.md +9 -0
  76. package/.cursor/commands/6aspec/brown/design.md +9 -0
  77. package/.cursor/commands/6aspec/brown/ff.md +9 -0
  78. package/.cursor/commands/6aspec/brown/impact.md +9 -0
  79. package/.cursor/commands/6aspec/brown/implement.md +9 -0
  80. package/.cursor/commands/6aspec/brown/list.md +9 -0
  81. package/.cursor/commands/6aspec/brown/new.md +9 -0
  82. package/.cursor/commands/6aspec/brown/proposal.md +9 -0
  83. package/.cursor/commands/6aspec/brown/quick.md +9 -0
  84. package/.cursor/commands/6aspec/brown/review.md +9 -0
  85. package/.cursor/commands/6aspec/brown/rollback.md +9 -0
  86. package/.cursor/commands/6aspec/brown/specs.md +9 -0
  87. package/.cursor/commands/6aspec/brown/status.md +9 -0
  88. package/.cursor/commands/6aspec/brown/tasks.md +9 -0
  89. package/.cursor/commands/6aspec/brown/understand.md +9 -0
  90. package/.cursor/commands/6aspec/brown/verify.md +9 -0
  91. package/.cursor/commands/6aspec/green/archive.md +9 -0
  92. package/.cursor/commands/6aspec/green/clarify.md +14 -0
  93. package/.cursor/commands/6aspec/green/continue.md +9 -0
  94. package/.cursor/commands/6aspec/green/design.md +9 -0
  95. package/.cursor/commands/6aspec/green/execute-task.md +21 -0
  96. package/.cursor/commands/6aspec/green/import-model-table.md +9 -0
  97. package/.cursor/commands/6aspec/green/init.md +15 -0
  98. package/.cursor/commands/6aspec/green/model.md +13 -0
  99. package/.cursor/commands/6aspec/green/new.md +14 -0
  100. package/.cursor/commands/6aspec/green/rollback.md +9 -0
  101. package/.cursor/commands/6aspec/green/status.md +9 -0
  102. package/.cursor/commands/6aspec/green/tasks.md +9 -0
  103. package/.cursor/commands/6aspec/green/visual-logic.md +10 -0
  104. package/README.en.md +36 -0
  105. package/README.md +146 -0
  106. package/bin/6a-spec-install +54 -0
  107. package/bin/6aspec +64 -0
  108. package/lib/cli.js +451 -0
  109. package/lib/installer.js +333 -0
  110. package/package.json +62 -0
@@ -0,0 +1,719 @@
1
+ # 后台任务开发规范
2
+
3
+ ## 概述
4
+
5
+ 本规范定义了系统中后台任务(BackgroundJob)的开发标准,包括包结构、注解使用、命名规范、批量处理等。
6
+
7
+ 后台任务与定时任务的区别:
8
+ - **定时任务**:按时间周期自动执行(如每天凌晨执行)
9
+ - **后台任务**:由业务触发异步执行(如批量操作、耗时操作)
10
+
11
+ ---
12
+
13
+ ## 一、后台任务开发规范
14
+
15
+ ### 1.1 包路径规范
16
+
17
+ ```
18
+ **.job.backgroundjob
19
+ ```
20
+
21
+ **示例**:
22
+ ```
23
+ com.mysoft.rental.caretaker.job.backgroundjob
24
+ com.mysoft.rental.charging.job.backgroundjob
25
+ ```
26
+
27
+ ### 1.2 类命名规范
28
+
29
+ **格式**:`{业务描述}BackgroundJob`
30
+
31
+ | 场景 | 命名格式 | 示例 |
32
+ |------|---------|------|
33
+ | 批量绑定 | `{Entity}BatchBindBackgroundJob` | `DeviceMeterBatchAutoBindBackgroundJob` |
34
+ | 批量解绑 | `{Entity}BatchUnBindBackgroundJob` | `DeviceMeterBatchAutoUnBindBackgroundJob` |
35
+ | 批量更新 | `{Entity}BatchUpdateBackgroundJob` | `MeterDeviceBindUpdatePayerAndPayeeBackgroundJob` |
36
+ | 批量作废 | `{Entity}BatchInvalidateBackgroundJob` | `DeviceMeterRoomChargeInvalidateBackgroundJob` |
37
+ | 批量处理 | `{Entity}BatchProcessBackgroundJob` | `DataBatchProcessBackgroundJob` |
38
+ | 数据同步 | `{Entity}SyncBackgroundJob` | `OrderDataSyncBackgroundJob` |
39
+
40
+ **命名规则**:
41
+ - 使用清晰、语义化的名称
42
+ - 体现任务的业务目的
43
+ - 统一以 `BackgroundJob` 结尾
44
+
45
+ ---
46
+
47
+ ## 二、基础开发规范
48
+
49
+ ### 2.1 类结构
50
+
51
+ ```java
52
+ package com.mysoft.rental.{module}.job.backgroundjob;
53
+
54
+ import com.mysoft.framework.backgroudbjob.BackgroundJob;
55
+ import com.mysoft.framework.backgroudbjob.annotation.JobName;
56
+ import lombok.extern.slf4j.Slf4j;
57
+ import org.springframework.beans.factory.annotation.Autowired;
58
+
59
+ /**
60
+ * {任务描述}后台任务
61
+ *
62
+ * @author {作者}
63
+ * @description {详细描述任务的作用}
64
+ * @date {创建日期}
65
+ */
66
+ @Slf4j
67
+ @JobName(name = "任务名称", jobGroup = "任务分组")
68
+ public class XxxBackgroundJob implements BackgroundJob<MessageType> {
69
+
70
+ @Autowired
71
+ private XxxService xxxService;
72
+
73
+ @Override
74
+ public void execute(MessageType message) throws Exception {
75
+ log.info("收到XXX任务消息:{}", JsonUtil.toString(message));
76
+
77
+ try {
78
+ // 业务处理逻辑
79
+ xxxService.process(message);
80
+
81
+ log.info("XXX任务执行完成");
82
+ } catch (Exception e) {
83
+ log.error("XXX任务执行失败", e);
84
+ throw e; // 抛出异常,框架会记录失败状态
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### 2.2 关键要点
91
+
92
+ #### 2.2.1 类注解(必需)
93
+
94
+ **@Slf4j**
95
+ - 用于日志记录
96
+ - 必须添加
97
+
98
+ **@JobName**
99
+ - 定义任务名称和分组
100
+ - 参数说明:
101
+ - `name`:任务名称(中文),用于识别和展示
102
+ - `jobGroup`:任务分组,用于任务分类管理
103
+
104
+ ```java
105
+ @Slf4j
106
+ @JobName(name = "房源关联水表批量解绑", jobGroup = "device")
107
+ public class DeviceMeterBatchAutoUnBindBackgroundJob implements BackgroundJob<BatchOperateMessageDTO> {
108
+ // ...
109
+ }
110
+ ```
111
+
112
+ **常用任务分组(jobGroup)**:
113
+
114
+ | 分组名 | 说明 | 示例场景 |
115
+ |--------|------|---------|
116
+ | `device` | 设备相关 | 设备绑定、解绑、同步 |
117
+ | `caretaker` | 管家相关 | 入住、退房、换房 |
118
+ | `charging` | 计费相关 | 账单生成、费用作废 |
119
+ | `contract` | 合同相关 | 合同签订、变更 |
120
+ | `data` | 数据处理 | 数据同步、数据清理 |
121
+ | `notification` | 通知相关 | 消息推送、邮件发送 |
122
+
123
+ #### 2.2.2 实现接口
124
+
125
+ **BackgroundJob<T>**
126
+ - `T` 为消息类型(DTO)
127
+ - 必须实现 `execute(T message)` 方法
128
+
129
+ ```java
130
+ public class XxxBackgroundJob implements BackgroundJob<XxxDTO> {
131
+ @Override
132
+ public void execute(XxxDTO message) throws Exception {
133
+ // 处理逻辑
134
+ }
135
+ }
136
+ ```
137
+
138
+ **常用消息类型**:
139
+
140
+ | 消息类型 | 说明 | 使用场景 |
141
+ |---------|------|---------|
142
+ | `BatchOperateMessageDTO` | 批量操作消息 | 批量绑定、解绑、更新等 |
143
+ | 自定义 DTO | 业务特定消息 | 单个业务处理 |
144
+
145
+ #### 2.2.3 execute 方法规范
146
+
147
+ **方法签名**:
148
+ ```java
149
+ @Override
150
+ public void execute(MessageType message) throws Exception
151
+ ```
152
+
153
+ **关键点**:
154
+ - `public` 访问修饰符
155
+ - `void` 返回类型
156
+ - `throws Exception` - 允许抛出异常
157
+ - 异常处理:
158
+ - 捕获并记录日志
159
+ - **可以抛出异常**,框架会处理失败状态
160
+ - 与定时任务不同,后台任务可以抛出异常
161
+
162
+ #### 2.2.4 日志规范
163
+
164
+ **必需日志**:
165
+ ```java
166
+ // 任务开始
167
+ log.info("收到XXX任务消息:{}", JsonUtil.toString(message));
168
+
169
+ // 任务完成
170
+ log.info("XXX任务执行完成");
171
+
172
+ // 任务失败
173
+ log.error("XXX任务执行失败", e);
174
+ ```
175
+
176
+ **建议日志**:
177
+ - 记录关键步骤
178
+ - 记录处理数量和结果
179
+
180
+ ---
181
+
182
+ ## 三、批量操作任务规范
183
+
184
+ ### 3.1 使用场景
185
+
186
+ 批量操作任务通常用于:
187
+ - 批量绑定/解绑
188
+ - 批量更新
189
+ - 批量作废
190
+ - 批量导入/导出
191
+
192
+ ### 3.2 标准结构
193
+
194
+ ```java
195
+ @Slf4j
196
+ @JobName(name = "房源关联水表批量解绑", jobGroup = "device")
197
+ public class DeviceMeterBatchAutoUnBindBackgroundJob implements BackgroundJob<BatchOperateMessageDTO> {
198
+
199
+ @Autowired
200
+ private BatchOperateFacade batchOperateFacade;
201
+
202
+ @Autowired
203
+ private UsageDeviceService usageDeviceService;
204
+
205
+ @Override
206
+ public void execute(BatchOperateMessageDTO batchOperateMessage) throws Exception {
207
+ log.info("房源关联水表批量解绑,{}", JsonUtil.toString(batchOperateMessage));
208
+
209
+ // 1. 获取批量操作数据
210
+ List<BatchOperateBusinessDTO> businesses = batchOperateFacade.getData(batchOperateMessage.getDataId());
211
+ TaskDTO taskDTO = batchOperateFacade.queryTask(batchOperateMessage.getTaskId());
212
+
213
+ // 2. 分组处理(大批量数据时)
214
+ List<List<UUID>> groups = buildGroups(businesses);
215
+ List<TaskItemDTO> taskItemList = new ArrayList<>();
216
+
217
+ // 3. 处理每个组
218
+ groups.forEach(group -> {
219
+ if (CollectionUtils.isEmpty(group)) {
220
+ return;
221
+ }
222
+ List<TaskItemDTO> currentTaskItemList = new ArrayList<>();
223
+ try {
224
+ // 执行业务逻辑
225
+ currentTaskItemList = usageDeviceService.batchUnBindUsageDeviceForBackGround(
226
+ group,
227
+ batchOperateMessage.getTaskId(),
228
+ taskDTO.getBusinessExtendedField()
229
+ );
230
+ } catch (Exception exception) {
231
+ log.error("房源关联水表批量解绑失败:", exception);
232
+ // 构建失败任务项
233
+ currentTaskItemList = group.stream()
234
+ .map(x -> new TaskItemDTO(batchOperateMessage.getTaskId(), x.toString(), Constant.SYSTEM_ERROR))
235
+ .collect(Collectors.toList());
236
+ } finally {
237
+ // 更新任务状态
238
+ taskItemList.addAll(currentTaskItemList);
239
+ batchOperateFacade.updateTaskAndResetTaskItems(taskItemList, false);
240
+ }
241
+ });
242
+
243
+ // 4. 最终更新任务状态
244
+ batchOperateFacade.updateTaskAndResetTaskItems(taskItemList, true);
245
+ }
246
+
247
+ /**
248
+ * 分组处理逻辑
249
+ */
250
+ public static List<List<UUID>> buildGroups(List<BatchOperateBusinessDTO> businesses) {
251
+ if (CollectionUtils.isEmpty(businesses)) {
252
+ return Collections.emptyList();
253
+ }
254
+
255
+ List<UUID> businessIds = businesses.stream()
256
+ .map(x -> UUID.fromString(x.getId()))
257
+ .collect(Collectors.toList());
258
+
259
+ final int maxGroups = 10; // 最多分10组
260
+ List<List<UUID>> groups = new ArrayList<>(maxGroups);
261
+ int size = businessIds.size();
262
+
263
+ // 初始化所有需要的组
264
+ int actualGroups = calculateActualGroups(size, maxGroups);
265
+ for (int i = 0; i < actualGroups; i++) {
266
+ groups.add(new ArrayList<>());
267
+ }
268
+
269
+ // 分配元素到各组
270
+ if (size <= maxGroups * 10) {
271
+ // ≤100 时的分配方式(每10个一组)
272
+ for (int i = 0; i < size; i++) {
273
+ int groupIndex = i / 10;
274
+ groups.get(groupIndex).add(businessIds.get(i));
275
+ }
276
+ } else {
277
+ // >100 时的均匀分配
278
+ int elementsPerGroup = size / maxGroups;
279
+ int remainder = size % maxGroups;
280
+
281
+ int currentIndex = 0;
282
+ for (int i = 0; i < maxGroups; i++) {
283
+ int groupSize = elementsPerGroup + (i < remainder ? 1 : 0);
284
+ List<UUID> group = businessIds.subList(currentIndex, currentIndex + groupSize);
285
+ groups.set(i, new ArrayList<>(group));
286
+ currentIndex += groupSize;
287
+ }
288
+ }
289
+
290
+ return groups;
291
+ }
292
+
293
+ public static int calculateActualGroups(int size, int maxGroups) {
294
+ if (size <= maxGroups * 10) {
295
+ return Math.max(1, (size + 9) / 10); // 向上取整计算需要的组数
296
+ }
297
+ return maxGroups;
298
+ }
299
+ }
300
+ ```
301
+
302
+ ### 3.3 分组策略
303
+
304
+ **为什么要分组?**
305
+ - 避免一次处理过多数据导致超时
306
+ - 提高任务的容错性(部分失败不影响整体)
307
+ - 便于任务进度跟踪
308
+
309
+ **分组规则**(推荐):
310
+ - 数据量 ≤ 100:每 10 个一组
311
+ - 数据量 > 100:均匀分配到 10 组
312
+ - 最多分 10 组
313
+
314
+ **分组处理模式**:
315
+ ```java
316
+ // 1. 分组
317
+ List<List<UUID>> groups = buildGroups(businesses);
318
+
319
+ // 2. 逐组处理
320
+ groups.forEach(group -> {
321
+ try {
322
+ // 处理当前组
323
+ processGroup(group);
324
+ } catch (Exception e) {
325
+ // 记录失败,继续处理下一组
326
+ log.error("处理分组失败", e);
327
+ }
328
+ });
329
+ ```
330
+
331
+ ### 3.4 任务状态更新
332
+
333
+ **使用 BatchOperateFacade**:
334
+
335
+ ```java
336
+ // 获取数据
337
+ List<BatchOperateBusinessDTO> businesses = batchOperateFacade.getData(dataId);
338
+
339
+ // 查询任务
340
+ TaskDTO taskDTO = batchOperateFacade.queryTask(taskId);
341
+
342
+ // 更新任务项(中间更新)
343
+ batchOperateFacade.updateTaskAndResetTaskItems(taskItemList, false);
344
+
345
+ // 最终更新(完成)
346
+ batchOperateFacade.updateTaskAndResetTaskItems(taskItemList, true);
347
+
348
+ // 删除数据
349
+ batchOperateFacade.deleteData(dataId);
350
+
351
+ // 更新任务状态
352
+ batchOperateFacade.updateTask(taskId, errorMessage, businesses, isSuccess);
353
+ ```
354
+
355
+ ---
356
+
357
+ ## 四、简单任务规范
358
+
359
+ ### 4.1 使用场景
360
+
361
+ 简单任务通常用于:
362
+ - 单个业务处理
363
+ - 数据同步
364
+ - 消息通知
365
+ - 数据更新
366
+
367
+ ### 4.2 标准结构
368
+
369
+ ```java
370
+ @Slf4j
371
+ @JobName(name = "用量类设备绑定同步收付款方", jobGroup = "caretaker")
372
+ public class MeterDeviceBindUpdatePayerAndPayeeBackgroundJob implements BackgroundJob<MeterDeviceBindDTO> {
373
+
374
+ @Autowired
375
+ private UsageDeviceService usageDeviceService;
376
+
377
+ @Override
378
+ public void execute(MeterDeviceBindDTO meterDeviceBind) throws Exception {
379
+ log.info("收到用量类设备绑定:{}", JsonUtil.toString(meterDeviceBind));
380
+
381
+ try {
382
+ usageDeviceService.meterDeviceBindUpdatePayerAndPayee(meterDeviceBind);
383
+ log.info("用量类设备绑定同步收付款方完成");
384
+ } catch (Exception e) {
385
+ log.error("用量类设备绑定同步收付款方失败", e);
386
+ throw e;
387
+ }
388
+ }
389
+ }
390
+ ```
391
+
392
+ ### 4.3 关键点
393
+
394
+ - 消息类型为自定义 DTO
395
+ - 处理逻辑简单直接
396
+ - 异常可以直接抛出
397
+ - 日志记录开始和结束
398
+
399
+ ---
400
+
401
+ ## 五、异常处理规范
402
+
403
+ ### 5.1 批量操作任务的异常处理
404
+
405
+ **原则**:部分失败不影响整体
406
+
407
+ ```java
408
+ groups.forEach(group -> {
409
+ List<TaskItemDTO> currentTaskItemList = new ArrayList<>();
410
+ try {
411
+ // 执行业务逻辑
412
+ currentTaskItemList = service.batchProcess(group);
413
+ } catch (Exception exception) {
414
+ log.error("批量处理失败:", exception);
415
+ // 构建失败任务项
416
+ currentTaskItemList = group.stream()
417
+ .map(x -> new TaskItemDTO(taskId, x.toString(), Constant.SYSTEM_ERROR))
418
+ .collect(Collectors.toList());
419
+ } finally {
420
+ // 更新任务状态
421
+ taskItemList.addAll(currentTaskItemList);
422
+ batchOperateFacade.updateTaskAndResetTaskItems(taskItemList, false);
423
+ }
424
+ });
425
+ ```
426
+
427
+ **关键点**:
428
+ - 每个分组独立处理
429
+ - 异常时构建失败任务项
430
+ - `finally` 块中更新状态
431
+ - 继续处理下一组
432
+
433
+ ### 5.2 简单任务的异常处理
434
+
435
+ **原则**:抛出异常,由框架处理
436
+
437
+ ```java
438
+ @Override
439
+ public void execute(MessageType message) throws Exception {
440
+ log.info("收到任务消息:{}", JsonUtil.toString(message));
441
+
442
+ try {
443
+ service.process(message);
444
+ log.info("任务执行完成");
445
+ } catch (Exception e) {
446
+ log.error("任务执行失败", e);
447
+ throw e; // 抛出异常,框架会标记任务失败
448
+ }
449
+ }
450
+ ```
451
+
452
+ **关键点**:
453
+ - 记录详细的错误日志
454
+ - 抛出异常给框架处理
455
+ - 框架会自动标记任务失败
456
+
457
+ ---
458
+
459
+ ## 六、最佳实践
460
+
461
+ ### 6.1 任务幂等性
462
+
463
+ **问题**:任务可能重复执行
464
+
465
+ **解决方案**:
466
+
467
+ **方式一:业务层面幂等**
468
+ ```java
469
+ @Override
470
+ public void execute(MessageType message) throws Exception {
471
+ // 检查是否已处理
472
+ if (service.isProcessed(message.getId())) {
473
+ log.info("任务已处理,跳过:{}", message.getId());
474
+ return;
475
+ }
476
+
477
+ // 执行业务逻辑
478
+ service.process(message);
479
+
480
+ // 标记已处理
481
+ service.markProcessed(message.getId());
482
+ }
483
+ ```
484
+
485
+ **方式二:使用事务和唯一索引**
486
+ ```java
487
+ @Transactional
488
+ public void process(MessageType message) {
489
+ // 业务逻辑
490
+ // 数据库唯一索引保证幂等性
491
+ }
492
+ ```
493
+
494
+ ### 6.2 大数据量处理
495
+
496
+ **问题**:数据量过大导致内存溢出或超时
497
+
498
+ **解决方案:分组 + 分页**
499
+
500
+ ```java
501
+ @Override
502
+ public void execute(BatchOperateMessageDTO message) throws Exception {
503
+ // 1. 分组处理
504
+ List<List<UUID>> groups = buildGroups(businesses);
505
+
506
+ // 2. 每组内再分页
507
+ groups.forEach(group -> {
508
+ int pageSize = 10;
509
+ for (int i = 0; i < group.size(); i += pageSize) {
510
+ int end = Math.min(i + pageSize, group.size());
511
+ List<UUID> page = group.subList(i, end);
512
+
513
+ try {
514
+ service.batchProcess(page);
515
+ } catch (Exception e) {
516
+ log.error("处理分页失败", e);
517
+ }
518
+ }
519
+ });
520
+ }
521
+ ```
522
+
523
+ ### 6.3 任务监控
524
+
525
+ **建议**:记录任务执行统计
526
+
527
+ ```java
528
+ @Override
529
+ public void execute(BatchOperateMessageDTO message) throws Exception {
530
+ long startTime = System.currentTimeMillis();
531
+ int successCount = 0;
532
+ int failCount = 0;
533
+
534
+ try {
535
+ // 业务处理
536
+ for (Item item : items) {
537
+ try {
538
+ service.process(item);
539
+ successCount++;
540
+ } catch (Exception e) {
541
+ failCount++;
542
+ log.error("处理失败:{}", item.getId(), e);
543
+ }
544
+ }
545
+
546
+ long duration = System.currentTimeMillis() - startTime;
547
+ log.info("任务执行完成,耗时:{}ms,成功:{},失败:{}", duration, successCount, failCount);
548
+
549
+ } catch (Exception e) {
550
+ log.error("任务执行异常", e);
551
+ throw e;
552
+ }
553
+ }
554
+ ```
555
+
556
+ ### 6.4 资源释放
557
+
558
+ **注意**:及时释放资源
559
+
560
+ ```java
561
+ @Override
562
+ public void execute(MessageType message) throws Exception {
563
+ Connection conn = null;
564
+ try {
565
+ conn = getConnection();
566
+ // 业务处理
567
+ } finally {
568
+ // 释放资源
569
+ if (conn != null) {
570
+ conn.close();
571
+ }
572
+ }
573
+ }
574
+ ```
575
+
576
+ ---
577
+
578
+ ## 七、检查清单
579
+
580
+ 在提交代码前,请确认:
581
+
582
+ - [ ] 类名符合命名规范(以 BackgroundJob 结尾)
583
+ - [ ] 包路径正确(`**.job.backgroundjob`)
584
+ - [ ] 添加了 `@Slf4j` 和 `@JobName` 注解
585
+ - [ ] 实现了 `BackgroundJob<T>` 接口
586
+ - [ ] 实现了 `execute(T message)` 方法
587
+ - [ ] `@JobName` 的 `name` 和 `jobGroup` 配置正确
588
+ - [ ] 添加了日志记录(开始、完成、异常)
589
+ - [ ] 添加了异常处理
590
+ - [ ] 批量任务使用了分组处理
591
+ - [ ] 考虑了任务幂等性
592
+ - [ ] 添加了 JavaDoc 注释
593
+
594
+ ---
595
+
596
+ ## 八、常见问题
597
+
598
+ ### Q1: 后台任务和定时任务的区别?
599
+ - **定时任务**:
600
+ - 按时间周期自动执行
601
+ - 使用 Cron 表达式配置
602
+ - 需要 XML 配置文件
603
+ - 不需要外部触发
604
+
605
+ - **后台任务**:
606
+ - 由业务代码触发执行
607
+ - 异步处理耗时操作
608
+ - 不需要配置文件
609
+ - 需要业务调用触发
610
+
611
+ ### Q2: 如何触发后台任务?
612
+ ```java
613
+ // 注入后台任务执行器
614
+ @Autowired
615
+ private BackgroundJobExecutor backgroundJobExecutor;
616
+
617
+ // 触发任务
618
+ MessageType message = new MessageType();
619
+ // 设置消息内容
620
+ backgroundJobExecutor.execute(XxxBackgroundJob.class, message);
621
+ ```
622
+
623
+ ### Q3: 后台任务的异常处理和定时任务有什么不同?
624
+ - **定时任务**:不能抛出异常,必须捕获所有异常
625
+ - **后台任务**:可以抛出异常,框架会处理失败状态
626
+
627
+ ### Q4: 为什么批量任务要分组处理?
628
+ - 避免一次处理过多数据导致超时
629
+ - 提高容错性(部分失败不影响整体)
630
+ - 便于进度跟踪和监控
631
+ - 降低内存占用
632
+
633
+ ### Q5: jobGroup 如何选择?
634
+ - 按业务领域分组
635
+ - 便于任务管理和监控
636
+ - 常用分组:`device`、`caretaker`、`charging`、`contract`、`data`、`notification`
637
+
638
+ ### Q6: 后台任务支持重试吗?
639
+ - 框架通常支持任务重试机制
640
+ - 重试次数和策略由框架配置
641
+ - 任务需要保证幂等性
642
+
643
+ ### Q7: 如何查看后台任务的执行状态?
644
+ - 通过管理后台查看任务列表
645
+ - 查看日志文件
646
+ - 使用监控平台(如有)
647
+
648
+ ### Q8: 批量任务的分组数量如何确定?
649
+ - 推荐最多分 10 组
650
+ - 数据量小(≤100):每 10 个一组
651
+ - 数据量大(>100):均匀分配到 10 组
652
+ - 根据实际业务调整
653
+
654
+ ---
655
+
656
+ ## 九、任务触发示例
657
+
658
+ ### 9.1 在 Service 中触发
659
+
660
+ ```java
661
+ @Service
662
+ public class DeviceService {
663
+
664
+ @Autowired
665
+ private BackgroundJobExecutor backgroundJobExecutor;
666
+
667
+ public void batchUnbindDevices(List<UUID> deviceIds, UUID taskId) {
668
+ // 构建消息
669
+ BatchOperateMessageDTO message = new BatchOperateMessageDTO();
670
+ message.setTaskId(taskId);
671
+ message.setDataId(dataId);
672
+
673
+ // 触发后台任务
674
+ backgroundJobExecutor.execute(
675
+ DeviceMeterBatchAutoUnBindBackgroundJob.class,
676
+ message
677
+ );
678
+ }
679
+ }
680
+ ```
681
+
682
+ ### 9.2 在 Controller 中触发
683
+
684
+ ```java
685
+ @PubAction(value = "/batchUnbind", method = RequestMethod.POST)
686
+ public void batchUnbind(@RequestBody List<UUID> deviceIds) {
687
+ // 创建任务
688
+ UUID taskId = batchOperateFacade.createTask("批量解绑设备");
689
+
690
+ // 保存数据
691
+ UUID dataId = batchOperateFacade.saveData(deviceIds);
692
+
693
+ // 构建消息
694
+ BatchOperateMessageDTO message = new BatchOperateMessageDTO();
695
+ message.setTaskId(taskId);
696
+ message.setDataId(dataId);
697
+
698
+ // 触发后台任务
699
+ backgroundJobExecutor.execute(
700
+ DeviceMeterBatchAutoUnBindBackgroundJob.class,
701
+ message
702
+ );
703
+ }
704
+ ```
705
+
706
+ ---
707
+
708
+ ## 十、相关文档
709
+
710
+ - 定时任务开发规范
711
+ - 批量操作接口规范
712
+ - 异步任务处理指南
713
+
714
+ ---
715
+
716
+ **文档版本**:v1.0
717
+ **最后更新**:2026-01-11
718
+ **维护人**:开发团队
719
+