/** * package api * 作用:处理 HTTP 请求接口,包括消息发送、历史记录、系统状态检查等。 * 说明:V2 版本中消息发送主要通过 HTTP 接口进行,而非 WebSocket 直接推送。 */ package api import ( "fmt" "net/http" "time" "xk-websocket-v2/internal/manager" "xk-websocket-v2/internal/model" "xk-websocket-v2/internal/service" "xk-websocket-v2/internal/turnserver" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) // ========================================== // 消息发送相关接口 // ========================================== /** * SendHandler * 功能:通用消息发送接口 (HTTP -> WebSocket)。 * 路径:POST /api/send * 逻辑:支持通过 target_client_id 或 receiver_user_id 发送。 * 重要:必须正确处理 SenderClientID,防止多端同步时误通知发送端自己。 */ func SendHandler(c *gin.Context) { var req model.SendMessageReq // 1. 绑定并校验 JSON 参数 if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的JSON参数"}) return } // 2. 获取发送者 ID (从 Header 中获取,模拟鉴权) // 生产环境应从 JWT Token 中解析 UserID senderID := c.GetHeader("X-User-ID") if senderID == "" { senderID = "system" // 默认为系统消息 } // 3. [关键修复] 获取发送端的 WebSocket ClientID // 前端在调用此接口时,必须带上自己的 socket_client_id // 如果前端没传 (兼容旧代码),则 fallback 到 api-gateway,这会导致发送端自己也收到"其他设备接听"通知 clientID := req.SenderClientID if clientID == "" { clientID = "api-gateway" } // 4. 构造临时的发送者客户端对象 // 这个对象将传递给 Service 层,用于识别消息来源 mockClient := &manager.Client{ UserID: senderID, ID: clientID, } // 5. 调用核心业务逻辑处理消息 service.ChatSvc.HandleUserMessage(mockClient, &req) // 6. 返回成功响应 c.JSON(http.StatusOK, gin.H{"status": "ok"}) } /** * SendToUserHandler (V1 兼容) * 功能:专门用于给指定用户发送消息。 * 路径:POST /api/send-to-user */ func SendToUserHandler(c *gin.Context) { // 复用 SendHandler,因为 V2 的 SendHandler 已经支持 ReceiverUserID SendHandler(c) } // ========================================== // 用户与连接管理接口 // ========================================== /** * BindHandler * 功能:手动绑定 ClientID 和 UserID。 * 路径:POST /api/bind * 场景:当 WebSocket 连接建立后,客户端通过 HTTP 接口补充用户信息。 */ func BindHandler(c *gin.Context) { var req model.BindReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) return } // 调用服务层进行绑定 service.ChatSvc.BindUserByClientID(req.ClientID, req.UserID) c.JSON(http.StatusOK, gin.H{"status": "success"}) } /** * CheckUserOnlineHandler * 功能:检查用户是否在线。 * 路径:GET /api/check-user-online */ func CheckUserOnlineHandler(c *gin.Context) { userID := c.Query("user_id") // 调用服务层查询 Redis isOnline := service.ChatSvc.IsUserOnline(userID) c.JSON(http.StatusOK, gin.H{"status": "success", "result": isOnline}) } // ========================================== // 数据查询接口 // ========================================== /** * HistoryHandler * 功能:获取历史消息记录 (分页)。 * 路径:GET /api/messages */ func HistoryHandler(c *gin.Context) { roomID := c.Query("room_id") var msgs []model.ChatMessage // 简单查询最近 50 条 // 生产环境应添加 page, page_size 参数 result := service.ChatSvc.DB.Where("room_id = ?", roomID). Order("created_at desc").Limit(50).Find(&msgs) if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "db error"}) return } c.JSON(http.StatusOK, gin.H{"data": msgs}) } /** * SyncMessagesHandler * 功能:全量同步消息 (V1 兼容)。 * 路径:GET /api/messages/sync */ func SyncMessagesHandler(c *gin.Context) { HistoryHandler(c) } /** * ContactListHandler * 功能:获取联系人列表 (模拟数据)。 * 路径:GET /api/contacts * 说明:用于前端展示登录后的好友列表。 */ func ContactListHandler(c *gin.Context) { // 模拟 10 个用户数据 users := []model.UserContact{ {ID: "1001", Name: "张三 (我)", Avatar: "张", Desc: "Golang 专家"}, {ID: "1002", Name: "李琦 (妻)", Avatar: "李", Desc: "在线"}, {ID: "1003", Name: "王医生", Avatar: "医", Desc: "主任医师"}, {ID: "1004", Name: "客服小蜜", Avatar: "客", Desc: "全天在线"}, {ID: "1005", Name: "技术支持", Avatar: "技", Desc: "请重启试试"}, {ID: "1006", Name: "财务小赵", Avatar: "财", Desc: "报销单请提交"}, {ID: "1007", Name: "运维阿强", Avatar: "运", Desc: "服务器维护中"}, {ID: "1008", Name: "测试小丽", Avatar: "测", Desc: "Bug 太多了"}, {ID: "1009", Name: "HR", Avatar: "人", Desc: "本月考勤异常"}, {ID: "1010", Name: "老板", Avatar: "老", Desc: "今晚开会"}, } c.JSON(http.StatusOK, gin.H{"data": users}) } // ========================================== // 系统与 WebRTC 接口 // ========================================== /** * HealthHandler * 功能:服务健康检查。 * 路径:GET /api/health */ func HealthHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "node": viper.GetString("app.node_id"), "time": time.Now().Format(time.RFC3339), }) } /** * ICEHandler * 功能:获取 TURN/STUN 服务器配置及临时凭证。 * 路径:GET /api/ice-servers * 用途:WebRTC 前端在建立 PeerConnection 前需调用此接口。 */ func ICEHandler(c *gin.Context) { userID := c.Query("user_id") // 生成临时凭证 (HMAC-SHA1) username, credential := turnserver.GenerateCredentials(userID) // 从配置读取公网IP和端口 ip := viper.GetString("turn.public_ip") port := viper.GetInt("turn.listen_port") // 构造配置返回给前端 cfg := model.ICEServerConfig{ Urls: []string{fmt.Sprintf("turn:%s:%d", ip, port)}, Username: username, Credential: credential, } c.JSON(http.StatusOK, gin.H{"data": []model.ICEServerConfig{cfg}}) }