143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
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[:])
|
||
}
|