l-export-async/attachment/uploader.go

143 lines
4.0 KiB
Go
Raw Permalink 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 attachment
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"strconv"
"time"
"github.com/duke-git/lancet/v2/retry"
"github.com/pkg/errors"
)
const (
TokenSalt = "LanSeXiongDi!@#&*("
UrlPreview = "v1/attachment/preview"
)
type UploadResp struct {
Url string `json:"url"`
PreviewUrl string `json:"previewUrl"`
}
// Upload 上传文件
// 返回值oss地址预览地址错误
func Upload(host, filePath, system, business, fieldFormName string) (*UploadResp, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, errors.WithMessage(err, "打开待上传文件失败")
}
defer f.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
formData := map[string]string{
"system": system,
"business": business,
}
for k, v := range formData {
err = writer.WriteField(k, v)
if err != nil {
return nil, errors.WithMessage(err, "构建form-data失败")
}
}
// 使用给出的属性名paramName和文件名filePath创建一个新的form-data头
part, err := writer.CreateFormFile(fieldFormName, filePath)
if err != nil {
return nil, errors.WithMessage(err, "创建文件流失败")
}
// 将源复制到目标将file写入到part 是按默认的缓冲区32k循环操作的不会将内容一次性全写入内存中,这样就能解决大文件的问题
_, err = io.Copy(part, f)
if err != nil {
return nil, errors.WithMessage(err, "复制文件流失败")
}
err = writer.Close()
if err != nil {
return nil, errors.WithMessage(err, "close writer失败")
}
httpClient := http.Client{
Timeout: time.Minute * 10,
}
url := fmt.Sprintf("%s/v1/attachment/upload", host)
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, errors.WithMessage(err, "new request 失败")
}
req.Header.Set("Content-Type", writer.FormDataContentType())
// 请求服务器
uploadResp := &UploadResp{}
requestHttp := func() error {
var requestErr error
var respHttp *http.Response
respHttp, requestErr = httpClient.Do(req)
if requestErr != nil {
return errors.WithMessage(err, "上传响应失败")
}
defer respHttp.Body.Close()
respBody, requestErr := io.ReadAll(respHttp.Body)
if requestErr != nil {
return errors.WithMessage(err, "读取响应体失败")
}
if respHttp.StatusCode != http.StatusOK {
respMap := make(map[string]string)
_ = json.Unmarshal(respBody, &respMap)
if respMap["message"] != "" {
// 非正常响应体
return errors.Errorf("上传响应状态异常,响应码:%d响应体%s", respHttp.StatusCode, string(respBody))
}
return errors.Errorf("响应错误:%s", respMap["message"])
}
requestErr = json.Unmarshal(respBody, &uploadResp)
if requestErr != nil {
// json失败为非正常响应体
if respHttp.StatusCode != http.StatusOK {
return errors.Errorf("上传响应状态异常,响应码:%d响应体%s", respHttp.StatusCode, string(respBody))
}
return errors.WithMessage(err, "json decode响应值失败")
}
return nil
}
_ = retry.Retry(func() error {
err = requestHttp()
return err
}, retry.RetryTimes(5), retry.RetryDuration(time.Second*3))
if err != nil {
return nil, err
}
return uploadResp, nil
}
// GeneratePreviewPrivateUrl 生成私有预览地址
func GeneratePreviewPrivateUrl(domain, param, attachmentUrl, water, fileName string, expireAt int64) string {
token := Signature(attachmentUrl, water, expireAt)
params := url.Values{}
params.Add("url", attachmentUrl)
params.Add("water", water)
params.Add("token", token)
params.Add("param", param)
params.Add("fileName", fileName)
params.Add("expireAt", strconv.FormatInt(expireAt, 10))
return fmt.Sprintf("%s/%s?%s", domain, UrlPreview, params.Encode())
}
// Signature 附件加签
func Signature(attachmentUrl, water string, expireAt int64) string {
s := fmt.Sprintf("%s,%s,%s,%d", TokenSalt, attachmentUrl, water, expireAt)
return MD5Sign(s)
}
func MD5Sign(s string) string {
sum := md5.Sum([]byte(s))
return hex.EncodeToString(sum[:])
}