# 用户端提现列表 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）的返回结构同步，避免前端类型联动断裂。
