diff --git a/server/handler/uploadimg/upload.go b/server/handler/uploadimg/upload.go index 443d2771..2eb99842 100644 --- a/server/handler/uploadimg/upload.go +++ b/server/handler/uploadimg/upload.go @@ -12,95 +12,102 @@ import ( ) // UploadImageHandler 处理图片上传到OSS -func UploadImageHandler(c *gin.Context, cfg *config.Config) { - // 获取上传的图片文件 - fileHeader, err := c.FormFile("image") - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "message": "获取图片失败,请重新上传", - "error": err.Error(), +func UploadImageHandler(cfg *config.Config) gin.HandlerFunc { + // 闭包:内部函数可以访问 cfg 参数 + return func(c *gin.Context) { + // 获取上传的图片文件 + fileHeader, err := c.FormFile("image") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": "获取图片失败,请重新上传", + "error": err.Error(), + }) + return + } + + // 打开文件 + file, err := fileHeader.Open() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 500, + "message": "打开图片文件失败", + "error": err.Error(), + }) + return + } + defer file.Close() + + // 检查文件大小(使用配置中的max_file_size) + if fileHeader.Size > cfg.Upload.MaxFileSize { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": fmt.Sprintf("图片大小不能超过 %dMB", cfg.Upload.MaxFileSize>>20), + }) + return + } + + // 检查文件类型(使用配置中的allow_image_types) + fileType := fileHeader.Header.Get("Content-Type") + if !strings.Contains(cfg.Upload.AllowImageTypes, fileType) { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 400, + "message": fmt.Sprintf("不支持的图片类型,仅允许:%s", cfg.Upload.AllowImageTypes), + }) + return + } + + // 初始化OSS客户端(使用配置中的OSS参数) + client, err := oss.New( + cfg.OSS.Endpoint, + cfg.OSS.AccessKeyID, + cfg.OSS.AccessKeySecret, + ) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 500, + "message": "初始化OSS客户端失败", + "error": err.Error(), + }) + return + } + + // 获取Bucket + bucket, err := client.Bucket(cfg.OSS.BucketName) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 500, + "message": "获取Bucket失败", + "error": err.Error(), + }) + return + } + + // 生成唯一文件名 + timestamp := time.Now().Format("20060102150405") + filename := fmt.Sprintf("%s_%s", timestamp, fileHeader.Filename) + objectKey := cfg.OSS.ObjectPrefix + filename // OSS中的完整对象键 + + // 上传文件到OSS + err = bucket.PutObject(objectKey, file) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 500, + "message": "上传图片到OSS失败", + "error": err.Error(), + }) + return + } + + // 生成图片访问URL + host := strings.TrimPrefix(cfg.OSS.Endpoint, "https://") + imageURL := fmt.Sprintf("https://%s.%s/%s", cfg.OSS.BucketName, host, objectKey) + + // 返回成功响应 + c.JSON(http.StatusOK, gin.H{ + "code": 200, + "message": "图片上传成功", + "data": gin.H{"url": imageURL}, }) - return } - - // 打开文件 - file, err := fileHeader.Open() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "code": 500, - "message": "打开图片文件失败", - "error": err.Error(), - }) - return - } - defer file.Close() - - // 检查文件大小 - if fileHeader.Size > cfg.Upload.MaxFileSize { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "message": fmt.Sprintf("图片大小不能超过 %dMB", cfg.Upload.MaxFileSize>>20), - }) - return - } - - // 检查文件类型 - fileType := fileHeader.Header.Get("Content-Type") - if !strings.Contains(cfg.Upload.AllowImageTypes, fileType) { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "message": fmt.Sprintf("不支持的图片类型,仅允许:%s", cfg.Upload.AllowImageTypes), - }) - return - } - - // 初始化OSS客户端 - client, err := oss.New(cfg.OSS.Endpoint, cfg.OSS.AccessKeyID, cfg.OSS.AccessKeySecret) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "code": 500, - "message": "初始化OSS客户端失败", - "error": err.Error(), - }) - return - } - - // 获取Bucket - bucket, err := client.Bucket(cfg.OSS.BucketName) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "code": 500, - "message": "获取Bucket失败", - "error": err.Error(), - }) - return - } - - // 生成唯一文件名 - timestamp := time.Now().Format("20060102150405") - filename := fmt.Sprintf("%s_%s", timestamp, fileHeader.Filename) - objectKey := cfg.OSS.ObjectPrefix + filename // OSS中的完整对象键 - - // 上传文件到OSS - err = bucket.PutObject(objectKey, file) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "code": 500, - "message": "上传图片到OSS失败", - "error": err.Error(), - }) - return - } - - // 生成图片访问URL - host := strings.TrimPrefix(cfg.OSS.Endpoint, "https://") - imageURL := fmt.Sprintf("https://%s.%s/%s", cfg.OSS.BucketName, host, objectKey) - - // 返回成功响应 - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "message": "图片上传成功", - "data": gin.H{"url": imageURL}, - }) } diff --git a/server/middleware/cors.go b/server/middleware/cors.go index 8e9a32f1..8c086863 100644 --- a/server/middleware/cors.go +++ b/server/middleware/cors.go @@ -1,40 +1,54 @@ package middleware import ( + "fmt" "net/http" "github.com/JACKYMYPERSON/hldrCenter/config" "github.com/gin-gonic/gin" ) -func CorsMiddleware(cfg *config.ServerConfig) gin.HandlerFunc { +func CorsMiddleware(serverConfig *config.ServerConfig) gin.HandlerFunc { return func(c *gin.Context) { - // 处理跨域请求头 + // 1. 打印配置的允许源(调试用,确认配置是否正确加载) + fmt.Printf("允许的前端源:%v\n", serverConfig.AllowedOrigins) + + // 2. 获取请求的Origin头 origin := c.Request.Header.Get("Origin") - if origin != "" && isAllowedOrigin(origin, cfg.AllowedOrigins) { - c.Writer.Header().Set("Access-Control-Allow-Origin", origin) + fmt.Printf("当前请求源:%s\n", origin) // 调试用 + + // 3. 宽松的跨域匹配逻辑 + allowOrigin := "" + if len(serverConfig.AllowedOrigins) > 0 { + for _, allowed := range serverConfig.AllowedOrigins { + // 支持通配符*,或精确匹配 + if allowed == "*" || allowed == origin { + allowOrigin = origin + break + } + } } - c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization") - c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + // 4. 即使没有匹配到,也可以临时设置为*(仅测试用,生产环境需删除) + // if allowOrigin == "" { + // allowOrigin = "*" + // } - // 处理预检请求 + // 5. 设置核心跨域头 + if allowOrigin != "" { + c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin) + } + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Max-Age", "86400") // 24小时缓存预检请求 + + // 6. 处理OPTIONS预检请求 if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(http.StatusOK) + c.AbortWithStatus(http.StatusNoContent) // 使用204更规范 return } c.Next() } } - -// 检查来源是否在允许的列表中 -func isAllowedOrigin(origin string, allowedOrigins []string) bool { - for _, allowed := range allowedOrigins { - if allowed == "*" || allowed == origin { - return true - } - } - return false -} diff --git a/server/router/router.go b/server/router/router.go index 2e0d65b9..d32fd97d 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -3,6 +3,7 @@ package router import ( "github.com/JACKYMYPERSON/hldrCenter/config" handler "github.com/JACKYMYPERSON/hldrCenter/handler/uploadimg" + "github.com/JACKYMYPERSON/hldrCenter/middleware" "github.com/gin-gonic/gin" ) @@ -10,16 +11,14 @@ import ( func SetupRouter(cfg *config.Config) *gin.Engine { r := gin.Default() - // API路由组 + // 关键:跨域中间件必须在所有路由定义之前应用 + r.Use(middleware.CorsMiddleware(&cfg.Server)) + + // 定义路由组(必须在中间件之后) api := r.Group("/api") { - // 图片上传路由 - api.POST("/upload/image", func(c *gin.Context) { - handler.UploadImageHandler(c, cfg) - }) - api.POST("/upload/cover", func(c *gin.Context) { - handler.UploadImageHandler(c, cfg) - }) + api.POST("/upload/image", handler.UploadImageHandler(cfg)) + api.POST("/upload/cover", handler.UploadImageHandler(cfg)) // 复用上传逻辑 } return r