修复了一些问题
This commit is contained in:
@@ -106,6 +106,8 @@ func initDB() *gorm.DB {
|
|||||||
&model.MomentLike{},
|
&model.MomentLike{},
|
||||||
&model.MomentComment{},
|
&model.MomentComment{},
|
||||||
&model.MomentNotification{},
|
&model.MomentNotification{},
|
||||||
|
&model.MessageReadReceipt{},
|
||||||
|
&model.UserSetting{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("❌ 数据库迁移失败: %v", err)
|
log.Fatalf("❌ 数据库迁移失败: %v", err)
|
||||||
@@ -131,6 +133,8 @@ func initDB() *gorm.DB {
|
|||||||
{"moment_likes", "动态点赞表"},
|
{"moment_likes", "动态点赞表"},
|
||||||
{"moment_comments", "动态评论表"},
|
{"moment_comments", "动态评论表"},
|
||||||
{"moment_notifications", "朋友圈通知表"},
|
{"moment_notifications", "朋友圈通知表"},
|
||||||
|
{"message_read_receipts", "消息已读回执表"},
|
||||||
|
{"user_settings", "用户个性化设置表"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tableComments {
|
for _, tc := range tableComments {
|
||||||
@@ -197,6 +201,15 @@ func main() {
|
|||||||
db := initDB() // 连接MySQL数据库
|
db := initDB() // 连接MySQL数据库
|
||||||
rdb := initRedis() // 连接Redis
|
rdb := initRedis() // 连接Redis
|
||||||
|
|
||||||
|
// 初始化 ip2region 离线 IP 库
|
||||||
|
ip2regionPath := viper.GetString("ip2region.db_path")
|
||||||
|
if ip2regionPath == "" {
|
||||||
|
ip2regionPath = "./ip2region.xdb"
|
||||||
|
}
|
||||||
|
if err := utils.InitIP2Region(ip2regionPath); err != nil {
|
||||||
|
log.Printf("⚠️ ip2region 初始化失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 步骤2: 启动WebSocket工作池,用于处理WebSocket消息
|
// 步骤2: 启动WebSocket工作池,用于处理WebSocket消息
|
||||||
ws.StartWorkerPool()
|
ws.StartWorkerPool()
|
||||||
defer ws.StopWorkerPool() // 程序退出时关闭工作池
|
defer ws.StopWorkerPool() // 程序退出时关闭工作池
|
||||||
@@ -283,7 +296,7 @@ func main() {
|
|||||||
clientID := fmt.Sprintf("%s-%d", viper.GetString("app.node_id"), time.Now().UnixNano())
|
clientID := fmt.Sprintf("%s-%d", viper.GetString("app.node_id"), time.Now().UnixNano())
|
||||||
|
|
||||||
// 步骤5: 创建客户端对象,包含连接和发送队列
|
// 步骤5: 创建客户端对象,包含连接和发送队列
|
||||||
client := &manager.Client{ID: clientID, Conn: conn, SendQueue: make(chan []byte, 256)}
|
client := &manager.Client{ID: clientID, RemoteIP: c.ClientIP(), Conn: conn, SendQueue: make(chan []byte, 256)}
|
||||||
|
|
||||||
// 步骤6: 注册客户端到管理器
|
// 步骤6: 注册客户端到管理器
|
||||||
manager.Manager.Register(client)
|
manager.Manager.Register(client)
|
||||||
@@ -344,6 +357,7 @@ func main() {
|
|||||||
// 用户管理
|
// 用户管理
|
||||||
authGroup.GET("/user/my-info", api.GetMyInfoHandler)
|
authGroup.GET("/user/my-info", api.GetMyInfoHandler)
|
||||||
authGroup.GET("/user/list", api.GetUserListHandler)
|
authGroup.GET("/user/list", api.GetUserListHandler)
|
||||||
|
authGroup.GET("/user/:id", api.GetUserByIDHandler)
|
||||||
authGroup.POST("/user/create", api.CreateUserHandler)
|
authGroup.POST("/user/create", api.CreateUserHandler)
|
||||||
authGroup.POST("/user/update", api.UpdateUserHandler)
|
authGroup.POST("/user/update", api.UpdateUserHandler)
|
||||||
authGroup.POST("/user/delete", api.DeleteUserHandler)
|
authGroup.POST("/user/delete", api.DeleteUserHandler)
|
||||||
@@ -363,6 +377,15 @@ func main() {
|
|||||||
authGroup.POST("/contacts/update/:id", api.UpdateContactHandler)
|
authGroup.POST("/contacts/update/:id", api.UpdateContactHandler)
|
||||||
authGroup.POST("/contacts/delete/:id", api.DeleteContactHandler)
|
authGroup.POST("/contacts/delete/:id", api.DeleteContactHandler)
|
||||||
|
|
||||||
|
// 消息撤回、登出、已读回执
|
||||||
|
authGroup.POST("/messages/recall", api.RecallMessageHandler)
|
||||||
|
authGroup.POST("/messages/read-receipts", api.MarkMessagesReadHandler)
|
||||||
|
authGroup.POST("/logout", api.LogoutHandler)
|
||||||
|
|
||||||
|
// 用户设置
|
||||||
|
authGroup.GET("/user/settings", api.GetUserSettingsHandler)
|
||||||
|
authGroup.POST("/user/settings", api.UpdateUserSettingsHandler)
|
||||||
|
|
||||||
// 会话管理
|
// 会话管理
|
||||||
authGroup.GET("/conversations/by-room/:room_id", api.GetConversationByRoomHandler)
|
authGroup.GET("/conversations/by-room/:room_id", api.GetConversationByRoomHandler)
|
||||||
authGroup.GET("/conversations", api.GetConversationListHandler)
|
authGroup.GET("/conversations", api.GetConversationListHandler)
|
||||||
@@ -389,7 +412,10 @@ func main() {
|
|||||||
authGroup.POST("/groups/:room_id/announcement", api.UpdateGroupAnnouncementHandler)
|
authGroup.POST("/groups/:room_id/announcement", api.UpdateGroupAnnouncementHandler)
|
||||||
authGroup.POST("/groups/:room_id/members/:user_id/mute", api.MuteGroupMemberHandler)
|
authGroup.POST("/groups/:room_id/members/:user_id/mute", api.MuteGroupMemberHandler)
|
||||||
authGroup.GET("/groups/:room_id/members/:user_id/mute", api.GetMemberMuteStatusHandler)
|
authGroup.GET("/groups/:room_id/members/:user_id/mute", api.GetMemberMuteStatusHandler)
|
||||||
|
authGroup.GET("/groups/:room_id/settings", api.GetGroupSettingsHandler)
|
||||||
|
authGroup.POST("/groups/:room_id/settings", api.UpdateGroupSettingsHandler)
|
||||||
authGroup.GET("/group-notifications", api.GetGroupNotificationsHandler)
|
authGroup.GET("/group-notifications", api.GetGroupNotificationsHandler)
|
||||||
|
authGroup.GET("/calls/history", api.GetCallHistoryHandler)
|
||||||
|
|
||||||
// 附件管理
|
// 附件管理
|
||||||
authGroup.POST("/attachments/upload", api.UploadAttachmentHandler)
|
authGroup.POST("/attachments/upload", api.UploadAttachmentHandler)
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -44,6 +44,7 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260630140118-a99989343ebd // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -71,6 +71,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260630140118-a99989343ebd h1:KWmIx5ok5cv0xfiMn6PajeITT/P4NcMV8FdjglAp0s8=
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260630140118-a99989343ebd/go.mod h1:sj5LMpsqB4IWdwIrcmmBJM6m+rW/uOQLSGUPhKkqdh8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func LoginHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取客户端IP
|
// 获取客户端IP
|
||||||
ip := getClientIP(c)
|
ip := utils.GetClientIP(c)
|
||||||
|
|
||||||
// 调用认证服务登录
|
// 调用认证服务登录
|
||||||
user, err := service.AuthSvc.Login(req.Account, req.Password)
|
user, err := service.AuthSvc.Login(req.Account, req.Password)
|
||||||
@@ -57,24 +57,6 @@ func LoginHandler(c *gin.Context) {
|
|||||||
}, "登录成功")
|
}, "登录成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClientIP 获取客户端IP
|
|
||||||
func getClientIP(c *gin.Context) string {
|
|
||||||
// 优先从X-Forwarded-For获取
|
|
||||||
ip := c.GetHeader("X-Forwarded-For")
|
|
||||||
if ip != "" {
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从X-Real-IP获取
|
|
||||||
ip = c.GetHeader("X-Real-IP")
|
|
||||||
if ip != "" {
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从RemoteAddr获取
|
|
||||||
return c.ClientIP()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RegisterHandler
|
* RegisterHandler
|
||||||
* 功能:用户注册
|
* 功能:用户注册
|
||||||
@@ -222,4 +204,3 @@ func SendSmsCodeHandler(c *gin.Context) {
|
|||||||
"code": code, // 仅开发环境,生产环境应移除
|
"code": code, // 仅开发环境,生产环境应移除
|
||||||
}, "验证码已发送")
|
}, "验证码已发送")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ func UpdateContactHandler(c *gin.Context) {
|
|||||||
GroupID uint `json:"group_id"`
|
GroupID uint `json:"group_id"`
|
||||||
IsTop *bool `json:"is_top"`
|
IsTop *bool `json:"is_top"`
|
||||||
IsMuted *bool `json:"is_muted"`
|
IsMuted *bool `json:"is_muted"`
|
||||||
|
IsBlocked *bool `json:"is_blocked"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@@ -330,6 +331,9 @@ func UpdateContactHandler(c *gin.Context) {
|
|||||||
if req.IsMuted != nil {
|
if req.IsMuted != nil {
|
||||||
updates["is_muted"] = *req.IsMuted
|
updates["is_muted"] = *req.IsMuted
|
||||||
}
|
}
|
||||||
|
if req.IsBlocked != nil {
|
||||||
|
updates["is_blocked"] = *req.IsBlocked
|
||||||
|
}
|
||||||
|
|
||||||
if err := service.ContactSvc.UpdateContact(userID.(string), contactID, updates); err != nil {
|
if err := service.ContactSvc.UpdateContact(userID.(string), contactID, updates); err != nil {
|
||||||
utils.BadRequest(c, "更新失败: "+err.Error())
|
utils.BadRequest(c, "更新失败: "+err.Error())
|
||||||
|
|||||||
@@ -56,14 +56,19 @@ func SendHandler(c *gin.Context) {
|
|||||||
// 4. 构造临时的发送者客户端对象
|
// 4. 构造临时的发送者客户端对象
|
||||||
// 这个对象将传递给 Service 层,用于识别消息来源
|
// 这个对象将传递给 Service 层,用于识别消息来源
|
||||||
mockClient := &manager.Client{
|
mockClient := &manager.Client{
|
||||||
UserID: senderID,
|
UserID: senderID,
|
||||||
ID: clientID,
|
ID: clientID,
|
||||||
|
RemoteIP: utils.GetClientIP(c),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 调用核心业务逻辑处理消息
|
// 5. 调用核心业务逻辑处理消息,返回持久化后的消息体供前端对齐 ID
|
||||||
service.ChatSvc.HandleUserMessage(mockClient, &req)
|
savedMsg := service.ChatSvc.HandleUserMessage(mockClient, &req)
|
||||||
|
|
||||||
// 6. 返回成功响应
|
// 6. 返回成功响应(含服务端消息 ID,避免前端重复展示)
|
||||||
|
if savedMsg != nil {
|
||||||
|
utils.SuccessWithData(c, savedMsg, "消息已发送")
|
||||||
|
return
|
||||||
|
}
|
||||||
utils.Success(c, "消息已发送")
|
utils.Success(c, "消息已发送")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +174,54 @@ func HistoryHandler(c *gin.Context) {
|
|||||||
}, "获取成功")
|
}, "获取成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecallMessageHandler
|
||||||
|
* 功能:撤回消息(2 分钟内,仅发送者可撤回)
|
||||||
|
* 路径:POST /api/messages/recall
|
||||||
|
*/
|
||||||
|
func RecallMessageHandler(c *gin.Context) {
|
||||||
|
userID, exists := c.Get("user_id")
|
||||||
|
if !exists {
|
||||||
|
utils.Unauthorized(c, "未认证")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
MessageID uint `json:"message_id" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.BadRequest(c, "参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg model.ChatMessage
|
||||||
|
if err := service.ChatSvc.DB.First(&msg, req.MessageID).Error; err != nil {
|
||||||
|
utils.NotFound(c, "消息不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.SenderUserID != userID.(string) {
|
||||||
|
utils.Forbidden(c, "无权撤回该消息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(msg.CreatedAt) > 2*time.Minute {
|
||||||
|
utils.BadRequest(c, "超过2分钟,无法撤回")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.MessageType = model.MessageTypeSystem
|
||||||
|
msg.Content = "撤回了一条消息"
|
||||||
|
if err := service.ChatSvc.DB.Save(&msg).Error; err != nil {
|
||||||
|
utils.InternalError(c, "撤回失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
service.ChatSvc.BroadcastChatMessage(msg)
|
||||||
|
|
||||||
|
utils.SuccessWithData(c, msg, "撤回成功")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SyncMessagesHandler
|
* SyncMessagesHandler
|
||||||
* 功能:全量同步消息 (V1 兼容)。
|
* 功能:全量同步消息 (V1 兼容)。
|
||||||
@@ -178,7 +231,6 @@ func SyncMessagesHandler(c *gin.Context) {
|
|||||||
HistoryHandler(c)
|
HistoryHandler(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 系统与 WebRTC 接口
|
// 系统与 WebRTC 接口
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -934,3 +934,93 @@ func GetMemberMuteStatusHandler(c *gin.Context) {
|
|||||||
"muted_until": mutedUntil,
|
"muted_until": mutedUntil,
|
||||||
}, "获取成功")
|
}, "获取成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetGroupSettingsHandler
|
||||||
|
* 功能:获取群设置(名称、头像、公告、邀请权限等)
|
||||||
|
* 路径:GET /api/groups/:room_id/settings
|
||||||
|
*/
|
||||||
|
func GetGroupSettingsHandler(c *gin.Context) {
|
||||||
|
roomID := c.Param("room_id")
|
||||||
|
room, err := service.RoomSvc.GetRoom(roomID)
|
||||||
|
if err != nil {
|
||||||
|
utils.NotFound(c, "群聊不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SuccessWithData(c, gin.H{
|
||||||
|
"name": room.RoomName,
|
||||||
|
"avatar": room.RoomAvatar,
|
||||||
|
"description": room.Announcement,
|
||||||
|
"invite_permission": 0,
|
||||||
|
"created_at": room.CreatedAt,
|
||||||
|
"updated_at": room.UpdatedAt,
|
||||||
|
}, "获取成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateGroupSettingsHandler
|
||||||
|
* 功能:更新群设置
|
||||||
|
* 路径:POST /api/groups/:room_id/settings
|
||||||
|
*/
|
||||||
|
func UpdateGroupSettingsHandler(c *gin.Context) {
|
||||||
|
userID, _ := c.Get("user_id")
|
||||||
|
roomID := c.Param("room_id")
|
||||||
|
|
||||||
|
ok, err := service.RoomSvc.IsGroupAdminOrOwner(roomID, userID.(string))
|
||||||
|
if err != nil || !ok {
|
||||||
|
utils.Forbidden(c, "无权限修改群设置")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
InvitePermission *int `json:"invite_permission"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.BadRequest(c, "参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updates := map[string]interface{}{}
|
||||||
|
if req.Name != "" {
|
||||||
|
updates["room_name"] = req.Name
|
||||||
|
}
|
||||||
|
if req.Avatar != "" {
|
||||||
|
updates["room_avatar"] = req.Avatar
|
||||||
|
}
|
||||||
|
if req.Description != "" {
|
||||||
|
updates["announcement"] = req.Description
|
||||||
|
}
|
||||||
|
if len(updates) == 0 {
|
||||||
|
utils.BadRequest(c, "没有可更新的字段")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := service.RoomSvc.DB.Model(&model.ChatRoom{}).Where("room_id = ?", roomID).Updates(updates).Error; err != nil {
|
||||||
|
utils.InternalError(c, "更新失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GetGroupSettingsHandler(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCallHistoryHandler
|
||||||
|
* 功能:获取当前用户的通话记录(基于 call_id 消息)
|
||||||
|
* 路径:GET /api/calls/history
|
||||||
|
*/
|
||||||
|
func GetCallHistoryHandler(c *gin.Context) {
|
||||||
|
userID, _ := c.Get("user_id")
|
||||||
|
var messages []model.ChatMessage
|
||||||
|
err := service.ChatSvc.DB.
|
||||||
|
Where("(sender_user_id = ? OR receiver_user_id = ?) AND call_id <> '' AND call_id IS NOT NULL", userID, userID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Limit(50).
|
||||||
|
Find(&messages).Error
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalError(c, "查询失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SuccessWithData(c, messages, "获取成功")
|
||||||
|
}
|
||||||
|
|||||||
126
internal/api/settings_handler.go
Normal file
126
internal/api/settings_handler.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* package api
|
||||||
|
* 作用:用户设置与消息已读回执 HTTP 接口
|
||||||
|
*/
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"xk-websocket-v2/internal/model"
|
||||||
|
"xk-websocket-v2/internal/service"
|
||||||
|
"xk-websocket-v2/internal/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetUserSettingsHandler
|
||||||
|
* 功能:获取当前用户的全部设置
|
||||||
|
*/
|
||||||
|
func GetUserSettingsHandler(c *gin.Context) {
|
||||||
|
userID, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
utils.Unauthorized(c, "未认证")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []model.UserSetting
|
||||||
|
if err := service.ChatSvc.DB.Where("user_id = ?", userID.(string)).Find(&rows).Error; err != nil {
|
||||||
|
utils.InternalError(c, "查询失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]string, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
result[row.SettingKey] = row.SettingValue
|
||||||
|
}
|
||||||
|
utils.SuccessWithData(c, result, "获取成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateUserSettingsHandler
|
||||||
|
* 功能:批量更新用户设置
|
||||||
|
*/
|
||||||
|
func UpdateUserSettingsHandler(c *gin.Context) {
|
||||||
|
userID, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
utils.Unauthorized(c, "未认证")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Settings map[string]string `json:"settings" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.BadRequest(c, "参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := userID.(string)
|
||||||
|
now := time.Now()
|
||||||
|
for key, val := range req.Settings {
|
||||||
|
setting := model.UserSetting{
|
||||||
|
UserID: uid,
|
||||||
|
SettingKey: key,
|
||||||
|
SettingValue: val,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
service.ChatSvc.DB.Save(&setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
GetUserSettingsHandler(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MarkMessagesReadHandler
|
||||||
|
* 功能:批量标记消息已读
|
||||||
|
*/
|
||||||
|
func MarkMessagesReadHandler(c *gin.Context) {
|
||||||
|
userID, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
utils.Unauthorized(c, "未认证")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
MessageIDs []uint `json:"message_ids" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.BadRequest(c, "参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := userID.(string)
|
||||||
|
now := time.Now()
|
||||||
|
readIDs := make([]uint, 0, len(req.MessageIDs))
|
||||||
|
for _, mid := range req.MessageIDs {
|
||||||
|
receipt := model.MessageReadReceipt{
|
||||||
|
MessageID: mid,
|
||||||
|
UserID: uid,
|
||||||
|
ReadAt: now,
|
||||||
|
}
|
||||||
|
service.ChatSvc.DB.Where("message_id = ? AND user_id = ?", mid, uid).
|
||||||
|
Assign(receipt).FirstOrCreate(&receipt)
|
||||||
|
readIDs = append(readIDs, mid)
|
||||||
|
|
||||||
|
// 通知消息发送者(已读回执)
|
||||||
|
var msg model.ChatMessage
|
||||||
|
if err := service.ChatSvc.DB.First(&msg, mid).Error; err == nil && msg.SenderUserID != "" && msg.SenderUserID != uid {
|
||||||
|
payload := model.WsPayload{
|
||||||
|
RequestType: "messages_read",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"message_ids": []uint{mid},
|
||||||
|
"reader_id": uid,
|
||||||
|
"room_id": msg.RoomID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if bytes, err := json.Marshal(payload); err == nil {
|
||||||
|
service.ChatSvc.DispatchMessage(msg.SenderUserID, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SuccessWithData(c, gin.H{"message_ids": readIDs}, "已标记已读")
|
||||||
|
}
|
||||||
@@ -40,6 +40,27 @@ func GetMyInfoHandler(c *gin.Context) {
|
|||||||
utils.SuccessWithData(c, user, "获取成功")
|
utils.SuccessWithData(c, user, "获取成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetUserByIDHandler
|
||||||
|
* 功能:根据用户 ID 获取公开资料
|
||||||
|
* 路径:GET /api/user/:id
|
||||||
|
*/
|
||||||
|
func GetUserByIDHandler(c *gin.Context) {
|
||||||
|
targetID := c.Param("id")
|
||||||
|
if targetID == "" {
|
||||||
|
utils.BadRequest(c, "用户ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := service.UserSvc.GetUserByID(targetID)
|
||||||
|
if err != nil {
|
||||||
|
utils.NotFound(c, "用户不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Password = ""
|
||||||
|
utils.SuccessWithData(c, user, "获取成功")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetUserListHandler
|
* GetUserListHandler
|
||||||
* 功能:获取用户列表
|
* 功能:获取用户列表
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
ID string // 客户端唯一标识 (格式: NodeID-Timestamp)
|
ID string // 客户端唯一标识 (格式: NodeID-Timestamp)
|
||||||
UserID string // 绑定的用户ID (未绑定时为空)
|
UserID string // 绑定的用户ID (未绑定时为空)
|
||||||
|
RemoteIP string // WebSocket 连接来源 IP
|
||||||
Conn *websocket.Conn // 底层 WebSocket 连接对象
|
Conn *websocket.Conn // 底层 WebSocket 连接对象
|
||||||
SendQueue chan []byte // 发送缓冲队列 (防止网络阻塞导致写协程卡死)
|
SendQueue chan []byte // 发送缓冲队列 (防止网络阻塞导致写协程卡死)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func (m *RequestLogMiddleware) Handler() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取请求IP
|
// 获取请求IP
|
||||||
ip := getClientIP(c)
|
ip := utils.GetClientIP(c)
|
||||||
|
|
||||||
// 本地IP不记录
|
// 本地IP不记录
|
||||||
if utils.GetIPLocation(ip) == "本地" {
|
if utils.GetIPLocation(ip) == "本地" {
|
||||||
@@ -253,28 +253,6 @@ func (m *RequestLogMiddleware) logRequest(c *gin.Context, ip, userID string, req
|
|||||||
m.DB.Create(&log)
|
m.DB.Create(&log)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClientIP 获取客户端IP
|
|
||||||
func getClientIP(c *gin.Context) string {
|
|
||||||
// 优先从X-Forwarded-For获取
|
|
||||||
ip := c.GetHeader("X-Forwarded-For")
|
|
||||||
if ip != "" {
|
|
||||||
// X-Forwarded-For可能包含多个IP,取第一个
|
|
||||||
ips := strings.Split(ip, ",")
|
|
||||||
if len(ips) > 0 {
|
|
||||||
return strings.TrimSpace(ips[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从X-Real-IP获取
|
|
||||||
ip = c.GetHeader("X-Real-IP")
|
|
||||||
if ip != "" {
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从RemoteAddr获取
|
|
||||||
return c.ClientIP()
|
|
||||||
}
|
|
||||||
|
|
||||||
// responseWriter 响应写入器(用于捕获响应内容)
|
// responseWriter 响应写入器(用于捕获响应内容)
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
gin.ResponseWriter
|
gin.ResponseWriter
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ type ChatMessage struct {
|
|||||||
RoomID string `gorm:"type:varchar(100);index;comment:房间ID" json:"room_id"`
|
RoomID string `gorm:"type:varchar(100);index;comment:房间ID" json:"room_id"`
|
||||||
// 发送者用户ID
|
// 发送者用户ID
|
||||||
SenderUserID string `gorm:"type:varchar(100);index;comment:发送者用户ID" json:"sender_user_id"`
|
SenderUserID string `gorm:"type:varchar(100);index;comment:发送者用户ID" json:"sender_user_id"`
|
||||||
|
// 发送者 IP
|
||||||
|
SenderIP string `gorm:"type:varchar(50);comment:发送者IP" json:"sender_ip"`
|
||||||
|
// IP 归属地
|
||||||
|
IPLocation string `gorm:"type:varchar(255);comment:IP归属地" json:"ip_location"`
|
||||||
// 接收者用户ID
|
// 接收者用户ID
|
||||||
ReceiverUserID string `gorm:"type:varchar(100);index;comment:接收者用户ID" json:"receiver_user_id"`
|
ReceiverUserID string `gorm:"type:varchar(100);index;comment:接收者用户ID" json:"receiver_user_id"`
|
||||||
// 消息类型 ( // 0:文本 1:图片 2:语音 3:视频 8:文件 6:信令)
|
// 消息类型 ( // 0:文本 1:图片 2:语音 3:视频 8:文件 6:信令)
|
||||||
@@ -798,6 +802,36 @@ const (
|
|||||||
MomentMediaTypeVideo = 2 // 视频
|
MomentMediaTypeVideo = 2 // 视频
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 已读回执与用户设置
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MessageReadReceipt
|
||||||
|
* 作用:消息已读回执表
|
||||||
|
*/
|
||||||
|
type MessageReadReceipt struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
|
MessageID uint `json:"message_id" gorm:"index;not null;uniqueIndex:uk_msg_user,priority:1"`
|
||||||
|
UserID string `json:"user_id" gorm:"size:100;index;not null;uniqueIndex:uk_msg_user,priority:2"`
|
||||||
|
ReadAt time.Time `json:"read_at" gorm:"not null"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MessageReadReceipt) TableName() string { return "message_read_receipts" }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserSetting
|
||||||
|
* 作用:用户个性化设置键值表
|
||||||
|
*/
|
||||||
|
type UserSetting struct {
|
||||||
|
UserID string `json:"user_id" gorm:"size:100;primaryKey"`
|
||||||
|
SettingKey string `json:"setting_key" gorm:"size:50;primaryKey"`
|
||||||
|
SettingValue string `json:"setting_value" gorm:"type:text"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UserSetting) TableName() string { return "user_settings" }
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 常量定义
|
// 常量定义
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"xk-websocket-v2/internal/manager"
|
"xk-websocket-v2/internal/manager"
|
||||||
"xk-websocket-v2/internal/model"
|
"xk-websocket-v2/internal/model"
|
||||||
"xk-websocket-v2/internal/turnserver"
|
"xk-websocket-v2/internal/turnserver"
|
||||||
|
"xk-websocket-v2/internal/utils"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@@ -92,8 +93,9 @@ func (s *ChatService) IsUserOnline(userID string) bool {
|
|||||||
* 功能:处理用户发来的消息(入口函数)。包含多端同步、URL 抓取、持久化和转发逻辑。
|
* 功能:处理用户发来的消息(入口函数)。包含多端同步、URL 抓取、持久化和转发逻辑。
|
||||||
* @param senderClient 发送消息的客户端(用于识别来源设备)
|
* @param senderClient 发送消息的客户端(用于识别来源设备)
|
||||||
* @param req 消息请求体
|
* @param req 消息请求体
|
||||||
|
* @returns 持久化后的消息(信令/群通知/被拦截时返回 nil)
|
||||||
*/
|
*/
|
||||||
func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model.SendMessageReq) {
|
func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model.SendMessageReq) *model.ChatMessage {
|
||||||
// 1. [关键] 拦截通话信令:处理多端同步逻辑
|
// 1. [关键] 拦截通话信令:处理多端同步逻辑
|
||||||
if req.CallStatus == "accepted" {
|
if req.CallStatus == "accepted" {
|
||||||
s.NotifyOtherDevices(senderClient.UserID, senderClient.ID, req.CallID)
|
s.NotifyOtherDevices(senderClient.UserID, senderClient.ID, req.CallID)
|
||||||
@@ -128,7 +130,7 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
|||||||
// 验证群成员身份
|
// 验证群成员身份
|
||||||
memberIDs, mErr := RoomSvc.GetRoomMembers(req.RoomID)
|
memberIDs, mErr := RoomSvc.GetRoomMembers(req.RoomID)
|
||||||
if mErr != nil {
|
if mErr != nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isMember := false
|
isMember := false
|
||||||
@@ -150,7 +152,7 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
|||||||
}
|
}
|
||||||
errorBytes, _ := json.Marshal(errorMsg)
|
errorBytes, _ := json.Marshal(errorMsg)
|
||||||
s.DispatchMessage(senderClient.UserID, errorBytes)
|
s.DispatchMessage(senderClient.UserID, errorBytes)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,9 +162,27 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
|||||||
receiverUserID = req.RoomID
|
receiverUserID = req.RoomID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 私聊:接收方已拉黑发送方则拒绝
|
||||||
|
if !isGroupMessage && req.ReceiverUserID != "" && ContactSvc != nil {
|
||||||
|
if ContactSvc.IsBlocked(req.ReceiverUserID, senderClient.UserID) {
|
||||||
|
errorMsg := model.WsPayload{
|
||||||
|
RequestType: "error",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"message": "消息已发出,但被对方拒收",
|
||||||
|
"code": "BLOCKED",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
errorBytes, _ := json.Marshal(errorMsg)
|
||||||
|
s.DispatchMessage(senderClient.UserID, errorBytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
msg := model.ChatMessage{
|
msg := model.ChatMessage{
|
||||||
RoomID: req.RoomID,
|
RoomID: req.RoomID,
|
||||||
SenderUserID: senderClient.UserID,
|
SenderUserID: senderClient.UserID,
|
||||||
|
SenderIP: senderClient.RemoteIP,
|
||||||
|
IPLocation: utils.GetIPLocation(senderClient.RemoteIP),
|
||||||
ReceiverUserID: receiverUserID,
|
ReceiverUserID: receiverUserID,
|
||||||
MessageType: req.MessageType,
|
MessageType: req.MessageType,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
@@ -225,11 +245,12 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
|||||||
|
|
||||||
// 处理@通知
|
// 处理@通知
|
||||||
s.handleMentionNotification(senderClient.UserID, req.RoomID, extraData, msg)
|
s.handleMentionNotification(senderClient.UserID, req.RoomID, extraData, msg)
|
||||||
return
|
return &msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.DispatchMessage(req.ReceiverUserID, msgBytes)
|
s.DispatchMessage(req.ReceiverUserID, msgBytes)
|
||||||
|
return &msg
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -275,6 +296,7 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
|||||||
log.Printf("📡 [WebRTC] 定向信令: To=%s Action=%s", req.ReceiverUserID, req.CallStatus)
|
log.Printf("📡 [WebRTC] 定向信令: To=%s Action=%s", req.ReceiverUserID, req.CallStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助:正则提取第一个 URL
|
// 辅助:正则提取第一个 URL
|
||||||
@@ -381,6 +403,39 @@ func (s *ChatService) NotifyOtherDevices(userID, currentClientID, callID string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BroadcastChatMessage
|
||||||
|
* 功能:将聊天消息推送给房间成员或私聊对方(含发送者其他设备)
|
||||||
|
*/
|
||||||
|
func (s *ChatService) BroadcastChatMessage(msg model.ChatMessage) {
|
||||||
|
pushMsg := model.WsPayload{
|
||||||
|
RequestType: "receive_message",
|
||||||
|
Data: msg,
|
||||||
|
}
|
||||||
|
msgBytes, _ := json.Marshal(pushMsg)
|
||||||
|
|
||||||
|
if msg.RoomID != "" {
|
||||||
|
room, err := RoomSvc.GetRoom(msg.RoomID)
|
||||||
|
if err == nil && room.RoomType == "group" {
|
||||||
|
memberIDs, mErr := RoomSvc.GetRoomMembers(msg.RoomID)
|
||||||
|
if mErr == nil {
|
||||||
|
for _, uid := range memberIDs {
|
||||||
|
if uid != msg.SenderUserID {
|
||||||
|
s.DispatchMessage(uid, msgBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.DispatchMessage(msg.SenderUserID, msgBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.ReceiverUserID != "" {
|
||||||
|
s.DispatchMessage(msg.ReceiverUserID, msgBytes)
|
||||||
|
}
|
||||||
|
s.DispatchMessage(msg.SenderUserID, msgBytes)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DispatchMessage
|
* DispatchMessage
|
||||||
* 功能:将消息路由到目标用户(无论其在哪个节点)。
|
* 功能:将消息路由到目标用户(无论其在哪个节点)。
|
||||||
|
|||||||
@@ -524,8 +524,54 @@ func (s *ContactService) GetUserDetailWithFriendStatus(currentUserID, targetUser
|
|||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"is_friend": isFriend,
|
"is_friend": isFriend,
|
||||||
"contact": contact,
|
"contact": contact,
|
||||||
"user": user,
|
"user": user,
|
||||||
|
"common_groups": s.GetCommonGroups(currentUserID, targetUserID),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCommonGroups
|
||||||
|
* 功能:获取两人共同加入的群聊列表
|
||||||
|
*/
|
||||||
|
func (s *ContactService) GetCommonGroups(userID, contactID string) []model.ChatRoom {
|
||||||
|
var myRooms []model.RoomMember
|
||||||
|
if err := s.DB.Where("user_id = ?", userID).Find(&myRooms).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
roomSet := make(map[string]bool, len(myRooms))
|
||||||
|
for _, m := range myRooms {
|
||||||
|
roomSet[m.RoomID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var theirRooms []model.RoomMember
|
||||||
|
if err := s.DB.Where("user_id = ?", contactID).Find(&theirRooms).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
commonIDs := make([]string, 0)
|
||||||
|
for _, m := range theirRooms {
|
||||||
|
if roomSet[m.RoomID] {
|
||||||
|
commonIDs = append(commonIDs, m.RoomID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(commonIDs) == 0 {
|
||||||
|
return []model.ChatRoom{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rooms []model.ChatRoom
|
||||||
|
_ = s.DB.Where("room_id IN ? AND room_type = ?", commonIDs, "group").Find(&rooms).Error
|
||||||
|
return rooms
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IsBlocked
|
||||||
|
* 功能:检查 owner 是否拉黑了 target(owner 对 target 设置了 is_blocked)
|
||||||
|
*/
|
||||||
|
func (s *ContactService) IsBlocked(ownerID, targetID string) bool {
|
||||||
|
var contact model.UserContact
|
||||||
|
err := s.DB.Where("user_id = ? AND contact_id = ? AND is_blocked = ?", ownerID, targetID, true).
|
||||||
|
First(&contact).Error
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|||||||
28
internal/utils/get_client_ip.go
Normal file
28
internal/utils/get_client_ip.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* package utils
|
||||||
|
* 作用:从 HTTP 请求中提取客户端真实 IP
|
||||||
|
*/
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetClientIP
|
||||||
|
* 功能:优先从代理头获取,否则使用 Gin ClientIP
|
||||||
|
*/
|
||||||
|
func GetClientIP(c *gin.Context) string {
|
||||||
|
ip := c.GetHeader("X-Forwarded-For")
|
||||||
|
if ip != "" {
|
||||||
|
parts := strings.Split(ip, ",")
|
||||||
|
return strings.TrimSpace(parts[0])
|
||||||
|
}
|
||||||
|
ip = c.GetHeader("X-Real-IP")
|
||||||
|
if ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return c.ClientIP()
|
||||||
|
}
|
||||||
@@ -1,163 +1,102 @@
|
|||||||
/**
|
/**
|
||||||
* package utils
|
* package utils
|
||||||
* 作用:IP归属地查询工具
|
* 作用:IP 归属地查询(基于本地 ip2region.xdb 离线库)
|
||||||
* 说明:使用第三方API查询IP归属地信息
|
|
||||||
*/
|
*/
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"log"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPLocationInfo IP归属地信息
|
|
||||||
type IPLocationInfo struct {
|
|
||||||
Country string `json:"country"` // 国家
|
|
||||||
Region string `json:"region"` // 省份
|
|
||||||
City string `json:"city"` // 城市
|
|
||||||
ISP string `json:"isp"` // 运营商
|
|
||||||
FullLocation string `json:"full_location"` // 完整归属地
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存结构(简单内存缓存)
|
|
||||||
var (
|
var (
|
||||||
ipCache = make(map[string]*IPLocationInfo)
|
ipSearcher *xdb.Searcher
|
||||||
cacheTimeout = 24 * time.Hour
|
ipSearcherOnce sync.Once
|
||||||
|
ipSearcherErr error
|
||||||
|
ipCache sync.Map
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InitIP2Region
|
||||||
|
* 功能:加载 ip2region.xdb,服务启动时调用一次
|
||||||
|
*/
|
||||||
|
func InitIP2Region(dbPath string) error {
|
||||||
|
ipSearcherOnce.Do(func() {
|
||||||
|
ipSearcher, ipSearcherErr = xdb.NewWithFileOnly(xdb.IPv4, dbPath)
|
||||||
|
if ipSearcherErr != nil {
|
||||||
|
log.Printf("❌ [IP2Region] 加载失败: %v", ipSearcherErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("✅ [IP2Region] 已加载: %s", dbPath)
|
||||||
|
})
|
||||||
|
return ipSearcherErr
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetIPLocation
|
* GetIPLocation
|
||||||
* 功能:获取IP归属地信息
|
* 功能:查询 IP 归属地,内网/本地返回「本地」
|
||||||
* @param ip IP地址
|
|
||||||
* @returns 归属地信息字符串
|
|
||||||
*/
|
*/
|
||||||
func GetIPLocation(ip string) string {
|
func GetIPLocation(ip string) string {
|
||||||
// 排除本地IP
|
|
||||||
if isLocalIP(ip) {
|
if isLocalIP(ip) {
|
||||||
return "本地"
|
return "本地"
|
||||||
}
|
}
|
||||||
|
if ip == "" {
|
||||||
// 检查缓存
|
return "未知"
|
||||||
if info, ok := ipCache[ip]; ok {
|
|
||||||
return info.FullLocation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询IP归属地
|
if cached, ok := ipCache.Load(ip); ok {
|
||||||
info := queryIPLocation(ip)
|
return cached.(string)
|
||||||
if info != nil {
|
|
||||||
// 构建完整归属地字符串
|
|
||||||
parts := []string{}
|
|
||||||
if info.Country != "" {
|
|
||||||
parts = append(parts, info.Country)
|
|
||||||
}
|
|
||||||
if info.Region != "" {
|
|
||||||
parts = append(parts, info.Region)
|
|
||||||
}
|
|
||||||
if info.City != "" {
|
|
||||||
parts = append(parts, info.City)
|
|
||||||
}
|
|
||||||
if info.ISP != "" {
|
|
||||||
parts = append(parts, info.ISP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) > 0 {
|
|
||||||
info.FullLocation = strings.Join(parts, " ")
|
|
||||||
} else {
|
|
||||||
info.FullLocation = "未知"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存入缓存
|
|
||||||
ipCache[ip] = info
|
|
||||||
return info.FullLocation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "未知"
|
if ipSearcher == nil {
|
||||||
|
return "未知"
|
||||||
|
}
|
||||||
|
|
||||||
|
region, err := ipSearcher.Search(ip)
|
||||||
|
if err != nil || region == "" {
|
||||||
|
return "未知"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式: 国家|区域|省份|城市|ISP
|
||||||
|
parts := strings.Split(region, "|")
|
||||||
|
labels := []string{}
|
||||||
|
for _, p := range parts {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p != "" && p != "0" {
|
||||||
|
labels = append(labels, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := "未知"
|
||||||
|
if len(labels) > 0 {
|
||||||
|
result = strings.Join(labels, " ")
|
||||||
|
}
|
||||||
|
ipCache.Store(ip, result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* isLocalIP
|
* isLocalIP
|
||||||
* 功能:判断是否为本地IP
|
* 功能:判断是否为本地/内网 IP
|
||||||
*/
|
*/
|
||||||
func isLocalIP(ip string) bool {
|
func isLocalIP(ip string) bool {
|
||||||
// 本地回环地址
|
|
||||||
if ip == "127.0.0.1" || ip == "localhost" || ip == "::1" {
|
if ip == "127.0.0.1" || ip == "localhost" || ip == "::1" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(ip, "192.168.") || strings.HasPrefix(ip, "10.") {
|
||||||
// 内网地址
|
return true
|
||||||
if strings.HasPrefix(ip, "192.168.") ||
|
}
|
||||||
strings.HasPrefix(ip, "10.") ||
|
if strings.HasPrefix(ip, "172.16.") || strings.HasPrefix(ip, "172.17.") ||
|
||||||
strings.HasPrefix(ip, "172.16.") ||
|
strings.HasPrefix(ip, "172.18.") || strings.HasPrefix(ip, "172.19.") ||
|
||||||
strings.HasPrefix(ip, "172.17.") ||
|
strings.HasPrefix(ip, "172.20.") || strings.HasPrefix(ip, "172.21.") ||
|
||||||
strings.HasPrefix(ip, "172.18.") ||
|
strings.HasPrefix(ip, "172.22.") || strings.HasPrefix(ip, "172.23.") ||
|
||||||
strings.HasPrefix(ip, "172.19.") ||
|
strings.HasPrefix(ip, "172.24.") || strings.HasPrefix(ip, "172.25.") ||
|
||||||
strings.HasPrefix(ip, "172.20.") ||
|
strings.HasPrefix(ip, "172.26.") || strings.HasPrefix(ip, "172.27.") ||
|
||||||
strings.HasPrefix(ip, "172.21.") ||
|
strings.HasPrefix(ip, "172.28.") || strings.HasPrefix(ip, "172.29.") ||
|
||||||
strings.HasPrefix(ip, "172.22.") ||
|
strings.HasPrefix(ip, "172.30.") || strings.HasPrefix(ip, "172.31.") {
|
||||||
strings.HasPrefix(ip, "172.23.") ||
|
|
||||||
strings.HasPrefix(ip, "172.24.") ||
|
|
||||||
strings.HasPrefix(ip, "172.25.") ||
|
|
||||||
strings.HasPrefix(ip, "172.26.") ||
|
|
||||||
strings.HasPrefix(ip, "172.27.") ||
|
|
||||||
strings.HasPrefix(ip, "172.28.") ||
|
|
||||||
strings.HasPrefix(ip, "172.29.") ||
|
|
||||||
strings.HasPrefix(ip, "172.30.") ||
|
|
||||||
strings.HasPrefix(ip, "172.31.") {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* queryIPLocation
|
|
||||||
* 功能:查询IP归属地(使用ip-api.com免费API)
|
|
||||||
*/
|
|
||||||
func queryIPLocation(ip string) *IPLocationInfo {
|
|
||||||
// 使用ip-api.com免费API(限制:每分钟45次请求)
|
|
||||||
url := fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN&fields=status,message,country,regionName,city,isp", ip)
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 3 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var result struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Country string `json:"country"`
|
|
||||||
Region string `json:"regionName"`
|
|
||||||
City string `json:"city"`
|
|
||||||
ISP string `json:"isp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Status != "success" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &IPLocationInfo{
|
|
||||||
Country: result.Country,
|
|
||||||
Region: result.Region,
|
|
||||||
City: result.City,
|
|
||||||
ISP: result.ISP,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
BIN
ip2region.xdb
Normal file
BIN
ip2region.xdb
Normal file
Binary file not shown.
207
nl_im_plus.sql
207
nl_im_plus.sql
@@ -1,17 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
Navicat Premium Dump SQL
|
Navicat Premium Dump SQL
|
||||||
|
|
||||||
Source Server : 新Mysql服务器
|
Source Server : 开发环境-本地
|
||||||
Source Server Type : MySQL
|
Source Server Type : MySQL
|
||||||
Source Server Version : 80407 (8.4.7)
|
Source Server Version : 80408 (8.4.8)
|
||||||
Source Host : 101.43.12.11:3306
|
Source Host : localhost:3306
|
||||||
Source Schema : nl_im_plus
|
Source Schema : nl_im_plus
|
||||||
|
|
||||||
Target Server Type : MySQL
|
Target Server Type : MySQL
|
||||||
Target Server Version : 80407 (8.4.7)
|
Target Server Version : 80408 (8.4.8)
|
||||||
File Encoding : 65001
|
File Encoding : 65001
|
||||||
|
|
||||||
Date: 08/12/2025 14:10:48
|
Date: 07/07/2026 16:44:26
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
SET NAMES utf8mb4;
|
||||||
@@ -39,11 +39,7 @@ CREATE TABLE `api_request_logs` (
|
|||||||
INDEX `idx_api_request_logs_user_id`(`user_id` ASC) USING BTREE,
|
INDEX `idx_api_request_logs_user_id`(`user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_api_request_logs_response_code`(`response_code` ASC) USING BTREE,
|
INDEX `idx_api_request_logs_response_code`(`response_code` ASC) USING BTREE,
|
||||||
INDEX `idx_api_request_logs_request_time`(`request_time` ASC) USING BTREE
|
INDEX `idx_api_request_logs_request_time`(`request_time` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '接口请求日志表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '接口请求日志表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of api_request_logs
|
|
||||||
-- ----------------------------
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for attachments
|
-- Table structure for attachments
|
||||||
@@ -65,18 +61,7 @@ CREATE TABLE `attachments` (
|
|||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
INDEX `idx_attachments_uploader_id`(`uploader_id` ASC) USING BTREE,
|
INDEX `idx_attachments_uploader_id`(`uploader_id` ASC) USING BTREE,
|
||||||
INDEX `idx_attachments_file_type`(`file_type` ASC) USING BTREE
|
INDEX `idx_attachments_file_type`(`file_type` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '附件表,记录上传的文件信息' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '附件表,记录上传的文件信息' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of attachments
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `attachments` VALUES (1, '10001', 'app-logo.png', 'image', 50300, 'uploads\\images\\1765155994172139000_139000.png', '/uploads/images/1765155994172139000_139000.png', 'image/png', 0, 0, 0, '2025-12-08 09:06:34.394');
|
|
||||||
INSERT INTO `attachments` VALUES (2, '10002', 'acf44708-e9f2-45c3-8399-16237ff619b4.jpg', 'image', 80342, 'uploads\\images\\1765159407925763500_763500.jpg', '/uploads/images/1765159407925763500_763500.jpg', 'image/jpeg', 0, 0, 0, '2025-12-08 10:03:27.937');
|
|
||||||
INSERT INTO `attachments` VALUES (3, '10002', 'acf44708-e9f2-45c3-8399-16237ff619b4.png', 'image', 1106410, 'uploads\\images\\1765159407978118500_118500.png', '/uploads/images/1765159407978118500_118500.png', 'image/png', 0, 0, 0, '2025-12-08 10:03:27.991');
|
|
||||||
INSERT INTO `attachments` VALUES (4, '10002', 'app-logo.png', 'image', 50300, 'uploads\\images\\1765159408031331600_331600.png', '/uploads/images/1765159408031331600_331600.png', 'image/png', 0, 0, 0, '2025-12-08 10:03:28.045');
|
|
||||||
INSERT INTO `attachments` VALUES (5, '10002', 'qd.png', 'image', 83764, 'uploads\\images\\1765159408083277400_277400.png', '/uploads/images/1765159408083277400_277400.png', 'image/png', 0, 0, 0, '2025-12-08 10:03:28.094');
|
|
||||||
INSERT INTO `attachments` VALUES (6, '10002', 'weibo_login.mp4', 'video', 20268266, 'uploads\\videos\\1765159423732615800_615800.mp4', '/uploads/videos/1765159423732615800_615800.mp4', 'video/mp4', 0, 0, 0, '2025-12-08 10:03:43.753');
|
|
||||||
INSERT INTO `attachments` VALUES (7, '10001', 'app-logo.png', 'image', 50300, 'uploads\\images\\1765170659549540900_540900.png', '/uploads/images/1765170659549540900_540900.png', 'image/png', 0, 0, 0, '2025-12-08 13:10:59.734');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for chat_conversations
|
-- Table structure for chat_conversations
|
||||||
@@ -101,15 +86,7 @@ CREATE TABLE `chat_conversations` (
|
|||||||
INDEX `idx_chat_conversations_target_id`(`target_id` ASC) USING BTREE,
|
INDEX `idx_chat_conversations_target_id`(`target_id` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_conversations_room_id`(`room_id` ASC) USING BTREE,
|
INDEX `idx_chat_conversations_room_id`(`room_id` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_conversations_last_time`(`last_time` ASC) USING BTREE
|
INDEX `idx_chat_conversations_last_time`(`last_time` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户会话表,记录每个用户的最近会话列表和未读信息' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户会话表,记录每个用户的最近会话列表和未读信息' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of chat_conversations
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `chat_conversations` VALUES (1, '10002', '10001', '256224828233748480', 1, 0, 0, 0, 0, '我已经通过了你的好友申请,开始和我聊天吧~', '2025-12-08 09:06:00', '2025-12-08 09:05:59.742', '2025-12-08 11:30:28.569');
|
|
||||||
INSERT INTO `chat_conversations` VALUES (2, '10001', '10002', '256224828233748480', 1, 0, 0, 0, 0, '我已经通过了你的好友申请,开始和我聊天吧~', '2025-12-08 09:06:00', '2025-12-08 09:05:59.813', '2025-12-08 09:06:00.018');
|
|
||||||
INSERT INTO `chat_conversations` VALUES (3, '10001', 'group_256244427461365760', 'group_256244427461365760', 2, 0, 0, 0, 0, '李四:222', '2025-12-08 13:54:45', '2025-12-08 10:23:52.545', '2025-12-08 13:54:47.473');
|
|
||||||
INSERT INTO `chat_conversations` VALUES (4, '10002', 'group_256244427461365760', 'group_256244427461365760', 2, 0, 0, 0, 0, '李四:222', '2025-12-08 13:54:45', '2025-12-08 10:23:52.545', '2025-12-08 13:54:44.991');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for chat_messages
|
-- Table structure for chat_messages
|
||||||
@@ -119,6 +96,8 @@ CREATE TABLE `chat_messages` (
|
|||||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '消息唯一标识ID',
|
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '消息唯一标识ID',
|
||||||
`room_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '房间ID',
|
`room_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '房间ID',
|
||||||
`sender_user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发送者用户ID',
|
`sender_user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发送者用户ID',
|
||||||
|
`sender_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发送者IP',
|
||||||
|
`ip_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'IP归属地',
|
||||||
`receiver_user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '接收者用户ID',
|
`receiver_user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '接收者用户ID',
|
||||||
`message_type` bigint NULL DEFAULT NULL COMMENT '消息类型',
|
`message_type` bigint NULL DEFAULT NULL COMMENT '消息类型',
|
||||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '消息内容',
|
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '消息内容',
|
||||||
@@ -132,17 +111,7 @@ CREATE TABLE `chat_messages` (
|
|||||||
INDEX `idx_chat_messages_sender_user_id`(`sender_user_id` ASC) USING BTREE,
|
INDEX `idx_chat_messages_sender_user_id`(`sender_user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_messages_receiver_user_id`(`receiver_user_id` ASC) USING BTREE,
|
INDEX `idx_chat_messages_receiver_user_id`(`receiver_user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_messages_call_id`(`call_id` ASC) USING BTREE
|
INDEX `idx_chat_messages_call_id`(`call_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '聊天消息表,持久化存储聊天记录,包括文本、多媒体和信令状态' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 104 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '聊天消息表,持久化存储聊天记录,包括文本、多媒体和信令状态' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of chat_messages
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `chat_messages` VALUES (1, '256224828233748480', '10001', '10002', 0, '我已经通过了你的好友申请,开始和我聊天吧~', '', 0, '', '', '2025-12-08 09:05:59.882');
|
|
||||||
INSERT INTO `chat_messages` VALUES (2, 'group_256244427461365760', '10001', 'group_256244427461365760', 4, '张三 邀请你加入了群聊', '{\"creator\":\"10001\",\"room_id\":\"group_256244427461365760\",\"type\":\"group_created\"}', 0, '', '', '2025-12-08 10:23:52.740');
|
|
||||||
INSERT INTO `chat_messages` VALUES (3, 'group_256244427461365760', '10001', 'group_256244427461365760', 0, '@李四 你好', '{\"mention_user_ids\":[\"10002\"]}', 0, '', '', '2025-12-08 11:27:32.014');
|
|
||||||
INSERT INTO `chat_messages` VALUES (4, 'group_256244427461365760', '10002', 'group_256244427461365760', 0, '.', '{}', 0, '', '', '2025-12-08 13:54:30.347');
|
|
||||||
INSERT INTO `chat_messages` VALUES (5, 'group_256244427461365760', '10002', 'group_256244427461365760', 0, '123', '{}', 0, '', '', '2025-12-08 13:54:35.594');
|
|
||||||
INSERT INTO `chat_messages` VALUES (6, 'group_256244427461365760', '10002', 'group_256244427461365760', 0, '222', '{}', 0, '', '', '2025-12-08 13:54:44.646');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for chat_rooms
|
-- Table structure for chat_rooms
|
||||||
@@ -161,18 +130,13 @@ CREATE TABLE `chat_rooms` (
|
|||||||
`announcement` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '群公告',
|
`announcement` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '群公告',
|
||||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
`updated_at` datetime(3) NULL DEFAULT NULL COMMENT '更新时间',
|
`updated_at` datetime(3) NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`members` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '成员列表JSON',
|
||||||
PRIMARY KEY (`room_id`) USING BTREE,
|
PRIMARY KEY (`room_id`) USING BTREE,
|
||||||
INDEX `idx_chat_rooms_room_type`(`room_type` ASC) USING BTREE,
|
INDEX `idx_chat_rooms_room_type`(`room_type` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_rooms_owner_id`(`owner_id` ASC) USING BTREE,
|
INDEX `idx_chat_rooms_owner_id`(`owner_id` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_rooms_creator_id`(`creator_id` ASC) USING BTREE,
|
INDEX `idx_chat_rooms_creator_id`(`creator_id` ASC) USING BTREE,
|
||||||
INDEX `idx_chat_rooms_last_message_time`(`last_message_time` ASC) USING BTREE
|
INDEX `idx_chat_rooms_last_message_time`(`last_message_time` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '聊天房间表,支持点对点和群聊' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '聊天房间表,支持点对点和群聊' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of chat_rooms
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `chat_rooms` VALUES ('256224828233748480', 'p2p', '', '', '0', '', '10001', NULL, '', '', '2025-12-08 09:05:59.479', '2025-12-08 09:05:59.479');
|
|
||||||
INSERT INTO `chat_rooms` VALUES ('group_256244427461365760', 'group', '1', '1', '10001', '', '10001', NULL, '', '你好', '2025-12-08 10:23:52.329', '2025-12-08 13:54:08.971');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for contact_groups
|
-- Table structure for contact_groups
|
||||||
@@ -186,11 +150,7 @@ CREATE TABLE `contact_groups` (
|
|||||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
`created_at` datetime(3) NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
INDEX `idx_contact_groups_user_id`(`user_id` ASC) USING BTREE
|
INDEX `idx_contact_groups_user_id`(`user_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '联系人分组表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '联系人分组表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of contact_groups
|
|
||||||
-- ----------------------------
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for friend_requests
|
-- Table structure for friend_requests
|
||||||
@@ -208,12 +168,7 @@ CREATE TABLE `friend_requests` (
|
|||||||
INDEX `idx_friend_requests_from_user_id`(`from_user_id` ASC) USING BTREE,
|
INDEX `idx_friend_requests_from_user_id`(`from_user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_friend_requests_to_user_id`(`to_user_id` ASC) USING BTREE,
|
INDEX `idx_friend_requests_to_user_id`(`to_user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_friend_requests_status`(`status` ASC) USING BTREE
|
INDEX `idx_friend_requests_status`(`status` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '好友申请表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '好友申请表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of friend_requests
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `friend_requests` VALUES (1, '10002', '10001', '你好,我是张三', 'accepted', '2025-12-08 09:05:54.580', '2025-12-08 09:05:59.446');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for login_logs
|
-- Table structure for login_logs
|
||||||
@@ -233,13 +188,7 @@ CREATE TABLE `login_logs` (
|
|||||||
INDEX `idx_login_logs_ip`(`ip` ASC) USING BTREE,
|
INDEX `idx_login_logs_ip`(`ip` ASC) USING BTREE,
|
||||||
INDEX `idx_login_logs_success`(`success` ASC) USING BTREE,
|
INDEX `idx_login_logs_success`(`success` ASC) USING BTREE,
|
||||||
INDEX `idx_login_logs_login_time`(`login_time` ASC) USING BTREE
|
INDEX `idx_login_logs_login_time`(`login_time` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '登录日志表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '登录日志表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of login_logs
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `login_logs` VALUES (1, 'user002@example.com', '10002', '本地', '::1', 1, '2025-12-08 13:10:40');
|
|
||||||
INSERT INTO `login_logs` VALUES (2, 'workerqi@163.com', '10001', '本地', '::1', 1, '2025-12-08 13:10:48');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for moment_comments
|
-- Table structure for moment_comments
|
||||||
@@ -259,15 +208,7 @@ CREATE TABLE `moment_comments` (
|
|||||||
INDEX `idx_moment_comments_user_id`(`user_id` ASC) USING BTREE,
|
INDEX `idx_moment_comments_user_id`(`user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_moment_comments_reply_to`(`reply_to_comment_id` ASC) USING BTREE,
|
INDEX `idx_moment_comments_reply_to`(`reply_to_comment_id` ASC) USING BTREE,
|
||||||
INDEX `idx_moment_comments_reply_to_comment_id`(`reply_to_comment_id` ASC) USING BTREE
|
INDEX `idx_moment_comments_reply_to_comment_id`(`reply_to_comment_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '动态评论表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '动态评论表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of moment_comments
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `moment_comments` VALUES (1, 1, '10002', NULL, '', '好', 0, '2025-12-08 09:51:33.512');
|
|
||||||
INSERT INTO `moment_comments` VALUES (2, 2, '10001', NULL, '', '你好', 0, '2025-12-08 09:52:13.529');
|
|
||||||
INSERT INTO `moment_comments` VALUES (3, 5, '10001', NULL, '', '确实不错', 0, '2025-12-08 11:31:23.850');
|
|
||||||
INSERT INTO `moment_comments` VALUES (4, 2, '10002', 2, '10001', '嗯嗯', 0, '2025-12-08 12:53:31.946');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for moment_likes
|
-- Table structure for moment_likes
|
||||||
@@ -282,18 +223,7 @@ CREATE TABLE `moment_likes` (
|
|||||||
UNIQUE INDEX `uk_moment_user`(`moment_id` ASC, `user_id` ASC) USING BTREE,
|
UNIQUE INDEX `uk_moment_user`(`moment_id` ASC, `user_id` ASC) USING BTREE,
|
||||||
INDEX `idx_moment_likes_moment_id`(`moment_id` ASC) USING BTREE,
|
INDEX `idx_moment_likes_moment_id`(`moment_id` ASC) USING BTREE,
|
||||||
INDEX `idx_moment_likes_user_id`(`user_id` ASC) USING BTREE
|
INDEX `idx_moment_likes_user_id`(`user_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '动态点赞表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '动态点赞表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of moment_likes
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `moment_likes` VALUES (1, 1, '10002', '2025-12-08 09:06:47.565');
|
|
||||||
INSERT INTO `moment_likes` VALUES (2, 2, '10001', '2025-12-08 09:52:07.061');
|
|
||||||
INSERT INTO `moment_likes` VALUES (3, 4, '10002', '2025-12-08 10:03:53.559');
|
|
||||||
INSERT INTO `moment_likes` VALUES (4, 4, '10001', '2025-12-08 10:04:06.564');
|
|
||||||
INSERT INTO `moment_likes` VALUES (5, 5, '10001', '2025-12-08 11:31:16.553');
|
|
||||||
INSERT INTO `moment_likes` VALUES (6, 2, '10002', '2025-12-08 13:10:29.289');
|
|
||||||
INSERT INTO `moment_likes` VALUES (7, 7, '10002', '2025-12-08 13:45:53.852');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for moment_notifications
|
-- Table structure for moment_notifications
|
||||||
@@ -313,23 +243,7 @@ CREATE TABLE `moment_notifications` (
|
|||||||
INDEX `idx_moment_notifications_is_read`(`is_read` ASC) USING BTREE,
|
INDEX `idx_moment_notifications_is_read`(`is_read` ASC) USING BTREE,
|
||||||
INDEX `idx_moment_notifications_created_at`(`created_at` ASC) USING BTREE,
|
INDEX `idx_moment_notifications_created_at`(`created_at` ASC) USING BTREE,
|
||||||
INDEX `idx_moment_notifications_moment_id`(`moment_id` ASC) USING BTREE
|
INDEX `idx_moment_notifications_moment_id`(`moment_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '朋友圈通知表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '朋友圈通知表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of moment_notifications
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `moment_notifications` VALUES (1, '10001', '10002', 1, 1, NULL, 0, '2025-12-08 09:06:47.673');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (2, '10001', '10002', 1, 2, 1, 0, '2025-12-08 09:51:33.620');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (3, '10002', '10001', 2, 1, NULL, 1, '2025-12-08 09:52:07.164');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (4, '10002', '10001', 2, 2, 2, 1, '2025-12-08 09:52:13.624');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (5, '10002', '10001', 4, 1, NULL, 1, '2025-12-08 10:04:06.659');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (6, '10001', '10002', 5, 4, NULL, 0, '2025-12-08 11:31:11.609');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (7, '10002', '10001', 5, 1, NULL, 1, '2025-12-08 11:31:16.642');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (8, '10002', '10001', 5, 2, 3, 1, '2025-12-08 11:31:23.936');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (9, '10001', '10002', 2, 3, 4, 0, '2025-12-08 12:53:32.069');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (10, '10002', '10001', 6, 4, NULL, 0, '2025-12-08 13:45:09.606');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (11, '10002', '10001', 7, 4, NULL, 0, '2025-12-08 13:45:33.703');
|
|
||||||
INSERT INTO `moment_notifications` VALUES (12, '10001', '10002', 7, 1, NULL, 0, '2025-12-08 13:45:53.961');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for moments
|
-- Table structure for moments
|
||||||
@@ -356,18 +270,7 @@ CREATE TABLE `moments` (
|
|||||||
INDEX `idx_moments_created_at`(`created_at` ASC) USING BTREE,
|
INDEX `idx_moments_created_at`(`created_at` ASC) USING BTREE,
|
||||||
INDEX `idx_moments_visibility`(`visibility` ASC) USING BTREE,
|
INDEX `idx_moments_visibility`(`visibility` ASC) USING BTREE,
|
||||||
INDEX `idx_moments_is_deleted`(`is_deleted` ASC) USING BTREE
|
INDEX `idx_moments_is_deleted`(`is_deleted` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '朋友圈动态表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '朋友圈动态表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of moments
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `moments` VALUES (1, '10001', '大家好,这个是我第一条动态~', 1, '[\"/uploads/images/1765155994172139000_139000.png\"]', '', 0, 'null', 'null', 'null', 1, 1, 0, '2025-12-08 09:06:34.699', '2025-12-08 09:06:34.699');
|
|
||||||
INSERT INTO `moments` VALUES (2, '10002', '这个是我第一个朋友圈~', 0, '[]', '', 0, 'null', 'null', 'null', 2, 2, 0, '2025-12-08 09:52:02.097', '2025-12-08 09:52:02.097');
|
|
||||||
INSERT INTO `moments` VALUES (3, '10002', '很开心', 1, '[\"/uploads/images/1765159407925763500_763500.jpg\",\"/uploads/images/1765159407978118500_118500.png\",\"/uploads/images/1765159408031331600_331600.png\",\"/uploads/images/1765159408083277400_277400.png\"]', '', 0, 'null', 'null', 'null', 0, 0, 0, '2025-12-08 10:03:28.142', '2025-12-08 10:03:28.142');
|
|
||||||
INSERT INTO `moments` VALUES (4, '10002', '哦豁', 2, '[\"/uploads/videos/1765159423732615800_615800.mp4\"]', '', 0, 'null', 'null', 'null', 2, 0, 0, '2025-12-08 10:03:43.810', '2025-12-08 10:03:43.810');
|
|
||||||
INSERT INTO `moments` VALUES (5, '10002', '今天天气真好 @张三 ', 0, '[]', '', 0, 'null', '[\"10001\"]', 'null', 1, 1, 0, '2025-12-08 11:31:11.549', '2025-12-08 11:31:11.549');
|
|
||||||
INSERT INTO `moments` VALUES (6, '10001', '... @李四 ', 0, '[]', '', 0, 'null', '[\"10002\"]', 'null', 0, 0, 0, '2025-12-08 13:45:09.525', '2025-12-08 13:45:09.525');
|
|
||||||
INSERT INTO `moments` VALUES (7, '10001', '@李四 ', 0, '[]', '', 0, 'null', '[\"10002\"]', 'null', 1, 0, 0, '2025-12-08 13:45:33.502', '2025-12-08 13:45:33.502');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for room_members
|
-- Table structure for room_members
|
||||||
@@ -381,15 +284,7 @@ CREATE TABLE `room_members` (
|
|||||||
`muted_until` datetime NULL DEFAULT NULL COMMENT '禁言到期时间',
|
`muted_until` datetime NULL DEFAULT NULL COMMENT '禁言到期时间',
|
||||||
PRIMARY KEY (`room_id`, `user_id`) USING BTREE,
|
PRIMARY KEY (`room_id`, `user_id`) USING BTREE,
|
||||||
INDEX `idx_room_members_user_id`(`user_id` ASC) USING BTREE
|
INDEX `idx_room_members_user_id`(`user_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '房间成员表,存储房间与用户的关联关系' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '房间成员表,存储房间与用户的关联关系' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of room_members
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `room_members` VALUES ('256224828233748480', '10001', 0, '2025-12-08 09:05:59.562', NULL);
|
|
||||||
INSERT INTO `room_members` VALUES ('256224828233748480', '10002', 0, '2025-12-08 09:05:59.523', NULL);
|
|
||||||
INSERT INTO `room_members` VALUES ('group_256244427461365760', '10001', 2, '2025-12-08 10:23:52.375', NULL);
|
|
||||||
INSERT INTO `room_members` VALUES ('group_256244427461365760', '10002', 1, '2025-12-08 10:23:52.351', NULL);
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for user_contacts
|
-- Table structure for user_contacts
|
||||||
@@ -416,13 +311,7 @@ CREATE TABLE `user_contacts` (
|
|||||||
INDEX `idx_user_contacts_contact_id`(`contact_id` ASC) USING BTREE,
|
INDEX `idx_user_contacts_contact_id`(`contact_id` ASC) USING BTREE,
|
||||||
INDEX `idx_user_contacts_room_id`(`room_id` ASC) USING BTREE,
|
INDEX `idx_user_contacts_room_id`(`room_id` ASC) USING BTREE,
|
||||||
INDEX `idx_user_contacts_group_id`(`group_id` ASC) USING BTREE
|
INDEX `idx_user_contacts_group_id`(`group_id` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户联系人表,存储好友关系、分组、备注等信息' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户联系人表,存储好友关系、分组、备注等信息' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of user_contacts
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `user_contacts` VALUES (1, '10002', '10001', '256224828233748480', '', 0, 0, 0, 0, 0, NULL, '', 0, '2025-12-08 09:05:59.632', '2025-12-08 09:05:59.632');
|
|
||||||
INSERT INTO `user_contacts` VALUES (2, '10001', '10002', '256224828233748480', '', 0, 0, 0, 0, 0, NULL, '', 0, '2025-12-08 09:05:59.662', '2025-12-08 09:05:59.662');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for users
|
-- Table structure for users
|
||||||
@@ -442,31 +331,7 @@ CREATE TABLE `users` (
|
|||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `idx_users_email`(`email` ASC) USING BTREE,
|
UNIQUE INDEX `idx_users_email`(`email` ASC) USING BTREE,
|
||||||
UNIQUE INDEX `idx_users_phone`(`phone` ASC) USING BTREE
|
UNIQUE INDEX `idx_users_phone`(`phone` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户基本信息表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户基本信息表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Records of users
|
|
||||||
-- ----------------------------
|
|
||||||
INSERT INTO `users` VALUES ('10001', 'workerqi@163.com', '13800138001', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '张三', '/uploads/images/1765170659549540900_540900.png', 'Golang 开发工程师', '北京', '2025-12-03 09:53:49.000', '2025-12-08 13:10:59.809');
|
|
||||||
INSERT INTO `users` VALUES ('10002', 'user002@example.com', '13800138002', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '李四', '李', '前端开发工程师', '上海', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10003', 'user003@example.com', '13800138003', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '王五', '王', '产品经理', '深圳', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10004', 'user004@example.com', '13800138004', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '赵六', '赵', 'UI设计师', '广州', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10005', 'user005@example.com', '13800138005', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '钱七', '钱', '测试工程师', '杭州', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10006', 'user006@example.com', '13800138006', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '孙八', '孙', '运维工程师', '成都', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10007', 'user007@example.com', '13800138007', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '周九', '周', '数据分析师', '武汉', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10008', 'user008@example.com', '13800138008', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '吴十', '吴', '项目经理', '西安', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10009', 'user009@example.com', '13800138009', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '郑十一', '郑', '架构师', '南京', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10010', 'user010@example.com', '13800138010', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '王十二', '王', '技术总监', '苏州', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10011', 'user011@example.com', '13800138011', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '冯十三', '冯', 'HR经理', '天津', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10012', 'user012@example.com', '13800138012', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '陈十四', '陈', '财务经理', '重庆', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10013', 'user013@example.com', '13800138013', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '褚十五', '褚', '市场经理', '长沙', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10014', 'user014@example.com', '13800138014', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '卫十六', '卫', '销售经理', '郑州', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10015', 'user015@example.com', '13800138015', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '蒋十七', '蒋', '客服主管', '济南', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10016', 'user016@example.com', '13800138016', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '沈十八', '沈', '运营专员', '青岛', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10017', 'user017@example.com', '13800138017', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '韩十九', '韩', '内容编辑', '大连', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10018', 'user018@example.com', '13800138018', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '杨二十', '杨', '商务拓展', '厦门', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10019', 'user019@example.com', '13800138019', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '朱二一', '朱', '法务专员', '福州', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
INSERT INTO `users` VALUES ('10020', 'user020@example.com', '13800138020', '$2a$10$OsVRHKowu.mVT4GNxzZ3/eo8xEABkWsUGAt4nV5nalzOXW47vtBmC', '秦二二', '秦', '行政助理', '合肥', '2025-12-03 09:53:49.000', '2025-12-03 09:53:49.000');
|
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for verification_codes
|
-- Table structure for verification_codes
|
||||||
@@ -482,10 +347,32 @@ CREATE TABLE `verification_codes` (
|
|||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
INDEX `idx_verification_codes_target`(`target` ASC) USING BTREE,
|
INDEX `idx_verification_codes_target`(`target` ASC) USING BTREE,
|
||||||
INDEX `idx_verification_codes_expires_at`(`expires_at` ASC) USING BTREE
|
INDEX `idx_verification_codes_expires_at`(`expires_at` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '验证码表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '验证码表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Records of verification_codes
|
-- Table structure for message_read_receipts
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `message_read_receipts`;
|
||||||
|
CREATE TABLE `message_read_receipts` (
|
||||||
|
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`message_id` bigint UNSIGNED NOT NULL COMMENT '消息ID',
|
||||||
|
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '已读用户ID',
|
||||||
|
`read_at` datetime(3) NOT NULL COMMENT '已读时间',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uk_msg_user`(`message_id` ASC, `user_id` ASC) USING BTREE,
|
||||||
|
INDEX `idx_message_read_receipts_user_id`(`user_id` ASC) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '消息已读回执表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for user_settings
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `user_settings`;
|
||||||
|
CREATE TABLE `user_settings` (
|
||||||
|
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户ID',
|
||||||
|
`setting_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '设置键',
|
||||||
|
`setting_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '设置值',
|
||||||
|
`updated_at` datetime(3) NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`user_id`, `setting_key`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户个性化设置表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|||||||
434
nl_im_plus2.sql
434
nl_im_plus2.sql
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user