Files
iqudo-top1/internal/middleware/http.go
2026-08-18 16:27:02 +08:00

118 lines
3.6 KiB
Go

package middleware
import (
"net/http"
"runtime/debug"
"strings"
"time"
"git.iwork-ai.com/xdc/iqudo-top1/internal/auth"
"git.iwork-ai.com/xdc/iqudo-top1/internal/response"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// CORS 完全开放跨域访问:允许所有来源、方法和请求头,并直接响应预检请求。
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Expose-Headers", "X-Request-ID, Content-Length")
c.Header("Access-Control-Max-Age", "86400")
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
func RequestLogger(log *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
started := time.Now()
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.NewString()
}
c.Set("request_id", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
duration := time.Since(started)
fields := []zap.Field{
zap.String("request_id", requestID), zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()), zap.Int("response_bytes", c.Writer.Size()), zap.Int64("duration_ms", duration.Milliseconds()), zap.String("client_ip", c.ClientIP()),
}
if principal, ok := auth.PrincipalFromContext(c); ok {
fields = append(fields, zap.Uint64("tenant_id", principal.TenantID), zap.Uint64("user_id", principal.UserID))
}
if len(c.Errors) > 0 {
fields = append(fields, zap.String("errors", c.Errors.String()))
}
switch status := c.Writer.Status(); {
case status >= http.StatusInternalServerError:
log.Error("http request", fields...)
case status >= http.StatusBadRequest:
log.Warn("http request", fields...)
default:
log.Info("http request", fields...)
}
}
}
func Recovery(log *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if recovered := recover(); recovered != nil {
log.Error("panic recovered", zap.Any("panic", recovered), zap.ByteString("stack", debug.Stack()))
response.Error(c, http.StatusInternalServerError, "INTERNAL_ERROR", "服务暂时不可用")
}
}()
c.Next()
}
}
func Authenticate(service *auth.Service) gin.HandlerFunc {
return func(c *gin.Context) {
header := c.GetHeader("Authorization")
parts := strings.SplitN(header, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
response.Error(c, http.StatusUnauthorized, "UNAUTHORIZED", "请先登录")
return
}
principal, err := service.ParseAccessToken(parts[1])
if err != nil {
response.Error(c, http.StatusUnauthorized, "INVALID_TOKEN", "登录状态已失效")
return
}
principal, err = service.RefreshPrincipal(principal)
if err != nil {
response.Error(c, http.StatusUnauthorized, "INVALID_MEMBERSHIP", "账号或企业成员身份已失效")
return
}
auth.SetPrincipal(c, principal)
c.Next()
}
}
func RequireAnyPermission(permissions ...string) gin.HandlerFunc {
return func(c *gin.Context) {
principal, ok := auth.PrincipalFromContext(c)
if !ok {
response.Error(c, http.StatusUnauthorized, "UNAUTHORIZED", "请先登录")
return
}
if !auth.HasAnyPermission(principal, permissions...) {
response.Error(c, http.StatusForbidden, "FORBIDDEN", "没有执行该操作的权限")
return
}
c.Next()
}
}
func RequirePermission(permission string) gin.HandlerFunc {
return RequireAnyPermission(permission)
}