Skip to content

聊天室

最後更新:2026-04-09


功能說明

聊天室是直播間內的即時互動系統,嵌入在 StreamerViewController(主播直播)和 STEventDetailViewController(賽事詳情)中。支援文字聊天、圖片、表情、禮物、紅包、競猜投票、主播推單、曬單分享、自動推訊息等功能。透過 WebSocket 連線實現即時通訊。

使用者流程

進入聊天室

  1. 進入直播間或賽事詳情頁 → 自動初始化 STChatManager
  2. STChatManager 建立 WebSocket 連線,取得聊天室 Token 和設定
  3. 發送 L01(subscribeHistory)訂閱歷史訊息
  4. 收到 L03(history)載入歷史訊息到 STChatView
  5. 後續透過 M01(instant)接收即時訊息

發送訊息

  1. 點擊輸入框 → 顯示 STChatInputView
  2. 輸入文字 → 發送
  3. 發送前依序檢查:是否登入 → 是否被禁言 → VIP 等級 → 充值金額 → 投注金額 → 發送頻率 → 訪客次數
  4. 檢查通過 → WebSocket 發送

送禮物

  1. 點擊禮物按鈕 → STGiftListView
  2. 選擇後透過 HTTP API SendGiftRequest 發送
  3. 伺服器推送 G01(gift)到聊天室 → GiftContainerView 播放動畫

搶紅包

  1. Socket 推送 G02(activity, type=redEnvelope)→ 顯示紅包小球
  2. 點擊 → 呼叫 ReceiveRedPacketRequest API 領取

競猜投票

  1. Socket 推送 G02(activity, type=answerLottery)→ 顯示 ChatVoteViewController
  2. 選擇投票 → 呼叫 SubmitVoteInfoRequest API
  3. 等待結果(G07 winningResult 推送)

頁面跳轉

  • 進入直播間 → 自動嵌入聊天室(無需使用者操作)
  • 點擊曬單訊息中的賽事連結 → 賽事詳情頁

技術視角(開發看這裡)

相關檔案

