MatchMaster/server/laf-cloud/rechargeStats.README.md

5.6 KiB
Raw Blame History

付费用户充值统计定时任务

云函数 rechargeStats 每次全量重算当前 users.pay_user === true 的玩家,将结果覆盖到 users.rechargeStats。默认每小时整点执行;不修改订单和玩家付费标记。

保存字段

字段 含义
amount15d 从本轮运行时刻倒推 15 × 24 小时的充值金额,元
amount30d 从本轮运行时刻倒推 30 × 24 小时的充值金额,元
amountTotal 截至本轮运行时刻、当前库中可统计的累计充值金额,元
currency 固定 CNY
asOf 本轮统计截止时间,原始 Unix 毫秒时间戳
orderCount 纳入累计金额的去重订单数
fallbackTimeOrderCount 无支付确认时间,使用下单时间的订单数
missingTimeOrderCount 支付和下单时间均缺失,仅计入累计的订单数
invalidOrderCount 缺失订单号、金额或数量无效而跳过的记录数
duplicateOrderCount 跳过的重复订单记录数
missingOpenid 用户是否缺少可关联订单的 openid
version 统计规则版本,当前为 1

统计口径

  • 通过 users.openid = order.openid 关联,仅纳入数值型 state: 1/2 的记录。未确认支付的 state: 0 和临时集合 iosOrder 不参与。
  • 排除 paymentAppEnv: "test" 或 outTradeNo 以 wct_ 开头的测试订单;保留没有环境字段的历史订单。
  • 每个 openid 内按 outTradeNo 去重,同号多条按 _id 升序取第一条金额有效、时间不在未来的记录。若同号金额不同,应人工核查重复数据。
  • 金额为 goodsPrice × itemCount,先用整数分累加,再除以 100 保存为元。金额和数量兼容数字字符串,要求正安全整数;缺失数量不默认当作 1,异常记录计入 invalidOrderCount。累计金额超出安全整数范围则报错,不保存失真的数值。
  • 每轮只获取一次当前时间,窗口为 [asOf - N × 86400000, asOf],不是自然日,也不是从午夜倒推。
  • 优先使用 chargeTime。当前支付写入代码使用 new Date(Date.now() + 8小时),因此还原时减去 8 小时;此处针对现有存储约定,若以后修正支付时间存储方式,必须同步调整统计规则。
  • chargeTime 缺失或无效时回退到原始 order.time,不再减 8 小时;两个时间都不可用时只计入累计,并记录异常数量。支持原始毫秒数、Date 和带时区的 ISO 字符串;不解析无时区日期字符串。
  • 已知时间在未来的订单暂不纳入任何金额。没有匹配订单的付费用户也保存零值结果;这不代表迁移前从未充值,结合 orderCount 核查历史完整性。
  • 当前项目没有退款同步逻辑,因此这是当前已确认订单的金额统计,不扣除退款,不还原渠道优惠后的实付。订单状态异常仍可能造成偏差。

执行与一致性

用户按 _id 游标每批 100 条读取,订单使用 MongoDB 游标遍历,避免默认查询上限截断。每批只更新 rechargeStats,写入时重新检查 pay_user 和 openid,且较早轮次不会覆盖较新 asOf 的快照。重复执行不会重复累加;没有新增充值时,旧订单也会按新截止时间退出滚动窗口。

整轮不是数据库事务:运行期间新支付或用户变更可能下一轮才体现。失败时抛出错误,已完成批次保留,剩余用户下次重算;每个用户的 asOf 可判断新旧结果,不能把混合轮次当作同一时刻的全库快照。

建议上线前在 Laf 数据库控制台建立以下非唯一索引(若已有等价索引则复用):

db.collection('users').createIndex({ pay_user: 1, _id: 1 });
db.collection('order').createIndex({ openid: 1, outTradeNo: 1, _id: 1 });

发布和启用

代码与触发器配置是独立资源,提交本地文件不会自动启动线上定时任务。recharge-stats.trigger.json 使用 Laf 创建触发器 API 的 desc / target / cron 字段。

  1. 在目标 Laf 应用发布 functions/rechargeStats.ts 和同名 YAML,保持 methods: [],不开放公共 HTTP 入口。
  2. 在云函数控制台手动执行一次 rechargeStats(无参数),检查返回的 processedUsers、updatedUsers 和部分玩家的结果,确认运行耗时。
  3. 在触发器面板绑定函数 rechargeStats,使用 recharge-stats.trigger.json 的配置:每小时整点执行。
  4. 检查已有触发器,避免重复创建。若使用已登录且已绑定正确应用的 Laf CLI,可执行:
laf trigger list
laf trigger create "付费用户充值统计(每小时)" rechargeStats "0 * * * *"
  1. 首次定时触发后确认日志出现 rechargeStats completed,抽查 users.rechargeStats.asOf 已更新。大规模历史数据上线前应确认全量耗时在云函数执行时限内。

参考:Laf 定时任务文档、官方 CLI 触发器命令、创建触发器字段。

本地测试

Node.js 24.11 或兼容 registerHooks / stripTypeScriptTypes 的版本:

node --test laf-cloud/tests/recharge-stats.test.mjs

覆盖滚动时间边界、8 小时修正、支付时间优先、时间回退、无效金额、数量、去重、测试订单排除、超过 1000 条订单、多页用户、重跑、并发写保护、异常中断和关闭 HTTP 入口。使用内存 MongoDB 接口替身,不连接生产数据库。