package handler import ( "strconv" "strings" "github.com/gin-gonic/gin" "nl-game-api-gin/internal/database" "nl-game-api-gin/internal/middleware" "nl-game-api-gin/internal/model" "nl-game-api-gin/internal/room" "nl-game-api-gin/pkg/resp" ) // userBrief 用户摘要(好友列表/会话列表/搜索结果通用) type userBrief struct { UserID int `json:"user_id"` // 用户ID Username string `json:"username"` // 用户名 Nickname string `json:"nickname"` // 昵称 Avatar string `json:"avatar"` // emoji 头像 Online bool `json:"online"` // 是否在线(有活跃 WebSocket 连接) } // briefOf 把用户模型转为摘要 func briefOf(u *model.User, online bool) userBrief { return userBrief{UserID: u.ID, Username: u.Username, Nickname: u.Nickname, Avatar: u.Avatar, Online: online} } // loadBriefs 批量加载用户摘要(自动附带在线状态) func loadBriefs(ids []int) map[int]userBrief { out := map[int]userBrief{} if len(ids) == 0 { return out } var users []model.User database.DB.Where("id IN ?", ids).Find(&users) online := room.OnlineMap(ids) for i := range users { u := &users[i] out[u.ID] = briefOf(u, online[u.ID]) } return out } // isFriend 两人是否已互为好友(任一方向的已通过记录) func isFriend(a, b int) bool { var cnt int64 database.DB.Model(&model.Friend{}). Where("status = ? AND ((user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?))", model.FriendStatusAccepted, a, b, b, a). Count(&cnt) return cnt > 0 } // FriendList 我的好友列表(在线的排在前面) func FriendList(c *gin.Context) { uid := middleware.UID(c) var rels []model.Friend database.DB.Where("status = ? AND (user_id = ? OR friend_id = ?)", model.FriendStatusAccepted, uid, uid). Order("updated_at DESC").Find(&rels) ids := make([]int, 0, len(rels)) sinceMap := map[int]int64{} for _, r := range rels { peer := r.UserID if peer == uid { peer = r.FriendID } ids = append(ids, peer) sinceMap[peer] = r.UpdatedAt } briefs := loadBriefs(ids) type friendItem struct { userBrief Since int64 `json:"since"` // 成为好友时间 } list := make([]friendItem, 0, len(ids)) for _, id := range ids { if b, ok := briefs[id]; ok { list = append(list, friendItem{userBrief: b, Since: sinceMap[id]}) } } // 在线的排前面(保持各自原有相对顺序) sorted := make([]friendItem, 0, len(list)) for _, it := range list { if it.Online { sorted = append(sorted, it) } } for _, it := range list { if !it.Online { sorted = append(sorted, it) } } resp.OK(c, sorted) } // FriendRequests 好友申请:received=发给我的待处理申请,sent=我发出的待验证申请 func FriendRequests(c *gin.Context) { uid := middleware.UID(c) var in, out []model.Friend database.DB.Where("friend_id = ? AND status = ?", uid, model.FriendStatusPending).Order("id DESC").Find(&in) database.DB.Where("user_id = ? AND status = ?", uid, model.FriendStatusPending).Order("id DESC").Find(&out) ids := make([]int, 0, len(in)+len(out)) for _, r := range in { ids = append(ids, r.UserID) } for _, r := range out { ids = append(ids, r.FriendID) } briefs := loadBriefs(ids) type reqItem struct { ID int `json:"id"` // 关系ID(同意/拒绝时用) User userBrief `json:"user"` // 对方信息 At int64 `json:"at"` // 申请时间 } received := make([]reqItem, 0, len(in)) for _, r := range in { received = append(received, reqItem{ID: r.ID, User: briefs[r.UserID], At: r.CreatedAt}) } sent := make([]reqItem, 0, len(out)) for _, r := range out { sent = append(sent, reqItem{ID: r.ID, User: briefs[r.FriendID], At: r.CreatedAt}) } resp.OK(c, gin.H{"received": received, "sent": sent}) } // FriendSearch 按用户名/昵称搜索用户(加好友入口),附带与我的关系状态 func FriendSearch(c *gin.Context) { uid := middleware.UID(c) kw := strings.TrimSpace(c.Query("keyword")) if kw == "" { resp.OK(c, []any{}) return } like := "%" + kw + "%" var users []model.User database.DB.Where("id != ? AND status = ? AND (username LIKE ? OR nickname LIKE ?)", uid, model.UserStatusNormal, like, like).Limit(10).Find(&users) ids := make([]int, 0, len(users)) for _, u := range users { ids = append(ids, u.ID) } online := room.OnlineMap(ids) // 一次取出我与这批用户的全部关系,标注状态 var rels []model.Friend if len(ids) > 0 { database.DB.Where("(user_id = ? AND friend_id IN ?) OR (friend_id = ? AND user_id IN ?)", uid, ids, uid, ids).Find(&rels) } // relation: none=无关系 friend=已是好友 pending_out=我已申请 pending_in=对方已申请我 relMap := map[int]string{} for _, r := range rels { peer := r.UserID dir := "pending_in" if r.UserID == uid { peer = r.FriendID dir = "pending_out" } if r.Status == model.FriendStatusAccepted { relMap[peer] = "friend" } else if relMap[peer] != "friend" { relMap[peer] = dir } } type searchItem struct { userBrief Relation string `json:"relation"` // none/friend/pending_out/pending_in } list := make([]searchItem, 0, len(users)) for i := range users { u := &users[i] rel := relMap[u.ID] if rel == "" { rel = "none" } list = append(list, searchItem{userBrief: briefOf(u, online[u.ID]), Relation: rel}) } resp.OK(c, list) } // friendReqBody 发起好友申请请求体 type friendReqBody struct { UserID int `json:"user_id" binding:"required"` // 要添加的用户ID } // FriendRequest 发起好友申请:对方在线时实时推送;若对方已申请过我则直接成为好友 func FriendRequest(c *gin.Context) { uid := middleware.UID(c) var req friendReqBody if err := c.ShouldBindJSON(&req); err != nil || req.UserID == uid { resp.Fail(c, "参数有误") return } var target model.User if err := database.DB.First(&target, req.UserID).Error; err != nil || target.Status != model.UserStatusNormal { resp.Fail(c, "用户不存在") return } var me model.User database.DB.First(&me, uid) // 已有关系判断(两个方向) var exist model.Friend if err := database.DB.Where("user_id = ? AND friend_id = ?", uid, req.UserID).First(&exist).Error; err == nil { if exist.Status == model.FriendStatusAccepted { resp.Fail(c, "你们已经是好友了") } else { resp.Fail(c, "已发送过申请,等待对方同意") } return } var reverse model.Friend if err := database.DB.Where("user_id = ? AND friend_id = ?", req.UserID, uid).First(&reverse).Error; err == nil { if reverse.Status == model.FriendStatusAccepted { resp.Fail(c, "你们已经是好友了") return } // 对方已申请过我:视为互相想加,直接通过 database.DB.Model(&reverse).Update("status", model.FriendStatusAccepted) room.PushToUser(req.UserID, "friend_event", gin.H{"kind": "accept", "user": briefOf(&me, true)}) resp.OK(c, gin.H{"relation": "friend", "msg": "对方也申请过你,已直接成为好友"}) return } rel := model.Friend{UserID: uid, FriendID: req.UserID, Status: model.FriendStatusPending} if err := database.DB.Create(&rel).Error; err != nil { resp.Fail(c, "申请失败,请稍后重试") return } // 对方在线则实时提醒 room.PushToUser(req.UserID, "friend_event", gin.H{"kind": "request", "user": briefOf(&me, true)}) resp.OK(c, gin.H{"relation": "pending_out"}) } // friendRespondBody 处理好友申请请求体 type friendRespondBody struct { ID int `json:"id" binding:"required"` // 关系ID(来自申请列表) Accept bool `json:"accept"` // true=同意 false=拒绝 } // FriendRespond 同意/拒绝好友申请(同意后通知申请人) func FriendRespond(c *gin.Context) { uid := middleware.UID(c) var req friendRespondBody if err := c.ShouldBindJSON(&req); err != nil { resp.Fail(c, "参数有误") return } var rel model.Friend if err := database.DB.First(&rel, req.ID).Error; err != nil || rel.FriendID != uid || rel.Status != model.FriendStatusPending { resp.Fail(c, "申请不存在或已处理") return } if !req.Accept { database.DB.Delete(&rel) resp.OK(c, gin.H{"relation": "none"}) return } database.DB.Model(&rel).Update("status", model.FriendStatusAccepted) var me model.User database.DB.First(&me, uid) room.PushToUser(rel.UserID, "friend_event", gin.H{"kind": "accept", "user": briefOf(&me, true)}) resp.OK(c, gin.H{"relation": "friend"}) } // FriendDelete 删除好友(清除双方全部关系记录,聊天记录保留) func FriendDelete(c *gin.Context) { uid := middleware.UID(c) peer, _ := strconv.Atoi(c.Param("uid")) if peer <= 0 { resp.Fail(c, "参数有误") return } database.DB.Where("(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)", uid, peer, peer, uid). Delete(&model.Friend{}) resp.OK(c, nil) }