ai-engineering-init 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.claude/skills/leniu-java-export/SKILL.md +389 -95
  2. package/.codex/skills/leniu-java-export/SKILL.md +389 -95
  3. package/.cursor/skills/bug-detective/SKILL.md +19 -19
  4. package/.cursor/skills/leniu-java-export/SKILL.md +389 -95
  5. package/.cursor/skills/project-navigator/SKILL.md +164 -258
  6. package/package.json +7 -1
  7. package/scripts/build-skills.js +180 -0
  8. package/src/platform-map.json +56 -0
  9. package/src/skills/add-skill/SKILL.md +488 -0
  10. package/src/skills/add-todo/SKILL.md +269 -0
  11. package/src/skills/api-development/SKILL.md +266 -0
  12. package/src/skills/architecture-design/SKILL.md +262 -0
  13. package/src/skills/backend-annotations/SKILL.md +302 -0
  14. package/src/skills/banana-image/CHANGELOG.md +37 -0
  15. package/src/skills/banana-image/README.md +146 -0
  16. package/src/skills/banana-image/SKILL.md +171 -0
  17. package/src/skills/banana-image/assets/logo.png +0 -0
  18. package/src/skills/banana-image/references/advanced-usage.md +189 -0
  19. package/src/skills/banana-image/scripts/apply_template.py +125 -0
  20. package/src/skills/banana-image/scripts/banana_image_exec.ts +412 -0
  21. package/src/skills/banana-image/scripts/batch_prep.py +82 -0
  22. package/src/skills/banana-image/scripts/package-lock.json +1437 -0
  23. package/src/skills/banana-image/scripts/package.json +18 -0
  24. package/src/skills/banana-image/scripts/requirements.txt +10 -0
  25. package/src/skills/banana-image/templates/poster.json +22 -0
  26. package/src/skills/banana-image/templates/product.json +17 -0
  27. package/src/skills/banana-image/templates/social.json +22 -0
  28. package/src/skills/banana-image/templates/thumbnail.json +17 -0
  29. package/src/skills/brainstorm/SKILL.md +216 -0
  30. package/src/skills/bug-detective/SKILL.md +256 -0
  31. package/src/skills/bug-detective/references/error-patterns.md +242 -0
  32. package/src/skills/check/SKILL.md +367 -0
  33. package/src/skills/code-patterns/SKILL.md +280 -0
  34. package/src/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  35. package/src/skills/codex-code-review/SKILL.md +135 -0
  36. package/src/skills/collaborating-with-codex/SKILL.md +174 -0
  37. package/src/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
  38. package/src/skills/collaborating-with-gemini/SKILL.md +194 -0
  39. package/src/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
  40. package/src/skills/crud/SKILL.md +265 -0
  41. package/src/skills/crud-development/SKILL.md +409 -0
  42. package/src/skills/data-permission/SKILL.md +292 -0
  43. package/src/skills/data-permission/references/custom-data-scope.md +90 -0
  44. package/src/skills/database-ops/SKILL.md +407 -0
  45. package/src/skills/dev/SKILL.md +187 -0
  46. package/src/skills/error-handler/SKILL.md +371 -0
  47. package/src/skills/file-oss-management/SKILL.md +255 -0
  48. package/src/skills/file-oss-management/references/entities.md +105 -0
  49. package/src/skills/file-oss-management/references/service-impl.md +104 -0
  50. package/src/skills/git-workflow/SKILL.md +397 -0
  51. package/src/skills/init-docs/SKILL.md +194 -0
  52. package/src/skills/json-serialization/SKILL.md +357 -0
  53. package/src/skills/leniu-api-development/SKILL.md +319 -0
  54. package/src/skills/leniu-api-development/references/real-examples.md +273 -0
  55. package/src/skills/leniu-architecture-design/SKILL.md +383 -0
  56. package/src/skills/leniu-backend-annotations/SKILL.md +277 -0
  57. package/src/skills/leniu-brainstorm/SKILL.md +242 -0
  58. package/src/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  59. package/src/skills/leniu-code-patterns/SKILL.md +411 -0
  60. package/src/skills/leniu-crud-development/SKILL.md +404 -0
  61. package/src/skills/leniu-crud-development/references/templates.md +597 -0
  62. package/src/skills/leniu-customization-location/SKILL.md +410 -0
  63. package/src/skills/leniu-data-permission/SKILL.md +341 -0
  64. package/src/skills/leniu-database-ops/SKILL.md +426 -0
  65. package/src/skills/leniu-error-handler/SKILL.md +462 -0
  66. package/src/skills/leniu-java-amount-handling/SKILL.md +461 -0
  67. package/src/skills/leniu-java-code-style/SKILL.md +510 -0
  68. package/src/skills/leniu-java-concurrent/SKILL.md +400 -0
  69. package/src/skills/leniu-java-entity/SKILL.md +237 -0
  70. package/src/skills/leniu-java-entity/references/templates.md +237 -0
  71. package/src/skills/leniu-java-export/SKILL.md +570 -0
  72. package/src/skills/leniu-java-logging/SKILL.md +229 -0
  73. package/src/skills/leniu-java-logging/references/data-mask.md +46 -0
  74. package/src/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  75. package/src/skills/leniu-java-mq/SKILL.md +338 -0
  76. package/src/skills/leniu-java-mybatis/SKILL.md +267 -0
  77. package/src/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  78. package/src/skills/leniu-java-report-query-param/SKILL.md +291 -0
  79. package/src/skills/leniu-java-task/SKILL.md +367 -0
  80. package/src/skills/leniu-java-total-line/SKILL.md +196 -0
  81. package/src/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  82. package/src/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  83. package/src/skills/leniu-mealtime/SKILL.md +215 -0
  84. package/src/skills/leniu-redis-cache/SKILL.md +331 -0
  85. package/src/skills/leniu-report-customization/SKILL.md +335 -0
  86. package/src/skills/leniu-report-customization/references/table-fields.md +93 -0
  87. package/src/skills/leniu-report-standard-customization/SKILL.md +328 -0
  88. package/src/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  89. package/src/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  90. package/src/skills/leniu-security-guard/SKILL.md +306 -0
  91. package/src/skills/leniu-utils-toolkit/SKILL.md +380 -0
  92. package/src/skills/mysql-debug/SKILL.md +364 -0
  93. package/src/skills/next/SKILL.md +137 -0
  94. package/src/skills/openspec-apply-change/SKILL.md +165 -0
  95. package/src/skills/openspec-archive-change/SKILL.md +122 -0
  96. package/src/skills/openspec-bulk-archive-change/SKILL.md +254 -0
  97. package/src/skills/openspec-continue-change/SKILL.md +126 -0
  98. package/src/skills/openspec-explore/SKILL.md +299 -0
  99. package/src/skills/openspec-ff-change/SKILL.md +109 -0
  100. package/src/skills/openspec-new-change/SKILL.md +82 -0
  101. package/src/skills/openspec-onboard/SKILL.md +414 -0
  102. package/src/skills/openspec-sync-specs/SKILL.md +146 -0
  103. package/src/skills/openspec-verify-change/SKILL.md +176 -0
  104. package/src/skills/performance-doctor/SKILL.md +303 -0
  105. package/src/skills/progress/SKILL.md +193 -0
  106. package/src/skills/project-navigator/SKILL.md +211 -0
  107. package/src/skills/redis-cache/SKILL.md +333 -0
  108. package/src/skills/redis-cache/references/listeners.md +23 -0
  109. package/src/skills/scheduled-jobs/SKILL.md +314 -0
  110. package/src/skills/security-guard/SKILL.md +353 -0
  111. package/src/skills/security-guard/references/encrypt-config.md +103 -0
  112. package/src/skills/security-guard/references/sensitive-strategies.md +42 -0
  113. package/src/skills/sms-mail/SKILL.md +308 -0
  114. package/src/skills/sms-mail/references/mail-config.md +88 -0
  115. package/src/skills/sms-mail/references/sms-config.md +74 -0
  116. package/src/skills/social-login/SKILL.md +266 -0
  117. package/src/skills/social-login/references/provider-configs.md +118 -0
  118. package/src/skills/start/SKILL.md +154 -0
  119. package/src/skills/store-pc/SKILL.md +366 -0
  120. package/src/skills/sync/SKILL.md +149 -0
  121. package/src/skills/task-tracker/SKILL.md +307 -0
  122. package/src/skills/tech-decision/SKILL.md +393 -0
  123. package/src/skills/tenant-management/SKILL.md +288 -0
  124. package/src/skills/tenant-management/references/tenant-scenarios.md +91 -0
  125. package/src/skills/test-development/SKILL.md +301 -0
  126. package/src/skills/test-development/references/parameterized-examples.md +119 -0
  127. package/src/skills/ui-pc/SKILL.md +438 -0
  128. package/src/skills/update-status/SKILL.md +159 -0
  129. package/src/skills/utils-toolkit/SKILL.md +362 -0
  130. package/src/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  131. package/src/skills/websocket-sse/SKILL.md +271 -0
  132. package/src/skills/workflow-engine/SKILL.md +321 -0
