223 lines
5.7 KiB
Go
223 lines
5.7 KiB
Go
/**
|
||
* package api
|
||
* 作用:处理 HTTP 请求接口,包括消息发送、历史记录、系统状态检查等。
|
||
* 说明:V2 版本中消息发送主要通过 HTTP 接口进行,而非 WebSocket 直接推送。
|
||
*/
|
||
package api
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"time"
|
||
"xk-websocket-v2/internal/manager"
|
||
"xk-websocket-v2/internal/model"
|
||
"xk-websocket-v2/internal/service"
|
||
"xk-websocket-v2/internal/turnserver"
|
||
"xk-websocket-v2/internal/utils"
|
||
|
||
"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 {
|
||
utils.BadRequest(c, "无效的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. 返回成功响应
|
||
utils.Success(c, "消息已发送")
|
||
}
|
||
|
||
/**
|
||
* 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 {
|
||
utils.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
// 调用服务层进行绑定
|
||
service.ChatSvc.BindUserByClientID(req.ClientID, req.UserID)
|
||
utils.Success(c, "绑定成功")
|
||
}
|
||
|
||
/**
|
||
* CheckUserOnlineHandler
|
||
* 功能:检查用户是否在线。
|
||
* 路径:GET /api/check-user-online
|
||
*/
|
||
func CheckUserOnlineHandler(c *gin.Context) {
|
||
userID := c.Query("user_id")
|
||
// 调用服务层查询 Redis
|
||
isOnline := service.ChatSvc.IsUserOnline(userID)
|
||
utils.SuccessWithData(c, gin.H{"is_online": isOnline}, "查询成功")
|
||
}
|
||
|
||
// ==========================================
|
||
// 数据查询接口
|
||
// ==========================================
|
||
|
||
/**
|
||
* HistoryHandler
|
||
* 功能:获取历史消息记录 (分页)。
|
||
* 路径:GET /api/messages
|
||
*/
|
||
func HistoryHandler(c *gin.Context) {
|
||
roomID := c.Query("room_id")
|
||
if roomID == "" {
|
||
utils.BadRequest(c, "room_id参数必填")
|
||
return
|
||
}
|
||
|
||
// 分页参数
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
|
||
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 100 {
|
||
pageSize = 50
|
||
}
|
||
|
||
var msgs []model.ChatMessage
|
||
var total int64
|
||
|
||
// 获取总数
|
||
service.ChatSvc.DB.Model(&model.ChatMessage{}).Where("room_id = ?", roomID).Count(&total)
|
||
|
||
// 分页查询
|
||
offset := (page - 1) * pageSize
|
||
result := service.ChatSvc.DB.Where("room_id = ?", roomID).
|
||
Order("created_at desc").
|
||
Offset(offset).
|
||
Limit(pageSize).
|
||
Find(&msgs)
|
||
|
||
if result.Error != nil {
|
||
utils.InternalError(c, "db error")
|
||
return
|
||
}
|
||
|
||
// 确保返回空数组而不是null
|
||
if msgs == nil {
|
||
msgs = []model.ChatMessage{}
|
||
}
|
||
|
||
utils.SuccessWithData(c, gin.H{
|
||
"data": msgs,
|
||
"total": total,
|
||
"page": page,
|
||
"size": pageSize,
|
||
}, "获取成功")
|
||
}
|
||
|
||
/**
|
||
* SyncMessagesHandler
|
||
* 功能:全量同步消息 (V1 兼容)。
|
||
* 路径:GET /api/messages/sync
|
||
*/
|
||
func SyncMessagesHandler(c *gin.Context) {
|
||
HistoryHandler(c)
|
||
}
|
||
|
||
|
||
// ==========================================
|
||
// 系统与 WebRTC 接口
|
||
// ==========================================
|
||
|
||
/**
|
||
* HealthHandler
|
||
* 功能:服务健康检查。
|
||
* 路径:GET /api/health
|
||
*/
|
||
func HealthHandler(c *gin.Context) {
|
||
utils.SuccessWithData(c, 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,
|
||
}
|
||
utils.SuccessWithData(c, []model.ICEServerConfig{cfg}, "获取成功")
|
||
}
|