276 lines
8.7 KiB
Go
276 lines
8.7 KiB
Go
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)
|
||
}
|