feat(contrib/files): 新增百度图床和58cdn图片上传功能实现

This commit is contained in:
1
2025-12-22 14:57:39 +00:00
parent c19a268b7b
commit 83ee9fba43
14 changed files with 1128 additions and 4 deletions

View File

@@ -0,0 +1,3 @@
module blazing/contrib/files/zhuanzhuan
go 1.25.0

View File

@@ -0,0 +1,148 @@
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)
}