From ef9a30ee54be349f6fe854ce5a662cefd6e87b9c Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Wed, 8 Apr 2026 02:28:10 +0800 Subject: [PATCH] feat(verification): enforce user verification requirement for posts, comments, and WebSocket connections Add verification status checks to protected routes including post creation, updates, deletion, likes, favorites, voting, and WebSocket connections. Also rename RequireVerification middleware to RequireVerified and update error response format with string error codes. --- cmd/server/wire.go | 3 ++ cmd/server/wire_gen.go | 6 ++- internal/handler/ws_handler.go | 16 ++++++++ internal/middleware/verification.go | 10 +++-- internal/pkg/response/response.go | 8 ++-- internal/router/router.go | 62 +++++++++++++++-------------- internal/wire/handler.go | 3 +- 7 files changed, 68 insertions(+), 40 deletions(-) diff --git a/cmd/server/wire.go b/cmd/server/wire.go index a58c7a7..6594311 100644 --- a/cmd/server/wire.go +++ b/cmd/server/wire.go @@ -5,6 +5,7 @@ package main import ( "carrot_bbs/internal/handler" + "carrot_bbs/internal/repository" "carrot_bbs/internal/router" "carrot_bbs/internal/service" appwire "carrot_bbs/internal/wire" @@ -24,6 +25,7 @@ func InitializeApp() (*App, error) { // ProvideRouter 提供路由 func ProvideRouter( + userRepo repository.UserRepository, userHandler *handler.UserHandler, postHandler *handler.PostHandler, commentHandler *handler.CommentHandler, @@ -58,6 +60,7 @@ func ProvideRouter( wsHandler *handler.WSHandler, ) *router.Router { return router.New( + userRepo, userHandler, postHandler, commentHandler, diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index af64fec..7832846 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -132,8 +132,8 @@ func InitializeApp() (*App, error) { verificationHandler := handler.NewVerificationHandler(verificationService) adminVerificationService := wire.ProvideAdminVerificationService(verificationRepository, userRepository) adminVerificationHandler := wire.ProvideAdminVerificationHandler(adminVerificationService, userRepository) - wsHandler := wire.ProvideWSHandler(hub, chatService, groupService, jwtService, callService) - router := ProvideRouter(userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, voteHandler, channelHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, adminLogHandler, qrCodeHandler, materialHandler, callHandler, reportHandler, adminReportHandler, verificationHandler, adminVerificationHandler, logService, userActivityService, casbinService, wsHandler) + wsHandler := wire.ProvideWSHandler(hub, chatService, groupService, jwtService, callService, userRepository) + router := ProvideRouter(userRepository, userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, voteHandler, channelHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, adminLogHandler, qrCodeHandler, materialHandler, callHandler, reportHandler, adminReportHandler, verificationHandler, adminVerificationHandler, logService, userActivityService, casbinService, wsHandler) hotRankWorker := wire.ProvideHotRankWorker(config, postRepository, cache) app := NewApp(config, db, router, pushService, hotRankWorker, server, logger) return app, nil @@ -143,6 +143,7 @@ func InitializeApp() (*App, error) { // ProvideRouter 提供路由 func ProvideRouter( + userRepo repository.UserRepository, userHandler *handler.UserHandler, postHandler *handler.PostHandler, commentHandler *handler.CommentHandler, @@ -177,6 +178,7 @@ func ProvideRouter( wsHandler *handler.WSHandler, ) *router.Router { return router.New( + userRepo, userHandler, postHandler, commentHandler, diff --git a/internal/handler/ws_handler.go b/internal/handler/ws_handler.go index a0d8594..14366dc 100644 --- a/internal/handler/ws_handler.go +++ b/internal/handler/ws_handler.go @@ -20,6 +20,7 @@ import ( "carrot_bbs/internal/model" "carrot_bbs/internal/pkg/response" "carrot_bbs/internal/pkg/ws" + "carrot_bbs/internal/repository" "carrot_bbs/internal/service" ) @@ -71,6 +72,7 @@ type WSHandler struct { groupService service.GroupService jwtService *service.JWTService callService service.CallService + userRepo repository.UserRepository clientSeq uint64 } @@ -81,6 +83,7 @@ func NewWSHandler( groupService service.GroupService, jwtService *service.JWTService, callService service.CallService, + userRepo repository.UserRepository, ) *WSHandler { return &WSHandler{ wsHub: wsHub, @@ -88,6 +91,7 @@ func NewWSHandler( groupService: groupService, jwtService: jwtService, callService: callService, + userRepo: userRepo, } } @@ -118,6 +122,18 @@ func (h *WSHandler) HandleWebSocket(c *gin.Context) { } userID := claims.UserID + + // 检查用户认证状态 + user, err := h.userRepo.GetByID(userID) + if err != nil { + response.InternalServerError(c, "获取用户信息失败") + return + } + if user.VerificationStatus != model.VerificationStatusApproved { + response.ErrorWithStringCode(c, http.StatusForbidden, "VERIFICATION_REQUIRED", "请先完成身份认证") + return + } + zap.L().Info("WebSocket connection attempt", zap.String("user_id", userID), ) diff --git a/internal/middleware/verification.go b/internal/middleware/verification.go index c495e6c..1103c96 100644 --- a/internal/middleware/verification.go +++ b/internal/middleware/verification.go @@ -1,14 +1,16 @@ package middleware import ( - "github.com/gin-gonic/gin" + "net/http" "carrot_bbs/internal/model" "carrot_bbs/internal/pkg/response" "carrot_bbs/internal/repository" + + "github.com/gin-gonic/gin" ) -func RequireVerification(userRepo repository.UserRepository) gin.HandlerFunc { +func RequireVerified(userRepo repository.UserRepository) gin.HandlerFunc { return func(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { @@ -19,13 +21,13 @@ func RequireVerification(userRepo repository.UserRepository) gin.HandlerFunc { user, err := userRepo.GetByID(userID.(string)) if err != nil { - response.HandleError(c, err, "获取用户信息失败") + response.InternalServerError(c, "获取用户信息失败") c.Abort() return } if user.VerificationStatus != model.VerificationStatusApproved { - response.Forbidden(c, "需要完成身份认证") + response.ErrorWithStringCode(c, http.StatusForbidden, "VERIFICATION_REQUIRED", "请先完成身份认证") c.Abort() return } diff --git a/internal/pkg/response/response.go b/internal/pkg/response/response.go index 8684336..6884b4d 100644 --- a/internal/pkg/response/response.go +++ b/internal/pkg/response/response.go @@ -184,9 +184,9 @@ func statusCodeFromAppErrorCode(code string) int { // ErrorWithStringCode 带字符串错误码的错误响应 func ErrorWithStringCode(c *gin.Context, statusCode int, code string, message string) { - c.JSON(statusCode, ResponseSnakeCase{ - Code: statusCode, - Message: message, - Data: nil, + c.JSON(statusCode, gin.H{ + "code": statusCode, + "message": message, + "error_code": code, }) } diff --git a/internal/router/router.go b/internal/router/router.go index 72ef2f3..adb63f2 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -6,12 +6,14 @@ import ( "carrot_bbs/internal/handler" "carrot_bbs/internal/middleware" "carrot_bbs/internal/model" + "carrot_bbs/internal/repository" "carrot_bbs/internal/service" ) // Router 路由配置 type Router struct { engine *gin.Engine + userRepo repository.UserRepository userHandler *handler.UserHandler postHandler *handler.PostHandler commentHandler *handler.CommentHandler @@ -47,6 +49,7 @@ type Router struct { // New 创建路由 func New( + userRepo repository.UserRepository, userHandler *handler.UserHandler, postHandler *handler.PostHandler, commentHandler *handler.CommentHandler, @@ -87,6 +90,7 @@ func New( r := &Router{ engine: gin.Default(), + userRepo: userRepo, userHandler: userHandler, postHandler: postHandler, commentHandler: commentHandler, @@ -249,9 +253,9 @@ func (r *Router) setupRoutes() { posts.GET("", middleware.OptionalAuth(r.jwtService), r.postHandler.List) posts.GET("/search", middleware.OptionalAuth(r.jwtService), r.postHandler.Search) posts.GET("/:id", middleware.OptionalAuth(r.jwtService), r.postHandler.GetByID) - posts.POST("", authMiddleware, r.postHandler.Create) - posts.PUT("/:id", authMiddleware, r.postHandler.Update) - posts.DELETE("/:id", authMiddleware, r.postHandler.Delete) + posts.POST("", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Create) + posts.PUT("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Update) + posts.DELETE("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Delete) // 浏览量记录(可选认证,允许游客浏览) posts.POST("/:id/view", middleware.OptionalAuth(r.jwtService), r.postHandler.RecordView) @@ -259,18 +263,18 @@ func (r *Router) setupRoutes() { posts.POST("/:id/share", middleware.OptionalAuth(r.jwtService), r.postHandler.RecordShare) // 点赞 - posts.POST("/:id/like", authMiddleware, r.postHandler.Like) - posts.DELETE("/:id/like", authMiddleware, r.postHandler.Unlike) + posts.POST("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Like) + posts.DELETE("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Unlike) // 收藏 - posts.POST("/:id/favorite", authMiddleware, r.postHandler.Favorite) - posts.DELETE("/:id/favorite", authMiddleware, r.postHandler.Unfavorite) + posts.POST("/:id/favorite", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Favorite) + posts.DELETE("/:id/favorite", authMiddleware, middleware.RequireVerified(r.userRepo), r.postHandler.Unfavorite) // 投票相关路由 - posts.POST("/vote", authMiddleware, r.voteHandler.CreateVotePost) // 创建投票帖子 - posts.GET("/:id/vote", middleware.OptionalAuth(r.jwtService), r.voteHandler.GetVoteResult) // 获取投票结果 - posts.POST("/:id/vote", authMiddleware, r.voteHandler.Vote) // 投票 - posts.DELETE("/:id/vote", authMiddleware, r.voteHandler.Unvote) // 取消投票 + posts.POST("/vote", authMiddleware, middleware.RequireVerified(r.userRepo), r.voteHandler.CreateVotePost) // 创建投票帖子 + posts.GET("/:id/vote", middleware.OptionalAuth(r.jwtService), r.voteHandler.GetVoteResult) // 获取投票结果 + posts.POST("/:id/vote", authMiddleware, middleware.RequireVerified(r.userRepo), r.voteHandler.Vote) // 投票 + posts.DELETE("/:id/vote", authMiddleware, middleware.RequireVerified(r.userRepo), r.voteHandler.Unvote) // 取消投票 } // 频道路由(公开) @@ -322,15 +326,15 @@ func (r *Router) setupRoutes() { comments.GET("/post/:id", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetByPostID) comments.GET("/post/:id/cursor", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetByPostIDByCursor) // 帖子评论游标分页 comments.GET("/:id", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetByID) - comments.POST("", authMiddleware, r.commentHandler.Create) - comments.PUT("/:id", authMiddleware, r.commentHandler.Update) - comments.DELETE("/:id", authMiddleware, r.commentHandler.Delete) + comments.POST("", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Create) + comments.PUT("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Update) + comments.DELETE("/:id", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Delete) comments.GET("/:id/replies", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetReplies) comments.GET("/:id/replies/flat", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetRepliesByRootID) // 扁平化分页获取回复 comments.GET("/:id/replies/cursor", middleware.OptionalAuth(r.jwtService), r.commentHandler.GetRepliesByRootIDByCursor) // 回复游标分页 // 评论点赞 - comments.POST("/:id/like", authMiddleware, r.commentHandler.Like) - comments.DELETE("/:id/like", authMiddleware, r.commentHandler.Unlike) + comments.POST("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Like) + comments.DELETE("/:id/like", authMiddleware, middleware.RequireVerified(r.userRepo), r.commentHandler.Unlike) } // 会话路由 @@ -340,18 +344,18 @@ func (r *Router) setupRoutes() { // ================================================================ // 新的 RESTful 风格路由(推荐使用) // ================================================================ - conversations.GET("", r.messageHandler.HandleGetConversationList) // 列表 - conversations.GET("/cursor", r.messageHandler.GetConversationsByCursor) // 会话列表游标分页 - conversations.POST("", r.messageHandler.HandleCreateConversation) // 创建 - conversations.GET("/:id", r.messageHandler.HandleGetConversation) // 详情 - conversations.GET("/:id/messages", r.messageHandler.HandleGetMessages) // 消息列表 - conversations.GET("/:id/messages/cursor", r.messageHandler.GetMessagesByCursor) // 消息列表游标分页 - conversations.POST("/:id/messages", r.messageHandler.HandleSendMessage) // 发送消息 - conversations.POST("/:id/read", r.messageHandler.HandleMarkRead) // 标记已读 - conversations.PUT("/:id/pinned", r.messageHandler.HandleSetConversationPinned) // 置顶设置 - conversations.GET("/unread/count", r.messageHandler.GetUnreadCount) // 未读数 - conversations.POST("/:id/typing", r.messageHandler.HandleTyping) // 输入状态 - conversations.DELETE("/:id/self", r.messageHandler.HandleDeleteConversationForSelf) // 删除会话(仅自己) + conversations.GET("", r.messageHandler.HandleGetConversationList) // 列表 + conversations.GET("/cursor", r.messageHandler.GetConversationsByCursor) // 会话列表游标分页 + conversations.POST("", middleware.RequireVerified(r.userRepo), r.messageHandler.HandleCreateConversation) // 创建 + conversations.GET("/:id", r.messageHandler.HandleGetConversation) // 详情 + conversations.GET("/:id/messages", r.messageHandler.HandleGetMessages) // 消息列表 + conversations.GET("/:id/messages/cursor", r.messageHandler.GetMessagesByCursor) // 消息列表游标分页 + conversations.POST("/:id/messages", middleware.RequireVerified(r.userRepo), r.messageHandler.HandleSendMessage) // 发送消息 + conversations.POST("/:id/read", r.messageHandler.HandleMarkRead) // 标记已读 + conversations.PUT("/:id/pinned", r.messageHandler.HandleSetConversationPinned) // 置顶设置 + conversations.GET("/unread/count", r.messageHandler.GetUnreadCount) // 未读数 + conversations.POST("/:id/typing", r.messageHandler.HandleTyping) // 输入状态 + conversations.DELETE("/:id/self", r.messageHandler.HandleDeleteConversationForSelf) // 删除会话(仅自己) } @@ -376,7 +380,7 @@ func (r *Router) setupRoutes() { messages.Use(authMiddleware) { // 撤回/删除消息(统一接口) - messages.POST("/delete_msg", r.messageHandler.HandleDeleteMsg) + messages.POST("/delete_msg", middleware.RequireVerified(r.userRepo), r.messageHandler.HandleDeleteMsg) } // 举报路由 diff --git a/internal/wire/handler.go b/internal/wire/handler.go index 5cbaca2..d62e283 100644 --- a/internal/wire/handler.go +++ b/internal/wire/handler.go @@ -85,8 +85,9 @@ func ProvideWSHandler( groupService service.GroupService, jwtService *service.JWTService, callService service.CallService, + userRepo repository.UserRepository, ) *handler.WSHandler { - return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService) + return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService, userRepo) } // ProvideAdminVerificationHandler 提供管理端身份认证处理器