项目文档

This commit is contained in:
2026-04-20 10:50:00 +08:00
parent b019fc2a62
commit c9975df04a
3 changed files with 1074 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
# 「36字花」数据库与实施计划 # 「36字花」数据库与实施计划
**文档版本**V1.10 **文档版本**V1.14
**依据**《36字花 PRD》《业务流程说明书》《后端系统规格书》及现有表 `user` **依据**《36字花 PRD》《业务流程说明书》《后端系统规格书》及现有表 `user`
**目标**:明确分阶段落地步骤、需新建表、两表适配方向与可执行验证清单。 **目标**:明确分阶段落地步骤、需新建表、两表适配方向与可执行验证清单。
@@ -27,7 +27,7 @@
| **期号 / 对局** | **全平台共用一套** `game_period`(及全网唯一的当前 `period_no`、同一套状态机)。不存在「每个渠道一局」或「每渠道独立期号表」。 | | **期号 / 对局** | **全平台共用一套** `game_period`(及全网唯一的当前 `period_no`、同一套状态机)。不存在「每个渠道一局」或「每渠道独立期号表」。 |
| **开奖结果** | **全渠道玩家共享同一期、同一 `result_number`**。所有玩家(无论来自哪个 `channel_id`)压的是**同一场**;避免用户感知「不同入口有不同局、可被渠道操纵」。 | | **开奖结果** | **全渠道玩家共享同一期、同一 `result_number`**。所有玩家(无论来自哪个 `channel_id`)压的是**同一场**;避免用户感知「不同入口有不同局、可被渠道操纵」。 |
| **渠道的用途** | `channel``user.channel_id`、注单上的 **`channel_id` 快照**(若有)仅用于 **代理归属、分润、风控与后台数据范围****不**用于生成多套并行对局或独立开奖。 | | **渠道的用途** | `channel``user.channel_id`、注单上的 **`channel_id` 快照**(若有)仅用于 **代理归属、分润、风控与后台数据范围****不**用于生成多套并行对局或独立开奖。 |
| **Redis / 接口** | 当前期倒计时、封盘、算票、开奖号码等 **热状态全局一份**;不得在业务上按渠道维护多套「当前期」或多套开奖结果。 | | **Redis / 接口** | 当前期倒计时、封盘、算票、开奖号码等 **热状态全局一份**;不得在业务上按渠道维护多套「当前期」或多套开奖结果。**已实现2026-04**`GameHotDataRedis`**`user` 全行、`game_config` 按 key、`game_record`(活跃局 / 按 id / 最新一行)** 以 JSON 缓存在 Redis键前缀 `dfw:v1:`),配置见 `config/game_hot_cache.php``.env-example``GAME_HOT_CACHE_*`;与 `config/cache.php``CACHE_DRIVER`(系统表 `config` 文件缓存)**相互独立**。 |
> **与 DDL 的对应**`game_period` **不设** `channel_id``bet_order.channel_id` 为下单时用户归属快照,不改变「全场一局」语义。详见 §11.5。 > **与 DDL 的对应**`game_period` **不设** `channel_id``bet_order.channel_id` 为下单时用户归属快照,不改变「全场一局」语义。详见 §11.5。
@@ -42,7 +42,7 @@
| 阶段 | 目标 | 主要内容 | | 阶段 | 目标 | 主要内容 |
|------|------|----------| |------|------|----------|
| **P0** | 数据地基 | 新建 `channel` 并适配 `user``admin``admin_group`;建立账本类最小表集(金额精度统一) | | **P0** | 数据地基 | 新建 `channel` 并适配 `user``admin``admin_group`;建立账本类最小表集(金额精度统一) |
| **P1** | 游戏核心 | 期号、注单、开奖、系统配置Redis 状态机 + MQ 异步落库 | | **P1** | 游戏核心 | 期号、注单、开奖、系统配置;**Redis 热点读缓存user / game_config / game_record已落地**;可选 MQ 异步落库等后续增强 |
| **P2** | 资金与风控 | 充提订单、流水账、提现审核、Jackpot / 大额拦截 | | **P2** | 资金与风控 | 充提订单、流水账、提现审核、Jackpot / 大额拦截 |
| **P3** | 代理与结算 | 流水占比分桶、级差、联营占成与负结转(表结构 + 跑批骨架) | | **P3** | 代理与结算 | 流水占比分桶、级差、联营占成与负结转(表结构 + 跑批骨架) |
| **P4** | 运营与审计 | 公告(含 Pop-out、站内信、后台 RBAC 与操作审计 | | **P4** | 运营与审计 | 公告(含 Pop-out、站内信、后台 RBAC 与操作审计 |
@@ -70,7 +70,7 @@
| 高 | **渠道**、**游戏用户**、**角色组 / 管理员**(含渠道与邀请码相关字段) | 搭好「谁管哪条线、玩家归谁」 | | 高 | **渠道**、**游戏用户**、**角色组 / 管理员**(含渠道与邀请码相关字段) | 搭好「谁管哪条线、玩家归谁」 |
| 高 | **用户管理 / 用户钱包流水** | 覆盖玩家主数据与账务对账 | | 高 | **用户管理 / 用户钱包流水** | 覆盖玩家主数据与账务对账 |
| 高 | **游戏对局 / 压注订单** | 核心玩法与对账入口 | | 高 | **游戏对局 / 压注订单** | 核心玩法与对账入口 |
| 中 | **游戏配置 / 36字花字典** | 运营参数与字典维护 | | 中 | **游戏配置**(侧栏目录名):**常规配置**`game_config` 通用 KV列表默认每页 50 条,不含 `deposit_tier` / `streak_win_reward` / `zi_hua_36_dictionary`)、**连胜奖励**、**充值档位**、**36字花字典** | 运营参数与字典维护 |
| 中 | **充值订单 / 提现订单** | 充提流程管理与审核 | | 中 | **充值订单 / 提现订单** | 充提流程管理与审核 |
| 低 | **公告 / 站内信**、**代理结算** 等 | 属 **P3P4**,核心跑通后再接 | | 低 | **公告 / 站内信**、**代理结算** 等 | 属 **P3P4**,核心跑通后再接 |
@@ -98,8 +98,18 @@
- `order/betOrder``order/depositOrder``order/withdrawOrder` - `order/betOrder``order/depositOrder``order/withdrawOrder`
- `game/period` - `game/period`
- `config/gameConfig``config/ziHuaDictionary` - `config/gameConfig``config/ziHuaDictionary`
- `record/userWalletRecord` - `user/userWalletRecord`
5. 清理收口:删除迁移过程中的冗余权限别名规则,仅保留最终模块节点(`user/order/game/config/record`)。 5. 清理收口:删除迁移过程中的冗余权限别名规则,仅保留最终模块节点(`user/order/game/config/record`)。
6. 运营模块:`operation_notice``user_notice_read` 表与后台目录 **`operation`**(子菜单 `operation/operationNotice``operation/userNoticeRead`)。
### 2.7 `game_config` 连胜与派彩2026-04 起)
| `config_key` | 说明 |
|--------------|------|
| `streak_win_reward` | JSON`rows[]` 每项含 `streak`110`odds_factor`(与 33 相乘为整段赔率)、`is_jackpot`(是否大奖)。派彩公式:`total_amount × odds_factor × 33`。默认第 10 档 `is_jackpot=true`。 |
| `deposit_tier` | 仍由「充值档位」独立菜单维护,**不出现在**「常规配置」列表。 |
开奖结算后更新 **`user.current_streak`**:本期有中奖注单则 `min(streak_at_bet+1, 10)`,否则 **连胜归 0**(无档位配置)。若命中大奖档,向玩家 **私有频道** `private-user-{uuid}`**公共频道** `public-game-period` 推送事件 **`jackpot.hit`**(负载含 `period_no``user_id``total_win_amount``result_number` 等)。
--- ---
@@ -113,7 +123,7 @@
| 方向 | 说明 | | 方向 | 说明 |
|------|------| |------|------|
| 顶级代理归属 | `top_admin_id`关联到顶级代理管理员 `admin.id` | | 渠道负责人 | `admin_id`渠道对应一名管理员(`admin.id`),权限与数据范围以该账号为准;不再单独维护 `top_admin_id` |
| 代理模式参数 | `agent_mode``turnover` 普通刷水 / `affiliate` 联营)、`turnover_share_rate``affiliate_share_rate``affiliate_fee_rate``carryover_balance` | | 代理模式参数 | `agent_mode``turnover` 普通刷水 / `affiliate` 联营)、`turnover_share_rate``affiliate_share_rate``affiliate_fee_rate``carryover_balance` |
| 渠道经营指标 | `profit_amount`(精度升级)、`total_profit_amount``commission_pool_amount` | | 渠道经营指标 | `profit_amount`(精度升级)、`total_profit_amount``commission_pool_amount` |
| 邀请码参数 | 渠道不生成邀请码,邀请码由管理员自动生成并用于注册归属 | | 邀请码参数 | 渠道不生成邀请码,邀请码由管理员自动生成并用于注册归属 |
@@ -137,7 +147,7 @@
|------|------| |------|------|
| 金额精度 | `coin` 调整为 **`decimal(18,4)`**(与规格书一致,禁止用浮点存币) | | 金额精度 | `coin` 调整为 **`decimal(18,4)`**(与规格书一致,禁止用浮点存币) |
| 注册与归属 | `invite_code` 或注册时解析 URL 与 `channel.code` 绑定 | | 注册与归属 | `invite_code` 或注册时解析 URL 与 `channel.code` 绑定 |
| 提现门槛 | `total_deposit_coin``total_valid_bet_coin` 或等价的「待完成流水」字段(全项目统一一种口径 | | 提现门槛 | `user.total_deposit_coin` / `user.total_withdraw_coin` / `user.bet_flow_coin` + `game_config.withdraw_bet_flow_ratio`(净充值打码口径,全项目统一) |
| 风控 | 禁止登录 / 禁止下注 / 禁止提现等,可用位标记或独立布尔字段 | | 风控 | 禁止登录 / 禁止下注 / 禁止提现等,可用位标记或独立布尔字段 |
| 连胜持久化 | 可选 `current_streak``last_bet_period_id`;高频仍以 **Redis** 为准DB 作兜底同步 | | 连胜持久化 | 可选 `current_streak``last_bet_period_id`;高频仍以 **Redis** 为准DB 作兜底同步 |
| 邮箱 | PRD 支持手机或邮箱注册时,可增加可空字段 `email`(是否唯一索引按产品定) | | 邮箱 | PRD 支持手机或邮箱注册时,可增加可空字段 `email`(是否唯一索引按产品定) |
@@ -152,16 +162,14 @@
| 渠道归属 | `channel_id` 表示该子代理属于哪个顶级渠道 | | 渠道归属 | `channel_id` 表示该子代理属于哪个顶级渠道 |
| 邀请管理 | `invite_code` 自动生成且全局唯一,用于发展玩家并做归属绑定 | | 邀请管理 | `invite_code` 自动生成且全局唯一,用于发展玩家并做归属绑定 |
| 角色标识 | `agent_role`(如 `agent_admin` / `sub_agent` / `staff` | | 角色标识 | `agent_role`(如 `agent_admin` / `sub_agent` / `staff` |
| 分红设置 | `commission_rate`百分比0-100最多两位小数管理员角色组改为单选同一角色组下管理员分红比例总和不超过100% | | 分红设置 | 不再使用 `admin` / `admin_group` 分红字段;统一改为渠道维度结算后按 `channel_admin_share` 二次分配 |
| 开奖权限 | 不在数据层开放给渠道/子代理;开奖权限仅由超管 RBAC 控制 | | 开奖权限 | 不在数据层开放给渠道/子代理;开奖权限仅由超管 RBAC 控制 |
### 5.1 角色组与管理员分红计算口径 ### 5.1 分红计算口径(现行)
- 角色组分红:`当前角色分红 = 渠道设置获取分红 × (1 - 上级角色分红比例) × 当前角色分红比例` - 渠道分红:先按 `channel.agent_mode` 计算渠道总佣金
- 管理员分红:`当前管理员分红 = 当前角色分红 × 当前管理员分红比例` - 管理员分红:再按 `channel_admin_share.share_rate` 对渠道总佣金进行二次分配
- 校验约束: - 角色组仅用于权限与数据范围,不再参与金额拆分
- 同一父级下角色组分红比例总和 `<= 100%`
- 同一角色组下管理员分红比例总和 `<= 100%`
--- ---
@@ -245,7 +253,8 @@
- `order/depositOrder``order/withdrawOrder``order/betOrder` - `order/depositOrder``order/withdrawOrder``order/betOrder`
- `game/period` - `game/period`
- `config/gameConfig``config/ziHuaDictionary` - `config/gameConfig``config/ziHuaDictionary`
- `record/userWalletRecord` - `user/userWalletRecord`
- `operation/operationNotice``operation/userNoticeRead`
4. 验证 `config/ziHuaDictionary` 的读取与保存权限均命中,且仅授权角色可操作。 4. 验证 `config/ziHuaDictionary` 的读取与保存权限均命中,且仅授权角色可操作。
### 8.2 业务规则(对照 PRD / 业务流程) ### 8.2 业务规则(对照 PRD / 业务流程)
@@ -269,7 +278,10 @@
### 8.4 非功能 ### 8.4 非功能
- Redis:当前期注单池、倒计时、近 30 期开奖缓存。 - **Redis(已落地)**
- **热点数据缓存**`app/common/service/GameHotDataRedis.php`,依赖 `webman/redis``config/redis.php`。缓存对象:`user` 行(鉴权与余额类读路径)、`game_config` 行(按 `config_key`)、`game_record`(活跃局、按 id、最新一条。写库后通过模型事件或服务内 **`gameRecordForget` / `gameConfigForget` / `userForget`** 失效;环境变量 `GAME_HOT_CACHE_*` 控制开关与 TTL`.env-example`)。
- **规划/增强**(未替代上述):当前期注单池、近 30 期开奖列表缓存、纯 Redis 状态机等,可按压测需求迭代。
- **与 `CACHE_DRIVER`**`config/cache.php` 默认 `file` 仅影响 **系统配置表 `config`**(如 `get_sys_config`**不**控制 `GameHotDataRedis`
- MQ派彩异步消费**幂等**(如 `period_id` + 用户 + 业务单号)。 - MQ派彩异步消费**幂等**(如 `period_id` + 用户 + 业务单号)。
- API下注、提现等核心接口**限流**生效。 - API下注、提现等核心接口**限流**生效。
@@ -279,7 +291,7 @@
1. **子代理数据源**:子代理统一在 `admin`,避免在 `channel` 重复建树造成双主数据源。 1. **子代理数据源**:子代理统一在 `admin`,避免在 `channel` 重复建树造成双主数据源。
2. **现有 `profit_amount`decimal(5,2)**:与游戏币 **18,4** 精度不一致,演进时改为 `decimal(18,4)` 或迁移至结算域,避免对账误差。 2. **现有 `profit_amount`decimal(5,2)**:与游戏币 **18,4** 精度不一致,演进时改为 `decimal(18,4)` 或迁移至结算域,避免对账误差。
3. 文档要求 **AI 算票在 Redis 内完成**,主库避免结算期同步锁表;账本与注单以异步一致性与幂等为准。 3. 文档要求 **AI 算票在 Redis 内完成**(演进目标),当前实现以 **MySQL `game_record` + 服务层缓存** 为主;主库仍应避免结算期大范围锁表;账本与注单以事务与幂等为准。
4. **全局对局一致性**:任何需求(多租户展示、渠道后台)均不得引入「按渠道独立期号/独立开奖」;若出现产品歧义,以 **§1.1** 为准,避免公平性质疑与客诉。 4. **全局对局一致性**:任何需求(多租户展示、渠道后台)均不得引入「按渠道独立期号/独立开奖」;若出现产品歧义,以 **§1.1** 为准,避免公平性质疑与客诉。
--- ---
@@ -334,6 +346,9 @@
| V1.8 | 2026-04-15 | §7.1 游戏引擎表与 §1.1 对齐:显式说明全局期号、`bet_order.channel_id` 仅为归属快照 | | V1.8 | 2026-04-15 | §7.1 游戏引擎表与 §1.1 对齐:显式说明全局期号、`bet_order.channel_id` 仅为归属快照 |
| V1.9 | 2026-04-15 | 落地模块化重构:`game_user->user``game_bet_order->bet_order`;新增 `deposit_order`/`withdraw_order`;后台菜单重组为 `user/order/game/config/record` 五大目录 | | V1.9 | 2026-04-15 | 落地模块化重构:`game_user->user``game_bet_order->bet_order`;新增 `deposit_order`/`withdraw_order`;后台菜单重组为 `user/order/game/config/record` 五大目录 |
| V1.10 | 2026-04-15 | 完成表名与权限规则收口:`user_wallet_record``game_config` 保持不变;移除冗余 snake alias 菜单规则,文档口径与线上结构一致 | | V1.10 | 2026-04-15 | 完成表名与权限规则收口:`user_wallet_record``game_config` 保持不变;移除冗余 snake alias 菜单规则,文档口径与线上结构一致 |
| V1.11 | 2026-04-16 | 落地运营公告:`operation_notice``user_notice_read` 表与菜单目录 `operation`(运营公告、用户阅读记录) |
| V1.12 | 2026-04-18 | 下注口径:`bet_order` 仅保留 `total_amount`(整笔压注),删除 `unit_amount``pick_count`DDL 与 Phinx 迁移 `20260418270000_bet_order_drop_unit_amount_pick_count` 对齐 |
| V1.14 | 2026-04-20 | 服务端 Redis 热点缓存:`GameHotDataRedis``user` / `game_config` / `game_record``config/game_hot_cache.php``.env-example``GAME_HOT_CACHE_*`;更新 §1.1、§2.1、§8.4、第九章风险与依赖、附录 `user`;与 `CACHE_DRIVER`(系统 `config` 表文件缓存)区分说明 |
--- ---
@@ -353,8 +368,7 @@
| `profit_amount` / `total_profit_amount` / `commission_pool_amount` | 经营与分红池快照类金额,**decimal(18,4)** | | `profit_amount` / `total_profit_amount` / `commission_pool_amount` | 经营与分红池快照类金额,**decimal(18,4)** |
| `turnover_share_rate` / `affiliate_share_rate` / `affiliate_fee_rate` | 分红与联营费率类参数 | | `turnover_share_rate` / `affiliate_share_rate` / `affiliate_fee_rate` | 分红与联营费率类参数 |
| `carryover_balance` | 联营负结转余额(可负) | | `carryover_balance` | 联营负结转余额(可负) |
| `top_admin_id` | 顶级代理管理员,关联 `admin.id` | | `admin_id` / `admin_group_id` | 渠道负责人(管理员)、渠道绑定的角色组 |
| `admin_id` / `admin_group_id` | 创建人、渠道绑定的角色组 |
| `status` / `remark` / `create_time` / `update_time` | 状态、备注、时间戳 | | `status` / `remark` / `create_time` / `update_time` | 状态、备注、时间戳 |
### 11.2 `user`C 端玩家) ### 11.2 `user`C 端玩家)
@@ -364,9 +378,10 @@
| `coin` | 当前游戏币余额,**decimal(18,4)**,更新须条件更新防负余额 | | `coin` | 当前游戏币余额,**decimal(18,4)**,更新须条件更新防负余额 |
| `channel_id` | 归属渠道;历史 `game_channel_id` 若仍存在,迁移期注意双写/对照 | | `channel_id` | 归属渠道;历史 `game_channel_id` 若仍存在,迁移期注意双写/对照 |
| `register_invite_code` | 注册时邀请码快照,用于审计与归属 | | `register_invite_code` | 注册时邀请码快照,用于审计与归属 |
| `total_deposit_coin` / `total_valid_bet_coin` | 累计充值入账、累计有效投注;提现流水倍数校验用 | | `total_deposit_coin` / `total_withdraw_coin` | 累计充值入账、累计提现出账(提现受理时累加);与「净充值」口径配合校验提现门槛 |
| `bet_flow_coin` | 打码量/流水(历史名 `total_valid_bet_coin`2026-04-18 迁移重命名);开奖结算成功时按注单 `total_amount` 1:1 累加,提现门槛 `bet_flow_coin >= (total_deposit_coin - total_withdraw_coin) × withdraw_bet_flow_ratio` |
| `risk_flags` | 风控位(如禁止登录/下注/提现,按位定义) | | `risk_flags` | 风控位(如禁止登录/下注/提现,按位定义) |
| `current_streak` / `last_bet_period_no` | 连胜与期号兜底;高频仍以 Redis 为准 | | `current_streak` / `last_bet_period_no` | 连胜与期号兜底;**读路径**可通过 **`GameHotDataRedis` 缓存 `user` 行** 降低压力,**写路径**仍以下注/结算后落库为准并主动失效缓存 |
| `admin_id` | 归属子代理管理员 | | `admin_id` | 归属子代理管理员 |
| 其余 | 账号、头像、状态、时间戳等见 DDL | | 其余 | 账号、头像、状态、时间戳等见 DDL |
@@ -379,7 +394,7 @@
| `admin.invite_code` | 子代理邀请码,注册归属 | | `admin.invite_code` | 子代理邀请码,注册归属 |
| `admin.agent_role` | 角色类型(均无开奖权) | | `admin.agent_role` | 角色类型(均无开奖权) |
| `admin_group.channel_id` | 角色组归属渠道;`NULL` 可为系统级(仅超管) | | `admin_group.channel_id` | 角色组归属渠道;`NULL` 可为系统级(仅超管) |
| `admin_group.commission_rate` | 角色组分红比例(百分比 | | `channel_admin_share.share_rate` | 渠道内管理员分配比例(百分比,启用项合计=100 |
### 11.4 `game_config`(动态参数) ### 11.4 `game_config`(动态参数)
@@ -394,7 +409,7 @@
| 表 | 要点 | | 表 | 要点 |
|----|------| |----|------|
| `game_period` | **全平台唯一**期号:`period_no` 全局唯一;`status` 状态机;开奖结果与作废原因。**无 `channel_id` 字段**:对局不按渠道拆分。 | | `game_period` | **全平台唯一**期号:`period_no` 全局唯一;`status` 状态机;开奖结果与作废原因。**无 `channel_id` 字段**:对局不按渠道拆分。 |
| `bet_order` | 注单关联**全局** `period_id``channel_id`(若有)为**用户/归属快照**,便于分润与查询,**不表示**独立牌桌或独立开奖。`idempotency_key` 幂等;金额 **18,4**`win_amount` / `jackpot_extra_amount` | | `bet_order` | 注单关联**全局** `period_id``channel_id`(若有)为**用户/归属快照**,便于分润与查询,**不表示**独立牌桌或独立开奖。`pick_numbers` 为所选号码集合;**`total_amount` 为整笔压注金额**(命中集合内任一开奖号码即按该金额参与派彩倍率计算)。已移除历史字段 `unit_amount` / `pick_count``idempotency_key` 幂等;金额 **18,4**`win_amount` / `jackpot_extra_amount` |
| `game_bet_auto` | 托管剩余局数、选号快照、抖动时间等;仍挂在**全局**期号下 | | `game_bet_auto` | 托管剩余局数、选号快照、抖动时间等;仍挂在**全局**期号下 |
### 11.6 `user_wallet_record`(玩家钱包流水) ### 11.6 `user_wallet_record`(玩家钱包流水)

View File

@@ -0,0 +1,854 @@
# 36字花移动端接口设计草案V1
本文基于 `docs/36字花-数据库与实施计划.md` 与 PRD先给出移动端可对接的接口清单与字段初设。
口径遵循:**全平台单期号、单开奖结果**;渠道仅用于归属、分润与风控,不拆分对局。
**补充2026-04**:§**1.5** 描述服务端 **Redis 热点缓存**`GameHotDataRedis`**不改变**各接口 URL、参数与响应字段约定仅供联调与运维对照。
## 1. 设计约定
### 1.1 基础约定
- 协议HTTPS + JSON
- 接口命名规范:`/api/{module}/{action}`,且必须满足正则 `^/api/[a-z]+/[a-z]+[A-Z][a-zA-Z]*$`
- **请求方法**:所有移动端业务接口(`/api/*`,不含 `/api/v1/authToken`)一律使用 `POST` 调用;查询类接口同时兼容 `GET`(便于浏览器/调试工具直接访问),客户端统一走 `POST`
- `POST` 时请求头 `Content-Type: application/json`,参数放在 JSON body
- `GET` 兼容模式下,参数走 URL query string
- **例外**:公告模块 `/api/notice/noticeList``/api/notice/noticeDetail``/api/notice/noticeConfirm` **仅支持 `GET`**,参数一律走 URL query string
- 鉴权类接口 `/api/v1/authToken` 仍为 `GET`
- 时间UTC 时间戳(秒) + 服务端时区配置
- 金额:字符串传输(如 `"100.00"`),客户端展示统一保留两位小数(存储仍为 `decimal(18,4)`
- 幂等:关键写接口要求 `idempotency_key`
- 请求头(必带):
- `auth-token`:通过 `GET /api/v1/authToken` 获取的接口鉴权令牌(含义:接口访问的签名鉴权凭证)
- `user-token`:用户登录态令牌;需要登录的接口必带
- 语言请求头:
- `lang=zh`:返回中文(默认)
- `lang=en`:返回英文
### 1.2 通用响应结构
```json
{
"code": 1,
"message": "ok",
"data": {}
}
```
- `code=1` 表示成功,非 1 为业务错误
- `/api/*` 所有接口返回文案支持中英双语:默认中文;请求头 `lang=en` 返回英文,`lang=zh` 返回中文
- 建议错误码段(按错误性质):
- `1000-1099`:参数错误(字段缺失、类型错误、格式错误、超范围)
- `1100-1199`鉴权错误未登录、token 失效、权限不足)
- `2000-2999`:业务错误(余额不足、对局不存在、订单不存在、公告不存在)
- `3000-3099`:流程错误(非法流程/状态不允许,如封盘后下注、重复确认、状态跃迁非法)
- `5000-5999`:系统错误(服务异常、依赖超时、未知错误)
- 推荐基础错误码(首版):
- `1`:成功
- `1001`:参数缺失
- `1002`:参数格式错误
- `1003`:参数取值非法
- `1101`:未登录或登录已过期
- `1103`:无权限操作
- `2001`:余额不足
- `2002`:对局不存在
- `2003`:订单不存在
- `2004`:公告不存在
- `3001`:当前流程不允许该操作
- `3002`:已封盘,禁止下注
- `3003`:重复请求(幂等冲突)
- `5000`:系统繁忙,请稍后重试
### 1.3 鉴权方式
- **接口鉴权auth-token**:所有移动端业务接口请求时必须携带请求头 `auth-token`(由 `/api/v1/authToken` 签发)
- **用户登录鉴权user-token**:需要登录的接口携带请求头 `user-token`token 失效后调用刷新或重新登录
### 1.4 获取接口鉴权 Tokenauth-token
- **GET** `/api/v1/authToken`
- 用途:获取 `auth-token`(所有接口请求头必带)
请求示例:
`/api/v1/authToken?secret=564d14asdasd113e46542asd6das1a2a&timestamp=1776331077&device_id=1&signature=AD84C49880896DBC16C59C7B122D1FF7`
请求参数:
- `secret`string含义客户端密钥服务端从环境变量 `AUTH_TOKEN_SECRET` 校验)
- `timestamp`int含义请求时间戳服务端允许与服务器时间误差 ±300 秒)
- `device_id`string含义设备码
- `signature`string含义签名值
签名算法:
- 取参与签名的参数(不含 `signature``device_id``secret``timestamp`
- 按参数名 **a-z** 排序后拼接为字符串:`key=value&key=value...`
- 计算:`signature = strtoupper(md5(拼接字符串))`
返回参数:
- `auth_token`string含义接口鉴权 token放到请求头 `auth-token`
- `expires_in`int含义有效期秒数
- `server_time`int含义服务器时间戳用于校时
可能错误码:
- `1001` 参数缺失
- `1002` 参数格式错误
- `1103` 密钥无效/签名错误
- `3001` 时间戳无效
### 1.5 服务端性能与 Redis 热点缓存(实现说明)
> **对客户端无契约变更**:请求路径、参数、响应 JSON 形状与错误码均不因缓存而改变;本节仅说明服务端如何降延迟、读路径与一致性注意点。
**与「框架文件缓存」的区别**
| 配置 | 作用域 |
|------|--------|
| `CACHE_DRIVER``config/cache.php`,如 `file` | Think-ORM / `get_sys_config()` 等**系统参数表 `config`** 的模型缓存,落盘在 `runtime/cache`**不参与**本游戏业务热点路径。 |
| `GAME_HOT_CACHE_*``config/game_hot_cache.php` | 游戏侧 **`user` / `game_config` / `game_record`** 行级 JSON 缓存,走 **`support\Redis`**`config/redis.php` 连接),键前缀 `dfw:v1:`。 |
**服务端缓存覆盖(与移动端直接相关的读路径)**
- **用户**:会员鉴权 `Auth::init` 优先读 Redis 中的 `user` 行快照,未命中再查库并回填;**余额、连胜、打码量等变更**后服务端会删除该用户缓存,下次请求重新加载。
- **游戏配置**`game_config``config_key` 缓存(如字典、`pick_max_number_count``withdraw_bet_flow_ratio`、连胜配置等);后台或脚本直连 `Db` 更新配置时,保存逻辑需调用 **`GameHotDataRedis::gameConfigForget($key)`**(已实现于模型事件及部分后台控制器),否则最长延迟为 TTL。
- **对局**:当前活跃局、按 `id` 加载的局、以及「最新一条 `game_record`」(与 `order id desc limit 1` 一致)缓存;**开奖、封盘、新建下一期**等写库后会 **`gameRecordForget`**,保证关键状态尽快一致。
**环境变量(示例见仓库根目录 `.env-example`**
- `GAME_HOT_CACHE_ENABLED`:是否启用上述 Redis 热点缓存(`false` 时全程回退数据库)。
- `GAME_HOT_CACHE_TTL_GAME_CONFIG` / `GAME_HOT_CACHE_TTL_GAME_RECORD` / `GAME_HOT_CACHE_TTL_USER`:各类缓存 TTL仍应以**写后失效**为主TTL 仅作兜底。
**一致性提示(联调/测试)**
- 在 TTL 窗口内,若存在**绕过模型事件、且未调用 forget** 的手工 SQL 改库,可能出现短时不一致;生产环境应避免。
- 客户端仍可按 **§3.2 `dictionaryList``version`** 做本地缓存;服务端字典数据另有 Redis 加速,二者可同时存在。
---
## 2. 认证与账户模块user
### 2.1 注册
- **POST** `/api/user/register`
- 用途仅手机号注册并绑定邀请归属admin/channel
请求参数:
- `username`string手机号含义注册账号仅支持大陆手机号
- `password`string明文经 HTTPS 传输(含义:登录密码,服务端需加盐哈希存储)
- `invite_code`string必填含义子代理邀请码用于绑定渠道 `channel_id` 与归属)
- `device_id`string可选含义设备标识用于风控与登录记录
返回参数:
- `user-token`string含义后续接口登录态令牌用于需要登录的接口请求头
- `refresh_token`string可选含义用于刷新访问令牌
- `expires_in`int含义令牌有效期
- `user`object仅返回非私密信息不返回 `id`
- `uuid`string含义用户对外唯一标识10 位)
- `username`string含义用户昵称/展示名)
- `coin`string含义当前余额
- `channel_id`int含义归属渠道 ID
- `risk_flags`int含义风控状态位
### 2.2 登录
- **POST** `/api/user/login`
请求参数:
- `username`string含义登录账号当前支持手机号
- `password`string含义登录密码
- `device_id`string可选含义设备标识辅助风控
返回参数:
- `user-token`string含义访问令牌用于需要登录的接口请求头
- `refresh_token`string可选含义用于刷新访问令牌
- `expires_in`int含义访问令牌剩余有效秒数
- `user`object仅返回非私密信息不返回 `id`
- `uuid`string含义用户对外唯一标识10 位)
- `username`string含义用户昵称/展示名)
- `coin`string含义当前余额
- `channel_id`int含义归属渠道 ID
- `risk_flags`int含义风控状态位
### 2.3 获取当前用户信息
- **POST** `/api/user/profile`
返回参数(金额类字段统一 4 位小数字符串,与 `/api/wallet/balanceSummary` 对齐):
**基础档案**
- `uuid`string含义用户对外唯一标识10 位)
- `username`string含义昵称
- `head_image`string含义头像地址
- `phone`string含义手机号
- `email`string含义邮箱
- `register_invite_code`string含义注册邀请码快照
- `channel_id`int含义归属渠道 ID
- `risk_flags`int含义风控状态位
- `current_streak`int含义当前连胜次数
- `last_bet_period_no`string含义最近一笔有效下注所在期号
- `create_time`int含义注册时间戳
**资金与提现配额**
- `coin` / `coin_balance`string含义当前余额两字段同值便于与 `/api/wallet/balanceSummary` 平滑切换)
- `frozen_balance`string含义冻结余额无冻结场景固定 `0.0000`
- `total_deposit_coin`string含义累计充值
- `total_withdraw_coin`string含义累计提现受理后累加
- `bet_flow_coin`string含义打码量/流水;开奖结算后按每注 `total_amount` 1:1 累加)
- `max_withdrawable`string含义**当前允许发起的单笔最大提现金额** = `min(coin_balance, max_withdraw_by_flow)`
- `withdraw_flow`object含义打码量 / 提现配额诊断快照,结构与 `/api/wallet/balanceSummary.withdraw_flow` 一致,此处额外附 `pending_withdraw`
- `ratio`string打码量倍数`0` 表示不限打码)
- `net_deposit`string净充值 = max(0, 累计充值 累计提现)
- `required_bet_flow`string按门槛口径所需打码量纯展示
- `remaining_bet_flow`string按门槛口径还差多少打码量纯展示
- `eligible`bool是否满足整体门槛纯展示真正放行以 `max_withdrawable` 为准)
- `max_withdraw_by_flow`string/null仅按打码量折算的上限`ratio=0` 时为 `null`
- `flow_unlimited`bool是否处于"不限打码"状态)
- `pending_withdraw`object
- `count`int当前待审核提现订单数
- `max`int单用户最多允许的待审核提现数当前为 `3`;超过 `withdrawCreate` 返回 `code=2004`
### 2.4 刷新令牌(可选)
- **POST** `/api/user/refreshToken`
请求参数:
- `refresh_token`string含义续签访问令牌的凭证
返回参数:
- `user-token`string含义新访问令牌
- `expires_in`int含义新令牌有效期
---
## 3. 游戏大厅与字典模块game/lobby
### 3.1 获取首页初始化数据
- **POST** `/api/game/lobbyInit`
- 用途一次返回本局、配置、36字花字典、用户快捷展示
返回参数:
- `server_time`int含义服务端当前时间用于客户端校时
- `period`object
- `period_no`string含义当前全局期号
- `status`string`betting`/`locked`/`settling`/`finished`,含义:当前期状态)
- `countdown`int含义当前期倒计时秒数
- `lock_at`int含义封盘时间戳
- `open_at`int含义预计开奖时间戳
- `bet_config`object
- `pick_max_number_count`int含义单注最多可选号码数来自 `game_config.config_key = pick_max_number_count`,缺省与库内种子一致,通常为 10合法范围 136
- `chips`array[string](如 `["1.00","5.00"]`,含义:快捷筹码面额)
- `single_number_max_bet`string含义单号码最大下注额
- `dictionary`array<object>
- `number`int1-36含义字花编号
- `name`string含义字花名称
- `category`string含义字花分类
- `icon`string含义图标资源地址
- `user_snapshot`object`coin``current_streak`,含义:用户状态快照)
### 3.2 获取36字花字典可缓存
- **POST** `/api/game/dictionaryList`
返回参数:
- `version`string含义字典版本号前端可用于缓存比对
- `items`:同 `dictionary`含义36字花字典明细
### 3.3 获取最近开奖记录近30期
- **POST** `/api/game/periodHistory`
请求参数:
- `limit`int可选默认 30含义返回最近几期
返回参数:
- `list`array<object>
- `period_no`string含义历史期号
- `result_number`int含义该期开奖结果
- `open_time`int含义开奖时间
---
## 4. 下注与对局模块game/bet
### 4.1 获取当前期详情
- **POST** `/api/game/periodCurrent`
返回参数:
- `period_id`int含义当前期主键 ID
- `period_no`string含义当前期号
- `status`string含义当前期状态
- `countdown`int含义当前期剩余秒数
- `bet_close_in`int含义距离封盘剩余秒数
- `result_number`int/null未开奖为 null含义开奖号码
### 4.2 提交下注
- **POST** `/api/game/betPlace`
- 用途:单期手动下注;玩家只需选择**压注号码**与**本笔压注总金额**。开奖只出一个号码,若该号码 ∈ 所选号码集合即视为**中奖**,派彩按整笔 `bet_amount`(落库为 `total_amount`× 赔率计算(赔率与连胜倍率见服务端实现)。
请求参数:
- `period_no`string含义下注目标期号
- `numbers`string含义本次压注号码集合**英文逗号分隔**,如 `1,8,16`;每个号码为 136 的整数,数量不超过 `pick_max_number_count`(同 `lobbyInit.bet_config`),重复号码会去重)
- `bet_amount`string含义**本笔整笔压注金额**> 0服务端按此金额从余额扣款并写入注单 `total_amount`**不再**按「单号金额 × 号码个数」计算)
- `idempotency_key`string必填含义防止重复下单
返回参数:
- `order_no`string含义下注订单号
- `period_no`string含义实际落单期号
- `status`string`accepted`/`rejected`,含义:受理结果)
- `locked_balance`string可选含义冻结金额
- `balance_after`string含义下单后余额
- `current_streak`int含义下单后连胜快照
> 说明:一键重复上一注、自动托管开启/停止均由前端控制,客户端在相应时机调用 `/api/game/betPlace` 即可完成,不再提供独立接口。
### 4.3 查询我的下注记录最近1个月
- **POST** `/api/game/betMyOrders`
请求参数:
- `page`int可选默认 1
- `page_size`int可选默认 20
返回参数:
- `list`array<object>
- `order_no`string含义下注订单号
- `period_no`string含义所属期号
- `numbers`array[int](含义:下注号码)
- `bet_amount`string含义本笔整笔压注金额`total_amount` 相同)
- `total_amount`string含义本笔整笔压注金额
- `result_number`int/null含义开奖号码未开可空
- `win_amount`string含义中奖金额
- `status`string含义订单状态
- `create_time`int含义下注时间
- `pagination`object`page``page_size``total`,含义:分页信息)
---
## 5. 钱包与资金模块wallet/finance
### 5.1 获取钱包摘要
- **POST** `/api/wallet/balanceSummary`
返回参数:
- `coin_balance`string含义可用余额等同于 `user.coin`
- `frozen_balance`string含义冻结余额当前系统无冻结场景固定返回 `0.0000`
- `withdrawable_balance`string含义可提现余额等同于 `coin_balance`**单笔实际上限以 `max_withdrawable` 为准**
- `max_withdrawable`string含义**当前允许发起的单笔最大提现金额** = min(`coin_balance`, 打码配额余量);客户端直接用于"最大可提 XXX"提示与金额输入上限校验)
- `total_deposit_coin`string含义累计充值币额
- `total_withdraw_coin`string含义累计提现币额提现受理时累加
- `bet_flow_coin`string含义打码量/流水;开奖结算后按每注 `total_amount` 1:1 累加)
- `withdraw_flow`object含义打码量 / 提现配额诊断快照,供前端展示说明与上限推导)
- `ratio`string含义打码量倍数来自 `game_config.withdraw_bet_flow_ratio`,默认 `1.0000``0` 表示"不限打码"
- `net_deposit`string含义净充值 = max(0, 累计充值 - 累计提现)
- `required_bet_flow`string含义按门槛口径所需的打码量 = 净充值 × 倍数,纯展示)
- `remaining_bet_flow`string含义按门槛口径还差多少打码量纯展示已达标为 `0.0000`
- `eligible`bool含义是否满足整体门槛纯展示实际放行以 `max_withdrawable` 为准)
- `max_withdraw_by_flow`string/null含义仅按打码量折算的单笔可提上限 = max(0, `bet_flow_coin` / `ratio` - `total_withdraw_coin`)`ratio=0` 不限打码时返回 `null`
- `flow_unlimited`bool含义是否处于"不限打码"状态,`ratio=0` 时为 `true`
说明(打码量即提现配额模型):
- 每笔提现按 `withdraw_coin × ratio` 消耗打码配额;`total_withdraw_coin` 已累积历史消耗。
- **单笔最大可提现 `max_withdrawable = min(coin_balance, max_withdraw_by_flow)`**`ratio=0` 时退化为仅受余额约束。
- 倍数 `ratio` 由后台「游戏配置 → `withdraw_bet_flow_ratio`」维护,修改后对新请求立即生效。
- 历史累计类字段(`total_deposit_coin` / `total_withdraw_coin` / `bet_flow_coin`)均为累加语义;若后续审核驳回,回冲逻辑由后台审核流程负责。
### 5.2 钱包流水
- **POST** `/api/wallet/recordList`
请求参数:
- `page`int可选默认 1
- `page_size`int可选默认 20
- `type`string可选含义流水类型筛选可选值如下不传表示查询全部
- `deposit`:充值入账(充值订单成功后,金额入账到玩家余额)
- `withdraw`:提现出账(提现订单受理/打款后,金额从玩家余额扣除或冻结)
- `bet`:下注扣款(提交下注时从玩家余额扣除的投注金额)
- `payout`:开奖派彩(中奖后系统将奖金入账到玩家余额)
- `adjust`:人工调整(后台管理员加/扣点,对应 `biz_type=admin_credit/admin_deduct`
返回参数:
- `list`array<object>
- `record_id`int含义钱包流水 ID
- `biz_type`string含义业务类型
- `direction`int1入2出含义资金方向
- `amount`string含义本次变动金额
- `balance_before`string含义变动前余额
- `balance_after`string含义变动后余额
- `ref_type`string含义关联业务单类型
- `ref_id`string含义关联业务单标识
- `create_time`int含义流水时间
补充约定:
- 金额字段(`amount``balance_before``balance_after` 等)客户端显示统一两位小数。
- 后台管理员加减点会生成 `biz_type=admin_credit/admin_deduct` 的流水记录,备注默认模板:`后台管理员(操作管理员)加点/扣点100`(示例)。
### 5.3 充值档位列表
- **POST** `/api/finance/depositTierList`
说明:
- 由后台「配置管理 → 充值档位」维护,存放在 `game_config.deposit_tier`JSON 数组)。
- 仅返回启用状态(`status=1`)的档位,按 `sort` 升序;玩家仅能从中选择。
- 档位仅描述"充值规格",不再包含收款账户;具体收款由第三方支付网关返回的 `pay_url` 引导。
- **多语言**:后台保存 `title`(中文名)、`title_en`(英文名)、`desc`(中文描述)、`desc_en`(英文描述)。接口返回的 `title` / `desc` 会根据请求头 `lang` 自动适配:
- `lang=zh`(默认):返回 `title` / `desc`,若为空则回退到英文
- `lang=en`:返回 `title_en` / `desc_en`,若为空则回退到中文
- 移动端客户端仅看到单一 `title` / `desc`,无需自行判断语言
请求参数:无(无需 body 与 query
返回参数:
- `list`list档位列表每一项结构
- `id`string含义档位稳定 ID创建订单时原样回传
- `title`string含义档位名称已按 `lang` 头切换;例如 `lang=en` 下返回 `"Starter Pack"``lang=zh` 下返回 `"新手首充礼包"`
- `amount`string4 位小数,含义:玩家本次需支付的充值金额)
- `bonus_amount`string4 位小数,含义:该档位赠送金额,无赠送为 `0.0000`
- `total_amount`string4 位小数,含义:到账总额 = amount + bonus_amount方便前端直接展示"到账 120"
- `desc`string含义档位描述/活动文案,已按 `lang` 头切换;可空)
### 5.4 创建充值订单
- **POST** `/api/finance/depositCreate`
- `Content-Type: application/json`(推荐)或 `application/x-www-form-urlencoded`
说明:
- **当前版本mock 支付网关**。玩家在客户端选中档位并点击"立即充值"即视为支付成功:服务端在同一请求内完成订单创建与钱包入账,返回 `paid=true``status=paid``pay_url=""`
- **未来第三方支付接入**:保持请求/响应形状不变。接口仅改为创建 `status=0` 订单并返回 `pay_url``paid=false``status=pending`);实际入账由第三方回调触发,服务端通过 `app\common\library\finance\DepositSettlement::settle` 完成钱包加币,客户端通过 `GET /api/finance/depositDetail` 轮询最终状态。
- 档位取自 `GET /api/finance/depositTierList`,服务端会二次校验档位存在且为启用状态。
请求参数:
- `tier_id`string必填含义玩家选择的充值档位 ID取自 `/api/finance/depositTierList` 返回)
- `idempotency_key`string必填≤64含义客户端生成的唯一键短时间内同 `idempotency_key` 不会重复下单;建议 UUID
返回参数:
- `order_no`string含义充值订单号
- `amount`string4 位小数,含义:玩家本次支付的充值金额,与所选档位 `amount` 一致)
- `bonus_amount`string4 位小数,含义:本次赠送金额,与所选档位 `bonus_amount` 一致,无赠送为 `0.0000`
- `total_amount`string4 位小数,含义:实际入账总额 = amount + bonus_amount
- `pay_channel`string含义支付通道标识当前 mock 模式固定为 `mock_gateway`,未来接入网关会替换为实际通道标识,如 `alipay_h5` / `wechatpay_native`
- `paid`bool含义当前单据是否已到账`true` 表示钱包已入账、`status=paid``false` 表示待玩家在第三方支付页面完成支付)
- `pay_url`string含义第三方支付页面地址`paid=true` 时为空串,`paid=false` 时为客户端需要跳转的支付页 URL
- `status`string`pending`/`paid`/`failed`含义订单处理状态mock 模式下始终返回 `paid`
- `create_time`int含义订单创建时间秒级时间戳
- `pay_time`int含义订单到账时间未到账为 0
错误码约定:
- `1001`:缺少必填参数(`tier_id`/`idempotency_key` 任一为空)
- `1002``idempotency_key` 过长,或与其他玩家的订单冲突
- `2000`:订单落库或入账失败(事务回滚后返回原始错误描述)
- `2003`:所选 `tier_id` 不存在或已停用
### 5.5 查看充值订单详情
- **POST** `/api/finance/depositDetail`
请求参数:
- `order_no`string必填含义充值订单号
返回参数(与 `depositCreate` 统一结构):
- `order_no`string含义充值订单号
- `amount`string4 位小数,含义:本单充值金额)
- `bonus_amount`string4 位小数,含义:本单赠送金额,无赠送为 `0.0000`
- `total_amount`string4 位小数,含义:入账总额)
- `pay_channel`string含义支付通道标识
- `paid`bool含义是否已到账
- `pay_url`string含义第三方支付页面地址已到账为空串
- `status`string`pending`/`paid`/`failed`
- `create_time`int含义订单创建时间
- `pay_time`int含义订单到账时间未到账为 0
### 5.6 查询充值订单列表
- **POST** `/api/finance/depositList`
用于我的充值记录页的分页列表;列表含订单状态,到账时间/支付通道等完整字段请再调用 `/api/finance/depositDetail` 获取。
请求参数:
- `page`int选填默认 `1`(含义:页码,从 1 开始)
- `page_size`int选填默认 `20`,最大 `100`(含义:每页数量,超出范围回退为 `20`
返回参数:
- `list`array含义充值订单列表`id desc` 排序)
- `order_no`string含义充值订单号
- `amount`string4 位小数,含义:本单充值金额)
- `bonus_amount`string4 位小数,含义:本单赠送金额,无赠送为 `0.0000`
- `status`string含义订单状态`depositDetail` 一致:`pending`/`paid`/`failed`
- `pagination`object含义分页信息
- `page`int含义当前页码
- `page_size`int含义每页数量
- `total`int含义总记录数
### 5.7 提现申请
- **POST** `/api/finance/withdrawCreate`
请求参数:
- `withdraw_coin`string含义申请提现金额必须 > 0
- `receive_account`string含义收款账号
- `receive_type`string`bank`/`ewallet`/`crypto`,含义:收款类型)
- `idempotency_key`string含义防重复提交提现
返回参数:
- `order_no`string含义提现订单号
- `status`string`pending_review`/`processing`,含义:提现状态)
- `fee_coin`string含义手续费
- `actual_arrival_coin`string含义实到账金额
- `risk_review_required`bool含义是否命中人工审核
校验顺序(任一失败即返回对应错误码,不再创建订单):
1. 参数完整性与金额合法性(`code=1001`;金额必须为数值且 > 0
2. **待审核订单数限制**:同一用户 `status=0`(待审核)的 `withdraw_order` 不得超过 3 笔,否则 `code=2004 Too many pending withdraw orders``data` 中回传:
- `max_pending`:上限值(当前为 `3`
- `pending_count`:当前待审核订单数
3. `coin_balance >= withdraw_coin`,否则 `code=2001 Insufficient balance`
4. **单笔上限校验**`withdraw_coin <= max_withdrawable`,否则 `code=2002 Withdraw exceeds available bet flow``data` 中回传:
- `max_withdrawable`**当前允许的单笔最大提现金额**= `min(coin_balance, max_withdraw_by_flow)`,前端据此提示"最大可提现金额为 XXX"
- `coin_balance``bet_flow_coin``total_withdraw_coin``ratio`
- `max_withdraw_by_flow`:仅按打码量折算的上限(= `max(0, bet_flow_coin / ratio - total_withdraw_coin)``ratio=0` 时为 `null`
5. 以上全通过后在同一事务内:
- `withdraw_order` 写入:`amount` / `fee`(默认 0.5% / `actual_amount = amount - fee` / `status=0`(待审核) / `channel_id` 取自用户归属渠道快照。
- `user` 表原子更新:`coin -= withdraw_coin``total_withdraw_coin += withdraw_coin`WHERE `coin >= withdraw_coin` 防止并发超额扣减)。
- `user_wallet_record` 写入 `biz_type=withdraw``direction=2``amount=withdraw_coin``ref_type=withdraw_order``idempotency_key=wd_apply_{order_no}`,代表"冻结"动作。
说明(打码量即提现配额模型):
- 单笔最大可提现 `max_withdrawable = min(coin_balance, max_withdraw_by_flow)`;每笔提现按 `withdraw_coin × ratio` 消耗打码配额,已消耗部分累积在 `total_withdraw_coin`
- `ratio = 0` 时视为"不限打码",单笔上限仅受 `coin_balance` 约束。
- 采用"申请即冻结"语义:提现在移动端提交后立即从 `user.coin` 中扣减并写出金流水;后台审核 **拒绝** 时由管理端在同一事务中回冲余额、`total_withdraw_coin` 与流水,不出现"等待审核期间用户还能把这笔钱再下注"的漏洞。
- 后台审核 **通过** 时不再额外触碰余额;若管理员调整了 `amount``fee`,按新旧差额再生成一条 `withdraw` / `withdraw_refund` 流水以保持账务平衡,并同步修正 `total_withdraw_coin`
- `withdraw_bet_flow_ratio` 由后台「游戏配置」维护,默认 `1.0000`,修改后对新请求立即生效。
### 5.8 查看提现订单详情
- **POST** `/api/finance/withdrawDetail`
请求参数:
- `order_no`string必填含义提现订单号
返回参数:
- `order_no`string含义提现订单号
- `status`string`pending_review`/`approved`/`rejected`,含义:审核状态;`status=3 已打款` 暂未对外暴露,合并到 `approved`
- `withdraw_coin`string含义申请提现金额与后台 `withdraw_order.amount` 对齐)
- `fee_coin`string含义手续费与后台 `withdraw_order.fee` 对齐)
- `actual_arrival_coin`string含义实际到账金额 = 申请金额 - 手续费;后台审核调整后会同步刷新)
- `reject_reason`string/null含义拒绝原因`status=rejected` 时取自 `withdraw_order.remark`,否则为 `null`
- `create_time`int含义申请时间
- `review_time`int/null含义审核时间戳未审核为 `null`
### 5.9 查询提现订单列表
- **POST** `/api/finance/withdrawList`
用于我的提现记录页的分页列表;列表含审核/打款状态摘要,手续费、实到账、拒绝原因等请再调用 `/api/finance/withdrawDetail` 获取。
请求参数:
- `page`int选填默认 `1`(含义:页码,从 1 开始)
- `page_size`int选填默认 `20`,最大 `100`(含义:每页数量,超出范围回退为 `20`
返回参数:
- `list`array含义提现订单列表`id desc` 排序)
- `order_no`string含义提现订单号
- `amount`string4 位小数,含义:申请提现金额,与后台 `withdraw_order.amount` 对齐)
- `status`string含义订单状态`withdrawDetail` 一致:`pending_review`/`approved`/`rejected`;后台已打款 `status=3` 合并为 `approved`
- `pagination`object含义分页信息
- `page`int含义当前页码
- `page_size`int含义每页数量
- `total`int含义总记录数
---
## 6. 公告与消息模块operation/notice
### 6.1 拉取公告列表
- **GET** `/api/notice/noticeList`
请求参数query string
- `page`int可选默认 1
- `page_size`int可选默认 20
返回参数:
- `list`array<object>
- `notice_id`int含义公告 ID
- `title`string含义公告标题
- `notice_type`string`silent`/`popout`,含义:公告类型)
- `is_read`bool含义当前用户是否已读
- `publish_time`int含义发布时间
### 6.2 公告详情
- **GET** `/api/notice/noticeDetail`
请求参数query string
- `id`int必填含义公告 ID
返回参数:
- `notice_id`int含义公告 ID
- `title`string含义公告标题
- `content`string含义公告正文
- `notice_type`string含义公告类型
- `must_confirm`bool含义是否必须手动确认
- `publish_time`int含义发布时间
### 6.3 强弹窗确认已读
- **GET** `/api/notice/noticeConfirm`
请求参数query string
- `notice_id`int含义待确认公告 ID
返回参数:
- `notice_id`int含义已确认公告 ID
- `confirmed`bool含义确认结果
- `confirm_time`int含义确认时间
---
## 7. 推送模块webman/push
> 用于移动端实时监听对局状态、开奖结果、余额变更与强公告事件。
> 协议与客户端行为对齐 [Pusher Channels](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/)webman/push 内置兼容客户端 `push.js`)。
> 参考:[webman/push 官方文档](https://www.workerman.net/doc/webman/plugin/push.html)
### 7.1 频道命名与职责(优化版)
| 频道名 | 类型 | 订阅方 | 典型事件 |
|--------|------|--------|----------|
| `private-user-{user.uuid}` | 私有(`private-` 前缀) | 当前登录用户;`{user.uuid}` 与登录态/档案中的 **10 位 `uuid`** 一致 | `bet.accepted``wallet.changed``withdraw.review_required`、定向 `notice.popout` 等 |
| `public-game-period` | 公共 | 所有在线客户端 | `period.tick``period.locked``period.opened` |
| `public-operation-notice` | 公共 | 所有在线客户端 | 全站/渠道级 `notice.popout`(与私有公告二选一或并存,由实现约定) |
约定说明:
- **用户私有频道一律使用对外标识 `uuid`,不使用数据库主键 `user_id`**,避免与后台、日志、多端展示口径不一致,并降低枚举内网 ID 的风险。
- 名称以 `private-` 开头的频道必须通过 **私有频道鉴权**(见 7.2)成功后才能收到服务端推送。
- `public-*` 可直接订阅,无需鉴权 HTTP 步骤。
### 7.2 连接地址与鉴权流程
**WebSocket 连接 URL与官方 `push.js` 一致)**
- 形如:`{websocket_base}/app/{app_key}`
- 示例(本地默认配置见 `config/plugin/webman/push/app.php``ws://127.0.0.1:3131/app/{app_key}`
- 生产环境请改为 `wss://` 与对外域名,并与网关/证书一致。
**连接建立后的协议步骤(简述)**
1. 客户端建立 WebSocket服务端下发 `pusher:connection_established`payload 内含 **`socket_id`**(后续鉴权必填)。
2. 订阅 **公共** 频道:发送 `pusher:subscribe``data` 仅含 `channel` 名即可。
3. 订阅 **私有** 频道:
- 客户端向 **鉴权接口** 发起 `POST``Content-Type: application/x-www-form-urlencoded`),表单字段:`channel_name``socket_id`
- 默认鉴权路径为 **`/plugin/webman/push/auth`**(与 `config/plugin/webman/push/app.php``auth` 一致,可随部署调整)。
- 服务端校验「当前登录用户是否允许订阅该 `channel_name`」——对 `private-user-{uuid}` 应校验 **`uuid` 与当前用户一致**,否则返回 `403`
- 鉴权成功返回的 JSON 由 `push.js` 原样作为 `pusher:subscribe``data` 发送(含 `auth` 等字段)。
**与移动端登录态的关系**
- 客户端在调用鉴权接口时,除 `channel_name` / `socket_id` 外,需携带与 REST API 一致的 **`user-token`(及业务所需的 `auth-token`**,由服务端解析用户身份后再比对 `private-user-{uuid}`
- **不建议**依赖浏览器 Cookie Session 作为唯一依据H5 外还有 App 内嵌、小程序等);若仅沿用框架示例中的 Session需在落地实现中改为 **无状态 token 校验**
### 7.3 事件定义(初设)
| 事件名 | 建议频道 | 说明 |
|--------|----------|------|
| `period.tick` | `public-game-period` | 倒计时广播 |
| `period.locked` | `public-game-period` | 封盘 |
| `period.opened` | `public-game-period` | 开奖完成(中奖号码) |
| `bet.accepted` | `private-user-{uuid}` | 下注成功回执 |
| `bet.settled` | `private-user-{uuid}` | **每期每用户一条**:该局开奖对该用户全部注单的汇总(`total_win_amount``order_count``hit_order_count``result_number``balance_after`;不再按单笔注单重复推送) |
| `wallet.changed` | `private-user-{uuid}` | 余额变化(中奖派彩入账等;`reason=payout` 等) |
| `notice.popout` | `public-operation-notice``private-user-{uuid}` | 强公告(按业务选择广播或定向) |
| `withdraw.review_required` | `private-user-{uuid}` | 提现进入审核 |
### 7.4 消息形态(客户端解析)
连接上收到的单帧一般为 JSON常见两类
- 协议类:`event``pusher:connection_established``pusher_internal:subscription_succeeded` 等。
- 业务类:`event` 为业务事件名,`channel` 为频道名,`data` 为负载(可能为字符串化的 JSON客户端需 `JSON.parse` 一次)。
业务负载示例(与初设一致,字段以实际实现为准):
```json
{
"event": "period.opened",
"channel": "public-game-period",
"data": {
"period_no": "20260416001",
"result_number": 18,
"open_time": 1776326400
}
}
```
### 7.5 降级与一致性
- 推送仅作 **体验增强**:断线、弱网时客户端仍应以 **HTTP 轮询/用户主动刷新**(如 `/api/game/periodCurrent``/api/wallet/balanceSummary`)为准。
- 同一业务状态以 **服务端落库与接口查询** 为最终一致;推送到达顺序不保证与业务因果严格一致,需客户端幂等与去重(可带 `period_no` / `order_no` / 时间戳)。
### 7.6 使用 Apipost 调试 WebSocket 与私有频道
Apipostv7+)支持 **WebSocket**:新建请求 → 选择 **WebSocket** → 类型选 **Raw**。私有频道遵循「先拿 `socket_id` → 再 HTTP 鉴权 → 再发 `pusher:subscribe`」,与 `vendor/webman/push/src/push.js` 行为一致。
**A. 仅调试公共频道(如 `public-game-period`**
1. 启动 webman 与 push 进程,确认 `config/plugin/webman/push/app.php``websocket``app_key`
2. 在 Apipost 中 WebSocket URL 填:`ws://127.0.0.1:3131/app/{app_key}`(将 `{app_key}` 换成配置中的真实值)。
3. 点击连接,在消息面板应收到一帧 `pusher:connection_established`,从中取出 `socket_id`(公共订阅可不依赖后续步骤,但便于对照协议)。
4. 在发送框填入一行 JSON勿带代码块标记并发送
`{"event":"pusher:subscribe","data":{"channel":"public-game-period"}}`
5. 成功时随后会收到 `pusher_internal:subscription_succeeded`;之后服务端向该频道 `trigger` 的事件会出现在消息列表中。
**B. 调试用户私有频道 `private-user-{uuid}`**
1. 同上先连接,从首帧解析出 **`socket_id`**。
2. 新建 **HTTP** 请求:`POST http://{你的HTTP入口}/plugin/webman/push/auth`
- Header`Content-Type: application/x-www-form-urlencoded`
- Bodyx-www-form-urlencoded`channel_name=private-user-{替换为真实uuid}&socket_id={上一步的socket_id}`
- 若鉴权已接入 `user-token`,请在 Header 中一并带上与移动端一致的 **`user-token`**(及 `auth-token` 等),否则会得到 `403` 或无效签名。
3. 将接口返回的 **JSON 正文**(整段)作为 `pusher:subscribe``data`:在 Apipost WebSocket 发送
`{"event":"pusher:subscribe","data": <上一步响应 JSON 对象>}`
注意:`push.js` 会把鉴权返回与 `channel` 字段合并后再发送;若手搓 JSON需保证与官方协议一致`auth` 字段)。
4. 订阅成功后即可在消息面板等待该私有频道上的业务事件。
**说明**:若仅做协议连通性验证,可暂时使用服务端对鉴权接口的占位实现;**上线前**必须落实「`channel_name` 与当前用户 `uuid` 匹配」校验,避免越权订阅。
---
## 8. 移动端完整调用流程
## 8.1 首次进入游戏
1. `GET /api/v1/authToken?secret=xxx&timestamp=xxx&device_id=xxx&signature=xxx` 获取 `auth-token`
2. `POST /api/user/login` 登录(请求头带 `auth-token`
3. `GET /api/game/lobbyInit` 拉首页初始化(请求头带 `auth-token`
3. 建立 webman/push 连接并订阅:
- `public-game-period`
- `private-user-{user.uuid}``uuid` 取自登录/档案接口,与 7.1 一致)
4. 收到 `period.tick` 实时刷新倒计时
5. 用户下注调用 `POST /api/game/betPlace`
6. 监听 `bet.accepted` + `wallet.changed` 更新下注结果和余额
7. 监听 `period.opened` 渲染开奖动画并刷新开奖记录
## 8.2 充值到下注到提现闭环
1. 拉取档位:`GET /api/finance/depositTierList`(玩家选择一档)
2. 创建订单:`POST /api/finance/depositCreate`JSON`tier_id` + `idempotency_key`
- **当前 mock 模式**:服务端同一请求内入账完成,返回 `paid=true``status=paid``pay_url=""`,客户端直接刷新钱包
- **未来第三方支付**:返回 `paid=false``status=pending``pay_url=<网关页面>`;客户端跳转网关页完成支付,后端由第三方回调触发入账(通过 `DepositSettlement::settle`
3. 客户端可选轮询 `GET /api/finance/depositDetail` 兜底确认状态;入账成功后会收到 `wallet.changed`
4. 下注:`POST /api/game/betPlace`
5. 派彩后收到 `wallet.changed`
6. 查询流水:`GET /api/wallet/recordList`
7. 提现:`POST /api/finance/withdrawCreate`(即时冻结 `user.coin` 与写出 `withdraw` 流水) -> `GET /api/finance/withdrawDetail`
- 等待后台 `/admin/order/withdrawOrder` 审核;通过后订单 `status` 变为 `approved`,拒绝则回冲余额并在 `reject_reason` 中回传管理员填写的驳回原因
## 8.3 公告强触达流程
1. 客户端监听 `notice.popout`
2. 拉取详情 `GET /api/notice/noticeDetail`
3. 用户勾选确认 `GET /api/notice/noticeConfirm?notice_id=...`
4. 未确认前可由前端阻断下注入口
---
## 9. 游戏时序流程图(接口 + 推送)
```mermaid
flowchart TD
A[用户登录 /api/user/login] --> B[拉初始化 /api/game/lobbyInit]
B --> C[连接webman/push并订阅频道]
C --> D[收到 period.tick 倒计时]
D --> E{0-20秒下注期?}
E -- 是 --> F[提交下注 /api/game/betPlace]
F --> G[推送 bet.accepted + wallet.changed]
E -- 否 --> H[进入封盘状态 period.locked]
H --> I[服务端算票与开奖]
I --> J[推送 period.opened]
J --> K[客户端开奖动画与结果展示]
K --> L[客户端刷新开奖记录 /api/game/periodHistory]
L --> D
```
---
## 10. 后台渠道分红比例配置(管理端补充)
> 本节为管理后台 `/admin/channel`「分配比例」弹窗补充口径,用于便于管理员按角色层级设置二次分红比例。
### 10.1 角色组展示规则
- 表格列顺序调整为:`角色组层级` -> `负责人` -> `状态` -> `分配比例(%)`
- `角色组层级``负责人` 前展示,降低识别与分配成本
- 层级路径使用 `/` 拼接,如:`顶级组 / 运营组 / 一组`
- 同一负责人若存在多个角色组,按多标签展示多条路径
- 无角色组时显示 `-`
### 10.2 接口:读取渠道管理员分配配置
- **GET** `/admin/channel/channelAdminShareList?id={channel_id}`
返回参数(`data.list[]`)新增:
- `group_paths`array<string>(负责人所属角色组层级路径列表)
- `group_paths_text`string层级路径拼接文本`|` 分隔,用于兼容纯文本场景)
返回示例(节选):
```json
{
"code": 1,
"message": "ok",
"data": {
"channel_id": 1,
"channel_name": "渠道A",
"list": [
{
"admin_id": 12,
"username": "zhuguan1",
"group_paths": ["顶级组 / 运营组 / A组"],
"group_paths_text": "顶级组 / 运营组 / A组",
"status": 1,
"share_rate": "30.0000"
}
]
}
}
```
### 10.3 保存约束(沿用现有)
- **POST** `/admin/channel/saveChannelAdminShare`
-`status=1` 的行参与占比汇总
- 启用项分配比例总和必须严格等于 `100.00`
---
## 11. 后台提现审核接口(管理端补充)
对应前端入口:`/admin/order/withdrawOrder`。列表读写沿用 `/admin/order.WithdrawOrder/index`BuildAdmin CRUD 标准协议)。审核流程另起 2 个动作接口,默认按钮 `add / del` 已在迁移中下线。
### 11.1 GET 审核详情
- **GET** `/admin/order.WithdrawOrder/edit?id={id}`
- 与 CRUD `edit` 协议复用,但对 `POST` 方法直接返回错误,强制走 `approve/reject`
- 返回 `row` 字段已 `withJoin` 关联:`user.username``channel.name``reviewAdmin.username`,方便弹窗直接展示"用户 / 渠道 / 审核人"。
### 11.2 审核通过
- **POST** `/admin/order.WithdrawOrder/approve`
- 请求参数:
- `id`int必填`withdraw_order.id`
- `amount`string必填审核后申请金额允许管理员调整
- `fee`string必填审核后手续费`>=0``<= amount`
- `remark`string可选为空时自动写入审核摘要
- 事务行为:
1. 订单状态必须为 `0 待审核`,否则返回错误。
2. 对比 `new_amount - old_amount`
- `>0`:用户 `coin -= diff``total_withdraw_coin += diff`,再写一条 `withdraw` 流水direction=2
- `<0`:用户 `coin += |diff|``total_withdraw_coin -= |diff|`,写一条 `withdraw_refund` 流水direction=1
3. 更新订单:`amount` / `fee` / `actual_amount = amount - fee` / `status=1` / `review_admin_id` / `review_time` / `remark`
### 11.3 审核拒绝
- **POST** `/admin/order.WithdrawOrder/reject`
- 请求参数:
- `id`int必填
- `remark`string必填拒绝原因最多 255 字,玩家将在 `/api/finance/withdrawDetail``reject_reason` 中看到)
- 事务行为:
1. 订单状态必须为 `0 待审核`
2. 回冲申请时的冻结:用户 `coin += amount``total_withdraw_coin -= amount`,写一条 `withdraw_refund` 流水direction=1`ref_type=withdraw_order`)。
3. 更新订单:`status=2` / `review_admin_id` / `review_time` / `remark`
### 11.4 权限节点
`20260418240000_withdraw_order_review_menu.php` 写入:
- `order/withdrawOrder/approve`(审核通过)
- `order/withdrawOrder/reject`(审核驳回)
- `order/withdraw_order/approve` / `order/withdraw_order/reject`snake 别名,兼容 `snake_case` 路径兜底)
- 默认 CRUD 按钮 `order/withdrawOrder/add` / `order/withdrawOrder/del`(含 snake 别名)已置为 `status=0`,审核流程不允许新增/删除订单。
---
## 12. 需要你确认的实现口径(进入接口开发前)
1. **登录方式**:仅账号密码,还是要短信/邮箱验证码?
2. **提现收款类型**:首版只做银行卡,还是同时支持电子钱包/加密地址?
3. **自动托管**:是否首期上线;若不上线可先隐藏 `auto-bet` 接口。
4. **push事件最小集**:是否先只上 `period.tick``period.opened``wallet.changed` 三类。
5. **错误码规范**:是否已有公司统一错误码表;若有需对齐替换本草案码段。
确认后可进入下一步:按该文档落地 controller + validate + service + 路由。

