144 lines
8.2 KiB
Markdown
144 lines
8.2 KiB
Markdown
# Phase 2: 权限/财务/实名/轮询修复 - Context
|
||
|
||
**Gathered:** 2026-03-28
|
||
**Status:** Ready for planning
|
||
|
||
<domain>
|
||
## Phase Boundary
|
||
|
||
修复和补全四个独立业务域的已知断点:企业账号权限边界(PERM)、提现财务流程完整性(FIN)、实名激活架构重构(REALNAME/G)、轮询调度小修(POLL)。共 14 个需求,全部为修复性变更,无新功能模块,REALNAME 涉及破坏性 DB 迁移(DROP 旧字段 + ADD 新字段)。
|
||
|
||
</domain>
|
||
|
||
<decisions>
|
||
## 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 v2 `clause.Locking` 写法,agent 参考项目现有 `clause.OnConflict` 用法对齐风格
|
||
- `isCEndPurchase` 的具体推断字段(`buyerType == "personal"` 或 order 字段),agent 阅读 `activateMainPackage` 上下文确认最准确的判断路径
|
||
- 每个子修复独立一个 commit(沿用 Phase 1 的粒度规范)
|
||
|
||
</decisions>
|
||
|
||
<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>
|
||
|
||
<specifics>
|
||
## Specific Ideas
|
||
|
||
- Plan 2(REALNAME)原子执行——规格书明确指出"REALNAME-01~05 是原子重构,必须在一个 plan 内完整执行,不可拆分"(STATE.md Accumulated Context)
|
||
- 旧字段 `enable_realname_activation` 直接清理,不做 false→from_purchase 映射,现有生产套餐全部默认 from_activation,后续运营按需手动调整
|
||
|
||
</specifics>
|
||
|
||
<deferred>
|
||
## Deferred Ideas
|
||
|
||
None — discussion stayed within phase scope
|
||
|
||
</deferred>
|
||
|
||
---
|
||
|
||
*Phase: 02-perm-fin-realname-poll*
|
||
*Context gathered: 2026-03-28*
|