@@ -0,0 +1,338 @@
1
+ ---
2
+ name: leniu-java-mq
3
+ description: |
4
+ leniu-tengyun-core / leniu-yunshitang 项目消息队列规范。当使用消息队列(MQ)时使用此skill,包括消息发送、消费和事务消息规范。
5
+
6
+ 触发场景:
7
+ - 使用消息队列发送/消费消息
8
+ - 实现延迟消息(延迟队列)
9
+ - 消息消费失败重试处理
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
+ 触发词:消息队列、MQ、MqUtil、@MqConsumer、延迟消息、消息重试、事务消息
17
+ ---
18
+
19
+ # leniu-tengyun-core 消息队列规范
20
+
21
+ ## 项目特征
22
+
23
+ | 特征 | 说明 |
24
+ |------|------|
25
+ | **包名前缀** | `net.xnzn.core.*` |
26
+ | **JDK 版本** | 21 |
27
+ | **消息工具** | `MqUtil` |
28
+ | **消费者注解** | `@MQMessageListener(group, topic, tag)` |
29
+ | **消费者接口** | `implements MQListener<MqPayload<String>>` |
30
+ | **主题常量** | `LeMqConstant.Topic` |
31
+ | **延迟枚举** | `LeMqConstant.DelayDuration` |
32
+ | **JSON工具** | `JacksonUtil` |
33
+ | **异常类** | `LeException` |
34
+
35
+ ## 核心架构:三层分工
36
+
37
+ ```
38
+ MsgSend(静态工具类)→ Listener(接收消息)→ Handler(分发处理)
39
+ ```
40
+
41
+ | 层 | 职责 | 示例 |
42
+ |----|------|------|
43
+ | **XxxMessageSend** | 静态工具类,封装消息发送逻辑 | `OrderMessageSend` |
44
+ | **XxxMqListenerYyy** | 消费者,接收 MQ 消息并分发到 Handler | `OrderMqListenerAsyncSave` |
45
+ | **XxxMqHandler** | 业务处理,统一处理各类消息 | `OrderMqHandler` |
46
+
47
+ ## 消息发送
48
+
49
+ ### 三种发送方式
50
+
51
+ ```java
52
+ import net.xnzn.core.common.mq.MqUtil;
53
+ import net.xnzn.core.common.constant.LeMqConstant;
54
+ import net.xnzn.core.common.utils.JacksonUtil;
55
+
56
+ // 1. 普通消息(立即发送)
57
+ MqUtil.send(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.XXX_TOPIC);
58
+
59
+ // 2. 事务消息(在 DB 事务提交后发送,保证一致性)
60
+ MqUtil.sendByTxEnd(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.XXX_TOPIC);
61
+
62
+ // 3. 延迟消息(指定延迟时长后触发)
63
+ MqUtil.sendDelay(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.XXX_TOPIC, LeMqConstant.DelayDuration.ONE_MINUTE);
64
+ ```
65
+
66
+ **关键点**:
67
+ - 消息体必须用 `JacksonUtil.writeValueAsString()` 序列化为 **String** 再发送
68
+ - 事务消息用 `sendByTxEnd()`(如 DB 事务回滚则不发送)
69
+ - 延迟时间用 `LeMqConstant.DelayDuration` 枚举(不用 `Duration.ofMinutes()`)
70
+
71
+ ### DelayDuration 枚举(常用值)
72
+
73
+ ```java
74
+ LeMqConstant.DelayDuration.ONE_MINUTE // 1分钟
75
+ LeMqConstant.DelayDuration.THIRTY_MINUTES // 30分钟
76
+ // 其他枚举值参见 LeMqConstant.DelayDuration
77
+ ```
78
+
79
+ ### 消息发送类(静态工具类模式)
80
+
81
+ ```java
82
+ /**
83
+ * 订单消息发送
84
+ * 关键特征:
85
+ * 1. 不是 @Component,是纯静态工具类
86
+ * 2. 私有构造器
87
+ * 3. PO 中包含 traceId 和 tenantId(用于跨线程追踪)
88
+ */
89
+ @Slf4j
90
+ public class XxxMessageSend {
91
+
92
+ private XxxMessageSend() {} // 禁止实例化
93
+
94
+ private static final String MQ_ERROR_LOG = "发送MQ消息失败";
95
+
96
+ /**
97
+ * 普通消息(适用于非事务性发送)
98
+ */
99
+ public static void sendXxxEvent(XxxPO po) {
100
+ log.info("[XxxMQ]发送xxx事件");
101
+ po.setTraceId(LogUtil.getCurrentTraceId());
102
+ po.setTenantId(TenantContextHolder.getTenantId());
103
+ MqUtil.send(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.XXX_TOPIC);
104
+ }
105
+
106
+ /**
107
+ * 事务消息(在 @Transactional 方法中使用,事务提交后才发送)
108
+ */
109
+ public static void sendXxxEventByTx(XxxPO po) {
110
+ log.info("[XxxMQ]发送xxx事务消息");
111
+ po.setTraceId(LogUtil.getCurrentTraceId());
112
+ po.setTenantId(TenantContextHolder.getTenantId());
113
+ try {
114
+ MqUtil.sendByTxEnd(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.XXX_TOPIC);
115
+ } catch (Exception e) {
116
+ log.error(MQ_ERROR_LOG, e);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * 延迟消息(超时取消等场景)
122
+ */
123
+ public static void sendXxxDelay(XxxPO po, LeMqConstant.DelayDuration delayDuration) {
124
+ log.info("[XxxMQ]发送xxx延迟消息");
125
+ po.setTraceId(LogUtil.getCurrentTraceId());
126
+ po.setTenantId(TenantContextHolder.getTenantId());
127
+ MqUtil.sendDelay(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.XXX_TOPIC, delayDuration);
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### PO 消息体规范
133
+
134
+ ```java
135
+ import lombok.Data;
136
+
137
+ /**
138
+ * MQ 消息 PO(Message Payload Object)
139
+ * 必须包含 traceId 和 tenantId 字段
140
+ */
141
+ @Data
142
+ public class XxxPO {
143
+
144
+ /** 链路追踪ID */
145
+ private String traceId;
146
+
147
+ /** 租户ID */
148
+ private String tenantId;
149
+
150
+ /** 业务字段 */
151
+ private Long orderId;
152
+ private String outTradeNo;
153
+ // 其他字段...
154
+ }
155
+ ```
156
+
157
+ ## 消息消费
158
+
159
+ ### Listener 类(真实代码模式)
160
+
161
+ ```java
162
+ import lombok.extern.slf4j.Slf4j;
163
+ import net.xnzn.core.common.mq.MqPayload;
164
+ import net.xnzn.framework.mq.MQListener;
165
+ import net.xnzn.framework.mq.MQMessageListener;
166
+ import org.springframework.beans.factory.annotation.Autowired;
167
+ import org.springframework.context.annotation.Lazy;
168
+
169
+ /**
170
+ * MQ 消费者 Listener
171
+ * @see LeMqConstant.Topic#XXX_TOPIC
172
+ */
173
+ @Slf4j
174
+ @MQMessageListener(
175
+ group = "module-xxx-topic-name", // 消费组,格式:模块名-topic-tag
176
+ topic = "xxx", // Topic 名称
177
+ tag = "xxx-topic-name" // Tag 名称(对应 LeMqConstant.Topic)
178
+ )
179
+ public class XxxMqListenerYyy implements MQListener<MqPayload<String>> {
180
+
181
+ @Autowired
182
+ @Lazy // ⚠️ 必须 @Lazy,避免循环依赖
183
+ private XxxMqHandler xxxMqHandler;
184
+
185
+ @Override
186
+ public void onMessage(MqPayload<String> payload) {
187
+ // 委托给 Handler 处理,使用方法引用
188
+ xxxMqHandler.handleMessage(payload, XxxPO.class, XxxMqHandler::handleXxx);
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Handler 类(统一处理消息)
194
+
195
+ ```java
196
+ import cn.hutool.core.text.CharSequenceUtil;
197
+ import com.pig4cloud.pigx.common.core.exception.LeException;
198
+ import lombok.extern.slf4j.Slf4j;
199
+ import net.xnzn.core.common.export.util.I18nUtil;
200
+ import net.xnzn.core.common.mq.MqPayload;
201
+ import net.xnzn.core.common.utils.JacksonUtil;
202
+ import net.xnzn.framework.data.tenant.TenantContextHolder;
203
+ import org.springframework.beans.factory.annotation.Autowired;
204
+ import org.springframework.context.annotation.Lazy;
205
+ import org.springframework.stereotype.Service;
206
+
207
+ import java.util.function.BiConsumer;
208
+
209
+ @Slf4j
210
+ @Service
211
+ public class XxxMqHandler {
212
+
213
+ @Lazy
214
+ @Autowired
215
+ private XxxService xxxService;
216
+
217
+ /**
218
+ * 统一处理调用(核心模板方法)
219
+ * 负责:反序列化、设置租户上下文、异常兜底
220
+ */
221
+ public <T> void handleMessage(MqPayload<String> payload, Class<T> clz, BiConsumer<XxxMqHandler, T> handleFunc) {
222
+ I18nUtil.loadDefaultLocale();
223
+ try {
224
+ log.info("[Xxx消息]收到消息 {}", payload);
225
+ T payloadData = JacksonUtil.readValue(payload.getData(), clz);
226
+ if (payloadData != null) {
227
+ TenantContextHolder.setTenantId(payload.getTenantId()); // 设置租户上下文
228
+ handleFunc.accept(this, payloadData);
229
+ } else {
230
+ log.error("[Xxx消息]解析失败");
231
+ }
232
+ } catch (Exception e) {
233
+ log.error("[Xxx消息]处理异常", e);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * 处理 xxx 事件
239
+ */
240
+ public void handleXxx(XxxPO payload) {
241
+ try {
242
+ log.info("[Xxx事件]MQ消费:开始");
243
+ xxxService.processXxx(payload);
244
+ log.info("[Xxx事件]MQ消费:消息消费完成");
245
+ } catch (Exception e) {
246
+ log.error("[Xxx事件]MQ消费:处理异常", e);
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ ## 常见场景
253
+
254
+ ### 场景1:事务消息(下单后通知)
255
+
256
+ ```java
257
+ @Transactional(rollbackFor = Exception.class)
258
+ public void createOrder(OrderParam param) {
259
+ // 1. 保存订单
260
+ OrderInfo order = OrderInfo.newDefaultInstance();
261
+ order.setCanteenId(param.getCanteenId());
262
+ orderMapper.insert(order);
263
+
264
+ // 2. 事务提交后发送消息(保证一致性)
265
+ OrderPlacedPO po = new OrderPlacedPO();
266
+ po.setOrderInfo(order);
267
+ OrderMessageSend.sendOrderPlacedByTx(po); // 内部使用 sendByTxEnd
268
+
269
+ log.info("订单创建成功,orderId:{}", order.getId());
270
+ }
271
+ ```
272
+
273
+ ### 场景2:延迟消息(订单超时取消)
274
+
275
+ ```java
276
+ public static LocalDateTime sendOrderTimeout(String macOrderId, LeMqConstant.DelayDuration delayDuration) {
277
+ log.info("[订单MQv3]发送未支付订单异步支付超时通知");
278
+ OrderCancelPO po = new OrderCancelPO();
279
+ po.setMacOrderId(macOrderId);
280
+ po.setTenantId(TenantContextHolder.getTenantId());
281
+ po.setTraceId(LogUtil.getCurrentTraceId());
282
+
283
+ // 延迟发送
284
+ MqUtil.sendDelay(JacksonUtil.writeValueAsString(po), LeMqConstant.Topic.ORDER_V3_ASYNC_TIMEOUT, delayDuration);
285
+
286
+ // 返回预计触发时间
287
+ return LocalDateTime.now().plusSeconds(delayDuration.getMilliseconds() / 1000);
288
+ }
289
+ ```
290
+
291
+ ### 场景3:带 Redisson 分布式锁的 MQ 消费
292
+
293
+ ```java
294
+ public void orderAsyncSave(OrderSavePO payload) {
295
+ // 消费时加分布式锁(防止并发处理同一订单)
296
+ RLock lock = RedisUtil.getLock(OrderCacheConstants.orderCacheSaveLockKey(payload.getMacOrderId()));
297
+ lock.lock();
298
+ try {
299
+ log.info("[订单异步保存]MQ消费:开始");
300
+ doSaveOrder(payload);
301
+ log.info("[订单异步保存]MQ消费:消息消费完成");
302
+ } catch (Exception e) {
303
+ log.error("[订单异步保存]MQ消费:处理异常", e);
304
+ } finally {
305
+ // 安全释放锁
306
+ try {
307
+ if (lock.isHeldByCurrentThread() && lock.isLocked()) {
308
+ lock.unlock();
309
+ }
310
+ } catch (Exception e) {
311
+ log.error("解锁异常", e);
312
+ }
313
+ }
314
+ }
315
+ ```
316
+
317
+ ## 日志规范
318
+
319
+ ```java
320
+ // 发送端日志格式
321
+ log.info("[模块MQv3]发送xxx事件");
322
+
323
+ // 消费端日志格式
324
+ log.info("[xxx事件]MQ消费:开始");
325
+ log.info("[xxx事件]MQ消费:消息消费完成");
326
+ log.error("[xxx事件]MQ消费:处理异常", e);
327
+ ```
328
+
329
+ ## 常见错误
330
+
331
+ | 错误写法 | 正确写法 | 说明 |
332
+ |---------|---------|------|
333
+ | `MqUtil.send(dto, topic)` 直接传对象 | `MqUtil.send(JacksonUtil.writeValueAsString(dto), topic)` | 必须先序列化为 String |
334
+ | `@MqConsumer(topic = ...)` | `@MQMessageListener(group, topic, tag)` + `implements MQListener<MqPayload<String>>` | 实际框架注解不同 |
335
+ | `Duration.ofMinutes(30)` | `LeMqConstant.DelayDuration.THIRTY_MINUTES` | 延迟枚举不是 Duration |
336
+ | 忘记在 PO 中设置 `traceId`/`tenantId` | `po.setTraceId(LogUtil.getCurrentTraceId())` | 多租户追踪必须设置 |
337
+ | 消费方法直接 `@Autowired` 服务 | `@Autowired @Lazy` | 避免循环依赖 |
338
+ | 在 MQ 发送类上加 `@Component` | 纯静态工具类(私有构造器,不注入 Spring) | 发送类是静态工具 |
@@ -0,0 +1,267 @@
1
+ ---
2
+ name: leniu-java-mybatis
3
+ description: |
4
+ leniu-tengyun-core 项目 MyBatis/MyBatis-Plus 使用规范。当编写 Mapper、XML 映射、分页查询时使用此 skill。
5
+
6
+ 触发场景:
7
+ - 编写 Mapper 接口(extends BaseMapper)
8
+ - 编写 MyBatis XML 映射文件(动态 SQL)
9
+ - 使用 LambdaQueryWrapper 构建查询
10
+ - 分页查询(PageHelper + PageVO)
11
+ - 租户隔离控制(@InterceptorIgnore)
12
+
13
+ 触发词:MyBatis、MyBatisPlus、Mapper、LambdaQueryWrapper、XML映射、动态SQL、BaseMapper、分页查询、租户隔离、报表Mapper、PageHelper
14
+ ---
15
+
16
+ # leniu MyBatis 规范
17
+
18
+ ## 项目特征速查
19
+
20
+ | 项 | 值 |
21
+ |---|---|
22
+ | XML 位置 | **与 Mapper 接口同目录**(非 `resources/mapper/`) |
23
+ | 分页 | PageHelper → `PageMethod.startPage(PageDTO)` → `PageVO.of(list)` |
24
+ | 逻辑删除 | **1=删除,2=正常**(与 RuoYi 相反) |
25
+ | Service | 无接口,直接 `@Service` 类,Mapper 字段名统一用 `baseMapper` |
26
+ | 循环依赖 | 跨模块依赖用 `@Autowired @Lazy` |
27
+
28
+ ## Mapper 接口模板
29
+
30
+ ### 基础 Mapper
31
+
32
+ ```java
33
+ @Mapper
34
+ public interface XxxMapper extends BaseMapper<XxxEntity> {
35
+
36
+ List<XxxVO> listByParam(@Param("param") XxxParam param);
37
+ }
38
+ ```
39
+
40
+ ### 忽略租户隔离(方法级)
41
+
42
+ ```java
43
+ @Mapper
44
+ public interface XxxMapper extends BaseMapper<XxxEntity> {
45
+
46
+ @InterceptorIgnore(tenantLine = "true")
47
+ List<XxxVO> queryWithoutTenant(@Param("param") XxxParam param);
48
+ }
49
+ ```
50
+
51
+ ### 全量忽略拦截器(类级别)
52
+
53
+ ```java
54
+ @Mapper
55
+ @InterceptorIgnore // 无参数,跳过所有拦截器(租户、数据权限等)
56
+ public interface XxxMapper extends BaseMapper<XxxEntity>, BaseExistsMapper<XxxEntity> {
57
+
58
+ List<XxxVO> listVoByIds(@Param("orderIds") List<Long> ids, @Param("tenantId") String tenantId);
59
+
60
+ @QueryExtension
61
+ List<XxxIdDateVO> queryXxx(@Param("param") XxxSearchParam param,
62
+ @Param("permission") XxxUserPermissionDTO permission);
63
+ }
64
+ ```
65
+
66
+ - `BaseExistsMapper` 提供 `existsOne(Wrapper)` 方法(比 `selectCount > 0` 高效)
67
+ - 适用于数据量大、需跨租户查询的核心表
68
+
69
+ ## XML 模板
70
+
71
+ ```xml
72
+ <?xml version="1.0" encoding="UTF-8"?>
73
+ <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
74
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
75
+ <mapper namespace="net.xnzn.core.xxx.mapper.XxxMapper">
76
+
77
+ <select id="listByParam" resultType="net.xnzn.core.xxx.vo.XxxVO">
78
+ SELECT
79
+ t.id,
80
+ t.name,
81
+ t.status
82
+ FROM table_name t
83
+ <where>
84
+ t.del_flag = 2
85
+ <if test="param.status != null">
86
+ AND t.status = #{param.status}
87
+ </if>
88
+ <if test="param.keyword != null and param.keyword != ''">
89
+ AND t.name LIKE CONCAT('%', #{param.keyword}, '%')
90
+ </if>
91
+ </where>
92
+ ORDER BY t.crtime DESC
93
+ </select>
94
+ </mapper>
95
+ ```
96
+
97
+ ### XML 编写规则
98
+
99
+ | 规则 | 说明 |
100
+ |------|------|
101
+ | 禁止 `SELECT *` | 必须明确指定字段 |
102
+ | `del_flag = 2` | 正常数据条件 |
103
+ | `#{}` 占位符 | 禁止 `${}`(SQL 注入) |
104
+ | `<where>` 标签 | 自动处理 AND 前缀 |
105
+ | 特殊字符 | `&lt;` / `&gt;` 或 `<![CDATA[ ]]>` |
106
+
107
+ ### 常用动态 SQL 片段
108
+
109
+ ```xml
110
+ <!-- IN 查询 -->
111
+ <if test="param.ids != null and param.ids.size() > 0">
112
+ AND t.id IN
113
+ <foreach collection="param.ids" item="id" open="(" separator="," close=")">
114
+ #{id}
115
+ </foreach>
116
+ </if>
117
+
118
+ <!-- 多条件 OR -->
119
+ <if test="param.keyword != null and param.keyword != ''">
120
+ AND (t.name LIKE CONCAT('%', #{param.keyword}, '%')
121
+ OR t.code LIKE CONCAT('%', #{param.keyword}, '%'))
122
+ </if>
123
+
124
+ <!-- 时间范围 -->
125
+ <if test="param.startDate != null">
126
+ AND t.crtime >= #{param.startDate}
127
+ </if>
128
+ <if test="param.endDate != null">
129
+ AND t.crtime &lt;= #{param.endDate}
130
+ </if>
131
+ ```
132
+
133
+ ## LambdaQuery 使用
134
+
135
+ ```java
136
+ List<XxxEntity> list = mapper.selectList(
137
+ Wrappers.lambdaQuery(XxxEntity.class)
138
+ .eq(XxxEntity::getStatus, 1)
139
+ .eq(XxxEntity::getDelFlag, 2) // 2=正常
140
+ .in(CollUtil.isNotEmpty(idList), XxxEntity::getId, idList)
141
+ .like(StrUtil.isNotBlank(name), XxxEntity::getName, name)
142
+ .ge(startDate != null, XxxEntity::getCrtime, startDate)
143
+ .le(endDate != null, XxxEntity::getCrtime, endDate)
144
+ .orderByDesc(XxxEntity::getCrtime)
145
+ );
146
+ ```
147
+
148
+ ## Service 注入规范
149
+
150
+ ```java
151
+ @Slf4j
152
+ @Service
153
+ @Validated
154
+ public class XxxService {
155
+
156
+ @Autowired
157
+ private XxxMapper baseMapper; // ✅ 统一命名 baseMapper
158
+
159
+ @Autowired @Lazy
160
+ private XxxDetailService xxxDetailService; // ✅ 跨模块用 @Lazy
161
+
162
+ public XxxEntity getOne(Long id) {
163
+ return baseMapper.selectById(id);
164
+ }
165
+
166
+ public boolean exists(String macOrderId) {
167
+ return baseMapper.existsOne(
168
+ Wrappers.lambdaQuery(XxxEntity.class)
169
+ .eq(XxxEntity::getMacOrderId, macOrderId)
170
+ .eq(XxxEntity::getDelFlag, 2)
171
+ );
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## 分页查询
177
+
178
+ ```java
179
+ public PageVO<XxxVO> pageList(XxxParam param) {
180
+ // ✅ 传入 PageDTO 对象(不要拆 pageNum/pageSize)
181
+ if (Objects.nonNull(param.getPage())) {
182
+ PageMethod.startPage(param.getPage());
183
+ }
184
+
185
+ List<XxxVO> records = xxxMapper.listByParam(param);
186
+
187
+ return PageVO.of(records); // 自动提取 total 等信息
188
+ }
189
+ ```
190
+
191
+ Mapper 方法返回 `List<XxxVO>` 即可,PageHelper 拦截器自动处理分页。
192
+
193
+ ## 报表 Mapper(无 BaseMapper)
194
+
195
+ ```java
196
+ @Mapper
197
+ public interface ReportXxxMapper { // ✅ 不继承 BaseMapper
198
+
199
+ List<XxxVO> listSummary(
200
+ @Param("param") XxxParam param,
201
+ @Param("authPO") MgrUserAuthPO authPO,
202
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
203
+ );
204
+
205
+ XxxVO getSummaryTotal( // 合计行
206
+ @Param("param") XxxParam param,
207
+ @Param("authPO") MgrUserAuthPO authPO,
208
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
209
+ );
210
+ }
211
+ ```
212
+
213
+ **命名规律**:`listXxx()` 分页数据 / `listXxx_COUNT()` 计数 / `getSummaryTotal()` 合计行 / `listXxxByDay()` 按维度
214
+
215
+ ## 合计行查询
216
+
217
+ ```xml
218
+ <!-- 列表查询 -->
219
+ <select id="listByParam" resultType="XxxVO">
220
+ SELECT id, name, amount, count
221
+ FROM table_name
222
+ <where>...</where>
223
+ ORDER BY id DESC
224
+ </select>
225
+
226
+ <!-- 合计查询:只返回数值字段 -->
227
+ <select id="getSummaryTotal" resultType="XxxVO">
228
+ SELECT SUM(amount) AS amount, SUM(count) AS count
229
+ FROM table_name
230
+ <where>...</where>
231
+ </select>
232
+ ```
233
+
234
+ 除零处理:
235
+ ```xml
236
+ CASE WHEN SUM(count) = 0 THEN 0 ELSE SUM(amount) / SUM(count) END AS avgAmount
237
+ ```
238
+
239
+ ## 禁止项
240
+
241
+ ```java
242
+ // ❌ 继承 RuoYi TenantEntity
243
+ import org.dromara.common.mybatis.core.domain.TenantEntity;
244
+
245
+ // ❌ delFlag: 0=正常(leniu 是 2=正常)
246
+ wrapper.eq(XxxEntity::getDelFlag, 0);
247
+
248
+ // ❌ XML 放 resources/mapper/(必须与 Mapper 接口同目录)
249
+
250
+ // ❌ MapstructUtils(用 BeanUtil.copyProperties)
251
+ MapstructUtils.convert(source, Target.class);
252
+
253
+ // ❌ Service 继承 IService / ServiceImpl
254
+ public interface IXxxService extends IService<XxxEntity> {}
255
+ ```
256
+
257
+ ## XML 文件位置
258
+
259
+ ```
260
+ net.xnzn.core.xxx.mapper/
261
+ ├── XxxMapper.java # 接口
262
+ └── XxxMapper.xml # XML(同目录!)
263
+ ```
264
+
265
+ ## 参考文档
266
+
267
+ - 报表 Mapper 完整示例:详见 `references/report-mapper.md`
@@ -0,0 +1,88 @@
1
+ # 报表 Mapper 完整示例
2
+
3
+ ## Mapper 接口(来自 ReportAnalysisTurnoverMapper 真实代码)
4
+
5
+ ```java
6
+ @Mapper
7
+ public interface ReportXxxMapper {
8
+
9
+ // 分页列表查询(PageHelper 拦截,方法返回 List)
10
+ List<XxxVO> listSummary(
11
+ @Param("param") XxxParam param,
12
+ @Param("authPO") MgrUserAuthPO authPO,
13
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
14
+ );
15
+
16
+ // 合计行查询(不分页,返回单个汇总 VO)
17
+ XxxVO getSummaryTotal(
18
+ @Param("param") XxxParam param,
19
+ @Param("authPO") MgrUserAuthPO authPO,
20
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
21
+ );
22
+
23
+ // 按日/按月分组(对应 dateType 参数)
24
+ List<XxxVO> listSummaryByDay(
25
+ @Param("param") XxxParam param,
26
+ @Param("authPO") MgrUserAuthPO authPO,
27
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
28
+ );
29
+ List<XxxVO> listSummaryByMonth(
30
+ @Param("param") XxxParam param,
31
+ @Param("authPO") MgrUserAuthPO authPO,
32
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
33
+ );
34
+
35
+ // 汇总数据(总金额、人次等)
36
+ ReportTurnoverPO getTurnoverTotal(
37
+ @Param("param") XxxParam param,
38
+ @Param("authPO") MgrUserAuthPO authPO,
39
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
40
+ );
41
+
42
+ // 排行榜类(不需要 authPO 时可省略)
43
+ List<RankVO> getXxxRank(
44
+ @Param("param") RankParam param,
45
+ @Param("dataPermission") ReportDataPermissionParam dataPermission
46
+ );
47
+ }
48
+ ```
49
+
50
+ ## 关键规则
51
+
52
+ 1. `@Mapper` 注解,**不继承 BaseMapper**(报表无 CRUD)
53
+ 2. 所有参数必须加 `@Param`,顺序:`param` -> `authPO` -> `dataPermission`
54
+ 3. 分页列表返回 `List<VO>`,由 Service 层调用 `PageMethod.startPage()` 控制
55
+ 4. 合计行返回单个 PO/VO 对象
56
+ 5. COUNT 方法(`listXxx_COUNT`)配合 `CompletableFuture` 并发使用
57
+
58
+ ## 命名规律
59
+
60
+ | 类型 | 命名 |
61
+ |------|------|
62
+ | 分页数据 | `listXxx()` |
63
+ | 对应 COUNT | `listXxx_COUNT()`(下划线 + COUNT) |
64
+ | 合计行 | `getSummaryTotal()` / `getSummaryXxxTotal()` |
65
+ | 按维度变体 | `listXxxByDay()` / `listXxxByDay_COUNT()` |
66
+
67
+ ## 合计 SQL 常用函数
68
+
69
+ | 函数 | 用途 |
70
+ |------|------|
71
+ | `SUM()` | 求和 |
72
+ | `COUNT(*)` | 计数 |
73
+ | `AVG()` | 平均值 |
74
+ | `MAX()` / `MIN()` | 极值 |
75
+
76
+ ## 除零处理
77
+
78
+ ```xml
79
+ CASE
80
+ WHEN SUM(count) = 0 THEN 0
81
+ ELSE SUM(amount) / SUM(count)
82
+ END AS avgAmount
83
+
84
+ CASE
85
+ WHEN SUM(staff_count) = 0 THEN 0
86
+ ELSE SUM(avg_salary) / COUNT(DISTINCT tenant_id)
87
+ END AS avgSalary
88
+ ```