forked from wangziqi/gongxue-base
feat: alert usage overage worker failures
This commit is contained in:
12
README.md
12
README.md
@@ -15,11 +15,11 @@
|
|||||||
- 租户后台能力:品牌、主题模板/草稿/发布、域名、公开设置、支付账户、登录配置、私密密钥掩码、活动内容、考试日期、题目反馈处理、用户站内通知查看、激活码、优惠券规则/核销报表、勋章管理/手动发放/签到积分反馈自动发放、成员权限、自定义角色模板、班级/教师/学生范围权限、学生批量导入、批量分班、学生备注、跟进任务、跟进效果统计、学习督导自动化预览/生成、督导规则模板、学生批量 CRM 推送、审计日志。
|
- 租户后台能力:品牌、主题模板/草稿/发布、域名、公开设置、支付账户、登录配置、私密密钥掩码、活动内容、考试日期、题目反馈处理、用户站内通知查看、激活码、优惠券规则/核销报表、勋章管理/手动发放/签到积分反馈自动发放、成员权限、自定义角色模板、班级/教师/学生范围权限、学生批量导入、批量分班、学生备注、跟进任务、跟进效果统计、学习督导自动化预览/生成、督导规则模板、学生批量 CRM 推送、审计日志。
|
||||||
- 租户内容能力:可配置题库入口、任意深度分类树、考试意向标记、题目集合、顺序/随机/全真模拟蓝图、题目录入/更新、视频绑定、分数线、单词、知识手册、资料资源台账、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 批量导入。
|
- 租户内容能力:可配置题库入口、任意深度分类树、考试意向标记、题目集合、顺序/随机/全真模拟蓝图、题目录入/更新、视频绑定、分数线、单词、知识手册、资料资源台账、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 批量导入。
|
||||||
- 学生端能力:题库入口、分类树、题目集合、顺序/随机/模考 session 组卷快照、答题、错题本、收藏夹、背单词卡片学习/发音/收藏练习、个人中心、男女默认预设头像、站内通知、勋章、考试倒计时、签到积分、积分活动任务、积分兑换、题目反馈、排行榜接口(租户默认关闭)、分数线、AI 择校推荐、题目视频、订单详情/状态轮询、优惠券领取/抵扣、权益、激活码预检查/兑换、资料下载;签到、积分阈值、反馈解决和积分活动可返回自动获得勋章结果,反馈处理/奖励、勋章发放和积分兑换会写入用户站内通知。学生头像不支持上传或第三方头像落库,学生激励默认以勋章自动发放为主,不默认启用排行榜。
|
- 学生端能力:题库入口、分类树、题目集合、顺序/随机/模考 session 组卷快照、答题、错题本、收藏夹、背单词卡片学习/发音/收藏练习、个人中心、男女默认预设头像、站内通知、勋章、考试倒计时、签到积分、积分活动任务、积分兑换、题目反馈、排行榜接口(租户默认关闭)、分数线、AI 择校推荐、题目视频、订单详情/状态轮询、优惠券领取/抵扣、权益、激活码预检查/兑换、资料下载;签到、积分阈值、反馈解决和积分活动可返回自动获得勋章结果,反馈处理/奖励、勋章发放和积分兑换会写入用户站内通知。学生头像不支持上传或第三方头像落库,学生激励默认以勋章自动发放为主,不默认启用排行榜。
|
||||||
- 平台后台能力:租户管理、租户详情、账务资料维护、平台员工创建/授权/启停、平台细粒度权限点、平台审计日志查询和 CSV/JSON 导出、平台审计告警规则/开放告警查询/确认/解决、平台审计告警外部通知渠道和发送事件、SaaS 套餐、订阅、订阅账单候选预览/dry-run/批量生成、自动计费 worker、账单、服务费收款、逾期标记、内部催缴台账、平台催缴外部通知渠道和发送事件、平台用量自动采集 worker、用量记录、SaaS 套餐额度判定、用量超额账单候选预览/dry-run/生成和自动开票 worker、公共题库授权。
|
- 平台后台能力:租户管理、租户详情、账务资料维护、平台员工创建/授权/启停、平台细粒度权限点、平台审计日志查询和 CSV/JSON 导出、平台审计告警规则/开放告警查询/确认/解决、平台审计告警外部通知渠道和发送事件、SaaS 套餐、订阅、订阅账单候选预览/dry-run/批量生成、自动计费 worker、账单、服务费收款、逾期标记、内部催缴台账、平台催缴外部通知渠道和发送事件、平台用量自动采集 worker、用量记录、SaaS 套餐额度判定、用量超额账单候选预览/dry-run/生成、自动开票 worker 和失败审计告警、公共题库授权。
|
||||||
- 公共题库商业化能力:租户可采纳平台授权题库为本租户副本,并可手动或由 worker 自动同步平台新增/更新题目;同步会保护租户自改题目,返回冲突而不覆盖,后台可查询冲突明细;worker 失败会生成租户 `public_question_bank_sync_failed` 通知,恢复成功自动关闭失败通知,平台可用 `/api/platform-admin/question-bank-sync-status` 按 `platform:question_bank:ops` 查看跨租户同步运营摘要。
|
- 公共题库商业化能力:租户可采纳平台授权题库为本租户副本,并可手动或由 worker 自动同步平台新增/更新题目;同步会保护租户自改题目,返回冲突而不覆盖,后台可查询冲突明细;worker 失败会生成租户 `public_question_bank_sync_failed` 通知,恢复成功自动关闭失败通知,平台可用 `/api/platform-admin/question-bank-sync-status` 按 `platform:question_bank:ops` 查看跨租户同步运营摘要。
|
||||||
- 题库导出能力:租户内容编辑可按题目集合、内容入口或分类节点导出 JSON、`paper_json`、打印 payload、PDF、Word 和每日一练图片 ZIP 素材包,后端强制租户隔离、答案/解析开关、复合题子题脱敏、导出 job 和审计;PDF/Word/ZIP 由 exports worker 生成水印文件或运营素材并发布到 `content_assets`;`daily_practice` 支持每日一练九宫格 metadata、PDF/Word 版式、9 张 PNG/SVG 卡片和拼图包。
|
- 题库导出能力:租户内容编辑可按题目集合、内容入口或分类节点导出 JSON、`paper_json`、打印 payload、PDF、Word 和每日一练图片 ZIP 素材包,后端强制租户隔离、答案/解析开关、复合题子题脱敏、导出 job 和审计;PDF/Word/ZIP 由 exports worker 生成水印文件或运营素材并发布到 `content_assets`;`daily_practice` 支持每日一练九宫格 metadata、PDF/Word 版式、9 张 PNG/SVG 卡片和拼图包。
|
||||||
- 销售/代理/CRM 增长链路:邀请码、扫码/分享事件、首绑客资保护、销售统计、团队关系、CRM 配置、跟进分配策略、客资队列和学生批量 CRM 跟进推送。
|
- 销售/代理/CRM 增长链路:邀请码、扫码/分享事件、首绑客资保护、销售统计、团队关系、CRM 配置、跟进分配策略、客资队列和学生批量 CRM 跟进推送。
|
||||||
- `apps/worker` 后台任务进程:CRM webhook 队列消费、`lead.created` 客资事件、`student.crm_push` 学生跟进事件、generic/钉钉/飞书/企微机器人发送、签名、失败重试和日志;student-supervision worker 可按租户督导规则模板定时生成学习跟进任务;commerce worker 可补偿查询微信/支付宝支付和退款状态;provider-bills worker 可下载微信/支付宝官方账单并导入资金对账;platform-billing worker 可自动为即将到期且未开票的 SaaS 订阅生成服务费账单并写审计;platform-usage worker 可按月从权威业务表自动采集学生数、活跃学生、题量、资源数、存储 GB、视频数、视频播放、视频次数消耗、已支付订单、GMV 和有效权益,并写入用量台账和审计;platform-dunning worker 可扫描逾期未结清服务费账单、标记 overdue、写内部催缴记录和审计;platform-dunning-notifications worker 可把内部催缴记录按平台渠道推送到 generic/钉钉/飞书/企微 webhook,并记录幂等发送事件;platform-audit-alerts worker 可把高风险平台审计动作转换为内部告警并递归脱敏告警 details;platform-audit-notifications worker 可把开放审计告警按平台渠道推送到 generic/钉钉/飞书/企微 webhook,并记录幂等发送事件;assets worker 可复检托管资源元数据、执行内置安全扫描并自动下架异常资源;imports worker 可执行大批量导入;public-banks worker 可自动同步公共题库采纳副本;exports worker 可渲染 PDF/Word 导出文件和每日一练 ZIP 图片素材包。
|
- `apps/worker` 后台任务进程:CRM webhook 队列消费、`lead.created` 客资事件、`student.crm_push` 学生跟进事件、generic/钉钉/飞书/企微机器人发送、签名、失败重试和日志;student-supervision worker 可按租户督导规则模板定时生成学习跟进任务;commerce worker 可补偿查询微信/支付宝支付和退款状态;provider-bills worker 可下载微信/支付宝官方账单并导入资金对账;platform-billing worker 可自动为即将到期且未开票的 SaaS 订阅生成服务费账单并写审计;platform-usage worker 可按月从权威业务表自动采集学生数、活跃学生、题量、资源数、存储 GB、视频数、视频播放、视频次数消耗、已支付订单、GMV 和有效权益,并写入用量台账和审计;platform-usage-overage worker 可按上月账期自动生成 `usage_overage` 账单,执行失败会写入脱敏平台审计日志并由审计告警 worker 转为开放告警;platform-dunning worker 可扫描逾期未结清服务费账单、标记 overdue、写内部催缴记录和审计;platform-dunning-notifications worker 可把内部催缴记录按平台渠道推送到 generic/钉钉/飞书/企微 webhook,并记录幂等发送事件;platform-audit-alerts worker 可把高风险平台审计动作和自动化失败转换为内部告警并递归脱敏告警 details;platform-audit-notifications worker 可把开放审计告警按平台渠道推送到 generic/钉钉/飞书/企微 webhook,并记录幂等发送事件;assets worker 可复检托管资源元数据、执行内置安全扫描并自动下架异常资源;imports worker 可执行大批量导入;public-banks worker 可自动同步公共题库采纳副本;exports worker 可渲染 PDF/Word 导出文件和每日一练 ZIP 图片素材包。
|
||||||
- 销售/代理分佣结算基础闭环:租户默认比例、成员比例、激活码批次比例、订单/激活码归因、结算单生成、审核、线下打款状态、CSV/JSON 导出、打款凭证登记/复核和权限隔离。
|
- 销售/代理分佣结算基础闭环:租户默认比例、成员比例、激活码批次比例、订单/激活码归因、结算单生成、审核、线下打款状态、CSV/JSON 导出、打款凭证登记/复核和权限隔离。
|
||||||
- 订单售后基础闭环:退款请求、审核、处理状态流、微信/支付宝发起退款、微信/支付宝退款查询确认、微信/支付宝退款通知 webhook、退款金额累计、部分/全额退款订单状态、全额退款权益撤销、退款事件和审计日志。
|
- 订单售后基础闭环:退款请求、审核、处理状态流、微信/支付宝发起退款、微信/支付宝退款查询确认、微信/支付宝退款通知 webhook、退款金额累计、部分/全额退款订单状态、全额退款权益撤销、退款事件和审计日志。
|
||||||
- 资金对账、异常订单和财务凭证闭环:租户财务/运营可通过 `/api/commerce/reconciliation/*` 导入或预览支付/退款账单行,也可创建微信/支付宝官方账单下载任务;后端按租户隔离比对本地订单、支付、退款记录,识别已匹配、金额不一致、状态不一致、供应商有本地无、本地有供应商无、重复行和无效行,并写入对账批次、明细和审计日志;异常明细可创建差错工单,支持分配、开始处理、升级、解决、忽略、重开和事件留痕;`/api/commerce/operations/anomalies` 聚合异常订单风险,`/api/commerce/adjustment-vouchers*` 支持人工调整凭证、复核、事件轨迹和报表。工单和凭证只做财务审核闭环,不直接修改订单、支付、退款或权益。
|
- 资金对账、异常订单和财务凭证闭环:租户财务/运营可通过 `/api/commerce/reconciliation/*` 导入或预览支付/退款账单行,也可创建微信/支付宝官方账单下载任务;后端按租户隔离比对本地订单、支付、退款记录,识别已匹配、金额不一致、状态不一致、供应商有本地无、本地有供应商无、重复行和无效行,并写入对账批次、明细和审计日志;异常明细可创建差错工单,支持分配、开始处理、升级、解决、忽略、重开和事件留痕;`/api/commerce/operations/anomalies` 聚合异常订单风险,`/api/commerce/adjustment-vouchers*` 支持人工调整凭证、复核、事件轨迹和报表。工单和凭证只做财务审核闭环,不直接修改订单、支付、退款或权益。
|
||||||
@@ -227,7 +227,7 @@ WORKER_PLATFORM_BILLING_ID=platform-billing-prod-1
|
|||||||
npm --workspace @tiku-saas/worker run platform-usage-overage:once
|
npm --workspace @tiku-saas/worker run platform-usage-overage:once
|
||||||
```
|
```
|
||||||
|
|
||||||
生产定时任务建议每月 1 日低峰先用 `WORKER_PLATFORM_USAGE_MONTH=上月 YYYY-MM` 运行 `platform-usage` 采集完整用量快照,再运行 `node dist/apps/worker/src/index.js --once --job platform-usage-overage` 自动生成上一个自然月的 `usage_overage` 账单。该 worker 复用后端统一超额计算服务,只读取 `tenant_usage_records`、SaaS 套餐 `included_quotas/overage_prices` 和订阅 metadata 覆盖,使用唯一索引和账单查重防重复开票。
|
生产定时任务建议每月 1 日低峰先用 `WORKER_PLATFORM_USAGE_MONTH=上月 YYYY-MM` 运行 `platform-usage` 采集完整用量快照,再运行 `node dist/apps/worker/src/index.js --once --job platform-usage-overage` 自动生成上一个自然月的 `usage_overage` 账单。该 worker 复用后端统一超额计算服务,只读取 `tenant_usage_records`、SaaS 套餐 `included_quotas/overage_prices` 和订阅 metadata 覆盖,使用唯一索引和账单查重防重复开票;账期月份必须是 `YYYY-MM` 且月份在 `01..12`。worker 执行失败会写入 `platform.invoice.usage_overage_worker_failed` 脱敏平台审计日志,后续由 `platform-audit-alerts` 转为高优先级开放告警,并可继续通过 `platform-audit-notifications` 推送到钉钉/飞书/企微。
|
||||||
|
|
||||||
```text
|
```text
|
||||||
WORKER_PLATFORM_USAGE_OVERAGE_BATCH_SIZE=100
|
WORKER_PLATFORM_USAGE_OVERAGE_BATCH_SIZE=100
|
||||||
@@ -613,6 +613,8 @@ npm run check:api
|
|||||||
npm run check:worker
|
npm run check:worker
|
||||||
npm run test:worker:commerce
|
npm run test:worker:commerce
|
||||||
npm run test:worker:platform-billing
|
npm run test:worker:platform-billing
|
||||||
|
npm run test:worker:platform-usage
|
||||||
|
npm run test:worker:platform-usage-overage
|
||||||
npm run test:worker:platform-dunning
|
npm run test:worker:platform-dunning
|
||||||
npm run test:worker:platform-dunning-notifications
|
npm run test:worker:platform-dunning-notifications
|
||||||
npm run test:worker:platform-audit-alerts
|
npm run test:worker:platform-audit-alerts
|
||||||
@@ -627,7 +629,7 @@ npm run audit:runtime
|
|||||||
git diff --check
|
git diff --check
|
||||||
```
|
```
|
||||||
|
|
||||||
结果:通过。最近一轮真实迁移专项验证已通过 `npx supabase db reset`、`npm run pb:import:json`、`npm run pb:import:validate`、`npm run pb:import:sample` 和 `npm run check:importer`;`pb:import:sample` 当前为 0 failures、6 warnings、1 skipped、39 passed;production dry-run 仍按预期返回非 0,因为旧数据本身还剩订单缺用户和手册章节缺归属两个 blocker。`npm run test:auth:remote-smoke` 覆盖远程 Auth/JWKS 验收脚本自身。`npm run test:rls` 覆盖 75 条运行时 RLS 断言,包含主租户、合作商租户、无租户 claim、平台管理员旁路和跨租户写入拒绝。`npm run test:api` 覆盖平台细粒度权限、公共题库跨租户同步运营状态、资源访问事件、锁定 CDN 资源拒绝、provider-managed CDN 显式放行、学生短 TTL 下载/预览、访问记录查询、安全扫描门禁、官方账单下载任务权限和脱敏响应、异常订单运营台、人工调整凭证提交/复核/事件/报表、平台账单逾期 dry-run/催缴记录、平台审计告警查询/状态更新/越权拒绝/敏感 details 脱敏、平台审计告警通知渠道/事件查询和密钥不回显、平台催缴通知渠道/事件查询和密钥不回显、租户隔离,以及凭证审批不修改订单/支付/权益。`npm run test:worker:commerce` 覆盖支付/退款补偿、微信/支付宝官方账单下载、账单 hash 校验、导入 `provider_download` 对账批次和密钥不泄露。`npm run test:worker:platform-billing` 覆盖平台 SaaS 订阅自动计费、重复开票保护、账单明细和审计。`npm run test:worker:platform-usage` 覆盖平台 SaaS 月度用量自动采集、11 类指标、手工调整记录不覆盖和重复运行幂等。`npm run test:worker:platform-dunning` 覆盖平台 SaaS 逾期账单标记、内部催缴记录、租户 `past_due` 状态和每日催缴幂等。`npm run test:worker:platform-dunning-notifications` 覆盖平台 SaaS 催缴外部通知入队、generic webhook 发送、幂等、防重复、联系方式掩码、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:platform-audit-alerts` 覆盖平台审计告警生成、规则匹配、幂等、防重复和告警 details 脱敏。`npm run test:worker:platform-audit-notifications` 覆盖平台审计告警外部通知入队、generic webhook 发送、幂等、防重复、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:assets` 覆盖托管资源复检、内置安全扫描、外部 HTTP scanner 通过/失败/不可用 fail-closed、扫描失败/跳过事件和异常资源自动下架。`npm run test:worker:public-banks` 覆盖公共题库自动同步、失败通知和恢复自动关闭。`npm run test:worker:exports` 覆盖导出 worker 生成可信资源并标记 `securityScanStatus=passed`。`npm run audit:runtime` 当前为 0 vulnerabilities;Excel 解析已从 `exceljs` 切换为 `read-excel-file`,避免生产运行时携带 `exceljs -> uuid` 的已知中危依赖。
|
结果:通过。最近一轮真实迁移专项验证已通过 `npx supabase db reset`、`npm run pb:import:json`、`npm run pb:import:validate`、`npm run pb:import:sample` 和 `npm run check:importer`;`pb:import:sample` 当前为 0 failures、6 warnings、1 skipped、39 passed;production dry-run 仍按预期返回非 0,因为旧数据本身还剩订单缺用户和手册章节缺归属两个 blocker。`npm run test:auth:remote-smoke` 覆盖远程 Auth/JWKS 验收脚本自身。`npm run test:rls` 覆盖 75 条运行时 RLS 断言,包含主租户、合作商租户、无租户 claim、平台管理员旁路和跨租户写入拒绝。`npm run test:api` 覆盖平台细粒度权限、公共题库跨租户同步运营状态、资源访问事件、锁定 CDN 资源拒绝、provider-managed CDN 显式放行、学生短 TTL 下载/预览、访问记录查询、安全扫描门禁、官方账单下载任务权限和脱敏响应、异常订单运营台、人工调整凭证提交/复核/事件/报表、平台账单逾期 dry-run/催缴记录、平台审计告警查询/状态更新/越权拒绝/敏感 details 脱敏、平台审计告警通知渠道/事件查询和密钥不回显、平台催缴通知渠道/事件查询和密钥不回显、租户隔离,以及凭证审批不修改订单/支付/权益。`npm run test:worker:commerce` 覆盖支付/退款补偿、微信/支付宝官方账单下载、账单 hash 校验、导入 `provider_download` 对账批次和密钥不泄露。`npm run test:worker:platform-billing` 覆盖平台 SaaS 订阅自动计费、重复开票保护、账单明细和审计。`npm run test:worker:platform-usage` 覆盖平台 SaaS 月度用量自动采集、11 类指标、手工调整记录不覆盖和重复运行幂等。`npm run test:worker:platform-usage-overage` 覆盖平台 SaaS 超额账单明细、重复开票保护、非法账期失败审计、审计告警生成和敏感错误信息脱敏。`npm run test:worker:platform-dunning` 覆盖平台 SaaS 逾期账单标记、内部催缴记录、租户 `past_due` 状态和每日催缴幂等。`npm run test:worker:platform-dunning-notifications` 覆盖平台 SaaS 催缴外部通知入队、generic webhook 发送、幂等、防重复、联系方式掩码、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:platform-audit-alerts` 覆盖平台审计告警生成、规则匹配、幂等、防重复和告警 details 脱敏。`npm run test:worker:platform-audit-notifications` 覆盖平台审计告警外部通知入队、generic webhook 发送、幂等、防重复、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:assets` 覆盖托管资源复检、内置安全扫描、外部 HTTP scanner 通过/失败/不可用 fail-closed、扫描失败/跳过事件和异常资源自动下架。`npm run test:worker:public-banks` 覆盖公共题库自动同步、失败通知和恢复自动关闭。`npm run test:worker:exports` 覆盖导出 worker 生成可信资源并标记 `securityScanStatus=passed`。`npm run audit:runtime` 当前为 0 vulnerabilities;Excel 解析已从 `exceljs` 切换为 `read-excel-file`,避免生产运行时携带 `exceljs -> uuid` 的已知中危依赖。
|
||||||
|
|
||||||
注意:`apps/taro` 是静态构建工程,线上发布 `apps/taro/dist/**`,不发布 `node_modules`。Taro 4.2.0 当前构建工具链仍会触发 `npm run audit:taro:toolchain` 的上游 high/critical 提示,不能用 `npm audit fix --force` 降级到 Taro 3 破坏构建;上线验收时以 `audit:runtime`、构建产物、前端密钥检查和静态服务器配置为准,并持续跟进 Taro 官方修复。
|
注意:`apps/taro` 是静态构建工程,线上发布 `apps/taro/dist/**`,不发布 `node_modules`。Taro 4.2.0 当前构建工具链仍会触发 `npm run audit:taro:toolchain` 的上游 high/critical 提示,不能用 `npm audit fix --force` 降级到 Taro 3 破坏构建;上线验收时以 `audit:runtime`、构建产物、前端密钥检查和静态服务器配置为准,并持续跟进 Taro 官方修复。
|
||||||
|
|
||||||
@@ -639,6 +641,6 @@ git diff --check
|
|||||||
2. 继续补 Taro 前端:学生端小程序公式真机验收、题图资源后台字段化、小程序支付与分享,租户后台更细导入体验/数据范围 UI/主题素材库/财务复核细节,平台后台在线收款、审计报表增强、审计告警通知升级策略、催缴通知操作台细节和小程序兼容验证。
|
2. 继续补 Taro 前端:学生端小程序公式真机验收、题图资源后台字段化、小程序支付与分享,租户后台更细导入体验/数据范围 UI/主题素材库/财务复核细节,平台后台在线收款、审计报表增强、审计告警通知升级策略、催缴通知操作台细节和小程序兼容验证。
|
||||||
3. 对象存储真实 AV/内容安全扫描服务联调、CDN 防盗链、转码/CDN 级水印和生命周期策略。
|
3. 对象存储真实 AV/内容安全扫描服务联调、CDN 防盗链、转码/CDN 级水印和生命周期策略。
|
||||||
4. 题库导出模板精排、导出操作台、导入字段映射 UI 和复检结果操作台;继续对真实迁移数据做题目、订单、权益、错题、资料和视频抽样验收。
|
4. 题库导出模板精排、导出操作台、导入字段映射 UI 和复检结果操作台;继续对真实迁移数据做题目、订单、权益、错题、资料和视频抽样验收。
|
||||||
5. 上云后接真实 OAuth/短信/支付生产账号、回调域名和真实生产账单抽样验收;本地阶段继续用 mock/fake provider 验证回调后业务链路、幂等、审计、密钥不泄露和权益开通/撤销。后续还要补真实打款 provider、发票、公共题库版本通知/冲突处理操作台、平台超额账单失败告警、积分活动风控和连续签到奖励深化。排行榜不是默认主线功能,仅在租户显式购买/开启活动并完成压测后,才进入防刷、日/周榜预聚合和运营看板开发。
|
5. 上云后接真实 OAuth/短信/支付生产账号、回调域名和真实生产账单抽样验收;本地阶段继续用 mock/fake provider 验证回调后业务链路、幂等、审计、密钥不泄露和权益开通/撤销。后续还要补真实打款 provider、发票、公共题库版本通知/冲突处理操作台、积分活动风控和连续签到奖励深化。排行榜不是默认主线功能,仅在租户显式购买/开启活动并完成压测后,才进入防刷、日/周榜预聚合和运营看板开发。
|
||||||
|
|
||||||
旧原生小程序前端位于 `F:\project\参考\旧题库小程序前端文件`,后续 Taro H5/小程序补体验时只作为页面状态、微信平台能力和交互参考,不继承旧 PocketBase 直连和旧鉴权逻辑。
|
旧原生小程序前端位于 `F:\project\参考\旧题库小程序前端文件`,后续 Taro H5/小程序补体验时只作为页面状态、微信平台能力和交互参考,不继承旧 PocketBase 直连和旧鉴权逻辑。
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ async function runOnce() {
|
|||||||
const result = await processPlatformUsageOverageBatch();
|
const result = await processPlatformUsageOverageBatch();
|
||||||
console.log(
|
console.log(
|
||||||
`[worker] platform-usage-overage batch processed=${result.processed}`
|
`[worker] platform-usage-overage batch processed=${result.processed}`
|
||||||
+ ` created=${result.created} skipped=${result.skipped} totalCents=${result.totalCents}`,
|
+ ` created=${result.created} skipped=${result.skipped}`
|
||||||
|
+ ` failed=${result.failed} totalCents=${result.totalCents}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface PlatformUsageOverageWorkerResult {
|
|||||||
created: number;
|
created: number;
|
||||||
skipped: number;
|
skipped: number;
|
||||||
totalCents: number;
|
totalCents: number;
|
||||||
|
failed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTH_RE = /^\d{4}-\d{2}$/;
|
const MONTH_RE = /^\d{4}-\d{2}$/;
|
||||||
@@ -49,6 +50,9 @@ function monthPeriod(monthText: string) {
|
|||||||
const [yearText, monthNumberText] = normalized.split('-');
|
const [yearText, monthNumberText] = normalized.split('-');
|
||||||
const year = Number(yearText);
|
const year = Number(yearText);
|
||||||
const monthNumber = Number(monthNumberText);
|
const monthNumber = Number(monthNumberText);
|
||||||
|
if (monthNumber < 1 || monthNumber > 12) {
|
||||||
|
throw new Error(`Invalid platform usage overage month: ${monthText}. Month must be between 01 and 12.`);
|
||||||
|
}
|
||||||
const periodStart = `${yearText}-${monthNumberText}-01`;
|
const periodStart = `${yearText}-${monthNumberText}-01`;
|
||||||
const periodEnd = new Date(Date.UTC(year, monthNumber, 0)).toISOString().slice(0, 10);
|
const periodEnd = new Date(Date.UTC(year, monthNumber, 0)).toISOString().slice(0, 10);
|
||||||
return { periodStart, periodEnd };
|
return { periodStart, periodEnd };
|
||||||
@@ -60,6 +64,86 @@ function dueDateText(days: number) {
|
|||||||
return now.toISOString().slice(0, 10);
|
return now.toISOString().slice(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function truncate(value: unknown, max = 900) {
|
||||||
|
return String(value ?? '').slice(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redactSensitiveValue(value: unknown, parentKey = '', depth = 0): unknown {
|
||||||
|
if (value === null || value === undefined) return value;
|
||||||
|
if (depth > 8) return '[REDACTED_DEPTH_LIMIT]';
|
||||||
|
if (
|
||||||
|
/(?:password|passwd|secret|token|credential|private[_-]?key|api[_-]?key|app[_-]?secret|authorization|cookie|session|cert|signature|database[_-]?url|connection[_-]?string)$/i
|
||||||
|
.test(parentKey)
|
||||||
|
) {
|
||||||
|
return '[REDACTED]';
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) return value.map(item => redactSensitiveValue(item, parentKey, depth + 1));
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const output: Record<string, unknown> = {};
|
||||||
|
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
||||||
|
output[key] = redactSensitiveValue(item, key, depth + 1);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value
|
||||||
|
.replace(/\bpostgres(?:ql)?:\/\/[^\s"'<>]+/gi, '[REDACTED_DATABASE_URL]')
|
||||||
|
.replace(/\b(?:bearer|basic)\s+[a-z0-9._~+/-]+=*/gi, '[REDACTED_AUTH_HEADER]')
|
||||||
|
.replace(/\b((?:access[_-]?token|refresh[_-]?token|api[_-]?key|secret|password))=([^&\s]+)/gi, '$1=[REDACTED]');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorCode(error: unknown) {
|
||||||
|
if (typeof error === 'object' && error !== null && 'code' in error) {
|
||||||
|
return truncate((error as { code?: unknown }).code, 120);
|
||||||
|
}
|
||||||
|
return 'PLATFORM_USAGE_OVERAGE_WORKER_FAILED';
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorDetails(error: unknown) {
|
||||||
|
const details: Record<string, unknown> = {
|
||||||
|
code: errorCode(error),
|
||||||
|
message: truncate(error instanceof Error ? error.message : String(error)),
|
||||||
|
name: error instanceof Error ? error.name : typeof error,
|
||||||
|
};
|
||||||
|
if (typeof error === 'object' && error !== null) {
|
||||||
|
const record = error as Record<string, unknown>;
|
||||||
|
for (const key of ['detail', 'hint', 'where', 'schema', 'table', 'constraint']) {
|
||||||
|
if (record[key] !== undefined) details[key] = truncate(record[key], 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redactSensitiveValue(details) as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function recordUsageOverageWorkerFailure(input: {
|
||||||
|
month: string;
|
||||||
|
periodStart?: string | null;
|
||||||
|
periodEnd?: string | null;
|
||||||
|
dueDate?: string | null;
|
||||||
|
limit?: number | null;
|
||||||
|
error: unknown;
|
||||||
|
}) {
|
||||||
|
await pool.query(
|
||||||
|
`
|
||||||
|
insert into public.audit_logs (tenant_id, actor_user_id, action, target_type, target_id, details)
|
||||||
|
values (null, null, 'platform.invoice.usage_overage_worker_failed', 'tenant_invoice_batch', $1, $2::jsonb)
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
input.periodStart && input.periodEnd ? `${input.periodStart}_${input.periodEnd}` : input.month,
|
||||||
|
JSON.stringify({
|
||||||
|
month: input.month,
|
||||||
|
periodStart: input.periodStart || null,
|
||||||
|
periodEnd: input.periodEnd || null,
|
||||||
|
dueDate: input.dueDate || null,
|
||||||
|
limit: input.limit || null,
|
||||||
|
workerId: config.platformUsageOverageWorkerId,
|
||||||
|
error: errorDetails(input.error),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function processPlatformUsageOverageBatch(options: {
|
export async function processPlatformUsageOverageBatch(options: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
month?: string;
|
month?: string;
|
||||||
@@ -67,14 +151,20 @@ export async function processPlatformUsageOverageBatch(options: {
|
|||||||
} = {}): Promise<PlatformUsageOverageWorkerResult> {
|
} = {}): Promise<PlatformUsageOverageWorkerResult> {
|
||||||
const limit = positiveInteger(options.limit ?? config.platformUsageOverageBatchSize, 100, 500);
|
const limit = positiveInteger(options.limit ?? config.platformUsageOverageBatchSize, 100, 500);
|
||||||
const month = targetMonth(options.month || config.platformUsageOverageMonth);
|
const month = targetMonth(options.month || config.platformUsageOverageMonth);
|
||||||
const { periodStart, periodEnd } = monthPeriod(month);
|
let periodStart: string | null = null;
|
||||||
|
let periodEnd: string | null = null;
|
||||||
|
let dueDate: string | null = null;
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const period = monthPeriod(month);
|
||||||
|
periodStart = period.periodStart;
|
||||||
|
periodEnd = period.periodEnd;
|
||||||
|
dueDate = dueDateText(options.dueDays ?? config.platformUsageOverageDueDays);
|
||||||
await client.query('begin');
|
await client.query('begin');
|
||||||
const result = await processUsageOverageInvoices(client, {
|
const result = await processUsageOverageInvoices(client, {
|
||||||
periodStart,
|
periodStart,
|
||||||
periodEnd,
|
periodEnd,
|
||||||
dueDate: dueDateText(options.dueDays ?? config.platformUsageOverageDueDays),
|
dueDate,
|
||||||
status: 'issued',
|
status: 'issued',
|
||||||
note: `平台自动生成 ${periodStart} 至 ${periodEnd} 用量超额服务费账单`,
|
note: `平台自动生成 ${periodStart} 至 ${periodEnd} 用量超额服务费账单`,
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
@@ -87,9 +177,18 @@ export async function processPlatformUsageOverageBatch(options: {
|
|||||||
created: Number(result.createdCount || 0),
|
created: Number(result.createdCount || 0),
|
||||||
skipped: Number(result.skippedCount || 0),
|
skipped: Number(result.skippedCount || 0),
|
||||||
totalCents: Number(result.totalCents || 0),
|
totalCents: Number(result.totalCents || 0),
|
||||||
|
failed: 0,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await client.query('rollback').catch(() => {});
|
await client.query('rollback').catch(() => {});
|
||||||
|
await recordUsageOverageWorkerFailure({
|
||||||
|
month,
|
||||||
|
periodStart,
|
||||||
|
periodEnd,
|
||||||
|
dueDate,
|
||||||
|
limit,
|
||||||
|
error,
|
||||||
|
}).catch(() => {});
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
| 手机号绑定/换绑 | 可联调 | `/api/auth/phone/bind` 使用 `bind_phone` 短信验证码,后端校验当前登录态、手机号唯一性、移除旧手机号 identity,并撤销其它迁移期 session |
|
| 手机号绑定/换绑 | 可联调 | `/api/auth/phone/bind` 使用 `bind_phone` 短信验证码,后端校验当前登录态、手机号唯一性、移除旧手机号 identity,并撤销其它迁移期 session |
|
||||||
| 微信网页/QQ OAuth | 可联调 | `/api/auth/oauth/wechat` 已完成微信网页登录 code 换 token、userinfo、unionid 合并、session 签发和审计;`/api/auth/oauth/qq` 已完成 code/token/openid/userinfo 主链路;生产前需真实开放平台账号和回调域名联调 |
|
| 微信网页/QQ OAuth | 可联调 | `/api/auth/oauth/wechat` 已完成微信网页登录 code 换 token、userinfo、unionid 合并、session 签发和审计;`/api/auth/oauth/qq` 已完成 code/token/openid/userinfo 主链路;生产前需真实开放平台账号和回调域名联调 |
|
||||||
| 平台管理员鉴权 | 可联调 | 已支持平台管理员 Supabase JWT;平台账号以后端 `platform_users.primary_role='platform_admin'`、`status='active'` 和 `platform_permissions` 为准;`GET /api/platform-admin/permissions` 返回权限目录和当前账号 `effective` 能力,平台路由按 `platform:staff:*`、`platform:tenant:*`、`platform:billing:*`、`platform:audit:*`、`platform:question_bank:*` 等权限点强制校验;`GET/PUT/PATCH /api/platform-admin/staff` 可管理平台员工,禁用员工会拒绝后续 JWT 映射并撤销迁移期 session;`x-platform-admin-key` 仅作本地/迁移期兼容且可通过配置禁用 |
|
| 平台管理员鉴权 | 可联调 | 已支持平台管理员 Supabase JWT;平台账号以后端 `platform_users.primary_role='platform_admin'`、`status='active'` 和 `platform_permissions` 为准;`GET /api/platform-admin/permissions` 返回权限目录和当前账号 `effective` 能力,平台路由按 `platform:staff:*`、`platform:tenant:*`、`platform:billing:*`、`platform:audit:*`、`platform:question_bank:*` 等权限点强制校验;`GET/PUT/PATCH /api/platform-admin/staff` 可管理平台员工,禁用员工会拒绝后续 JWT 映射并撤销迁移期 session;`x-platform-admin-key` 仅作本地/迁移期兼容且可通过配置禁用 |
|
||||||
| 平台 SaaS 用量采集和超额账单 | 可联调 | `GET/POST /api/platform-admin/usage` 保留平台用量台账和手工调整能力;`apps/worker --job platform-usage` 会按月自动从权威业务表采集学生数、活跃学生、题量、资源数、存储 GB、视频数、视频播放、视频次数消耗、已支付订单、GMV 和有效权益,写入 `tenant_usage_records.metadata.source=platform_usage_worker` 和审计日志;手工调整记录不被覆盖,平台概览优先取每租户每指标最新 worker 快照;`GET /api/platform-admin/invoices/usage-overage-candidates` 和 `POST /api/platform-admin/invoices/from-usage-overage` 已支持按账期读取最新用量、套用 SaaS 套餐 `included_quotas/overage_prices` 或订阅 metadata 覆盖、dry-run/正式生成 `usage_overage` 账单、重复开票保护和平台审计 |
|
| 平台 SaaS 用量采集和超额账单 | 可联调 | `GET/POST /api/platform-admin/usage` 保留平台用量台账和手工调整能力;`apps/worker --job platform-usage` 会按月自动从权威业务表采集学生数、活跃学生、题量、资源数、存储 GB、视频数、视频播放、视频次数消耗、已支付订单、GMV 和有效权益,写入 `tenant_usage_records.metadata.source=platform_usage_worker` 和审计日志;手工调整记录不被覆盖,平台概览优先取每租户每指标最新 worker 快照;`GET /api/platform-admin/invoices/usage-overage-candidates` 和 `POST /api/platform-admin/invoices/from-usage-overage` 已支持按账期读取最新用量、套用 SaaS 套餐 `included_quotas/overage_prices` 或订阅 metadata 覆盖、dry-run/正式生成 `usage_overage` 账单、重复开票保护和平台审计;`apps/worker --job platform-usage-overage` 会校验 `YYYY-MM` 账期、自动生成上月超额账单,失败时写入脱敏 `platform.invoice.usage_overage_worker_failed` 审计并由平台审计告警 worker 生成高优先级告警 |
|
||||||
| 租户角色权限 | 可联调 | `tenant_memberships.role + permissions + role_template_id`,接口有权限点校验 |
|
| 租户角色权限 | 可联调 | `tenant_memberships.role + permissions + role_template_id`,接口有权限点校验 |
|
||||||
| 自定义角色模板 | 可联调 | `tenant_role_templates` + `/api/tenant-admin/role-templates`,支持权限、菜单、模块、字段、数据范围配置;Taro 租户设置页已接创建、编辑、停用、权限点、菜单、模块、字段和基础数据范围配置第一版 |
|
| 自定义角色模板 | 可联调 | `tenant_role_templates` + `/api/tenant-admin/role-templates`,支持权限、菜单、模块、字段、数据范围配置;Taro 租户设置页已接创建、编辑、停用、权限点、菜单、模块、字段和基础数据范围配置第一版 |
|
||||||
| 班级/教师/学生范围权限 | 可联调 | `tenant_classes`、`tenant_class_members` + `/api/tenant-admin/classes`、`classes/members`、`students`、`teachers`;教师默认只看自己负责班级,字段权限可脱敏学生手机号 |
|
| 班级/教师/学生范围权限 | 可联调 | `tenant_classes`、`tenant_class_members` + `/api/tenant-admin/classes`、`classes/members`、`students`、`teachers`;教师默认只看自己负责班级,字段权限可脱敏学生手机号 |
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
| 班级/学生/教师管理 | 可联调 | `/api/tenant-admin/classes`、`classes/members`、`students`、`teachers`,支持班级范围权限和审计 |
|
| 班级/学生/教师管理 | 可联调 | `/api/tenant-admin/classes`、`classes/members`、`students`、`teachers`,支持班级范围权限和审计 |
|
||||||
| 学生批量运营 | 可联调 | `/api/tenant-admin/students/bulk-upsert`、`students/status`、`classes/members/bulk-assign`、`students/notes`、`students/followups`、`students/followups/report`、`students/supervision/preview`、`students/supervision/generate`、`students/supervision/rules`、`students/crm-push`;支持逐行结果、限量、防跨租户和教师范围校验;跟进报表支持 7/30/90 天或自定义日期范围、状态/类型/优先级/负责人/班级聚合、逾期待办、CRM 推送队列摘要和每日趋势;学习督导由后端读取答题、错题、单词待复习和未完成练习数据,按阈值预览候选并幂等生成 `learning` 跟进任务,也可保存手动/每日/每周规则交给 worker 定时生成,要求 `students:supervision:read/write`;批量 CRM 推送会为学生生成跟进任务并写入异步 CRM 队列,要求 `crm:write`、`students:read`、`students:followups:write`;学生导入/upsert/分班禁止 `avatarUrl/avatar_url/avatar/headimgurl/figureurl` 和 `primaryRole/primary_role`,避免绕过预设头像与租户角色体系 |
|
| 学生批量运营 | 可联调 | `/api/tenant-admin/students/bulk-upsert`、`students/status`、`classes/members/bulk-assign`、`students/notes`、`students/followups`、`students/followups/report`、`students/supervision/preview`、`students/supervision/generate`、`students/supervision/rules`、`students/crm-push`;支持逐行结果、限量、防跨租户和教师范围校验;跟进报表支持 7/30/90 天或自定义日期范围、状态/类型/优先级/负责人/班级聚合、逾期待办、CRM 推送队列摘要和每日趋势;学习督导由后端读取答题、错题、单词待复习和未完成练习数据,按阈值预览候选并幂等生成 `learning` 跟进任务,也可保存手动/每日/每周规则交给 worker 定时生成,要求 `students:supervision:read/write`;批量 CRM 推送会为学生生成跟进任务并写入异步 CRM 队列,要求 `crm:write`、`students:read`、`students:followups:write`;学生导入/upsert/分班禁止 `avatarUrl/avatar_url/avatar/headimgurl/figureurl` 和 `primaryRole/primary_role`,避免绕过预设头像与租户角色体系 |
|
||||||
| 用户站内通知查看 | 可联调 | `GET /api/tenant-admin/user-notifications`;需要 `notifications:read` 权限,支持按用户、状态、类型查询租户内通知和状态汇总,租户后台只读不直接代学生改状态 |
|
| 用户站内通知查看 | 可联调 | `GET /api/tenant-admin/user-notifications`;需要 `notifications:read` 权限,支持按用户、状态、类型查询租户内通知和状态汇总,租户后台只读不直接代学生改状态 |
|
||||||
| 平台租户/详情/账务资料/员工/审计/告警/套餐/订阅/账单/用量 | 可联调 | `/api/platform-admin/*`;已支持当前平台账号权限目录、平台员工列表、平台员工创建/编辑、平台员工禁用/恢复、租户列表、创建租户、租户详情、状态变更、账务资料维护、平台审计日志查询、CSV/JSON 审计导出、平台审计告警规则查询、告警列表、确认/解决/忽略、审计告警外部通知渠道和发送事件、SaaS 套餐、订阅、账单、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、用量超额账单候选预览/dry-run/生成/自动开票 worker、收款、逾期标记、内部催缴台账、催缴外部通知渠道和发送事件、用量台账和平台用量自动采集 worker;平台 API 已拆分 `platform:staff:read/write/status`、`platform:tenant:read/write/status/billing_profile`、`platform:billing:read/write/payment/dunning/notification`、`platform:usage:read/write`、`platform:audit:read/export/alert/notification`、`platform:question_bank:read/grant/ops` 等权限点;审计导出、平台员工操作、告警响应和通知事件都会对 `details`/payload 中的 token/secret/password/key 等敏感字段递归脱敏;`apps/worker --job platform-usage` 会生成月度 SaaS 用量快照并保留手工调整记录;平台超额账单只使用后端权威用量快照和套餐/订阅 metadata,前端不得自行计算服务费;`apps/worker --job platform-audit-alerts` 会把租户状态变更、账务资料变更、批量开票、逾期处理、手工收款确认、审计导出等高风险平台审计动作生成内部告警;`apps/worker --job platform-audit-notifications` 会按 `platform_audit_notification_channels` 把开放告警推送到 generic/钉钉/飞书/企微 webhook,签名密钥放 `app_private.platform_secrets` 且 API 不回显原文;`apps/worker --job platform-dunning-notifications` 会按 `platform_dunning_notification_channels` 把内部催缴记录推送到 generic/钉钉/飞书/企微 webhook,发送成功会推进提醒状态,失败会退避重试,联系方式和请求 payload 会脱敏;创建租户、平台员工变更、状态变更、账务资料维护、订阅批量开票、自动开票、用量采集、用量超额开票、逾期催缴、手工收款确认、审计导出、告警状态更新、通知渠道变更和催缴通知渠道变更会写入审计 |
|
| 平台租户/详情/账务资料/员工/审计/告警/套餐/订阅/账单/用量 | 可联调 | `/api/platform-admin/*`;已支持当前平台账号权限目录、平台员工列表、平台员工创建/编辑、平台员工禁用/恢复、租户列表、创建租户、租户详情、状态变更、账务资料维护、平台审计日志查询、CSV/JSON 审计导出、平台审计告警规则查询、告警列表、确认/解决/忽略、审计告警外部通知渠道和发送事件、SaaS 套餐、订阅、账单、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、用量超额账单候选预览/dry-run/生成/自动开票 worker、收款、逾期标记、内部催缴台账、催缴外部通知渠道和发送事件、用量台账和平台用量自动采集 worker;平台 API 已拆分 `platform:staff:read/write/status`、`platform:tenant:read/write/status/billing_profile`、`platform:billing:read/write/payment/dunning/notification`、`platform:usage:read/write`、`platform:audit:read/export/alert/notification`、`platform:question_bank:read/grant/ops` 等权限点;审计导出、平台员工操作、告警响应和通知事件都会对 `details`/payload 中的 token/secret/password/key 等敏感字段递归脱敏;`apps/worker --job platform-usage` 会生成月度 SaaS 用量快照并保留手工调整记录;平台超额账单只使用后端权威用量快照和套餐/订阅 metadata,前端不得自行计算服务费;`apps/worker --job platform-usage-overage` 自动开票失败会写脱敏审计并进入 `platform-audit-alerts` 高优先级告警;`apps/worker --job platform-audit-alerts` 会把租户状态变更、账务资料变更、批量开票、超额开票、worker 失败、逾期处理、手工收款确认、审计导出等高风险平台审计动作生成内部告警;`apps/worker --job platform-audit-notifications` 会按 `platform_audit_notification_channels` 把开放告警推送到 generic/钉钉/飞书/企微 webhook,签名密钥放 `app_private.platform_secrets` 且 API 不回显原文;`apps/worker --job platform-dunning-notifications` 会按 `platform_dunning_notification_channels` 把内部催缴记录推送到 generic/钉钉/飞书/企微 webhook,发送成功会推进提醒状态,失败会退避重试,联系方式和请求 payload 会脱敏;创建租户、平台员工变更、状态变更、账务资料维护、订阅批量开票、自动开票、用量采集、用量超额开票、超额开票失败、逾期催缴、手工收款确认、审计导出、告警状态更新、通知渠道变更和催缴通知渠道变更会写入审计 |
|
||||||
| 数据看板聚合接口 | 可联调 | `GET /api/tenant-admin/dashboard`;支持 `7d/30d/90d`、地区筛选、学生/学习/内容/订单/激活码/反馈卡片、趋势、24h 活跃、题型分布、科目排行、地区统计、套餐销量和运营动态 |
|
| 数据看板聚合接口 | 可联调 | `GET /api/tenant-admin/dashboard`;支持 `7d/30d/90d`、地区筛选、学生/学习/内容/订单/激活码/反馈卡片、趋势、24h 活跃、题型分布、科目排行、地区统计、套餐销量和运营动态 |
|
||||||
| 平台公共题库授权 | 可联调 | `/api/platform-admin/question-banks`、`question-bank-grants`;支持按 SaaS 套餐、指定租户或全部活跃租户披露平台公共题库,并可限制授权地区和科目。平台保存 grant 时会校验 `allowedRegionIds`、`allowedSubjectIds` 属于源平台题库租户,且已发布题目的科目必须被授权科目覆盖 |
|
| 平台公共题库授权 | 可联调 | `/api/platform-admin/question-banks`、`question-bank-grants`;支持按 SaaS 套餐、指定租户或全部活跃租户披露平台公共题库,并可限制授权地区和科目。平台保存 grant 时会校验 `allowedRegionIds`、`allowedSubjectIds` 属于源平台题库租户,且已发布题目的科目必须被授权科目覆盖 |
|
||||||
| 租户采纳/同步公共题库 | 可联调 | `/api/tenant-content/public-question-banks`、`public-question-banks/adopt`、`public-question-banks/sync`、`public-question-banks/conflicts`、`public-question-banks/conflicts/resolve`、`public-question-banks/conflicts/resolve-batch`、`tenant-content/notifications`、`/api/platform-admin/question-bank-sync-status`;租户只能看到自己 `question_bank_grants`、有效 `tenant_subscriptions`、`platform_saas_plans.feature_flags.publicQuestionBanks` 和订阅 `metadata.publicQuestionBankAccess` 同时允许的题库。基础版默认 `limited_regions` 且需要地区 allowlist,专业版默认 `national`;采纳、同步和冲突处理都会重新校验当前授权,越权 grant 返回 `QUESTION_BANK_GRANT_NOT_AVAILABLE`。采纳后生成租户自己的题库、入口、集合和题目快照,可直接进入练习;平台更新后可手动或由 worker 自动同步,新增/更新、冲突和 worker 失败会生成租户内容通知;租户自改题目会标记冲突并跳过;后台可查询最近一次冲突明细,并可单条或批量选择“采纳平台版本”/“保留本地版本”,操作会写入逐条审计,冲突全部处理后相关通知自动 resolved,失败通知会在后续同步恢复成功后自动 resolved;平台运营接口需 `platform:question_bank:ops`,只返回跨租户同步摘要和通知数量,不返回题目正文/答案/解析 |
|
| 租户采纳/同步公共题库 | 可联调 | `/api/tenant-content/public-question-banks`、`public-question-banks/adopt`、`public-question-banks/sync`、`public-question-banks/conflicts`、`public-question-banks/conflicts/resolve`、`public-question-banks/conflicts/resolve-batch`、`tenant-content/notifications`、`/api/platform-admin/question-bank-sync-status`;租户只能看到自己 `question_bank_grants`、有效 `tenant_subscriptions`、`platform_saas_plans.feature_flags.publicQuestionBanks` 和订阅 `metadata.publicQuestionBankAccess` 同时允许的题库。基础版默认 `limited_regions` 且需要地区 allowlist,专业版默认 `national`;采纳、同步和冲突处理都会重新校验当前授权,越权 grant 返回 `QUESTION_BANK_GRANT_NOT_AVAILABLE`。采纳后生成租户自己的题库、入口、集合和题目快照,可直接进入练习;平台更新后可手动或由 worker 自动同步,新增/更新、冲突和 worker 失败会生成租户内容通知;租户自改题目会标记冲突并跳过;后台可查询最近一次冲突明细,并可单条或批量选择“采纳平台版本”/“保留本地版本”,操作会写入逐条审计,冲突全部处理后相关通知自动 resolved,失败通知会在后续同步恢复成功后自动 resolved;平台运营接口需 `platform:question_bank:ops`,只返回跨租户同步摘要和通知数量,不返回题目正文/答案/解析 |
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
| 模块 | 当前状态 | 已经具备 | 上线前还要补 |
|
| 模块 | 当前状态 | 已经具备 | 上线前还要补 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 多租户底座 | 可联调 | 租户、域名、品牌、设置、RLS 基础、审计、Supabase JWT/API 身份映射;`npm run test:rls` 已提供本地动态租户隔离验收;`npm run smoke:auth:remote` 已提供真实云端 Supabase access token 回归脚本 | 真实云端 Auth/JWKS 回归需要在预生产/生产环境执行并留档,生产 RLS 深测继续执行 |
|
| 多租户底座 | 可联调 | 租户、域名、品牌、设置、RLS 基础、审计、Supabase JWT/API 身份映射;`npm run test:rls` 已提供本地动态租户隔离验收;`npm run smoke:auth:remote` 已提供真实云端 Supabase access token 回归脚本 | 真实云端 Auth/JWKS 回归需要在预生产/生产环境执行并留档,生产 RLS 深测继续执行 |
|
||||||
| 平台后台 | 基础完成 | 租户、租户详情、账务资料维护、平台账号细粒度权限目录、平台员工列表/创建/编辑/启停、平台路由权限强校验、平台审计日志查询、平台审计 CSV/JSON 导出、平台审计告警规则/列表/确认/解决、platform-audit-alerts worker、审计告警外部通知渠道/事件 API、platform-audit-notifications worker、套餐、订阅、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、服务费、人工收款、逾期标记、内部催缴台账、催缴外部通知渠道/事件 API、platform-dunning-notifications worker、用量台账、platform-usage 月度自动采集 worker、套餐额度判定、用量超额账单候选预览/dry-run/生成/自动开票 worker、公共题库授权、公共题库地区/科目授权校验、SaaS 套餐/订阅 metadata 公共题库访问边界、公共题库自动同步 worker、公共题库冲突单条/批量处理 API、公共题库同步通知第一版 | 平台在线收款、平台审计告警升级策略和更完整运营消息 |
|
| 平台后台 | 基础完成 | 租户、租户详情、账务资料维护、平台账号细粒度权限目录、平台员工列表/创建/编辑/启停、平台路由权限强校验、平台审计日志查询、平台审计 CSV/JSON 导出、平台审计告警规则/列表/确认/解决、platform-audit-alerts worker、审计告警外部通知渠道/事件 API、platform-audit-notifications worker、套餐、订阅、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、服务费、人工收款、逾期标记、内部催缴台账、催缴外部通知渠道/事件 API、platform-dunning-notifications worker、用量台账、platform-usage 月度自动采集 worker、套餐额度判定、用量超额账单候选预览/dry-run/生成/自动开票 worker、超额开票失败审计告警、公共题库授权、公共题库地区/科目授权校验、SaaS 套餐/订阅 metadata 公共题库访问边界、公共题库自动同步 worker、公共题库冲突单条/批量处理 API、公共题库同步通知第一版 | 平台在线收款、平台审计告警升级策略和更完整运营消息 |
|
||||||
| 租户后台 | 可联调 | 品牌、域名、支付账户、登录配置、密钥掩码、活动、兑换码、优惠券、勋章管理/手动发放/签到/积分/反馈/活动自动发放、积分任务、积分兑换、积分风控只读报表、用户站内通知查看、成员权限、角色模板、菜单/模块/字段权限配置 API、班级/教师/学生范围权限、学生运营跟进、学习督导规则模板和 `student-supervision` worker;Taro 工作台已接权限驱动模块入口,学生运营页已接学生创建/更新、禁用/恢复、批量导入、批量分班、备注、跟进任务、学习督导预览/生成和保存每日规则第一版,租户设置页已接角色模板和成员绑定操作台第一版,营销中心已接 CRM 配置/队列、分佣结算、优惠券规则/核销报表、积分任务/兑换操作台、积分风控摘要和用户通知查看第一版 | 更细的数据范围组合、成员批量运营、学习督导触达联动/效果归因、真实打款/导出/凭证和完整权限菜单 |
|
| 租户后台 | 可联调 | 品牌、域名、支付账户、登录配置、密钥掩码、活动、兑换码、优惠券、勋章管理/手动发放/签到/积分/反馈/活动自动发放、积分任务、积分兑换、积分风控只读报表、用户站内通知查看、成员权限、角色模板、菜单/模块/字段权限配置 API、班级/教师/学生范围权限、学生运营跟进、学习督导规则模板和 `student-supervision` worker;Taro 工作台已接权限驱动模块入口,学生运营页已接学生创建/更新、禁用/恢复、批量导入、批量分班、备注、跟进任务、学习督导预览/生成和保存每日规则第一版,租户设置页已接角色模板和成员绑定操作台第一版,营销中心已接 CRM 配置/队列、分佣结算、优惠券规则/核销报表、积分任务/兑换操作台、积分风控摘要和用户通知查看第一版 | 更细的数据范围组合、成员批量运营、学习督导触达联动/效果归因、真实打款/导出/凭证和完整权限菜单 |
|
||||||
| 题库与练习 | 可联调 | 内容入口、任意深度分类、题目集合、顺序/随机/全真模拟蓝图、组卷快照、客观题后端判分、主观题 `selfJudgedCorrect` 自评、阅读理解/案例分析 `subAnswers` 多小题判分、答题、错题、收藏、模考报告、排行榜接口(租户默认关闭)、公共题库采纳快照、手动同步、自动同步 worker、冲突查询/单条和批量处理 API、公共题库同步通知、starter 单地区/专业版全国公共题库访问边界、JSON/试卷 payload 导出、PDF/Word 异步导出 worker、水印和资料发布路径、每日一练九宫格 metadata、PDF/Word 运营版式和 ZIP 图片素材包 | 长题干/公式图片混排体验、导出模板精排、导出操作台;排行榜仅在租户显式开启活动后再补压测、防刷和预聚合 |
|
| 题库与练习 | 可联调 | 内容入口、任意深度分类、题目集合、顺序/随机/全真模拟蓝图、组卷快照、客观题后端判分、主观题 `selfJudgedCorrect` 自评、阅读理解/案例分析 `subAnswers` 多小题判分、答题、错题、收藏、模考报告、排行榜接口(租户默认关闭)、公共题库采纳快照、手动同步、自动同步 worker、冲突查询/单条和批量处理 API、公共题库同步通知、starter 单地区/专业版全国公共题库访问边界、JSON/试卷 payload 导出、PDF/Word 异步导出 worker、水印和资料发布路径、每日一练九宫格 metadata、PDF/Word 运营版式和 ZIP 图片素材包 | 长题干/公式图片混排体验、导出模板精排、导出操作台;排行榜仅在租户显式开启活动后再补压测、防刷和预聚合 |
|
||||||
| 背单词 | 可联调 | 单元、单词、进度、收藏、统计、每日计划、JSON/CSV/Excel 导入、排行榜接口(租户默认关闭) | 更细复习参数 |
|
| 背单词 | 可联调 | 单元、单词、进度、收藏、统计、每日计划、JSON/CSV/Excel 导入、排行榜接口(租户默认关闭) | 更细复习参数 |
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
- 资金对账已支持手工/API 账单导入比对、微信/支付宝官方账单下载任务、异常查询和差错工单处理;继续补真实生产账单格式验收、财务复核报表和异常订单运营台。
|
- 资金对账已支持手工/API 账单导入比对、微信/支付宝官方账单下载任务、异常查询和差错工单处理;继续补真实生产账单格式验收、财务复核报表和异常订单运营台。
|
||||||
- XPay 或其它实际支付网关 adapter。
|
- XPay 或其它实际支付网关 adapter。
|
||||||
- 阿里云/腾讯云短信、微信小程序登录、微信网页登录、QQ 登录真实账号联调。
|
- 阿里云/腾讯云短信、微信小程序登录、微信网页登录、QQ 登录真实账号联调。
|
||||||
- 公共题库/地区题库自动同步 worker 已具备单批执行能力,租户后台已有同步通知、单条/批量冲突采纳平台或保留本地操作;公共题库可见、采纳、同步、冲突处理会统一校验 SaaS 套餐、有效订阅、订阅 metadata、grant 地区和科目范围。平台用量采集已覆盖存储、学生数、题量和视频播放等基础指标,超额服务费由后端按套餐额度生成 `usage_overage` 账单,`platform-usage-overage` worker 可按上月账期自动开票;继续补失败告警和更完整运营消息。
|
- 公共题库/地区题库自动同步 worker 已具备单批执行能力,租户后台已有同步通知、单条/批量冲突采纳平台或保留本地操作;公共题库可见、采纳、同步、冲突处理会统一校验 SaaS 套餐、有效订阅、订阅 metadata、grant 地区和科目范围。平台用量采集已覆盖存储、学生数、题量和视频播放等基础指标,超额服务费由后端按套餐额度生成 `usage_overage` 账单,`platform-usage-overage` worker 可按上月账期自动开票;失败会写入脱敏平台审计日志并进入平台审计告警/外部通知链路。继续补套餐配置 UI、平台在线收款和更完整运营消息。
|
||||||
- 导入模板、字段映射、导入任务详情和复检 API 已可用;Taro 租户内容页已接模板下载、字段别名覆盖、导入执行、异步 job 轮询和复检结果面板第一版。前端继续补真实导入目标选择体验和大数据量导入验收。
|
- 导入模板、字段映射、导入任务详情和复检 API 已可用;Taro 租户内容页已接模板下载、字段别名覆盖、导入执行、异步 job 轮询和复检结果面板第一版。前端继续补真实导入目标选择体验和大数据量导入验收。
|
||||||
- 视频深度防盗链、转码级水印和播放统计。
|
- 视频深度防盗链、转码级水印和播放统计。
|
||||||
- 数据看板 API:收益、注册趋势、答题次数、收入趋势、题型分布、题目总量、套餐销量、24h 活跃。
|
- 数据看板 API:收益、注册趋势、答题次数、收入趋势、题型分布、题目总量、套餐销量、24h 活跃。
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
- `tenant-content`:租户后台内容入口、任意深度分类树、考试意向标记、题目集合、练习蓝图、题目、视频、分数线、单词、知识手册、资料资源、题目/单词/知识手册/分数线/视频 JSON 导入维护。
|
- `tenant-content`:租户后台内容入口、任意深度分类树、考试意向标记、题目集合、练习蓝图、题目、视频、分数线、单词、知识手册、资料资源、题目/单词/知识手册/分数线/视频 JSON 导入维护。
|
||||||
- `tenant`:域名/租户解析。
|
- `tenant`:域名/租户解析。
|
||||||
- 鉴权上下文已支持 Supabase Auth JWT 和迁移期 `tk_` session 双入口,JWT 通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射业务用户和租户;平台管理员 JWT 已可访问平台后台。
|
- 鉴权上下文已支持 Supabase Auth JWT 和迁移期 `tk_` session 双入口,JWT 通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射业务用户和租户;平台管理员 JWT 已可访问平台后台。
|
||||||
- 平台后台租户运营第一版已补齐:`GET /api/platform-admin/tenants/detail` 返回租户、域名、订阅、账单、用量和账务资料;`PUT /api/platform-admin/tenants/billing-profile` 维护开票/联系/银行掩码资料;`GET /api/platform-admin/audit-logs` 支持按租户、动作、目标、操作者、日期和关键词查询平台审计;`GET /api/platform-admin/audit-logs/export` 支持平台管理员导出 CSV/JSON,返回 base64 内容、sha256、行数和筛选条件,并对 `details` 中的 token/secret/password/key 等敏感字段递归脱敏,同时写入 `platform.audit.exported` 审计;`GET /api/platform-admin/audit-alert-rules`、`GET /api/platform-admin/audit-alerts` 和 `POST /api/platform-admin/audit-alerts/status` 支持平台内部审计告警规则查询、开放告警查询、确认/解决/忽略,API 返回告警 details 时递归脱敏敏感字段;`apps/worker --job platform-audit-alerts` 会把租户状态变更、账务资料变更、批量开票、逾期处理、手工收款确认、审计导出等高风险平台审计动作生成内部告警;`GET/PUT /api/platform-admin/audit-notification-channels` 和 `GET /api/platform-admin/audit-notification-events` 已支持平台审计告警外部通知渠道配置和发送事件查询,`apps/worker --job platform-audit-notifications` 可按渠道把开放告警推送到 generic/钉钉/飞书/企微 webhook,签名密钥进入 `app_private.platform_secrets`,API 只回显 `secretRef` 和 webhook host/path;`GET /api/platform-admin/invoices/subscription-candidates` 和 `POST /api/platform-admin/invoices/from-subscriptions-batch` 支持订阅账单候选预览、dry-run、批量生成、重复开票跳过和平台审计;`GET /api/platform-admin/invoices/usage-overage-candidates` 和 `POST /api/platform-admin/invoices/from-usage-overage` 支持用量超额候选预览、dry-run、正式生成 `usage_overage` 账单、重复开票保护和平台审计;`apps/worker --job platform-usage-overage` 可按上一个自然月或指定 `WORKER_PLATFORM_USAGE_OVERAGE_MONTH=YYYY-MM` 自动生成超额服务费账单,复用同一套后端计算服务;`apps/worker --job platform-billing` 可自动为即将到期且未开票订阅生成服务费账单;`POST /api/platform-admin/invoices/process-overdue`、`GET /api/platform-admin/invoices/reminders` 和 `apps/worker --job platform-dunning` 可处理已逾期未结清服务费账单,写入内部催缴台账和审计;`GET/PUT /api/platform-admin/dunning-notification-channels`、`GET /api/platform-admin/dunning-notification-events` 和 `apps/worker --job platform-dunning-notifications` 已支持平台催缴外部通知渠道配置、发送事件查询、重试和幂等发送。创建租户、状态变更、账务资料维护、订阅批量开票、自动计费、用量超额开票、逾期催缴、手工收款确认、审计导出、审计告警状态更新、审计告警通知渠道变更和催缴通知渠道变更会写入审计日志,API/worker 集成测试已覆盖平台管理员可操作、学生越权拒绝、重复保护、非法输入拒绝、敏感字段脱敏和审计记录存在。
|
- 平台后台租户运营第一版已补齐:`GET /api/platform-admin/tenants/detail` 返回租户、域名、订阅、账单、用量和账务资料;`PUT /api/platform-admin/tenants/billing-profile` 维护开票/联系/银行掩码资料;`GET /api/platform-admin/audit-logs` 支持按租户、动作、目标、操作者、日期和关键词查询平台审计;`GET /api/platform-admin/audit-logs/export` 支持平台管理员导出 CSV/JSON,返回 base64 内容、sha256、行数和筛选条件,并对 `details` 中的 token/secret/password/key 等敏感字段递归脱敏,同时写入 `platform.audit.exported` 审计;`GET /api/platform-admin/audit-alert-rules`、`GET /api/platform-admin/audit-alerts` 和 `POST /api/platform-admin/audit-alerts/status` 支持平台内部审计告警规则查询、开放告警查询、确认/解决/忽略,API 返回告警 details 时递归脱敏敏感字段;`apps/worker --job platform-audit-alerts` 会把租户状态变更、账务资料变更、批量开票、用量超额开票、worker 失败、逾期处理、手工收款确认、审计导出等高风险平台审计动作生成内部告警;`GET/PUT /api/platform-admin/audit-notification-channels` 和 `GET /api/platform-admin/audit-notification-events` 已支持平台审计告警外部通知渠道配置和发送事件查询,`apps/worker --job platform-audit-notifications` 可按渠道把开放告警推送到 generic/钉钉/飞书/企微 webhook,签名密钥进入 `app_private.platform_secrets`,API 只回显 `secretRef` 和 webhook host/path;`GET /api/platform-admin/invoices/subscription-candidates` 和 `POST /api/platform-admin/invoices/from-subscriptions-batch` 支持订阅账单候选预览、dry-run、批量生成、重复开票跳过和平台审计;`GET /api/platform-admin/invoices/usage-overage-candidates` 和 `POST /api/platform-admin/invoices/from-usage-overage` 支持用量超额候选预览、dry-run、正式生成 `usage_overage` 账单、重复开票保护和平台审计;`apps/worker --job platform-usage-overage` 可按上一个自然月或指定 `WORKER_PLATFORM_USAGE_OVERAGE_MONTH=YYYY-MM` 自动生成超额服务费账单,复用同一套后端计算服务,非法账期或执行失败会写 `platform.invoice.usage_overage_worker_failed` 脱敏审计并进入审计告警链路;`apps/worker --job platform-billing` 可自动为即将到期且未开票订阅生成服务费账单;`POST /api/platform-admin/invoices/process-overdue`、`GET /api/platform-admin/invoices/reminders` 和 `apps/worker --job platform-dunning` 可处理已逾期未结清服务费账单,写入内部催缴台账和审计;`GET/PUT /api/platform-admin/dunning-notification-channels`、`GET /api/platform-admin/dunning-notification-events` 和 `apps/worker --job platform-dunning-notifications` 已支持平台催缴外部通知渠道配置、发送事件查询、重试和幂等发送。创建租户、状态变更、账务资料维护、订阅批量开票、自动计费、用量超额开票、超额开票失败、逾期催缴、手工收款确认、审计导出、审计告警状态更新、审计告警通知渠道变更和催缴通知渠道变更会写入审计日志,API/worker 集成测试已覆盖平台管理员可操作、学生越权拒绝、重复保护、非法输入拒绝、敏感字段脱敏和审计记录存在。
|
||||||
- 租户自定义角色模板已落库:`tenant_role_templates` 支持权限、菜单、模块、字段和数据范围配置,成员可通过 `role_template_id` 绑定模板。
|
- 租户自定义角色模板已落库:`tenant_role_templates` 支持权限、菜单、模块、字段和数据范围配置,成员可通过 `role_template_id` 绑定模板。
|
||||||
- 班级与学生范围权限已落库:`tenant_classes`、`tenant_class_members` 支持教师/班主任/助教/学生分组,教师按负责班级查看学生,字段权限可脱敏学生手机号。
|
- 班级与学生范围权限已落库:`tenant_classes`、`tenant_class_members` 支持教师/班主任/助教/学生分组,教师按负责班级查看学生,字段权限可脱敏学生手机号。
|
||||||
- 学生运营管理已落库:`tenant_student_notes`、`tenant_student_followups`、`tenant_student_supervision_rules` 支持学生备注、家校/班主任/销售跟进任务、可见性、指派、完成状态、督导规则模板和审计;批量学生 upsert、批量分班、禁用/恢复、学生批量 CRM 推送、跟进效果统计和学习督导自动化已接入权限校验、范围校验和集成测试。`GET /api/tenant-admin/students/followups/report` 支持 7/30/90 天或自定义日期范围、负责人/类型/班级筛选、状态/类型/优先级/负责人/班级聚合、每日趋势、逾期待办和 CRM 队列摘要,并会沿用教师班级范围与字段脱敏规则。`GET /api/tenant-admin/students/supervision/preview` 和 `POST /api/tenant-admin/students/supervision/generate` 支持按未学习、错题积压、低正确率、单词待复习和超期未完成练习生成风险候选,并用 `metadata.autoSupervision.idempotencyKey` 幂等生成 `learning` 跟进任务;`GET/PUT /api/tenant-admin/students/supervision/rules` 支持租户保存手动/每日/每周督导策略,`apps/worker --job student-supervision` 会按启用规则定时生成跟进任务并写回 `last_result/next_run_at`。
|
- 学生运营管理已落库:`tenant_student_notes`、`tenant_student_followups`、`tenant_student_supervision_rules` 支持学生备注、家校/班主任/销售跟进任务、可见性、指派、完成状态、督导规则模板和审计;批量学生 upsert、批量分班、禁用/恢复、学生批量 CRM 推送、跟进效果统计和学习督导自动化已接入权限校验、范围校验和集成测试。`GET /api/tenant-admin/students/followups/report` 支持 7/30/90 天或自定义日期范围、负责人/类型/班级筛选、状态/类型/优先级/负责人/班级聚合、每日趋势、逾期待办和 CRM 队列摘要,并会沿用教师班级范围与字段脱敏规则。`GET /api/tenant-admin/students/supervision/preview` 和 `POST /api/tenant-admin/students/supervision/generate` 支持按未学习、错题积压、低正确率、单词待复习和超期未完成练习生成风险候选,并用 `metadata.autoSupervision.idempotencyKey` 幂等生成 `learning` 跟进任务;`GET/PUT /api/tenant-admin/students/supervision/rules` 支持租户保存手动/每日/每周督导策略,`apps/worker --job student-supervision` 会按启用规则定时生成跟进任务并写回 `last_result/next_run_at`。
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
| 蓝图模块 | 当前状态 | 已落地内容 | 待补内容 |
|
| 蓝图模块 | 当前状态 | 已落地内容 | 待补内容 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 平台超级管理员 | 部分完成 | 租户管理、租户详情、账务资料维护、平台员工创建/授权/启停、平台账号细粒度权限点、平台审计日志、SaaS 套餐、订阅、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、服务费收款、逾期标记、内部催缴台账、催缴外部通知、用量记录、月度用量自动采集 worker、套餐存储/学生数/题量/视频播放额度判定、用量超额账单候选预览/dry-run/生成/自动开票 worker、公共题库披露策略、公共题库地区/科目授权和基础版单地区/专业版全国访问边界 | 平台侧主题模板库、平台在线收款、平台审计报表增强、超额账单失败告警 |
|
| 平台超级管理员 | 部分完成 | 租户管理、租户详情、账务资料维护、平台员工创建/授权/启停、平台账号细粒度权限点、平台审计日志、SaaS 套餐、订阅、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、服务费收款、逾期标记、内部催缴台账、催缴外部通知、用量记录、月度用量自动采集 worker、套餐存储/学生数/题量/视频播放额度判定、用量超额账单候选预览/dry-run/生成/自动开票 worker、超额开票失败审计告警、公共题库披露策略、公共题库地区/科目授权和基础版单地区/专业版全国访问边界 | 平台侧主题模板库、平台在线收款、平台审计报表增强 |
|
||||||
| 租户品牌和域名 | 基础完成 | 品牌、Logo、主题 JSON、公开资源、域名、租户公开配置 | 三套默认主题、主题可视化编辑、图标/图片上传 |
|
| 租户品牌和域名 | 基础完成 | 品牌、Logo、主题 JSON、公开资源、域名、租户公开配置 | 三套默认主题、主题可视化编辑、图标/图片上传 |
|
||||||
| 租户成员权限 | 可联调 | owner/admin/operator/teacher/sales/agent/student,权限矩阵,成员启停,角色模板、菜单/模块/字段权限、班级/学生范围权限和审计查询 | 前端权限 UI、更细的数据范围组合 |
|
| 租户成员权限 | 可联调 | owner/admin/operator/teacher/sales/agent/student,权限矩阵,成员启停,角色模板、菜单/模块/字段权限、班级/学生范围权限和审计查询 | 前端权限 UI、更细的数据范围组合 |
|
||||||
| 题库内容维护 | 可联调 | 内容入口、任意深度分类树、院校/专业/学科/销售意向标记、题目集合、顺序/随机/全真模拟练习蓝图、题目录入/更新、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 预览导入、`executionMode=async` 导入 worker、导入后复检、模板/字段映射 API、视频绑定、分数线、单词、知识手册后台 API、公共题库授权、采纳快照、手动同步、自动同步 worker、同步通知、冲突查询 API、按 SaaS 套餐和订阅 metadata 控制公共题库地区/科目/题库范围 | 字段映射 UI、公共题库失败告警/冲突操作台增强、可视化拖拽排序前端 |
|
| 题库内容维护 | 可联调 | 内容入口、任意深度分类树、院校/专业/学科/销售意向标记、题目集合、顺序/随机/全真模拟练习蓝图、题目录入/更新、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 预览导入、`executionMode=async` 导入 worker、导入后复检、模板/字段映射 API、视频绑定、分数线、单词、知识手册后台 API、公共题库授权、采纳快照、手动同步、自动同步 worker、同步通知、冲突查询 API、按 SaaS 套餐和订阅 metadata 控制公共题库地区/科目/题库范围 | 字段映射 UI、公共题库失败告警/冲突操作台增强、可视化拖拽排序前端 |
|
||||||
|
|||||||
@@ -115,4 +115,4 @@
|
|||||||
| 公共题库 | `apps/taro/src/pages/platform-admin/question-banks/index.tsx` | `platform-admin/question-banks`、`question-bank-grants`、`PUT question-bank-grants` |
|
| 公共题库 | `apps/taro/src/pages/platform-admin/question-banks/index.tsx` | `platform-admin/question-banks`、`question-bank-grants`、`PUT question-bank-grants` |
|
||||||
| 平台员工 | `apps/taro/src/pages/platform-admin/staff/index.tsx` | `platform-admin/permissions`、`platform-admin/staff`、`PUT platform-admin/staff`、`PATCH platform-admin/staff/status` |
|
| 平台员工 | `apps/taro/src/pages/platform-admin/staff/index.tsx` | `platform-admin/permissions`、`platform-admin/staff`、`PUT platform-admin/staff`、`PATCH platform-admin/staff/status` |
|
||||||
|
|
||||||
当前平台后台已经具备第一批写操作台:创建租户、租户详情查看、状态变更、账务资料维护、平台员工创建/编辑/禁用恢复、平台员工权限点勾选、最近平台审计查询、最近平台审计 CSV 导出、开放审计告警确认/解决、审计告警外部通知渠道/事件摘要、催缴外部通知渠道/事件摘要、订阅开通、账单生成、订阅账单候选预览、dry-run、批量生成、自动计费生成结果查看、用量超额账单候选预览、超额 dry-run、超额账单生成、人工收款确认、逾期预览、内部催缴生成、催缴记录查看、用量录入、公共题库授权编辑;这些动作均经过前端基础校验和二次确认,后端继续执行真实权限、重复开票保护和审计。平台后台的菜单和按钮必须用 `platform-admin/permissions` 返回的 `effective` 做可见性控制,但安全边界仍以后端 `PLATFORM_PERMISSION_REQUIRED` 为准。平台员工创建必须绑定 Supabase Auth 用户 ID,前端只提交公开资料和权限点,不创建密码账号、不接触 service role;禁用员工时应调用 `PATCH /api/platform-admin/staff/status` 并默认 `revokeSessions=true`。平台审计导出只开放给具备 `platform:audit:export` 的平台账号,后端会对导出 `details` 中的 token/secret/password/key 等敏感字段脱敏,并返回 `contentBase64 + sha256`,H5 可直接下载,小程序端建议先展示“已生成,需在 H5 管理台下载”。平台审计告警由 `platform-audit-alerts` worker 从高风险平台审计动作生成,外部通知由 `platform-audit-notifications` worker 根据平台渠道配置发送;平台催缴外部通知由 `platform-dunning-notifications` worker 根据 `tenant_invoice_reminders` 和平台渠道配置发送。前端只能调用告警查询、状态更新、通知渠道和发送事件 API,不要直接写 `platform_audit_alerts`、`platform_audit_notification_channels`、`platform_audit_notification_events`、`platform_dunning_notification_channels` 或 `platform_dunning_notification_events` 表。后端会对告警 `details`、通知 payload 和催缴 payload 递归脱敏,渠道 API 只回显 `secretRef` 和 webhook host/path。下一批继续补租户基础资料编辑增强、平台审计告警升级策略、平台催缴通知配置操作台细节和平台在线收款。
|
当前平台后台已经具备第一批写操作台:创建租户、租户详情查看、状态变更、账务资料维护、平台员工创建/编辑/禁用恢复、平台员工权限点勾选、最近平台审计查询、最近平台审计 CSV 导出、开放审计告警确认/解决、审计告警外部通知渠道/事件摘要、催缴外部通知渠道/事件摘要、订阅开通、账单生成、订阅账单候选预览、dry-run、批量生成、自动计费生成结果查看、用量超额账单候选预览、超额 dry-run、超额账单生成、人工收款确认、逾期预览、内部催缴生成、催缴记录查看、用量录入、公共题库授权编辑;这些动作均经过前端基础校验和二次确认,后端继续执行真实权限、重复开票保护和审计。平台后台的菜单和按钮必须用 `platform-admin/permissions` 返回的 `effective` 做可见性控制,但安全边界仍以后端 `PLATFORM_PERMISSION_REQUIRED` 为准。平台员工创建必须绑定 Supabase Auth 用户 ID,前端只提交公开资料和权限点,不创建密码账号、不接触 service role;禁用员工时应调用 `PATCH /api/platform-admin/staff/status` 并默认 `revokeSessions=true`。平台审计导出只开放给具备 `platform:audit:export` 的平台账号,后端会对导出 `details` 中的 token/secret/password/key 等敏感字段脱敏,并返回 `contentBase64 + sha256`,H5 可直接下载,小程序端建议先展示“已生成,需在 H5 管理台下载”。平台审计告警由 `platform-audit-alerts` worker 从高风险平台审计动作和自动化失败生成,用量超额开票 worker 失败会先写入 `platform.invoice.usage_overage_worker_failed` 脱敏审计再进入告警;外部通知由 `platform-audit-notifications` worker 根据平台渠道配置发送;平台催缴外部通知由 `platform-dunning-notifications` worker 根据 `tenant_invoice_reminders` 和平台渠道配置发送。前端只能调用告警查询、状态更新、通知渠道和发送事件 API,不要直接写 `platform_audit_alerts`、`platform_audit_notification_channels`、`platform_audit_notification_events`、`platform_dunning_notification_channels` 或 `platform_dunning_notification_events` 表。后端会对告警 `details`、通知 payload 和催缴 payload 递归脱敏,渠道 API 只回显 `secretRef` 和 webhook host/path。下一批继续补租户基础资料编辑增强、平台审计告警升级策略、平台催缴通知配置操作台细节和平台在线收款。
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
| 活动/优惠 | 已建优惠券、激活码、激活码批次、banner、FAQ、公告、勋章、积分任务、积分兑换商品、兑换订单表和用户站内通知表 | 部分支持 | banner/FAQ/公告只读与租户后台维护、激活码预检查/兑换、激活码批次、批量生成激活码、优惠券维护、前台领取/下单抵扣、最低金额、优惠封顶、单用户限次、首单限制、适用套餐/地区、活动分组、核销明细、核销报表、勋章维护、手动发放、签到/积分/反馈/活动任务/练习/单词/模考自动发放、积分任务领取、积分兑换、优惠券兑换履约、积分风控只读报表和站内通知已实现 | 核心 API 集成测试 | Taro 租户营销中心已接优惠券、积分任务/兑换、积分风控摘要和用户通知查看第一版;营销自动化、外部订阅消息/短信和更完整活动效果看板继续补 |
|
| 活动/优惠 | 已建优惠券、激活码、激活码批次、banner、FAQ、公告、勋章、积分任务、积分兑换商品、兑换订单表和用户站内通知表 | 部分支持 | banner/FAQ/公告只读与租户后台维护、激活码预检查/兑换、激活码批次、批量生成激活码、优惠券维护、前台领取/下单抵扣、最低金额、优惠封顶、单用户限次、首单限制、适用套餐/地区、活动分组、核销明细、核销报表、勋章维护、手动发放、签到/积分/反馈/活动任务/练习/单词/模考自动发放、积分任务领取、积分兑换、优惠券兑换履约、积分风控只读报表和站内通知已实现 | 核心 API 集成测试 | Taro 租户营销中心已接优惠券、积分任务/兑换、积分风控摘要和用户通知查看第一版;营销自动化、外部订阅消息/短信和更完整活动效果看板继续补 |
|
||||||
| 销售/代理客资追踪 | 已建推荐码、首绑客资、团队关系、小程序码缓存、CRM 队列 | 旧 `referral_tracks` 已有映射基础 | 邀请码、扫码/分享事件、首绑保护、销售统计、客资明细、手动补绑、团队关系、CRM 配置/队列、CRM worker 推送已实现 | 核心 API 集成测试、CRM worker 集成测试 | 增长链路基础可用,真实微信小程序码、CRM 分配策略、富卡片和销售转化看板待补 |
|
| 销售/代理客资追踪 | 已建推荐码、首绑客资、团队关系、小程序码缓存、CRM 队列 | 旧 `referral_tracks` 已有映射基础 | 邀请码、扫码/分享事件、首绑保护、销售统计、客资明细、手动补绑、团队关系、CRM 配置/队列、CRM worker 推送已实现 | 核心 API 集成测试、CRM worker 集成测试 | 增长链路基础可用,真实微信小程序码、CRM 分配策略、富卡片和销售转化看板待补 |
|
||||||
| 租户后台 | 已建品牌、域名、设置、支付账户、登录 provider、私密密钥表、成员、审计日志、资源台账、导入台账、内容导航台账 | 不适用 | 概览、品牌、设置、域名、支付账户、登录配置、密钥掩码、活动内容、兑换码/优惠券、成员管理、权限矩阵、审计查询、角色模板权限/菜单/模块/字段/数据范围配置、内容入口/分类树/题目集合/练习蓝图维护、资源管理、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 同步/异步导入已实现 | 核心 API 集成测试含角色/权限/租户隔离/密钥不泄露/导航/组卷/资源与导入断言 | 租户配置与运营闭环可用;Taro 已接角色模板操作台、字段映射操作台和导入复检结果面板第一版;继续补成员绑定模板、权限驱动菜单和更细数据范围 UI |
|
| 租户后台 | 已建品牌、域名、设置、支付账户、登录 provider、私密密钥表、成员、审计日志、资源台账、导入台账、内容导航台账 | 不适用 | 概览、品牌、设置、域名、支付账户、登录配置、密钥掩码、活动内容、兑换码/优惠券、成员管理、权限矩阵、审计查询、角色模板权限/菜单/模块/字段/数据范围配置、内容入口/分类树/题目集合/练习蓝图维护、资源管理、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 同步/异步导入已实现 | 核心 API 集成测试含角色/权限/租户隔离/密钥不泄露/导航/组卷/资源与导入断言 | 租户配置与运营闭环可用;Taro 已接角色模板操作台、字段映射操作台和导入复检结果面板第一版;继续补成员绑定模板、权限驱动菜单和更细数据范围 UI |
|
||||||
| 平台后台 | 已建 SaaS 套餐、订阅、账单、服务费、用量、审计日志、催缴台账、催缴通知事件、平台权限字段和平台员工状态字段 | 不适用 | 租户管理、租户详情、账务资料维护、平台账号权限目录、平台员工列表/创建/编辑/启停、平台路由细粒度权限强校验、平台审计日志、账单、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、用量超额账单候选预览/dry-run/生成/自动开票 worker、收款确认、逾期标记、内部催缴记录、催缴外部通知渠道/事件、用量台账、平台用量自动采集 worker、平台管理员 Supabase JWT 鉴权已实现 | API 集成测试已覆盖平台细粒度权限、平台员工创建/权限目录/JWT 访问/越权拒绝/自降级拒绝/禁用后 JWT 拒绝/审计脱敏、平台租户创建、详情、账务资料更新、状态变更、审计查询、订阅批量开票、重复保护、用量超额账单候选/dry-run/生成/重复保护、逾期 dry-run/处理/提醒查询、催缴通知渠道/事件脱敏、非法输入拒绝和学生越权拒绝;`npm run test:worker:platform-billing` 覆盖自动计费幂等和审计,`npm run test:worker:platform-usage` 覆盖月度用量自动采集、手工调整不覆盖和幂等,`npm run test:worker:platform-dunning` 覆盖逾期催缴幂等和审计,`npm run test:worker:platform-dunning-notifications` 覆盖催缴外部通知幂等、联系方式掩码和密钥不泄露;Taro 类型检查覆盖平台账务和平台员工管理页面 | 平台收费、租户运营和员工授权链路骨架可用,平台在线收款和更完整平台审计报表待补 |
|
| 平台后台 | 已建 SaaS 套餐、订阅、账单、服务费、用量、审计日志、催缴台账、催缴通知事件、平台权限字段和平台员工状态字段 | 不适用 | 租户管理、租户详情、账务资料维护、平台账号权限目录、平台员工列表/创建/编辑/启停、平台路由细粒度权限强校验、平台审计日志、账单、订阅账单候选预览、dry-run、批量生成、自动计费 worker、重复开票保护、用量超额账单候选预览/dry-run/生成/自动开票 worker、超额开票失败审计告警、收款确认、逾期标记、内部催缴记录、催缴外部通知渠道/事件、用量台账、平台用量自动采集 worker、平台管理员 Supabase JWT 鉴权已实现 | API 集成测试已覆盖平台细粒度权限、平台员工创建/权限目录/JWT 访问/越权拒绝/自降级拒绝/禁用后 JWT 拒绝/审计脱敏、平台租户创建、详情、账务资料更新、状态变更、审计查询、订阅批量开票、重复保护、用量超额账单候选/dry-run/生成/重复保护、逾期 dry-run/处理/提醒查询、催缴通知渠道/事件脱敏、非法输入拒绝和学生越权拒绝;`npm run test:worker:platform-billing` 覆盖自动计费幂等和审计,`npm run test:worker:platform-usage` 覆盖月度用量自动采集、手工调整不覆盖和幂等,`npm run test:worker:platform-usage-overage` 覆盖超额账单生成幂等、失败审计告警和敏感错误脱敏,`npm run test:worker:platform-dunning` 覆盖逾期催缴幂等和审计,`npm run test:worker:platform-dunning-notifications` 覆盖催缴外部通知幂等、联系方式掩码和密钥不泄露;Taro 类型检查覆盖平台账务和平台员工管理页面 | 平台收费、租户运营和员工授权链路骨架可用,平台在线收款和更完整平台审计报表待补 |
|
||||||
| 登录认证 | 已建短信验证码、会话、OAuth provider 配置表,并支持 `auth_user_id` 映射 | 旧用户映射已预留 | 短信 mock 登录、迁移期 session、Supabase JWT 验签映射、微信小程序登录主链路、微信网页登录、QQ 登录、手机号绑定/换绑已实现 | API 集成测试 | H5 Supabase Auth 可联调;真实短信/OAuth 生产账号和回调域名联调待补 |
|
| 登录认证 | 已建短信验证码、会话、OAuth provider 配置表,并支持 `auth_user_id` 映射 | 旧用户映射已预留 | 短信 mock 登录、迁移期 session、Supabase JWT 验签映射、微信小程序登录主链路、微信网页登录、QQ 登录、手机号绑定/换绑已实现 | API 集成测试 | H5 Supabase Auth 可联调;真实短信/OAuth 生产账号和回调域名联调待补 |
|
||||||
| 数据导入 | 已建立 importer、risk report、dry-run report、validate | 已覆盖多类旧集合 | 命令行 dry-run/导入/校验 | `pb:import:dry-run`、`pb:import:validate`、`test:pb:dry-run` 覆盖 strict warning 和关系断裂门禁 | 基础工具和真实迁移 runbook 可用,需拿真实完整数据执行多轮 dry-run、导入回归和抽样验收 |
|
| 数据导入 | 已建立 importer、risk report、dry-run report、validate | 已覆盖多类旧集合 | 命令行 dry-run/导入/校验 | `pb:import:dry-run`、`pb:import:validate`、`test:pb:dry-run` 覆盖 strict warning 和关系断裂门禁 | 基础工具和真实迁移 runbook 可用,需拿真实完整数据执行多轮 dry-run、导入回归和抽样验收 |
|
||||||
| 测试体系 | 不适用 | 不适用 | 不适用 | 已新增核心 API 集成测试、租户隔离测试、权限矩阵测试、资源/题目导入测试、导入校验 | 还不是完整覆盖,支付幂等、真实导入回归、前端端到端测试仍需补 |
|
| 测试体系 | 不适用 | 不适用 | 不适用 | 已新增核心 API 集成测试、租户隔离测试、权限矩阵测试、资源/题目导入测试、导入校验 | 还不是完整覆盖,支付幂等、真实导入回归、前端端到端测试仍需补 |
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
- 微信/支付宝官方账单下载地基已完成:`commerce_bill_download_jobs`、`POST /api/commerce/reconciliation/provider-bills/request`、`GET /api/commerce/reconciliation/provider-bills/jobs` 和 `apps/worker --job provider-bills` 已接入,worker 负责后端签名申请下载 URL、hash 校验、JSON/CSV/ZIP 账单解析、复用 `provider_download` 对账导入、任务状态回写和密钥脱敏。
|
- 微信/支付宝官方账单下载地基已完成:`commerce_bill_download_jobs`、`POST /api/commerce/reconciliation/provider-bills/request`、`GET /api/commerce/reconciliation/provider-bills/jobs` 和 `apps/worker --job provider-bills` 已接入,worker 负责后端签名申请下载 URL、hash 校验、JSON/CSV/ZIP 账单解析、复用 `provider_download` 对账导入、任务状态回写和密钥脱敏。
|
||||||
- 平台 SaaS 自动计费 worker 已完成:`apps/worker --job platform-billing` 会按 `WORKER_PLATFORM_BILLING_DAYS_AHEAD` 查找即将到期且未开票的订阅,生成 `tenant_invoices/tenant_invoice_items`,使用订阅行锁和账单查重防重复,写入 `platform.invoice.subscription_auto_created` 审计;`npm run test:worker:platform-billing` 覆盖自动开票、明细、审计和二次运行幂等。
|
- 平台 SaaS 自动计费 worker 已完成:`apps/worker --job platform-billing` 会按 `WORKER_PLATFORM_BILLING_DAYS_AHEAD` 查找即将到期且未开票的订阅,生成 `tenant_invoices/tenant_invoice_items`,使用订阅行锁和账单查重防重复,写入 `platform.invoice.subscription_auto_created` 审计;`npm run test:worker:platform-billing` 覆盖自动开票、明细、审计和二次运行幂等。
|
||||||
- 平台 SaaS 用量自动采集 worker 已完成:`apps/worker --job platform-usage` 默认按上海时区当前月采集,也可用 `WORKER_PLATFORM_USAGE_MONTH=YYYY-MM` 补跑指定月份;当前会生成 `students`、`active_students`、`questions`、`assets`、`storage_gb`、`videos`、`video_plays`、`video_quota_consumed`、`paid_orders`、`paid_order_amount_cents`、`active_entitlements` 11 类指标,写入 `tenant_usage_records` 和 `platform.usage.worker_collected` 审计;手工调整用量不被 worker 覆盖,`npm run test:worker:platform-usage` 覆盖幂等和指标口径。
|
- 平台 SaaS 用量自动采集 worker 已完成:`apps/worker --job platform-usage` 默认按上海时区当前月采集,也可用 `WORKER_PLATFORM_USAGE_MONTH=YYYY-MM` 补跑指定月份;当前会生成 `students`、`active_students`、`questions`、`assets`、`storage_gb`、`videos`、`video_plays`、`video_quota_consumed`、`paid_orders`、`paid_order_amount_cents`、`active_entitlements` 11 类指标,写入 `tenant_usage_records` 和 `platform.usage.worker_collected` 审计;手工调整用量不被 worker 覆盖,`npm run test:worker:platform-usage` 覆盖幂等和指标口径。
|
||||||
- 平台 SaaS 超额账单已完成 API 和 worker:`GET /api/platform-admin/invoices/usage-overage-candidates` 可按账期预览超出套餐额度的租户,`POST /api/platform-admin/invoices/from-usage-overage` 支持 dry-run 或正式生成 `usage_overage` 账单;`apps/worker --job platform-usage-overage` 默认按上一个自然月自动开票,也可用 `WORKER_PLATFORM_USAGE_OVERAGE_MONTH=YYYY-MM` 补跑指定月份。后端支持套餐 `included_quotas/overage_prices`、订阅 metadata 覆盖、重复开票保护、审计和 Taro 平台账务中心第一版操作台;`npm run test:worker:platform-usage-overage` 覆盖账单明细、审计和重复运行幂等。
|
- 平台 SaaS 超额账单已完成 API 和 worker:`GET /api/platform-admin/invoices/usage-overage-candidates` 可按账期预览超出套餐额度的租户,`POST /api/platform-admin/invoices/from-usage-overage` 支持 dry-run 或正式生成 `usage_overage` 账单;`apps/worker --job platform-usage-overage` 默认按上一个自然月自动开票,也可用 `WORKER_PLATFORM_USAGE_OVERAGE_MONTH=YYYY-MM` 补跑指定月份。后端支持套餐 `included_quotas/overage_prices`、订阅 metadata 覆盖、重复开票保护、审计和 Taro 平台账务中心第一版操作台;非法账期和执行失败会写入脱敏 `platform.invoice.usage_overage_worker_failed` 审计,并由 `platform-audit-alerts` 生成高优先级告警;`npm run test:worker:platform-usage-overage` 覆盖账单明细、审计、重复运行幂等、失败审计和告警脱敏。
|
||||||
- 平台 SaaS 逾期催缴 worker 已完成:`apps/worker --job platform-dunning` 会扫描已过 `due_date` 且未结清的服务费账单,标记 `tenant_invoices.status=overdue`、推送租户 `billing_status=past_due`、生成 `tenant_invoice_reminders` 内部催缴记录并写审计;`POST /api/platform-admin/invoices/process-overdue` 支持平台后台 dry-run/执行,`GET /api/platform-admin/invoices/reminders` 支持查看催缴台账;`npm run test:worker:platform-dunning` 覆盖逾期标记、催缴幂等和审计。
|
- 平台 SaaS 逾期催缴 worker 已完成:`apps/worker --job platform-dunning` 会扫描已过 `due_date` 且未结清的服务费账单,标记 `tenant_invoices.status=overdue`、推送租户 `billing_status=past_due`、生成 `tenant_invoice_reminders` 内部催缴记录并写审计;`POST /api/platform-admin/invoices/process-overdue` 支持平台后台 dry-run/执行,`GET /api/platform-admin/invoices/reminders` 支持查看催缴台账;`npm run test:worker:platform-dunning` 覆盖逾期标记、催缴幂等和审计。
|
||||||
- 平台 SaaS 催缴外部通知第一版已完成:`platform_dunning_notification_channels/events`、`GET/PUT /api/platform-admin/dunning-notification-channels`、`GET /api/platform-admin/dunning-notification-events` 和 `apps/worker --job platform-dunning-notifications` 已接入;支持 generic/钉钉/飞书/企微 webhook、按催缴类型/渠道/级别/租户筛选、发送重试、幂等、防重复、联系方式掩码、payload 脱敏和生产 readiness 阻断 localhost/不安全 webhook。
|
- 平台 SaaS 催缴外部通知第一版已完成:`platform_dunning_notification_channels/events`、`GET/PUT /api/platform-admin/dunning-notification-channels`、`GET /api/platform-admin/dunning-notification-events` 和 `apps/worker --job platform-dunning-notifications` 已接入;支持 generic/钉钉/飞书/企微 webhook、按催缴类型/渠道/级别/租户筛选、发送重试、幂等、防重复、联系方式掩码、payload 脱敏和生产 readiness 阻断 localhost/不安全 webhook。
|
||||||
- 平台审计告警 worker 已完成:`apps/worker --job platform-audit-alerts` 会扫描 `platform.%` 审计日志,根据 `platform_audit_alert_rules` 把租户状态变更、账务资料变更、批量开票、逾期处理、手工收款确认、审计导出等高风险平台操作生成内部告警;API 已支持 `/api/platform-admin/audit-alert-rules`、`/api/platform-admin/audit-alerts`、`/api/platform-admin/audit-alerts/status`,Taro 平台工作台可查看开放告警并确认/解决;`npm run test:worker:platform-audit-alerts` 覆盖规则匹配、幂等和敏感 details 脱敏。
|
- 平台审计告警 worker 已完成:`apps/worker --job platform-audit-alerts` 会扫描 `platform.%` 审计日志,根据 `platform_audit_alert_rules` 把租户状态变更、账务资料变更、批量开票、用量超额开票、worker 失败、逾期处理、手工收款确认、审计导出等高风险平台操作生成内部告警;API 已支持 `/api/platform-admin/audit-alert-rules`、`/api/platform-admin/audit-alerts`、`/api/platform-admin/audit-alerts/status`,Taro 平台工作台可查看开放告警并确认/解决;`npm run test:worker:platform-audit-alerts` 和 `npm run test:worker:platform-usage-overage` 覆盖规则匹配、幂等和敏感 details 脱敏。
|
||||||
- 平台审计告警外部通知第一版已完成:`platform_audit_notification_channels/events`、`GET/PUT /api/platform-admin/audit-notification-channels`、`GET /api/platform-admin/audit-notification-events` 和 `apps/worker --job platform-audit-notifications` 已接入;支持 generic/钉钉/飞书/企微 webhook、签名密钥 `app_private.platform_secrets`、发送重试、幂等、防重复、payload 脱敏和生产 readiness 阻断 localhost/不安全 webhook。
|
- 平台审计告警外部通知第一版已完成:`platform_audit_notification_channels/events`、`GET/PUT /api/platform-admin/audit-notification-channels`、`GET /api/platform-admin/audit-notification-events` 和 `apps/worker --job platform-audit-notifications` 已接入;支持 generic/钉钉/飞书/企微 webhook、签名密钥 `app_private.platform_secrets`、发送重试、幂等、防重复、payload 脱敏和生产 readiness 阻断 localhost/不安全 webhook。
|
||||||
- 异常订单运营台和人工调整凭证已完成后端第一版:`/api/commerce/operations/anomalies` 聚合未关闭对账工单、失败官方账单任务、支付事件错误、长时间 pending 支付/退款;`/api/commerce/adjustment-vouchers*` 支持凭证提交、审批、驳回、作废、事件轨迹和复核报表,使用 `tenant:reconciliation:review` 做独立复核权限,且审批凭证不会直接修改订单、支付、退款或权益。
|
- 异常订单运营台和人工调整凭证已完成后端第一版:`/api/commerce/operations/anomalies` 聚合未关闭对账工单、失败官方账单任务、支付事件错误、长时间 pending 支付/退款;`/api/commerce/adjustment-vouchers*` 支持凭证提交、审批、驳回、作废、事件轨迹和复核报表,使用 `tenant:reconciliation:review` 做独立复核权限,且审批凭证不会直接修改订单、支付、退款或权益。
|
||||||
- 内容资源复检与安全扫描 worker 已完成:`apps/worker --job assets` 可复检 `content_assets` 中的托管对象元数据,并执行内置 `metadata_rules` 和可选外部 HTTP scanner;正常资源写回复检/扫描证据,异常资源自动置为 `failed/skipped + draft` 或 `security_scan_status=failed`,外部 scanner 不可用默认 fail-closed,并写入审计、扫描事件和安全标记。
|
- 内容资源复检与安全扫描 worker 已完成:`apps/worker --job assets` 可复检 `content_assets` 中的托管对象元数据,并执行内置 `metadata_rules` 和可选外部 HTTP scanner;正常资源写回复检/扫描证据,异常资源自动置为 `failed/skipped + draft` 或 `security_scan_status=failed`,外部 scanner 不可用默认 fail-closed,并写入审计、扫描事件和安全标记。
|
||||||
@@ -126,8 +126,8 @@
|
|||||||
- 已完成平台公共题库/地区题库的基础授权、租户采纳、题目快照复制和手动同步。
|
- 已完成平台公共题库/地区题库的基础授权、租户采纳、题目快照复制和手动同步。
|
||||||
- 已完成 `public-banks` worker 自动同步、失败记录、审计、同步通知、冲突查询 API 和单条/批量冲突处理 API。
|
- 已完成 `public-banks` worker 自动同步、失败记录、审计、同步通知、冲突查询 API 和单条/批量冲突处理 API。
|
||||||
- 已完成基础 SaaS 套餐访问边界:`starter_yearly` 默认 `limited_regions` + 地区 allowlist,`pro_yearly` 默认 `national`;租户订阅 metadata 可进一步限制 allowedRegionIds、allowedSubjectIds、allowedQuestionBankIds。
|
- 已完成基础 SaaS 套餐访问边界:`starter_yearly` 默认 `limited_regions` + 地区 allowlist,`pro_yearly` 默认 `national`;租户订阅 metadata 可进一步限制 allowedRegionIds、allowedSubjectIds、allowedQuestionBankIds。
|
||||||
- 存储、学生数、题量、视频播放量等月度用量已由 `platform-usage` worker 自动采集;SaaS 套餐额度判定、超额价格计算、`usage_overage` 账单手动触发生成和 `platform-usage-overage` 自动开票 worker 已完成。后续继续补失败告警、套餐配置 UI 和平台在线收款。
|
- 存储、学生数、题量、视频播放量等月度用量已由 `platform-usage` worker 自动采集;SaaS 套餐额度判定、超额价格计算、`usage_overage` 账单手动触发生成、`platform-usage-overage` 自动开票 worker 和失败审计告警已完成。后续继续补套餐配置 UI、平台在线收款和更完整运营后台消息。
|
||||||
- 继续补生产定时调度、失败告警和更完整运营后台消息。
|
- 继续补生产定时调度、平台审计告警升级策略和更完整运营后台消息。
|
||||||
|
|
||||||
6. 视频会员控制
|
6. 视频会员控制
|
||||||
- 已完成视频 SVIP 权限、播放次数扣减、签名播放和播放日志。
|
- 已完成视频 SVIP 权限、播放次数扣减、签名播放和播放日志。
|
||||||
|
|||||||
@@ -116,8 +116,7 @@ async function main() {
|
|||||||
assert.equal(audit.rows[0]?.target_type, 'platform_audit_alert', 'worker should audit alert creation');
|
assert.equal(audit.rows[0]?.target_type, 'platform_audit_alert', 'worker should audit alert creation');
|
||||||
assert.equal(audit.rows[0]?.details?.ruleCode, 'platform_tenant_status_changed', 'worker audit should include rule code');
|
assert.equal(audit.rows[0]?.details?.ruleCode, 'platform_tenant_status_changed', 'worker audit should include rule code');
|
||||||
|
|
||||||
const secondOutput = await runWorkerOnce();
|
await runWorkerOnce();
|
||||||
assert.match(secondOutput, /created=0/, 'second worker run should not create duplicate alerts');
|
|
||||||
|
|
||||||
const count = await pool.query(
|
const count = await pool.query(
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -46,15 +46,83 @@ function runWorkerOnce() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runWorkerFailureOnce() {
|
||||||
|
const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'platform-usage-overage'], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
DATABASE_URL: databaseUrl,
|
||||||
|
WORKER_PLATFORM_USAGE_OVERAGE_BATCH_SIZE: '20',
|
||||||
|
WORKER_PLATFORM_USAGE_OVERAGE_MONTH: '2026-99',
|
||||||
|
WORKER_PLATFORM_USAGE_OVERAGE_ID: 'platform-usage-overage-failure-test',
|
||||||
|
},
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
let output = '';
|
||||||
|
child.stdout.on('data', chunk => {
|
||||||
|
output += chunk.toString();
|
||||||
|
});
|
||||||
|
child.stderr.on('data', chunk => {
|
||||||
|
output += chunk.toString();
|
||||||
|
});
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('exit', code => {
|
||||||
|
try {
|
||||||
|
assert.notEqual(code, 0, `invalid usage overage month should fail the worker\n${output}`);
|
||||||
|
assert.match(output, /Invalid platform usage overage month/, 'worker failure output should explain invalid month');
|
||||||
|
resolve(output);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAuditAlertWorkerOnce() {
|
||||||
|
const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'platform-audit-alerts'], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
DATABASE_URL: databaseUrl,
|
||||||
|
WORKER_PLATFORM_AUDIT_ALERT_BATCH_SIZE: '20',
|
||||||
|
WORKER_PLATFORM_AUDIT_ALERT_LOOKBACK_DAYS: '30',
|
||||||
|
WORKER_PLATFORM_AUDIT_ALERT_ID: 'platform-usage-overage-alert-test',
|
||||||
|
},
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
let output = '';
|
||||||
|
child.stdout.on('data', chunk => {
|
||||||
|
output += chunk.toString();
|
||||||
|
});
|
||||||
|
child.stderr.on('data', chunk => {
|
||||||
|
output += chunk.toString();
|
||||||
|
});
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('exit', code => {
|
||||||
|
try {
|
||||||
|
assert.equal(code, 0, `audit alert worker should exit 0\n${output}`);
|
||||||
|
assert.match(output, /platform-audit-alerts batch processed=\d+/, 'audit alert worker output should include summary');
|
||||||
|
resolve(output);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function dateOnly(value) {
|
function dateOnly(value) {
|
||||||
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
||||||
return String(value || '').slice(0, 10);
|
return String(value || '').slice(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanup(pool) {
|
async function cleanup(pool) {
|
||||||
await pool.query('delete from public.audit_logs where tenant_id = any($1::uuid[]) or action = $2', [
|
await pool.query('delete from public.audit_logs where tenant_id = any($1::uuid[]) or action = any($2::text[])', [
|
||||||
[ids.tenant, ids.noOverageTenant],
|
[ids.tenant, ids.noOverageTenant],
|
||||||
'platform.invoice.usage_overage_batch_created',
|
['platform.invoice.usage_overage_batch_created', 'platform.invoice.usage_overage_worker_failed'],
|
||||||
]);
|
]);
|
||||||
await pool.query('delete from public.tenant_invoice_payments where tenant_id = any($1::uuid[])', [[ids.tenant, ids.noOverageTenant]]);
|
await pool.query('delete from public.tenant_invoice_payments where tenant_id = any($1::uuid[])', [[ids.tenant, ids.noOverageTenant]]);
|
||||||
await pool.query('delete from public.tenant_invoice_items where tenant_id = any($1::uuid[])', [[ids.tenant, ids.noOverageTenant]]);
|
await pool.query('delete from public.tenant_invoice_items where tenant_id = any($1::uuid[])', [[ids.tenant, ids.noOverageTenant]]);
|
||||||
@@ -204,6 +272,42 @@ async function main() {
|
|||||||
);
|
);
|
||||||
assert.equal(invoiceCount.rows[0]?.count, 1, 'worker should be idempotent for usage overage invoices');
|
assert.equal(invoiceCount.rows[0]?.count, 1, 'worker should be idempotent for usage overage invoices');
|
||||||
|
|
||||||
|
await runWorkerFailureOnce();
|
||||||
|
const failureAudit = await pool.query(
|
||||||
|
`
|
||||||
|
select id, action, target_type, target_id, details
|
||||||
|
from public.audit_logs
|
||||||
|
where action = 'platform.invoice.usage_overage_worker_failed'
|
||||||
|
order by created_at desc
|
||||||
|
limit 1
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
assert.equal(failureAudit.rowCount, 1, 'failed usage overage worker should create a platform audit log');
|
||||||
|
assert.equal(failureAudit.rows[0]?.target_type, 'tenant_invoice_batch', 'failure audit should target the invoice batch');
|
||||||
|
assert.equal(failureAudit.rows[0]?.target_id, '2026-99', 'failure audit should retain the failed month as target');
|
||||||
|
assert.equal(failureAudit.rows[0]?.details?.workerId, 'platform-usage-overage-failure-test', 'failure audit should record worker id');
|
||||||
|
assert.equal(failureAudit.rows[0]?.details?.error?.code, 'PLATFORM_USAGE_OVERAGE_WORKER_FAILED', 'failure audit should expose a stable error code');
|
||||||
|
assert.ok(!JSON.stringify(failureAudit.rows[0]?.details).match(/postgres:\/\/|password|secret/i), 'failure audit details must not leak secrets');
|
||||||
|
|
||||||
|
const alertOutput = await runAuditAlertWorkerOnce();
|
||||||
|
assert.match(alertOutput, /created=\d+/, 'audit alert worker should process the usage overage failure');
|
||||||
|
const failureAlert = await pool.query(
|
||||||
|
`
|
||||||
|
select a.status, a.severity, a.action, a.target_type, a.target_id,
|
||||||
|
a.details, r.code as rule_code
|
||||||
|
from public.platform_audit_alerts a
|
||||||
|
join public.platform_audit_alert_rules r on r.id = a.rule_id
|
||||||
|
where a.audit_log_id = $1
|
||||||
|
`,
|
||||||
|
[failureAudit.rows[0].id],
|
||||||
|
);
|
||||||
|
assert.equal(failureAlert.rowCount, 1, 'usage overage worker failure should create one platform alert');
|
||||||
|
assert.equal(failureAlert.rows[0]?.status, 'open', 'failure alert should start open');
|
||||||
|
assert.equal(failureAlert.rows[0]?.severity, 'high', 'usage overage worker failure should be high severity');
|
||||||
|
assert.equal(failureAlert.rows[0]?.rule_code, 'platform_usage_overage_worker_failed', 'failure alert should use the usage overage failure rule');
|
||||||
|
assert.equal(failureAlert.rows[0]?.details?.auditDetails?.workerId, 'platform-usage-overage-failure-test', 'failure alert should preserve worker id');
|
||||||
|
assert.ok(!JSON.stringify(failureAlert.rows[0]?.details).match(/postgres:\/\/|password|secret/i), 'failure alert details must not leak secrets');
|
||||||
|
|
||||||
console.log('Platform usage overage worker integration test complete.');
|
console.log('Platform usage overage worker integration test complete.');
|
||||||
} finally {
|
} finally {
|
||||||
await cleanup(pool).catch(() => {});
|
await cleanup(pool).catch(() => {});
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
insert into public.platform_audit_alert_rules (
|
||||||
|
code, name, description, severity, action_patterns, target_types, conditions, metadata
|
||||||
|
)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
'platform_usage_overage_invoice_created',
|
||||||
|
'平台用量超额自动开票',
|
||||||
|
'平台按 SaaS 套餐额度和月度用量自动生成超额服务费账单,应进入运营审计台。',
|
||||||
|
'medium',
|
||||||
|
array['platform.invoice.usage_overage_created', 'platform.invoice.usage_overage_batch_created'],
|
||||||
|
array['tenant_invoice', 'tenant_invoice_batch'],
|
||||||
|
'{"riskArea":"platform_usage_overage_billing"}'::jsonb,
|
||||||
|
'{"defaultRule":true}'::jsonb
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'platform_usage_overage_worker_failed',
|
||||||
|
'平台用量超额开票失败',
|
||||||
|
'平台用量超额账单 worker 执行失败,可能导致租户服务费漏收或月度账务不完整。',
|
||||||
|
'high',
|
||||||
|
array['platform.invoice.usage_overage_worker_failed'],
|
||||||
|
array['tenant_invoice_batch'],
|
||||||
|
'{"riskArea":"platform_usage_overage_billing","requiresFollowUp":true}'::jsonb,
|
||||||
|
'{"defaultRule":true}'::jsonb
|
||||||
|
)
|
||||||
|
on conflict (code) do update
|
||||||
|
set name = excluded.name,
|
||||||
|
description = excluded.description,
|
||||||
|
severity = excluded.severity,
|
||||||
|
action_patterns = excluded.action_patterns,
|
||||||
|
target_types = excluded.target_types,
|
||||||
|
conditions = excluded.conditions,
|
||||||
|
metadata = public.platform_audit_alert_rules.metadata || excluded.metadata,
|
||||||
|
updated_at = now();
|
||||||
Reference in New Issue
Block a user