package main import ( "fmt" "net/http" "os" "path" "strings" "time" "github.com/gin-gonic/gin" ) // 配置常量 const ( uploadDir = "./uploads" // 图片保存目录 allowImageTypes = "image/jpeg,image/png,image/gif,image/webp" // 允许的图片类型 maxFileSize = 5 << 20 // 最大上传文件大小(5MB) ) func main() { // 1. 初始化 Gin 引擎 r := gin.Default() // 2. 配置跨域 r.Use(corsMiddleware()) // 3. 初始化上传目录 if err := initUploadDir(); err != nil { fmt.Printf("初始化上传目录失败:%v\n", err) return } // 4. 静态文件路由(访问上传的图片) r.Static("/uploads", uploadDir) // 5. 图片上传接口(核心修正在这里) r.POST("/api/upload/image", uploadImageHandler) // 6. 启动服务 fmt.Println("后端服务启动成功,地址:http://localhost:8080") if err := r.Run(":8080"); err != nil { fmt.Printf("服务启动失败:%v\n", err) } } // corsMiddleware 跨域中间件 func corsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 关键修正:替换 * 为前端实际域名(本地开发是 http://localhost:5173) c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173") c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") // 允许携带 Cookie(与前端 credentials: 'include' 对应) c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") // 处理 OPTIONS 预检请求 if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusOK) return } c.Next() } } // initUploadDir 初始化上传目录 func initUploadDir() error { _, err := os.Stat(uploadDir) if os.IsNotExist(err) { return os.MkdirAll(uploadDir, 0755) } return err } // uploadImageHandler 图片上传处理函数(关键修正部分) func uploadImageHandler(c *gin.Context) { // 【修正1:Gin v1.9+ 中 FormFile 只返回 2 个值:fileHeader 和 err】 fileHeader, err := c.FormFile("image") if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "message": "获取图片失败,请重新上传", "error": err.Error(), }) return } // 【修正2:通过 fileHeader.Open() 获取 file 对象(原 3 返回值中的 file)】 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 > maxFileSize { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "message": fmt.Sprintf("图片大小不能超过 %dMB", maxFileSize>>20), }) return } fileType := fileHeader.Header.Get("Content-Type") if !strings.Contains(allowImageTypes, fileType) { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "message": fmt.Sprintf("不支持的图片类型,仅允许:%s", allowImageTypes), }) return } // 生成唯一文件名 timestamp := time.Now().Format("20060102150405") filename := fmt.Sprintf("%s_%s", timestamp, fileHeader.Filename) savePath := path.Join(uploadDir, filename) // 【修正3:保存文件时,使用 fileHeader(原代码用 file 也可,但 Gin 推荐用 fileHeader)】 if err := c.SaveUploadedFile(fileHeader, savePath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "message": "图片保存失败,请重试", "error": err.Error(), }) return } // 生成图片访问 URL imageURL := fmt.Sprintf("http://%s/uploads/%s", c.Request.Host, filename) // 返回成功响应 c.JSON(http.StatusOK, gin.H{ "code": 200, "message": "图片上传成功", "data": gin.H{"url": imageURL}, }) }