ai-engineering-init 1.16.3 → 1.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/{leniu-java-amount-handling/SKILL.md → leniu-report-scenario/references/amount-handling.md} +0 -13
- package/.claude/skills/{leniu-java-export/SKILL.md → leniu-report-scenario/references/export.md} +0 -17
- package/{.cursor/skills/leniu-mealtime/SKILL.md → .claude/skills/leniu-report-scenario/references/mealtime.md} +0 -18
- package/{.codex/skills/leniu-java-report-query-param/SKILL.md → .claude/skills/leniu-report-scenario/references/query-param.md} +0 -17
- package/.claude/skills/leniu-report-scenario/references/standard-customization.md +112 -0
- package/{.codex/skills/leniu-java-total-line/SKILL.md → .claude/skills/leniu-report-scenario/references/total-line.md} +0 -17
- package/.codex/skills/leniu-report-scenario/references/amount-handling.md +448 -0
- package/.codex/skills/{leniu-java-export/SKILL.md → leniu-report-scenario/references/export.md} +0 -17
- package/{.claude/skills/leniu-mealtime/SKILL.md → .codex/skills/leniu-report-scenario/references/mealtime.md} +0 -18
- package/{.cursor/skills/leniu-java-report-query-param/SKILL.md → .codex/skills/leniu-report-scenario/references/query-param.md} +0 -17
- package/.codex/skills/leniu-report-scenario/references/standard-customization.md +112 -0
- package/{.cursor/skills/leniu-java-total-line/SKILL.md → .codex/skills/leniu-report-scenario/references/total-line.md} +0 -17
- package/.cursor/skills/leniu-report-scenario/references/amount-handling.md +448 -0
- package/.cursor/skills/{leniu-java-export/SKILL.md → leniu-report-scenario/references/export.md} +0 -17
- package/{.codex/skills/leniu-mealtime/SKILL.md → .cursor/skills/leniu-report-scenario/references/mealtime.md} +0 -18
- package/{.claude/skills/leniu-java-report-query-param/SKILL.md → .cursor/skills/leniu-report-scenario/references/query-param.md} +0 -17
- package/.cursor/skills/leniu-report-scenario/references/standard-customization.md +112 -0
- package/{.claude/skills/leniu-java-total-line/SKILL.md → .cursor/skills/leniu-report-scenario/references/total-line.md} +0 -17
- package/package.json +1 -1
- package/.claude/skills/leniu-marketing-price-rule-customizer/SKILL.md +0 -301
- package/.claude/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +0 -285
- package/.claude/skills/leniu-report-customization/SKILL.md +0 -415
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +0 -391
- package/.codex/skills/leniu-marketing-price-rule-customizer/SKILL.md +0 -301
- package/.codex/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +0 -285
- package/.codex/skills/leniu-report-customization/SKILL.md +0 -415
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +0 -391
- package/.cursor/skills/leniu-marketing-price-rule-customizer/SKILL.md +0 -301
- package/.cursor/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +0 -285
- package/.cursor/skills/leniu-report-customization/SKILL.md +0 -415
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +0 -391
- /package/.claude/skills/{leniu-report-standard-customization → leniu-report-scenario}/references/analysis-module.md +0 -0
- /package/.claude/skills/{leniu-report-customization/references/table-fields.md → leniu-report-scenario/references/customization-table-fields.md} +0 -0
- /package/.claude/skills/{leniu-report-standard-customization/references/table-fields.md → leniu-report-scenario/references/standard-table-fields.md} +0 -0
- /package/.codex/skills/{leniu-report-standard-customization → leniu-report-scenario}/references/analysis-module.md +0 -0
- /package/.codex/skills/{leniu-report-customization/references/table-fields.md → leniu-report-scenario/references/customization-table-fields.md} +0 -0
- /package/.codex/skills/{leniu-report-standard-customization/references/table-fields.md → leniu-report-scenario/references/standard-table-fields.md} +0 -0
- /package/.cursor/skills/{leniu-report-standard-customization → leniu-report-scenario}/references/analysis-module.md +0 -0
- /package/.cursor/skills/{leniu-report-customization/references/table-fields.md → leniu-report-scenario/references/customization-table-fields.md} +0 -0
- /package/.cursor/skills/{leniu-report-standard-customization/references/table-fields.md → leniu-report-scenario/references/standard-table-fields.md} +0 -0
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: leniu-marketing-recharge-rule-customizer
|
|
3
|
-
description: |
|
|
4
|
-
leniu-tengyun-core 项目营销充值(recharge)规则定制指南。当需要定制营销充值规则时使用,支持新增充值规则类型、重写规则逻辑、扩展 DTO 字段。
|
|
5
|
-
|
|
6
|
-
触发场景:
|
|
7
|
-
- 新增营销充值规则类型(满赠、按次赠送、限额、管理费等)
|
|
8
|
-
- 重写现有充值规则逻辑(@Primary 模式)
|
|
9
|
-
- 扩展充值规则 DTO 字段(向后兼容)
|
|
10
|
-
- 定制充值规则计算行为(handle 方法实现)
|
|
11
|
-
- 注册充值规则枚举(RuleRechargeEnum)
|
|
12
|
-
|
|
13
|
-
适用项目:
|
|
14
|
-
- leniu-tengyun-core:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core
|
|
15
|
-
- leniu-yunshitang:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang
|
|
16
|
-
|
|
17
|
-
触发词:营销充值、充值规则、RuleRechargeHandler、RuleRechargeEnum、满赠规则、充值赠送、管理费规则
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
# leniu-tengyun-core 营销充值规则定制
|
|
21
|
-
|
|
22
|
-
## 概述
|
|
23
|
-
|
|
24
|
-
leniu-tengyun-core 项目的营销充值(recharge)规则功能采用扩展点设计,支持灵活的规则定制。
|
|
25
|
-
|
|
26
|
-
营销充值规则是营销系统的核心组件,负责计算充值的优惠金额、限制充值行为、收取管理费等功能。
|
|
27
|
-
|
|
28
|
-
## 何时使用此 Skill
|
|
29
|
-
|
|
30
|
-
- 需要新增一个充值规则类型
|
|
31
|
-
- 需要修改现有规则的计算逻辑
|
|
32
|
-
- 需要为规则添加新的配置字段
|
|
33
|
-
- 需要针对特定项目定制规则行为
|
|
34
|
-
- 参考现有规则实现新的定制需求
|
|
35
|
-
|
|
36
|
-
## 规则定制工作流
|
|
37
|
-
|
|
38
|
-
### 步骤1:确定定制模式
|
|
39
|
-
|
|
40
|
-
根据需求选择合适的定制模式:
|
|
41
|
-
|
|
42
|
-
**模式A:新增规则**
|
|
43
|
-
- 适用场景:创建全新的规则类型
|
|
44
|
-
- 需要创建:DTO、扩展接口、默认实现
|
|
45
|
-
|
|
46
|
-
**模式B:重写规则(@Primary)**
|
|
47
|
-
- 适用场景:完全替换现有规则行为
|
|
48
|
-
- 需要创建:定制实现类(使用@Primary注解)
|
|
49
|
-
- 可选:扩展DTO字段
|
|
50
|
-
|
|
51
|
-
**模式C:重写规则(Ordered)**
|
|
52
|
-
- 适用场景:多个实现共存
|
|
53
|
-
- 需要创建:定制实现类(实现Ordered接口)
|
|
54
|
-
|
|
55
|
-
### 步骤2:理解规则结构
|
|
56
|
-
|
|
57
|
-
了解:
|
|
58
|
-
- 规则接口层次(RuleRechargeHandler → Extension → Implementation)
|
|
59
|
-
- 规则DTO结构
|
|
60
|
-
- 规则计算入参(RuleRechargeResultDTO、MarketRuleVO)
|
|
61
|
-
- 规则计算流程
|
|
62
|
-
- 常用工具类
|
|
63
|
-
|
|
64
|
-
### 步骤3:参考实际案例
|
|
65
|
-
|
|
66
|
-
查看实际案例,了解如何实现具体的定制需求。
|
|
67
|
-
|
|
68
|
-
### 步骤4:实现规则定制
|
|
69
|
-
|
|
70
|
-
#### 新增规则的实现步骤
|
|
71
|
-
|
|
72
|
-
1. **创建规则DTO**
|
|
73
|
-
- 位置:`net.xnzn.core.marketing.v2.rule.recharge.handler.[ruletype].dto`
|
|
74
|
-
- 包含规则配置字段
|
|
75
|
-
- 实现 `toString()` 方法返回可读描述
|
|
76
|
-
|
|
77
|
-
2. **创建扩展接口**
|
|
78
|
-
- 位置:`net.xnzn.core.marketing.v2.rule.recharge.handler.[ruletype].extension`
|
|
79
|
-
- 继承 `RuleRechargeHandler`
|
|
80
|
-
- 实现 `getRuleType()` 和 `checkRuleInfo()` 方法
|
|
81
|
-
|
|
82
|
-
3. **创建默认实现**
|
|
83
|
-
- 位置:`net.xnzn.core.marketing.v2.rule.recharge.handler.[ruletype].extension.impl`
|
|
84
|
-
- 实现扩展接口
|
|
85
|
-
- 添加 `@Service` 注解
|
|
86
|
-
- 实现 `handle()` 方法
|
|
87
|
-
|
|
88
|
-
4. **注册规则枚举**
|
|
89
|
-
- 在 `RuleRechargeEnum` 中添加新的规则类型
|
|
90
|
-
|
|
91
|
-
#### 重写规则的实现步骤(@Primary模式)
|
|
92
|
-
|
|
93
|
-
1. **(可选)扩展DTO字段**
|
|
94
|
-
- 在定制项目中覆盖核心DTO类
|
|
95
|
-
- 添加新字段并保持向后兼容
|
|
96
|
-
|
|
97
|
-
2. **创建定制实现**
|
|
98
|
-
- 位置:定制项目包(如 `net.xnzn.yunshitang.marketing.handler`)
|
|
99
|
-
- 继承默认实现类(可选,用于复用逻辑)
|
|
100
|
-
- 实现扩展接口
|
|
101
|
-
- 添加 `@Service` 和 `@Primary` 注解
|
|
102
|
-
- 重写 `handle()` 方法
|
|
103
|
-
|
|
104
|
-
3. **实现定制逻辑**
|
|
105
|
-
- 解析规则配置(包含新字段)
|
|
106
|
-
- 实现自定义计算逻辑
|
|
107
|
-
- 更新充值金额或抛出限制异常
|
|
108
|
-
|
|
109
|
-
### 步骤5:测试规则
|
|
110
|
-
|
|
111
|
-
1. 测试向后兼容性(新字段为null的场景)
|
|
112
|
-
2. 测试新功能(新字段有值的场景)
|
|
113
|
-
3. 测试边界条件
|
|
114
|
-
4. 测试与其他规则的组合使用
|
|
115
|
-
|
|
116
|
-
## 代码模板
|
|
117
|
-
|
|
118
|
-
### 新增规则模板
|
|
119
|
-
|
|
120
|
-
```java
|
|
121
|
-
// 1. DTO
|
|
122
|
-
@Data
|
|
123
|
-
@ApiModel("充值规则详情-[规则名称]")
|
|
124
|
-
public class [RuleType]DTO {
|
|
125
|
-
@ApiModelProperty("[字段说明]")
|
|
126
|
-
private [Type] fieldName;
|
|
127
|
-
|
|
128
|
-
@Override
|
|
129
|
-
public String toString() {
|
|
130
|
-
return "字段:" + fieldName;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 2. 扩展接口
|
|
135
|
-
public interface [RuleType]HandlerExtension extends RuleRechargeHandler {
|
|
136
|
-
@Override
|
|
137
|
-
default Integer getRuleType() {
|
|
138
|
-
return RuleRechargeEnum.[RULE_TYPE_ENUM].getKey();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
@Override
|
|
142
|
-
default void checkRuleInfo(String ruleInfo) {
|
|
143
|
-
[RuleType]DTO rule = JSON.parseObject(ruleInfo, [RuleType]DTO.class);
|
|
144
|
-
// 添加必要的校验逻辑
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 3. 默认实现
|
|
149
|
-
@Slf4j
|
|
150
|
-
@Service
|
|
151
|
-
public class Default[RuleType]HandlerImpl implements [RuleType]HandlerExtension {
|
|
152
|
-
|
|
153
|
-
@Override
|
|
154
|
-
public void handle(RuleRechargeResultDTO resultDTO, MarketRuleVO rule) {
|
|
155
|
-
// 1. 解析规则配置
|
|
156
|
-
[RuleType]DTO ruleInfo = JSON.parseObject(rule.getRuleInfo(), [RuleType]DTO.class);
|
|
157
|
-
|
|
158
|
-
// 2. 实现规则计算逻辑
|
|
159
|
-
// ...
|
|
160
|
-
|
|
161
|
-
// 3. 更新充值金额或抛出异常
|
|
162
|
-
resultDTO.setActualAmount(resultDTO.getActualAmount().add(giftAmount));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### 重写规则模板(@Primary)
|
|
168
|
-
|
|
169
|
-
```java
|
|
170
|
-
@Slf4j
|
|
171
|
-
@Service
|
|
172
|
-
@Primary // 标记为主要实现
|
|
173
|
-
public class Custom[RuleType]HandlerImpl extends Default[RuleType]HandlerImpl
|
|
174
|
-
implements [RuleType]HandlerExtension {
|
|
175
|
-
|
|
176
|
-
@Override
|
|
177
|
-
public void handle(RuleRechargeResultDTO resultDTO, MarketRuleVO rule) {
|
|
178
|
-
// 解析规则配置(可能包含扩展字段)
|
|
179
|
-
[RuleType]DTO ruleInfo = JSON.parseObject(rule.getRuleInfo(), [RuleType]DTO.class);
|
|
180
|
-
|
|
181
|
-
// 实现定制逻辑
|
|
182
|
-
// 可以调用父类方法:super.handle(resultDTO, rule);
|
|
183
|
-
// 或完全自定义实现
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## 常见规则类型
|
|
189
|
-
|
|
190
|
-
### 优惠类规则
|
|
191
|
-
- 满赠规则:`RechargeFullGiftHandlerExtension`
|
|
192
|
-
- 按次数赠送:`RechargeTimesGiftHandlerExtension`
|
|
193
|
-
|
|
194
|
-
### 限制类规则
|
|
195
|
-
- 限额-单次金额:`RechargeMaxAmountHandlerExtension`
|
|
196
|
-
- 限额-累计金额:`RechargeSumAmountHandlerExtension`
|
|
197
|
-
|
|
198
|
-
### 其他费用规则
|
|
199
|
-
- 管理费:`RechargeCostHandlerExtension`
|
|
200
|
-
|
|
201
|
-
## 最佳实践
|
|
202
|
-
|
|
203
|
-
### 包名规范
|
|
204
|
-
- 覆盖核心类:使用核心工程的包名(`net.xnzn.core.marketing.v2.rule.recharge.handler.[ruletype]`)
|
|
205
|
-
- 定制实现:使用项目特定包名(如 `net.xnzn.yunshitang.marketing.handler`)
|
|
206
|
-
|
|
207
|
-
### 类名规范
|
|
208
|
-
- 扩展接口:`[RuleType]HandlerExtension`
|
|
209
|
-
- 默认实现:`Default[RuleType]HandlerImpl`
|
|
210
|
-
- 定制实现:`Custom[RuleType]HandlerImpl` 或描述性名称
|
|
211
|
-
|
|
212
|
-
### 注解使用
|
|
213
|
-
- 所有实现类必须添加 `@Service` 注解
|
|
214
|
-
- 重写规则使用 `@Primary` 注解(推荐)
|
|
215
|
-
- 日志记录使用 `@Slf4j` 注解
|
|
216
|
-
|
|
217
|
-
### 向后兼容
|
|
218
|
-
- 扩展DTO字段时,新字段应支持null值
|
|
219
|
-
- 新字段为null时应保持原有行为
|
|
220
|
-
- 在toString方法中包含所有字段
|
|
221
|
-
|
|
222
|
-
## 快速开始示例
|
|
223
|
-
|
|
224
|
-
假设需要为满赠规则(`RechargeFullGiftHandlerExtension`)添加钱包类型选择功能:
|
|
225
|
-
|
|
226
|
-
1. **扩展DTO**:在 `RechargeGiftFullDTO` 中添加 `walletTypes` 字段
|
|
227
|
-
2. **创建定制实现**:创建 `CustomRechargeFullGiftHandlerImpl`,位于 `net.xnzn.yunshitang.marketing.handler`,使用 `@Primary` 注解
|
|
228
|
-
3. **实现逻辑**:在 `handle()` 方法中使用 `walletTypes` 字段过滤钱包充值
|
|
229
|
-
4. **向后兼容**:`walletTypes` 为null时适用所有钱包
|
|
230
|
-
|
|
231
|
-
```java
|
|
232
|
-
// 1. 扩展DTO(在核心工程包名下覆盖)
|
|
233
|
-
// 位置:net.xnzn.core.marketing.v2.rule.recharge.handler.fullgift.dto.RechargeGiftFullDTO
|
|
234
|
-
@Data
|
|
235
|
-
@ApiModel("充值满赠规则详情")
|
|
236
|
-
public class RechargeGiftFullDTO {
|
|
237
|
-
@ApiModelProperty("满足金额门槛(分)")
|
|
238
|
-
private BigDecimal fullAmount;
|
|
239
|
-
|
|
240
|
-
@ApiModelProperty("赠送金额(分)")
|
|
241
|
-
private BigDecimal giftAmount;
|
|
242
|
-
|
|
243
|
-
// 扩展字段:适用钱包类型,null时表示所有钱包
|
|
244
|
-
@ApiModelProperty("适用钱包类型列表(null=所有钱包)")
|
|
245
|
-
private List<Integer> walletTypes;
|
|
246
|
-
|
|
247
|
-
@Override
|
|
248
|
-
public String toString() {
|
|
249
|
-
return "fullAmount=" + fullAmount + ", giftAmount=" + giftAmount
|
|
250
|
-
+ ", walletTypes=" + walletTypes;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 2. 定制实现(在yunshitang项目包下)
|
|
255
|
-
// 位置:net.xnzn.yunshitang.marketing.handler
|
|
256
|
-
@Slf4j
|
|
257
|
-
@Service
|
|
258
|
-
@Primary
|
|
259
|
-
public class CustomRechargeFullGiftHandlerImpl
|
|
260
|
-
implements RechargeFullGiftHandlerExtension {
|
|
261
|
-
|
|
262
|
-
@Override
|
|
263
|
-
public void handle(RuleRechargeResultDTO resultDTO, MarketRuleVO rule) {
|
|
264
|
-
RechargeGiftFullDTO ruleInfo = JSON.parseObject(
|
|
265
|
-
rule.getRuleInfo(), RechargeGiftFullDTO.class);
|
|
266
|
-
|
|
267
|
-
// 钱包类型过滤(向后兼容:null时不过滤)
|
|
268
|
-
if (CollUtil.isNotEmpty(ruleInfo.getWalletTypes())
|
|
269
|
-
&& !ruleInfo.getWalletTypes().contains(resultDTO.getWalletType())) {
|
|
270
|
-
return; // 不在适用钱包范围内,跳过
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// 满赠逻辑
|
|
274
|
-
if (resultDTO.getRechargeAmount().compareTo(ruleInfo.getFullAmount()) >= 0) {
|
|
275
|
-
resultDTO.setActualAmount(
|
|
276
|
-
resultDTO.getActualAmount().add(ruleInfo.getGiftAmount()));
|
|
277
|
-
log.info("满赠规则生效,赠送金额:{}", ruleInfo.getGiftAmount());
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## 参考文档
|
|
284
|
-
|
|
285
|
-
详见:[leniu-tengyun-core 源码](/Users/xujiajun/Developer/gongsi_proj/core/leniu-tengyun-core)
|
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: leniu-report-customization
|
|
3
|
-
description: |
|
|
4
|
-
leniu-tengyun-core 项目(v5.29)定制报表开发指南。基于 report_order_info / report_order_detail / report_account_flow 等报表基础表实现汇总报表。
|
|
5
|
-
|
|
6
|
-
触发场景:
|
|
7
|
-
- 基于订单数据实现定制汇总报表
|
|
8
|
-
- 基于账户流水实现定制汇总报表
|
|
9
|
-
- 处理报表中的退款数据(正向/逆向/部分退)
|
|
10
|
-
- 实现报表的 MQ 消费逻辑和 fix 初始化逻辑
|
|
11
|
-
- 报表金额计算(消费金额减退款)
|
|
12
|
-
|
|
13
|
-
触发词:定制报表、汇总报表、report_order_info、report_order_detail、report_account_flow、退款汇总、消费金额统计、订单报表、流水报表
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# leniu 定制报表开发指南
|
|
17
|
-
|
|
18
|
-
> 详细字段说明见 `references/table-fields.md`
|
|
19
|
-
|
|
20
|
-
## 概述
|
|
21
|
-
|
|
22
|
-
定制报表基于**报表基础表**(由 MQ 消息写入)进行二次汇总:
|
|
23
|
-
1. **订单类**:`report_order_info` + `report_order_detail`(消费/退款数据)
|
|
24
|
-
2. **账户流水类**:`report_account_flow` + `report_account_flow_detail`(钱包变动数据)
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 一、报表系统架构
|
|
29
|
-
|
|
30
|
-
### 1.1 两阶段消费模型
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
MQ 消息到达(下单/退款)
|
|
34
|
-
↓
|
|
35
|
-
第一阶段(order < 10,同步写基础表)
|
|
36
|
-
├── ORDER=1 ReportOrderInfoService → report_order_info
|
|
37
|
-
├── ORDER=3 ReportOrderDetailService → report_order_detail
|
|
38
|
-
└── ORDER=5 ReportRefundService → report_refund / report_refund_detail
|
|
39
|
-
↓
|
|
40
|
-
第二阶段(order >= 10,批量写汇总表,由 Redis 计数触发)
|
|
41
|
-
├── ORDER=11 ReportSumCanteenService → report_sum_canteen(食堂汇总)
|
|
42
|
-
└── ORDER=3x ReportSumDishesService → 菜品销售汇总
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### 1.2 MQ 消息类型
|
|
46
|
-
|
|
47
|
-
| 消息 | Topic/Tag | 监听器 |
|
|
48
|
-
|------|-----------|--------|
|
|
49
|
-
| 下单 | `order / order-v3-placed` | `ReportOrderMQListener` |
|
|
50
|
-
| 退款 | `order / order-v3-refunded` | `ReportOrderRefundMQListener` |
|
|
51
|
-
|
|
52
|
-
### 1.3 触发汇总消费的机制
|
|
53
|
-
|
|
54
|
-
Redis 计数器 `ORDER_REPORT_COUNT_KEY:{merchantId}`,每来一条 MQ 消息递减,达到阈值时异步触发 `consumeOrderReport()`,从 `report_order_info` 中查出 `status=0`(未消费)的数据,批量调用所有 `order >= 10` 的 Service 的 `batchConsume()`。
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## 二、核心基础表概要
|
|
59
|
-
|
|
60
|
-
### 2.1 report_order_info(报表订单主表)
|
|
61
|
-
|
|
62
|
-
> v5.29 后,正向下单和逆向退款**都存在这张表中**,通过 `consumeType` 区分。
|
|
63
|
-
|
|
64
|
-
关键字段:`orderId`(主键), `relationOrderId`(退款指向原始订单), `consumeType`(**1=消费,2=退款**), `orderRefundState`(1未退/2全退/3部分退), `realAmount`/`refundAmount`/`walletAmount`/`subsidyAmount`(金额,分), `payTime`, `orderDate`, `mealtimeType`, `orderType`, `canteenId/stallId`, `status`(0未消费/1已消费)
|
|
65
|
-
|
|
66
|
-
### 2.2 report_order_detail(菜品明细表)
|
|
67
|
-
|
|
68
|
-
关键字段:`detailId`, `orderId`, `goodsDishesId/goodsDishesName`, `price/totalAmount/realAmount`(分), `quantity`, `detailState`(**1正常/2已退菜/3部分退菜**), `goodsRefundNum`, `refundAmount`, `detailType`(1菜品/2套餐/3商品/4按键/5补扣/6报餐)
|
|
69
|
-
|
|
70
|
-
### 2.3 report_account_flow(账户流水主表)
|
|
71
|
-
|
|
72
|
-
关键字段:`flowId`, `custId`, `flowType`(AccTradeTypeEnum), `flowRealAmount/flowAmount`, `accTotalBal/accAllBal`, `payTime`, `status`(0未消费/1已消费)
|
|
73
|
-
|
|
74
|
-
> `custName`、`mobile` 使用 SM4 加密存储。
|
|
75
|
-
|
|
76
|
-
### 2.4 report_account_flow_detail(流水钱包明细)
|
|
77
|
-
|
|
78
|
-
关键字段:`flowId`, `walletId`(AccWalletIdEnum), `amount`(转出取负值), `walletBal`, `frozenBalance`
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## 三、退款数据处理(核心重点)
|
|
83
|
-
|
|
84
|
-
### 3.1 退款数据在 report_order_info 中的表现
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
正向订单:orderId=订单ID, consumeType=1, orderRefundState=1
|
|
88
|
-
退款记录:orderId=退款单ID, consumeType=2, relationOrderId=原始订单ID
|
|
89
|
-
金额字段全部为负数(realRefundAmount、walletAmount、subsidyAmount 都乘以 -1)
|
|
90
|
-
payTime = 退款审核时间(checkTime)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 3.2 净消费金额计算(3种方式)
|
|
94
|
-
|
|
95
|
-
**方式一:直接 SUM(推荐)** - 退款金额已为负数
|
|
96
|
-
|
|
97
|
-
```sql
|
|
98
|
-
SELECT SUM(real_amount) AS netAmount,
|
|
99
|
-
SUM(wallet_amount) AS netWalletAmount
|
|
100
|
-
FROM report_order_info WHERE pay_time BETWEEN #{startTime} AND #{endTime}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**方式二:分别统计消费和退款**
|
|
104
|
-
|
|
105
|
-
```sql
|
|
106
|
-
-- 消费总额(consumeType=1)/ 退款总额(consumeType=2,取ABS)
|
|
107
|
-
-- 净额 = 消费总额 - 退款总额
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
**方式三:排除全退订单**
|
|
111
|
-
|
|
112
|
-
```sql
|
|
113
|
-
SELECT SUM(real_amount - IFNULL(refund_amount, 0)) AS netAmount
|
|
114
|
-
FROM report_order_info
|
|
115
|
-
WHERE consume_type = 1 AND order_refund_state IN (1, 3)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 3.3 菜品级别退款
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
detailState=1(正常)/ 2(全退)/ 3(部分退)
|
|
122
|
-
菜品净销量 = quantity - IFNULL(goods_refund_num, 0)
|
|
123
|
-
菜品净金额 = total_amount - IFNULL(refund_amount, 0)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## 四、钱包与交易类型枚举
|
|
129
|
-
|
|
130
|
-
### 4.1 AccWalletIdEnum
|
|
131
|
-
|
|
132
|
-
| key | 枚举 | 含义 |
|
|
133
|
-
|-----|------|------|
|
|
134
|
-
| 1 | WALLET | 个人钱包 |
|
|
135
|
-
| 2 | SUBSIDY | 补贴钱包 |
|
|
136
|
-
| 4 | LUCK_MONEY | 红包 |
|
|
137
|
-
|
|
138
|
-
### 4.2 AccTradeTypeEnum
|
|
139
|
-
|
|
140
|
-
| key | 枚举 | 金额方向 |
|
|
141
|
-
|-----|------|---------|
|
|
142
|
-
| 10/11 | RECHARGE/RECHARGE_GIFT | 正 |
|
|
143
|
-
| 12/40/50 | 撤销赠送/撤销充值/撤销补贴 | **负** |
|
|
144
|
-
| 20 | SUBSIDY 补贴 | 正 |
|
|
145
|
-
| 30 | WITHDRAW 提现 | **负** |
|
|
146
|
-
| 60/80/100 | 转出/冻结/补贴清空 | **负** |
|
|
147
|
-
| 70/90 | 转入/解冻 | 正 |
|
|
148
|
-
| 110/120 | CONSUME/CONSUME_REPAIR | **负** |
|
|
149
|
-
| 130 | CONSUME_REFUND 退款 | 正 |
|
|
150
|
-
| 131/132 | 账户预扣/预扣退款 | **负**/正 |
|
|
151
|
-
| 140/141/142 | 红包/撤销红包/红包清空 | 正/**负**/**负** |
|
|
152
|
-
|
|
153
|
-
### 4.3 账户流水常用过滤
|
|
154
|
-
|
|
155
|
-
```java
|
|
156
|
-
// 净消费额:消费为负,退款为正 → SUM 即为净消费额(负值)
|
|
157
|
-
WHERE wallet_id = #{walletId} AND flow_type IN (110, 130)
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## 五、汇总表开发标准模式
|
|
163
|
-
|
|
164
|
-
### 5.1 实现 ReportOrderConsumeService 接口
|
|
165
|
-
|
|
166
|
-
```java
|
|
167
|
-
@Service @Slf4j
|
|
168
|
-
public class ReportSumXxxService implements ReportOrderConsumeService {
|
|
169
|
-
@Override public int getOrder() { return 15; } // <10基础, 10-29汇总, 30+菜品
|
|
170
|
-
|
|
171
|
-
@Override public void consume(OrderChangePO payload, ReportOrderInfoDTO baseInfo) {
|
|
172
|
-
// 汇总表通常留空
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@Override public void batchConsume(List<ReportOrderConsumeDTO> list) {
|
|
176
|
-
// 分组 → 查存量 → 累加/新建(见下方模板)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@Override public void fix(ReportBaseParam param) {
|
|
180
|
-
// 先删后插,从基础表重新聚合
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### 5.2 batchConsume 模板
|
|
186
|
-
|
|
187
|
-
```java
|
|
188
|
-
@Override
|
|
189
|
-
public void batchConsume(List<ReportOrderConsumeDTO> list) {
|
|
190
|
-
Map<String, List<ReportOrderConsumeDTO>> grouped = list.stream()
|
|
191
|
-
.collect(Collectors.groupingBy(e ->
|
|
192
|
-
e.getStatisticDate() + "_" + e.getCanteenId() + "_" + e.getStallId()));
|
|
193
|
-
|
|
194
|
-
List<ReportSumXxx> insertList = new ArrayList<>(), updateList = new ArrayList<>();
|
|
195
|
-
|
|
196
|
-
for (var entry : grouped.entrySet()) {
|
|
197
|
-
ReportOrderConsumeDTO first = entry.getValue().get(0);
|
|
198
|
-
ReportSumXxx existing = mapper.selectOne(Wrappers.<ReportSumXxx>lambdaQuery()
|
|
199
|
-
.eq(ReportSumXxx::getStatisticDate, first.getStatisticDate())
|
|
200
|
-
.eq(ReportSumXxx::getCanteenId, first.getCanteenId())
|
|
201
|
-
.eq(ReportSumXxx::getStallId, first.getStallId()));
|
|
202
|
-
|
|
203
|
-
if (existing == null) {
|
|
204
|
-
ReportSumXxx record = new ReportSumXxx();
|
|
205
|
-
record.setId(Id.next());
|
|
206
|
-
// 设置维度 + 累加金额
|
|
207
|
-
insertList.add(record);
|
|
208
|
-
} else {
|
|
209
|
-
// existing.setXxxAmount(existing.getXxxAmount().add(delta));
|
|
210
|
-
updateList.add(existing);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (CollUtil.isNotEmpty(insertList)) { /* 批量插入 */ }
|
|
214
|
-
if (CollUtil.isNotEmpty(updateList)) { /* 批量更新 */ }
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### 5.3 fix 方法 + SQL 模板
|
|
219
|
-
|
|
220
|
-
```java
|
|
221
|
-
@Override
|
|
222
|
-
public void fix(ReportBaseParam param) {
|
|
223
|
-
mapper.delete(Wrappers.<ReportSumXxx>lambdaQuery()
|
|
224
|
-
.between(ReportSumXxx::getStatisticDate,
|
|
225
|
-
param.getStartTime().toLocalDate(), param.getEndTime().toLocalDate()));
|
|
226
|
-
mapper.initFix(null, param.getStartTime(), param.getEndTime());
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
```xml
|
|
231
|
-
<insert id="initFix">
|
|
232
|
-
INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
|
|
233
|
-
order_count, consume_amount, refund_amount, net_amount)
|
|
234
|
-
SELECT #{id}, DATE(pay_time), canteen_id, canteen_name,
|
|
235
|
-
COUNT(*),
|
|
236
|
-
SUM(CASE WHEN consume_type = 1 THEN real_amount ELSE 0 END),
|
|
237
|
-
SUM(CASE WHEN consume_type = 2 THEN ABS(real_refund_amount) ELSE 0 END),
|
|
238
|
-
SUM(real_amount) + SUM(IFNULL(real_refund_amount, 0))
|
|
239
|
-
FROM report_order_info
|
|
240
|
-
WHERE pay_time BETWEEN #{startTime} AND #{endTime}
|
|
241
|
-
GROUP BY DATE(pay_time), canteen_id, canteen_name
|
|
242
|
-
</insert>
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## 六、数据权限集成
|
|
248
|
-
|
|
249
|
-
- Service 注入 `MgrAuthV2Api` 和 `ReportDataPermissionService`
|
|
250
|
-
- 查询/导出方法首行调用 `mgrAuthApi.getUserAuthPO()` + `reportDataPermissionService.getDataPermission(authPO)`
|
|
251
|
-
- Mapper 签名携带 `@Param("authPO")` 和 `@Param("dataPermission")`
|
|
252
|
-
- XML `baseWhere` 末尾引入 `<include refid="net.xnzn.core.report.statistics.common.mapper.ReportDataPermissionMapper.dataPermission"/>`
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## 七、查询接口标准模式(三并行 CompletableFuture)
|
|
257
|
-
|
|
258
|
-
```java
|
|
259
|
-
public PageVO<XxxVO> pageSummary(XxxParam param) {
|
|
260
|
-
CompletableFuture<Long> countF = CompletableFuture.supplyAsync(() -> mapper.selectCount(param));
|
|
261
|
-
CompletableFuture<List<XxxVO>> listF = CompletableFuture.supplyAsync(() -> mapper.selectPageList(param));
|
|
262
|
-
CompletableFuture<XxxTotalVO> totalF = CompletableFuture.supplyAsync(() -> mapper.selectTotal(param));
|
|
263
|
-
CompletableFuture.allOf(countF, listF, totalF).join();
|
|
264
|
-
|
|
265
|
-
PageVO<XxxVO> pageVO = new PageVO<>();
|
|
266
|
-
pageVO.setTotal(countF.join());
|
|
267
|
-
pageVO.setList(listF.join());
|
|
268
|
-
pageVO.setTotalLine(totalF.join());
|
|
269
|
-
return pageVO;
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## 八、账户流水汇总报表
|
|
276
|
-
|
|
277
|
-
实现 `ReportAccountConsumeService` 接口(类似 ReportOrderConsumeService),数据源为 `report_account_flow`。
|
|
278
|
-
|
|
279
|
-
```sql
|
|
280
|
-
-- 按钱包类型统计消费/退款
|
|
281
|
-
SELECT d.wallet_id,
|
|
282
|
-
SUM(CASE WHEN d.flow_type = 110 THEN ABS(d.amount) ELSE 0 END) AS consume_amount,
|
|
283
|
-
SUM(CASE WHEN d.flow_type = 130 THEN d.amount ELSE 0 END) AS refund_amount
|
|
284
|
-
FROM report_account_flow f
|
|
285
|
-
JOIN report_account_flow_detail d ON f.flow_id = d.flow_id
|
|
286
|
-
WHERE f.pay_time BETWEEN #{startTime} AND #{endTime}
|
|
287
|
-
GROUP BY d.wallet_id
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
流水退款:`flowType=110` amount 为负,`flowType=130` amount 为正,直接 SUM 即净额。
|
|
291
|
-
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
## 九、MySQL only_full_group_by 规范(必须遵守)
|
|
295
|
-
|
|
296
|
-
> 生产环境 MySQL 开启了 `sql_mode=only_full_group_by`,所有报表 SQL 必须满足此规则,否则报 `BadSqlGrammarException`。
|
|
297
|
-
|
|
298
|
-
### 核心规则
|
|
299
|
-
|
|
300
|
-
**SELECT 中所有非聚合字段,必须出现在 GROUP BY 中,且表达式必须完全一致。**
|
|
301
|
-
|
|
302
|
-
### 常见错误:GROUP BY 表达式与 SELECT 不一致
|
|
303
|
-
|
|
304
|
-
```sql
|
|
305
|
-
-- ❌ 错误:SELECT 用 DATE_FORMAT,GROUP BY 用 DATE
|
|
306
|
-
SELECT
|
|
307
|
-
DATE_FORMAT(atr.trade_time, '%Y-%m-%d') AS statisticDate,
|
|
308
|
-
SUM(atr.amount) AS totalAmount
|
|
309
|
-
FROM acc_trade atr
|
|
310
|
-
GROUP BY DATE(atr.trade_time) -- ❌ 表达式不同,触发 only_full_group_by 报错
|
|
311
|
-
ORDER BY DATE(atr.trade_time) ASC
|
|
312
|
-
|
|
313
|
-
-- ✅ 正确:GROUP BY 与 SELECT 使用完全相同的表达式
|
|
314
|
-
SELECT
|
|
315
|
-
DATE_FORMAT(atr.trade_time, '%Y-%m-%d') AS statisticDate,
|
|
316
|
-
SUM(atr.amount) AS totalAmount
|
|
317
|
-
FROM acc_trade atr
|
|
318
|
-
GROUP BY DATE_FORMAT(atr.trade_time, '%Y-%m-%d') -- ✅ 与 SELECT 一致
|
|
319
|
-
ORDER BY DATE_FORMAT(atr.trade_time, '%Y-%m-%d') ASC
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### 常见错误:SELECT 包含非聚合字段未加入 GROUP BY
|
|
323
|
-
|
|
324
|
-
```sql
|
|
325
|
-
-- ❌ 错误:canteen_name 未在 GROUP BY 中
|
|
326
|
-
SELECT
|
|
327
|
-
DATE(pay_time) AS orderDate,
|
|
328
|
-
canteen_name, -- ❌ 非聚合字段,未在 GROUP BY
|
|
329
|
-
SUM(real_amount) AS netAmount
|
|
330
|
-
FROM report_order_info
|
|
331
|
-
GROUP BY DATE(pay_time), canteen_id
|
|
332
|
-
|
|
333
|
-
-- ✅ 正确:所有非聚合字段都加入 GROUP BY
|
|
334
|
-
SELECT
|
|
335
|
-
DATE(pay_time) AS orderDate,
|
|
336
|
-
canteen_name,
|
|
337
|
-
SUM(real_amount) AS netAmount
|
|
338
|
-
FROM report_order_info
|
|
339
|
-
GROUP BY DATE(pay_time), canteen_id, canteen_name -- ✅ 包含 canteen_name
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### fix SQL 中的正确写法
|
|
343
|
-
|
|
344
|
-
```xml
|
|
345
|
-
<insert id="initFix">
|
|
346
|
-
INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
|
|
347
|
-
order_count, consume_amount, net_amount)
|
|
348
|
-
SELECT
|
|
349
|
-
#{id},
|
|
350
|
-
DATE(pay_time), -- SELECT 用 DATE(pay_time)
|
|
351
|
-
canteen_id,
|
|
352
|
-
canteen_name,
|
|
353
|
-
COUNT(*),
|
|
354
|
-
SUM(CASE WHEN consume_type = 1 THEN real_amount ELSE 0 END),
|
|
355
|
-
SUM(real_amount)
|
|
356
|
-
FROM report_order_info
|
|
357
|
-
WHERE pay_time BETWEEN #{startTime} AND #{endTime}
|
|
358
|
-
GROUP BY DATE(pay_time), canteen_id, canteen_name -- ✅ 与 SELECT 完全一致
|
|
359
|
-
</insert>
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### 开发检查项
|
|
363
|
-
|
|
364
|
-
- [ ] SELECT 每个非聚合字段,在 GROUP BY 中都有对应
|
|
365
|
-
- [ ] GROUP BY 的表达式与 SELECT 中的**完全一致**(`DATE_FORMAT(x, 'Y-m-d')` ≠ `DATE(x)`)
|
|
366
|
-
- [ ] ORDER BY 也使用相同表达式(保持一致)
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## 十、开发检查清单
|
|
371
|
-
|
|
372
|
-
### 建表
|
|
373
|
-
- [ ] 分组维度字段 + 金额汇总字段 + 审计字段(crby/crtime/upby/uptime/del_flag),无 tenant_id
|
|
374
|
-
|
|
375
|
-
### 实现
|
|
376
|
-
- [ ] 实现 `ReportOrderConsumeService`(或 Account 版本),设置 `getOrder()`
|
|
377
|
-
- [ ] `batchConsume()` — 分组 → 查存量 → 累加/新建
|
|
378
|
-
- [ ] `fix()` — 先删后插,从基础表重新聚合
|
|
379
|
-
|
|
380
|
-
### 退款处理
|
|
381
|
-
- [ ] 选择退款计算方式(直接 SUM 负数 / 分别统计 / 排除全退)
|
|
382
|
-
- [ ] 菜品退款关注 `detailState` 和 `goodsRefundNum`
|
|
383
|
-
|
|
384
|
-
### 查询接口
|
|
385
|
-
- [ ] 分页 + 合计行(PageVO + TotalVO)+ 三并行 CompletableFuture
|
|
386
|
-
|
|
387
|
-
### SQL 合规(only_full_group_by)
|
|
388
|
-
- [ ] SELECT 非聚合字段全部在 GROUP BY 中
|
|
389
|
-
- [ ] GROUP BY 表达式与 SELECT 完全一致
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## 十一、关键代码位置
|
|
394
|
-
|
|
395
|
-
| 类型 | 路径 |
|
|
396
|
-
|------|------|
|
|
397
|
-
| 下单 MQ 监听器 | `sys-canteen/.../report/statistics/config/mq/ReportOrderMQListener.java` |
|
|
398
|
-
| 退款 MQ 监听器 | `sys-canteen/.../report/statistics/config/mq/ReportOrderRefundMQListener.java` |
|
|
399
|
-
| 汇总消费调度 | `sys-canteen/.../report/statistics/config/mq/service/ReportConsumerService.java` |
|
|
400
|
-
| ConsumeService 接口 | `sys-canteen/.../report/statistics/config/mq/ReportOrderConsumeService.java` |
|
|
401
|
-
| 食堂汇总参考实现 | `sys-canteen/.../report/statistics/order/summary/service/ReportSumCanteenService.java` |
|
|
402
|
-
| Fix Controller | `sys-canteen/.../report/statistics/order/fix/controller/ReportFixController.java` |
|
|
403
|
-
| ReportOrderInfo 实体 | `sys-canteen/.../report/statistics/order/basic/model/ReportOrderInfo.java` |
|
|
404
|
-
| AccTradeTypeEnum | `sys-canteen/.../account/v3/constants/AccTradeTypeEnum.java` |
|
|
405
|
-
| AccWalletIdEnum | `sys-canteen/.../account/v3/constants/AccWalletIdEnum.java` |
|
|
406
|
-
|
|
407
|
-
---
|
|
408
|
-
|
|
409
|
-
## 注意
|
|
410
|
-
|
|
411
|
-
- CRUD 开发(非报表)请使用 `leniu-crud-development`
|
|
412
|
-
- MyBatis XML 编写规范请使用 `leniu-java-mybatis`
|
|
413
|
-
- 报表入参设计请使用 `leniu-java-report-query-param`
|
|
414
|
-
- 合计行实现请使用 `leniu-java-total-line`
|
|
415
|
-
- 餐次过滤请使用 `leniu-mealtime`
|