8.2 KiB
8.2 KiB
Phase 2: 权限/财务/实名/轮询修复 - Context
Gathered: 2026-03-28 Status: Ready for planning
## Phase Boundary修复和补全四个独立业务域的已知断点:企业账号权限边界(PERM)、提现财务流程完整性(FIN)、实名激活架构重构(REALNAME/G)、轮询调度小修(POLL)。共 14 个需求,全部为修复性变更,无新功能模块,REALNAME 涉及破坏性 DB 迁移(DROP 旧字段 + ADD 新字段)。
## Implementation Decisions执行分组策略
- D-01: 拆分为 2 个 Plan,而非 1 个或 4 个:
- Plan 1(小修合并):B(PERM-01/02)+ H(POLL-01/02/03/04)+ C(FIN-01/02/03)
- Plan 2(REALNAME 独立):G(REALNAME-01~05)—— 单独 Plan,独立可回滚
- D-02: Plan 1 内部执行顺序:B → H → C(权限修复最小化先上,轮询小修次之,财务流程收尾)
- D-03: Plan 2(REALNAME)必须原子执行,不可拆分 —— 5 个需求在一个 Plan 的同一事务性推进中完成
企业权限(PERM)
- D-04:
asset.go:Resolve—— 移除企业账号拦截(企业应可查询被授权资产) - D-05:
asset.go:Refresh—— 新增企业账号拦截(企业只读,不可触发运营商刷新) - D-06: 其他 8 个 AssetHandler 方法维持现状(无拦截/已拦截均不动)
财务流程(FIN)
- D-07: FIN-01:
commission_withdrawal/service.go:Approve()补填approved_by/approved_at字段 - D-08: FIN-02:
commission_withdrawal.go:Reject()在 BodyParser 后补validator.Validate(&req) - D-09: FIN-03:激活配置并发保护使用 GORM v2 语法
tx.Clauses(clause.Locking{Strength: "UPDATE"})—— 规格书写的是 GORM v1 的gorm:query_option,项目实际用 GORM v2,agent 使用正确语法
REALNAME 架构重构(G)
- D-10: DB 迁移策略:直接 ADD DEFAULT 'from_activation',旧字段 DROP
- 不做
enable_realname_activation=false → from_purchase的数据迁移 - 原因:旧字段控制的是"是否等实名",新字段
expiry_base控制的是"计时起点",两者语义不同,无法直接映射;旧字段已无存在意义,直接清理 - 现有生产套餐记录全部默认
from_activation,后续如需from_purchase由运营手动修改
- 不做
- D-11:
activateMainPackage激活决策逻辑改为按三维判断:- 行业卡(
card_category == "industry")→ 永远直接激活 - C 端购买(
buyerType == "personal"或order.OrderType标记 C 端)→ 直接激活(已前置实名检查) - 后台囤货 → 按
pkg.ExpiryBase:from_purchase直接激活,from_activation进入等实名队列
- 行业卡(
- D-12: C 端实名检查改为按卡类型(
card_category == "normal")判断,删除packagesNeedRealname()函数 - D-13:
ActivateByRealname按ExpiryBase选择计时基准:from_purchase用CreatedAt,from_activation用当前时刻
轮询系统小修(POLL)
- D-14: POLL-01:在
scheduler.go主调度循环中,复用已有的processManualQueue/processTimedQueue,补充polling:protect的调度接入 - D-15: POLL-02:
stopCardWithRetry/resumeCardWithRetry两处,gatewayClient == nil时改为返回明确业务错误 - D-16: POLL-03:
GetPackages()接口新增分页参数,默认page=1, pageSize=50,最大 100 - D-17: POLL-04:
package_series/service.go:Delete()前置CountBySeriesID()检查,有关联套餐则拒绝删除
Agent's Discretion
- FIN-03 事务内
FOR UPDATE的具体 GORM v2clause.Locking写法,agent 参考项目现有clause.OnConflict用法对齐风格 isCEndPurchase的具体推断字段(buyerType == "personal"或 order 字段),agent 阅读activateMainPackage上下文确认最准确的判断路径- 每个子修复独立一个 commit(沿用 Phase 1 的粒度规范)
<canonical_refs>
Canonical References
Downstream agents MUST read these before planning or implementing.
修复规格(核心参考)
.sisyphus/plans/修正业务-完整方案.md— 完整修复规格书,含每个需求的文件路径、代码片段、人工验收清单:- 方案 B(PERM-01/02):第 766-863 行
- 方案 C(FIN-01/02/03):第 864-944 行
- 方案 G(REALNAME-01~05):第 1492-1611 行
- 方案 H(POLL-01~04):第 1612-1711 行
核心修改文件
internal/handler/admin/asset.go— PERM-01(删 Resolve 企业拦截)、PERM-02(加 Refresh 企业拦截)internal/service/commission_withdrawal/service.go— FIN-01(Approve 补审批人字段)internal/handler/admin/commission_withdrawal.go— FIN-02(Reject 加 Validate)internal/service/commission_withdrawal_setting/service.go— FIN-03(并发行锁)internal/store/postgres/wechat_config_store.go— FIN-03(微信配置并发行锁)internal/model/package.go— REALNAME-01(drop EnableRealnameActivation,add ExpiryBase)internal/model/dto/package_dto.go— REALNAME-05(DTO 更新 expiry_base 字段)internal/service/order/service.go— REALNAME-02(activateMainPackage 三维决策)internal/service/client_order/service.go— REALNAME-03(按卡类型检查实名)internal/service/package/activation_service.go— REALNAME-04(ActivateByRealname 计时基准)internal/store/postgres/package_store.go— REALNAME-01(删 enable_realname_activation 两步写入特殊处理)internal/polling/scheduler.go— POLL-01(补 protect 调度)internal/task/polling_handler.go— POLL-02(nil gateway 改返错误)internal/service/asset/service.go或相关 package 接口 — POLL-03(GetPackages 分页)internal/service/package_series/service.go— POLL-04(Delete 前检查关联套餐)
DB 迁移
migrations/目录 — 遵循现有迁移文件命名规范,REALNAME 迁移需新建一个 .sql 文件
项目规范
AGENTS.md— 语言要求、错误处理、分层规范docs/api-documentation-guide.md— 若有 DTO 变更需同步更新文档
</canonical_refs>
<code_context>
Existing Code Insights
关键现状确认
asset.go:Resolve(第 41-44 行):企业拦截代码已确认存在,直接删除即可asset.go:Refresh(第 78 行附近):当前无企业检查,需在方法开头新增- GORM v2 locking 语法:项目已有
clause.OnConflict使用(package_usage_daily_record_store.go),FIN-03 应参照同样风格使用clause.Locking enable_realname_activation:在package.go、package_dto.go(3处)、package_store.go(2处两步写入特殊处理)均有引用,全部需清理card_category:在polling_handler.go:108已有if card.CardCategory == "industry"判断,REALNAME-02 决策逻辑与此对齐processManualQueue/processTimedQueue:scheduler.go:243-250已有 3 个 polling 类型,补 protect 参照同一模式
Established Patterns
- 行锁:
tx.Clauses(clause.Locking{...})— 参考clause.OnConflict已有用法 - 错误返回:Service 层用
errors.New(code, msg)/errors.Wrap(code, err, msg) - 分页:
QueryOptions{Page: page, PageSize: pageSize}— 参考已有 List 接口 - Commit 粒度:每个独立 Bug 修复一个 commit(沿用 Phase 1 规范)
Integration Points
- REALNAME:
package_store.go的两步写入特殊处理在 REALNAME-01 后需删除(字段删除后特殊处理失去意义) - POLL-03 分页:若
GetPackages上层调用点(handler/admin/asset.go:102、handler/app/client_asset.go:213)无分页参数传入,需同时更新 Handler 层
</code_context>
## Specific Ideas- Plan 2(REALNAME)原子执行——规格书明确指出"REALNAME-01~05 是原子重构,必须在一个 plan 内完整执行,不可拆分"(STATE.md Accumulated Context)
- 旧字段
enable_realname_activation直接清理,不做 false→from_purchase 映射,现有生产套餐全部默认 from_activation,后续运营按需手动调整
None — discussion stayed within phase scope
Phase: 02-perm-fin-realname-poll Context gathered: 2026-03-28