129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"xk-websocket-v2/internal/api"
|
|
"xk-websocket-v2/internal/manager"
|
|
"xk-websocket-v2/internal/model"
|
|
"xk-websocket-v2/internal/service"
|
|
"xk-websocket-v2/internal/turnserver"
|
|
"xk-websocket-v2/internal/ws"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-redis/redis/v8"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/spf13/viper"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ... (initConfig, initDB, initRedis 保持不变) ...
|
|
func initConfig() {
|
|
viper.SetConfigName("config")
|
|
viper.SetConfigType("yaml")
|
|
viper.AddConfigPath("configs")
|
|
viper.AddConfigPath(".")
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
log.Fatalf("❌ 无法读取配置文件: %v", err)
|
|
}
|
|
}
|
|
|
|
func initDB() *gorm.DB {
|
|
dsn := viper.GetString("database.dsn")
|
|
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
|
if err != nil {
|
|
log.Fatalf("❌ 数据库连接失败: %v", err)
|
|
}
|
|
db.AutoMigrate(&model.ChatMessage{})
|
|
return db
|
|
}
|
|
|
|
func initRedis() *redis.Client {
|
|
rdb := redis.NewClient(&redis.Options{
|
|
Addr: viper.GetString("redis.addr"),
|
|
Password: viper.GetString("redis.password"),
|
|
DB: viper.GetInt("redis.db"),
|
|
})
|
|
if _, err := rdb.Ping(rdb.Context()).Result(); err != nil {
|
|
log.Fatalf("❌ Redis 连接失败: %v", err)
|
|
}
|
|
return rdb
|
|
}
|
|
|
|
func main() {
|
|
initConfig()
|
|
db := initDB()
|
|
rdb := initRedis()
|
|
|
|
ws.StartWorkerPool()
|
|
defer ws.StopWorkerPool()
|
|
service.InitChatService(db, rdb)
|
|
go turnserver.Start()
|
|
|
|
r := gin.Default()
|
|
|
|
// 允许跨域 (重要:为了前端本地开发)
|
|
r.Use(func(c *gin.Context) {
|
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-User-ID")
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(204)
|
|
return
|
|
}
|
|
c.Next()
|
|
})
|
|
|
|
r.GET("/ws", func(c *gin.Context) {
|
|
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
|
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
userID := c.Query("user_id")
|
|
clientID := fmt.Sprintf("%s-%d", viper.GetString("app.node_id"), time.Now().UnixNano())
|
|
client := &manager.Client{ID: clientID, Conn: conn, SendQueue: make(chan []byte, 256)}
|
|
|
|
manager.Manager.Register(client)
|
|
if userID != "" {
|
|
service.ChatSvc.BindUser(client, userID)
|
|
}
|
|
client.SendQueue <- []byte(fmt.Sprintf(`{"clientId": "%s"}`, clientID))
|
|
|
|
for {
|
|
_, message, err := conn.ReadMessage()
|
|
if err != nil {
|
|
manager.Manager.Unregister(client)
|
|
break
|
|
}
|
|
// WebSocket 仅处理信令和心跳,不再处理 send_message (改走API)
|
|
// 但为了兼容,仍保留 PushTask
|
|
ws.PushTask(client, message)
|
|
}
|
|
})
|
|
|
|
apiGroup := r.Group("/api")
|
|
{
|
|
apiGroup.POST("/send", api.SendHandler)
|
|
apiGroup.POST("/send-to-user", api.SendToUserHandler)
|
|
apiGroup.POST("/bind", api.BindHandler)
|
|
apiGroup.GET("/check-user-online", api.CheckUserOnlineHandler)
|
|
apiGroup.GET("/messages", api.HistoryHandler)
|
|
apiGroup.GET("/messages/sync", api.SyncMessagesHandler)
|
|
apiGroup.GET("/health", api.HealthHandler)
|
|
apiGroup.GET("/ice-servers", api.ICEHandler)
|
|
|
|
// 新增:联系人列表
|
|
apiGroup.GET("/contacts", api.ContactListHandler)
|
|
}
|
|
|
|
port := viper.GetString("app.port")
|
|
log.Printf("🚀 服务启动在端口: %s", port)
|
|
r.Run(":" + port)
|
|
}
|