Files
nl-im-websocket-demo/controller/websocket_controller.go
liqi 1b9b4c5ed9 修复:
1. 接听电话以实现
2. 后端接口适配实现

现有问题:
1. rtc通话问题待修复
2. 点击接听会有挂断的报错,需要检查逻辑
3. 修复lastMessage的更新问题
2025-07-09 09:35:06 +08:00

986 lines
34 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controller
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
"xk-websocket/models"
"xk-websocket/utils"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// WebSocketController WebSocket控制器结构体
type WebSocketController struct {
Clients map[string]*websocket.Conn
ClientsMux sync.RWMutex
WriteMutex sync.Mutex // 添加写锁
RedisCli *redis.Client
NodeID string
Port string
RedisCtx context.Context
Logger *log.Logger
}
func NewWebSocketController() *WebSocketController {
return &WebSocketController{
Clients: make(map[string]*websocket.Conn),
RedisCtx: context.Background(),
}
}
// ConfigureSystem 配置系统参数
func (c *WebSocketController) ConfigureSystem() {
c.NodeID = utils.GetEnv("NODE_ID", "local")
// 只有当端口未设置时才从环境变量获取
if c.Port == "" {
c.Port = utils.GetEnv("PORT", "12080")
log.Printf("📡📡📡📡📡📡📡📡📡📡📡📡📡📡📡📡 使用环境变量设置端口: %s", c.Port)
}
log.SetPrefix(fmt.Sprintf("[Node:%s] ", c.NodeID))
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
c.configureLogger()
// 必须先初始化 Redis 客户端
c.InitRedisClient()
// 注册节点到Redis
go c.registerNode() // 现在 RedisCli 已初始化
}
// 注册节点到Redis
func (c *WebSocketController) registerNode() {
// 添加空值检查
if c.RedisCli == nil {
log.Println("⚠️ Redis客户端未初始化节点注册已取消")
return
}
for {
key := fmt.Sprintf("websocket:nodes:%s", c.NodeID)
value := fmt.Sprintf("%s:%s", utils.GetOutboundIP(), c.Port)
if err := c.RedisCli.Set(c.RedisCtx, key, value, 30*time.Second).Err(); err != nil {
log.Printf("⚠️ 节点注册失败: %v", err)
} else {
log.Printf("📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌 节点已注册: %s = %s", key, value)
}
time.Sleep(20 * time.Second)
}
}
// 配置日志记录器
func (c *WebSocketController) configureLogger() {
logDir := utils.GetEnv("LOG_DIR", "./logs")
if err := os.MkdirAll(logDir, 0755); err != nil {
log.Fatalf("❌❌❌❌❌❌❌❌ 创建日志目录失败: %v", err)
}
logFreq := utils.GetEnv("LOG_ROTATE_FREQ", "daily")
if c.Logger == nil {
c.Logger = log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds)
}
c.Logger.SetPrefix(fmt.Sprintf("[Node:%s] ", c.NodeID))
log.SetOutput(c.getLogWriter(logDir, logFreq))
}
// 获取日志文件写入器
func (c *WebSocketController) getLogWriter(logDir, freq string) *utils.DailyFileWriter {
return utils.NewDailyFileWriter(logDir, freq, c.Logger, c.NodeID)
}
// InitRedisClient 初始化Redis客户端
func (c *WebSocketController) InitRedisClient() {
redisAddr := utils.GetEnv("REDIS_ADDR", "localhost:6379")
redisPassword := utils.GetEnv("REDIS_PASSWORD", "")
c.RedisCli = redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: redisPassword,
DB: 0,
})
if err := c.checkRedisConnection(); err != nil {
log.Fatalf("❌❌❌❌❌❌❌❌ Redis连接失败: %v", err)
}
}
// 检查Redis连接
func (c *WebSocketController) checkRedisConnection() error {
_, err := c.RedisCli.Ping(c.RedisCtx).Result()
return err
}
// PrintStartupInfo 打印启动信息
func (c *WebSocketController) PrintStartupInfo() {
hostname, _ := os.Hostname()
log.Printf("🚀🚀🚀🚀🚀🚀🚀🚀 WebSocket服务启动: NodeID=%s", c.NodeID)
log.Printf("🌐🌐🌐🌐🌐🌐🌐🌐 监听端口: %s", c.Port)
log.Printf("📡📡📡📡📡📡📡📡 Redis地址: %s", utils.GetEnv("REDIS_ADDR", "localhost:6379"))
log.Printf("💻💻💻💻💻💻💻💻 主机: %s", hostname)
log.Printf("🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒🕒 启动时间: %s", time.Now().Format("2006-01-02 15:04:05"))
log.Printf("🔔🔔🔔🔔🔔🔔🔔🔔 支持消息类型: \n 0:文本\n 1:图片\n 2:音频\n 3:视频\n 4:处方\n 5:病例\n 6:视频通话\n 7:语音通话\n 8:文件消息")
log.Printf("🔑🔑🔑🔑🔑🔑🔑🔑 用户绑定键: \n %s\n %s", models.ClientUserKey, models.UserClientKey)
log.Printf("📬📬📬📬📬📬📬📬 新增接口: POST /send-to-user (直接通过用户ID发送消息)")
log.Printf("🔗🔗🔗🔗🔗🔗🔗🔗 集群节点注册: %s:%s", utils.GetOutboundIP(), c.Port)
log.Println("🔗🔗🔗🔗🔗🔗🔗🔗 等待客户端连接...")
}
// HealthHandler 健康检查处理器
func (c *WebSocketController) HealthHandler(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"status": "ok",
"node": c.NodeID,
"port": c.Port,
"time": time.Now().Format(time.RFC3339),
})
}
// HandleWebSocket WebSocket连接处理器
func (c *WebSocketController) HandleWebSocket(ctx *gin.Context) {
start := time.Now()
clientIP := ctx.ClientIP()
log.Printf("👤👤👤👤👤👤👤👤 客户端连接中: IP=%s", clientIP)
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
log.Printf("⚠️ WebSocket升级失败: %v | ClientIP=%s", err, clientIP)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "无法升级为WebSocket连接"})
return
}
defer conn.Close()
clientID := utils.GenerateClientID(c.NodeID)
log.Printf("✅ 客户端已连接: \n ClientID=%s \n IP=%s \n Duration=%s",
clientID, clientIP, time.Since(start))
c.addClient(clientID, conn)
defer c.removeClient(clientID)
if err := conn.WriteJSON(gin.H{"clientId": clientID}); err != nil {
log.Printf("⚠️ 发送客户端ID失败: %v | ClientID=%s", err, clientID)
return
}
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
log.Printf("❌❌❌❌❌❌❌❌ 连接意外断开: %v | ClientID=%s", err, clientID)
} else {
log.Printf("⚠️ 连接正常关闭: ClientID=%s", clientID)
}
break
}
if messageType == websocket.TextMessage {
go c.handleClientMessage(clientID, message)
}
}
}
// 处理客户端消息
func (c *WebSocketController) handleClientMessage(senderID string, message []byte) {
log.Printf("📥📥📥📥📥📥📥📥 收到客户端消息: \n SenderID=%s \n Size=%d bytes", senderID, len(message))
var payload models.SendMessagePayload
if err := json.Unmarshal(message, &payload); err == nil && payload.RequestType != "" {
log.Printf("📦📦📦📦📦📦📦📦 解析JSON消息成功: Type=%s", payload.RequestType)
if payload.RequestType == "bind" && payload.SenderUserID != "" {
log.Printf("🔗🔗🔗🔗🔗🔗🔗🔗 处理绑定请求: \n ClientID=%s \n UserID=%s", senderID, payload.SenderUserID)
if err := c.bindClientToUser(senderID, payload.SenderUserID); err != nil {
log.Printf("❌❌❌❌❌❌❌❌ 绑定失败: %v", err)
} else {
log.Printf("✅ 绑定成功: \n ClientID=%s \n UserID=%s", senderID, payload.SenderUserID)
}
return
}
if payload.RequestType == "send_message" {
if payload.TargetClientID != "" {
log.Printf("📨📨📨📨📨📨📨📨 处理客户端发起的发送请求: \n TargetID=%s \n MsgType=%d",
payload.TargetClientID, payload.MessageType)
clientMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: payload.TargetClientID,
SenderUserID: payload.SenderUserID,
ReceiverUserID: payload.ReceiverUserID,
MessageType: payload.MessageType,
Content: payload.MessageContent,
SendTime: time.Now().Format(time.RFC3339),
}
c.sendMessageToClient(payload.TargetClientID, clientMsg)
return
}
if payload.ReceiverUserID != "" {
log.Printf("📨📨📨📨📨📨📨📨 处理客户端发起的用户发送请求: \n ReceiverUserID=%s \n MsgType=%d",
payload.ReceiverUserID, payload.MessageType)
clientMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: payload.SenderUserID,
ReceiverUserID: payload.ReceiverUserID,
MessageType: payload.MessageType,
Content: payload.MessageContent,
SendTime: time.Now().Format(time.RFC3339),
}
c.sendMessageToUser(payload.ReceiverUserID, clientMsg)
return
}
}
}
var clientMsg models.ClientReceivedMessage
if err := json.Unmarshal(message, &clientMsg); err == nil && clientMsg.ReceiverID != "" {
log.Printf("📨📨📨📨📨📨📨📨 处理客户端封装消息: \n TargetID=%s \n MsgType=%d",
clientMsg.ReceiverID, clientMsg.MessageType)
clientMsg.SenderID = senderID
c.sendMessageToClient(clientMsg.ReceiverID, clientMsg)
return
}
// 处理通话信令
var callSignal models.CallSignal
if err := json.Unmarshal(message, &callSignal); err == nil && callSignal.CallStatus != "" {
// 获取发送者用户ID
senderUserID, _ := c.getUserIDByClientID(senderID)
log.Printf("📞📞 处理通话信令: \n CallStatus=%s \n CallID=%s \n CallType=%d \n SenderUserID=%s \n CallerID=%s \n CalleeID=%s",
callSignal.CallStatus, callSignal.CallID, callSignal.CallType, senderUserID, callSignal.CallerID, callSignal.CalleeID)
// 处理不同类型的通话动作
switch callSignal.CallStatus {
case "invite":
c.handleCallInvite(senderID, senderUserID, callSignal)
case "accepted":
c.handleCallAccept(senderID, senderUserID, callSignal)
case "rejected":
c.handleCallReject(senderID, senderUserID, callSignal)
case "ended":
c.handleCallEnd(senderID, senderUserID, callSignal)
case "offer":
c.handleCallOffer(senderID, senderUserID, callSignal)
case "answer":
c.handleCallAnswer(senderID, senderUserID, callSignal)
case "candidate":
c.handleCallCandidate(senderID, senderUserID, callSignal)
case "hangup":
c.handleCallHangup(senderID, senderUserID, callSignal)
case "disconnected":
c.handleCallDisconnected(senderID, senderUserID, callSignal)
case "terminated":
c.handleCallTerminated(senderID, senderUserID, callSignal)
case "no-answer":
c.handleCallNoAnswer(senderID, senderUserID, callSignal)
case "busy":
c.handleCallBusy(senderID, senderUserID, callSignal)
case "failed":
c.handleCallFailed(senderID, senderUserID, callSignal)
default:
log.Printf("⚠️ 未知的通话动作: %s", callSignal.CallStatus)
}
return
}
log.Printf("⚠️ 无法识别的消息格式: \n Size=%d bytes \n Message=%s", len(message), string(message))
}
// 处理通话邀请
func (c *WebSocketController) handleCallInvite(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理通话邀请: \n CallID=%s \n CallType=%d \n From=%s \n To=%s",
signal.CallID, signal.CallType, senderUserID, signal.CalleeID)
// 创建通话消息
callMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "invite",
}
// 发送邀请给被叫方
if err := c.sendMessageToUser(signal.CalleeID, callMsg); err != nil {
log.Printf("❌ 发送通话邀请失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理通话接受
func (c *WebSocketController) handleCallAccept(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("✅ 处理通话接受: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CallerID)
// 创建通话接受消息
acceptMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CallerID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "accepted",
}
// 发送接受消息给主叫方
if err := c.sendMessageToUser(signal.CallerID, acceptMsg); err != nil {
log.Printf("❌ 发送通话接受失败: %v | CallerID=%s", err, signal.CallerID)
}
}
// 处理通话拒绝
func (c *WebSocketController) handleCallReject(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("❌ 处理通话拒绝: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CallerID)
// 创建通话拒绝消息
rejectMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CallerID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "rejected",
}
// 发送拒绝消息给主叫方
if err := c.sendMessageToUser(signal.CallerID, rejectMsg); err != nil {
log.Printf("❌ 发送通话拒绝失败: %v | CallerID=%s", err, signal.CallerID)
}
}
// 处理通话结束
func (c *WebSocketController) handleCallEnd(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理通话结束: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建通话结束消息
endMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "ended",
}
// 发送结束消息给对端
if err := c.sendMessageToUser(signal.CalleeID, endMsg); err != nil {
log.Printf("❌ 发送通话结束失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理Offer信令
func (c *WebSocketController) handleCallOffer(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理Offer信令: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建Offer消息
offerMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "offer",
}
// 发送Offer给对端
if err := c.sendMessageToUser(signal.CalleeID, offerMsg); err != nil {
log.Printf("❌ 发送Offer失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理Answer信令
func (c *WebSocketController) handleCallAnswer(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理Answer信令: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CallerID)
// 创建Answer消息
answerMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CallerID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "answer",
}
// 发送Answer给对端
if err := c.sendMessageToUser(signal.CallerID, answerMsg); err != nil {
log.Printf("❌ 发送Answer失败: %v | CallerID=%s", err, signal.CallerID)
}
}
// 处理通话候选
func (c *WebSocketController) handleCallCandidate(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📶 处理通话候选: \n CallID=%s From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建候选消息
candidateMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "candidate",
}
// 发送候选消息给对端
if err := c.sendMessageToUser(signal.CalleeID, candidateMsg); err != nil {
log.Printf("❌ 发送候选失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理通话挂断
func (c *WebSocketController) handleCallHangup(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理通话挂断: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建挂断消息
hangupMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "hangup",
}
// 发送挂断消息给对端
if err := c.sendMessageToUser(signal.CalleeID, hangupMsg); err != nil {
log.Printf("❌ 发送通话挂断失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理通话掉线
func (c *WebSocketController) handleCallDisconnected(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理通话掉线: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建掉线消息
disconnectedMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "disconnected",
}
// 发送掉线消息给对端
if err := c.sendMessageToUser(signal.CalleeID, disconnectedMsg); err != nil {
log.Printf("❌ 发送通话掉线失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理通话终止
func (c *WebSocketController) handleCallTerminated(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理通话终止: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建终止消息
terminatedMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "terminated",
}
// 发送终止消息给对端
if err := c.sendMessageToUser(signal.CalleeID, terminatedMsg); err != nil {
log.Printf("❌ 发送通话终止失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// 处理无人接听
func (c *WebSocketController) handleCallNoAnswer(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理无人接听: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CallerID)
// 创建无人接听消息
noAnswerMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CallerID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "no-answer",
}
// 发送无人接听消息
if err := c.sendMessageToUser(signal.CallerID, noAnswerMsg); err != nil {
log.Printf("❌ 发送无人接听失败: %v | CallerID=%s", err, signal.CallerID)
}
}
// 处理忙线状态
func (c *WebSocketController) handleCallBusy(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理忙线状态: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CallerID)
// 创建忙线消息
busyMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CallerID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "busy",
}
// 发送忙线消息
if err := c.sendMessageToUser(signal.CallerID, busyMsg); err != nil {
log.Printf("❌ 发送忙线状态失败: %v | CallerID=%s", err, signal.CallerID)
}
}
// 处理通话失败
func (c *WebSocketController) handleCallFailed(senderID, senderUserID string, signal models.CallSignal) {
log.Printf("📞 处理通话失败: \n CallID=%s \n From=%s \n To=%s", signal.CallID, senderUserID, signal.CalleeID)
// 创建失败消息
failedMsg := models.ClientReceivedMessage{
SenderID: senderID,
ReceiverID: "",
SenderUserID: senderUserID,
ReceiverUserID: signal.CalleeID,
MessageType: signal.CallType,
Content: signal.Data,
SendTime: time.Now().Format(time.RFC3339),
CallID: signal.CallID,
CallStatus: "failed",
}
// 发送失败消息
if err := c.sendMessageToUser(signal.CalleeID, failedMsg); err != nil {
log.Printf("❌ 发送通话失败消息失败: %v | CalleeID=%s", err, signal.CalleeID)
}
}
// SendMessageHandler API消息发送处理器
func (c *WebSocketController) SendMessageHandler(ctx *gin.Context) {
start := time.Now()
var payload models.SendMessagePayload
if err := ctx.ShouldBindJSON(&payload); err != nil {
log.Printf("⚠️ 无效的JSON请求格式: %v", err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "无效的JSON格式"})
return
}
if payload.RequestType == "" || (payload.TargetClientID == "" && payload.ReceiverUserID == "") ||
payload.SenderUserID == "" || payload.ReceiverUserID == "" {
log.Printf("⚠️ 缺少必要参数: \n request_type=%s \n target_client_id=%s \n receiver_user_id=%s \n sender_user_id=%s \n receiver_user_id=%s",
payload.RequestType, payload.TargetClientID, payload.ReceiverUserID, payload.SenderUserID, payload.ReceiverUserID)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "缺少必要参数"})
return
}
if payload.RequestType == "send_message" {
log.Printf("📤📤📤📤📤📤📤📤 处理API发送请求: \n SenderUser=%s \n MsgType=%d",
payload.SenderUserID, payload.MessageType)
clientMsg := models.ClientReceivedMessage{
SenderID: "system",
ReceiverID: payload.TargetClientID,
SenderUserID: payload.SenderUserID,
ReceiverUserID: payload.ReceiverUserID,
MessageType: payload.MessageType,
Content: payload.MessageContent,
SendTime: time.Now().Format(time.RFC3339),
}
var result string
var sendErr error
if payload.TargetClientID != "" {
log.Printf("🎯🎯🎯🎯🎯🎯🎯🎯 目标类型: ClientID | Target=%s", payload.TargetClientID)
sendErr = c.sendMessageToClient(payload.TargetClientID, clientMsg)
result = fmt.Sprintf("消息已发送到ClientID: %s", payload.TargetClientID)
} else if payload.ReceiverUserID != "" {
log.Printf("🎯🎯🎯🎯🎯🎯🎯🎯 目标类型: UserID | ReceiverUser=%s", payload.ReceiverUserID)
sendErr = c.sendMessageToUser(payload.ReceiverUserID, clientMsg)
result = fmt.Sprintf("消息已发送到UserID: %s", payload.ReceiverUserID)
} else {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "必须指定目标客户端ID或用户ID"})
return
}
if sendErr != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": sendErr.Error()})
return
}
ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": result})
log.Printf("✅ API请求完成: Duration=%s", time.Since(start))
return
}
ctx.JSON(http.StatusBadRequest, gin.H{"error": "不支持的request_type"})
}
// 用户绑定处理器
func (c *WebSocketController) BindHandler(ctx *gin.Context) {
start := time.Now()
var req models.BindRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
log.Printf("⚠️ 无效的绑定请求格式: %v", err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "无效的JSON格式"})
return
}
if req.UserID == "" || req.ClientID == "" {
log.Printf("⚠️ 缺少必要参数: \n user_id=%s \n client_id=%s", req.UserID, req.ClientID)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "user_id和client_id不能为空"})
return
}
log.Printf("🔗🔗🔗🔗🔗🔗🔗🔗 处理用户绑定请求: \n UserID=%s \n ClientID=%s", req.UserID, req.ClientID)
if err := c.bindClientToUser(req.ClientID, req.UserID); err != nil {
log.Printf("❌❌❌❌❌❌❌❌ 绑定失败: %v", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, gin.H{
"status": "success",
"message": fmt.Sprintf("绑定成功: ClientID %s -> UserID %s", req.ClientID, req.UserID),
})
log.Printf("✅ 绑定请求完成: \n UserID=%s \n ClientID=%s \n Duration=%s",
req.UserID, req.ClientID, time.Since(start))
}
// SendToUserHandler 通过用户ID发送消息处理器支持通话信令
func (c *WebSocketController) SendToUserHandler(ctx *gin.Context) {
start := time.Now()
var payload models.SendToUserPayload
if err := ctx.ShouldBindJSON(&payload); err != nil {
log.Printf("⚠️ 无效的JSON请求格式: %v", err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "无效的JSON格式"})
return
}
if payload.ReceiverUserID == "" || payload.SenderUserID == "" {
log.Printf("⚠️ 缺少必要参数: \n receiver_user_id=%s \n sender_user_id=%s",
payload.ReceiverUserID, payload.SenderUserID)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "缺少接收方或发送方用户ID"})
return
}
log.Printf("📤📤📤📤📤📤📤📤 处理API用户发送请求: \n SenderUser=%s → ReceiverUser=%s \n MsgType=%d",
payload.SenderUserID, payload.ReceiverUserID, payload.MessageType)
// 创建基础消息结构
clientMsg := models.ClientReceivedMessage{
SenderID: "system",
ReceiverID: "",
SenderUserID: payload.SenderUserID,
ReceiverUserID: payload.ReceiverUserID,
MessageType: payload.MessageType,
Content: payload.MessageContent,
SendTime: time.Now().Format(time.RFC3339),
}
// 如果是通话信令消息,添加通话相关字段
if payload.MessageType == 6 || payload.MessageType == 7 {
clientMsg.CallStatus = payload.CallStatus
clientMsg.CallID = payload.CallID
log.Printf("📞📞 处理通话信令消息: \n CallStatus=%s \n CallID=%s",
payload.CallStatus, payload.CallID)
}
// 发送消息给目标用户
if err := c.sendMessageToUser(payload.ReceiverUserID, clientMsg); err != nil {
log.Printf("❌ 发送消息失败: %v | ReceiverUserID=%s", err, payload.ReceiverUserID)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, gin.H{
"status": "success",
"message": fmt.Sprintf("消息已发送到用户: %s", payload.ReceiverUserID),
})
log.Printf("✅ API用户发送请求完成: Duration=%s", time.Since(start))
}
// 添加客户端连接
func (c *WebSocketController) addClient(clientID string, conn *websocket.Conn) {
c.ClientsMux.Lock()
defer c.ClientsMux.Unlock()
c.Clients[clientID] = conn
log.Printf("📊📊📊📊📊📊📊📊 当前连接数: %d", len(c.Clients))
}
// 移除客户端连接
func (c *WebSocketController) removeClient(clientID string) {
c.ClientsMux.Lock()
defer c.ClientsMux.Unlock()
if _, exists := c.Clients[clientID]; exists {
delete(c.Clients, clientID)
log.Printf("🔌🔌🔌🔌🔌🔌🔌🔌 客户端已断开: ClientID=%s | 剩余连接数: %d", clientID, len(c.Clients))
// 清理用户绑定关系
c.cleanupUserBinding(clientID)
}
}
// 清理用户绑定关系
func (c *WebSocketController) cleanupUserBinding(clientID string) {
// 获取用户ID
userID, err := c.getUserIDByClientID(clientID)
if err != nil {
return
}
// 从用户-客户端映射中移除
userClientsKey := fmt.Sprintf("%s:%s", models.UserClientKey, userID)
c.RedisCli.SRem(c.RedisCtx, userClientsKey, clientID)
// 从客户端-用户映射中移除
clientUserKey := fmt.Sprintf("%s:%s", models.ClientUserKey, clientID)
c.RedisCli.Del(c.RedisCtx, clientUserKey)
log.Printf("🧹🧹🧹🧹🧹🧹🧹🧹 清理绑定关系: ClientID=%s | UserID=%s", clientID, userID)
}
// 绑定客户端到用户
func (c *WebSocketController) bindClientToUser(clientID, userID string) error {
// 设置客户端->用户映射
clientUserKey := fmt.Sprintf("%s:%s", models.ClientUserKey, clientID)
if err := c.RedisCli.Set(c.RedisCtx, clientUserKey, userID, 0).Err(); err != nil {
return fmt.Errorf("设置客户端用户映射失败: %v", err)
}
// 添加用户->客户端集合映射
userClientsKey := fmt.Sprintf("%s:%s", models.UserClientKey, userID)
if err := c.RedisCli.SAdd(c.RedisCtx, userClientsKey, clientID).Err(); err != nil {
return fmt.Errorf("添加用户客户端集合失败: %v", err)
}
log.Printf("🔗🔗🔗🔗🔗🔗🔗🔗 绑定成功: \n ClientID=%s \n UserID=%s", clientID, userID)
return nil
}
// 根据客户端ID获取用户ID
func (c *WebSocketController) getUserIDByClientID(clientID string) (string, error) {
clientUserKey := fmt.Sprintf("%s:%s", models.ClientUserKey, clientID)
userID, err := c.RedisCli.Get(c.RedisCtx, clientUserKey).Result()
if err != nil {
return "", fmt.Errorf("获取用户ID失败: %v", err)
}
return userID, nil
}
// 根据用户ID获取客户端ID列表
func (c *WebSocketController) getClientIDsByUserID(userID string) ([]string, error) {
userClientsKey := fmt.Sprintf("%s:%s", models.UserClientKey, userID)
clientIDs, err := c.RedisCli.SMembers(c.RedisCtx, userClientsKey).Result()
if err != nil {
return nil, fmt.Errorf("获取客户端ID列表失败: %v", err)
}
return clientIDs, nil
}
// 发送消息给指定客户端
func (c *WebSocketController) sendMessageToClient(clientID string, message models.ClientReceivedMessage) error {
c.ClientsMux.RLock()
conn, exists := c.Clients[clientID]
c.ClientsMux.RUnlock()
if !exists {
// 尝试通过Redis转发到其他节点
return c.forwardMessageToOtherNodes(clientID, message)
}
c.WriteMutex.Lock()
defer c.WriteMutex.Unlock()
if err := conn.WriteJSON(message); err != nil {
log.Printf("❌ 发送消息失败: %v | ClientID=%s", err, clientID)
c.removeClient(clientID)
return err
}
log.Printf("✅ 消息已发送: \n ClientID=%s \n MsgType=%d \n Content=%s",
clientID, message.MessageType, message.Content)
return nil
}
// 发送消息给指定用户
func (c *WebSocketController) sendMessageToUser(userID string, message models.ClientReceivedMessage) error {
clientIDs, err := c.getClientIDsByUserID(userID)
if err != nil {
log.Printf("⚠️ 获取用户客户端列表失败: %v | UserID=%s", err, userID)
return err
}
if len(clientIDs) == 0 {
log.Printf("⚠️ 用户无在线客户端: UserID=%s", userID)
return fmt.Errorf("用户 %s 无在线客户端", userID)
}
log.Printf("📤📤📤📤📤📤📤📤 发送消息给用户: \n UserID=%s \n ClientCount=%d \n MsgType=%d",
userID, len(clientIDs), message.MessageType)
var lastErr error
successCount := 0
for _, clientID := range clientIDs {
if err := c.sendMessageToClient(clientID, message); err != nil {
log.Printf("⚠️ 发送到客户端失败: %v | ClientID=%s", err, clientID)
lastErr = err
} else {
successCount++
}
}
if successCount == 0 {
return fmt.Errorf("所有客户端发送失败,最后错误: %v", lastErr)
}
log.Printf("✅ 用户消息发送完成: \n UserID=%s \n 成功=%d/%d", userID, successCount, len(clientIDs))
return nil
}
// 转发消息到其他节点
func (c *WebSocketController) forwardMessageToOtherNodes(clientID string, message models.ClientReceivedMessage) error {
redisMsg := models.RedisMessage{
SenderNodeID: c.NodeID,
ClientID: clientID,
Message: "",
}
msgBytes, err := json.Marshal(message)
if err != nil {
return fmt.Errorf("序列化消息失败: %v", err)
}
redisMsg.Message = string(msgBytes)
redisMsgBytes, err := json.Marshal(redisMsg)
if err != nil {
return fmt.Errorf("序列化Redis消息失败: %v", err)
}
channel := fmt.Sprintf("websocket:forward:%s", clientID)
if err := c.RedisCli.Publish(c.RedisCtx, channel, redisMsgBytes).Err(); err != nil {
return fmt.Errorf("发布Redis消息失败: %v", err)
}
log.Printf("📡📡📡📡📡📡📡📡 消息已转发到其他节点: \n ClientID=%s \n Channel=%s", clientID, channel)
return nil
}
// 启动Redis消息监听
func (c *WebSocketController) SubscribeToRedis() {
go func() {
pattern := "websocket:forward:*"
pubsub := c.RedisCli.PSubscribe(c.RedisCtx, pattern)
defer pubsub.Close()
log.Printf("📡📡📡📡📡📡📡📡 Redis消息监听已启动: Pattern=%s", pattern)
for msg := range pubsub.Channel() {
var redisMsg models.RedisMessage
if err := json.Unmarshal([]byte(msg.Payload), &redisMsg); err != nil {
log.Printf("⚠️ 解析Redis消息失败: %v", err)
continue
}
// 忽略自己发送的消息
if redisMsg.SenderNodeID == c.NodeID {
continue
}
// 检查目标客户端是否在本节点
c.ClientsMux.RLock()
conn, exists := c.Clients[redisMsg.ClientID]
c.ClientsMux.RUnlock()
if !exists {
continue
}
var clientMsg models.ClientReceivedMessage
if err := json.Unmarshal([]byte(redisMsg.Message), &clientMsg); err != nil {
log.Printf("⚠️ 解析客户端消息失败: %v", err)
continue
}
c.WriteMutex.Lock()
if err := conn.WriteJSON(clientMsg); err != nil {
log.Printf("❌ 转发消息失败: %v | ClientID=%s", err, redisMsg.ClientID)
c.removeClient(redisMsg.ClientID)
} else {
log.Printf("✅ 转发消息成功: \n ClientID=%s \n FromNode=%s",
redisMsg.ClientID, redisMsg.SenderNodeID)
}
c.WriteMutex.Unlock()
}
}()
}