Files
smartmate/backend/gateway/api/forumapi/routes.go
2026-05-06 00:30:08 +08:00

88 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package forumapi
import (
"context"
"errors"
"net/http"
"time"
"github.com/LoveLosita/smartflow/backend/services/runtime/dao"
gatewaymiddleware "github.com/LoveLosita/smartflow/backend/gateway/middleware"
rootmiddleware "github.com/LoveLosita/smartflow/backend/gateway/middleware"
"github.com/LoveLosita/smartflow/backend/gateway/shared/respond"
ratelimit "github.com/LoveLosita/smartflow/backend/shared/infra/ratelimit"
"github.com/LoveLosita/smartflow/backend/shared/ports"
"github.com/gin-gonic/gin"
)
// RegisterRoutes 把计划广场 HTTP 入口挂到 gateway 路由组。
//
// 职责边界:
// 1. 只注册 /plan-square 下的边缘路由,不承载论坛业务规则;
// 2. 公开读接口允许匿名访问,若携带 token 则补齐 viewer_state
// 3. 写接口必须登录,并按既有 Redis 幂等中间件保护重复提交。
func RegisterRoutes(apiGroup *gin.RouterGroup, handler *Handler, authClient ports.AccessTokenValidator, cache *dao.CacheDAO, limiter *ratelimit.RateLimiter) {
if apiGroup == nil || handler == nil {
return
}
planSquare := apiGroup.Group("/plan-square")
{
publicGroup := planSquare.Group("")
publicGroup.Use(optionalJWTTokenAuth(authClient), rootmiddleware.RateLimitMiddleware(limiter, 40, 1))
publicGroup.GET("/posts", handler.ListPosts)
publicGroup.GET("/tags", handler.ListTags)
publicGroup.GET("/posts/:post_id", handler.GetPost)
publicGroup.GET("/posts/:post_id/comments", handler.ListComments)
writeGroup := planSquare.Group("")
writeGroup.Use(gatewaymiddleware.JWTTokenAuth(authClient), rootmiddleware.RateLimitMiddleware(limiter, 20, 1))
writeGroup.POST("/posts", rootmiddleware.IdempotencyMiddleware(cache), handler.CreatePost)
writeGroup.POST("/posts/:post_id/like", handler.LikePost)
writeGroup.DELETE("/posts/:post_id/like", handler.UnlikePost)
writeGroup.POST("/posts/:post_id/comments", rootmiddleware.IdempotencyMiddleware(cache), handler.CreateComment)
writeGroup.DELETE("/comments/:comment_id", handler.DeleteComment)
writeGroup.POST("/posts/:post_id/import", rootmiddleware.IdempotencyMiddleware(cache), handler.ImportPost)
}
}
// optionalJWTTokenAuth 为计划广场公开读接口提供“可登录增强”。
//
// 步骤说明:
// 1. 没有 Authorization 时直接放行,让匿名用户也能浏览计划广场;
// 2. 有 Authorization 时复用 user/auth 校验,并把 user_id 写入上下文;
// 3. token 非法时按正常鉴权失败返回,避免前端误以为已登录状态仍可用。
func optionalJWTTokenAuth(validator ports.AccessTokenValidator) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := gatewaymiddleware.ExtractTokenFromAuthorization(c.GetHeader("Authorization"))
if tokenString == "" {
c.Next()
return
}
if validator == nil {
c.JSON(http.StatusInternalServerError, respond.InternalError(errors.New("计划广场可选鉴权依赖未初始化")))
c.Abort()
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
defer cancel()
resp, err := validator.ValidateAccessToken(ctx, tokenString)
if err != nil {
respond.DealWithError(c, err)
c.Abort()
return
}
if resp == nil || !resp.Valid || resp.UserID <= 0 {
c.JSON(http.StatusUnauthorized, respond.InvalidClaims)
c.Abort()
return
}
c.Set("user_id", resp.UserID)
c.Set("claims", resp)
c.Next()
}
}