Files
nl-im-service/cmd/server/main.go

412 lines
14 KiB
Go
Raw Normal View History

2025-12-03 11:00:47 +08:00
/**
* package main
*
* IM系统后端服务主程序
*
* 功能概述
* 1. 初始化配置数据库Redis等基础服务
* 2. 初始化业务服务认证用户联系人房间聊天附件等
* 3. 启动WebSocket服务器和TURN服务器
* 4. 注册HTTP API路由和中间件
* 5. 启动HTTP服务器
*
* 技术栈
* - Gin: HTTP Web框架
* - GORM: ORM数据库操作
* - Redis: 缓存和消息队列
* - WebSocket: 实时通信
* - JWT: 身份认证
*/
2025-12-02 21:00:26 +08:00
package main
import (
"fmt"
"log"
"net/http"
"time"
"xk-websocket-v2/internal/api"
"xk-websocket-v2/internal/manager"
2025-12-15 09:04:14 +08:00
"xk-websocket-v2/internal/mediaserver"
2025-12-03 11:00:47 +08:00
"xk-websocket-v2/internal/middleware"
2025-12-02 21:00:26 +08:00
"xk-websocket-v2/internal/model"
"xk-websocket-v2/internal/service"
"xk-websocket-v2/internal/turnserver"
2025-12-03 11:00:47 +08:00
"xk-websocket-v2/internal/utils"
2025-12-02 21:00:26 +08:00
"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"
)
2025-12-03 11:00:47 +08:00
/**
* initConfig
*
* 功能初始化配置文件读取器
*
* 步骤
* 1. 设置配置文件名为 "config"
* 2. 设置配置文件类型为 YAML
* 3. 添加配置文件搜索路径configs目录和当前目录
* 4. 读取配置文件如果失败则终止程序
*/
2025-12-02 21:00:26 +08:00
func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("configs")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("❌ 无法读取配置文件: %v", err)
}
}
2025-12-03 11:00:47 +08:00
/**
* initDB
*
* 功能初始化MySQL数据库连接并执行数据库迁移
*
* 步骤
* 1. 从配置文件中读取数据库连接字符串DSN
* 2. 使用GORM连接MySQL数据库
* 3. 如果连接失败终止程序
* 4. 执行自动数据库迁移创建所有表结构
* 5. 为所有表添加中文注释
* 6. 返回数据库连接实例
*
* @returns *gorm.DB 数据库连接实例
*/
2025-12-02 21:00:26 +08:00
func initDB() *gorm.DB {
2025-12-03 11:00:47 +08:00
// 步骤1: 从配置文件读取数据库连接字符串
2025-12-02 21:00:26 +08:00
dsn := viper.GetString("database.dsn")
2025-12-03 11:00:47 +08:00
// 步骤2: 使用GORM连接MySQL数据库
2025-12-02 21:00:26 +08:00
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("❌ 数据库连接失败: %v", err)
}
2025-12-03 11:00:47 +08:00
// 步骤3: 执行自动数据库迁移,创建所有表结构
err = db.AutoMigrate(
&model.ChatMessage{},
&model.User{},
&model.UserContact{},
&model.ContactGroup{},
&model.ChatRoom{},
2025-12-04 08:51:40 +08:00
&model.ChatConversation{},
2025-12-03 11:00:47 +08:00
&model.FriendRequest{},
&model.VerificationCode{},
&model.Attachment{},
&model.ApiRequestLog{},
&model.LoginLog{},
2025-12-08 09:08:10 +08:00
&model.Moment{},
&model.MomentLike{},
&model.MomentComment{},
&model.MomentNotification{},
2025-12-03 11:00:47 +08:00
)
if err != nil {
log.Fatalf("❌ 数据库迁移失败: %v", err)
}
// 步骤4: 为所有表添加中文注释,提高数据库可读性
tableComments := []struct {
table string
comment string
}{
{"chat_messages", "聊天消息表,持久化存储聊天记录,包括文本、多媒体和信令状态"},
{"users", "用户基本信息表"},
{"user_contacts", "用户联系人表,存储好友关系、分组、备注等信息"},
2025-12-04 08:51:40 +08:00
{"chat_conversations", "用户会话表,记录每个用户的最近会话列表和未读信息"},
2025-12-03 11:00:47 +08:00
{"contact_groups", "联系人分组表"},
{"chat_rooms", "聊天房间表,支持点对点和群聊"},
{"friend_requests", "好友申请表"},
{"verification_codes", "验证码表"},
{"attachments", "附件表,记录上传的文件信息"},
{"api_request_logs", "接口请求日志表"},
{"login_logs", "登录日志表"},
2025-12-08 09:08:10 +08:00
{"moments", "朋友圈动态表"},
{"moment_likes", "动态点赞表"},
{"moment_comments", "动态评论表"},
{"moment_notifications", "朋友圈通知表"},
2025-12-03 11:00:47 +08:00
}
for _, tc := range tableComments {
sql := fmt.Sprintf("ALTER TABLE `%s` COMMENT = '%s'", tc.table, tc.comment)
if err := db.Exec(sql).Error; err != nil {
log.Printf("⚠️ 添加表注释失败 %s: %v", tc.table, err)
}
}
log.Println("✅ 数据库迁移完成")
2025-12-02 21:00:26 +08:00
return db
}
2025-12-03 11:00:47 +08:00
/**
* initRedis
*
* 功能初始化Redis连接
*
* 步骤
* 1. 从配置文件读取Redis连接信息地址密码数据库编号
* 2. 创建Redis客户端实例
* 3. 执行Ping操作测试连接
* 4. 如果连接失败终止程序
* 5. 返回Redis客户端实例
*
* @returns *redis.Client Redis客户端实例
*/
2025-12-02 21:00:26 +08:00
func initRedis() *redis.Client {
2025-12-03 11:00:47 +08:00
// 步骤1: 从配置文件读取Redis连接信息
2025-12-02 21:00:26 +08:00
rdb := redis.NewClient(&redis.Options{
2025-12-03 11:00:47 +08:00
Addr: viper.GetString("redis.addr"), // Redis服务器地址
Password: viper.GetString("redis.password"), // Redis密码
DB: viper.GetInt("redis.db"), // Redis数据库编号
2025-12-02 21:00:26 +08:00
})
2025-12-03 11:00:47 +08:00
// 步骤2: 执行Ping操作测试连接是否正常
2025-12-02 21:00:26 +08:00
if _, err := rdb.Ping(rdb.Context()).Result(); err != nil {
log.Fatalf("❌ Redis 连接失败: %v", err)
}
2025-12-03 11:00:47 +08:00
2025-12-02 21:00:26 +08:00
return rdb
}
2025-12-03 11:00:47 +08:00
/**
* main
*
* 功能程序主入口初始化所有服务并启动HTTP服务器
*
* 执行流程
* 1. 初始化配置数据库Redis
* 2. 启动WebSocket工作池
* 3. 初始化雪花ID生成器
* 4. 初始化所有业务服务
* 5. 启动TURN服务器用于WebRTC
* 6. 创建Gin路由引擎
* 7. 注册中间件响应时间请求日志CORS
* 8. 注册WebSocket路由
* 9. 注册HTTP API路由
* 10. 启动HTTP服务器
*/
2025-12-02 21:00:26 +08:00
func main() {
2025-12-03 11:00:47 +08:00
// 步骤1: 初始化基础服务
initConfig() // 读取配置文件
db := initDB() // 连接MySQL数据库
rdb := initRedis() // 连接Redis
2025-12-02 21:00:26 +08:00
2025-12-03 11:00:47 +08:00
// 步骤2: 启动WebSocket工作池用于处理WebSocket消息
2025-12-02 21:00:26 +08:00
ws.StartWorkerPool()
2025-12-03 11:00:47 +08:00
defer ws.StopWorkerPool() // 程序退出时关闭工作池
// 步骤3: 初始化雪花ID生成器用于生成全局唯一ID
// 从配置文件读取数据中心ID和机器ID如果未配置则使用默认值
datacenterID := viper.GetInt64("snowflake.datacenter_id")
if datacenterID == 0 {
datacenterID = 1 // 默认数据中心ID为1
}
machineID := viper.GetInt64("snowflake.machine_id")
if machineID == 0 {
machineID = 1 // 默认机器ID为1
}
if err := utils.InitSnowflake(datacenterID, machineID); err != nil {
log.Fatalf("❌ 初始化雪花ID生成器失败: %v", err)
}
// 步骤4: 初始化所有业务服务
2025-12-04 08:51:40 +08:00
service.InitChatService(db, rdb) // 聊天服务消息处理、WebSocket分发
service.InitAuthService(db, rdb) // 认证服务(登录、注册、验证码)
service.InitUserService(db) // 用户服务(用户信息管理)
service.InitContactService(db) // 联系人服务(好友管理、分组管理)
service.InitConversationService(db) // 会话服务(最近聊天列表)
service.InitRoomService(db) // 房间服务(聊天房间管理)
service.InitAttachmentService(db) // 附件服务(文件上传、管理)
service.InitLoginLogService(db) // 登录日志服务(记录登录历史)
2025-12-08 09:08:10 +08:00
service.InitMomentService(db) // 朋友圈服务(动态、点赞、评论)
2025-12-14 15:19:24 +08:00
service.InitSearchService(db) // 搜索服务(聚合搜索)
2025-12-03 11:00:47 +08:00
// 步骤5: 启动TURN服务器用于WebRTC音视频通话
2025-12-02 21:00:26 +08:00
go turnserver.Start()
2025-12-15 09:04:14 +08:00
// 步骤5.5: 启动媒体服务器WebRTC SFU + RTMP
go mediaserver.Start()
2025-12-03 11:00:47 +08:00
// 步骤6: 创建Gin路由引擎
2025-12-02 21:00:26 +08:00
r := gin.Default()
2025-12-03 11:00:47 +08:00
// 步骤7: 注册中间件(按顺序执行)
// 响应时间统计中间件(必须在最前面,用于记录请求开始时间)
r.Use(middleware.ResponseTimeMiddleware())
// 接口请求日志中间件记录所有API请求信息
requestLogMiddleware := middleware.NewRequestLogMiddleware(db)
r.Use(requestLogMiddleware.Handler())
// CORS跨域中间件允许前端跨域访问重要为了前端本地开发
2025-12-02 21:00:26 +08:00
r.Use(func(c *gin.Context) {
2025-12-03 11:00:47 +08:00
// 设置允许的源(*表示允许所有源)
2025-12-02 21:00:26 +08:00
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
2025-12-03 11:00:47 +08:00
// 设置允许的HTTP方法
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
// 设置允许的请求头
2025-12-02 21:00:26 +08:00
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-User-ID")
2025-12-03 11:00:47 +08:00
// 处理OPTIONS预检请求
2025-12-02 21:00:26 +08:00
if c.Request.Method == "OPTIONS" {
2025-12-03 11:00:47 +08:00
c.AbortWithStatus(204) // 返回204 No Content
2025-12-02 21:00:26 +08:00
return
}
c.Next()
})
2025-12-03 11:00:47 +08:00
// 静态文件服务(提供附件访问功能)
// 访问路径:/uploads/xxx -> ./uploads/xxx
r.Static("/uploads", "./uploads")
// 步骤8: 注册WebSocket路由
// 路径GET /ws?user_id=xxx
2025-12-02 21:00:26 +08:00
r.GET("/ws", func(c *gin.Context) {
2025-12-03 11:00:47 +08:00
// 步骤1: 创建WebSocket升级器允许所有来源连接
2025-12-02 21:00:26 +08:00
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
2025-12-03 11:00:47 +08:00
// 步骤2: 将HTTP连接升级为WebSocket连接
2025-12-02 21:00:26 +08:00
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
2025-12-03 11:00:47 +08:00
return // 升级失败,直接返回
2025-12-02 21:00:26 +08:00
}
2025-12-03 11:00:47 +08:00
// 步骤3: 获取用户ID从查询参数中获取
2025-12-02 21:00:26 +08:00
userID := c.Query("user_id")
2025-12-03 11:00:47 +08:00
// 步骤4: 生成唯一的客户端ID节点ID + 时间戳)
2025-12-02 21:00:26 +08:00
clientID := fmt.Sprintf("%s-%d", viper.GetString("app.node_id"), time.Now().UnixNano())
2025-12-03 11:00:47 +08:00
// 步骤5: 创建客户端对象,包含连接和发送队列
2025-12-02 21:00:26 +08:00
client := &manager.Client{ID: clientID, Conn: conn, SendQueue: make(chan []byte, 256)}
2025-12-03 11:00:47 +08:00
// 步骤6: 注册客户端到管理器
2025-12-02 21:00:26 +08:00
manager.Manager.Register(client)
2025-12-03 11:00:47 +08:00
// 步骤7: 如果提供了用户ID绑定用户到客户端
2025-12-02 21:00:26 +08:00
if userID != "" {
service.ChatSvc.BindUser(client, userID)
}
2025-12-03 11:00:47 +08:00
// 步骤8: 发送客户端ID给前端
2025-12-02 21:00:26 +08:00
client.SendQueue <- []byte(fmt.Sprintf(`{"clientId": "%s"}`, clientID))
2025-12-03 11:00:47 +08:00
// 步骤9: 循环读取WebSocket消息
2025-12-02 21:00:26 +08:00
for {
_, message, err := conn.ReadMessage()
if err != nil {
2025-12-03 11:00:47 +08:00
// 连接断开,注销客户端
2025-12-02 21:00:26 +08:00
manager.Manager.Unregister(client)
break
}
// WebSocket 仅处理信令和心跳,不再处理 send_message (改走API)
// 但为了兼容,仍保留 PushTask
ws.PushTask(client, message)
}
})
2025-12-03 11:00:47 +08:00
// 步骤9: 注册HTTP API路由
2025-12-02 21:00:26 +08:00
apiGroup := r.Group("/api")
{
2025-12-03 11:00:47 +08:00
// 公开接口(不需要认证,任何人都可以访问)
apiGroup.POST("/login", api.LoginHandler)
apiGroup.POST("/register", api.RegisterHandler)
apiGroup.POST("/send-email-code", api.SendEmailCodeHandler)
apiGroup.POST("/send-sms-code", api.SendSmsCodeHandler)
apiGroup.GET("/check-token", api.CheckTokenHandler)
apiGroup.GET("/health", api.HealthHandler)
apiGroup.GET("/ice-servers", api.ICEHandler)
// 消息相关(可选认证,兼容旧代码)
2025-12-02 21:00:26 +08:00
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)
2025-12-03 11:00:47 +08:00
// 需要认证的接口组必须携带有效的JWT Token
authGroup := apiGroup.Group("")
authGroup.Use(middleware.JWTAuthMiddleware()) // 使用JWT认证中间件
{
2025-12-14 15:19:24 +08:00
// 聚合搜索
authGroup.GET("/search", api.GlobalSearchHandler)
2025-12-03 11:00:47 +08:00
// 用户管理
authGroup.GET("/user/my-info", api.GetMyInfoHandler)
authGroup.GET("/user/list", api.GetUserListHandler)
authGroup.POST("/user/create", api.CreateUserHandler)
authGroup.POST("/user/update", api.UpdateUserHandler)
authGroup.POST("/user/delete", api.DeleteUserHandler)
// 联系人管理
authGroup.GET("/contacts", api.ContactListHandler)
authGroup.GET("/contacts/search", api.SearchUsersHandler)
authGroup.POST("/contacts/add-friend", api.AddFriendHandler)
authGroup.GET("/contacts/friend-requests", api.GetFriendRequestsHandler)
authGroup.POST("/contacts/accept-request", api.AcceptFriendRequestHandler)
authGroup.POST("/contacts/reject-request", api.RejectFriendRequestHandler)
authGroup.GET("/contacts/groups", api.GetGroupsHandler)
authGroup.POST("/contacts/groups", api.CreateGroupHandler)
authGroup.POST("/contacts/groups/update/:id", api.UpdateGroupHandler)
authGroup.POST("/contacts/groups/delete/:id", api.DeleteGroupHandler)
authGroup.GET("/contacts/:id", api.GetContactDetailHandler)
authGroup.POST("/contacts/update/:id", api.UpdateContactHandler)
authGroup.POST("/contacts/delete/:id", api.DeleteContactHandler)
2025-12-04 08:51:40 +08:00
// 会话管理
2025-12-05 16:21:43 +08:00
authGroup.GET("/conversations/by-room/:room_id", api.GetConversationByRoomHandler)
2025-12-04 08:51:40 +08:00
authGroup.GET("/conversations", api.GetConversationListHandler)
authGroup.POST("/conversations/reset-unread", api.ResetConversationUnreadHandler)
authGroup.POST("/conversations/update", api.UpdateConversationHandler)
authGroup.POST("/conversations/delete", api.DeleteConversationHandler)
2025-12-04 22:29:32 +08:00
// 房间 / 群聊管理
2025-12-03 11:00:47 +08:00
authGroup.POST("/rooms", api.CreateRoomHandler)
authGroup.GET("/rooms/:id", api.GetRoomHandler)
2025-12-04 22:29:32 +08:00
// 群聊相关接口
authGroup.POST("/groups", api.CreateChatGroupHandler)
2025-12-05 13:12:48 +08:00
authGroup.GET("/groups", api.ListUserGroupsHandler) // 获取用户群聊列表(必须在 /groups/:room_id 之前)
2025-12-04 22:29:32 +08:00
authGroup.GET("/groups/:room_id", api.GetGroupInfoHandler)
authGroup.GET("/groups/:room_id/members", api.ListGroupMembersHandler)
authGroup.POST("/groups/:room_id/members", api.AddGroupMembersHandler)
2025-12-05 09:25:24 +08:00
authGroup.POST("/groups/:room_id/members/:user_id/remove", api.RemoveGroupMemberHandler)
authGroup.POST("/groups/:room_id/update", api.UpdateGroupInfoHandler)
authGroup.POST("/groups/:room_id/members/:user_id/role", api.ChangeMemberRoleHandler)
authGroup.POST("/groups/:room_id/quit", api.QuitGroupHandler)
authGroup.POST("/groups/:room_id/dissolve", api.DissolveGroupHandler)
2025-12-05 13:12:48 +08:00
authGroup.GET("/groups/:room_id/announcement", api.GetGroupAnnouncementHandler)
authGroup.POST("/groups/:room_id/announcement", api.UpdateGroupAnnouncementHandler)
2025-12-08 14:18:00 +08:00
authGroup.POST("/groups/:room_id/members/:user_id/mute", api.MuteGroupMemberHandler)
authGroup.GET("/groups/:room_id/members/:user_id/mute", api.GetMemberMuteStatusHandler)
2025-12-05 09:25:24 +08:00
authGroup.GET("/group-notifications", api.GetGroupNotificationsHandler)
2025-12-04 22:29:32 +08:00
2025-12-03 11:00:47 +08:00
// 附件管理
authGroup.POST("/attachments/upload", api.UploadAttachmentHandler)
authGroup.GET("/attachments", api.GetAttachmentsHandler)
authGroup.GET("/attachments/:id", api.GetAttachmentHandler)
authGroup.POST("/attachments/delete/:id", api.DeleteAttachmentHandler)
2025-12-08 09:08:10 +08:00
// 朋友圈管理
api.RegisterMomentRoutes(authGroup)
2025-12-15 09:04:14 +08:00
// 通话管理(音视频)
api.RegisterCallRoutes(authGroup)
// 媒体服务器管理
api.RegisterMediaRoutes(authGroup)
2025-12-03 11:00:47 +08:00
}
2025-12-02 21:00:26 +08:00
}
2025-12-03 11:00:47 +08:00
// 步骤10: 启动HTTP服务器
port := viper.GetString("app.port") // 从配置文件读取端口号
2025-12-02 21:00:26 +08:00
log.Printf("🚀 服务启动在端口: %s", port)
2025-12-03 11:00:47 +08:00
r.Run(":" + port) // 启动服务器并监听指定端口
2025-12-02 21:00:26 +08:00
}