Files
nl-im-service/internal/api/handler.go
2025-12-02 21:00:26 +08:00

210 lines
6.2 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 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}})
}