feat:2、数据显示优化
This commit is contained in:
@@ -412,9 +412,8 @@ func (s *RockService) ListTokens(ctx context.Context, page, pageSize int, status
|
||||
}
|
||||
|
||||
var tokens []model.RockToken
|
||||
// 按包 ID 倒序(未打包的手工采购记录排在最后),同包内按本地 ID 倒序,保证分页稳定
|
||||
err := db.Order("package_id DESC NULLS LAST").
|
||||
Order("id DESC").
|
||||
// 按本地 ID 倒序:ID 自增,等价于按入库时间倒序;分页稳定,且与列表首列 ID 的降序一致
|
||||
err := db.Order("id DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Find(&tokens).Error
|
||||
|
||||
@@ -15,24 +15,24 @@
|
||||
|
||||
依照逆向工程与高强度分析标准,本次所有结论均严格基于抓包实体通信数据与前端源代码交叉验证:
|
||||
|
||||
| 证据项编号 | 证据分类 | 来源与定位 | 关键事实 / 发现 | 确信度 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **EV-01** | 通信抓包 (HAR) | `发货黑色.har` [Entry 34] | 提取 Token 请求:`POST /api/user/tokens/claim`,Body: `{"color":"black","count":"2"}`,扣除积分提取可用 Token。 | **100%** |
|
||||
| **EV-02** | 通信抓包 (HAR) | `发货黑色.har` [Entry 36, 40] | 获取绑定及兑换配置:`GET /api/redeem/{redeem_code}`,返回腾讯官方绑定链接 `bindUrl` 及皮肤/货币配置。 | **100%** |
|
||||
| **EV-03** | 通信抓包 (HAR) | `发货黑色.har` [Entry 37, 39] | 刷新绑定状态:`POST /api/user/tokens/{id}/refresh`,返回 `binding.bound: true`,角色名、角色ID、大区及历史发放记录。 | **100%** |
|
||||
| **EV-04** | 通信抓包 (HAR) | `发货黑色.har` [Entry 42] | 提交兑换发货:`POST /api/redeem/{id}`,Body: `{"skin":"黑色ROCK长袖T恤","currency":"幸运币","account":"感性的洁少"}`,官方发货成功。 | **100%** |
|
||||
| **EV-05** | 前端源码 | `app.js` (L3617~L3720) | 皮肤与货币官方数据字典 `officialSkus`,黑蓝皮肤权限控制与 180 点价值换算公式。 | **100%** |
|
||||
| **EV-06** | 前端源码 | `app.js` (L217513~L217600) | 兑换发货执行函数 `redeemToken()`,揭示其具备登录态与免登录公开态两种调用形态。 | **100%** |
|
||||
| **EV-07** | 前端源码 | `app.js` (L305062~L305120) | 绑定链接解析与生成逻辑,底层依托和平精英官方活动链接 `gp.qq.com/cp/a20241128mobile/...`。 | **100%** |
|
||||
| **EV-08** | 用户截图 | 微信/扫码绑定弹窗截图 | 弹窗展示 Token、状态(已绑定)、大区、角色、当前货币组合与兑换皮肤下拉选择框。 | **100%** |
|
||||
| **EV-09** | 通信抓包 (HAR) | `兑换记录等杂项.har` [Entry 4, 7] | 兑换历史对账接口:`GET /api/user/exchange-records?page=1&pageSize=20`,支持按账号/UID、Token、区服、时间范围精准分页对账。 | **100%** |
|
||||
| **EV-10** | 通信抓包 (HAR) | `兑换记录等杂项.har` [Entry 5] | 腾讯官方履约凭证:刷新/兑换成功记录中返回 `fulfillId: 4519960`(官方发放流水号)及 `membership: 202610`(月份期号)。 | **100%** |
|
||||
| **EV-11** | 通信抓包 (HAR) | `兑换记录等杂项.har` [Entry 3] | 下属发货员列表:`GET /api/user/delivery-agents`,返回该商户绑定的发货员(如丁德鑫、曹强等)。 | **100%** |
|
||||
| **EV-12** | 用户截图 | 批量获取 Token 弹窗截图 | 提取类型默认下拉选项为 `1 = 黑色`,提取数量 2~500,印证了统一下单通用黑色 Token 的业务形态。 | **100%** |
|
||||
| **EV-13** | 通信抓包 (HAR) | `蓝色一个发货.har` [Entry 13] | 蓝色发货兑换请求:`POST /api/redeem/151154`,Body: `{"skin":"蓝色运动服上衣","currency":"幸运币","account":"打破虚妄的浪漫"}`,发放成功。 | **100%** |
|
||||
| **EV-14** | 通信抓包 (HAR) | `蓝色一个发货.har` [Entry 1~10] | **旧绑定数据实锤证据**:初次 refresh 返回旧角色“总荒凉”(3751279035);买家真正微信扫码后更新为“打破虚妄的浪漫”(3688825401),`bind_change_type: "rebind"`,旧角色归入 `previous_bound_account`。 | **100%** |
|
||||
| **EV-15** | 通信抓包 (HAR) | `蓝色一个发货.har` [Entry 0] | 再次证实用于发货蓝色的 Token 151154 其本身规格仍然是 `color: "black"`(黑色通用 Token)。 | **100%** |
|
||||
| **EV-16** | 前端源码 | `app.js` (L3700~L3760) | **商户登录与鉴权机制**:平台无固定密码登录,采用“用户名+手机验证码”(`POST /api/auth/send-code` + `POST /api/auth/phone-login`),登录后下发 Bearer Token 并存于 `localStorage.yd_token`。请求鉴权全量走 `Authorization: Bearer <yd_token>`。 | **100%** |
|
||||
| 证据项编号 | 证据分类 | 来源与定位 | 关键事实 / 发现 | 确信度 |
|
||||
| :-------- | :--------- | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------- |
|
||||
| **EV-01** | 通信抓包 (HAR) | `发货黑色.har` [Entry 34] | 提取 Token 请求:`POST /api/user/tokens/claim`,Body: `{"color":"black","count":"2"}`,扣除积分提取可用 Token。 | **100%** |
|
||||
| **EV-02** | 通信抓包 (HAR) | `发货黑色.har` [Entry 36, 40] | 获取绑定及兑换配置:`GET /api/redeem/{redeem_code}`,返回腾讯官方绑定链接 `bindUrl` 及皮肤/货币配置。 | **100%** |
|
||||
| **EV-03** | 通信抓包 (HAR) | `发货黑色.har` [Entry 37, 39] | 刷新绑定状态:`POST /api/user/tokens/{id}/refresh`,返回 `binding.bound: true`,角色名、角色ID、大区及历史发放记录。 | **100%** |
|
||||
| **EV-04** | 通信抓包 (HAR) | `发货黑色.har` [Entry 42] | 提交兑换发货:`POST /api/redeem/{id}`,Body: `{"skin":"黑色ROCK长袖T恤","currency":"幸运币","account":"感性的洁少"}`,官方发货成功。 | **100%** |
|
||||
| **EV-05** | 前端源码 | `app.js` (L3617~L3720) | 皮肤与货币官方数据字典 `officialSkus`,黑蓝皮肤权限控制与 180 点价值换算公式。 | **100%** |
|
||||
| **EV-06** | 前端源码 | `app.js` (L217513~L217600) | 兑换发货执行函数 `redeemToken()`,揭示其具备登录态与免登录公开态两种调用形态。 | **100%** |
|
||||
| **EV-07** | 前端源码 | `app.js` (L305062~L305120) | 绑定链接解析与生成逻辑,底层依托和平精英官方活动链接 `gp.qq.com/cp/a20241128mobile/...`。 | **100%** |
|
||||
| **EV-08** | 用户截图 | 微信/扫码绑定弹窗截图 | 弹窗展示 Token、状态(已绑定)、大区、角色、当前货币组合与兑换皮肤下拉选择框。 | **100%** |
|
||||
| **EV-09** | 通信抓包 (HAR) | `兑换记录等杂项.har` [Entry 4, 7] | 兑换历史对账接口:`GET /api/user/exchange-records?page=1&pageSize=20`,支持按账号/UID、Token、区服、时间范围精准分页对账。 | **100%** |
|
||||
| **EV-10** | 通信抓包 (HAR) | `兑换记录等杂项.har` [Entry 5] | 腾讯官方履约凭证:刷新/兑换成功记录中返回 `fulfillId: 4519960`(官方发放流水号)及 `membership: 202610`(月份期号)。 | **100%** |
|
||||
| **EV-11** | 通信抓包 (HAR) | `兑换记录等杂项.har` [Entry 3] | 下属发货员列表:`GET /api/user/delivery-agents`,返回该商户绑定的发货员(如丁德鑫、曹强等)。 | **100%** |
|
||||
| **EV-12** | 用户截图 | 批量获取 Token 弹窗截图 | 提取类型默认下拉选项为 `1 = 黑色`,提取数量 2~500,印证了统一下单通用黑色 Token 的业务形态。 | **100%** |
|
||||
| **EV-13** | 通信抓包 (HAR) | `蓝色一个发货.har` [Entry 13] | 蓝色发货兑换请求:`POST /api/redeem/151154`,Body: `{"skin":"蓝色运动服上衣","currency":"幸运币","account":"打破虚妄的浪漫"}`,发放成功。 | **100%** |
|
||||
| **EV-14** | 通信抓包 (HAR) | `蓝色一个发货.har` [Entry 1~10] | **旧绑定数据实锤证据**:初次 refresh 返回旧角色“总荒凉”(3751279035);买家真正微信扫码后更新为“打破虚妄的浪漫”(3688825401),`bind_change_type: "rebind"`,旧角色归入 `previous_bound_account`。 | **100%** |
|
||||
| **EV-15** | 通信抓包 (HAR) | `蓝色一个发货.har` [Entry 0] | 再次证实用于发货蓝色的 Token 151154 其本身规格仍然是 `color: "black"`(黑色通用 Token)。 | **100%** |
|
||||
| **EV-16** | 前端源码 | `app.js` (L3700~L3760) | **商户登录与鉴权机制**:平台无固定密码登录,采用“用户名+手机验证码”(`POST /api/auth/send-code` + `POST /api/auth/phone-login`),登录后下发 Bearer Token 并存于 `localStorage.yd_token`。请求鉴权全量走 `Authorization: Bearer <yd_token>`。 | **100%** |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
# affiliate_dash 全项目状态机总览
|
||||
|
||||
> 生成日期:2026-10-30
|
||||
> 分析范围:`backend/internal/model`、`backend/internal/service`、`backend/internal/handler`、`backend/internal/database/migrations`(截至第 18 个迁移)
|
||||
> 来源约定:代码使用 `文件:行号`;**状态值统一写成 `英文值(中文含义)`**,表格与流转图都可直接对照
|
||||
> 配套文档:`docs/项目梳理-业务流程与数据模型.md`(业务全貌)、`docs/系统脑图与流程图.md`(系统结构)
|
||||
|
||||
---
|
||||
|
||||
## 0. 阅读指南
|
||||
|
||||
全项目共有 **14 个状态机**,按性质分成 5 组,理解成本从高到低排列:
|
||||
|
||||
| 组 | 包含 | 特点 | 改动风险 |
|
||||
| --- | --- | --- | --- |
|
||||
| ① 交易主线 | 订单、Rock Token、充值申请 | 真正的多状态业务流程,有审核/重试/回滚 | 高 |
|
||||
| ② 对外通知 | 回调订阅、回调投递 | 一个单例开关 + 一个带退避的重试机 | 中 |
|
||||
| ③ 登录与设备 | 设备认证、用户设备、系统账号 | 原子状态跃迁,部分状态不落库 | 中 |
|
||||
| ④ 基础主数据 | 商户、API 密钥、商品、商户成员 | 本质是启用/停用开关,无终态 | 低 |
|
||||
| ⑤ 隐藏状态 | Provider 同步结果、发货提交阶段 | 不落库或存在 JSON 子字段里,排查时才可见 | **高(最难发现)** |
|
||||
|
||||
阅读顺序建议:先看第 1 节速查表 → 第 2 节全貌图 → 按需跳到具体小节 → 最后看第 8 节终态与第 9 节踩坑。
|
||||
|
||||
---
|
||||
|
||||
## 1. 30 秒速查表
|
||||
|
||||
| # | 实体 | 表.字段 | 状态值(英文 + 中文) | 终态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 订单 | `fulfillment_orders.order_status` | `paid`(已扣款待发货)、`delivering`(发货中)、`delivered`(已交付)、`ship_failed`(发货失败可重试)、`cancelled`(已取消并退款) | `delivered`、`cancelled` |
|
||||
| 2 | Rock Token | `rock_tokens.status` | `available`(可用库存)、`using`(使用中)、`bound`(已绑定)、`redeemed`(已发货)、`mismatch`(绑定错误)、`abnormal`(异常作废) | `redeemed` |
|
||||
| 3 | 充值申请 | `recharge_applications.status` | `pending`(待审核)、`approved`(已通过已入账)、`rejected`(已驳回) | `approved`、`rejected` |
|
||||
| 4 | 回调订阅 | `callback_subscriptions.status` | `active`(推送中)、`disabled`(已停用) | 无(可来回切换) |
|
||||
| 5 | 回调投递 | `callback_deliveries.status` | `pending`(待投递)、`sending`(投递中)、`delivered`(已送达)、`failed`(已放弃) | `delivered`、`failed` |
|
||||
| 6 | 设备认证 | `device_challenges.status` | `pending`(待确认)、`authorized`(已授权)、`ticket_issued`(已签发票据)、`consumed`(已消费)、`expired`(已过期,仅读取态)、`rejected`(已拒绝,仅读取态) | `consumed` |
|
||||
| 7 | 用户设备 | `user_devices.status` | `active`(已绑定)、`revoked`(已解绑) | `revoked` |
|
||||
| 8 | 系统账号 | `users.status` | `1`(启用)、`0`(禁用) | 无 |
|
||||
| 9 | 商户 | `merchants.status` | `active`(启用)、`disabled`(停用) | 无 |
|
||||
| 10 | API 密钥 | `api_clients.status` | `active`(启用)、`disabled`(停用) | 无 |
|
||||
| 11 | 商品 / 商户商品 | `products.status` / `merchant_products.status` | `active`(上架)、`inactive`(下架) | 无 |
|
||||
| 12 | 商户成员 / 角色 | `merchant_members.status` / `merchant_roles.status` | `1`(启用)、`0`(停用) | 无 |
|
||||
| 13 | Provider 同步结果 | 内存结构,**不落库** | `success`(已成功)、`failed`(已失败)、`delivering`(处理中)、`""`(未知) | — |
|
||||
| 14 | 发货提交阶段 | `result_data.provider_order_stage`(JSON 子字段) | 8 个 stage,见 7.2 | `submitted`(已提交上游) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 全貌图
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph A["① 交易主线"]
|
||||
ORDER["订单 fulfillment_orders"]
|
||||
ROCK["Rock Token rock_tokens"]
|
||||
RECHARGE["充值申请 recharge_applications"]
|
||||
end
|
||||
|
||||
subgraph B["② 对外通知"]
|
||||
SUB["回调订阅 callback_subscriptions"]
|
||||
DELIV["回调投递 callback_deliveries"]
|
||||
end
|
||||
|
||||
subgraph C["③ 登录与设备"]
|
||||
CHALLENGE["设备认证 device_challenges"]
|
||||
DEVICE["用户设备 user_devices"]
|
||||
USER["系统账号 users"]
|
||||
end
|
||||
|
||||
subgraph D["④ 基础主数据"]
|
||||
MERCHANT["商户 merchants"]
|
||||
APICLIENT["API 密钥 api_clients"]
|
||||
PRODUCT["商品 products / merchant_products"]
|
||||
MEMBER["商户成员 merchant_members"]
|
||||
end
|
||||
|
||||
ORDER -. "占用 / 释放" .-> ROCK
|
||||
ORDER -- "状态变更事件" --> SUB
|
||||
SUB -- "生成投递记录" --> DELIV
|
||||
RECHARGE -. "审核通过后入账" .-> MERCHANT
|
||||
MERCHANT --> APICLIENT
|
||||
MERCHANT --> PRODUCT
|
||||
MERCHANT --> MEMBER
|
||||
USER --> MEMBER
|
||||
USER --> DEVICE
|
||||
DEVICE --> CHALLENGE
|
||||
```
|
||||
|
||||
> 关键理解:**订单是中心**。它驱动 Rock Token 的占用与释放,也驱动回调订阅产生投递记录;其余状态机要么是「开/关」,要么是独立小流程。
|
||||
|
||||
---
|
||||
|
||||
# ① 交易主线
|
||||
|
||||
## 3.1 订单状态机(最核心)
|
||||
|
||||
**字段**:`fulfillment_orders.order_status`
|
||||
**常量定义**:`backend/internal/model/merchant.go:46-50`
|
||||
|
||||
| 状态值 | 中文含义 | 何时进入 | 可以去往 |
|
||||
| --- | --- | --- | --- |
|
||||
| `paid` | 已扣款,待发货 | 创建订单时写入(`fulfillment_create.go:172`) | `delivering`(发货中)、`delivered`(已交付)、`ship_failed`(发货失败)、`cancelled`(已取消) |
|
||||
| `delivering` | 发货中 | 开始发货或重试发货(`delivery_submit.go:150`) | `delivered`(已交付)、`ship_failed`(发货失败) |
|
||||
| `delivered` | 已交付 | 发货接口成功、上游回调 success(`fulfillment_shipnotify.go:48`) | **无(终态)** |
|
||||
| `ship_failed` | 发货失败,可重试 | 发货失败、`delivering` 超时(`fulfillment_timeout.go:17`) | `delivering`(重试)、`delivered`(补投成功)、`cancelled`(取消)、`ship_failed`(再次失败) |
|
||||
| `cancelled` | 已取消并退款 | 取消订单并回补库存(`fulfillment_cancel.go:86`) | **无(终态)** |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "paid 已扣款待发货" as paid
|
||||
state "delivering 发货中" as delivering
|
||||
state "delivered 已交付(终态)" as delivered
|
||||
state "ship_failed 发货失败可重试" as ship_failed
|
||||
state "cancelled 已取消并退款(终态)" as cancelled
|
||||
|
||||
[*] --> paid : 创建订单并扣款
|
||||
paid --> delivering : 开始发货
|
||||
paid --> delivered : 同步接口直接成功
|
||||
paid --> ship_failed : 发货失败
|
||||
paid --> cancelled : 取消并退款
|
||||
delivering --> delivered : 发货成功
|
||||
delivering --> ship_failed : 发货失败或超时
|
||||
ship_failed --> delivering : 人工重试发货
|
||||
ship_failed --> delivered : 补投成功
|
||||
ship_failed --> ship_failed : 再次失败
|
||||
ship_failed --> cancelled : 未提交上游可取消
|
||||
delivered --> [*]
|
||||
cancelled --> [*]
|
||||
```
|
||||
|
||||
**规则校验**:`backend/internal/service/fulfillment_state.go:76-134`
|
||||
|
||||
- `current == next` 直接放行,**幂等**,重复回调不会报错(`fulfillment_state.go:81`)。
|
||||
- `cancelled`(已取消)与 `delivered`(已交付)是终态,任何变更都被拒绝;错误文案分别是「订单已取消,不能更新状态」「订单已交付,不能回退状态」。
|
||||
- `timeout` 类型的失败**只能**来自 `delivering`(发货中),防止把 `paid`(待发货)的订单误标超时(`fulfillment_state.go:102`)。
|
||||
- 取消白名单只有 `paid`(已扣款)与 `ship_failed`(发货失败);`delivering`(发货中)、`delivered`(已交付)一律不可取消(`fulfillment_state.go:118`)。
|
||||
|
||||
**五个改状态的入口**
|
||||
|
||||
| 入口 | 代码位置 | 允许改成 |
|
||||
| --- | --- | --- |
|
||||
| 创建订单 | `fulfillment_create.go:172` | 初始 `paid`(已扣款待发货) |
|
||||
| 人工更新 | `fulfillment_update.go:22` | `delivering`(发货中)、`delivered`(已交付)、`ship_failed`(发货失败) |
|
||||
| 发货回调 | `fulfillment_shipnotify.go:48` | success → `delivered`(已交付);failed → `ship_failed`(发货失败) |
|
||||
| 超时扫描 | `fulfillment_timeout.go:17`、`:28` | `delivering`(发货中)→ `ship_failed`(发货失败) |
|
||||
| 取消订单 | `fulfillment_cancel.go:26`、`:86` | → `cancelled`(已取消并退款) |
|
||||
|
||||
---
|
||||
|
||||
## 3.2 Rock Token 状态机
|
||||
|
||||
**字段**:`rock_tokens.status`
|
||||
**常量定义**:`backend/internal/model/rock.go:21-26`
|
||||
|
||||
| 状态值 | 中文含义 | 何时进入 | 可以去往 |
|
||||
| --- | --- | --- | --- |
|
||||
| `available` | 可用库存 | 打包导入、解冻重置、订单释放回退 | `using`(使用中) |
|
||||
| `using` | 使用中,已分配给订单正在提货扫码 | 分配订单(`rock_assign.go:120`,行锁 `SKIP LOCKED`) | `bound`(已绑定)、`mismatch`(绑定错误)、`available`(释放回库)、`abnormal`(异常作废) |
|
||||
| `bound` | 已绑定成功 | 绑定校验通过(`rock_bindcheck.go`、`rock_assign.go:72`、`rock_binding_refresh.go`) | `redeemed`(已发货)、`mismatch`(绑定错误)、`abnormal`(异常作废) |
|
||||
| `redeemed` | 已发货完成 | 兑换完成(`rock_redeem.go:82`) | **无(终态)** |
|
||||
| `mismatch` | 绑定错误,UID 与预期不符 | 绑定校验发现 UID 不一致(`rock_bindcheck.go:173`、`rock_binding_refresh.go`) | `bound`(改 UID 对齐)、`available`(解冻重置)、`abnormal`(异常作废) |
|
||||
| `abnormal` | 异常作废 | 人工上报失效(`rock_recover.go:144`) | `available`(解冻重置) |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "available 可用库存" as available
|
||||
state "using 使用中已分配" as using
|
||||
state "bound 已绑定成功" as bound
|
||||
state "redeemed 已发货(终态)" as redeemed
|
||||
state "mismatch 绑定错误 UID 不符" as mismatch
|
||||
state "abnormal 异常作废" as abnormal
|
||||
|
||||
[*] --> available : 打包导入或人工新增
|
||||
available --> using : 分配给订单
|
||||
using --> bound : 扫码绑定成功
|
||||
using --> mismatch : UID 校验不一致
|
||||
using --> available : 释放或订单取消
|
||||
using --> abnormal : 人工上报失效
|
||||
bound --> redeemed : 发货完成
|
||||
bound --> mismatch : 刷新后发现 UID 不符
|
||||
bound --> abnormal : 人工上报失效
|
||||
mismatch --> bound : 手工改成正确 UID
|
||||
mismatch --> available : 解冻重置
|
||||
mismatch --> abnormal : 人工上报失效
|
||||
abnormal --> available : 解冻重置
|
||||
redeemed --> [*]
|
||||
```
|
||||
|
||||
**各跃迁的代码位置**
|
||||
|
||||
| 跃迁 | 代码位置 |
|
||||
| --- | --- |
|
||||
| `available`(可用)→ `using`(使用中) | `rock_assign.go:120`(`FOR UPDATE SKIP LOCKED`) |
|
||||
| `using`(使用中)→ `bound`(已绑定) | `rock_bindcheck.go`、`rock_assign.go:72`、`rock_binding_refresh.go` |
|
||||
| `using`(使用中)→ `available`(释放回库) | `rock_redeem.go:125`(释放)、`provider_rock.go:355`(取消) |
|
||||
| `bound`(已绑定)→ `redeemed`(已发货) | `rock_redeem.go:82` |
|
||||
| 任意 → `mismatch`(绑定错误) | `rock_bindcheck.go:173`、`rock_binding_refresh.go` |
|
||||
| 任意 → `abnormal`(异常作废) | `rock_recover.go:144` |
|
||||
| `mismatch`(绑定错误)→ `bound`(已绑定) | `rock_recover.go:46`(改 UID 对齐) |
|
||||
| `mismatch` / `abnormal` → `available`(可用) | `rock_recover.go:115`(解冻重置) |
|
||||
|
||||
**关联字段 `using_at`(使用中时长预警)**
|
||||
|
||||
| 动作 | 位置 |
|
||||
| --- | --- |
|
||||
| 写入 `using_at` | 分配时 `rock_assign.go`;打包导入 using 记录时 `rock_package.go` |
|
||||
| 清空 `using_at` | 释放 `rock_redeem.go`;取消 `provider_rock.go`;解冻重置 `rock_recover.go`;备注改为 available 时 `rock_recover.go` |
|
||||
|
||||
> 规则:只对 `using`(使用中)生效;满 24 小时计 1 天,默认 3 天橙色预警、5 天红色预警(阈值存在 `rock_configs.using_warn_days` / `using_danger_days`)。
|
||||
> **任何把状态改回 `available` 的路径都必须同步清空 `using_at`,否则会留下错误的超时告警。**
|
||||
|
||||
**打包导入的特殊规则**:`source=package` 的 Token 在**没有关联订单且非 `available`** 时,允许按上游最新记录刷新字段(`rock_package.go` 的 `canRefreshUpstream`)。
|
||||
|
||||
---
|
||||
|
||||
## 3.3 充值申请状态机
|
||||
|
||||
**字段**:`recharge_applications.status`
|
||||
**常量定义**:`backend/internal/model/recharge.go:11-13`
|
||||
|
||||
| 状态值 | 中文含义 | 何时进入 | 可以去往 |
|
||||
| --- | --- | --- | --- |
|
||||
| `pending` | 待审核 | 商户提交充值申请 | `approved`(审核通过)、`rejected`(审核驳回) |
|
||||
| `approved` | 审核通过,已入账 | 管理员通过,同一事务内调用 `AdjustWallet`(幂等键 = 申请单号) | **无(终态)** |
|
||||
| `rejected` | 审核驳回 | 管理员驳回并填写原因 | **无(终态)** |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "pending 待审核" as pending
|
||||
state "approved 审核通过已入账(终态)" as approved
|
||||
state "rejected 审核驳回(终态)" as rejected
|
||||
|
||||
[*] --> pending : 商户提交充值申请
|
||||
pending --> approved : 管理员通过并调用 AdjustWallet
|
||||
pending --> rejected : 管理员驳回
|
||||
approved --> [*]
|
||||
rejected --> [*]
|
||||
```
|
||||
|
||||
> 只有 `pending`(待审核)可被审核(`recharge.go:135`);重复点通过会被拒。入账与状态变更在同一事务,靠申请单号做幂等,不会重复加钱。
|
||||
|
||||
---
|
||||
|
||||
# ② 对外通知
|
||||
|
||||
## 4.1 回调订阅状态机
|
||||
|
||||
**字段**:`callback_subscriptions.status`
|
||||
**常量定义**:`backend/internal/model/merchant.go:61-62`
|
||||
|
||||
| 状态值 | 中文含义 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| `active` | 推送中 | **每个商户最多只能有一条**。新建或启用时,会自动把该商户其他订阅置为 `disabled`(`callback.go:187-191`) |
|
||||
| `disabled` | 已停用 | 不产生任何投递记录 |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "active 推送中" as active
|
||||
state "disabled 已停用" as disabled
|
||||
|
||||
[*] --> active : 创建订阅
|
||||
active --> disabled : 停用或被新订阅顶掉
|
||||
disabled --> active : 重新启用
|
||||
```
|
||||
|
||||
> 这不是终态机,而是一个**单例指针**:`active`(推送中)永远最多一条。排查「回调没收到」时,先确认订阅是不是被别的配置顶成 `disabled`(已停用)了。
|
||||
|
||||
---
|
||||
|
||||
## 4.2 回调投递状态机(Outbox 重试)
|
||||
|
||||
**字段**:`callback_deliveries.status`
|
||||
**常量定义**:`backend/internal/model/merchant.go:64-67`
|
||||
|
||||
| 状态值 | 中文含义 | 何时进入 | 可以去往 |
|
||||
| --- | --- | --- | --- |
|
||||
| `pending` | 待投递 | 业务事件入队;或失败后等待退避 | `sending`(投递中) |
|
||||
| `sending` | 投递中 | Worker 用原子 UPDATE 认领(`callback.go:292`) | `delivered`(已送达)、`pending`(重试)、`failed`(已放弃) |
|
||||
| `delivered` | 已送达 | 收到 HTTP 2xx | **无(终态)** |
|
||||
| `failed` | 已放弃 | 达到最大次数 `16`,或遇到终止性错误(如 404)(`callback.go:365`) | **无(终态)** |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "pending 待投递" as pending
|
||||
state "sending 投递中" as sending
|
||||
state "delivered 已送达(终态)" as delivered
|
||||
state "failed 已放弃(终态)" as failed
|
||||
|
||||
[*] --> pending : 业务事件入队
|
||||
pending --> sending : Worker 原子认领
|
||||
sending --> delivered : 收到 HTTP 2xx
|
||||
sending --> pending : 失败且仍可重试
|
||||
sending --> failed : 满 16 次或终止性错误
|
||||
sending --> pending : 卡住超 5 分钟被回收
|
||||
delivered --> [*]
|
||||
failed --> [*]
|
||||
```
|
||||
|
||||
**重试节奏**(`callback.go:30-46`,可用环境变量 `CALLBACK_RETRY_SCHEDULE` / `CALLBACK_MAX_ATTEMPTS` 覆盖):
|
||||
|
||||
```text
|
||||
第 2 次 15 秒
|
||||
第 3 次 15 秒
|
||||
第 4 次 30 秒
|
||||
第 5 次 3 分钟
|
||||
第 6 次 10 分钟
|
||||
第 7 次 20 分钟
|
||||
第 8 次 30 分钟
|
||||
第 9 次 30 分钟
|
||||
第 10 次 30 分钟
|
||||
第 11 次 60 分钟
|
||||
第 12 次 3 小时
|
||||
第 13 次 3 小时
|
||||
第 14 次 3 小时
|
||||
第 15 次 6 小时
|
||||
第 16 次 6 小时
|
||||
```
|
||||
|
||||
> 共 16 次尝试,全程约 26 小时。超过 5 分钟仍停留在 `sending`(投递中)的记录会被重新认领(`callback.go:261`),因此**接收方必须按事件 ID 做幂等**。
|
||||
|
||||
---
|
||||
|
||||
# ③ 登录与设备
|
||||
|
||||
## 5.1 设备认证 Challenge 状态机(PC ↔ 手机扫码)
|
||||
|
||||
**字段**:`device_challenges.status`
|
||||
**常量定义**:`backend/internal/model/login_auth.go:8-13`
|
||||
**有效期**:120 秒
|
||||
|
||||
| 状态值 | 中文含义 | 何时进入 | 可以去往 |
|
||||
| --- | --- | --- | --- |
|
||||
| `pending` | 待手机确认 | 客户端发起挑战 | `authorized`(已授权) |
|
||||
| `authorized` | 手机已确认授权 | 手机端确认,原子 UPDATE 条件为 `status=pending` 且未过期(`login_dcauth_service.go:302`) | `ticket_issued`(已签发票据) |
|
||||
| `ticket_issued` | 已签发一次性票据 | 首次轮询时签发(`login_dcauth_service.go:354`) | `consumed`(已消费) |
|
||||
| `consumed` | 已消费(登录完成) | PC 端原子消费票据(`login_dcauth_service.go:393`) | **无(终态)** |
|
||||
| `expired` | 已过期 | **读取时计算,不落库**(`login_dcauth_service.go:344`) | — |
|
||||
| `rejected` | 已拒绝 | **读取时计算,不落库** | — |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "pending 待手机确认" as pending
|
||||
state "authorized 手机已确认" as authorized
|
||||
state "ticket_issued 已签发一次性票据" as ticket_issued
|
||||
state "consumed 已消费(终态)" as consumed
|
||||
state "expired 已过期(不落库)" as expired
|
||||
state "rejected 已拒绝(不落库)" as rejected
|
||||
|
||||
[*] --> pending : 发起挑战 有效期 120 秒
|
||||
pending --> authorized : 手机端确认授权
|
||||
authorized --> ticket_issued : 首次轮询签发 ticket
|
||||
ticket_issued --> consumed : PC 端消费 ticket
|
||||
consumed --> [*]
|
||||
|
||||
pending -.-> expired : 超时后读取判定
|
||||
authorized -.-> expired : 超时后读取判定
|
||||
pending -.-> rejected : 手机端拒绝或记录不存在
|
||||
|
||||
note right of expired
|
||||
expired 与 rejected 不写库
|
||||
仅在读取时按 expires_at 与 status 计算
|
||||
end note
|
||||
```
|
||||
|
||||
> 最易误判的一点:数据库里**永远查不到** `expired`(已过期)或 `rejected`(已拒绝)的行,它们只出现在接口返回值里。想查「失败的挑战」只能看 `created_at` 与 `expires_at`。
|
||||
|
||||
---
|
||||
|
||||
## 5.2 用户设备状态机
|
||||
|
||||
**字段**:`user_devices.status`
|
||||
**常量定义**:`backend/internal/model/login_auth.go:15-16`
|
||||
|
||||
| 状态值 | 中文含义 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| `active` | 已绑定为信任设备 | **每个用户最多 1 台**,由部分唯一索引 `ON CONFLICT (user_id) WHERE status='active'` 保证;登录时自动绑定(`login_dcauth_service.go:427`) |
|
||||
| `revoked` | 已解绑 | 管理员重置密码、删除账号时置为此态(`user.go:344`、`user.go:455`) |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "active 已绑定信任设备" as active
|
||||
state "revoked 已解绑" as revoked
|
||||
|
||||
[*] --> active : 登录时自动绑定
|
||||
active --> revoked : 管理员重置密码或改密
|
||||
revoked --> active : 重新登录绑定
|
||||
```
|
||||
|
||||
> `revoked`(已解绑)不是永久终态:用户重新登录会建一条新的 `active` 记录,旧记录保持 `revoked`。
|
||||
|
||||
---
|
||||
|
||||
## 5.3 系统账号状态
|
||||
|
||||
**字段**:`users.status`
|
||||
**定义**:`backend/internal/model/models.go:28`,注意是 **int 型而不是字符串**
|
||||
|
||||
| 状态值 | 中文含义 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `1` | 启用 | 字段默认值 |
|
||||
| `0` | 禁用 | 禁用后不能登录;系统保护「至少保留一个启用的管理员」 |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "1 启用" as enabled
|
||||
state "0 禁用" as disabled
|
||||
|
||||
[*] --> enabled : 创建账号
|
||||
enabled --> disabled : 管理员禁用
|
||||
disabled --> enabled : 管理员启用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# ④ 基础主数据(开关型)
|
||||
|
||||
这一组本质上是同一个模式,可以统一理解为一张图。
|
||||
|
||||
| 实体 | 字段 | 启用值 | 停用值 | 停用后的效果与特殊约束 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 商户 | `merchants.status`(`merchant.go:10-11`) | `active`(启用) | `disabled`(停用) | 不能下单、加成员、建密钥、配回调(`fulfillment_create.go:110`) |
|
||||
| API 密钥 | `api_clients.status`(`merchant.go:40-41`) | `active`(启用) | `disabled`(停用) | 不能调用开放接口;**必须先停用才能删除**(`merchant_api_client.go:123`) |
|
||||
| 商品 / 商户商品 | `products.status` / `merchant_products.status`(`merchant.go:43-44`) | `active`(上架) | `inactive`(下架) | 下单只认 `active`(`fulfillment_create.go:118`);下架后才能删除(`merchant_product.go:140`) |
|
||||
| 商户成员 / 角色 | `merchant_members.status`(`merchant.go:109`)/ `merchant_roles.status`(`merchant.go:125`) | `1`(启用) | `0`(停用) | 成员停用后失去权限;角色停用时判断能否复用(`merchant_member.go:292`) |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "active 或 1 启用" as ON
|
||||
state "disabled 或 0 停用" as OFF
|
||||
|
||||
[*] --> ON : 创建
|
||||
ON --> OFF : 停用或下架
|
||||
OFF --> ON : 重新启用
|
||||
```
|
||||
|
||||
> 设计取舍:这些表刻意要求「先停用再删除」,避免「删了又被历史订单引用」造成业务断链。它们都**没有终态**,可以反复切换。
|
||||
|
||||
---
|
||||
|
||||
# ⑤ 隐藏状态机
|
||||
|
||||
## 6.1 Provider 同步结果(瞬时,不落库)
|
||||
|
||||
`ProviderSyncResult.Status` 只在内存中流转,随后被翻译成订单状态,**不写数据库**。
|
||||
|
||||
| 状态值 | 中文含义 | 后续动作 |
|
||||
| --- | --- | --- |
|
||||
| `success` | 上游已成功 | 调 `HandleShipNotify` → 订单 `delivered`(已交付) |
|
||||
| `failed` | 上游已失败 | 调 `HandleShipNotify` → 订单 `ship_failed`(发货失败) |
|
||||
| `delivering` | 上游处理中 | 补充写入游戏 UID / 角色名 / 区服,订单保持 `delivering`(发货中) |
|
||||
| `""` | 未知 | 不做任何处理 |
|
||||
|
||||
**映射位置**:`backend/internal/service/delivery_provider.go:58`
|
||||
|
||||
> 调试要点:这个状态无法用 SQL 查到,只能看日志或订单的 `result_data`。
|
||||
|
||||
---
|
||||
|
||||
## 6.2 发货提交阶段(订单内的子状态机)
|
||||
|
||||
**位置**:`result_data` JSON 字段 `provider_order_stage`(**不是独立列,无法建索引**)
|
||||
**常量定义**:`backend/internal/service/delivery.go:14-22`
|
||||
**用途**:断点续传 + 防止重复发货
|
||||
|
||||
| 阶段值 | 中文含义 | 是否算「已提交上游」 |
|
||||
| --- | --- | --- |
|
||||
| `claimed` | 已锁定订单,尚未创建 queue | 否 |
|
||||
| `queue_created` | 已在上游创建 queue,尚未生成正式订单 | 否 |
|
||||
| `submitted` | 已在上游创建正式订单 | **是,禁止自动重发** |
|
||||
| `bind_verify` | 绑定账号校验失败 | 否 |
|
||||
| `bind_mismatch` | 玩家编号与绑定账号不一致 | 否 |
|
||||
| `create_queue` | 创建上游 queue 失败 | 否 |
|
||||
| `patch_queue` | 补充 queue 账号信息失败 | 否 |
|
||||
| `create_upstream` | 创建上游正式订单失败 | 否 |
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "claimed 已锁单未建 queue" as claimed
|
||||
state "queue_created 已建上游 queue" as queue_created
|
||||
state "submitted 已创建上游正式订单" as submitted
|
||||
state "create_queue 建 queue 失败" as create_queue
|
||||
state "patch_queue 补账号信息失败" as patch_queue
|
||||
state "bind_verify 绑定校验失败" as bind_verify
|
||||
state "bind_mismatch 编号不一致" as bind_mismatch
|
||||
state "create_upstream 建正式单失败" as create_upstream
|
||||
|
||||
[*] --> claimed : 锁定订单并写 submit_started_at
|
||||
claimed --> queue_created : 上游建 queue 成功
|
||||
claimed --> create_queue : 建 queue 失败
|
||||
queue_created --> submitted : 上游正式下单成功
|
||||
queue_created --> patch_queue : 补账号信息失败
|
||||
queue_created --> bind_verify : 绑定校验失败
|
||||
queue_created --> bind_mismatch : 玩家编号与绑定账号不一致
|
||||
queue_created --> create_upstream : 创建正式订单失败
|
||||
submitted --> [*]
|
||||
```
|
||||
|
||||
**并发保护**(`delivery_submit.go:157-174`)
|
||||
|
||||
| 规则 | 数值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 活跃提交窗口 | 35 秒 | 处于 `claimed`(已锁单)/ `queue_created`(已建 queue)且 `submit_started_at` 在 35 秒内,即使被 Webhook 临时推回 `ship_failed`,也**禁止并发重入** |
|
||||
| stale 回收阈值 | 10 分钟 | `deliverySubmissionStaleTimeout`(`delivery.go:24`),超过可被重新认领 |
|
||||
|
||||
**「已提交上游」的判定**(`delivery.go:31`)——以下任一成立即为真,都会禁止自动重发与取消:
|
||||
|
||||
1. `provider_order_no` 非空;
|
||||
2. `result_data.delivery` 存在;
|
||||
3. `result_data.provider_order_stage == submitted`(已创建上游正式订单)。
|
||||
|
||||
---
|
||||
|
||||
# 7. 终态汇总(不可再变)
|
||||
|
||||
| 实体 | 终态值 | 强行变更的后果 |
|
||||
| --- | --- | --- |
|
||||
| 订单 | `delivered`(已交付) | 报错「订单已交付,不能回退状态」 |
|
||||
| 订单 | `cancelled`(已取消) | 报错「订单已取消,不能更新状态」 |
|
||||
| Rock Token | `redeemed`(已发货) | 无代码路径可回退,只能人工改库 |
|
||||
| 充值申请 | `approved`(已通过) / `rejected`(已驳回) | 非 `pending` 一律拒绝审核 |
|
||||
| 回调投递 | `delivered`(已送达) / `failed`(已放弃) | 不再被 Worker 认领 |
|
||||
| 设备认证 | `consumed`(已消费) | 票据一次性,重放失败 |
|
||||
| 用户设备 | `revoked`(已解绑) | 只能重新登录生成新记录 |
|
||||
| 发货提交阶段 | `submitted`(已提交上游) | 禁止自动重发、禁止取消 |
|
||||
|
||||
---
|
||||
|
||||
# 8. 容易踩的坑(按严重度排序)
|
||||
|
||||
| # | 坑 | 说明 | 相关代码 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `delivered`(已交付)不可回退 | 上游回调 success 后即使发现发错货,也只能人工退款,不能改状态 | `fulfillment_state.go:87` |
|
||||
| 2 | `ship_failed`(发货失败)分两种含义 | 已提交上游(有 `provider_order_no` 或 stage=`submitted`)的失败单**禁止自动重发、禁止取消**;只有提交中断的才允许重试 | `fulfillment_cancel.go:76`、`delivery_submit.go:60` |
|
||||
| 3 | Token `mismatch`(绑定错误)不能换号 | 只能改 `expected_uid` 对齐成 `bound`(已绑定),或解冻回 `available`(可用);没有「换一个 Token」的路径 | `rock_recover.go:46` |
|
||||
| 4 | 回调 `sending`(投递中)会重入 | 超过 5 分钟未更新即可被重新认领,接收方必须按事件 ID 幂等 | `callback.go:261`、`callback.go:292` |
|
||||
| 5 | `expired`(已过期)/ `rejected`(已拒绝)查不到 | 设备认证的这两个状态不落库,只能靠 `expires_at` 计算 | `login_dcauth_service.go:334`、`:344` |
|
||||
| 6 | 订单 `current == next` 放行 | 重复回调安全,但也意味着「状态没变」不等于「没执行」,副作用要看别处 | `fulfillment_state.go:81` |
|
||||
| 7 | 回调订阅是单例 | 新建一条 `active` 会自动禁用旧的,不会报错;排查「回调没收到」先查是否被顶掉 | `callback.go:187` |
|
||||
| 8 | 基础主数据删除前必须停用 | 商户 / 密钥 / 商品 / 成员都有前置校验,直接删会被拒 | `merchant_api_client.go:123`、`merchant_product.go:140` |
|
||||
| 9 | `provider_order_stage` 是 JSON 子字段 | 无法直接建索引,只能 `result_data->>'provider_order_stage'`,数据量大时查询慢 | `delivery.go:13` |
|
||||
| 10 | Token `using_at` 与 `status` 强耦合 | 任何把状态改回 `available` 的路径都要同步清空 `using_at`,漏了会误报超时告警 | `rock_recover.go`、`rock_redeem.go` |
|
||||
| 11 | 商户/账号状态是 int | `users.status`、`merchant_members.status`、`merchant_roles.status` 用 `1/0`,不是 `active/disabled`,写 SQL 时容易混 | `models.go:28`、`merchant.go:109` |
|
||||
| 12 | Provider 同步状态不落库 | 只能看日志,排查上游返回什么必须翻应用日志 | `delivery_provider.go:58` |
|
||||
|
||||
---
|
||||
|
||||
# 9. 非状态机的枚举(避免混淆)
|
||||
|
||||
以下值看起来像状态,但**不参与流转**,只是分类标签:
|
||||
|
||||
| 枚举 | 取值(英文 + 中文) | 位置 |
|
||||
| --- | --- | --- |
|
||||
| 订单来源 | `api`(开放接口创建)、`manual`(后台人工创建)、`test`(联调测试) | `merchant.go:52-54` |
|
||||
| 钱包流水类型 | `credit`(入账)、`debit`(扣款)、`refund`(退款)、`adjust`(人工调整) | `merchant.go:56-59` |
|
||||
| 手续费类型 | `rate`(按百分比)、`fixed`(按固定金额) | `merchant.go:37-38` |
|
||||
| 商户成员角色 | `owner`(负责人)、`operator`(运营)、`finance`(财务)、`viewer`(只读) | `merchant.go:23-26` |
|
||||
| Rock 颜色 | `black`(黑色)、`blue`(蓝色) | `rock.go:18-19` |
|
||||
|
||||
---
|
||||
|
||||
# 10. 附录:状态常量位置索引
|
||||
|
||||
| 文件 | 常量 | 覆盖范围 |
|
||||
| --- | --- | --- |
|
||||
| `backend/internal/model/merchant.go:46-50` | `OrderStatus*` | 订单 5 状态 |
|
||||
| `backend/internal/model/merchant.go:40-44` | `APIClientStatus*`、`ProductStatus*` | 密钥、商品 |
|
||||
| `backend/internal/model/merchant.go:61-67` | `CallbackStatus*`、`CallbackDelivery*` | 回调订阅、回调投递 |
|
||||
| `backend/internal/model/merchant.go:10-11` | `MerchantStatus*` | 商户 |
|
||||
| `backend/internal/model/rock.go:21-26` | `RockStatus*` | Rock Token 6 状态 |
|
||||
| `backend/internal/model/recharge.go:11-13` | `RechargeStatus*` | 充值申请 3 状态 |
|
||||
| `backend/internal/model/login_auth.go:8-13` | `DeviceChallengeStatus*` | 设备认证 6 状态 |
|
||||
| `backend/internal/model/login_auth.go:15-16` | `UserDeviceStatus*` | 用户设备 2 状态 |
|
||||
| `backend/internal/model/models.go:28` | `User.Status`(int) | 系统账号 1/0 |
|
||||
| `backend/internal/service/fulfillment_state.go:76-134` | 状态跃迁校验函数 | 订单合法流转 |
|
||||
| `backend/internal/service/delivery.go:14-22` | `deliveryStage*` | 发货提交阶段 8 值 |
|
||||
| `backend/internal/service/callback.go:30-46` | `defaultCallbackRetrySchedule` | 回调重试节奏 |
|
||||
|
||||
---
|
||||
|
||||
> 维护提示:状态相关常量集中在 `backend/internal/model/`,跃迁校验集中在 `backend/internal/service/`。**新增状态值时必须同步修改:常量定义 → 跃迁校验函数 → 前端标签映射 → 本文件。**
|
||||
@@ -55,6 +55,15 @@ export function buildTokenColumns({
|
||||
onMarkAbnormal,
|
||||
}: TokenColumnHandlers): ColumnsType<RockTokenItem> {
|
||||
return [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 80,
|
||||
align: 'right',
|
||||
render: (id: number) => (
|
||||
<Text style={{ fontSize: 12, fontFamily: 'monospace', color: '#64748b' }}>{id}</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '0666 编号 / 兑换码',
|
||||
dataIndex: 'upstream_token_id',
|
||||
|
||||
Reference in New Issue
Block a user