182
docs/分红说明文档.md Normal file
View File

@@ -0,0 +1,182 @@
# 分红说明文档(现状分析 + 简化设计建议)
## 1. 文档目的
本文用于回答两个问题:
1. 甲方管理员是否容易看懂当前分红方式?
2. 是否有更简单、更容易执行和对账的分红设计?
并给出推荐方案与落地路径。
---
## 2. 当前分红设计现状(按代码实际行为)
## 2.1 配置层面(看起来是三级)
历史上后台有三处比例字段(其中后两项已下线):
- `channel`:渠道分红参数(`turnover_share_rate``affiliate_*` 等)
- `admin_group`:角色组 `commission_rate`(已删除)
- `admin`:管理员 `commission_rate`(已删除)
从“配置界面”角度,容易被理解为:
**渠道比例 -> 角色组比例 -> 管理员比例** 的三级链路。
## 2.2 结算层面(实际执行是单层)
当前手动结算逻辑在 `Channel` 控制器中:
- 按渠道下注汇总计算 `total_bet_amount / platform_profit_amount`
- 根据 `channel.agent_mode` 计算佣金基数与佣金比例
- 生成 `agent_settlement_period``agent_commission_record`
关键点:**佣金记录只写了一条 admin_id渠道下首个管理员**,并未按角色组/管理员比例拆分多条佣金明细。
也就是说,目前“角色组比例、管理员比例”主要在新增/编辑时做约束校验,但**没有进入最终结算分账公式**。
---
## 3. 当前方案对甲方可理解性的评估
结论:**一般不容易看懂,且容易产生“配置了却不生效”的认知落差。**
主要原因:
1. **心智复杂**:甲方要同时理解渠道模式(返水/联营)、角色组比例、管理员比例三层规则。
2. **口径不一致**:界面上有三级比例,但结算时是渠道单层落账,容易质疑“为什么不是按我配的三级比例发放”。
3. **对账难**:财务看到佣金记录时只能看到渠道维度,不容易反推角色组和管理员分配关系。
4. **运维负担高**:层级变化(调组、换管理员)后,历史解释成本高。
---
## 4. 更简单的分红设计方式(推荐)
## 4.1 推荐方案:`渠道结算 + 渠道内二次分配`(两层)
将分红流程拆为两个明确步骤:
### A. 一级:渠道结算(平台对渠道)
- 保留当前 `channel.agent_mode` 的计算方式turnover/affiliate
- 先得到一个渠道应发佣金:`channel_commission_amount`
- 生成渠道结算单(当前已具备)
### B. 二级:渠道内分配(渠道对管理员)
- 不再使用“角色组分红比例”参与金额运算(角色组只负责权限)
- 仅维护“渠道下管理员分配权重”(例如总和=100%
- 自动拆分:`admin_commission = channel_commission_amount × admin_weight`
- 为每个管理员生成独立佣金记录(多条)
> 这样甲方只需要理解一句话:
> **平台先算渠道总佣金,再按渠道内管理员权重拆分。**
## 4.2 为什么推荐该方案
1. **业务更直观**:甲方看“渠道总佣金 + 管理员占比”即可。
2. **和现有代码贴近**:你们当前已经是“先算渠道佣金”,只需补“二次拆分”。
3. **对账容易**:每个管理员佣金来源清晰,可直接汇总对账。
4. **后续可扩展**:以后要按团队、按代理线拆分,可以在“二次分配”层升级,不影响一级结算。
---
## 5. 备选方案对比
## 5.1 方案A现状表象渠道 -> 角色组 -> 管理员三级
- 优点:理论上精细
- 缺点:配置理解成本高、维护复杂、容易和实际结算脱节
- 适用:组织结构非常稳定、财务系统成熟的大盘
## 5.2 方案B推荐渠道 -> 管理员两级
- 优点:简单、易讲、易对账、改造成本低
- 缺点:若强依赖“角色组抽成”会减少一层表达
- 适用:当前阶段(快速上线、减少运营误解)
## 5.3 方案C最简仅渠道一级
- 优点:最简单
- 缺点:无法直接落地到管理员收益,不利于激励
- 适用:仅用于统计,不用于发佣
---
## 6. 建议的产品口径(给甲方的话术)
建议统一说明为:
1. **渠道分红**:系统按渠道配置(返水或联营)自动计算该周期渠道总佣金。
2. **人员分配**:渠道总佣金按“渠道内管理员分配比例”自动拆分。
3. **角色组作用**:角色组只负责菜单权限与数据权限,不参与金额运算。
4. **结算可追溯**:每条管理员佣金都能追溯到对应渠道结算单。
---
## 7. 数据与实现改造建议(按最小改动)
## 7.1 数据字段建议
- 保留:`channel` 的分红计算参数(现有)
- 建议新增(任选其一):
-`admin` 增加 `channel_share_rate`(该管理员在所属渠道的拆分比例)
- 或新增 `channel_admin_share(channel_id, admin_id, share_rate)`
## 7.2 逻辑改造建议
1. 渠道结算得到 `commission_amount`
2. 拉取该渠道有效管理员分配比例列表(总和=100%
3. 按比例拆分并批量写入 `agent_commission_record`
4. 若分配比例未配置:
- 方案一默认100%给渠道负责人
- 方案二:阻止结算并提示“请先配置渠道管理员分配比例”
## 7.3 风险控制建议
- 分配比例总和强校验(必须=100%
- 管理员离职/禁用时自动重算或禁止结算
- 结算后锁单,后改比例不影响历史账单
---
## 8. 迁移策略建议
分三步上线:
1. **第1步兼容期**:保留现有字段,新增“渠道内管理员分配比例”配置
2. **第2步双轨期**:结算时同时产出“旧口径结果 + 新口径预览”用于核对
3. **第3步切换期**:正式切到“渠道+管理员两级”,角色组比例仅保留展示或下线
---
## 9. 最终建议结论
从“甲方能否看懂”和“系统可维护性”来看,建议采用:
**渠道结算 + 渠道内管理员二次分配(两级)**
这是在你们当前代码基础上改造成本最低、解释成本最低、财务对账最清晰的方案。
不建议继续强化“渠道->角色组->管理员”三级分红公式作为主线。
---
## 10. 本次已落地改造2026-04-18
已在系统中完成以下改造:
1. 新增渠道管理员分配表:`channel_admin_share`
2. 下线并删除旧字段:`admin_group.commission_rate``admin.commission_rate`
3. 新增后台接口:
- `channelAdminShareList`:读取渠道管理员分配配置
- `saveChannelAdminShare`保存渠道管理员分配配置启用项合计必须100%
4. 手动结算改造:
- 先算渠道总佣金
- 再按 `channel_admin_share` 比例拆分为多条 `agent_commission_record`
- 结算预览支持查看拆分明细
5. 渠道后台页面新增“分配比例”按钮与配置弹窗,用于维护管理员分配比例
6. 角色组与管理员页面已移除旧分红比例字段展示与编辑项
说明:
- 若未配置 `channel_admin_share`系统会回退为“渠道首个管理员100%”以保证兼容历史流程。