首次提交:初始化项目代码

This commit is contained in:
sunct
2026-07-31 15:24:14 +08:00
commit 8d3c97bd01
73 changed files with 11720 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
package middleware
import (
"net/http"
"strings"
"cloudnest/internal/pkg/logger"
"github.com/gin-gonic/gin"
)
// allowedOrigins defines the list of allowed origins for development.
// In production, this should be loaded from configuration.
// allowedOrigins 定义开发环境允许的源列表。生产环境应从配置加载。
var allowedOrigins = []string{
"http://localhost:8081",
"https://localhost:8081",
"http://127.0.0.1:8081",
"https://127.0.0.1:8081",
}
// isAllowedOrigin checks if the given origin is in the allowed list.
// isAllowedOrigin 检查给定的源是否在允许列表中。
func isAllowedOrigin(origin string) bool {
for _, o := range allowedOrigins {
if strings.EqualFold(o, origin) {
return true
}
}
return false
}
// CORS creates a middleware that handles Cross-Origin Resource Sharing.
// CORS 创建一个处理跨域资源共享(CORS)的中间件。
//
// This middleware sets the necessary headers to allow cross-origin requests,
// including the allowed origins, methods, and headers.
//
// Important:
// - For requests with Authorization header, the browser requires the
// Access-Control-Allow-Origin header to be a specific origin, not "*".
// - This middleware dynamically sets the origin to match the request's
// Origin header, which allows cross-origin requests with credentials.
// - In production, you should restrict allowed origins to specific domains.
//
// 此中间件设置必要的头部以允许跨域请求,包括允许的来源、方法和头部。
//
// 重要:
// - 对于带有 Authorization 头部的请求,浏览器要求 Access-Control-Allow-Origin
// 必须是具体的源,不能是 "*"。
// - 此中间件动态设置源以匹配请求的 Origin 头部,允许带凭据的跨域请求。
// - 在生产环境中,应将允许的源限制为特定域名。
//
// Returns:
// - gin.HandlerFunc: The CORS middleware handler.
//
// 返回值:
// - gin.HandlerFunc: CORS 中间件处理器。
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
// Get the Origin header from the request
// 从请求中获取 Origin 头部
origin := c.Request.Header.Get("Origin")
logger.Info("CORS middleware invoked",
"method", c.Request.Method,
"path", c.Request.URL.Path,
"origin", origin,
)
// Determine the allowed origin to return.
// If the origin is in our allowed list, echo it back.
// Otherwise, for development, also echo it back to be permissive.
// In production, you should reject unallowed origins.
//
// 确定要返回的允许源。如果在允许列表中,则回显。
// 开发环境下也回显其他源以方便调试。生产环境应拒绝未允许的源。
allowedOrigin := ""
if origin != "" {
if isAllowedOrigin(origin) {
allowedOrigin = origin
} else {
// Development fallback: allow any origin for easier local testing
// 开发环境回退:允许任何源以方便本地测试
allowedOrigin = origin
logger.Warn("CORS request from unknown origin",
"origin", origin,
"path", c.Request.URL.Path,
)
}
}
// Set Access-Control-Allow-Origin to the request's origin
// This allows cross-origin requests with Authorization header
// 将 Access-Control-Allow-Origin 设置为请求的源
// 这允许带 Authorization 头部的跨域请求
if allowedOrigin != "" {
c.Writer.Header().Set("Access-Control-Allow-Origin", allowedOrigin)
}
// Allow credentials (cookies, Authorization headers)
// 允许凭据(cookies、Authorization 头部)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
// Allow common HTTP methods
// 允许常用 HTTP 方法
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
// Allow common headers including Authorization
// 允许常用头部,包括 Authorization
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, Accept, Origin, X-Request-ID")
// Expose headers to the client
// 暴露头部给客户端
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Disposition, Content-Length, X-Request-ID")
// Handle OPTIONS preflight requests
// 处理 OPTIONS 预检请求
if c.Request.Method == "OPTIONS" {
logger.Info("CORS handling OPTIONS preflight", "origin", origin, "path", c.Request.URL.Path)
c.AbortWithStatus(http.StatusOK)
return
}
c.Next()
}
}
+57
View File
@@ -0,0 +1,57 @@
package middleware
import (
"cloudnest/internal/pkg/errors"
"cloudnest/internal/pkg/response"
"cloudnest/internal/pkg/logger"
"github.com/gin-gonic/gin"
)
// ErrorHandler creates a middleware that handles errors and panics.
// ErrorHandler 创建一个处理错误和 panic 的中间件。
//
// This middleware:
// 1. Recovers from panics and logs the error.
// 2. Handles errors stored in the Gin context after processing.
// 3. Formats consistent error responses.
//
// Returns:
// - gin.HandlerFunc: The error handling middleware handler.
//
// Note:
// - Should be added as the first middleware to catch all panics.
// - Uses the custom response package for consistent error formatting.
//
// 此中间件:
// 1. 从 panic 中恢复并记录错误。
// 2. 处理处理后存储在 Gin 上下文中的错误。
// 3. 格式化一致的错误响应。
//
// 返回值:
// - gin.HandlerFunc: 错误处理中间件处理器。
//
// 注意:
// - 应该作为第一个中间件添加以捕获所有 panic。
// - 使用自定义的 response 包进行一致的错误格式化。
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
logger.Error("panic recovered", "error", err)
response.InternalError(c, "服务器内部错误")
}
}()
c.Next()
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
if appErr, ok := errors.AsAppError(err); ok {
response.Error(c, appErr.Code, appErr.Message)
} else {
logger.Error("request error", "error", err)
response.InternalError(c, err.Error())
}
}
}
}
+38
View File
@@ -0,0 +1,38 @@
package middleware
import (
"strings"
"cloudnest/internal/pkg/jwt"
"cloudnest/internal/pkg/response"
"github.com/gin-gonic/gin"
)
func JWT(jwtSecret string) gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == "OPTIONS" {
c.Next()
return
}
auth := c.GetHeader("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
response.Unauthorized(c, "未提供认证令牌")
c.Abort()
return
}
tokenStr := strings.TrimPrefix(auth, "Bearer ")
claims, err := jwt.ParseToken(tokenStr, jwtSecret)
if err != nil {
response.Unauthorized(c, "令牌无效")
c.Abort()
return
}
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)
c.Set("user_code", claims.UserCode)
c.Next()
}
}