1.优化后台页面样式

2.优化统一订单中红利的状态和失败原因
3.移除项目中冗余代码和字段
This commit is contained in:
2026-04-21 11:59:15 +08:00
parent 1c900e7132
commit 3ac825f15d
26 changed files with 199 additions and 264 deletions

View File

@@ -2,14 +2,17 @@
说明:本文档严格依据当前代码 `app/api/controller/v1/Playx.php``app/api/controller/v1/Auth.php`(临时登录)、`config/playx.php` 与定时任务 `app/process/PlayxJobs.php` 整理。
三类接口分别为
- `积分商城 -> PlayX`PlayX 调用商城)
- `PlayX -> 积分商城`(商城调用 PlayX
- `积分商城 -> H5`H5 调用商城)
按调用方向分为三类(避免与历史章节标题混淆)
| 方向 | 含义 | 本文位置 |
|------|------|----------|
| **PlayX → 积分商城** | PlayX或上游批处理**主动 HTTP 调用商城**开放接口 | **§1**(如 Daily Push |
| **积分商城 → PlayX** | 商城 Worker / 后台 **主动 HTTP 调用 PlayX / Cash Market** 提供的接口 | **不展开于本文**;交付 PlayX 的说明见 **`docs/PlayX-接口待完善清单.md`** |
| **积分商城 → H5** | H5 / 内嵌页 **调用商城** 的会员与积分业务接口 | **§3** |
---
## 1. 积分商城 -> PlayXPlayX 调用商城
## 1. PlayX → 积分商城(外部系统调用商城开放接口
### 1.1 Daily Push API
* 方法:`POST`
@@ -36,8 +39,8 @@
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 是 | 外部推送请求号(原样返回) |
| `date` | string(YYYY-MM-DD) | 是 | 业务日期(入库到 `mall_playx_daily_push.date` |
| `user_id` | string | 是 | PlayX 用户 ID用于幂等入库 `mall_playx_daily_push.user_id` 等;服务端会映射/创建 `mall_user` `mall_playx_user_asset` |
| `date` | string(YYYY-MM-DD) | 是 | 业务日期(入库到 `mall_daily_push.date` |
| `user_id` | string | 是 | PlayX 用户 ID用于幂等入库 `mall_daily_push.user_id` 等;服务端会`user_id`/`username` **确保存在** `mall_user_asset` 资产行 |
| `username` | string | 否 | 展示冗余(同步到商城用户侧逻辑时使用) |
| `yesterday_win_loss_net` | number | 否 | 昨日净输赢(仅当 `< 0` 时计算新增保障金) |
| `yesterday_total_deposit` | number | 否 | 昨日总充值(用于计算今日可领取上限) |
@@ -178,186 +181,40 @@ curl -X POST 'http://localhost:1818/api/v1/mall/dailyPush' \
---
## 2. PlayX -> 积分商城(商城调用 PlayX
## 2. 积分商城 → PlayX贵方需提供的 HTTP 接口
> 下面这些接口由 PlayX 提供。商城侧仅按“请求参数 + 期望返回判定条件”发起调用与处理结果
> **说明**H5 调商城的 **`/api/v1/mall/verifyToken`** 在配置 **`playx.verify_token_local_only=true`**(默认)时**不会请求**本节接口,而是在商城内校验 `muser` token远程对接 PlayX 时见 **3.3** 与下文 **2.1**。
商城在验 Token、红利发放、交易轮询、Angpow 导入等场景会 **主动请求 PlayX / Cash Market**
**完整 URL、请求/响应字段、成功判定、与 Angpush 双路径关系、联调待办** 已单独整理,便于 **直接转发给 PlayX 平台**
### 2.1 Token Verification APIPlayX 侧实现,远程验证时使用)
* 方法:`POST`
* URL`${playx.api.base_url}${playx.api.token_verify_url}`
* 默认:`/api/v1/auth/verify-token`
- **`docs/PlayX-接口待完善清单.md`**
#### 请求 Body商城侧发送
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 是 | 形如 `mall_{uniqid}` |
| `token` | string | 是 | 前端传入的 PlayX token |
#### 返回(期望)
商城侧校验:
* HTTP 状态码必须为 `200`
* 且响应体中必须包含 `user_id`
期望字段(示例):
| 字段 | 类型 | 说明 |
|------|------|------|
| `user_id` | string | 必选 |
| `username` | string | 可选 |
| `token_expire_at` | string | 可选(能被 `strtotime` 解析) |
示例(成功):
```json
{
"user_id": "U123",
"username": "demo_user",
"token_expire_at": "2026-04-01T12:00:00Z"
}
```
示例(失败):
```json
{
"message": "invalid token"
}
```
本文 **§1** 仅描述「谁调用商城」;**§3** 描述「H5 调用商城」。
---
### 2.2 Bonus Grant API
* 方法:`POST`
* URL`${playx.api.base_url}${playx.api.bonus_grant_url}`
* 默认:`/api/v1/bonus/grant`
#### 请求 Body商城侧发送
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 是 | 形如 `mall_bonus_{uniqid}` |
| `externalTransactionId` | string | 是 | `MallPlayxOrder.external_transaction_id` |
| `user_id` | string | 是 | PlayX 用户 ID |
| `amount` | number | 是 | `MallPlayxOrder.amount` |
| `rewardName` | string | 是 | `mall_item.title` |
| `category` | string | 是 | `mall_item.category`(默认 `daily` |
| `categoryTitle` | string | 是 | `mall_item.category_title` |
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
#### 返回(期望)
商城侧判定:
* HTTP 状态码 `200`
*`data.status === "accepted"`
成功时读取:
* `data.playx_transaction_id`
失败时读取:
* `data.message` 写入订单 `fail_reason`
示例accepted
```json
{
"status": "accepted",
"playx_transaction_id": "PX_TX_001"
}
```
示例rejected
```json
{
"status": "rejected",
"message": "insufficient balance"
}
```
---
### 2.3 Balance Credit API
* 方法:`POST`
* URL`${playx.api.base_url}${playx.api.balance_credit_url}`
* 默认:`/api/v1/balance/credit`
#### 请求 Body商城侧发送
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 是 | 形如 `mall_withdraw_{uniqid}` |
| `externalTransactionId` | string | 是 | `MallPlayxOrder.external_transaction_id` |
| `user_id` | string | 是 | PlayX 用户 ID |
| `amount` | number | 是 | `MallPlayxOrder.amount` |
| `multiplier` | int | 是 | `MallPlayxOrder.multiplier` |
#### 返回(期望)
与 Bonus Grant 一致:
* `data.status === "accepted"` -> 读取 `playx_transaction_id`
* 否则 -> 读取 `message` 写入 `fail_reason`
示例accepted
```json
{
"status": "accepted",
"playx_transaction_id": "PX_TX_002"
}
```
示例rejected
```json
{
"status": "rejected",
"message": "insufficient balance"
}
```
---
### 2.4 Transaction Status Query API交易终态查询
* 方法:`GET`
* URL`${playx.api.base_url}${playx.api.transaction_status_url}`
* 默认:`/api/v1/transaction/status`
#### Query 参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `externalTransactionId` | string | 是 | 订单幂等键 `external_transaction_id` |
#### 返回(期望)
定时任务读取 `data.status`
* `COMPLETED`:商城将订单 `status` 更新为 `COMPLETED`
* `FAILED``REJECTED`:商城将订单 `status=REJECTED``grant_status=FAILED_FINAL`,并退回积分
* 失败信息取 `data.message` 写入订单 `fail_reason`
示例completed
```json
{ "status": "COMPLETED" }
```
示例failed
```json
{ "status": "FAILED", "message": "grant rejected by PlayX" }
```
---
## 3. 积分商城 -> H5服务端提供给 H5 的接口)
## 3. 积分商城 → H5服务端提供给 H5 的接口)
### 3.0 数据模型说明(与代码一致)
* **商城用户**`mall_user`(主键 `id`)。
* **PlayX 资产扩展**`mall_playx_user_asset`,与 `mall_user` **一对一**`mall_user_id` 唯一,`playx_user_id` 一)。
* **对外业务 ID**:接口里返回或订单里使用的 `user_id` 字符串多为 **PlayX 侧用户 ID**`playx_user_id`H5 临时登录场景若尚无真实 PlayX ID会生成形如 **`mall_{mall_user.id}`** 的占位 ID`temLogin`)。
* **服务端内部**`Playx` 控制器内部用 **`mall_user.id`**整型)解析资产;`session_id` / `token` / `user_id` 会映射到该 `mall_user`
* **积分商城用户资产主表**`mall_user_asset`(账号、积分、`playx_user_id`H5 临时登录 `temLogin` 直接创建/复用该表行,**不依赖**独立会员 `user`)。
* **会话缓存**`mall_session`(字段含 `session_id``user_id`(此处存 **PlayX 侧用户标识字符串**,与 `mall_user_asset.playx_user_id`致)、`expire_time`)。
* **统一订单**`mall_order`(红利/实物/提现订单;`user_id` 字段为 **`playx_user_id` 字符串**)。
* **对外业务 ID**:订单与推送中的 `user_id` 多为 **PlayX 用户 ID**`playx_user_id`)。临时登录场景下,资产表会生成占位 ID形如 **`mall_{mall_user_asset.id}`**`MallUserAsset::ensureForUsername`
### 3.1 鉴权解析规则(`resolveMallUserIdFromRequest`
### 3.1 鉴权解析规则(`resolvePlayxAssetIdFromRequest`
以下接口在服务端最终都会解析出 **商城用户 `mall_user.id`**,再按该用户查询 `mall_playx_user_asset`
以下接口在服务端最终都会解析出 **`mall_user_asset.id`(整型,资产表主键)**,再按该 ID 加载资产与关联数据
优先级(由高到低):
1. **`session_id`**`post` 优先,`get` 兼容)
*`mall_playx_session` 中存在且未过期:用会话里的 `user_id``playx_user_id`)在 `mall_playx_user_asset` 反查 `mall_user_id`
*`mall_session` 中存在且未过期:用会话里的 `user_id``playx_user_id` 字符串)在 `mall_user_asset` `playx_user_id` 查找,得到资产主键
* 若会话无效:兼容把 `session_id` 参数误当作 **商城 token** 再试一次UUID 形态 token
2. **`token`**`post` / `get` 或请求头 **`ba-token`** / **`token`**
* 校验 `token` 表:类型为会员 `user` 或商城临时 **`muser`**`mall_user` 登录),未过期`user_id` 字段**`mall_user.id`**。
* 校验 `token` 表:类型为会员 `user` 或商城临时 **`muser`**未过期时,`user_id` 字段为 **`mall_user_asset.id`**`muser`)或会员体系约定 ID`user`
3. **`user_id`**`post` / `get` 兼容)
* **纯数字**:视为 **`mall_user.id`**。
* **非纯数字**:视为 **`playx_user_id`**,在 `mall_playx_user_asset` 查找对应 `mall_user_id`
* **纯数字**:视为 **`mall_user_asset.id`**。
* **非纯数字**:视为 **`playx_user_id`**,在 `mall_user_asset` 按该字段查找主键
> 注意:请求参数的取值方式是 `post()` 优先,`get()` 兼容(即同字段既可传 post 也可传 get
@@ -373,19 +230,18 @@ curl -X POST 'http://localhost:1818/api/v1/mall/dailyPush' \
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `username` | string | 是 | 商城用户名(唯一);不存在则自动创建 `mall_user` |
| `username` | string | 是 | 登录名(唯一);不存在则自动创建 `mall_user_asset` |
#### 行为说明
* `mall_user` 不存在:创建用户(随机占位手机号、随机密码等,与后台「商城用户」一致)。
* **无论是否新用户**:保证存在 **`mall_playx_user_asset`** 一条记录(`MallPlayxUserAsset::ensureForMallUser``playx_user_id` 默认 **`mall_{mall_user.id}`**(与 PlayX 真实 ID 冲突概率低)
* 签发 **商城 token**(类型 **`muser`**,非会员表 `user`),并签发 `muser-refresh` 刷新令牌。
*用户名不存在:`MallUserAsset::ensureForUsername` 创建资产行(随机密码等),并将 `playx_user_id` 更新为 **`mall_{id}`** 形式(与真实 PlayX ID 区分)。
* 签发 **商城 token**(类型 **`muser`**`token` 表内 `user_id` = **`mall_user_asset.id`**),并签发 `muser-refresh` 刷新令牌
#### 返回(成功 data.userInfo
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | `mall_user.id` |
| `id` | int | **`mall_user_asset.id`** |
| `username` | string | 用户名 |
| `nickname` | string | 同 `username` |
| `playx_user_id` | string | 资产表中的 `playx_user_id`(如 `mall_12` |
@@ -413,10 +269,10 @@ curl -G 'http://localhost:1818/api/v1/temLogin' --data-urlencode 'username=demo_
* 配置项:`config/playx.php`**`verify_token_local_only`**(环境变量 **`PLAYX_VERIFY_TOKEN_LOCAL_ONLY`**,未设置时默认为 **`1` / 开启本地验证)。
* **`verify_token_local_only = true`(默认)**
* **不请求** PlayX HTTP。
* 仅接受商城临时登录 token类型 **`muser`**),校验 `token` 表后,根据 `mall_user``mall_playx_user_asset` 写入 `mall_playx_session`
* 返回的 `data.user_id`**`playx_user_id`**资产记录时回退为 `mall_user.id` 字符串,一般 temLogin 后已有资产)。
* 仅接受商城临时登录 token类型 **`muser`**),校验 `token` 表后写入 **`mall_session`**
* 返回的 `data.user_id`**`playx_user_id`**资产表一致)。
* **`verify_token_local_only = false`**(生产对接 PlayX
* 需配置 **`playx.api.base_url`**,由商城向 PlayX 发起 `POST` 校验(见下文「远程模式」)。
* 需配置 **`playx.api.base_url`**,由商城向 PlayX 发起 `POST` 校验(请求/响应约定见 **`docs/PlayX-接口待完善清单.md`** 第一部分 §1)。
* 若未配置 `base_url`,返回 `PlayX API not configured`
#### 请求参数
@@ -429,7 +285,7 @@ curl -G 'http://localhost:1818/api/v1/temLogin' --data-urlencode 'username=demo_
| 字段 | 类型 | 说明 |
|------|------|------|
| `session_id` | string | 写入 `mall_playx_session` |
| `session_id` | string | 写入 `mall_session` |
| `user_id` | string | PlayX 用户 ID`playx_user_id`,会话内与订单/推送一致) |
| `username` | string | 用户名 |
| `token_expire_at` | string | ISO 字符串(服务端 `date('c', expireAt)` |
@@ -484,37 +340,6 @@ Body`id` 必填,其余字段按需传入更新。
Body`id` 必填。若删除默认地址,服务端会自动挑选一条剩余地址设为默认(如存在)。
#### 远程模式(`verify_token_local_only=false` + 已配置 `base_url`
商城侧请求 URL`${playx.api.base_url}${playx.api.token_verify_url}`(默认路径 `/api/v1/auth/verify-token`)。
#### 请求 Body商城侧发送——仅远程模式
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 是 | 形如 `mall_{uniqid}` |
| `token` | string | 是 | 前端传入的 PlayX token |
#### 返回(期望)——仅远程模式
* HTTP 状态码必须为 `200`
* 且响应体中必须包含 `user_id`
响应(成功示例):
```json
{
"code": 1,
"msg": "",
"data": {
"session_id": "7b1c....",
"user_id": "U123",
"username": "demo_user",
"token_expire_at": "2026-04-01T12:00:00+00:00"
}
}
```
---
### 3.4 用户资产Assets
@@ -527,7 +352,7 @@ Body`id` 必填。若删除默认地址,服务端会自动挑选一条剩
* `session_id`
* `token`(或请求头 `ba-token` / `token`
* `user_id`(纯数字为 `mall_user.id`,否则为 `playx_user_id`
* `user_id`(纯数字为 **`mall_user_asset.id`**,否则为 **`playx_user_id`**
#### 返回(成功 data
若未找到资产:返回 0。
@@ -784,7 +609,7 @@ curl -X POST 'http://localhost:1818/api/v1/mall/withdrawApply' \
#### 返回(成功 data
* `list`:订单列表(最多 100 条),并包含关联的 `mallItem`(关系对象)
* 列表项中的 `user_id`**PlayX 侧 `playx_user_id`**(字符串),与 `mall_playx_order.user_id` 一致
* 列表项中的 `user_id`**PlayX 侧 `playx_user_id`**(字符串),与 `mall_order.user_id` 一致
#### 示例
请求:

View File

@@ -526,7 +526,7 @@ curl -G 'http://localhost:1818/api/v1/mall/orders' --data-urlencode 'session_id=
- 发货:录入物流公司、单号 → `SHIPPED`
- 驳回:录入驳回原因 → `REJECTED`,自动退回积分
- **红利/提现订单**
- 展示 `external_transaction_id``playx_transaction_id`推送playx
- 展示 `external_transaction_id`、推送 playx 状态
- 手动重试:仅对 `FAILED_RETRYABLE` 状态,记录 `retry_request_id`、操作者、原因
### 2.3 用户资产与人工调账
@@ -613,8 +613,7 @@ curl -G 'http://localhost:1818/api/v1/mall/orders' --data-urlencode 'session_id=
| points_cost | int | 消耗积分 |
| amount | decimal(15,2) | 现金面值(红利/提现) |
| multiplier | int | 流水倍数 |
| external_transaction_id | varchar(64) | 订单号 |
| playx_transaction_id | varchar(64) | PlayX 流水号 |
| external_transaction_id | varchar(64) | 订单号(商城侧幂等/对账主键Angpow 流程不单独落库 PlayX 内部流水号) |
| grant_status | enum | NOT_SENT / SENT_PENDING / ACCEPTED / FAILED_RETRYABLE / FAILED_FINAL |
| fail_reason | text | 失败原因 |
| retry_count | int | 重试次数 |