first commit
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
This commit is contained in:
17
internal/middleware/cors.go
Normal file
17
internal/middleware/cors.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
)
|
||||
|
||||
// SetupCORS 设置CORS中间件
|
||||
// 配置跨域资源共享,允许前端应用访问API
|
||||
func SetupCORS() fiber.Handler {
|
||||
return cors.New(cors.Config{
|
||||
AllowOrigins: "*",
|
||||
AllowMethods: "GET, POST, PUT, DELETE, OPTIONS",
|
||||
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
|
||||
MaxAge: 86400, // 24小时
|
||||
})
|
||||
}
|
||||
59
internal/middleware/logging.go
Normal file
59
internal/middleware/logging.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/subconverter-go/internal/logging"
|
||||
)
|
||||
|
||||
// SetupLogging 设置日志中间件
|
||||
// 记录所有HTTP请求的详细信息
|
||||
func SetupLogging(logger *logging.Logger) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
start := time.Now()
|
||||
|
||||
// 处理请求
|
||||
err := c.Next()
|
||||
|
||||
// 计算请求耗时
|
||||
duration := time.Since(start)
|
||||
|
||||
// 记录请求日志
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"method": c.Method(),
|
||||
"path": c.Path(),
|
||||
"ip": c.IP(),
|
||||
"user_agent": c.Get("User-Agent"),
|
||||
"status": c.Response().StatusCode(),
|
||||
"duration": duration.Milliseconds(),
|
||||
"size": len(c.Response().Body()),
|
||||
}).Infof("HTTP %s %s - %d (%v)", c.Method(), c.Path(), c.Response().StatusCode(), duration)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// SetupRequestID 设置请求ID中间件
|
||||
// 为每个请求生成唯一ID,便于追踪和调试
|
||||
func SetupRequestID() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 生成请求ID
|
||||
requestID := generateRequestID()
|
||||
|
||||
// 设置到响应头
|
||||
c.Set("X-Request-ID", requestID)
|
||||
|
||||
// 存储到上下文
|
||||
c.Locals("requestID", requestID)
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// generateRequestID 生成请求ID
|
||||
func generateRequestID() string {
|
||||
// 使用时间戳和随机数生成唯一ID
|
||||
return fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().Nanosecond()%1000000)
|
||||
}
|
||||
27
internal/middleware/ratelimit.go
Normal file
27
internal/middleware/ratelimit.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||
)
|
||||
|
||||
// SetupRateLimit 设置限流中间件
|
||||
// 防止API滥用,限制每个IP的请求频率
|
||||
func SetupRateLimit(maxRequests int, expiration int) fiber.Handler {
|
||||
return limiter.New(limiter.Config{
|
||||
Max: maxRequests,
|
||||
Expiration: time.Duration(expiration) * time.Second, // 过期时间(秒)
|
||||
KeyGenerator: func(c *fiber.Ctx) string {
|
||||
return c.IP()
|
||||
},
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
|
||||
"success": false,
|
||||
"error": "Rate limit exceeded",
|
||||
"message": "Too many requests, please try again later",
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
36
internal/middleware/recovery.go
Normal file
36
internal/middleware/recovery.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/subconverter-go/internal/logging"
|
||||
)
|
||||
|
||||
// SetupRecovery 设置恢复中间件
|
||||
// 捕获panic并记录错误日志,返回友好的错误响应
|
||||
func SetupRecovery(logger *logging.Logger) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// 记录panic日志
|
||||
logger.WithFields(map[string]interface{}{
|
||||
"method": c.Method(),
|
||||
"path": c.Path(),
|
||||
"ip": c.IP(),
|
||||
"user_agent": c.Get("User-Agent"),
|
||||
"panic": fmt.Sprintf("%v", r),
|
||||
}).Error("Recovered from panic")
|
||||
|
||||
// 返回错误响应
|
||||
c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"success": false,
|
||||
"error": "Internal server error",
|
||||
"message": "An unexpected error occurred",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
87
internal/middleware/security.go
Normal file
87
internal/middleware/security.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/subconverter-go/internal/logging"
|
||||
)
|
||||
|
||||
// SetupSecurity 设置安全中间件
|
||||
// 添加各种安全相关的HTTP头
|
||||
func SetupSecurity() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 设置安全相关的HTTP头
|
||||
c.Set("X-Content-Type-Options", "nosniff")
|
||||
c.Set("X-Frame-Options", "DENY")
|
||||
c.Set("X-XSS-Protection", "1; mode=block")
|
||||
c.Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
c.Set("Content-Security-Policy", "default-src 'self'")
|
||||
|
||||
// 移除服务器信息
|
||||
c.Set("Server", "")
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// SetupAuthentication 设置认证中间件
|
||||
// 验证访问令牌,保护API端点
|
||||
func SetupAuthentication(logger *logging.Logger, validTokens []string) fiber.Handler {
|
||||
allowed := make(map[string]struct{}, len(validTokens))
|
||||
for _, token := range validTokens {
|
||||
if token == "" {
|
||||
continue
|
||||
}
|
||||
allowed[strings.TrimSpace(token)] = struct{}{}
|
||||
}
|
||||
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 如果没有配置令牌,跳过认证
|
||||
if len(allowed) == 0 {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
token := ""
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
token = strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
token = strings.TrimSpace(c.Query("token"))
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
logger.Warn("Missing authorization token")
|
||||
return forbiddenResponse(c)
|
||||
}
|
||||
|
||||
if _, ok := allowed[token]; !ok {
|
||||
logger.WithField("token", token).Warn("Invalid authorization token")
|
||||
return forbiddenResponse(c)
|
||||
}
|
||||
|
||||
c.Locals("authenticated", true)
|
||||
c.Locals("token", token)
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func forbiddenResponse(c *fiber.Ctx) error {
|
||||
c.Set("Content-Type", "text/plain")
|
||||
return c.Status(fiber.StatusForbidden).SendString("Forbidden\n")
|
||||
}
|
||||
|
||||
// SetupCompression 设置压缩中间件
|
||||
// 启用响应压缩,减少传输数据量
|
||||
func SetupCompression() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 设置Accept-Encoding头
|
||||
c.Set("Accept-Encoding", "gzip, deflate")
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user