Files
bl/common/contrib/files/locimg/locimg.go

188 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"compress/gzip"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"os"
"time"
)
// 全局配置与PHP代码保持完全一致
const (
// locimg上传接口地址
locimgUploadURL = "https://yunimg.cc/upload/upload.html"
// 必需的Referer
locimgReferer = "https://yunimg.cc/"
// 固定User-Agent与PHP的curl配置一致
locimgUA = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
// 请求超时时间
locimgTimeout = 30 * time.Second
// 文件字段固定MIME类型对应PHP的CURLFile第二个参数
locimgFileMIME = "image/jpeg"
)
// LocimgData 响应数据中的data结构体对应PHP的$arr['data']
type LocimgData struct {
URL string `json:"url"` // 图片URL字段
}
// LocimgUploadResponse locimg接口返回JSON结构对应PHP的响应格式
type LocimgUploadResponse struct {
Data LocimgData `json:"data"` // 成功返回的数据字段
Msg string `json:"msg"` // 失败返回的提示信息
}
// LocimgImgUpload locimg图床上传函数完全对齐PHP功能
// filepath: 本地文件路径
// filename: 上传文件名包含后缀对应PHP的$filename
// 返回值: 最终图片URL错误信息
func LocimgImgUpload(filepath string, filename string) (string, error) {
// 1. 参数校验
if filepath == "" {
return "", errors.New("本地文件路径不能为空")
}
if filename == "" {
return "", errors.New("上传文件名不能为空")
}
// 2. 打开本地文件
file, err := os.Open(filepath)
if err != nil {
return "", fmt.Errorf("打开本地文件失败: %w, 路径: %s", err, filepath)
}
defer file.Close() // 延迟关闭文件,确保资源释放
// 3. 构建multipart/form-data请求体包含文件字段和普通表单字段
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
defer bodyWriter.Close() // 延迟关闭writer确保请求体完整
// 3.1 添加文件字段:"image"修复核心使用textproto.MIMEHeader构建头部
// 步骤1创建textproto.MIMEHeader实例
mimeHeader := textproto.MIMEHeader{}
// 步骤2设置Content-Disposition指定表单字段名和文件名必须用双引号包裹
mimeHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="image"; filename="%s"`, filename))
// 步骤3设置Content-Type指定固定MIME类型
mimeHeader.Set("Content-Type", locimgFileMIME)
// 步骤4传入CreatePartmimeHeader会自动转为指针类型
fileWriter, err := bodyWriter.CreatePart(mimeHeader)
if err != nil {
return "", fmt.Errorf("创建文件表单字段失败: %w", err)
}
// 复制文件内容到请求体(保持不变)
if _, err := io.Copy(fileWriter, file); err != nil {
return "", fmt.Errorf("复制文件内容到请求体失败: %w", err)
}
// 3.2 添加普通表单字段:"fileId"对应PHP的'fileId' => $filename
if err := bodyWriter.WriteField("fileId", filename); err != nil {
return "", fmt.Errorf("写入表单字段fileId失败: %w", err)
}
// 4. 创建HTTP POST请求
contentType := bodyWriter.FormDataContentType() // 自动包含boundary无需手动拼接
req, err := http.NewRequest(http.MethodPost, locimgUploadURL, bodyBuf)
if err != nil {
return "", fmt.Errorf("创建HTTP请求失败: %w", err)
}
// 5. 设置请求头完全对齐PHP的curl请求头配置
setLocimgRequestHeaders(req, contentType)
// 6. 配置HTTP客户端支持gzip解码、忽略SSL验证、超时控制
client := &http.Client{
Timeout: locimgTimeout,
Transport: &http.Transport{
// 忽略SSL证书验证对应PHP的CURLOPT_SSL_VERIFYPEER=false/CURLOPT_SSL_VERIFYHOST=false
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 注意:字段名是 InsecureSkipVerify非 TLSInsecureSkipVerify
},
// 支持gzip解码对应PHP的CURLOPT_ENCODING "gzip"
DisableCompression: false,
},
}
// 7. 发送HTTP请求
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("发送HTTP请求失败: %w", err)
}
defer resp.Body.Close() // 延迟关闭响应体
// 8. 读取并解码响应内容处理gzip压缩响应
var respBody []byte
if resp.Header.Get("Content-Encoding") == "gzip" {
// 解压gzip响应对应PHP的gzip解码
gzipReader, err := gzip.NewReader(resp.Body)
if err != nil {
return "", fmt.Errorf("创建gzip解压阅读器失败: %w", err)
}
defer gzipReader.Close()
respBody, err = io.ReadAll(gzipReader)
} else {
respBody, err = io.ReadAll(resp.Body)
}
if err != nil {
return "", fmt.Errorf("读取响应内容失败: %w", err)
}
// 9. 解析JSON响应
var locimgResp LocimgUploadResponse
if err := json.Unmarshal(respBody, &locimgResp); err != nil {
return "", fmt.Errorf("解析JSON响应失败: %w, 响应内容: %s", err, string(respBody))
}
// 10. 按PHP逻辑判断响应结果
if locimgResp.Data.URL != "" {
// 存在data.url字段上传成功
return locimgResp.Data.URL, nil
} else if locimgResp.Msg != "" {
// 存在msg字段返回对应错误
return "", fmt.Errorf("上传失败请重试(%s", locimgResp.Msg)
} else {
// 既无data.url也无msg接口错误
return "", fmt.Errorf("上传失败!接口错误,响应内容: %s", string(respBody))
}
}
// setLocimgRequestHeaders 设置locimg必需的请求头完全对齐PHP的curl配置
func setLocimgRequestHeaders(req *http.Request, contentType string) {
// 默认请求头对应PHP的$httpheader数组
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Encoding", "gzip,deflate,sdch")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8")
req.Header.Set("Connection", "close") // 对应PHP的Connection: close
req.Header.Set("User-Agent", locimgUA) // 固定User-Agent与PHP一致
// 额外请求头对应PHP的$addheader参数X-Requested-With: XMLHttpRequest
req.Header.Set("X-Requested-With", "XMLHttpRequest")
// 核心请求头
req.Header.Set("Content-Type", contentType) // 包含multipart boundary
req.Header.Set("Referer", locimgReferer) // 来源校验与PHP一致
}
// 示例调用
func main() {
// 配置本地文件路径和上传文件名(请替换为实际值)
localFilePath := "./test.jpg" // 本地图片实际路径
uploadFilename := "custom-locimg.jpg" // 上传时的自定义文件名(包含后缀)
// 调用locimg上传函数
imgURL, err := LocimgImgUpload(localFilePath, uploadFilename)
if err != nil {
fmt.Printf("locimg图床上传失败: %v\n", err)
return
}
fmt.Printf("locimg图床上传成功图片URL: %s\n", imgURL)
}