# 用户端提现列表 API — 订阅字段现状调研 > 调研日期:2026-05-27 > 调研范围:用户端「提现记录列表」接口当前返回字段,重点关注是否包含订阅相关信息 ## 一、接口信息 | 项目 | 值 | |------|-----| | 方法 | `GET` | | 路径 | `/v1/public/user/withdrawal_log` | | 认证 | JWT Token(`AuthMiddleware` + `DeviceMiddleware`) | | 分组 | `apis/public/user.api` | ## 二、文件定位 | 层 | 路径 | |----|------| | API DSL | `apis/public/user.api:118` (`WithdrawalLog`) / `apis/public/user.api:368` (路由) | | Handler | `internal/handler/public/user/queryWithdrawalLogHandler.go` | | Logic | `internal/logic/public/user/queryWithdrawalLogLogic.go:30` | | 类型生成 | `internal/types/types.go`(`WithdrawalLog`、`QueryWithdrawalLogListRequest`、`QueryWithdrawalLogListResponse`) | | 数据模型 | `internal/model/user/user.go:167` (`Withdrawal`,表名 `withdrawals`) | ## 三、请求参数 ```go QueryWithdrawalLogListRequest { Page int `form:"page"` Size int `form:"size"` } ``` - 默认值:`page=1`、`size=10`(在 logic 内兜底) ## 四、响应结构 ### 4.1 顶层响应 ```go QueryWithdrawalLogListResponse { List []WithdrawalLog `json:"list"` Total int64 `json:"total"` } ``` ### 4.2 列表项 `WithdrawalLog` ```go WithdrawalLog { Id int64 `json:"id"` UserId int64 `json:"user_id"` Amount int64 `json:"amount"` // 单位:分 Content string `json:"content"` // 收款附加信息 Status uint8 `json:"status"` // 0:Pending 1:Approved 2:Rejected 3:Cancelled Reason string `json:"reason,omitempty"` // 拒绝原因 Method uint8 `json:"method"` // 0:其他 1:支付宝 2:微信 3:USDT Account string `json:"account"` // 收款账号 QrCodeUrl string `json:"qr_code_url"` // 收款码图片 URL CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } ``` ## 五、底层数据模型 `Withdrawal` ```go type Withdrawal struct { Id int64 UserId int64 // index:idx_user_id Amount int64 Content string // type:text Status uint8 // 0:Pending 1:Approved 2:Rejected 3:Cancelled Reason string // varchar(500) Method uint8 // 0:其他 1:支付宝 2:微信 3:USDT Account string // varchar(255) QrCodeUrl string // varchar(500) CreatedAt time.Time UpdatedAt time.Time } ``` > 表名:`withdrawals`,与用户关联仅靠 `user_id` 外键,**无任何订阅 ID / 订阅快照字段**。 ## 六、订阅字段现状(核心结论) ### 6.1 当前结论 | 维度 | 是否包含订阅信息 | |------|------------------| | API 响应(`WithdrawalLog`) | ❌ 无 | | 数据库表(`withdrawals`) | ❌ 无 | | Logic 查询逻辑 | ❌ 无 JOIN、无附加查询 `user_subscribe` | 提现记录与订阅之间**完全没有关联**。原因:佣金来源于多次订单累计,提现是从「佣金余额(`user.commission`)」整体扣减,不绑定到任何具体订阅。 ### 6.2 Logic 当前实现要点 ```go // internal/logic/public/user/queryWithdrawalLogLogic.go:46-72 query := l.svcCtx.DB.WithContext(l.ctx). Model(&user.Withdrawal{}). Where("user_id = ?", u.Id) // 仅按 user_id 过滤 + 分页 + 倒序,无任何 Preload / Join ``` ## 七、已发现的隐患(与本次需求关联) ### 7.1 时间戳违反项目约定 ⚠️ `queryWithdrawalLogLogic.go:70-71`: ```go CreatedAt: row.CreatedAt.UnixMilli(), UpdatedAt: row.UpdatedAt.UnixMilli(), ``` - 项目约定:**后端统一返回秒级 Unix 时间戳**(前端 `formatDate` 已按 `数字 × 1000` 处理) - 当前实现返回毫秒级,前端会解析为约公元 +55000 年的日期,**展示必然异常** - 修复方式:改为 `.Unix()` > 该问题独立于「订阅字段」需求,但属于同一接口,建议同批修复。 ## 八、可选扩展方向(待业务确认) 若产品希望在提现列表中展示订阅相关信息,可选方案如下: | 方案 | 字段示意 | 实现成本 | 适用场景 | |------|----------|----------|----------| | A. 当前生效订阅摘要 | `current_subscribe: { id, name, expire_at }` | 中(每行额外查 `user_subscribe`) | 想让用户看到「我提的是哪个订阅产生的佣金对应的余额」 | | B. 用户全部订阅列表 | `subscribes: [{ id, name, expire_at }]` | 高(N+1 风险) | 极少场景,需评估必要性 | | C. 仅订阅 ID 数组 | `subscribe_ids: [int64]` | 低 | 仅前端跳详情用 | | D. 不加,保持现状 | — | 0 | 若业务上提现与订阅本就无关 | > **推荐先与产品确认动机**:提现是佣金余额提现,与订阅本身没有直接业务关系,加字段前需明确「让用户看到订阅信息要解决什么问题」。 ## 九、相关接口(一并列出,便于对照) | 接口 | 方法 | 路径 | 说明 | |------|------|------|------| | 提交提现 | POST | `/v1/public/user/commission_withdraw` | 入参 `CommissionWithdrawRequest`,返回 `WithdrawalLog` | | 取消提现 | POST | `/v1/public/user/withdrawal_cancel` | 入参 `CancelWithdrawalRequest`,返回 `WithdrawalLog` | | 提现记录列表 | GET | `/v1/public/user/withdrawal_log` | 本文主角 | > 三个接口共用 `WithdrawalLog` 类型,**任何字段变更需统一同步**,否则前端类型会错位。 ## 十、后续动作建议 1. **产品确认**:是否真的需要在提现列表里返回订阅字段?目的是什么? 2. **若需新增**:在 `apis/public/user.api` 修改 `WithdrawalLog`,运行 goctl 重新生成,再补 Logic 查询。 3. **顺手修复**:将 `UnixMilli()` 改为 `Unix()`(独立小 PR 即可)。 4. **如新增订阅字段**:注意三个接口(list / cancel / withdraw)的返回结构同步,避免前端类型联动断裂。