package zhuanzhuan import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "time" ) // 全局配置 - 建议通过环境变量或配置文件加载,此处保持原有结构 const ( uploadURL = "https://dsyy.zhuanzhuan.com/uploadFile" savePath = "./uploads" // 文件保存根路径,需替换为实际路径 timeout = 30 * time.Second defaultCT = "image/jpeg" ) // UploadResponse 定义接口返回的JSON结构 type UploadResponse struct { Status bool `json:"status"` Data []UploadData `json:"data"` } // UploadData 定义返回数据中的单个文件信息 type UploadData struct { URL string `json:"url"` } // UploadFile 上传文件到指定接口 // fileKey: 文件标识(不含后缀),接口会拼接 .jpg 查找本地文件 // 返回值: 上传成功后的文件URL,错误信息 func UploadFile(fileKey string) (string, error) { // 1. 校验参数 if fileKey == "" { return "", fmt.Errorf("fileKey 不能为空") } // 2. 拼接本地文件路径 fileName := fmt.Sprintf("%s.jpg", fileKey) localPath := filepath.Join(savePath, fileName) // 3. 检查并打开文件 file, err := os.Open(localPath) if err != nil { return "", fmt.Errorf("打开本地文件失败: %w, 路径: %s", err, localPath) } defer file.Close() // 延迟关闭文件,确保资源释放 // 4. 构建multipart/form-data请求体 bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) defer bodyWriter.Close() // 延迟关闭writer,简化代码 // 4.1 添加表单字段(picDirType) if err := bodyWriter.WriteField("picDirType", "1"); err != nil { return "", fmt.Errorf("写入表单字段 picDirType 失败: %w", err) } // 4.2 添加文件字段 fileWriter, err := bodyWriter.CreateFormFile("uploadfiles", fileName) if err != nil { return "", fmt.Errorf("创建文件表单字段失败: %w", err) } // 复制文件内容到请求体 if _, err := io.Copy(fileWriter, file); err != nil { return "", fmt.Errorf("复制文件内容到请求体失败: %w", err) } // 5. 创建HTTP POST请求 contentType := bodyWriter.FormDataContentType() // 自动包含boundary,无需手动拼接 req, err := http.NewRequest(http.MethodPost, uploadURL, bodyBuf) if err != nil { return "", fmt.Errorf("创建HTTP请求失败: %w", err) } // 6. 设置请求头(对齐原有Java版本请求头) setRequestHeaders(req, contentType) // 7. 发送HTTP请求 client := &http.Client{Timeout: timeout} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("发送HTTP请求失败: %w", err) } defer resp.Body.Close() // 延迟关闭响应体 // 8. 读取响应内容 respBody, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("读取响应内容失败: %w", err) } // 9. 解析JSON响应 var uploadResp UploadResponse if err := json.Unmarshal(respBody, &uploadResp); err != nil { return "", fmt.Errorf("解析JSON响应失败: %w, 响应内容: %s", err, string(respBody)) } // 10. 校验响应结果并提取URL if err := validateUploadResponse(uploadResp, respBody); err != nil { return "", err } return uploadResp.Data[0].URL, nil } // setRequestHeaders 设置HTTP请求头,抽离为独立函数提升可读性 func setRequestHeaders(req *http.Request, contentType string) { req.Header.Set("Connection", "keep-alive") req.Header.Set("Accept", "*/*") req.Header.Set("Origin", "https://m.zhuanzhuan.com") req.Header.Set("User-Agent", "Mozilla/5.0") req.Header.Set("Content-Type", contentType) req.Header.Set("Referer", "https://m.zhuanzhuan.com/ec/") req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") req.Header.Set("Accept-Encoding", "gzip,deflate") } // validateUploadResponse 校验上传响应结果,抽离为独立函数 func validateUploadResponse(resp UploadResponse, respBody []byte) error { if !resp.Status { return fmt.Errorf("接口返回失败状态, 响应内容: %s", string(respBody)) } if len(resp.Data) == 0 { return fmt.Errorf("接口返回数据为空, 响应内容: %s", string(respBody)) } if resp.Data[0].URL == "" { return fmt.Errorf("接口返回URL为空, 响应内容: %s", string(respBody)) } return nil } // 示例调用 func main() { // 替换为实际的文件标识(不含.jpg后缀) fileKey := "test-file-key" url, err := UploadFile(fileKey) if err != nil { fmt.Printf("文件上传失败: %v\n", err) return } fmt.Printf("文件上传成功,访问URL: %s\n", url) }