6aspec 2.0.0-dev.2

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 +296 -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 +227 -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 +211 -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 +48 -0
  108. package/lib/cli.js +318 -0
  109. package/lib/installer.js +333 -0
  110. package/package.json +62 -0
@@ -0,0 +1,529 @@
1
+ # 事件订阅开发规范
2
+
3
+ ## 概述
4
+
5
+ 本规范定义了系统中事件订阅的开发标准,包括包结构、注解使用、命名规范、异常处理等。
6
+
7
+ ---
8
+
9
+ ## 一、事件订阅模式
10
+
11
+ 系统支持两种事件订阅模式,推荐使用**框架 EventSubscriber 模式**。
12
+
13
+ ### 1.1 框架 EventSubscriber 模式(推荐)
14
+
15
+ **适用场景**:所有事件订阅场景
16
+
17
+ **包路径规范**:
18
+ ```
19
+ **.event.subscriber
20
+ **.event.subscriber.{业务分类} # 可选:按业务领域分类
21
+ ```
22
+
23
+ **开发规范**:
24
+ - **继承**:`extends EventSubscriber<TEvent, TResult>`
25
+ - `TEvent`:事件类型(如 `ContractSignedEvent`)
26
+ - `TResult`:返回结果类型,通常为 `Void`
27
+ - **类注解**:
28
+ - `@Subscriber(name = "订阅名称")` - 必需,定义订阅器名称
29
+ - `@Slf4j` - 必需,用于日志记录
30
+ - **必须实现的方法**:
31
+ - `subscribe(TEvent message)` - 订阅处理逻辑
32
+ - `getMessageType()` - 返回消息类型引用
33
+
34
+ **参考示例**:
35
+
36
+ ```java
37
+ package com.mysoft.rental.caretaker.event.subscriber;
38
+
39
+ import com.fasterxml.jackson.core.type.TypeReference;
40
+ import com.mysoft.framework.common.exception.BusinessLogicException;
41
+ import com.mysoft.framework.common.util.JsonUtil;
42
+ import com.mysoft.framework.event.annotation.Subscriber;
43
+ import com.mysoft.framework.event.api.EventSubscriber;
44
+ import com.mysoft.framework.event.enums.EventResultEnum;
45
+ import com.mysoft.framework.event.info.EventArgument;
46
+ import com.mysoft.framework.event.info.Result;
47
+ import com.mysoft.rental.caretaker.service.checkin.CheckInService;
48
+ import com.mysoft.rental.contract.model.event.contract.ContractSignedEvent;
49
+ import lombok.extern.slf4j.Slf4j;
50
+ import org.springframework.beans.factory.annotation.Autowired;
51
+
52
+ /**
53
+ * 合同已签订事件订阅器 - 自动创建入住单
54
+ *
55
+ * @author yanghui
56
+ * @description 监听合同签订事件,自动创建入住单
57
+ * @date 2025/3/7 16:35
58
+ */
59
+ @Subscriber(name = "入住单创建-合同已签订订阅")
60
+ @Slf4j
61
+ public class ContractSignedEventSubscriber extends EventSubscriber<ContractSignedEvent, Void> {
62
+
63
+ @Autowired
64
+ private CheckInService checkInService;
65
+
66
+ @Override
67
+ public Result<Void> subscribe(ContractSignedEvent message) {
68
+ log.info("入住单创建-收到合同已签订事件:{}", JsonUtil.toString(message));
69
+ try {
70
+ this.checkInService.autoCreatePersonalCheckIn(message.getBusinessIds().get(0));
71
+ } catch (BusinessLogicException e) {
72
+ log.warn("入住单创建-收到合同已签订事件,处理入住单发生业务异常:{}", e.getMessage());
73
+ } catch (Exception e) {
74
+ log.error("入住单创建-收到合同已签订事件,处理入住单发生异常", e);
75
+ }
76
+ return new Result<>(EventResultEnum.SUCCESS);
77
+ }
78
+
79
+ @Override
80
+ public TypeReference<EventArgument<ContractSignedEvent, Void>> getMessageType() {
81
+ return new TypeReference<EventArgument<ContractSignedEvent, Void>>() {};
82
+ }
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 二、包结构规范
89
+
90
+ ### 2.1 基础包结构
91
+
92
+ ```
93
+ com.mysoft.rental.{module}.event.subscriber
94
+ ├── {EventName}Subscriber.java # 顶层事件订阅器
95
+ ├── {业务分类}/ # 按业务领域分类(可选)
96
+ │ └── {EventName}Subscriber.java
97
+ └── ...
98
+ ```
99
+
100
+ ### 2.2 业务分类示例
101
+
102
+ 根据业务复杂度,可以创建子包进行分类:
103
+
104
+ | 分类包名 | 说明 | 示例 |
105
+ |---------|------|------|
106
+ | `device` | 设备相关事件订阅 | `DeviceMeterReadingChargeGeneratedEventSubscriber` |
107
+ | `changeroom` | 换房相关事件订阅 | `ChangeRoomContractAlternationSignedEventSubscriber` |
108
+ | `roomstatus` | 房态相关事件订阅 | `RoomStatusResourceNameChangeSubscriber` |
109
+ | `roomstatusview` | 房态视图相关事件订阅 | - |
110
+ | `smartdevice` | 智能设备相关事件订阅 | `FormalDeviceEleMeterOnOffSubscriber` |
111
+
112
+ **建议**:
113
+ - 订阅器数量少于 5 个:直接放在 `subscriber` 包下
114
+ - 订阅器数量多于 5 个:按业务领域创建子包分类
115
+
116
+ ---
117
+
118
+ ## 三、命名规范
119
+
120
+ ### 3.1 类命名规范
121
+
122
+ **格式**:`{事件名称}{Subscriber/EventSubscriber}`
123
+
124
+ | 场景 | 命名格式 | 示例 |
125
+ |------|---------|------|
126
+ | 通用事件订阅 | `{EventName}Subscriber` | `ContractSignedEventSubscriber` |
127
+ | 特定业务场景 | `{EventName}For{Purpose}Subscriber` | `CheckInHandledEventForResidentPasswordSubscriber` |
128
+
129
+ **命名建议**:
130
+ - 使用清晰、语义化的名称
131
+ - 体现订阅的事件类型和处理目的
132
+ - 以 `Subscriber` 或 `EventSubscriber` 结尾
133
+
134
+ ### 3.2 订阅器名称(@Subscriber name)
135
+
136
+ **格式**:`{业务场景}-{事件描述}`
137
+
138
+ | 场景 | 示例 |
139
+ |------|------|
140
+ | 创建业务单据 | `入住单创建-合同已签订订阅` |
141
+ | 完成业务操作 | `入住完成-自动创建住户密码` |
142
+ | 处理业务事件 | `退房办理-费用结清事件` |
143
+
144
+ **命名规则**:
145
+ - 前半部分:描述业务场景或目标
146
+ - 后半部分:描述触发的事件
147
+ - 使用中文,便于理解和维护
148
+
149
+ ---
150
+
151
+ ## 四、开发规范详解
152
+
153
+ ### 4.1 类结构
154
+
155
+ ```java
156
+ @Subscriber(name = "订阅器名称")
157
+ @Slf4j
158
+ public class XxxEventSubscriber extends EventSubscriber<EventType, ResultType> {
159
+
160
+ // 1. 依赖注入
161
+ @Autowired
162
+ private XxxService xxxService;
163
+
164
+ // 2. 订阅处理方法(必需)
165
+ @Override
166
+ public Result<ResultType> subscribe(EventType message) {
167
+ // 订阅处理逻辑
168
+ }
169
+
170
+ // 3. 消息类型方法(必需)
171
+ @Override
172
+ public TypeReference<EventArgument<EventType, ResultType>> getMessageType() {
173
+ return new TypeReference<EventArgument<EventType, ResultType>>() {};
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### 4.2 subscribe 方法规范
179
+
180
+ #### 必需元素:
181
+ 1. **日志记录**:记录接收到的事件信息
182
+ 2. **异常处理**:捕获业务异常和系统异常
183
+ 3. **返回结果**:返回 `Result<T>` 对象
184
+
185
+ #### 标准模板:
186
+
187
+ ```java
188
+ @Override
189
+ public Result<Void> subscribe(XxxEvent message) {
190
+ log.info("收到XXX事件:{}", JsonUtil.toString(message));
191
+ try {
192
+ // 业务处理逻辑
193
+ xxxService.handleEvent(message.getBusinessIds());
194
+ } catch (BusinessLogicException e) {
195
+ log.warn("处理XXX事件-业务异常:{}", e.getMessage());
196
+ // 或 log.error("处理XXX事件-业务异常", e);
197
+ } catch (Exception e) {
198
+ log.error("处理XXX事件-系统异常", e);
199
+ }
200
+ return new Result<>(EventResultEnum.SUCCESS);
201
+ }
202
+ ```
203
+
204
+ ### 4.3 异常处理规范
205
+
206
+ #### 两层异常捕获:
207
+
208
+ **1. BusinessLogicException(业务异常)**
209
+ - 使用 `log.warn()` 记录(或 `log.error()`,视业务重要性)
210
+ - 不影响事件订阅流程继续执行
211
+ - 通常只记录异常消息
212
+
213
+ ```java
214
+ catch (BusinessLogicException e) {
215
+ log.warn("业务操作失败:{}", e.getMessage());
216
+ }
217
+ ```
218
+
219
+ **2. Exception(系统异常)**
220
+ - 使用 `log.error()` 记录
221
+ - 记录完整异常堆栈信息
222
+ - 确保问题可追溯
223
+
224
+ ```java
225
+ catch (Exception e) {
226
+ log.error("系统异常", e);
227
+ }
228
+ ```
229
+
230
+ #### 返回值规范:
231
+
232
+ **始终返回成功**:
233
+ ```java
234
+ return new Result<>(EventResultEnum.SUCCESS);
235
+ ```
236
+
237
+ **原因**:
238
+ - 保证事件订阅不会因为某个订阅器失败而阻塞其他订阅器
239
+ - 异常已通过日志记录,便于排查
240
+ - 失败重试由框架或业务层面处理
241
+
242
+ ---
243
+
244
+ ## 五、高级特性(可选)
245
+
246
+ ### 5.1 分布式锁
247
+
248
+ **适用场景**:防止并发事件重复处理
249
+
250
+ **实现方式**:
251
+
252
+ ```java
253
+ @Subscriber(name = "退房单创建-合同终止已签订订阅")
254
+ @Slf4j
255
+ public class TerminationSignedEventSubscriber extends EventSubscriber<TerminationSignedEvent, Void> {
256
+
257
+ @Autowired
258
+ private CheckOutService checkOutService;
259
+
260
+ @Autowired
261
+ private BaseDistributedLockFactory baseDistributedLockFactory;
262
+
263
+ @Override
264
+ public Result<Void> subscribe(TerminationSignedEvent message) {
265
+ log.info("退房单创建-收到合同终止已签订事件:{}", JsonUtil.toString(message));
266
+ UUID contractTerminationId = message.getBusinessIds().get(0);
267
+
268
+ // 构建分布式锁
269
+ BaseDistributedLock distributedLock = baseDistributedLockFactory
270
+ .build("checkout_contract-terminationSigned:" + contractTerminationId);
271
+
272
+ try {
273
+ // 尝试获取锁
274
+ if (distributedLock.tryLock(3, TimeUnit.SECONDS)) {
275
+ this.checkOutService.autoCreateCheckOut(contractTerminationId);
276
+ }
277
+ } catch (BusinessLogicException e) {
278
+ log.warn("退房单创建-业务异常:{}", e.getMessage());
279
+ } catch (Exception e) {
280
+ log.error("退房单创建-系统异常", e);
281
+ } finally {
282
+ // 释放锁
283
+ if (distributedLock.isLocked() && distributedLock.isHeldByCurrentThread()) {
284
+ distributedLock.unlock();
285
+ }
286
+ }
287
+ return new Result<>(EventResultEnum.SUCCESS);
288
+ }
289
+
290
+ @Override
291
+ public TypeReference<EventArgument<TerminationSignedEvent, Void>> getMessageType() {
292
+ return new TypeReference<EventArgument<TerminationSignedEvent, Void>>() {};
293
+ }
294
+ }
295
+ ```
296
+
297
+ **关键要点**:
298
+ - **锁命名**:使用业务语义化的锁名称,包含业务ID
299
+ - **超时时间**:根据业务处理时长设置合理的超时时间
300
+ - **finally 释放**:确保在 finally 块中释放锁
301
+ - **判断持有**:释放前判断是否持有锁
302
+
303
+ ### 5.2 批量处理
304
+
305
+ **适用场景**:事件携带多个业务ID
306
+
307
+ ```java
308
+ @Override
309
+ public Result<Void> subscribe(XxxEvent message) {
310
+ log.info("收到XXX事件:{}", JsonUtil.toString(message));
311
+ try {
312
+ // 批量处理
313
+ List<UUID> businessIds = message.getBusinessIds();
314
+ for (UUID businessId : businessIds) {
315
+ try {
316
+ xxxService.handle(businessId);
317
+ } catch (BusinessLogicException e) {
318
+ log.warn("处理业务ID[{}]失败:{}", businessId, e.getMessage());
319
+ // 继续处理下一个,不中断整个批次
320
+ }
321
+ }
322
+ } catch (Exception e) {
323
+ log.error("批量处理异常", e);
324
+ }
325
+ return new Result<>(EventResultEnum.SUCCESS);
326
+ }
327
+ ```
328
+
329
+ ---
330
+
331
+ ## 六、日志规范
332
+
333
+ ### 6.1 日志级别
334
+
335
+ | 场景 | 日志级别 | 格式示例 |
336
+ |------|---------|---------|
337
+ | 接收事件 | INFO | `收到合同已签订事件:{}` |
338
+ | 业务成功 | INFO | `入住单创建成功,入住单ID:{}` |
339
+ | 业务警告 | WARN | `处理XXX事件-业务异常:{}` |
340
+ | 业务异常 | ERROR | `处理XXX事件-业务异常`, e |
341
+ | 系统异常 | ERROR | `处理XXX事件-系统异常`, e |
342
+
343
+ ### 6.2 日志内容规范
344
+
345
+ **必需信息**:
346
+ - 事件描述
347
+ - 业务数据(使用 `JsonUtil.toString()` 序列化)
348
+ - 异常信息或堆栈
349
+
350
+ **示例**:
351
+ ```java
352
+ // 接收事件
353
+ log.info("收到合同已签订事件:{}", JsonUtil.toString(message));
354
+
355
+ // 业务异常(方式一:只记录消息)
356
+ log.warn("入住单创建-收到合同已签订事件,处理入住单发生业务异常:{}", e.getMessage());
357
+
358
+ // 业务异常(方式二:记录堆栈)
359
+ log.error("入住单创建-收到合同已签订事件,处理入住单发生业务异常", e);
360
+
361
+ // 系统异常
362
+ log.error("入住单创建-收到合同已签订事件,处理入住单发生系统异常", e);
363
+ ```
364
+
365
+ ---
366
+
367
+ ## 七、检查清单
368
+
369
+ 在提交代码前,请确认:
370
+
371
+ - [ ] 类名符合命名规范(以 Subscriber 结尾)
372
+ - [ ] 包路径正确(`**.event.subscriber`)
373
+ - [ ] 添加了 `@Subscriber` 和 `@Slf4j` 注解
374
+ - [ ] 继承了 `EventSubscriber<TEvent, TResult>`
375
+ - [ ] 实现了 `subscribe()` 和 `getMessageType()` 方法
376
+ - [ ] 添加了完整的异常处理(BusinessLogicException + Exception)
377
+ - [ ] 添加了日志记录(接收事件 + 异常处理)
378
+ - [ ] 返回 `new Result<>(EventResultEnum.SUCCESS)`
379
+ - [ ] 添加了 JavaDoc 注释(类和方法)
380
+ - [ ] 如需并发控制,使用了分布式锁并正确释放
381
+
382
+ ---
383
+
384
+ ## 八、常见问题
385
+
386
+ ### Q1: 什么时候使用 EventSubscriber 模式,什么时候使用 Spring 事件监听模式?
387
+ - **EventSubscriber 模式**(推荐):
388
+ - 跨系统/跨模块的异步事件
389
+ - 需要解耦的业务场景
390
+ - 需要事件持久化和重试机制
391
+
392
+ - **Spring 事件监听模式**:
393
+ - 同一应用内的简单同步事件
394
+ - 不需要持久化的临时事件
395
+
396
+ ### Q2: 为什么即使处理失败也要返回 SUCCESS?
397
+ - 保证其他订阅器不受影响
398
+ - 避免事件消费失败导致的重复消费
399
+ - 失败已通过日志记录,可人工介入处理
400
+ - 如需重试,应在业务层面实现补偿机制
401
+
402
+ ### Q3: 如何处理事件的幂等性?
403
+ - **方式一**:业务层面实现幂等(推荐)
404
+ - 在 Service 层判断业务是否已处理
405
+ - 使用唯一索引防止重复数据
406
+
407
+ - **方式二**:使用分布式锁
408
+ - 适用于创建类操作
409
+ - 锁的粒度要合理
410
+
411
+ ### Q4: 订阅器数量很多时如何组织?
412
+ - 少于 5 个:直接放在 `subscriber` 包下
413
+ - 多于 5 个:按业务领域创建子包
414
+ - 设备相关 → `device/`
415
+ - 换房相关 → `changeroom/`
416
+ - 房态相关 → `roomstatus/`
417
+
418
+ ### Q5: 如何测试事件订阅器?
419
+ ```java
420
+ @SpringBootTest
421
+ public class ContractSignedEventSubscriberTest {
422
+
423
+ @Autowired
424
+ private ContractSignedEventSubscriber subscriber;
425
+
426
+ @Test
427
+ public void testSubscribe() {
428
+ // 构造事件
429
+ ContractSignedEvent event = new ContractSignedEvent();
430
+ event.setBusinessIds(Collections.singletonList(contractId));
431
+
432
+ // 调用订阅方法
433
+ Result<Void> result = subscriber.subscribe(event);
434
+
435
+ // 验证结果
436
+ assertEquals(EventResultEnum.SUCCESS, result.getResultCode());
437
+ }
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## 九、最佳实践
444
+
445
+ ### 9.1 单一职责
446
+
447
+ 每个订阅器只处理一个明确的业务场景:
448
+
449
+ ✅ **推荐**:
450
+ ```java
451
+ @Subscriber(name = "入住单创建-合同已签订订阅")
452
+ public class ContractSignedEventSubscriber {
453
+ // 只负责创建入住单
454
+ }
455
+
456
+ @Subscriber(name = "账单生成-合同已签订订阅")
457
+ public class ContractSignedForBillingEventSubscriber {
458
+ // 只负责生成账单
459
+ }
460
+ ```
461
+
462
+ ❌ **不推荐**:
463
+ ```java
464
+ @Subscriber(name = "合同已签订处理")
465
+ public class ContractSignedEventSubscriber {
466
+ // 处理创建入住单、生成账单、发送通知等多个职责
467
+ }
468
+ ```
469
+
470
+ ### 9.2 避免长事务
471
+
472
+ 事件订阅器应该快速处理,避免长时间阻塞:
473
+
474
+ ✅ **推荐**:
475
+ ```java
476
+ @Override
477
+ public Result<Void> subscribe(XxxEvent message) {
478
+ // 快速处理,复杂逻辑交给 Service 异步处理
479
+ xxxService.asyncHandle(message.getBusinessIds());
480
+ return new Result<>(EventResultEnum.SUCCESS);
481
+ }
482
+ ```
483
+
484
+ ❌ **不推荐**:
485
+ ```java
486
+ @Override
487
+ public Result<Void> subscribe(XxxEvent message) {
488
+ // 在订阅器中执行大量复杂计算或外部调用
489
+ for (int i = 0; i < 10000; i++) {
490
+ // 复杂处理...
491
+ }
492
+ return new Result<>(EventResultEnum.SUCCESS);
493
+ }
494
+ ```
495
+
496
+ ### 9.3 合理使用日志
497
+
498
+ 记录关键信息,避免过度日志:
499
+
500
+ ✅ **推荐**:
501
+ ```java
502
+ log.info("收到合同已签订事件,合同ID:{}", message.getBusinessIds());
503
+ log.error("处理入住单创建失败", e);
504
+ ```
505
+
506
+ ❌ **不推荐**:
507
+ ```java
508
+ log.info("开始处理事件");
509
+ log.info("事件内容:{}", message);
510
+ log.info("获取业务ID");
511
+ log.info("调用服务");
512
+ log.info("处理完成");
513
+ // 过多的 INFO 日志会淹没关键信息
514
+ ```
515
+
516
+ ---
517
+
518
+ ## 十、相关文档
519
+
520
+ - 事件发布规范
521
+ - 领域事件设计指南
522
+ - 分布式锁使用指南
523
+
524
+ ---
525
+
526
+ **文档版本**:v1.0
527
+ **最后更新**:2026-01-11
528
+ **维护人**:开发团队
529
+
@@ -0,0 +1,90 @@
1
+ ---
2
+ alwaysApply: false
3
+ ---
4
+ # 项目结构与代码规范
5
+
6
+ 本项目采用多模块分层架构,目录结构及代码规范如下:
7
+
8
+ ## 目录结构
9
+ - 每个业务领域(如合同、客户、租控等)为一个独立模块,模块下分为:
10
+ - `-service`:服务实现层
11
+ - `-interfaces`:接口层
12
+ - `-model`:数据模型层
13
+ - 公共模块如 `rental-common`、`rental-utility` 存放通用工具、基础设施、公共接口和模型。
14
+
15
+
16
+ ## 命名规范
17
+ - 类/接口名:大驼峰,接口以 `Facade`、`Service`、`Controller` 结尾
18
+ - 方法名:小驼峰,动词+业务含义
19
+ - 常量名:全大写+下划线
20
+ - 枚举名:以 `Enum` 结尾,枚举值全大写
21
+
22
+ ## 注释规范
23
+ - 类、方法、枚举等均有 Javadoc 注释,说明用途、参数、返回值、作者、日期
24
+ - 重要业务逻辑、异常、特殊处理有详细注释
25
+
26
+ ## 接口与实现
27
+ - 接口定义在 `interfaces` 层,通常以 `Facade` 结尾
28
+ - 实现类在 `service` 层,以 `Impl` 结尾,`@Service` 注解
29
+ - 参数、返回值多用 DTO
30
+
31
+
32
+ ## 设计模式
33
+ - 常用:Facade、Strategy、Template、Enum、注解驱动
34
+ - 业务异常采用统一枚举管理
35
+
36
+ ## 分层规范
37
+ - 必须遵循三层架构规范来写代码
38
+ ```
39
+ 入口->service层->Repository
40
+ -> gateway
41
+ ```
42
+ - 入口包括controller,定时任务,事件订阅,后台任务
43
+ - service层:处理核心业务逻辑
44
+ - Repository层:数据访问层,数据操作
45
+ - gateway层:封装夸模块,以及跨系统调用
46
+
47
+ ## 上下文用户获取
48
+ - B端用户使用:ContextManager
49
+ ```java
50
+ @Autowired
51
+ private ContextManager contextManager;
52
+
53
+ contextManager.fetchContextInfo()
54
+ ```
55
+ - C端用户使用:类`com.rental.assetrental.component.customerauth.context.IdentityUserInfoThreadContextHolder`
56
+ ```java
57
+ IdentityUserInfoThreadContextHolder.get().getTocUserId();
58
+ ```
59
+
60
+
61
+ ## Service 层代码规范
62
+
63
+ - Service 不需要接口,直接以Service或者ApplicationService结尾,需要继承AppService类;如果是Facade接口,则接口放在interfaces层,实现写在service层,并以Impl结尾
64
+ - 实现类加 `@Service` 注解,扩展点用自定义注解(如 `@Extension`),需要事务支持加 `@Transactional`。
65
+ - 依赖注入用 `@Autowired`。
66
+ - 方法参数、返回值优先用 DTO/Command/Query/VO,避免直接用实体或原始类型。
67
+ - 只处理业务逻辑,不做参数校验,数据操作通过 Repository/DAO。
68
+ - 业务异常抛出自定义异常并配合统一异常码,异常需记录日志并保证事务回滚。
69
+ - 类、方法需有 Javadoc 注释,重要逻辑有详细注释。
70
+ - 方法名小驼峰,动词+业务含义,代码整洁,常量提取。
71
+ - 常用设计模式:模板方法、策略、工厂、单例/枚举,扩展点用接口+注解+工厂注册。
72
+ - Service 层不直接暴露远程服务,需通过接口层(facade)暴露,业务锁、幂等、分布式事务有统一机制。
73
+
74
+ ## Repository 层代码规范
75
+
76
+ - Repository 类名以 `Repository` 结尾,放在 `repository` 包下,继承 `BaseRepository`,DAO 以 `Dao` 结尾,实体以 `Entity` 结尾。
77
+ - 类上加 `@Repository` 注解,依赖注入用 `@Autowired`。
78
+ - 方法参数优先用实体、DTO、ID、集合,方法名以 `get`、`find`、`query`、`insert`、`update`、`delete`、`save` 开头。
79
+ - 只负责数据持久化,不包含业务逻辑,复杂查询/批量/分页用 Wrapper、Page 等实现。
80
+ - 实体与 DTO/VO 转换用工具类,数据操作异常需抛出自定义异常或统一异常。
81
+ - 需要事务支持的方法加 `@Transactional`,不捕获并吞掉异常。
82
+ - 类、方法需有 Javadoc 注释,重要操作有详细注释。
83
+ - 方法名小驼峰,动词+业务含义,代码整洁,常量提取。
84
+ - 仅做数据访问,不包含业务逻辑,命名规范统一,支持多数据源扩展。
85
+
86
+
87
+ ## 其他
88
+ - 统一使用 Lombok
89
+ - 统一异常码、常量、业务单元编码有详细分段和注释
90
+ - 代码风格整洁,缩进、空行、注释规范