fix(phase2a): 代码审查修复 - 8项关键/重要问题

安全修复:
- WebSocket Token 增加 Redis 有效性校验(已登出用户无法建立 WS)

功能修复:
- GetRecommendFriends 改为批量查询,正确返回用户名/昵称/头像
- 上下线通知:OnlineService 通过接口注入获取好友列表推送状态变更
- 管理端在线用户 API 补充用户名信息

代码质量:
- 所有 json.Marshal/Redis 错误增加检查与日志
- ContactController 13 个 endpoint 统一走 handleError 业务错误映射
- 管理端 Controller 补全包注释、函数注释和结构化日志
- 前端 5 个联系人页面 avatar 工具函数抽取到 utils/avatar.js

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-03-02 18:48:28 +08:00
parent 8ec0261427
commit 2c59500e27
26 changed files with 632 additions and 153 deletions

View File

@@ -41,7 +41,7 @@ func (ctl *ContactController) GetFriendList(c *gin.Context) {
friends, err := ctl.contactService.GetFriendList(ctx, userID, groupID)
if err != nil {
utils.ResponseError(c, "获取好友列表失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, friends)
@@ -131,7 +131,7 @@ func (ctl *ContactController) GetPendingRequests(c *gin.Context) {
requests, err := ctl.contactService.GetPendingRequests(ctx, userID)
if err != nil {
utils.ResponseError(c, "获取好友申请列表失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, requests)
@@ -154,7 +154,7 @@ func (ctl *ContactController) DeleteFriend(c *gin.Context) {
}
if err := ctl.contactService.DeleteFriend(ctx, userID, friendID); err != nil {
utils.ResponseError(c, "删除好友失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, nil)
@@ -183,7 +183,7 @@ func (ctl *ContactController) UpdateRemark(c *gin.Context) {
}
if err := ctl.contactService.UpdateRemark(ctx, userID, friendID, req.Remark); err != nil {
utils.ResponseError(c, "更新备注失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, nil)
@@ -230,7 +230,7 @@ func (ctl *ContactController) UnblockUser(c *gin.Context) {
}
if err := ctl.contactService.UnblockUser(ctx, userID, targetID); err != nil {
utils.ResponseError(c, "取消拉黑失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, nil)
@@ -248,7 +248,7 @@ func (ctl *ContactController) GetBlockList(c *gin.Context) {
blocked, err := ctl.contactService.GetBlockList(ctx, userID)
if err != nil {
utils.ResponseError(c, "获取黑名单失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, blocked)
@@ -275,7 +275,7 @@ func (ctl *ContactController) SearchUsers(c *gin.Context) {
users, total, err := ctl.contactService.SearchUsers(ctx, userID, keyword, page, pageSize)
if err != nil {
utils.ResponseError(c, "搜索用户失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, gin.H{
@@ -296,7 +296,7 @@ func (ctl *ContactController) GetRecommendFriends(c *gin.Context) {
friends, err := ctl.contactService.GetRecommendFriends(ctx, userID)
if err != nil {
utils.ResponseError(c, "获取好友推荐失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, friends)
@@ -314,7 +314,7 @@ func (ctl *ContactController) GetGroups(c *gin.Context) {
groups, err := ctl.contactService.GetGroups(ctx, userID)
if err != nil {
utils.ResponseError(c, "获取分组列表失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, groups)
@@ -338,7 +338,7 @@ func (ctl *ContactController) CreateGroup(c *gin.Context) {
group, err := ctl.contactService.CreateGroup(ctx, userID, req.Name)
if err != nil {
utils.ResponseError(c, "创建分组失败")
ctl.handleError(c, err)
return
}
utils.ResponseCreated(c, group)
@@ -367,7 +367,7 @@ func (ctl *ContactController) UpdateGroup(c *gin.Context) {
}
if err := ctl.contactService.UpdateGroup(ctx, userID, groupID, req.Name, req.SortOrder); err != nil {
utils.ResponseError(c, "修改分组失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, nil)
@@ -390,7 +390,7 @@ func (ctl *ContactController) DeleteGroup(c *gin.Context) {
}
if err := ctl.contactService.DeleteGroup(ctx, userID, groupID); err != nil {
utils.ResponseError(c, "删除分组失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, nil)
@@ -419,7 +419,7 @@ func (ctl *ContactController) MoveToGroup(c *gin.Context) {
}
if err := ctl.contactService.MoveToGroup(ctx, userID, friendID, req.GroupID); err != nil {
utils.ResponseError(c, "移动好友到分组失败")
ctl.handleError(c, err)
return
}
utils.ResponseOK(c, nil)

View File

@@ -81,9 +81,11 @@ func (d *FriendGroupDAO) DeleteGroup(ctx context.Context, groupID int64, userID
zap.Int64("group_id", groupID), zap.Int64("user_id", userID))
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
tx.Model(&model.Friendship{}).
if err := tx.Model(&model.Friendship{}).
Where("user_id = ? AND group_id = ?", userID, groupID).
Update("group_id", nil)
Update("group_id", nil).Error; err != nil {
return err
}
result := tx.Where("id = ? AND user_id = ?", groupID, userID).
Delete(&model.FriendGroup{})

View File

@@ -196,9 +196,11 @@ func (d *FriendshipDAO) BlockUser(ctx context.Context, userID, targetID int64) e
zap.Int64("user_id", userID), zap.Int64("target_id", targetID))
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
tx.Where("(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
if err := tx.Where("(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
userID, targetID, targetID, userID).
Delete(&model.Friendship{})
Delete(&model.Friendship{}).Error; err != nil {
return err
}
block := &model.Friendship{
UserID: userID,
@@ -324,6 +326,23 @@ func (d *FriendshipDAO) GetFriendIDs(ctx context.Context, userID int64) ([]int64
return ids, err
}
// GetUsersByIDs 批量查询用户信息(仅返回 id/username/nickname/avatar
func (d *FriendshipDAO) GetUsersByIDs(ctx context.Context, userIDs []int64) ([]authModel.User, error) {
funcName := "dao.friendship_dao.GetUsersByIDs"
logs.Debug(ctx, funcName, "批量查询用户信息", zap.Int("count", len(userIDs)))
var users []authModel.User
err := d.db.WithContext(ctx).
Model(&authModel.User{}).
Select("id, username, nickname, avatar").
Where("id IN ?", userIDs).
Find(&users).Error
if err != nil {
logs.Error(ctx, funcName, "批量查询用户信息失败", zap.Error(err))
}
return users, err
}
// CountFriendsByGroup 统计每个分组的好友数
func (d *FriendshipDAO) CountFriendsByGroup(ctx context.Context, userID int64) (map[int64]int, error) {
type GroupCount struct {

View File

@@ -4,6 +4,7 @@ package service
import (
"context"
"errors"
"sort"
"github.com/echochat/backend/app/contact/dao"
"github.com/echochat/backend/app/dto"
@@ -287,7 +288,7 @@ func (s *ContactService) SearchUsers(ctx context.Context, userID int64, keyword
return result, total, nil
}
// GetRecommendFriends 好友推荐(基于共同好友)
// GetRecommendFriends 好友推荐(基于共同好友数量排序
func (s *ContactService) GetRecommendFriends(ctx context.Context, userID int64) ([]dto.SearchUserInfo, error) {
funcName := "service.contact_service.GetRecommendFriends"
logs.Debug(ctx, funcName, "好友推荐", zap.Int64("user_id", userID))
@@ -322,31 +323,47 @@ func (s *ContactService) GetRecommendFriends(ctx context.Context, userID int64)
id int64
count int
}
var candidates []candidate
candidates := make([]candidate, 0, len(candidateCount))
for id, count := range candidateCount {
candidates = append(candidates, candidate{id: id, count: count})
}
sort.Slice(candidates, func(i, j int) bool {
return candidates[i].count > candidates[j].count
})
limit := 10
if len(candidates) > limit {
for i := 0; i < limit; i++ {
for j := i + 1; j < len(candidates); j++ {
if candidates[j].count > candidates[i].count {
candidates[i], candidates[j] = candidates[j], candidates[i]
}
}
}
candidates = candidates[:limit]
}
candidateIDs := make([]int64, len(candidates))
for i, c := range candidates {
candidateIDs[i] = c.id
}
users, err := s.friendshipDAO.GetUsersByIDs(ctx, candidateIDs)
if err != nil {
logs.Error(ctx, funcName, "批量查询推荐用户信息失败", zap.Error(err))
return nil, err
}
userMap := make(map[int64]dto.SearchUserInfo, len(users))
for _, u := range users {
userMap[u.ID] = dto.SearchUserInfo{
ID: u.ID,
Username: u.Username,
Nickname: u.Nickname,
Avatar: u.Avatar,
IsFriend: false,
}
}
result := make([]dto.SearchUserInfo, 0, len(candidates))
for _, c := range candidates {
users, _, err := s.friendshipDAO.SearchUsers(ctx, "", 0, 1, 1)
_ = users
_ = err
result = append(result, dto.SearchUserInfo{
ID: c.id,
})
if info, ok := userMap[c.id]; ok {
result = append(result, info)
}
}
return result, nil