Files
bl/docs/pvp-cross-server-battle-cmd-data-design-2026-04-27.md

258 lines
6.8 KiB
Go
Raw Normal View History

# PVP 跨服战斗消息 `cmd/data` 转发改造说明
## 背景
巅峰赛跨服战斗原本已经有 Redis pub/sub 通道但战斗中的客户端操作转发仍然偏间接
1. 本服 controller 先把客户端请求翻译成 `FightI` 调用
2. `RemoteFightProxy` 再把 `FightI` 调用翻译成 `battle_command`
3. 宿主服收到 `battle_command` 再翻译回 `FightI` 调用
这样一来跨服战斗链路和本地战斗链路之间多了一层动作语义映射调试时不容易直接看到前端到底发了哪个 cmd带了什么 data
本次改造只处理战斗中的消息转发不改匹配/加入队列逻辑
## 保持不变的部分
- 巅峰赛加入和取消匹配仍然走原有 RPC
- `MatchJoinOrUpdate`
- `MatchCancel`
- Ban/PickMatchFoundSessionClose PVP 服务内消息仍兼容旧外层格式
- `battle_command` 旧语义消息仍保留作为兼容兜底不强制一次性切掉
对应原因
- 匹配加入需要同步返回结果RPC 更直接
- 现网如果已有旧 envelope 或旧 battle command 发送方不能直接断
## 改造目标
把跨服战斗中的客户端操作转发改成直接发送
```json
{
"cmd": "battle_client_command",
"data": {
"sessionId": "xsvr-...",
"userId": 10001,
"cmd": 2405,
"data": {
"SkillId": 1234
}
}
}
```
核心含义
- 外层 `cmd`PVP 内部 Redis 消息类型
- 内层 `data.cmd`真实客户端战斗协议 cmd
- 内层 `data.data` cmd 对应的 JSON 业务字段
这样宿主服拿到消息后可以直接按真实战斗 cmd 处理而不是先理解一层额外的跨服动作协议
## 本次代码落点
### 1. 新增 `cmd/data` envelope 能力
文件
- `logic/service/fight/pvpwire/types.go`
改动
- `Envelope` 新增
- `Cmd string`
- `Data json.RawMessage`
- 保留旧字段
- `Type string`
- `Body []byte`
- 新增辅助方法
- `NewEnvelope`
- `MessageCmd`
- `MessageData`
作用
- 新消息可以直接用 `cmd/data`
- 旧消息仍可通过 `type/body` 解析
### 2. controller 层遇到远端跨服战斗时直接转发原始战斗 cmd
文件
- `logic/controller/fight_unified.go`
- `logic/controller/fight_base.go`
改动
- `fight_unified.go` 增加 `relayRemoteFightCommand`
- `c.FightC` `*pvp.RemoteFightProxy` 不再走本地 `dispatchFightActionEnvelope` 分发而是直接转发当前请求的
- `data.Head.CMD`
- 当前请求结构体 `data`
已接入的战斗入口包括
- `2404` 准备
- `7556` 旧组队准备
- `2405` 单战位技能
- `7505` 多战位技能
- `7558` 旧组队技能
- `2406` 使用道具
- `7562` 旧组队道具
- `2407` 切宠
- `7563` 旧组队切宠
- `2410` 逃跑
- `7565` 旧组队逃跑
- `2441` 加载进度
- `50002` 战斗聊天
### 3. RemoteFightProxy 新增直接转发客户端命令能力
文件
- `logic/service/fight/pvp/proxy.go`
改动
- 新增 `RelayClientCommand(cmd uint32, data any) bool`
- 新增 `marshalClientCommandData(data any) ([]byte, error)`
说明
- `marshalClientCommandData` 会把请求结构体转成 JSON
- 会删除 `Head/head` 字段避免把协议头二进制字段一起塞进跨服消息
- 这样发出去的 `data.data` 只保留业务字段
### 4. PVP 宿主服新增 `battle_client_command` 处理
文件
- `logic/service/fight/pvp/service.go`
- `logic/service/fight/pvpwire/types.go`
改动
- 新增消息类型
- `MessageTypeBattleClientCommand`
- 新增载荷
- `BattleClientCommandPayload`
- `handleRedisMessage` 增加 `battle_client_command` 分支
- 新增 `handleBattleClientCommand`
处理方式
- 宿主服根据内层真实 `cmd` 解出 `data`
- 再直接调用当前 `FightI` / `FightC`
当前支持的映射
- `2404` / `7556` -> `ReadyFight`
- `2405` / `7505` / `7558` -> `UseSkillAt`
- `2406` / `7562` -> `UseItemAt`
- `2407` / `7563` -> `ChangePetAt`
- `2410` / `7565` -> `Over(PlayerEscape)`
- `2441` -> `LoadPercent`
- `50002` -> `Chat`
### 5. 战斗发包转发也统一改成 `cmd/data`
文件
- `logic/service/player/rpc.go`
改动
- `PacketRelayPayload` 外层 envelope `type/body` 改为 `cmd/data`
原因
- 它本质也是跨服战斗中的消息转发
- 和新的 `battle_client_command` 保持一致便于抓包和日志排查
## 当前实际链路
### 客户端操作转发
1. 客户端发战斗包到本服 controller
2. controller 判断当前 `FightC` 是否为 `RemoteFightProxy`
3. 如果是
- 直接发布 `battle_client_command`
- 内层携带真实 `cmd` JSON `data`
4. 宿主服 `pvp.service` 收到后按 `cmd` 分发回战斗逻辑
### 战斗结果/下行发包转发
1. 宿主服在 `RPC_player.SendPackCmd` 中组包
2. 发布 `packet_relay` 消息外层使用 `cmd/data`
3. 客服端所在服收到后解包并发回真实客户端
## 兼容策略
为了避免一次性改动过大这次保留了两层兼容
### 1. envelope 兼容
`Envelope` 同时支持
- 新格式`cmd/data`
- 旧格式`type/body`
`handleRedisMessage` 统一通过
- `MessageCmd()`
- `MessageData()`
读取消息
### 2. `battle_command` 兼容
`RemoteFightProxy` 旧的
- `UseSkill`
- `UseSkillAt`
- `UseItem`
- `ChangePet`
- `Chat`
这些方法仍然保留并继续发送原来的 `battle_command`
新接入的 controller 优先走 `battle_client_command`旧路径仍可兜底
## 这次改造的收益
### 好处
- 跨服战斗消息更直观日志里能直接看到真实客户端 cmd
- controller 到宿主服之间不需要再先翻译成一套额外动作协议
- 宿主服调试时更容易复现前端输入
- 新老消息可并存改造风险相对可控
### 仍然保留的复杂度
- 宿主服仍然需要针对不同战斗 cmd 做一次 decode 和分发
- 这是必要复杂度因为真实战斗入口本来就有多种协议包
## 验证
本次已执行
- `cd logic && go test ./service/fight/pvp ./service/fight/pvpwire`
- `cd logic && go test -c ./controller`
结果
- `pvp``pvpwire` 包通过
- `controller` 可以完成编译检查
补充说明
- `go test ./controller ./service/player ...` 在当前环境会因为依赖初始化读取 `/proc/sys/kernel/osrelease` 失败而 panic这属于运行环境问题不是这次改动引入的编译错误
## 后续建议
如果后面继续收敛这块逻辑可以按下面顺序做
1. `battle_command` 的旧语义层逐步下线只保留 `battle_client_command`
2. `handleBattleClientCommand` 里的 cmd 映射抽成独立表避免 `switch` 继续膨胀
3. 如果后续战斗协议继续统一可考虑把 controller 入站结构和跨服转发 decode 共享同一套注册表