類型檔案路徑
Manager(聊天核心)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/STChatManager.swift
ViewModel/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/VM/STChatViewModel.swift
API/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/STChatAPI.swift
Model(訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/M/STChatMessageModel.swift
View(聊天主視圖)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/V/STChatView.swift
View(Header)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/V/STChatHeaderView.swift
View(跑馬燈)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/V/STChatMarqueeView.swift
Cell(文字訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STTextChatMessageTableViewCell.swift
Cell(圖片訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STImageChatMessageTableViewCell.swift
Cell(曬單訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STChatOrderShareTableViewCell.swift
View(禮物列表)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/STGiftListView.swift
ViewController(禮物列表)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/STGiftListViewController.swift
ViewModel(禮物列表)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/VM/STGiftListViewModel.swift
View(禮物動畫容器)/Users/user/Work/bbsport-new/BBSport/Tab/体育/Sport/EventDetail/Views/ChatView/Gift/Animation/GiftContainerView.swift
Socket 底層/Users/user/Work/bbsport-new/BBSport/Tools/UtilityToolComponentOC/Classes/Helper/Socket/SocketServices.m
Socket 管理(單例)/Users/user/Work/bbsport-new/BBSport/Tools/UtilityToolComponentOC/Classes/Helper/Socket/BBSocketServices.m
Cell(自動訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STAutoMessageTableViewCell.swift
Cell(訊息基底)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STChatMessageBaseTableViewCell.swift
Cell(系統訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STChatSystemMessageTableViewCell.swift
Cell(禮物訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STGiftMessageTableViewCell.swift
Cell(競猜訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STLiveBetGameMessageTableViewCell.swift
Cell(回覆訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STReplyChatMessageTableViewCell.swift
Cell(用戶稱號圖片)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/Cell/STUserTitleImageCell.swift
View(更多訊息)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/V/STChatMoreMessageView.swift
View(引用回覆)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/V/STChatQuoteView.swift
View(跑馬燈 Label)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/STChatView/V/STMarqueeLabel.swift
View(聊天輸入框)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/ChatInputView/STChatInputView.swift
View(表情選擇)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/ChatInputView/STEmojiView.swift
ViewController(禮物輸入)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/STGiftInputViewController.swift
Layout(禮物分頁)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/STPageCollectionViewFlowLayout.swift
View(禮物排行)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/GiftRank/STGiftRankView.swift
View(禮物排行前三)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/GiftRank/V/STGiftRankTopThreeView.swift
View(排行視圖)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/GiftRank/V/STRankView.swift
ViewModel(禮物排行)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/GiftRank/VM/GiftRankViewModel.swift
Model(禮物排行 Cell)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/Gift/M/STGiftRankTableCellModel.swift
Cell(競猜遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/LiveBetGame/Cell/LiveBetGameCell.swift
Cell(骰寶競猜)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/LiveBetGame/Cell/LiveBetGameSicBoCell.swift
View(競猜遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/LiveBetGame/LiveBetGameView.swift
ViewModel(競猜遊戲)/Users/user/Work/bbsport-new/BBSport/Tab/广场/FullScreenLive/LiveBetGame/LiveBetGameViewModel.swift
Model(直播投注遊戲)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/Model/LiveBetGame/LiveBetGameModel.swift
API(活動中獎結果)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+ActivityWinResultRequest.swift
API(活動中獎名單)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+ActivityWinUserListRequest.swift
API(取消訂閱直播)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+CancelLiveSubscribeRequest.swift
API(舉報類別列表)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+ChatReportListRequest.swift
API(娛樂直播)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+EntertainmentLiveRequest.swift
API(自動推訊息)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+FetchChatAutoMessageRequest.swift
API(支持率資訊)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetApprovalInfoRequest.swift
API(聊天 Token)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetChatTokenRequest.swift
API(直播活動)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetLiveActivityRequest.swift
API(直播投注遊戲)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetLiveBetGameRequest.swift
API(賽事直播狀態)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetMatchIDLiveStatusRequest.swift
API(推單支持率列表)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetPushOrderApprovalInfoRequest.swift
API(禮物排行榜)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetTopRankGiftListRequest.swift
API(投票資訊)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+GetVoteInfoRequest.swift
API(首頁聊天紀錄)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+HomeChatListRequest.swift
API(大額曬單配置)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LargeOrderShareConfigRequest.swift
API(加入活動)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LiveActivityJoinRequest.swift
API(表情列表)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LiveEmojiListRequest.swift
API(禮物列表)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LiveGiftListRequest.swift
API(主播資訊)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LiveHostInfoRequest.swift
API(直播訂閱資訊)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LiveSubcribeInfoRequest.swift
API(訂閱直播)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LiveSubscribeRequest.swift
API(抽獎資訊)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+LuckyDrawStartInfoRequest.swift
API(紅包雨狀態)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+RainRedPacketRequest.swift
API(領取紅包雨)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+ReceiveRainRedPacketRequest.swift
API(領取紅包)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+ReceiveRedPacketRequest.swift
API(紅包狀態)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+RedPacketRequest.swift
API(曬單上報)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+ReportOrderShareRequest.swift
API(送出舉報)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+SendChatReportRequest.swift
API(送禮物)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+SendGiftRequest.swift
API(提交支持率)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+SubmitApprovalInfoRequest.swift
API(提交投票)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+SubmitVoteInfoRequest.swift
API(轉盤遊戲資訊)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+TurnTableGameInfoRequest.swift
API(上報觀看人)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+UploadSTWatchManRequest.swift
API(進入動畫)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+UserEnterAnimationRequest.swift
API(影片 URL)/Users/user/Work/bbsport-new/BBSport/API/STAPI/Chat/STAPI+VideoUrlRequest.swift

API Endpoints

功能說明Request 名稱EndpointMethod主要參數
取得聊天 TokenGetChatTokenRequestapi/forehead/user/chat/token/v2GETliveHostType
禮物列表LiveGiftListRequestapi/forehead/live/load/giftPOSTmatchType
表情列表LiveEmojiListRequestapi/forehead/live/emoji/listPOSTmatchType
自動推訊息FetchChatAutoMessageRequestapi/forehead/live/match/message/selectPOSTmatchId, platformId, hostId, messageType
發送禮物SendGiftRequestapi/forehead/live/submit/gift/presentPOSTliveRoomId, giftId, giftCount
禮物排行榜GetTopRankGiftListRequestapi/forehead/live/room/gift/top5POSTliveRoomId
紅包狀態RedPacketRequestapi/forehead/live/get/room/redEnvelopePOSTLiveRoomId
紅包雨狀態RainRedPacketRequestapi/forehead/live/get/room/redEnvelope/templatePOSTliveRoomId, sendRecordId
領取紅包ReceiveRedPacketRequestapi/forehead/live/submit/receive/redEnvelopePOSTLiveRoomId, sendRecordId
領取紅包雨ReceiveRainRedPacketRequestapi/forehead/live/submit/receive/redEnvelope/templatePOSTliveRoomId, sendRecordId, catchTimes
舉報類別列表ChatReportListRequestapi/forehead/live/chat/report/catePOST
送出舉報SendChatReportRequestapi/forehead/live/chat/report/addPOSTaccount, cate, game, targetUid
活動列表GetLiveActivityRequestapi/forehead/live/activity/selectapi/forehead/live/match/activity/selectPOSTliveRoomId
加入活動LiveActivityJoinRequestapi/forehead/live/activity/join/record/addPOSTroomId, activityId, chatRoomId
投票資訊GetVoteInfoRequestapi/forehead/live/lottery/vote/infoPOSTroomId
提交投票SubmitVoteInfoRequestapi/forehead/live/lottery/vote/submitInfoPOSTvoteResult, recordId, roomId
支持率資訊GetApprovalInfoRequestapi/forehead/live/lottery/approval/infoPOSTroomId, matchId
推單支持率列表GetPushOrderApprovalInfoRequestapi/forehead/live/lottery/approval/info/listPOSTroomId
提交支持率SubmitApprovalInfoRequestapi/forehead/live/lottery/approval/submitInfoPOSTapprovalResult, roomId, matchId, orderId
抽獎資訊LuckyDrawStartInfoRequestapi/forehead/live/lottery/startInfoPOSTmatchId, hostId
抽獎結果ActivityWinResultRequestapi/forehead/live/lottery/winPOSTlotteryId, userId, lotteryType
中獎名單ActivityWinUserListRequestapi/forehead/live/lottery/winListPOSTlotteryId, lotteryType, page, size
主播轉盤資訊TurnTableGameInfoRequestapi/forehead/live/lottery/big/spin/infoPOSTmatchId, hostId
大額曬單配置LargeOrderShareConfigRequestapi/forehead/live/showorder/configPOST
曬單上報ReportOrderShareRequestapi/forehead/live/showorder/reportPOSTReportOrderShareRequestModel
上報觀看人UploadSTWatchManRequestapi/forehead/live/submit/watchPOSTliveRoomId, matchId
進入動畫UserEnterAnimationRequestapi/forehead/live/enter/animationPOSTchId
直播訂閱資訊LiveSubcribeInfoRequestapi/forehead/live/user/subscribe/infoPOST
首頁聊天紀錄HomeChatListRequestmsg(host=chat)GETchId
聊天室骰寶GetLiveBetGameRequestapi/forehead/live-bet-game/rounds/currentPOSTliveRoomId

資料模型

STChatMessageModel(聊天訊息)完整欄位

欄位型別說明
cmdString指令代碼(對應 STChatMessageCommand
accountString使用者帳號
msgString訊息內容(已處理過)
originMsgString原始訊息內容
nickString暱稱(含冒號後綴 ": "
nickOriginString原始暱稱
roleInt角色(0=一般, 其他=特殊身份)
timeInt時間戳(自動推訊息用來設定推送間隔,單位:分鐘)
uidString使用者 ID("0" = 系統訊息, "-1" = 自動訊息)
contentString組合後的顯示內容
vipIntVIP 等級
msgTypeInt訊息類型(對應 STChatMessageType
titleIdInt稱號 ID(從 ext 解析)
photoTitleString自訂表情名稱(從 ext 解析)
chIdString聊天室頻道 ID
giftPhotoIdString禮物圖片 ID
fullScreenPhotoIdString全螢幕禮物動效 ID
giftNameString禮物名稱
giftNumberInt禮物數量
userIconString使用者頭像
giftIdInt禮物 ID
giftDurationFloat禮物顯示時長
giftLevelInt禮物級別(0=平庸, 1=普通, 2=全螢幕炫酷)
orderInfoSTChatOrderInfo曬單資訊(僅曬單訊息有值)
replyInfoSTChatReplyInfo回覆資訊(僅回覆訊息有值)

STChatMessageType 訊息類型

名稱說明
0x01text文字訊息
0x02image圖片/自訂表情
0x03richText富文本
0x04reply回覆訊息
0x05orderShare曬單訊息
0x07autoMessage平台自動推送訊息
999none無類型

STChatOrderInfo(曬單資訊)

欄位型別說明
betTitleString投注標題
betSectionString投注選項
oddString賠率
betAmountString投注金額
wonAmountString中獎金額
orderStatusOrderShareStatus訂單狀態
betIdString投注 ID
orderIdString訂單 ID
isEarlySettlementBool是否提前結算
orderCanBeFollowedBool是否可跟單
msgString訊息
orderImageString訂單圖片
photoTitleString圖片標題
nickString暱稱
uidString使用者 ID
orderHintString訂單提示
titleIdInt稱號 ID
isFoldBool是否折疊(預設 true)
bets[STChatBet]投注明細(串關用)

STGiftModel(禮物)

欄位型別說明
idInt禮物 ID
nameString禮物名稱
photoIdString禮物圖片 ID
fullScreenPhotoIdString全螢幕動效 ID
amountDoubleFloat禮物價格
giftNumberInt禮物數量
giftDurationFloat顯示時長
giftLevelInt級別(0=平庸, 1=普通, 2=全螢幕炫酷)
typeInt類型(0=現金, 1=金幣)
goldAmountInt金幣額度
landscapeBool是否橫屏

STChatManager 初始化參數

參數說明
eventId賽事 ID
hostId主播 ID(預設 0)
liveRoomId直播房間 ID(預設 0)
chatRoomType.sport.entertainment
gameType遊戲類型(影響 chId prefix)
liveHostType主播類型(傳給 Token API)

WebSocket 連線管理

連線架構

STChatManager (Swift)
  └── BBSocketServices (ObjC, 單例)
        └── SocketServices (ObjC, 一個 URL 一個實例)
              └── SRWebSocket (SocketRocket)
  • BBSocketServices 是單例,內部用 NSMutableDictionary socketMap 以 URL 為 key 管理多個 SocketServices 實例
  • 每個 SocketServices 封裝一個 SRWebSocket 連線

連線建立流程

  1. STChatManager.connectToSocket() 被呼叫
  2. 檢查 eventId != "0"connectStatus != .connected
  3. 呼叫 fetchChatData() 取得公告、自動訊息、禮物、表情、紅包、抽獎等資料
  4. 呼叫 fetchChatToken() 取得聊天 Token(api/forehead/user/chat/token/v2
  5. Token 取得成功後,呼叫 BBSocketServices.sharedInstance().openSocket(withUrlStr:) 建立 WebSocket
  6. WebSocket 連線成功 → connectStatus = .connected → 發送 L01 訂閱歷史訊息 → 呼叫 enterAnimation() → 啟動心跳

斷線重連策略

層級機制間隔
SocketServices(底層)fl_reconnect → 非手動斷線時自動重連5 秒後重試
STChatManager(上層)reconnect() → 連線失敗時重新走完整連線流程10 秒後重試
Token 取得fetchChatToken 內建重試最多 3 次重試
  • 底層 SocketServicesdidFailWithErrordidCloseWithCode 時,若非手動斷線(isCloseByUser = NO),5 秒後重連
  • 上層 STChatManagerfailConnected 回調中先關閉 socket 再 10 秒後重新 connectToSocket()
  • 重連成功後(reConnected),發送 L01isFirst = "0")重新訂閱歷史訊息

心跳機制

  • Ping 內容:{"cmd": "ping"}(以 NSData 發送)
  • 間隔:每 30 秒發送一次
  • 首次在連線成功後啟動,heartbeat flag 確保不重複啟動
  • 實作位置:STChatManager.sendPing()BBSocketServices.sendPingWithurlStr:SocketServices.sendping:

Token 取得方式

  • API:GET api/forehead/user/chat/token/v2,帶 liveHostType 參數
  • 回傳 STChatInfoModel,包含:
    • token:聊天室 Token
    • chatBanInfo:禁言資訊(是否被禁言)
    • chatRule:發言規則(VIP 限制、充值限制、投注限制)
    • specialChIds:特殊頻道 ID(不受發言限制)
  • Token 過期時伺服器推送 E04 指令,Client 收到後重新呼叫 fetchChatToken()

聊天室切換

  • changeChannel() 方法:清空訊息 → 更新 eventId/hostId/liveRoomId → 若 socket 已連線則直接發送 L01 重新訂閱(免斷線重連)
  • 若 socket 未連線則走完整 connectToSocket() 流程

WebSocket 指令與 Payload

完整指令列表

Command方向說明
L01發送訂閱歷史訊息
L02(廢棄)點讚數
L03接收收到歷史訊息
A01(廢棄)點讚數變化
B01(廢棄)接受系統廣播訊息
M01發送/接收即時訊息
R03接收使用者被禁言
R04接收使用者充值/VIP 不足
R05接收發送頻率太高
R06接收曬單次數達到上限
R09接收訪客發言次數達上限
G01接收禮物訊息
G02接收活動通知(紅包/紅包雨/投票)
G04接收主播推單
G05接收主播撤單
G06接收抽獎訊息
G07接收活動中獎結果
P02接收使用者進入聊天室(歡迎動畫)
P001接收主播聊天室自動推訊息
P003接收主播競猜(骰寶)
E04接收Token 過期
ping發送心跳

L01 訂閱歷史訊息(發送)

json
{
  "cmd": "L01",
  "data": {
    "chId": "fb12345|67",
    "isFirst": "1",
    "guestTeamId": "fb12345|67guest",
    "homeTeamId": "fb12345|67home",
    "nick": "使用者暱稱",
    "uid": "123456",
    "vip": 3,
    "token": "chat-token-string",
    "version": "1.0.0",
    "platform": 1
  }
}
  • chId 格式:{gameType.chatIdPrefix}{eventId}{gameType.chatIdPrefix}{eventId}|{hostId}(有主播時)
  • isFirst:首次連線 "1",重連 "0"
  • platform:1=iOS, 2=Android, 3=Web

L03 歷史訊息(接收)

json
{
  "cmd": "L03",
  "data": {
    "msg": [
      {
        "cmd": "L03",
        "nick": "使用者暱稱",
        "msg": "訊息內容",
        "uid": "123456",
        "vip": 3,
        "role": 0,
        "msgType": 1,
        "chId": "fb12345|67",
        "ext": { "titleId": "1", "photoTitle": "" }
      }
    ]
  }
}

M01 即時訊息(發送 - 文字)

json
{
  "cmd": "M01",
  "data": {
    "uid": "123456",
    "account": "user_account",
    "nick": "使用者暱稱",
    "chId": "fb12345|67",
    "token": "chat-token-string",
    "vip": 3,
    "ext": { "titleId": "1" },
    "msg": "你好",
    "msgType": 1
  }
}

M01 即時訊息(發送 - 自訂表情)

json
{
  "cmd": "M01",
  "data": {
    "uid": "123456",
    "account": "user_account",
    "nick": "使用者暱稱",
    "chId": "fb12345|67",
    "token": "chat-token-string",
    "vip": 3,
    "ext": { "titleId": "1", "photoTitle": "表情名稱" },
    "msg": "image-id-string",
    "msgType": 2
  }
}

M01 即時訊息(發送 - 回覆)

json
{
  "cmd": "M01",
  "data": {
    "uid": "123456",
    "account": "user_account",
    "nick": "使用者暱稱",
    "chId": "fb12345|67",
    "token": "chat-token-string",
    "vip": 3,
    "msg": "回覆內容",
    "msgType": 4,
    "ext": {
      "titleId": "1",
      "nick": "被回覆者暱稱",
      "msg": "被回覆的訊息",
      "msgType": 1,
      "uid": "654321",
      "betTitle": "",
      "betSection": "",
      "odd": "",
      "bets": []
    }
  }
}

M01 即時訊息(發送 - 曬單)

json
{
  "cmd": "M01",
  "data": {
    "uid": "123456",
    "account": "user_account",
    "nick": "使用者暱稱",
    "chId": "fb12345|67",
    "token": "chat-token-string",
    "vip": 3,
    "msg": " 晒了一单",
    "msgType": 5,
    "ext": {
      "titleId": "1",
      "msgType": 5,
      "betTitle": "投注標題",
      "betSection": "投注選項",
      "odd": "1.85",
      "betAmount": "100",
      "wonAmount": "185",
      "orderStatus": 1,
      "betId": "bet123",
      "orderId": "order456",
      "isEarlySettlement": false,
      "orderCanBeFollowed": true,
      "orderImage": "",
      "photoTitle": "",
      "orderHint": "",
      "titleId": 1,
      "bets": []
    }
  }
}

R04 充值/VIP 不足(接收)

json
{
  "cmd": "R04",
  "data": {
    "recharge_limit": 100,
    "vip_limit": 3
  }
}

G02 活動通知(接收)

根據 notifyType 區分三種類型:

紅包(redEnvelope):

json
{
  "cmd": "G02",
  "data": {
    "notifyType": "redEnvelope",
    "sendRecordId": 12345
  }
}

紅包雨(redRain):

json
{
  "cmd": "G02",
  "data": {
    "notifyType": "redRain",
    "sendRecordId": 12345,
    "times": 5,
    "endBettingTime": 1680000000,
    "abortStatus": 0
  }
}

投票/答題(answerLottery):

json
{
  "cmd": "G02",
  "data": {
    "notifyType": "answerLottery",
    "abortStatus": 0,
    "closeTime": 1680000000,
    "status": 1
  }
}

G04 主播推單(接收)

json
{
  "cmd": "G04",
  "data": {
    "newData": [
      {
        "id": "order-id",
        ...
      }
    ]
  }
}

G05 主播撤單(接收)

json
{
  "cmd": "G05",
  "data": {
    "id": "order-id-to-delete"
  }
}

G06 抽獎訊息(接收)

json
{
  "cmd": "G06",
  "data": {
    "lotteryType": 1,
    "abortStatus": 0
  }
}
  • abortStatus:0=開始,非 0=中止
  • lotteryType.turnTable 走主播轉盤路線,其他走普通抽獎

G07 中獎結果(接收)

json
{
  "cmd": "G07",
  "data": {
    "lotteryId": 123,
    "lotteryType": 1
  }
}
  • lotteryType.turnTable 走轉盤結果顯示;.answer 重新拉投票資料;其他移除抽獎入口
  • 收到後會呼叫 fetchActivityWinResult() 查詢使用者是否中獎

P001 主播自動推訊息(接收)

json
{
  "cmd": "P001",
  "data": {
    "matchId": "12345",
    "nickname": "系統",
    "message": "自動推送的訊息內容",
    "sendTime": "5"
  }
}

P02 使用者進入(接收)

json
{
  "cmd": "P02",
  "data": {
    "chId": "fb12345|67",
    ...
  }
}

E04 Token 過期(接收)

json
{
  "cmd": "E04",
  "data": {}
}

收到後自動重新呼叫 fetchChatToken() 取得新 Token。

發言限制

發言檢查分為兩層:本地預檢(進入聊天室時)和伺服器即時回報(發送訊息後)。

本地預檢(checkChatConditions

進入聊天室後依照 Token API 回傳的規則檢查,順序為:

  1. 禁言chatBanInfo.isBan == true → 提示「您已被禁言,有疑问可联系客服」
  2. 特殊頻道豁免specialChIds 包含當前 chId → 跳過後續檢查
  3. 充值限制chatRule.rechargeLimitRule.rechargeLimitIsAccord == false → 提示「充值大于 X 元才能发言」
  4. VIP 限制chatRule.vipLimitIsAccord == false → 提示「VIP X 以上会员才能发言」

具體的 VIP 等級門檻(vipLimitVal)和充值金額門檻(rechargeLimitVal)均由後台配置,透過 Token API 下發。

伺服器即時回報

Command說明提示訊息
R03被禁言(舉報封鎖)「您已被禁言,有疑问可联系客服」
R04充值/VIP 不足「充值大于 X 元才能发言」或「VIP X 以上会员才能发言」
R05發送頻率太快「您的發送頻率太快了」(顯示為系統訊息)
R06曬單次數到限「您的晒单次数达到限制」(Toast)
R09訪客發言次數到限由伺服器推送

規則 Model 結構

STChatRuleModel
├── isAvailable: Bool          // 是否符合所有規則
├── vipLimitVal: Int           // VIP 等級門檻(後台配置)
├── vipCurrentVal: Int         // 使用者目前 VIP 等級
├── vipLimitIsAccord: Bool     // VIP 是否達標
├── rechargeLimitRule
│   ├── rechargeLimitVal: Int  // 充值金額門檻(後台配置)
│   ├── rechargeCurrentVal: Int // 使用者目前充值金額
│   ├── rechargeLimitIsAccord: Bool // 充值是否達標
│   └── coin: String           // 幣種
└── betLimitRule
    ├── limitVal: Int          // 投注金額門檻(後台配置)
    ├── currentVal: Int        // 使用者目前投注金額
    ├── limitIsAccord: Bool    // 投注是否達標
    ├── cond: Int              // 條件(0=未設置, 1=並且, 2=或是)
    └── coin: String           // 幣種

實作重點

  1. WebSocket 連線管理STChatManager 維護連線狀態(connecting / connected / noConnection),支援斷線重連
  2. 訊息上限kMaxMessageCount = 500,超過時移除最舊的訊息
  3. 發言限制層級:禁言 > 充值金額 > VIP 等級(本地預檢);伺服器額外檢查頻率限制、訪客次數、曬單次數
  4. 自動推訊息機制:體育聊天室依 time 欄位(分鐘)設定定時推送間隔,使用 STTimerTaskQueue 管理;娛樂主播首次走 API,後續走 Socket P001
  5. 聊天室切換changeChannel() 回調 didConnectToNewChat,切換賽事/主播時若 socket 已連線則無需斷線重連
  6. Chat API VersionkChatApiVersion = "1.0.0"
  7. 支持率定時刷新approvalTimer 每 5 秒呼叫 fetchApproval() 更新支持率資料
  8. 禮物發送走 HTTP 非 WebSocketSendGiftRequest 是 POST API,成功 code 為 1 或 429;code 300000 代表餘額不足,data 回傳不足額(Double)

API 呼叫流程

進入聊天室 (WebSocket 連線)

STChatManager.connectToSocket()
  ├─ [API 1] GET api/forehead/user/chat/token/v2 {liveHostType}
  │  → chatToken + chatBanInfo + chatRule (重試 3 次)

  ├─ 建立 WebSocket 連線
  │  └─ BBSocketServices.openSocket(url: getChatSocketUrl2())

  ├─ 連線成功 → 訂閱歷史訊息
  │  └─ [WS 送出] cmd:L01
  │     {chId, isFirst:"1", guestTeamId, homeTeamId, nick, uid, vip, token}
  │     → [WS 接收] cmd:L03 → 歷史訊息陣列

  ├─ [API 2] POST api/forehead/live/enter/animation {chId} → 進場動畫

  └─ 啟動心跳: 每 30 秒 ping

chId 格式

getChId():
  ├─ 有 hostId: gameType.chatIdPrefix + eventId + "|" + hostId
  │  例: "100100001|15234"
  └─ 無 hostId: gameType.chatIdPrefix + eventId
     例: "34100001"

發送訊息

用戶輸入文字 → STChatManager.sendTextMessage()
  └─ [WS 送出] cmd:M01
     {chId, uid, nick, vip, token, msg:"文字", msgType:1, ext:{titleId}}

回覆訊息 → msgType:4, ext 含原訊息資訊
自定義表情 → msgType:2, msg=imageId, ext.photoTitle=imageName
注單分享   → msgType:5, ext 含下注詳情

WebSocket 接收訊息類型

├─ L03: 歷史訊息
├─ M01: 即時文字訊息
├─ G01: 禮物通知
├─ G02: 活動事件 (投票/紅包/紅包雨)
├─ G04: 主播推單
├─ G05: 主播撤單
├─ G06: 抽獎開始
├─ G07: 中獎結果
├─ P001: 主播自動推送
├─ P002: 用戶進場
├─ P003: 骰寶遊戲
├─ R03: 用戶禁言
├─ R04: 充值不足
├─ R05: 發送頻率過高
├─ R09: 遊客發言上限
├─ E04: Token 過期 → 重新取得 token

斷線重連

連線失敗 / 異常斷開
  └─ 等待 5-10 秒 → 重新建立 WebSocket
     → reConnectedBlock → subscribeHistoryMessage(first: false)