LoginHub —
/loginhubWS + JWT,用于大厅资产同步、全服广播、邮件通知
📤 客户端 → 服务器
| 方法 | 参数 | 说明 |
|---|---|---|
| C→S Bind | string userId | 必须:连接后立即调用,将连接与 userId 绑定。服务端加入 userId 命名的 SignalR 组,后续定向推送通过此组发送。 |
| C→S DismissPendingRoomNEW | string userId | 用户拒绝返回断线房间。登录后若收到 PendingRoomNotice,用户选择"离开"时调用此方法,服务端清理旧房间残留。 |
📥 服务器 → 客户端
| 事件 | 数据 | 发送时机 |
|---|---|---|
| S→C Broadcast | {
UserName: string, // 昵称
AvatarUrl: string, // 头像URL
Level: int, // 等级
VipLevel: int, // VIP等级
Message: string, // 消息内容
Time: long // Unix时间戳(秒)
} | 大喇叭全服广播 |
| S→C KillAnnouncement | {
Text: string, // 公告文案: "恭喜玩家momo在风雷之渊高级场捕获 金龙*2000炮倍 100000000大奖"
UserName: string, // 玩家名: "momo"
GameTypeName: string, // 游戏类型: "风雷之渊"
RoomName: string, // 房间名: "高级场"
FishName: string, // 鱼名: "金龙"
Multiple: int, // 倍数: 2000
Gold: long, // 获得金币: 100000000
Time: long // Unix时间戳(秒)
} | 全局击杀公告(特殊Boss击杀 / 机器人模拟击杀),走 LoginHub |
| S→C AssetUpdated | {
CurrencyType: int, // 货币类型: 1=金币 2=保险箱 3=钻石
ChangeType: int, // 变动类型: 1=增加 2=减少
Amount: long, // 变动数量
NewGold: long, // 最新金币
NewDiamond: int, // 最新钻石
Source: string, // 来源: "游戏金币结算"、"救济金"、"签到" 等
Remark: string // 备注(房间名等)
} | 资产变更:退出房间结算 / 后台充值扣币 / 救济金 / 签到 / 捕鱼返利 |
| S→C LevelChangedNEW | {
NewLevel: int, // 最新等级
NewExp: long, // 最新经验
NewGold: long, // 最新金币
NewDiamond: int, // 最新钻石
Source: string, // 来源
Remark: string // 备注
} | 后台修改玩家等级 |
| S→C BackpackUpdated | {
Action: string, // "add" | "update" | "delete"
PropNumber: string, // 道具编号
Count: int // 数量
} | 道具背包变更(后台操作) |
| S→C NewEmail | {
Title: string, // 邮件标题
EmailId: string // 邮件ID
} | 收到新邮件通知 |
| S→C ChangeConfigNEW | changeType: int 1 = 活动配置更新 2 = 游戏配置更新 | 后台活动配置或游戏配置保存成功并发布 CAP 后,由 UserHost 通过 LoginHub 向所有在线玩家广播。前端收到后应按 changeType 重新请求对应配置。监听示例:connection.on("ChangeConfig", changeType => { ... }) |
| S→C AccountKickedNEW | {
userId: string // 被顶的玩家ID
} | 账号被顶通知。该账号在其他设备登录时,旧连接收到此事件。前端应提示"账号已在其他地方登录"并清空房间 UI、断开游戏连接。 |
| S→C KickToMainPageNEW | {
userId: string // 被踢的玩家ID
} | 强制退出到主页面。当前账号的连接已失效(被顶号/用户不匹配),前端收到后应立即清除所有游戏 UI 并导航到主页面/登录页。 |
| S→C PendingRoomNoticeNEW | {
roomId: string, // 房间ID
gameType: string, // 游戏类型: "风雷之渊"
roomName: string // 房间名: "高级场"
} | 登录后检测到有未完成的断线游戏。金币已结算至当前余额。前端应弹窗提示用户选择"返回房间"(调 Reconnect)或"离开"(调 DismissPendingRoom),60秒无操作自动离开。 |
FishGameHub —
/fishinghubWS,捕鱼游戏核心 Hub。连接后先调 Reconnect 绑定用户。
🤖 机器人说明:捕鱼房间中的机器人(IsBot=true)由服务端完全模拟。客户端无需发送机器人指令,机器人在协议层面与真人一致——同样触发 RoomFireNotice、RoomKillNotice 等事件。
📤 客户端 → 服务器
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
| C→S Reconnect | string userId | 无 | 断线重连必须先调用。服务端返回 RoomInfo(含 activeFishes)并恢复房间状态 |
| C→S JoinRoom | string roomId, string userId, int posituin | 无 | 加入房间。posituin=-1 自动分配座位 |
| C→S FireCannon | string roomId, string userId, long gold, int count, long timestamp, string sign, object? extra | { bullets: [{ bulletId: string, gold: long }], gold: long, timestamp: long } | 发炮。每颗返回子弹携带本次发射炮倍;外层 gold 为玩家剩余金币。sign=MD5签名 |
| C→S KillFish | string roomId, string userId, string fishNumber, string fihsId, long gold, long timespan, string bulletId, long timestamp, string sign | { gold: long, timestamp: long } | 击杀鱼 |
| C→S LeaveRoom | string roomId, string userId | 无 | 退出房间(金币结算后自动 Context.Abort 断开) |
| C→S UseProp | string roomId, string userId, string propNumber, int diamond=0 | int 剩余数量 | 使用道具(钻石购买时传 diamond)。返回背包剩余数量,-1 表示失败。冰冻道具(40003)触发 FishFrozen 事件 |
| C→S ChangeGunMultiple | string roomId, string userId, int multiple | 无 | 更改炮倍 |
| C→S GetRoomInfo | string roomId | 无 | 结果通过 GetRoomInfoResult 返回 |
| C→S GetPlayerInfo | string roomId, string userId | 无 | 获取玩家信息 |
| C→S SyncGameData | string roomId, object data | 无 | 同步游戏数据 |
| C→S SetPlayerAfkNEW | string roomId, string userId | 无 | 玩家挂机,触发 PlayerAfkNotice |
| C→S CancelPlayerAfkNEW | string roomId, string userId | 无 | 取消挂机,触发 PlayerCancelAfkNotice,返回 RoomInfo(含 activeFishes)同步当前鱼状态 |
📥 服务器 → 客户端
| 事件 | 数据 | 目标 | 说明 |
|---|---|---|---|
| S→C BindSuccess | string | 调用者 | 重连绑定成功 |
| S→C Exception / ErrorMessage | { code: int, message: string } | 调用者 | 错误通知 |
| S→C RoomInfo | RedisGameRoom 扩展字段,包含首次及当前活跃鱼:
{
RoomId, RoomName, GameType, RoomLevel, RoomPartition,
RoomNumber, MaxPlayerCount, CurrentOnlinePlayerCount,
RobotCount, Players[], CreateUserId, SystemFactor,
activeFishes: [{ // 首次进房/后续进房/重连/取消挂机时包含
fishId: number,
fishIds: number[],
fishNumber: string,
routeId: number,
fishArrayNumber: string,
createdAt: number, // 毫秒
expireAt: number, // 毫秒
remainingSeconds: int
}]
} | 调用者 | 加入房间/断线重连/取消挂机返回房间信息。先用 activeFishes 初始化完整鱼群;服务端约 300ms 后仅兼容补发普通散鱼的 OnFishSpawn(init),Boss、黄金鱼、特殊鱼和鱼阵不会重复补发。 |
| S→C SyncPlayerGold | long | 调用者 | 金币同步(金币≤0时触发) |
| S→C SendPlayerInfoResult | long? | 调用者 | 玩家Gold值 |
| S→C GetRoomInfoResult | RedisGameRoom? | 调用者 | 房间信息查询结果 |
| S→C RoomKillNotice | {
fishNumber: string, userId: string, gold: long,
fishId: string, timespan: long, killtime: long,
msg: string, // "时间:X秒"
beishu: double,
skillsGold: long, // 炮倍金币
fishScore: int,
param: object?, // Boss爆奖结果
drop: object? // Boss掉落 { propNumber, propName, count }
} | 房间组 | 击杀通知 |
| S→C RoomFireNotice | { userId, bulletIds[], gold, count, extra, isBot } | 房间组 | 发炮通知 |
| S→C RoomJoinNotice | {
userId: string, // 玩家ID
userName: string, // 玩家名字
userNumber: string, // 玩家编号
avatar: string, // 玩家头像
isRedName: bool, // 是否红名
level: int, // 玩家等级
exp: long, // 经验值
maxExp: long, // 当前等级最大经验值
vipLevel: int, // VIP等级
gold: long, // 金币
diamond: int, // 钻石
cannonId: string, // 炮台
wingsId: string, // 翅膀
warGodId: string, // 战神
cannonMultiple: int, // 炮倍
position: int, // 位置
beginCoin: long, // 进场金币
maxGold: long, // 最高金币
beginTime: long, // 进场时间
state: int, // 状态 0正常 1断线中
playerFactor: object, // 玩家因子
totalRechargeGold: long, // 累计充值
safeBoxGold: long, // 保险箱金币
isBot: bool, // 是否机器人
isAfk: bool // 是否挂机
} | 房间组 | 玩家加入 |
| S→C RoomLeaveNotice | string userId | 房间组 | 玩家离开 |
| S→C BotLeaveRoomNoticeNEW | { userId: string } | 房间组 | 机器人退出房间 |
| S→C HostDisconnectNEW | 无 | 房间组 | 房主断线,其他玩家可接受机器人控制权 |
| S→C RoomDisconnectNotice | string userId | 房间组 | 玩家断线 |
| S→C ChangeGunMultipleNotice | { userId, multiple } | 房间组 | 炮倍变更 |
| S→C ChangeControllerUser | string userId | 房间组 | 房主控制权移交 |
| S→C UsePropNotice | { userId, propNumber } | 房间组 | 使用道具 |
| S→C SummonCardFishNoticeNEW | {
roomId: string, // 房间ID
userId: string, // 使用召唤卡的玩家ID
userNumber: string, // 玩家编号
userName: string, // 玩家昵称
fishType: int, // 2=Boss鱼,3=黄金鱼
fishTypeName: string, // "Boss鱼"或"黄金鱼"
fishNumber: string, // 召唤出的鱼配置编号
fishId: number // 本次生成的鱼实例ID
} | 房间组 | 召唤卡成功生成鱼后广播。鱼的路径和生命周期仍由 OnFishSpawn 处理;本事件用于显示“哪个玩家召唤了什么鱼”等提示。 |
| S→C PlayerAfkNoticeNEW | { userId: string } | 房间组 | 玩家挂机通知 |
| S→C PlayerCancelAfkNoticeNEW | { userId: string } | 房间组 | 取消挂机通知 |
| S→C TorpedoResultNoticeNEW | { userId: string, propNumber: string, gold: long, color: string } | 房间组 | 鱼雷使用结果通知 |
| S→C OnPlayerInfoNEW | { userId, userName, gold, diamond, viplvl, cannonMultiple, cannonId, wingsId, position } | 调用者 | 玩家信息同步 |
| S→C SyncGameDataNEW | object data | 房间组 | 游戏数据同步(服务端主动推送) |
| S→C FishFrozenNEW | 无 | 房间组 | 冰冻道具(40003)生效,鱼存活时间延长 15 秒(客户端写死 15s 倒计时) |
| S→C OnSeatChange | // type=join 时:
{ type: "join", playerId: string, position: int, playerName: string,
nickname: string, gold: long, diamond: int, vipLevel: int,
turretId: string, turretRate: int, turretLv: int, headId: string,
roomRate: int, bulletPower: int }
// type=leave 时:
{ type: "leave", userId: string, playerId: string,
position: int, showWaiting: bool? } | 房间组 | 座位变更(加入/离开) |
| S→C OnFishSpawnNEW | {
type: "init" | "spawn" | "sync", // init=进房补发,spawn=定时出鱼,sync=主动同步
timestamp: number, // 服务器当前时间戳(毫秒)
fish: [{
fishId: number, // 普通鱼ID;鱼阵时为首条普通鱼ID(兼容旧客户端)
fishNumber: string, // 普通鱼=鱼编号;鱼阵=""
routeId: number, // 普通鱼=路径编号;鱼阵=固定阵型编号(如71001)
fishArrayNumber: string,// 鱼阵编号(非鱼阵时为"")
fishIds: number[], // 所有出鱼必传;普通鱼=[fishId],鱼阵=阵内全部鱼ID
createdAt: number, // 鱼创建时间戳(毫秒),前端用它算路径进度
expireAt: number, // 鱼过期时间戳(毫秒)
remainingSeconds: int // 鱼剩余存活秒数
}]
} | 房间组(init/spawn)/调用者(sync) | fishIds 在首次出鱼、定时出鱼、入房及重连同步中都必须提供。普通鱼为单元素集合 [fishId];鱼阵按鱼阵配置的生成顺序使用 ID 集合。鱼阵的 fishNumber 为空,鱼信息由 fishArrayNumber 对应的本地配置确定。击中时把对应 ID 作为 KillFish 的 fihsId 原样传回。 |
| S→C KillNotice | {
FishId: string, // 鱼编号
userName: string, // 玩家名
gold: long, // 金币
// 机器人模拟时额外字段:
fishName: string?, // 鱼名
multiplier: int? // 倍数
} | 全部 | 全局击杀公告(真人击杀/机器人模拟) |
FishRoomHub —
/fishingroomhubWS,房间列表
📤 客户端 → 服务器
| 方法 | 参数 | 说明 |
|---|---|---|
| C→S GetRoomList | string userId, int gameType, int roomLevel, int roomPartition | 获取房间列表。结果通过 GetRoomListResult 事件返回 |
| C→S GetRoomInfo | string roomId | 获取房间详情 |
📥 服务器 → 客户端
| 事件 | 数据 | 说明 |
|---|---|---|
| S→C GetRoomListResult | List<GameRoomReponse>GameRoomReponse: { RoomId, MaxPlayerCount, CurrentOnlinePlayerCount, RobotCount, RoomNumber } | 房间列表(仅统计真人) |
| S→C RoomChange | RedisGameRoom | 房间变化通知(人数、状态变更) |
| S→C GetRoomInfoResult | RedisGameRoom? | 房间详情 |
ChessGameHub —
/chessgamehubWS,中国象棋
📤 客户端 → 服务器
| 方法 | 参数 | 说明 |
|---|---|---|
| C→S CreateRoom | CreateRoomParam { roomId, userId, gameType(1=我方黑 2=我方红), password, gold } | 创建房间 |
| C→S JoinGame | string gameId, string pwd, string userId | 加入游戏 |
| C→S GameReady | string gameId, string userId | 准备 |
| C→S EndGame | string gameId, string winUserId | 直接结束(对方获胜) |
| C→S RequestEndGame | string gameId, string userId, string winUserId | 请求结束(需双方确认) |
| C→S RestartGame | string gameId, string userId | 对局结束后重新开始 |
| C→S GoldConfig | string gameId, long gold, string userId | 房主设置金币 |
| C→S ExitRoom | string gameId, string userId | 退出(房主退出清空房间,非房主保留房间) |
| C→S Heartbeat | string gameId | 心跳,更新活跃时间 |
| C→S CheckTimeout | string gameId | 检查超时(60秒断线判负) |
| C→S GetRoomList | int partition=0 | 房间列表;0=全部分区,正数=指定分区 |
| C→S SyncGameData | string gameId, string data | 同步棋子移动 |
| C→S GameOperation | string gameId, int type, string datatype: 1=求和 2=认输(自动结束) 3=悔棋 | 游戏操作 |
📥 服务器 → 客户端
| 事件 | 数据 | 目标 |
|---|---|---|
| S→C GameError | string | 调用者 |
| S→C BeginGame | ChessGameRoom | 调用者/房间组 |
| S→C JoinRoom | ChessGameRoom | 调用者 |
| S→C JoinRoomNotice | ChessGameRoom | 全部 |
| S→C Reconnect | { userId, userName } | 房间组 |
| S→C Disconnect | { userId, disconnectTime, timeoutSeconds } | 房间组 |
| S→C ReadyNotice | string userId | 房间组 |
| S→C AllBeginTime | ChessGameRoom | 房间组 |
| S→C GameEnd | { winUserId, room } | 房间组 |
| S→C EndGameRequestSent | string | 调用者 |
| S→C OpponentRequestEnd | string userId | 房间组 |
| S→C RestartGameSuccess | ChessGameRoom | 房间组 |
| S→C RestartRoomNotice | ChessGameRoom | 全部 |
| S→C ExitRoomNotice | ChessGameRoom | 全部 |
| S→C ExitRoomSuccess | string userId | 房间组 |
| S→C CreateRoomNotice | ChessGameRoom | 全部 |
| S→C GoldConfigSuccess | ChessGameRoom | 房间组 |
| S→C GoldConfigRoomNotice | ChessGameRoom | 全部 |
| S→C GetRoomList | List<ChessGameRoom> | 调用者 |
| S→C SyncGameData | string | 全部 |
| S→C GameOperation | { type, data } | 全部 |
CustomerHub —
/customerHubWS,客服即时通讯。客服端需 JWT,玩家端无需 Token。
📤 客户端 → 服务器
| 方法 | 参数 | 说明 |
|---|---|---|
| C→S JoinConversation | string conversationId | 加入会话房间组 |
| C→S LeaveConversation | string conversationId | 离开会话房间组 |
| C→S SendMessage | string conversationId, int senderType, string senderId, string senderName, string? senderAvatar, int contentType, string content, string guId=""senderType: 1=用户 2=客服 contentType: 1=文字 2=图片 conversationId 为空时自动创建新会话 | 发送消息 |
| C→S StartConversation | string userId, string userName | 主动发起新会话 |
📥 服务器 → 客户端
| 事件 | 数据 | 目标 |
|---|---|---|
| S→C OnMessageReceived | {
GuId: string,
ConversationId: string,
SenderType: int,
SenderId: string,
SenderName: string,
SenderAvatar: string?,
ContentType: int,
Content: string,
CreateTime: DateTime
} | 会话组 |
| S→C OnConversationStarted | ConversationEntity | 调用者 |
| S→C OnNewConversation | { conversationId, customerId?, customerName?, userId?, userName?, status, createTime } | 客服组 |
| S→C ErrorMessage | string | 调用者 |
注意:客服连接时携带 JWT,服务端自动从 uid Claim 加入通知组,无需手动 JoinCustomerQueue。
🔗 Hub 路由总览
| Hub | 路由 | 认证 |
|---|---|---|
| LoginHub | /loginhub | JWT |
| FishGameHub | /fishinghub | 方法传参 userId |
| FishRoomHub | /fishingroomhub | 方法传参 userId |
| ChessGameHub | /chessgamehub | 方法传参 userId |
| CustomerHub | /customerHub | 客服需JWT,玩家无需 |
📦 通用数据结构
| 名称 | 字段 |
|---|---|
RedisGameRoom |
{
RoomId, RoomName, GameType, RoomLevel, RoomPartition,
RoomNumber, MaxPlayerCount, CurrentOnlinePlayerCount,
RobotCount, Players[], CreateUserId,
SystemFactor: double // 当前系统因子(根据当日盈利动态调整)
} |
RedisGamePlayer |
{
UserId, UserName, UserNumber, Avatar, IsRedName,
Level, VipLevel, Gold, Diamond, Position,
CannonId, WingsId, WarGodId, CannonMultiple,
BeginCoin, MaxGold, BeginTime, State(0=正常 1=断线),
CId, PlayerFactor, TotalRechargeGold, SafeBoxGold,
IsBot, IsAfk
} |
ChessGameRoom |
{
RoomId, state(1=等待 2=游戏中 3=已结束), users[],
gameType(1=我方黑 2=我方红), gold, password,
createUserId, robotCount, isGameEnded,
timeoutSeconds(默认60), endGameRequests, pendingWinUserId
} |
ConversationEntity |
{
UserId?, UserName?, CustomerId?, CustomerName?,
Status(0=等待 1=进行中 2=已结束), CreateTime,
EndTime?, LastReadTime?
} |
📤 发送基础设施(IHubSend<T>)
| 方法 | 说明 |
|---|---|
SendUserCId(cid, method, obj) | 发送给指定连接 |
SendUserUserId(userId, method, obj) | 通过 userId 查找 CId 后发送 |
SendGroup(groupId, method, obj) | 发送给 SignalR 群组 |
SendAll(method, obj) | 广播给所有客户端 |
GroupExcept(cid, groupId, method, obj) | 发送给群组排除指定连接 |
流程说明
1. 登录 / Bind 流程 (LoginHub)
- 客户端连接 /loginhub (JWT)
- 调用 Bind(userId),加入 userId 组
- Bind 读取 Redis LoginCId:
- 若存在旧 LoginCId 且不等于当前连接 -> 顶号:
- 发 AccountKicked 给旧 LoginHub 连接
- 查旧 LoginHub 关联的游戏 Hub CId,发 KickToMainPage
- 更新 Redis LoginCId = 当前连接 CId
- 若存在旧 LoginCId 且不等于当前连接 -> 顶号:
- 检测断线房间 (PendingRoomId):
- 有 -> 发 PendingRoomNotice(弹窗:返回房间 / 离开)
- 无 -> 进入大厅
2. 顶号流程
- 设备 A 登录:Bind -> LoginCId = cid_A
- 设备 B 登录:Bind -> LoginCId = cid_A != cid_B -> 发 AccountKicked 到 cid_A -> 更新 LoginCId = cid_B -> A 被顶
- 设备 C 登录:Bind -> LoginCId = cid_B != cid_C -> 发 AccountKicked 到 cid_B -> 更新 LoginCId = cid_C -> B 被顶
- 关键:仅 LoginHub.Bind 负责发 AccountKicked,FishGameHub 不再发送
3. JoinRoom 流程 (FishGameHub)
- 客户端调 JoinRoom(roomId, userId, posituin)
- 检查玩家是否已在房间 -> 是则返回
- 获取/创建房间 roomInfo
- 判断是否首个真人进房:
- 是 -> RegisterRoom(roomId, roomLevel) 注册出鱼服务
- 否 -> GetActiveFishes(roomId) 获取当前活跃鱼列表
- 发 RoomInfo(cid, room, activeFishes) 给调用者
- 发 OnSeatChange(join) 通知房间组
4. Reconnect 流程 (FishGameHub)
- 客户端连接 FishGameHub 后立即调 Reconnect(userId)
- 查 Redis 有无断线房间 (PendingRoomId):
- 有 -> 恢复玩家状态 State=0,走 JoinRoom 逻辑(含鱼同步)
- 无 -> 无操作,等待后续指令
- 返回结果:RoomInfo(首次完整鱼群放入 activeFishes;约 300ms 后仅兼容补发普通散鱼,Boss、黄金鱼、特殊鱼和鱼阵不重复补发)
5. CancelPlayerAfk 流程
- 客户端调 CancelPlayerAfk(roomId, userId)
- 清除玩家 IsAfk 标记
- GetActiveFishes(roomId) 获取当前活跃鱼
- 发 RoomInfo(cid, room, activeFishes) 给调用者(同步鱼状态)
- 发 PlayerCancelAfkNotice 通知房间组
6. LeaveRoom / 退出流程
- 客户端调 LeaveRoom(roomId, userId)
- 金币结算,从房间移除玩家
- 发 RoomLeaveNotice 通知房间组
- 检查剩余真人:
- 无真人 -> UnregisterRoom(roomId) 注销出鱼 + 清理房间
- 有真人 -> 房主转移(若原房主退出),发 ChangeControllerUser
7. 房主断线 / 所有权转移
- 房主断线触发 HandleDisconnect
- 查找下一个非挂机真人作为新房主:
- 找到 -> 转移所有权,发 ChangeControllerUser(新房主 userId) 通知房间组
- 未找到 -> 发 HostDisconnect(前端禁用发炮/出鱼,显示等待提示)
8. 出鱼服务 (FishSpawnService)
- 首个真人进房 -> RegisterRoom -> 启动定时器持续出鱼
- 定时出鱼 -> OnFishSpawn(type=spawn) 广播房间组
- 后续真人进房/重连/取消挂机 -> GetActiveFishes -> 嵌入 RoomInfo.activeFishes 返回,并约 300ms 后仅补发普通散鱼 OnFishSpawn(type=init)
- 最后一个真人退出/断线 -> UnregisterRoom -> 停止定时器 + 清除活跃鱼