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

@@ -15,29 +15,37 @@ import (
"go.uber.org/zap"
)
// TokenValidator 有状态 JWT 验证接口(检查 Token 是否在 Redis 中有效)
// 由 auth.AuthService 实现,用于防止已登出用户建立 WebSocket 连接
type TokenValidator interface {
ValidateAccessToken(ctx context.Context, userID int64, clientType, token string) bool
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 开发阶段允许所有来源,生产环境需限制
return true // TODO: 生产环境通过配置限制 allowed origins
},
}
// Handler WebSocket 连接处理器
type Handler struct {
hub *ws.Hub
pubsub *ws.PubSub
jwtCfg *config.JWTConfig
onlineService *OnlineService
hub *ws.Hub
pubsub *ws.PubSub
jwtCfg *config.JWTConfig
onlineService *OnlineService
tokenValidator TokenValidator
}
// NewHandler 创建 WebSocket Handler 实例
func NewHandler(hub *ws.Hub, pubsub *ws.PubSub, jwtCfg *config.JWTConfig, onlineService *OnlineService) *Handler {
func NewHandler(hub *ws.Hub, pubsub *ws.PubSub, jwtCfg *config.JWTConfig, onlineService *OnlineService, tokenValidator TokenValidator) *Handler {
return &Handler{
hub: hub,
pubsub: pubsub,
jwtCfg: jwtCfg,
onlineService: onlineService,
hub: hub,
pubsub: pubsub,
jwtCfg: jwtCfg,
onlineService: onlineService,
tokenValidator: tokenValidator,
}
}
@@ -59,6 +67,17 @@ func (h *Handler) Upgrade(c *gin.Context) {
return
}
clientType := claims.ClientType
if clientType == "" {
clientType = "frontend"
}
if h.tokenValidator != nil && !h.tokenValidator.ValidateAccessToken(c.Request.Context(), claims.UserID, clientType, token) {
logs.Warn(nil, funcName, "WebSocket Token 已失效Redis 校验)",
zap.Int64("user_id", claims.UserID))
utils.ResponseUnauthorized(c, "认证已失效,请重新登录")
return
}
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
logs.Error(nil, funcName, "WebSocket 升级失败",
@@ -68,6 +87,11 @@ func (h *Handler) Upgrade(c *gin.Context) {
client := ws.NewClient(h.hub, conn, claims.UserID)
client.SetOnDisconnect(func(userID int64) {
if client.IsClosedByHub() {
logs.Info(nil, "ws.handler.onDisconnect", "连接被 Hub 踢出(重复连接),跳过下线清理",
zap.Int64("user_id", userID))
return
}
h.pubsub.Unsubscribe(userID)
h.onlineService.UserOffline(context.Background(), userID)
})
@@ -97,7 +121,11 @@ func (h *Handler) createReadHandler(userID int64) ws.MessageHandler {
case "heartbeat":
h.onlineService.HeartbeatRenew(context.Background(), userID)
resp := ws.NewResponse(msg.Event, msg.Seq, 0, "pong", nil)
data, _ := ws.MarshalResponse(resp)
data, err := ws.MarshalResponse(resp)
if err != nil {
logs.Error(nil, funcName, "序列化心跳响应失败", zap.Error(err))
return
}
client.Send(data)
default:
resp := ws.NewResponse(msg.Event, msg.Seq, 0, "ok", nil)