@kunlv/atdc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +58 -0
- package/GO.md +245 -0
- package/bin/add-command.py +144 -0
- package/bin/atdc.js +460 -0
- package/bin/cls.js +98 -0
- package/central/link.sh +60 -0
- package/central/ticket_center_server/.handoffs/TASK-238389_QYinVipLimit.md +128 -0
- package/central/ticket_center_server/.test_plans/TASK-227254-SupportMembership-self-test-plan.md +566 -0
- package/central/ticket_center_server/.test_plans/task-238389-qyin-vip-limit-self-test-plan.md +210 -0
- package/commands/claude/atd.md +3 -0
- package/commands/claude/atdc/1-spec.md +5 -0
- package/commands/claude/atdc/1-update.md +5 -0
- package/commands/claude/atdc/2-qa.md +3 -0
- package/commands/claude/atdc/3-impl-skip-spec.md +5 -0
- package/commands/claude/atdc/3-impl.md +3 -0
- package/commands/claude/atdc/4-verify.md +3 -0
- package/commands/claude/atdc/5-done.md +3 -0
- package/commands/claude/atdc/add-command.md +4 -0
- package/commands/claude/atdc/add-to-hc.md +3 -0
- package/commands/claude/atdc/analysis-cls-log.md +4 -0
- package/commands/claude/atdc/article.md +6 -0
- package/commands/claude/atdc/bugfix.md +4 -0
- package/commands/claude/atdc/code-simplifier.md +8 -0
- package/commands/claude/atdc/commit.md +4 -0
- package/commands/claude/atdc/compound.md +5 -0
- package/commands/claude/atdc/context.md +3 -0
- package/commands/claude/atdc/diagnose.md +8 -0
- package/commands/claude/atdc/git-guardrails.md +18 -0
- package/commands/claude/atdc/grill-with-docs.md +8 -0
- package/commands/claude/atdc/handoff.md +8 -0
- package/commands/claude/atdc/health.md +3 -0
- package/commands/claude/atdc/learn.md +4 -0
- package/commands/claude/atdc/review.md +10 -0
- package/commands/claude/atdc/search.md +4 -0
- package/commands/claude/atdc/strategy.md +3 -0
- package/commands/claude/atdc/table2xlsx.md +4 -0
- package/commands/claude/atdc/test-plan.md +5 -0
- package/commands/claude/atdc/travel-planner.md +4 -0
- package/commands/claude/atdc/xlsx2sql-insert.md +4 -0
- package/commands/claude/atdc/xlsx2sql-update.md +4 -0
- package/commands/claude/atdc/zoom-out.md +8 -0
- package/commands/codex/atdc/SKILL.md +8 -0
- package/commands/codex/atdc-1-spec/SKILL.md +8 -0
- package/commands/codex/atdc-1-update/SKILL.md +8 -0
- package/commands/codex/atdc-2-qa/SKILL.md +8 -0
- package/commands/codex/atdc-3-impl/SKILL.md +8 -0
- package/commands/codex/atdc-3-impl-skip-spec/SKILL.md +8 -0
- package/commands/codex/atdc-4-verify/SKILL.md +8 -0
- package/commands/codex/atdc-5-done/SKILL.md +8 -0
- package/commands/codex/atdc-add-command/SKILL.md +7 -0
- package/commands/codex/atdc-add-to-hc/SKILL.md +8 -0
- package/commands/codex/atdc-analysis-cls-log/SKILL.md +5 -0
- package/commands/codex/atdc-article/SKILL.md +8 -0
- package/commands/codex/atdc-bugfix/SKILL.md +5 -0
- package/commands/codex/atdc-code-simplifier/SKILL.md +7 -0
- package/commands/codex/atdc-commit/SKILL.md +7 -0
- package/commands/codex/atdc-compound/SKILL.md +8 -0
- package/commands/codex/atdc-context/SKILL.md +8 -0
- package/commands/codex/atdc-diagnose/SKILL.md +9 -0
- package/commands/codex/atdc-git-guardrails/SKILL.md +19 -0
- package/commands/codex/atdc-grill-with-docs/SKILL.md +9 -0
- package/commands/codex/atdc-handoff/SKILL.md +9 -0
- package/commands/codex/atdc-health/SKILL.md +8 -0
- package/commands/codex/atdc-learn/SKILL.md +7 -0
- package/commands/codex/atdc-review/SKILL.md +49 -0
- package/commands/codex/atdc-search/SKILL.md +5 -0
- package/commands/codex/atdc-strategy/SKILL.md +8 -0
- package/commands/codex/atdc-table2xlsx/SKILL.md +8 -0
- package/commands/codex/atdc-test-plan/SKILL.md +13 -0
- package/commands/codex/atdc-travel-planner/SKILL.md +7 -0
- package/commands/codex/atdc-xlsx2sql-insert/SKILL.md +8 -0
- package/commands/codex/atdc-xlsx2sql-update/SKILL.md +8 -0
- package/commands/codex/atdc-zoom-out/SKILL.md +9 -0
- package/commands/cursor/atdc/SKILL.md +8 -0
- package/commands/cursor/atdc-1-spec/SKILL.md +8 -0
- package/commands/cursor/atdc-1-update/SKILL.md +8 -0
- package/commands/cursor/atdc-2-qa/SKILL.md +8 -0
- package/commands/cursor/atdc-3-impl/SKILL.md +8 -0
- package/commands/cursor/atdc-3-impl-skip-spec/SKILL.md +8 -0
- package/commands/cursor/atdc-4-verify/SKILL.md +8 -0
- package/commands/cursor/atdc-5-done/SKILL.md +8 -0
- package/commands/cursor/atdc-add-command/SKILL.md +7 -0
- package/commands/cursor/atdc-add-to-hc/SKILL.md +8 -0
- package/commands/cursor/atdc-analysis-cls-log/SKILL.md +5 -0
- package/commands/cursor/atdc-article/SKILL.md +8 -0
- package/commands/cursor/atdc-bugfix/SKILL.md +5 -0
- package/commands/cursor/atdc-code-simplifier/SKILL.md +7 -0
- package/commands/cursor/atdc-commit/SKILL.md +7 -0
- package/commands/cursor/atdc-compound/SKILL.md +8 -0
- package/commands/cursor/atdc-context/SKILL.md +8 -0
- package/commands/cursor/atdc-diagnose/SKILL.md +8 -0
- package/commands/cursor/atdc-git-guardrails/SKILL.md +18 -0
- package/commands/cursor/atdc-grill-with-docs/SKILL.md +8 -0
- package/commands/cursor/atdc-handoff/SKILL.md +8 -0
- package/commands/cursor/atdc-health/SKILL.md +8 -0
- package/commands/cursor/atdc-learn/SKILL.md +7 -0
- package/commands/cursor/atdc-review/SKILL.md +49 -0
- package/commands/cursor/atdc-search/SKILL.md +5 -0
- package/commands/cursor/atdc-strategy/SKILL.md +8 -0
- package/commands/cursor/atdc-table2xlsx/SKILL.md +8 -0
- package/commands/cursor/atdc-test-plan/SKILL.md +7 -0
- package/commands/cursor/atdc-travel-planner/SKILL.md +7 -0
- package/commands/cursor/atdc-xlsx2sql-insert/SKILL.md +8 -0
- package/commands/cursor/atdc-xlsx2sql-update/SKILL.md +8 -0
- package/commands/cursor/atdc-zoom-out/SKILL.md +8 -0
- package/commands/opencode/atd.md +6 -0
- package/commands/opencode/atdc/1-spec.md +8 -0
- package/commands/opencode/atdc/1-update.md +8 -0
- package/commands/opencode/atdc/2-qa.md +6 -0
- package/commands/opencode/atdc/3-impl-skip-spec.md +8 -0
- package/commands/opencode/atdc/3-impl.md +6 -0
- package/commands/opencode/atdc/4-verify.md +6 -0
- package/commands/opencode/atdc/5-done.md +6 -0
- package/commands/opencode/atdc/add-command.md +6 -0
- package/commands/opencode/atdc/add-to-hc.md +6 -0
- package/commands/opencode/atdc/analysis-cls-log.md +6 -0
- package/commands/opencode/atdc/article.md +6 -0
- package/commands/opencode/atdc/bugfix.md +6 -0
- package/commands/opencode/atdc/code-simplifier.md +8 -0
- package/commands/opencode/atdc/commit.md +6 -0
- package/commands/opencode/atdc/compound.md +8 -0
- package/commands/opencode/atdc/context.md +6 -0
- package/commands/opencode/atdc/diagnose.md +8 -0
- package/commands/opencode/atdc/git-guardrails.md +18 -0
- package/commands/opencode/atdc/grill-with-docs.md +8 -0
- package/commands/opencode/atdc/handoff.md +8 -0
- package/commands/opencode/atdc/health.md +6 -0
- package/commands/opencode/atdc/learn.md +6 -0
- package/commands/opencode/atdc/review.md +7 -0
- package/commands/opencode/atdc/search.md +6 -0
- package/commands/opencode/atdc/strategy.md +6 -0
- package/commands/opencode/atdc/table2xlsx.md +6 -0
- package/commands/opencode/atdc/test-plan.md +6 -0
- package/commands/opencode/atdc/travel-planner.md +6 -0
- package/commands/opencode/atdc/xlsx2sql-insert.md +6 -0
- package/commands/opencode/atdc/xlsx2sql-update.md +6 -0
- package/commands/opencode/atdc/zoom-out.md +8 -0
- package/config/cls-defaults.json +19 -0
- package/hooks/atd-archive.sh +86 -0
- package/hooks/atd-state.sh +165 -0
- package/hooks/evidence-hook.sh +88 -0
- package/hooks/evidence-log.sh +54 -0
- package/hooks/guardrails.sh +65 -0
- package/hooks/handoff-compress.sh +104 -0
- package/hooks/phase-guard.sh +134 -0
- package/hooks/phase-write-guard.sh +96 -0
- package/hooks/ponytail-fix.sh +179 -0
- package/hooks/ponytail-guard.sh +66 -0
- package/hooks/ponytail-scan.sh +62 -0
- package/install.sh +276 -0
- package/package.json +20 -0
- package/packages/cls/bin/cls.js +146 -0
- package/packages/cls/config/defaults.json +17 -0
- package/packages/cls/mcp/server.js +142 -0
- package/packages/cls/package.json +29 -0
- package/packages/cls/scripts/postinstall.js +19 -0
- package/packages/cls/src/client.js +262 -0
- package/prompts/1-spec.md +271 -0
- package/prompts/1-update.md +198 -0
- package/prompts/2-qa.md +164 -0
- package/prompts/3-impl-bugfix.md +271 -0
- package/prompts/3-impl.md +419 -0
- package/prompts/4-verify.md +160 -0
- package/prompts/5-done.md +227 -0
- package/prompts/6-article.md +571 -0
- package/prompts/X-add-command.md +60 -0
- package/prompts/X-add-to-hc.md +178 -0
- package/prompts/X-analysis-cls-log.md +68 -0
- package/prompts/X-bugfix.md +10 -0
- package/prompts/X-code-simplifier.md +38 -0
- package/prompts/X-commit.md +116 -0
- package/prompts/X-compound.md +188 -0
- package/prompts/X-context-scan.md +101 -0
- package/prompts/X-diagnose.md +116 -0
- package/prompts/X-grill-with-docs.md +120 -0
- package/prompts/X-handoff.md +80 -0
- package/prompts/X-health.md +78 -0
- package/prompts/X-learn.md +151 -0
- package/prompts/X-review.md +417 -0
- package/prompts/X-search.md +74 -0
- package/prompts/X-strategy.md +95 -0
- package/prompts/X-table2xlsx.md +46 -0
- package/prompts/X-test-plan.md +436 -0
- package/prompts/X-travel-planner.md +22 -0
- package/prompts/X-xlsx2sql-insert.md +34 -0
- package/prompts/X-xlsx2sql-update.md +40 -0
- package/prompts/X-zoom-out.md +119 -0
- package/reference/diagnose-loop.md +66 -0
- package/reference/gates.md +90 -0
- package/reference/git-guardrails.md +71 -0
- package/reference/guards.md +275 -0
- package/reference/learn.md +100 -0
- package/reference/memory.md +190 -0
- package/reference/risk.md +74 -0
- package/reference/tdd.md +40 -0
- package/scripts/svg2png.js +46 -0
- package/src/cls/client.js +244 -0
- package/src/constants.js +49 -0
- package/src/evidence/ai-draft-metrics.js +157 -0
- package/src/evidence/canvas.js +184 -0
- package/src/evidence/index.js +98 -0
- package/src/evidence/sink-events.js +165 -0
- package/src/evidence/stage-timing.js +112 -0
- package/src/installers/claude.js +79 -0
- package/src/installers/codex.js +62 -0
- package/src/installers/opencode.js +72 -0
- package/src/manifest/ponytail-patterns.json +185 -0
- package/src/mcp/cls-mcp-server.js +142 -0
- package/src/utils.js +79 -0
- package/src/validators/state-validate.js +123 -0
- package/templates/.phase-state.json +13 -0
- package/templates/adr.md +62 -0
- package/templates/changelog.md +21 -0
- package/templates/concepts.md +35 -0
- package/templates/design.md +47 -0
- package/templates/handoff.md +64 -0
- package/templates/learn-skill.md +43 -0
- package/templates/progress.json +17 -0
- package/templates/proposal.md +29 -0
- package/templates/solution.md +36 -0
- package/templates/strategy.md +39 -0
- package/templates/tasks.md +41 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# TASK-238389_QYinVipLimit 交接文档
|
|
2
|
+
|
|
3
|
+
> 生成时间:2026-07-17 12:00
|
|
4
|
+
> 生成方式:/atd:handoff
|
|
5
|
+
> 源交接:`ticket_center_server.TASK-fxiQyin/docs/handoffs/TASK-238389_QYinVipLimit.md`
|
|
6
|
+
> 当前工作区:`/Users/lvkun/Documents/Codes/ticket_center_server.TASK-238389_QYinVipLimit`(分支 `TASK-238389_QYinVipLimit`)
|
|
7
|
+
> 会话来源:Q音绿钻发货失败静默成功排查 → 存量补发 → 错误语义修正 → `rights_grant_check` 并发/限流分析 → **切至此分支做限流改造**
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 做了什么
|
|
12
|
+
|
|
13
|
+
**动机**:Q音绿钻发货失败(如未映射码 `10101`)曾被当成功吞掉导致不重试/失货;线上 `rights_grant_check` 多 job 并发打满对方限流 300;需在本分支做 Q音 VIP 发放限流改造。
|
|
14
|
+
|
|
15
|
+
**结果**:前序 worktree(`TASK-fxiQyin`)已厘清失败语义与补单路径、补发 CLI/名单、64 路并发打到 300 的机制,并落地 Retry vs 永久失败语义;**本分支尚未开始限流代码/openspec**,当前即为限流改造起点。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 关键决策
|
|
20
|
+
|
|
21
|
+
| # | 决策 | 选了 | 排除了 | 原因 |
|
|
22
|
+
|---|------|------|--------|------|
|
|
23
|
+
| 1 | 发货失败是否一律 `return error` | 仅 `DeliveryStatusRetry(=3)` 返回 error | 所有非 Shipped 都 return error | `DeliveryQQVipStatusFail(=4)`「不允许发货」重试无意义,会打爆补单/限流 |
|
|
24
|
+
| 2 | 存量 800+ 单补发顺序 | 先补发再改 bug | 先上线错误语义再补发 | 业务要求先止血到账;补发以权益表验收,不信 `SendSvip` 返回值 |
|
|
25
|
+
| 3 | 补发方式 | CLI `resend_qmusic_svip` 读 CSV + 查 `user_ticket_rights` | 仅把 `sync_status` 打回 0 跑 `rights_check` | 旧逻辑失败也 `nil`,rights_check 会误推 sync 成功 |
|
|
26
|
+
| 4 | 限流改造落点 | 本分支 worktree `TASK-238389_QYinVipLimit` | 在 `TASK-fxiQyin` 上继续改 | 独立需求/分支,避免串工作区 |
|
|
27
|
+
|
|
28
|
+
### 状态语义(务必保持,限流改造勿破坏)
|
|
29
|
+
|
|
30
|
+
| Status | 常量 | 含义 | `handleQMusicGreenDiamond` |
|
|
31
|
+
|--------|------|------|----------------------------|
|
|
32
|
+
| 2 | `DeliveryStatusShipped` | 真成功 | 落库 + 短信 |
|
|
33
|
+
| 3 | `DeliveryStatusRetry` | 未映射/可重试(含原 10101 类) | `return error` 触发重试 |
|
|
34
|
+
| 4 | `DeliveryQQVipStatusFail` | 不允许发货(白名单码) | `return nil`,不重试、不落成功态 |
|
|
35
|
+
|
|
36
|
+
`parseDeliveryResponse`:成功→2;`isQMusicDeliveryNotAllowed`→4;其余未知码→3。
|
|
37
|
+
|
|
38
|
+
本分支已确认存在:`add_right_impl.go` 中 Retry→`fmt.Errorf(...)`;`qmusicResultStatus` 将 status=4 映射为 `ResultStatusFailed`。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 受影响模块
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
# 前序已改(来自 TASK-fxiQyin,本分支应已包含语义修复)
|
|
46
|
+
service/biz/rights/
|
|
47
|
+
└── add_right_impl.go — Retry 用响应字段构造 error(勿 wrap nil err);永久失败 return nil
|
|
48
|
+
service/biz/rights/member/
|
|
49
|
+
└── qmusic_provider.go — status=4 → ResultStatusFailed
|
|
50
|
+
service/rpc/
|
|
51
|
+
└── qmusic_direct_vip_impl.go — 未知码→DeliveryStatusRetry;callTencentMusicAPI 为限流落点候选
|
|
52
|
+
|
|
53
|
+
cli/cmd/rights/
|
|
54
|
+
├── resend_qmusic_svip.go — CSV 补发;以 QY right_type=4 + status=1 验收
|
|
55
|
+
├── rights_grant_check.go — 延迟发放扫表;8 库×每库8表;无客户端限流(问题源)
|
|
56
|
+
└── rights_check.go — sync_status=0 补全链路;SendSvip nil 会推进 sync=1
|
|
57
|
+
cli/
|
|
58
|
+
└── register.go — 注册 resend_qmusic_svip
|
|
59
|
+
|
|
60
|
+
# 本分支待改(限流)
|
|
61
|
+
service/redis/
|
|
62
|
+
├── redis_rate.go — 已有 RateLimiter.AllowPerSecond(可复用)
|
|
63
|
+
└── redis_limit.go — 计数限流备选
|
|
64
|
+
service/rpc/
|
|
65
|
+
└── qmusic_direct_vip_impl.go / local.go(SendSvip) — 限流挂载点待定
|
|
66
|
+
|
|
67
|
+
docs/bugfix/
|
|
68
|
+
└── ticket-QQ音乐绿钻发货静默失败-ISSUE-0027.md
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 相关路径 / 资产
|
|
72
|
+
|
|
73
|
+
| 项 | 路径 |
|
|
74
|
+
|----|------|
|
|
75
|
+
| **限流改造 worktree(当前)** | `/Users/lvkun/Documents/Codes/ticket_center_server.TASK-238389_QYinVipLimit` |
|
|
76
|
+
| 前序语义修复 worktree | `/Users/lvkun/Documents/Codes/ticket_center_server.TASK-fxiQyin` |
|
|
77
|
+
| 补发名单 | `/Users/lvkun/Documents/kugou/userid_orderno.csv`(872 行,已校验可解析) |
|
|
78
|
+
| openspec change | **尚无** `openspec/changes/*238389*` / `*QYinVip*` — 需新建 |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 测试状态
|
|
83
|
+
|
|
84
|
+
| 覆盖范围 | 状态 | 说明 |
|
|
85
|
+
|---------|------|------|
|
|
86
|
+
| 单元测试 | ⚠️ | Retry wrap-nil / 永久失败不重试等语义变更未见专门单测补齐 |
|
|
87
|
+
| CSV 解析 | ✅ | 872 行 PARSE_OK,无重复/坏行 |
|
|
88
|
+
| 存量补发 | ⚠️ | CLI 已就绪;是否全量跑完、失败清单需接手确认 |
|
|
89
|
+
| 限流改造 | ❌ | 本分支目标功能,**尚未开始**(无 openspec、无代码 diff) |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 未解决问题
|
|
94
|
+
|
|
95
|
+
- **Q音侧限流 300(本分支主目标)**:`rights_grant_check` 8 job×每库 8 协程 = **64 路并发**,无本地限流;对方 RTT~200ms 时理论 QPS≈320,已实锤可触发限流。应加共享令牌桶/间隔或降并发,目标峰值 **<300**(建议 200~250)。
|
|
96
|
+
- **永久失败 return nil 与 RightsCheck**:`status=4` 不重试合理,但 `rights_check` 若走 SendSvip 得 nil 仍可能把订单 `sync_status` 推成成功——会员永久失败时其它权益仍前进;是否要落「失败态权益记录」便于对账未定。
|
|
97
|
+
- **aiconsult #366882**:审查建议「非成功一律 return error」与业务决策冲突;已按「仅 Retry 抛错」落地,相关 MR 需说明。
|
|
98
|
+
- **补发 CLI 内对 SendSvip error 仅 Warn**:验收靠权益表;限流改造后建议失败也写入 fail-out 并可选尊重 error。
|
|
99
|
+
- **存量补发是否跑完**:需查 `resend_ok/fail/skip` CSV,失败单用 fail 名单重跑。
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 下一步
|
|
104
|
+
|
|
105
|
+
1. **本目录已是正确 worktree**(`TASK-238389_QYinVipLimit`)— 勿再切回 `TASK-fxiQyin` 写限流。
|
|
106
|
+
2. **按 openspec 流程起规格**:`/opsx:propose`(或项目 flow)写清限流方案;候选挂载点:`callTencentMusicAPI` / `SendSvip` / `processPendingRight`;优先复用 `service/redis/redis_rate.go` 的 `RateLimiter`(跨 job 全局生效)。
|
|
107
|
+
3. **并发调参备选(改代码前可先运维止血)**:减 job 或降 `-w`(例如每库 `-w=2` → 总并发≈16);代码侧共享限流器更稳。
|
|
108
|
+
4. **确认存量补发**:查 `resend_ok/fail/skip`;失败单重跑。
|
|
109
|
+
5. **可选**:为 `handleQMusicGreenDiamond` Retry/永久失败分支补单测,防止再次把 4 当成可重试。
|
|
110
|
+
|
|
111
|
+
### `rights_grant_check` 并发速查(线上)
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
命令:bin/cli rights_grant_check -t=1000 -i={0..7} -w=10
|
|
115
|
+
配置:ticket_user sharding_db=8, sharding_table=64 → 每库 8 表
|
|
116
|
+
实际:每 job 8 协程(不是 10);8 job 合计 64 协程;表内串行
|
|
117
|
+
峰值 QPS ≈ 64 / 单次耗时
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 相关资源
|
|
123
|
+
|
|
124
|
+
- Bugfix:`docs/bugfix/ticket-QQ音乐绿钻发货静默失败-ISSUE-0027.md`
|
|
125
|
+
- 审查事件:aiconsult #168343 / Bug 366873(wrap nil err)、366882(非成功 return nil)
|
|
126
|
+
- 样本订单:`12607131000330004220001010395` / userid `2534695307`
|
|
127
|
+
- 已有限流设施:`service/redis/redis_rate.go`(`RateLimiter.AllowPerSecond`)、`service/redis/redis_limit.go`
|
|
128
|
+
- 源交接(前序会话):`/Users/lvkun/Documents/Codes/ticket_center_server.TASK-fxiQyin/docs/handoffs/TASK-238389_QYinVipLimit.md`
|
package/central/ticket_center_server/.test_plans/TASK-227254-SupportMembership-self-test-plan.md
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# TASK-227254-SupportMembership 自测计划(可执行版)
|
|
2
|
+
|
|
3
|
+
> 所有命令可直接复制运行。变量 `<HOST>` 替换为测试环境地址(如 `http://ticketcenter-test.kgidc.cn`)。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 第零步:测试前准备
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. 预置 SKU 数据
|
|
11
|
+
mysql> INSERT INTO membership_product_sku (sku_code, name, icon, platform, member_type, vip_type, list_price, sale_price, status, sort, sale_start_time, sale_end_time, create_time, update_time)
|
|
12
|
+
VALUES ('test-kg-vip-month', '酷狗VIP月卡', 'https://img.example.com/kg.png', 101, 1, 1001, 1800, 1500, 1, 100, '2025-01-01', '2027-12-31', NOW(), NOW());
|
|
13
|
+
INSERT INTO membership_product_sku (sku_code, name, icon, platform, member_type, vip_type, list_price, sale_price, status, sort, sale_start_time, sale_end_time, create_time, update_time)
|
|
14
|
+
VALUES ('test-qm-vip-month', 'Q音绿钻月卡', 'https://img.example.com/qm.png', 102, 1, 2001, 1800, 1500, 1, 100, '2025-01-01', '2027-12-31', NOW(), NOW());
|
|
15
|
+
INSERT INTO membership_product_sku (sku_code, name, icon, platform, member_type, vip_type, list_price, sale_price, status, sort, sale_start_time, sale_end_time, create_time, update_time)
|
|
16
|
+
VALUES ('test-kw-vip-month', '酷我VIP月卡', 'https://img.example.com/kw.png', 103, 1, 3001, 1800, 1500, 1, 100, '2025-01-01', '2027-12-31', NOW(), NOW());
|
|
17
|
+
|
|
18
|
+
# 2. 记录测试 userid(后续命令会用 ***)
|
|
19
|
+
# 酷狗 userid: XXXXXXXXX(需替换)
|
|
20
|
+
# Q音 userid: YYYYYYYYY(需替换)
|
|
21
|
+
# 酷我 userid: ZZZZZZZZZ(需替换)
|
|
22
|
+
|
|
23
|
+
# 3. 记下一个全局变量方便后续命令复用
|
|
24
|
+
export HOST=http://127.0.0.1:8080
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 链 1:商品查询
|
|
30
|
+
|
|
31
|
+
### 1.1 正常查询
|
|
32
|
+
```bash
|
|
33
|
+
curl -s "${HOST}/membership/product/list?performance_id=474134&app_type=1" | jq '.list | length'
|
|
34
|
+
# 预期:>0,返回酷狗平台 SKU
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 1.2 Q音平台过滤
|
|
38
|
+
```bash
|
|
39
|
+
curl -s "${HOST}/membership/product/list?performance_id=474134&app_type=2" | jq '.list[] | select(.platform==101)'
|
|
40
|
+
# 预期:空(不返回酷狗 SKU)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 1.3 无匹配演出
|
|
44
|
+
```bash
|
|
45
|
+
curl -s "${HOST}/membership/product/list?performance_id=99999999&app_type=1" | jq '.list'
|
|
46
|
+
# 预期:[],popup 字段存在
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 1.4 Redis 缓存验证
|
|
50
|
+
```bash
|
|
51
|
+
# 连续调两次,查看服务日志
|
|
52
|
+
curl -s "${HOST}/membership/product/list?performance_id=474134&app_type=1" > /dev/null
|
|
53
|
+
sleep 1
|
|
54
|
+
curl -s "${HOST}/membership/product/list?performance_id=474134&app_type=1" > /dev/null
|
|
55
|
+
# 查日志:第一次有 "membership GetProductList loaded, platform:xxx count:N"
|
|
56
|
+
# 第二次无 DB 查询日志(命中缓存)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 1.5 售卖窗口外
|
|
60
|
+
```sql
|
|
61
|
+
UPDATE membership_product_sku SET sale_end_time = '2025-01-02 00:00:00' WHERE sku_code = 'test-kg-vip-month';
|
|
62
|
+
```
|
|
63
|
+
```bash
|
|
64
|
+
curl -s "${HOST}/membership/product/list?performance_id=474134&app_type=1" | jq '.list[] | select(.sku_code=="test-kg-vip-month")'
|
|
65
|
+
# 预期:空
|
|
66
|
+
```
|
|
67
|
+
```sql
|
|
68
|
+
-- 恢复
|
|
69
|
+
UPDATE membership_product_sku SET sale_end_time = '2027-12-31 00:00:00' WHERE sku_code = 'test-kg-vip-month';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 链 2:订单创建
|
|
75
|
+
|
|
76
|
+
### 2.1 正常下单(记录 orderNo 供后续使用)
|
|
77
|
+
```bash
|
|
78
|
+
# 酷狗用户下单
|
|
79
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
80
|
+
-H 'Content-Type: application/json' \
|
|
81
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"test-kg-vip-month","num":1,"ext":"{}"}' | jq .
|
|
82
|
+
# 预期:{"order_no":"...", "price_total":1500, "expire_time":"2026-..."}
|
|
83
|
+
# ⚠️ 记下 order_no → 暴露 ORD_KG_1="..."
|
|
84
|
+
```
|
|
85
|
+
```bash
|
|
86
|
+
# Q音用户下单
|
|
87
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
88
|
+
-H 'Content-Type: application/json' \
|
|
89
|
+
-d '{"userid":YYYYYYYYY,"app_type":2,"sku_code":"test-qm-vip-month","num":1,"ext":"{\"c_field\":\"1_2\"}"}' | jq .
|
|
90
|
+
# ⚠️ 记下 order_no → 暴露 ORD_QM_1="..."
|
|
91
|
+
```
|
|
92
|
+
```bash
|
|
93
|
+
# 酷我用户下单
|
|
94
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
95
|
+
-H 'Content-Type: application/json' \
|
|
96
|
+
-d '{"userid":ZZZZZZZZZ,"app_type":3,"sku_code":"test-kw-vip-month","num":1,"ext":""}' | jq .
|
|
97
|
+
# ⚠️ 记下 order_no → 暴露 ORD_KW_1="..."
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2.2 多数量下单
|
|
101
|
+
```bash
|
|
102
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
103
|
+
-H 'Content-Type: application/json' \
|
|
104
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"test-kg-vip-month","num":3}' | jq .
|
|
105
|
+
# 预期:price_total=4500(=1500*3)
|
|
106
|
+
# ⚠️ 记下 order_no → 暴露 ORD_KG_NUM3="..."
|
|
107
|
+
```
|
|
108
|
+
```sql
|
|
109
|
+
SELECT order_no, num, price_payable FROM membership_order WHERE order_no='${ORD_KG_NUM3}';
|
|
110
|
+
-- 预期:num=3, price_payable=4500
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2.3 Ext 透传
|
|
114
|
+
```sql
|
|
115
|
+
SELECT ext FROM membership_order WHERE order_no='${ORD_QM_1}';
|
|
116
|
+
-- 预期:{"c_field":"1_2"}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2.4 SKU 不存在
|
|
120
|
+
```bash
|
|
121
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
122
|
+
-H 'Content-Type: application/json' \
|
|
123
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"bad-sku-not-exist"}' | jq .
|
|
124
|
+
# 预期:返回 20504(MembershipSkuNotAvailableErr)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2.5 平台不匹配
|
|
128
|
+
```bash
|
|
129
|
+
# 用酷狗用户买 Q音 SKU
|
|
130
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
131
|
+
-H 'Content-Type: application/json' \
|
|
132
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"test-qm-vip-month"}' | jq .
|
|
133
|
+
# 预期:返回 20504(MembershipSkuNotAvailableErr,sku platform=102 ≠ app_type=1)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2.6 重复下单保护
|
|
137
|
+
```bash
|
|
138
|
+
# 先完成一次下单
|
|
139
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
140
|
+
-H 'Content-Type: application/json' \
|
|
141
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"test-kg-vip-month"}' | jq .
|
|
142
|
+
# 记下 order_no → ORD_KG_DUP="..."
|
|
143
|
+
```
|
|
144
|
+
```sql
|
|
145
|
+
-- 手工制造"已支付"假数据
|
|
146
|
+
UPDATE membership_order SET order_status=2, pay_status=2, version=1, price_paid=1500, paid_at=NOW() WHERE order_no='${ORD_KG_DUP}';
|
|
147
|
+
```
|
|
148
|
+
```bash
|
|
149
|
+
# 再次检查重复
|
|
150
|
+
curl -s "${HOST}/membership/order/repeat_check?userid=XXXXXXXXX&app_type=1" | jq .
|
|
151
|
+
# 预期:need_block=true, product_name 非空
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 2.7 参数校验
|
|
155
|
+
```bash
|
|
156
|
+
# userid=0
|
|
157
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
158
|
+
-H 'Content-Type: application/json' \
|
|
159
|
+
-d '{"userid":0,"app_type":1,"sku_code":"test-kg-vip-month"}' | jq .
|
|
160
|
+
```
|
|
161
|
+
```bash
|
|
162
|
+
# 不传 sku_code
|
|
163
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
164
|
+
-H 'Content-Type: application/json' \
|
|
165
|
+
-d '{"userid":XXXXXXXXX,"app_type":1}' | jq .
|
|
166
|
+
# 两个预期都返回 ParamErr
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 链 3:支付
|
|
172
|
+
|
|
173
|
+
### 3.1 发起支付
|
|
174
|
+
```bash
|
|
175
|
+
curl -s -X POST "${HOST}/membership/order/pay" \
|
|
176
|
+
-H 'Content-Type: application/json' \
|
|
177
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"order_no":"${ORD_KG_1}","pay_type":1,"openid":"test-openid-xxx"}' | jq .
|
|
178
|
+
# 预期:返回 pay_data 对象
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 3.2 iOS → 小程序支付
|
|
182
|
+
```bash
|
|
183
|
+
# 需要 middleware 注入 client_code 到 context,如果本地无法注入,改为直接调用 inner 接口验证日志:
|
|
184
|
+
# 把订单状态重置为待支付
|
|
185
|
+
mysql> UPDATE membership_order SET order_status=1, pay_status=1, version=version+1 WHERE order_no='${ORD_KG_1}';
|
|
186
|
+
# 修改 model 验证代码后通过日志验证:
|
|
187
|
+
# grep "PayOrder WxMiniprogramPay req" /path/to/log
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 3.5 iOS 无 Openid 报错
|
|
191
|
+
```bash
|
|
192
|
+
# 需要 client_code 含 "ios"。本地不好模拟,可以在 PayOrder 方法里打 log 确认分支。
|
|
193
|
+
# 替代验证:修改代码临时让 isIOS=true,不传 openid,应返回 ParamErr
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 3.6 Q音 BizAppID
|
|
197
|
+
```bash
|
|
198
|
+
curl -s -X POST "${HOST}/membership/order/pay" \
|
|
199
|
+
-H 'Content-Type: application/json' \
|
|
200
|
+
-d '{"userid":YYYYYYYYY,"app_type":2,"order_no":"${ORD_QM_1}","pay_type":1,"openid":"test-openid-xxx"}' | jq .
|
|
201
|
+
# 日志关键词:grep "qqmusic_biz_key"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 3.7 酷狗 BizAppID
|
|
205
|
+
```bash
|
|
206
|
+
curl -s -X POST "${HOST}/membership/order/pay" \
|
|
207
|
+
-H 'Content-Type: application/json' \
|
|
208
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"order_no":"${ORD_KG_1}","pay_type":1,"openid":"test-openid-xxx"}' | jq .
|
|
209
|
+
# 日志关键词:grep "kugou_biz_key"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 3.8 支付回调
|
|
213
|
+
```bash
|
|
214
|
+
# 先确保订单 order_status=1(待支付)
|
|
215
|
+
mysql> UPDATE membership_order SET order_status=1, pay_status=1, version=version+1 WHERE order_no='${ORD_KG_1}';
|
|
216
|
+
```
|
|
217
|
+
```bash
|
|
218
|
+
curl -s "${HOST}/inner/v1/membership/order/pay_notify?order_no=${ORD_KG_1}&appid=123&sign=abc&out_trade_no=TEST_TRADE_001&trade_status=1"
|
|
219
|
+
# 预期:返回 "success"
|
|
220
|
+
```
|
|
221
|
+
```sql
|
|
222
|
+
SELECT order_status, pay_status, price_paid FROM membership_order WHERE order_no='${ORD_KG_1}';
|
|
223
|
+
-- 预期:order_status=2(Paid), pay_status=2
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 3.9 重复回调幂等
|
|
227
|
+
```bash
|
|
228
|
+
curl -s "${HOST}/inner/v1/membership/order/pay_notify?order_no=${ORD_KG_1}&appid=123&sign=abc&out_trade_no=TEST_TRADE_001&trade_status=1"
|
|
229
|
+
# 预期:仍返回 "success",不报错
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 链 4:履约发放
|
|
235
|
+
|
|
236
|
+
### 4.1 手动触发履约
|
|
237
|
+
```bash
|
|
238
|
+
# 酷狗订单(先确认已支付:order_status=2)
|
|
239
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
240
|
+
-H 'Content-Type: application/json' \
|
|
241
|
+
-d "{\"order_no\":\"${ORD_KG_1}\"}"
|
|
242
|
+
# 预期:返回成功
|
|
243
|
+
```
|
|
244
|
+
```sql
|
|
245
|
+
SELECT order_status, completed_at FROM membership_order WHERE order_no='${ORD_KG_1}';
|
|
246
|
+
-- 预期:order_status=4(Completed)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 4.2 酷狗会员发放日志
|
|
250
|
+
```bash
|
|
251
|
+
grep "kugou Grant VipCreateOrder req" /path/to/log | tail -1
|
|
252
|
+
grep "kugou Grant VipCreateOrder resp" /path/to/log | tail -1
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 4.3 Q音会员发放日志
|
|
256
|
+
```bash
|
|
257
|
+
# 先让 Q音订单也变成已支付再调 fulfill
|
|
258
|
+
mysql> UPDATE membership_order SET order_status=2, pay_status=2, version=version+1, price_paid=1500, paid_at=NOW() WHERE order_no='${ORD_QM_1}';
|
|
259
|
+
```
|
|
260
|
+
```bash
|
|
261
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
262
|
+
-H 'Content-Type: application/json' \
|
|
263
|
+
-d "{\"order_no\":\"${ORD_QM_1}\"}"
|
|
264
|
+
grep "qmusic Grant" /path/to/log | tail -2
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 4.4 酷我会员发放日志
|
|
268
|
+
```bash
|
|
269
|
+
mysql> UPDATE membership_order SET order_status=2, pay_status=2, version=version+1, price_paid=1500, paid_at=NOW() WHERE order_no='${ORD_KW_1}';
|
|
270
|
+
```
|
|
271
|
+
```bash
|
|
272
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
273
|
+
-H 'Content-Type: application/json' \
|
|
274
|
+
-d "{\"order_no\":\"${ORD_KW_1}\"}"
|
|
275
|
+
grep "kuwo Grant" /path/to/log | tail -2
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 4.5 发放幂等
|
|
279
|
+
```bash
|
|
280
|
+
# 再调一次同一个 order_no
|
|
281
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
282
|
+
-H 'Content-Type: application/json' \
|
|
283
|
+
-d "{\"order_no\":\"${ORD_KG_1}\"}"
|
|
284
|
+
grep "fulfillment skipped by idempotency" /path/to/log | tail -1
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 4.6 数量×发放周期
|
|
288
|
+
```bash
|
|
289
|
+
# 用多数量订单 ORD_KG_NUM3(num=3, member_type=1 月卡)
|
|
290
|
+
mysql> UPDATE membership_order SET order_status=2, pay_status=2, version=version+1, price_paid=4500, paid_at=NOW() WHERE order_no='${ORD_KG_NUM3}';
|
|
291
|
+
```
|
|
292
|
+
```bash
|
|
293
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
294
|
+
-H 'Content-Type: application/json' \
|
|
295
|
+
-d "{\"order_no\":\"${ORD_KG_NUM3}\"}"
|
|
296
|
+
grep "kugou Grant VipCreateOrder req" /path/to/log | tail -1 | grep "month"
|
|
297
|
+
# 预期:month=3(月卡×3 份=3 个月)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 4.7 发放失败重试
|
|
301
|
+
```bash
|
|
302
|
+
# 构造一条新订单并支付
|
|
303
|
+
curl -s -X POST "${HOST}/membership/order/create" \
|
|
304
|
+
-H 'Content-Type: application/json' \
|
|
305
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"test-kg-vip-month","num":1}' | jq -r '.order_no'
|
|
306
|
+
# 记下 → EXPORT ORD_KG_FAIL1="..."
|
|
307
|
+
mysql> UPDATE membership_order SET order_status=2, pay_status=2, version=version+1, price_paid=1500, paid_at=NOW() WHERE order_no='${ORD_KG_FAIL1}';
|
|
308
|
+
```
|
|
309
|
+
```bash
|
|
310
|
+
# 触发履约(如酷狗 RPC 不可达会失败)
|
|
311
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
312
|
+
-H 'Content-Type: application/json' \
|
|
313
|
+
-d "{\"order_no\":\"${ORD_KG_FAIL1}\"}"
|
|
314
|
+
```
|
|
315
|
+
```sql
|
|
316
|
+
SELECT o.order_status, f.fulfillment_status, f.retry_strategy, f.next_retry_at
|
|
317
|
+
FROM membership_order o
|
|
318
|
+
JOIN membership_order_fulfillment f ON o.order_no = f.order_no
|
|
319
|
+
WHERE o.order_no = '${ORD_KG_FAIL1}';
|
|
320
|
+
-- 预期:fulfillment_status=3, retry_strategy=1, next_retry_at > NOW()
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 4.8 履约状态锁
|
|
324
|
+
```bash
|
|
325
|
+
# 同一订单同时发两个 fulfill 请求(用 & 后台)
|
|
326
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
327
|
+
-H 'Content-Type: application/json' \
|
|
328
|
+
-d "{\"order_no\":\"${ORD_KG_1}\"}" &
|
|
329
|
+
curl -s -X POST "${HOST}/inner/v1/membership/order/fulfill" \
|
|
330
|
+
-H 'Content-Type: application/json' \
|
|
331
|
+
-d "{\"order_no\":\"${ORD_KG_1}\"}" &
|
|
332
|
+
wait
|
|
333
|
+
# 日志中一个 "state locked for grant",另一个 "already processing and not timeout"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 链 5:履约失败重试 Cron
|
|
339
|
+
|
|
340
|
+
### 5.1 重试扫描
|
|
341
|
+
```bash
|
|
342
|
+
# 确保 4.7 留下了失败记录,然后手动改 next_retry_at 为过去
|
|
343
|
+
mysql> UPDATE membership_order_fulfillment SET next_retry_at = '2020-01-01 00:00:00', retry_strategy=1 WHERE order_no='${ORD_KG_FAIL1}';
|
|
344
|
+
```
|
|
345
|
+
```bash
|
|
346
|
+
# 启动 cron
|
|
347
|
+
./cli membership_fulfill_retry -l=10 -k=lock_test_fulfill_retry 2>&1 | tail -5
|
|
348
|
+
# 预期:日志 "membership fulfill retry success" 或 "deferred again"
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 5.2 处理中超时
|
|
352
|
+
```sql
|
|
353
|
+
-- 模拟一条卡在 Processing 且超时(>15 分钟前开始)的记录
|
|
354
|
+
UPDATE membership_order_fulfillment
|
|
355
|
+
SET fulfillment_status = 1, processing_started_at = DATE_SUB(NOW(), INTERVAL 20 MINUTE)
|
|
356
|
+
WHERE order_no = '${ORD_KG_FAIL1}';
|
|
357
|
+
```
|
|
358
|
+
```bash
|
|
359
|
+
./cli membership_fulfill_retry -l=10 -k=lock_test_fulfill_retry 2>&1 | tail -3
|
|
360
|
+
# 预期:该记录被重新触发
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### 5.3 未到重试时间
|
|
364
|
+
```sql
|
|
365
|
+
UPDATE membership_order_fulfillment SET next_retry_at = DATE_ADD(NOW(), INTERVAL 1 HOUR), fulfillment_status=3 WHERE order_no='${ORD_KG_FAIL1}';
|
|
366
|
+
```
|
|
367
|
+
```bash
|
|
368
|
+
./cli membership_fulfill_retry -l=10 -k=lock_test_fulfill_retry 2>&1 | tail -3
|
|
369
|
+
# 预期:日志 "finished, retryable: 0"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## 链 6:业务方通知
|
|
375
|
+
|
|
376
|
+
### 6.1 履约成功后通知
|
|
377
|
+
```sql
|
|
378
|
+
-- 找一个已完成履约的订单
|
|
379
|
+
SELECT order_no, biz_notify_status FROM membership_order WHERE order_no='${ORD_KG_1}';
|
|
380
|
+
-- 预期:biz_notify_status=1(只要通知接口可达)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 6.2 通知失败吞错误
|
|
384
|
+
```bash
|
|
385
|
+
# 构造新订单走完 链2→链3→链4 流程,如果回调接口失败(本地环境降级验证):
|
|
386
|
+
```
|
|
387
|
+
```sql
|
|
388
|
+
-- 手工修改为"已完成但未通知"
|
|
389
|
+
UPDATE membership_order SET biz_notify_status=0, biz_notify_retry_count=0, biz_notify_next_retry_at='1970-01-01 00:00:00' WHERE order_no='${ORD_KG_1}';
|
|
390
|
+
```
|
|
391
|
+
```bash
|
|
392
|
+
# 通过 RetryBizNotify 触发的日志验证:
|
|
393
|
+
grep "biz notify callback failed" /path/to/log | tail -1
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 6.3 通知重试 cron
|
|
397
|
+
```sql
|
|
398
|
+
-- 制造一条待通知订单
|
|
399
|
+
UPDATE membership_order SET biz_notify_status=0, biz_notify_retry_count=0, biz_notify_next_retry_at='2020-01-01 00:00:00' WHERE order_status=4 AND order_no='${ORD_KG_1}';
|
|
400
|
+
```
|
|
401
|
+
```bash
|
|
402
|
+
./cli membership_biz_notify_retry -l=10 2>&1 | tail -5
|
|
403
|
+
# 预期:日志 "biz notify retry finished, count:1"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 6.4 重试间隔递增
|
|
407
|
+
```sql
|
|
408
|
+
-- 模拟第一次重试失败,把 next_retry_at 改过去
|
|
409
|
+
UPDATE membership_order SET biz_notify_retry_count=0, biz_notify_next_retry_at='2020-01-01 00:00:00', biz_notify_status=0 WHERE order_no='${ORD_KG_1}';
|
|
410
|
+
```
|
|
411
|
+
```bash
|
|
412
|
+
./cli membership_biz_notify_retry -l=10 2>&1 | tail -3
|
|
413
|
+
```
|
|
414
|
+
```sql
|
|
415
|
+
SELECT biz_notify_retry_count, biz_notify_next_retry_at FROM membership_order WHERE order_no='${ORD_KG_1}';
|
|
416
|
+
-- 预期:retry_count=1, next_retry_at ≈ NOW()+30s(Apollo 默认间隔)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 6.5 超过最大重试次数
|
|
420
|
+
```sql
|
|
421
|
+
-- 手动设满
|
|
422
|
+
UPDATE membership_order SET biz_notify_retry_count=99, biz_notify_next_retry_at='2020-01-01 00:00:00' WHERE order_no='${ORD_KG_1}';
|
|
423
|
+
```
|
|
424
|
+
```bash
|
|
425
|
+
./cli membership_biz_notify_retry -l=10 2>&1 | tail -3
|
|
426
|
+
```
|
|
427
|
+
```sql
|
|
428
|
+
SELECT biz_notify_retry_count FROM membership_order WHERE order_no='${ORD_KG_1}';
|
|
429
|
+
-- 预期:count 不变(≥ 5 = Apollo max,不再重试)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### 6.6 通知请求体映射
|
|
433
|
+
```bash
|
|
434
|
+
grep "biz notify request built" /path/to/log | tail -1
|
|
435
|
+
# 预期日志:user_id=XXXXXXXXX, quantity=1, product_id=test-kg-vip-month
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 6.7 Extend 原样透传
|
|
439
|
+
```bash
|
|
440
|
+
grep "MembershipBizNotifyCallback" /path/to/log | tail -3
|
|
441
|
+
# 检查请求 body 中的 Extend 字段是否和 order.Ext 一致
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## 链 7:订单管理
|
|
447
|
+
|
|
448
|
+
### 7.1 查询状态
|
|
449
|
+
```bash
|
|
450
|
+
curl -s "${HOST}/membership/order/query_status?userid=XXXXXXXXX&app_type=1&order_no=${ORD_KG_1}" | jq .
|
|
451
|
+
# 预期:{"status":4}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### 7.2 取消订单(需先有未支付订单)
|
|
455
|
+
```bash
|
|
456
|
+
# 创建一条新订单
|
|
457
|
+
ORD_KG_CANCEL=$(curl -s -X POST "${HOST}/membership/order/create" \
|
|
458
|
+
-H 'Content-Type: application/json' \
|
|
459
|
+
-d '{"userid":XXXXXXXXX,"app_type":1,"sku_code":"test-kg-vip-month","num":1}' | jq -r '.order_no')
|
|
460
|
+
echo "ORD_KG_CANCEL=${ORD_KG_CANCEL}"
|
|
461
|
+
```
|
|
462
|
+
```bash
|
|
463
|
+
curl -s -X POST "${HOST}/membership/order/cancel" \
|
|
464
|
+
-H 'Content-Type: application/json' \
|
|
465
|
+
-d "{\"userid\":XXXXXXXXX,\"app_type\":1,\"order_no\":\"${ORD_KG_CANCEL}\"}" | jq .
|
|
466
|
+
```
|
|
467
|
+
```sql
|
|
468
|
+
SELECT order_status, pay_status FROM membership_order WHERE order_no='${ORD_KG_CANCEL}';
|
|
469
|
+
-- 预期:order_status=5, pay_status=4
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### 7.3 已支付不能取消
|
|
473
|
+
```bash
|
|
474
|
+
curl -s -X POST "${HOST}/membership/order/cancel" \
|
|
475
|
+
-H 'Content-Type: application/json' \
|
|
476
|
+
-d "{\"userid\":XXXXXXXXX,\"app_type\":1,\"order_no\":\"${ORD_KG_1}\"}" | jq .
|
|
477
|
+
# 预期:返回 MembershipOrderStatusInvalidErr
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 7.4 订单详情
|
|
481
|
+
```bash
|
|
482
|
+
curl -s "${HOST}/membership/order/detail?userid=XXXXXXXXX&app_type=1&order_no=${ORD_KG_1}" | jq .
|
|
483
|
+
# 预期:product_name、icon、price_total、order_status 都和 DB 一致
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### 7.5 订单列表
|
|
487
|
+
```bash
|
|
488
|
+
curl -s "${HOST}/membership/order/list?userid=XXXXXXXXX&app_type=1&page=1&page_size=5" | jq '{has_more,count: (.list|length)}'
|
|
489
|
+
# 预期:有记录,has_more 正确
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### 7.6 数量展示
|
|
493
|
+
```bash
|
|
494
|
+
curl -s "${HOST}/membership/order/detail?userid=XXXXXXXXX&app_type=1&order_no=${ORD_KG_NUM3}" | jq '.order | {quantity, price_total}'
|
|
495
|
+
# 预期:quantity=3
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## 链 8:会员优先购权限
|
|
501
|
+
|
|
502
|
+
### 8.1 未登录
|
|
503
|
+
```bash
|
|
504
|
+
curl -s "${HOST}/v1/performance/detail?performance_id=474134&app_type=1&userid=0" | jq '.performance_vip_sale_permission'
|
|
505
|
+
# 预期:{"eligible":false,"reason":"not_logged_in"} 或 null(无配置时)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### 8.2 平台不匹配
|
|
509
|
+
```bash
|
|
510
|
+
curl -s "${HOST}/v1/performance/detail?performance_id=474134&app_type=1&userid=XXXXXXXXX" | jq '.performance_vip_sale_permission'
|
|
511
|
+
# 检查 reason 字段(有配置时可能是 eligible 或 platform_mismatch)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### 8.3 非 VIP 用户
|
|
515
|
+
```bash
|
|
516
|
+
# 用一个不是 VIP 的普通用户查
|
|
517
|
+
curl -s "${HOST}/v1/performance/detail?performance_id=474134&app_type=1&userid=999999999" | jq '.performance_vip_sale_permission'
|
|
518
|
+
# 预期:reason 为 not_eligible 或 check_degraded(取决于 RPC 是否可达)
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### 8.4 无 VIP 配置演出
|
|
522
|
+
```bash
|
|
523
|
+
curl -s "${HOST}/v1/performance/detail?performance_id=474134&app_type=1&userid=XXXXXXXXX" | jq 'keys | contains(["performance_vip_sale_config"])'
|
|
524
|
+
# 如果演出未配置优先购,预期:false(字段不下发)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## 链 9:日志验证
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
# 全部链跑完后,汇总 grep 检查
|
|
533
|
+
echo "=== 下单 ==="
|
|
534
|
+
grep "CreateOrder success" /path/to/log | tail -3
|
|
535
|
+
|
|
536
|
+
echo "=== 支付 RPC ==="
|
|
537
|
+
grep "PayOrder.*Pay.*req\|PayOrder.*Pay.*resp" /path/to/log | tail -4
|
|
538
|
+
|
|
539
|
+
echo "=== 履约发放 RPC ==="
|
|
540
|
+
grep "Grant .*req\|Grant .*resp" /path/to/log | tail -6
|
|
541
|
+
|
|
542
|
+
echo "=== 履约步骤 ==="
|
|
543
|
+
grep "fulfillment prepare start\|fulfillment prepared\|fulfillment finalize success" /path/to/log | tail -5
|
|
544
|
+
|
|
545
|
+
echo "=== 业务通知 ==="
|
|
546
|
+
grep "biz notify" /path/to/log | tail -5
|
|
547
|
+
|
|
548
|
+
echo "=== Cron 重试 ==="
|
|
549
|
+
grep "membership.*retry finished" /path/to/log | tail -3
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 测试完成检查
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
# 最终编译验证
|
|
558
|
+
go build ./...
|
|
559
|
+
|
|
560
|
+
# 最终单元测试
|
|
561
|
+
go test ./service/biz/membership/... -count=1
|
|
562
|
+
|
|
563
|
+
# ⚠️ 清理测试数据(可选)
|
|
564
|
+
mysql> DELETE FROM membership_order WHERE order_no LIKE 'MC%' AND user_id IN (XXXXXXXXX, YYYYYYYYY, ZZZZZZZZZ);
|
|
565
|
+
mysql> DELETE FROM membership_order_fulfillment WHERE order_no LIKE 'MC%';
|
|
566
|
+
```
|