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

@@ -25,19 +25,26 @@ type UserStatus struct {
IP string `json:"ip"` // 连接 IP
}
// FriendIDsGetter 获取好友 ID 列表的接口(避免直接依赖 contact 模块)
type FriendIDsGetter interface {
GetFriendIDs(ctx context.Context, userID int64) ([]int64, error)
}
// OnlineService 在线状态管理服务
type OnlineService struct {
rdb *redis.Client
hub *ws.Hub
pubsub *ws.PubSub
rdb *redis.Client
hub *ws.Hub
pubsub *ws.PubSub
friendGetter FriendIDsGetter
}
// NewOnlineService 创建 OnlineService 实例
func NewOnlineService(rdb *redis.Client, hub *ws.Hub, pubsub *ws.PubSub) *OnlineService {
func NewOnlineService(rdb *redis.Client, hub *ws.Hub, pubsub *ws.PubSub, friendGetter FriendIDsGetter) *OnlineService {
return &OnlineService{
rdb: rdb,
hub: hub,
pubsub: pubsub,
rdb: rdb,
hub: hub,
pubsub: pubsub,
friendGetter: friendGetter,
}
}
@@ -53,7 +60,11 @@ func (s *OnlineService) UserOnline(ctx context.Context, userID int64, ip string)
ConnectAt: time.Now().Format("2006-01-02 15:04:05"),
IP: ip,
}
statusJSON, _ := json.Marshal(status)
statusJSON, err := json.Marshal(status)
if err != nil {
logs.Error(ctx, funcName, "序列化用户状态失败", zap.Int64("user_id", userID), zap.Error(err))
return
}
pipe.Set(ctx, statusKey(userID), statusJSON, statusTTL)
if _, err := pipe.Exec(ctx); err != nil {
@@ -63,6 +74,15 @@ func (s *OnlineService) UserOnline(ctx context.Context, userID int64, ip string)
}
logs.Info(ctx, funcName, "用户上线", zap.Int64("user_id", userID))
if s.friendGetter != nil {
friendIDs, err := s.friendGetter.GetFriendIDs(ctx, userID)
if err != nil {
logs.Warn(ctx, funcName, "获取好友列表失败,跳过上线通知", zap.Error(err))
} else if len(friendIDs) > 0 {
s.NotifyFriendsStatusChange(ctx, userID, true, friendIDs)
}
}
}
// UserOffline 用户下线:清除 Redis + 通知在线好友
@@ -79,11 +99,23 @@ func (s *OnlineService) UserOffline(ctx context.Context, userID int64) {
}
logs.Info(ctx, funcName, "用户下线", zap.Int64("user_id", userID))
if s.friendGetter != nil {
friendIDs, err := s.friendGetter.GetFriendIDs(ctx, userID)
if err != nil {
logs.Warn(ctx, funcName, "获取好友列表失败,跳过下线通知", zap.Error(err))
} else if len(friendIDs) > 0 {
s.NotifyFriendsStatusChange(ctx, userID, false, friendIDs)
}
}
}
// HeartbeatRenew 心跳续期:延长状态 TTL
func (s *OnlineService) HeartbeatRenew(ctx context.Context, userID int64) {
s.rdb.Expire(ctx, statusKey(userID), statusTTL)
if err := s.rdb.Expire(ctx, statusKey(userID), statusTTL).Err(); err != nil {
logs.Warn(ctx, "ws.online_service.HeartbeatRenew", "心跳续期失败",
zap.Int64("user_id", userID), zap.Error(err))
}
}
// IsOnline 检查用户是否在线Redis 查询)