leniu-report-standard-customization
leniu 标准版定制报表开发指南
详细字段说明见
references/table-fields.md,经营分析详情见references/analysis-module.md
版本识别(必读)
本 skill 仅适用于标准版(core-report 独立模块)。
| 判断方式 | 标准版(本指南) | v5.29 版本 |
|---|---|---|
| 目录结构 | core-report/ 独立模块 |
sys-canteen/ 内嵌 |
| 退款存储 | 独立 report_refund 表(正数) |
合并入 report_order_info(consumeType=2,负数) |
| 第二阶段 | fix() 按日重算 |
batchConsume() 增量累加 |
| consumeType | 无此字段 | 1=消费,2=退款 |
v5.29 报表请使用
leniu-report-customization。钱包/交易类型枚举同 v5.29,参见该 skill。
一、报表系统架构
1.1 模块结构
core-report/.../statistics/
├── config/mq/ # MQ 监听器 + 消费调度 + 线程池
├── order/ # 订单报表(basic/summary/fix/analysis)
├── account/ # 账户流水报表
├── merchant/ # 商户维度报表
├── common/ # 错误日志/定时任务
└── param/vo/constants/ # 公共类
1.2 三大 MQ 监听器
| 监听器 | Topic/Tag |
|---|---|
ReportOrderMQListener |
order / order-v3-placed |
ReportOrderRefundMQListener |
order / order-v3-refunded |
ReportAccountMQListener |
acc / acc-trade-report-queue |
1.3 两阶段消费模型
第一阶段(ORDER < 10,同步写基础表)
├── ORDER=1 ReportOrderInfoService → report_order_info
├── ORDER=2 ReportOrderDiscountService → report_order_discount
├── ORDER=3 ReportOrderDetailService → report_order_detail
├── ORDER=4 ReportOrderPayService → report_order_pay
├── ORDER=5 ReportRefundService → report_refund
├── ORDER=6 ReportRefundDetailService → report_refund_detail
└── ORDER=11 ReportOrderInfoSnapshotService → report_order_info_snapshot
第二阶段(ORDER >= 10,fix 按日重算汇总表,由 Redis 计数触发)
├── ORDER=13 ReportSumMealtimeService → 分餐次汇总
├── ORDER=16 ReportSumPayService → 支付渠道汇总
├── ORDER=17 ReportSumPayMerService → 商户支付汇总
├── ORDER=18 ReportSumDishesService → 菜品销售汇总
├── ORDER=50 ReportAnalysisCustService → 用户分析
└── ORDER=51 ReportAnalysisDishesSaleService→ 菜品销售分析
1.4 第二阶段核心逻辑(fix 重算模式)
// ReportConsumerService.consumeOrderReport()
void consumeOrderReport() {
for (TenantInfo tenant : allTenants) {
Executors.doInTenant(tenant.getId(), () -> {
RLock lock = RedisUtil.getLock(REPORT_ORDER_LOCK);
lock.lock(120, TimeUnit.MINUTES);
try {
List<ReportNotConsumeDTO> list = reportOrderInfoService.queryNotConsumeData();
// 菜品:按 orderDate 分组调 fix
// 其他:按 statisticDate 分组,依次调所有 ORDER>=10 的 fix()
reportOrderInfoService.updateOrderMsg(list); // 标记已消费
} finally { lock.unlock(); }
});
}
}
触发机制:Redis 计数器每条消息递减,达阈值(默认100)触发 + XxlJob 定时兜底。
二、核心基础表概要
2.1 report_order_info(仅存正向订单,无 consumeType)
关键字段:orderId(主键), canteenId/stallId, mealtimeType, orderType, payableAmount/realAmount/refundAmount(分), accPayAmount/outPayAmount, payTime/orderDate, orderRefundState(1未退/2全退/3部分退), status(0未消费/1已消费), nuClearMode, psnType, ageType/holidayType
2.2 report_order_detail(菜品明细)
关键字段:detailId, orderId, goodsDishesId/goodsDishesName, price/totalAmount/realAmount(分), quantity, salesMode(1按份/2称重), detailState(1正常/2全退/3部分退), goodsRefundNum, refundAmount, detailType
2.3 report_refund / report_refund_detail(标准版特有,退款为正数)
report_refund:orderRefundId(主键), orderId(原订单), realRefundAmount(正数), applyType(1退单/2纠错), checkTime
report_refund_detail:orderRefundId, detailId, realQuantity, realRefundAmount(正数)
2.4 其他基础表
- report_order_pay:
orderId,payType/payChannel,payAmount/refundAmount - report_order_discount:
orderId,changeAmount,changeType(1上浮/2优惠),changeDetailType - report_order_info_snapshot:订单交易快照
三、退款数据处理(核心重点)
3.1 存储模型
正向订单 → report_order_info(realAmount 为正)
退款记录 → report_refund(realRefundAmount 为正)+ report_refund_detail
同时更新 → report_order_info.orderRefundState + refundAmount
3.2 净消费金额计算(3种方式)
方式一:主表 refundAmount 减退(推荐)
SELECT SUM(real_amount - IFNULL(refund_amount, 0)) AS netAmount
FROM report_order_info WHERE pay_time BETWEEN #{start} AND #{end}
方式二:排除全退
WHERE order_refund_state IN (1, 3)
方式三:关联 report_refund
SELECT o.canteen_id, SUM(o.real_amount) AS consume, IFNULL(SUM(r.real_refund_amount), 0) AS refund
FROM report_order_info o LEFT JOIN report_refund r ON o.order_id = r.order_id
GROUP BY o.canteen_id
3.3 菜品级别退款
SELECT goods_dishes_name,
SUM(quantity - IFNULL(goods_refund_num, 0)) AS netQuantity,
SUM(total_amount - IFNULL(refund_amount, 0)) AS netAmount
FROM report_order_detail WHERE detail_state IN (1, 3) GROUP BY goods_dishes_name
四、账户流水报表
4.1 report_account_flow(流水主表)
核心字段:flowId, custId/orgId, payTime, flowType(AccTradeTypeEnum), flowRealAmount/flowAmount, manageCost, accTotalBal/accAllBal, status
4.2 report_account_summary(用户账户日结表)
联合主键:statisticDate + custId。期末余额 = 期初 + 充值 - 撤销充值 + 补贴 - 撤销补贴 + 红包 + 赠送 - 消费 - 补扣 + 退款 - 提现 - 清空 - 管理费
4.3 AccountConsumeService 实现
| ORDER | 类 | 汇总表 |
|---|---|---|
| 1/2 | Flow/FlowDetail | 基础表 |
| 13 | AccountSummary | 日结 |
| 14 | AccountOperator | 操作员 |
| 15-17 | ConsumeSummary/Org/Type | 消费维度 |
| 18 | WalletConsume | 钱包 |
| 19 | SumRechargeMer | 商户充值 |
五、汇总表开发标准模式
5.1 接口与实现
@Service @Slf4j
public class ReportSumXxxService implements ReportOrderConsumeService {
@Override public int getOrder() { return 15; } // 10-29普通,30+菜品,50+分析
@Override public void consume(OrderChangePO payload, ReportOrderInfoDTO baseInfo) {
// 标准版:留空,由 fix() 统一处理
}
@Override public void fix(ReportBaseParam param) {
LocalDateTime start = param.getStartPayTime(), end = param.getEndPayTime();
mapper.delete(Wrappers.<ReportSumXxx>lambdaQuery()
.between(ReportSumXxx::getStatisticDate, start.toLocalDate(), end.toLocalDate()));
mapper.initFix(start, end);
}
}
5.2 fix SQL 模板
<insert id="initFix">
INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
order_count, consume_amount, refund_amount, net_amount)
SELECT #{id}, DATE(a.pay_time), a.canteen_id, a.canteen_name,
COUNT(*), SUM(a.real_amount),
SUM(IFNULL(a.refund_amount, 0)),
SUM(a.real_amount - IFNULL(a.refund_amount, 0))
FROM report_order_info a
WHERE a.pay_time BETWEEN #{startTime} AND #{endTime}
GROUP BY DATE(a.pay_time), a.canteen_id, a.canteen_name
</insert>
5.3 查询接口(并行 + 权限)
public ReportBaseTotalVO<XxxVO> pageSummary(XxxParam param) {
MgrUserAuthPO authPO = MgrUserAuthApi.getUserAuthPO();
CompletableFuture<List<XxxVO>> listF = supplyAsync(() -> mapper.listSummary(param, authPO));
CompletableFuture<XxxVO> totalF = supplyAsync(() -> mapper.getSummaryTotal(param, authPO));
CompletableFuture.allOf(listF, totalF).join();
return new ReportBaseTotalVO<>(PageVO.of(listF.join(), param.getPage()), totalF.join());
}
权限 SQL:
<if test="'-1'.toString() != authPO.roleType.toString()">
AND EXISTS (SELECT null FROM mgr_role_org it1
WHERE a.org_id = it1.org_id AND it1.role_id = #{authPO.roleId})
</if>
六、汇总模型速查
| 表 | 维度 | 金额 |
|---|---|---|
| report_sum_mealtime | date/canteen/stall/org/age/mealtime/psn/machine/source | custNum/consumeNum/realAmount/refundAmount |
| report_sum_pay | date/mealtime/canteen/stall/org/age/payChannel/payType | payNum/realAmount/refundAmount |
| report_sum_dishes | date/area/canteen/stall/reportOrderType/mealtime/cook/device/dishes/salesMode/detailType | quantity/realAmount |
| report_sum_pay_mer | tenantId/date/payChannel/payType | custNum/payNum/realAmount/refundAmount |
七、经营分析模块
| 分析 | Service | 路由前缀 |
|---|---|---|
| 营业额 | ReportAnalysisTurnoverService | /summary/analysis/turnover/ |
| 用户 | ReportAnalysisCustService (ORDER=50) | /summary/analysis/cust/ |
| 菜品 | ReportAnalysisDishesSaleService (ORDER=51) | /summary/analysis/dishes/ |
| 满意度 | ReportAnalysisEvaluateService | /summary/analysis/evaluate/ |
| 充值 | ReportAnalysisTurnoverService | /summary/analysis/recharge/ |
| 设备 | ReportAnalysisTurnoverService | /summary/analysis/device/ |
八、公共模块
- 报表错误日志(report_error_log):
reportErrorType(1账户/2订单),reportErrorState(1已创建/2已处理)。定时任务@XxlJob("reportExceptionHandle")自动修复。 - 金额范围设置:
POST /report/alloc/amount-scope/save - 数据修复:
POST /summary/fix/order|account(限31天,Redisson 锁 120 分钟)
核心枚举
| 枚举 | 值 |
|---|---|
| ReportClassifyEnum | 1组织/2类别/3食堂/4设备/5收入/6渠道/7餐次 |
| ReportPayTypeEnum | 1微信/2支付宝/3系统账户/9现金/20其他 |
九、MySQL only_full_group_by 规范(必须遵守)
MySQL 默认开启
sql_mode=ONLY_FULL_GROUP_BY,SELECT 中所有非聚合列必须出现在 GROUP BY 中,且 GROUP BY 表达式必须与 SELECT 表达式完全一致。
核心规则
SELECT 的表达式 == GROUP BY 的表达式(字符级别完全一致)
❌ 错误示例(GROUP BY 与 SELECT 不一致)
<!-- 报错:Expression #1 of SELECT list is not in GROUP BY clause -->
SELECT
DATE_FORMAT(roi.consume_time, '%Y-%m-%d') AS statisticDate,
SUM(roi.real_amount) AS totalAmount
FROM report_order_info roi
GROUP BY DATE(roi.consume_time) <!-- ❌ DATE() ≠ DATE_FORMAT(..., '%Y-%m-%d') -->
ORDER BY DATE(roi.consume_time)
✅ 正确示例(GROUP BY 与 SELECT 完全一致)
SELECT
DATE_FORMAT(roi.consume_time, '%Y-%m-%d') AS statisticDate,
SUM(roi.real_amount) AS totalAmount
FROM report_order_info roi
GROUP BY DATE_FORMAT(roi.consume_time, '%Y-%m-%d') <!-- ✅ 与 SELECT 完全一致 -->
ORDER BY DATE_FORMAT(roi.consume_time, '%Y-%m-%d') <!-- ✅ ORDER BY 也保持一致 -->
❌ 错误示例(SELECT 含非聚合列未加入 GROUP BY)
SELECT
roi.canteen_id,
roi.canteen_name, <!-- ❌ 非聚合列未在 GROUP BY 中 -->
SUM(roi.real_amount) AS totalAmount
FROM report_order_info roi
GROUP BY roi.canteen_id
✅ 正确示例(所有非聚合列都在 GROUP BY 中)
SELECT
roi.canteen_id,
roi.canteen_name,
SUM(roi.real_amount) AS totalAmount
FROM report_order_info roi
GROUP BY roi.canteen_id, roi.canteen_name <!-- ✅ 所有非聚合列都在 GROUP BY -->
检查清单
- SELECT 中按日期分组时使用
DATE_FORMAT(col, '%Y-%m-%d'),不要用DATE() - GROUP BY 表达式与 SELECT 中对应列逐字相同(复制粘贴而非重写)
- ORDER BY 中同样使用与 GROUP BY 一致的表达式
- SELECT 中所有非聚合列(无 SUM/COUNT/AVG/MAX/MIN 包裹)都出现在 GROUP BY 中
十、开发检查清单
建表
- 分组维度 + 金额汇总 + 审计字段(crby/crtime/upby/uptime/del_flag),无 tenant_id
实现
- 实现
ReportOrderConsumeService,设getOrder() -
fix()先删后插(标准版核心模式),consume()留空
退款(标准版特有)
- 退款在独立
report_refund表(正数金额) - 净消费 =
real_amount - IFNULL(refund_amount, 0) - 不要使用 consumeType 字段
查询
- ReportBaseTotalVO + CompletableFuture 并行 + MgrUserAuthPO 权限
- GROUP BY / ORDER BY 表达式与 SELECT 完全一致(only_full_group_by)
十一、关键代码位置
路径前缀均为
core-report/.../statistics/
| 类型 | 路径 |
|---|---|
| MQ 监听器 | config/mq/ReportOrderMQListener.java / ReportAccountMQListener.java |
| 消费调度 | config/mq/service/ReportConsumerService.java |
| 订单基础表 | order/basic/model/ReportOrderInfo.java / ReportRefund.java |
| 汇总 Service | order/summary/service/ReportSumMealtimeService.java / ReportSumPayService.java |
| 分析 Service | order/analysis/service/ReportAnalysisTurnoverService.java |
| 账户 Service | account/service/ReportAccountSummaryService.java |
| Fix | order/fix/controller/ReportFixController.java |
注意
- 标准版退款为独立表(正数金额),不要使用 consumeType 字段
- 标准版第二阶段用
fix()按日重算,不要使用 batchConsume() 增量模式 - CRUD 用
leniu-crud-development,MyBatis 用leniu-java-mybatis,入参用leniu-java-report-query-param,合计行用leniu-java-total-line,餐次用leniu-mealtime