feat/asset_material_relations #2

Merged
xiaoyu merged 102 commits from feat/asset_material_relations into dev 2026-06-15 17:17:04 +00:00
Owner

合并代码

合并代码
xiaoyu added 102 commits 2026-06-15 17:15:42 +00:00
- design doc §0.1 scope matrix (16 items: 8  in, 8  out)
- 4-phase breakdown (P1-P4) with cycle estimates
- P1 末 precheck list (10 items) with real method names
- plan doc 3014 lines, 16 tasks, 126 checkboxes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 6 config blocks (DB/Redis/Channel/Refresh/Partition/Extension with 4 EnableXxx=false)
- 14 Prometheus metric declarations
- self-impl healthz (/healthz + /metrics) — bypasses pkg/health to keep /metrics
- main.go startup: logger → config → DB → Redis → healthz HTTP :21009
- 10 SQL migrations: events partitioned + 4 MV + 3 pre-agg + refresh_log + 7-day initial

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Event model + ToJSON
- EventSink interface + ChannelEventSink (non-blocking Submit)
- event_repo: batch INSERT ON CONFLICT DO NOTHING dedup
- event_service: 7-type whitelist + 1KB props limit + ReceivedAt auto-fill
- event_flusher: 100/1s batch + sync metric_recent_level_ups on level_up
- metric_weekly + metric_upcoming workers (5min/15min with pg_try_advisory_lock)
- partitioner: 7-day pre-create + 30-day cleanup (00:05 create / 00:30 cleanup)
- 22 unit + integration tests (model/repo/service/sink/worker)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- StatisticInternalProvider: TrackEvent/BatchTrackEvent
- StatisticCombinedProvider: all 9 RPCs (7 dashboard + 2 event) on single service
- materializer: 4 MV REFRESH CONCURRENTLY + pg_try_advisory_lock + refresh_log
- dashboard_repo: 7 aggregation SQLs (week_rank / 7d curve / top5 / level dist / upgrade progress)
- dashboard_service: 7 RPCs with Redis 5min TTL + cache miss protection (1min empty)
- Cache wrapper: JSON serialize + format dash:{rpc}:{starID}:{userID}
- main.go: integrated workers + Dubbo triple server :20009
- cross-service userService.GetFanProfile (for crystal_balance)
- client/user_rpc_client.go

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gateway:
- 7 routes /api/v1/dashboard/* with JWT auth (middleware.AuthMiddleware)
- statistic_controller.go: 7 methods, response format {code:200, data:resp}
- gateway/main.go: StatisticServiceClient wired
- gateway/config: StatisticServiceURL (DUBBO_STATISTIC_SERVICE_URL, default tri://127.0.0.1:20009)

pkg/statistic SDK:
- fire-and-forget TrackEvent + BatchTrackEvent
- Init(client) + Get() global singleton pattern

Business-side integration (7/7 event types):
- socialService.LikeAsset → asset.like
- galleryService.PlaceAsset → exhibition.start
- galleryService.RemoveFromSlot → exhibition.end (with duration_ms)
- taskService.OnExhibitionCompleted → exhibition.revenue
- assetService.CreateMintOrder → asset.mint
- assetService.logLevelChange → asset.level_up
- userService.UpdateCrystalBalance → crystal.change (wrapper fn added)

Cache warmup:
- main.go: 7 RPCs x 5 sample starIDs at startup (15s delay)
- prevents cold-start DB thundering herd

Existing service modifications:
- galleryService/exhibition_service.go
- taskService/revenue_service.go
- assetService/{mint_service,asset_level_service}.go
- userService/user_service.go
- socialService/asset_like_service.go

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modifications to 5 existing business services for TrackEvent integration:
- socialService/asset_like_service.go: TrackEvent(asset.like) after LikeAsset
- galleryService/exhibition_service.go: TrackEvent(exhibition.start) + (exhibition.end with duration)
- taskService/revenue_service.go: TrackEvent(exhibition.revenue) after OnExhibitionCompleted
- assetService/mint_service.go: TrackEvent(asset.mint) after CreateMintOrder
- assetService/asset_level_service.go: TrackEvent(asset.level_up) in logLevelChange
- userService/user_service.go: fireCrystalChangeEvent wrapper (call site TBD by service owner)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three approaches analyzed (single-tenant, hybrid, full-isolation), with detailed pros/cons comparison. Recommended: shared platform services + per-group data services isolation via namespaces and Helm chart.
Per user feedback: admin/review/ai-image-gen/ai-chat are completely new independent systems with their own codebases and DBs, not reusing existing services. They only have API-level calls between each other and to existing services. Section 1.3 table and 'key clarification' paragraph updated. Other 'shared' references in the document refer to the platform-architecture sense (shared services for all groups), not code reuse, so no other changes needed.
Per user feedback, split into two phases:

Phase 1 (this task): Merged deployment to reduce cost. All services in single topfans namespace, single gateway, external DB (RDS/ElastiCache), HPA for high-load services. Focus on getting off single VM and using K8s elasticity.

Phase 2 (future, not in this task): Per-group namespace isolation. Triggered by scale/isolation needs. Sketch included as future reference.

Removed from this spec (moved to Phase 2 or out of scope):
- per-group namespace architecture (was the original main design)
- ResourceQuota / LimitRange
- per-group gateway with cross-ns Dubbo
- application-layer group_id changes
- new services (admin/review/ai-*) implementation

Updated:
- Section 0: New 'Phased Strategy' section at top
- Section 2: Three candidates reframed for Phase 1 (don't migrate / single-namespace / per-group from day 1)
- Section 4: Detailed design is now single-namespace with all services merged
- Section 5: Single chart (topfans/) instead of two (topfans-shared + topfans-group)
- Section 6: Migration plan is Phase 1 only
- Section 10: Refinements trimmed to Phase 1-relevant items
- Section 11 (NEW): Phase 2 future design sketch for reference
Fixed 7 issues found during self-audit:

1. §4.2.4: stale reference to '$4.2.5 旧内容' (that section is now Secrets, not healthcheck). Pointed to §10.4 instead.

2. §6 Step 3: wrong cross-reference §10.2 (which is Secret strategy). Should be §10.1 (image registry).

3. §5 directory tree: hpa was duplicated in both gateway/ and hpa/. Unified to single hpa/ directory.

4. §5 directory tree: .gitignore was placed under k8s/. Moved indication to repo root with clearer comment.

5. §5 principles: added '关注点分离' (separation of concerns) for HPA/Ingress/Secret dirs.

6. §6 Step 4: title was '灰度切换' (gradual cutover) but content said 'single namespace, all traffic switched at once'. Renamed to '流量切换'.

7. §6 Step 4 + Step 5: sequence sync step was duplicated with unclear timing. Consolidated into Step 4 as a hard blocker. Step 5 now just stops VM.

8. §4.4 (data layer multi-tenancy): duplicated §11.4.4. Deleted §4.4; kept pointer in §4.3.

9. §3 comparison table: '后续按组隔离成本' for B was undersold as 'low (helm values)'. Corrected to '中' with reference to §11.5 (~2-3 months).

Advisory items left as-is (not blocking): §1.1 '腰部明星' line, §11.4.3 '方式 2' detail.
Per user feedback, the relationships of the 4 new services to the existing backend are different than previously described:

- admin: SHARES database with current system (direct DB connection, not API). Access control via DB account/RBAC.
- review: CALLED BY current backend (gateway/assetservice invokes its API for submission; result returns via callback/message/shared table)
- ai-image-gen: CALLED BY current backend (gateway originally called MiniMax directly, now calls ai-image-gen's API)
- ai-chat: CALLED BY current backend/frontend (extracted from aichatservice)

Section 1.3 rewritten to make this distinction explicit.
Section 11.4.3 expanded to describe the different cross-namespace patterns:
  - admin: no API calls, connects to same RDS via ExternalName
  - review: needs cross-ns Dubbo to read content from each group
  - ai-image-gen / ai-chat: receive calls, may indirectly call userservice via short DNS

This also has implications for the data-layer multi-tenancy in §11.4.4 — admin still needs group_id to filter across groups when reading the shared DB.
- 后端:proto 加 verify_token,SMS Redis key 场景化,Service 加 verify_token 校验+新旧密码一致性
- 前端:BUG#1 拦截器白名单修复、BUG#2 补 APP介绍 handler、BUG#3 加前端校验
- 新增 2 个错误码:ErrInvalidVerifyToken、ErrSameAsOldPassword
- 后端单测覆盖矩阵 10 条
- 前端手动验证 checklist 10 条
- 4.2: 明确 VerifyToken 函数改造为 (ctx, scene, mobile, token)
- 4.3: 补 scene="password" 传入,加 scene 一致性注释
- 5.3.4: 确认 sendCodeApi/verifyCodeApi 已支持 scene,updatePasswordApi 需扩展为 3 参数
- 7: 增加 401 业务码多义性说明,提示 res.message 必须稳定
- 10: auth_service.go 改动明确为 Register 中 VerifyToken 调用加 scene 参数
- 3.1 序列图:补 GetByID 调用,与 §4.3 一致
- 4.2: 加 call-site 审计 grep 命令 + 已确认 9 文件列表
- 4.6: 补 mocking 模式代码示例 + Test #9 错误类型规约
- 5.3.3: 解释手动 removeStorageSync 的原因(store 不清 temp_register_*)
- 5.3.4: 补 updatePasswordApi 调用点审计(2 文件已确认)
- 9: 补 AuthMiddleware 已挂载,前端凭 token 即可访问
按用户反馈,BUG #1 修复应从状态码语义入手,而非前端 URL 白名单:

后端修订 (§4.5):
- ErrInvalidVerifyToken: 401 -> 400 (业务校验,非鉴权)
- 新增 ErrInvalidOldPassword: 400 (改密场景下旧密码错)
- 保留 ErrInvalidPassword (Login 用) = 401 不变
- 关键原则:已登录态下的业务校验都走 400

前端简化 (§5.1):
- 拦截器去掉 NO_AUTO_LOGOUT_PATHS 白名单
- 只对 401 (token 失效) / 403 (账号被封) 自动登出
- 400 类业务错误统一 toast,让用户重试

同步:
- §4.3 service 代码:用 ErrInvalidOldPassword 替代 ErrInvalidPassword
- §4.6 测试用例 #5:用 ErrInvalidOldPassword
- §6.2 手动测试 #2:旧密码错返回 400
- §7 错误码表:增加是否触发自动登出列,统一规则
- §10 文件列表:errors.go 新增 3 错误码,api.js 改为非白名单改造
- §1.1 BUG #1 描述同步更新
问题: auth_service.go Login 流程被冻结/封禁时用 fmt.Errorf/errors.New
返回 generic error,落到 ToStatusCode default 分支 -> 500 而非 403

修复:
- errors.go: 新增 NewAccountBannedError / NewAccountFrozenError 2 helper,
  保留 typed error 身份但允许附加 reason / frozenUntil 信息
- errors.go: ToStatusCode 改用 errors.Is 全面识别 wrapped error
  (一处 switch 大改造,使其他 service 中已有的 fmt.Errorf("%w") 自动受益)
- auth_service.go: Login 流程 2 处 return 改用 helper
- auth_service_login_test.go (新建或追加): 2 个新单测验证 403 行为
核心:
- proto: BaseResponse.code 改用 google.rpc.Code 数字 (0/3/5/7/8/13/16)
- proto: 新增 legacy_code 字段保留旧 HTTP 镜像码(过渡期用)
- errors.go: 重写 ToStatusCode -> ToGRPCCode + ToLegacyCode,全面用 errors.Is
- service: 所有硬编码 StatusCode_STATUS_X 改为 status.Error(codes.X, msg)
- gateway: 新增拦截器强制 HTTP 200,剥除 Dubbo 自动 gRPC->HTTP 转换
- 前端: api.js 拦截器重写,优先读 code 兼容 legacy_code

迁移: 双协议期 4 阶段(准备/双协议/前端升级/清理),约 4 sprint
执行顺序: 先实现 change-password spec,再启动本重构

与 change-password spec 协同:
- §4.5 错误码映射自动升级为 gRPC code
- §5.1 拦截器修复方案被本设计自然吸收
- §12 Login BUG 修复(ToGRPCCode 用 errors.Is)作为前置依赖
🔴 严重 (2):
1. "双协议期"实际无法双协议: 原设计 code=gRPC 会破坏老前端(它们读 code 期望 HTTP 码)
   - 修订: code 字段保持 HTTP 镜像码不变,新增 grpc_code 字段;老前端零改动
2. Gateway 拦截器只改 header 不改 body: Dubbo 已把 gRPC code 写进 body 字段
   - 修订: 用 responseRecorder 完整捕获 body,重写 header 200 后回写

🟡 高 (3):
3. errors.go 代码块缺 import (status, strings)
4. responseRecorder 类型引用但未定义 -> 补充完整类型定义
5. ErrInvalidOldPassword (change-password spec §4.5 新增) 未在 ToStatusCode/ToGRPCCode 映射
   - 修订: 同步加到两个函数的 InvalidArgument 分支
6. ErrUserInactive 此前 fall into default 500 -> 现在映射到 PermissionDenied (修复)

🟢 中 (4):
7. ToStatusCode 函数需要保留作为兼容(原 spec 说删) -> 保留并补 case
8. NewError 函数引用 status 但无 import -> 补 import
9. 缺 AuthMiddleware 与 GRPCStatusInterceptor 中间件链顺序说明
10. Phase 0 描述与实际实现对齐(保留 ToStatusCode)

🔵 低 (3):
11. grep 命令可能漏掉 raw 数字赋值 -> 补 grep -rn 'Code:\s*uint32...'
12. 全文清理 legacy_code / ToLegacyCode 残留
13. §3.4 完整响应流示例更新为新设计(code+grpc_code)
14. §5.1 getBizCode helper 改为新增 getGrpcCode helper,老前端拦截器不动
15. §6.1 Phase 0 描述对齐,Phase 3 清理描述对齐
16. §10 部署清单更新
- §7.1 测试表 #9/#10 重复行清理
- §6.1 增加 Phase 0a Dubbo 探针(验证 dubbo-go-pixiu/triples
  对 gRPC code->HTTP status 的实际行为,影响 gateway 拦截器策略)
- §6.3 Day 4 与 §5.1 冲突修复(老前端拦截器保持不变,只新业务代码引入 getGrpcCode)
- §5.4 / §8 文件数 '10+/15+/12+' 统一为 'TBD pending audit'
- §10 E2E 测试细化(3 类断言);增加'第三方客户端风险通知'清单项
🔴 高 (4):
1. §3.1 序列图'6 UI 区' -> '5 UI 区'(实际是 1+1+3=5)
2. §4.3 'service.VerifyToken' 错误引用 -> 'VerifyToken' (同包)
3. §5.3.3 confirmChangePassword else 分支是死代码
   (api.js 业务码 400 时 reject 跳到 catch),删除 else 分支
4. §5.3.3 setInterval 倒计时无 cleanup
   - 新增 codeCountdownTimer ref
   - handleSendCode 存 timer,清旧 timer
   - closePasswordModal 清 timer

🟡 中 (2):
5. §4.3 事务内代码 // 原逻辑保持 占位 -> 补完整
   (password_hash + updated_at + access_token=nil 分两步)
6. §4.5 ErrUserInactive 描述'不变'不准 -> 改为'修复'
   (此前 default 500,本次 403),并补完整 ToStatusCode 函数代码

🟢 低 (1):
7. §12.5 状态码重构 spec 描述'草稿' -> '已 Approved'
- §3.1 序列图弹窗描述过长 -> 简化为'5个UI区'
- §4.3 GetByID 错误处理用 '...' 省略 -> 补完整(NotFound/wrap)
- §4.5 错误映射表 - 删除重复的 ErrPasswordTooShort 行
- §10 文件变更总览 - 补 auth_service_login_test.go (§12.4 新增)
- §11 部署清单 - 增 'API 接口回归' 和 '登录态回归' 重点
  (401 不变,403 上升,500 下降)
- Task 1: proto 加 verify_token + regen
- Task 2: errors.go 新增错误码 + ToStatusCode errors.Is 改造
- Task 3: SMS Redis key scene-aware 重构
- Task 4: SMS service SendCode/VerifyCode/VerifyToken 加 scene
- Task 5: auth_service.Register 调用点更新
- Task 6: user_service.UpdatePassword 完整实现(TDD)
- Task 7: password_test 10 个测试骨架
- Task 8: user_provider 透传 ctx
- Task 9: api.js 拦截器去 400 自动登出
- Task 10: auth_service.Login BUG 修复 + 2 测试
- Task 11: profile.vue 改密弹窗 5 UI 区 + handler + CSS
- Task 12: 回归 + 灰度发布

总览:
- 12 个 task,每 task 含文件路径+步骤+命令
- TDD 优先,frequent commits
- 任务依赖图清晰,可识别可并行项
- 验收标准 12 条
- Task 3.3: 补 rate-limit 函数(IncrMobileCount/IncrIPCount 等)
  场景化,11 个函数完整列出
- Task 4: 加 4.4-4.6 修复 auth_provider.go / unified_provider.go /
  auth_controller.go 调用点(否则编译失败);修订'明确不动'列表
- Task 4.3: 引入 VerifyTokenFn package-level var DI 模式(让
  Task 6.2 的 setupVerifyTokenMock 可工作)
- Task 6.3: 改用 VerifyTokenFn 注入调用,匹配 DI 模式
- Task 7: 5 PASS + 5 SKIP 明确分工(避免验收标准误导)
- Task 10.2: 完整 import 块 + 用 errors.Is 标准库 + pbCommon 别名
- 依赖图:刷新编号,前端 Task 9/11 可独立推进说明
- 验收标准:5 PASS + 5 SKIP 标注
xiaoyu merged commit 7542fb0a93 into dev 2026-06-15 17:17:04 +00:00
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: xiaoyu/topfans#2
No description provided.