bilibili-backup/library/database/bfs/upload.go
2019-04-22 02:59:20 +00:00

219 lines
4.8 KiB
Go

package bfs
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"go-common/library/ecode"
xhttp "go-common/library/net/http/blademaster"
xtime "go-common/library/time"
)
const (
_appKey = "appkey"
_ts = "ts"
)
var (
_defaultHost = "http://api.bilibili.co"
_pathUpload = "/x/internal/upload"
_pathGenWatermark = "/x/internal/image/gen"
_defaultHTTPClientConfig = &xhttp.ClientConfig{
App: &xhttp.App{
Key: "3c4e41f926e51656",
Secret: "26a2095b60c24154521d24ae62b885bb",
},
Dial: xtime.Duration(1 * time.Second),
Timeout: xtime.Duration(1 * time.Second),
}
errBucket = errors.New("bucket is empty")
errFile = errors.New("file is empty")
)
// Config bfs upload config
type Config struct {
Host string
HTTPClient *xhttp.ClientConfig
}
// Request bfs upload request
type Request struct {
Bucket string
Dir string
ContentType string
Filename string
File []byte
WMKey string
WMText string
WMPaddingX uint32
WMPaddingY uint32
WMScale float64
}
// verify verify bfs request.
func (r *Request) verify() error {
if r.Bucket == "" {
return errBucket
}
if r.File == nil || len(r.File) == 0 {
return errFile
}
return nil
}
// BFS bfs instance
type BFS struct {
conf *Config
client *xhttp.Client
}
// New new a bfs client.
func New(c *Config) *BFS {
if c == nil {
c = &Config{
Host: _defaultHost,
HTTPClient: _defaultHTTPClientConfig,
}
}
return &BFS{
conf: c,
client: xhttp.NewClient(c.HTTPClient),
}
}
// Upload bfs internal upload.
func (b *BFS) Upload(ctx context.Context, req *Request) (location string, err error) {
var (
buf = &bytes.Buffer{}
bw io.Writer
request *http.Request
response *struct {
Code int `json:"code"`
Data struct {
ETag string `json:"etag"`
Location string `json:"location"`
} `json:"data"`
Message string `json:"message"`
}
url = b.conf.Host + _pathUpload + "?" + b.sign()
)
if err = req.verify(); err != nil {
return
}
w := multipart.NewWriter(buf)
if bw, err = w.CreateFormFile("file", "bfs-upload"); err != nil {
return
}
if _, err = bw.Write(req.File); err != nil {
return
}
w.WriteField("bucket", req.Bucket)
if req.Filename != "" {
w.WriteField("file_name", req.Filename)
}
if req.Dir != "" {
w.WriteField("dir", req.Dir)
}
if req.WMText != "" {
w.WriteField("wm_text", req.WMKey)
}
if req.WMKey != "" {
w.WriteField("wm_key", req.WMKey)
}
if req.WMPaddingX > 0 {
w.WriteField("wm_padding_x", fmt.Sprint(req.WMPaddingX))
}
if req.WMPaddingY > 0 {
w.WriteField("wm_padding_y", fmt.Sprint(req.WMPaddingY))
}
if req.WMScale > 0 {
w.WriteField("wm_scale", strconv.FormatFloat(req.WMScale, 'f', 2, 64))
}
if req.ContentType != "" {
w.WriteField("content_type", req.ContentType)
}
if err = w.Close(); err != nil {
return
}
if request, err = http.NewRequest(http.MethodPost, url, buf); err != nil {
return
}
request.Header.Set("Content-Type", w.FormDataContentType())
if err = b.client.Do(ctx, request, &response); err != nil {
return
}
if !ecode.Int(response.Code).Equal(ecode.OK) {
err = ecode.Int(response.Code)
return
}
location = response.Data.Location
return
}
// GenWatermark create watermark image by key and text.
func (b *BFS) GenWatermark(ctx context.Context, uploadKey, wmKey, wmText string, vertical bool, distance int) (location string, err error) {
var (
params = url.Values{}
uri = b.conf.Host + _pathGenWatermark
response *struct {
Code int `json:"code"`
Data struct {
Location string `json:"location"`
Width int `json:"width"`
Height int `json:"height"`
MD5 string `json:"md5"`
} `json:"data"`
Message string `json:"message"`
}
)
params.Set("upload_key", uploadKey)
params.Set("wm_key", wmKey)
params.Set("wm_text", wmText)
params.Set("wm_vertical", fmt.Sprint(vertical))
params.Set("distance", fmt.Sprint(distance))
if err = b.client.Post(ctx, uri, "", params, &response); err != nil {
return
}
if !ecode.Int(response.Code).Equal(ecode.OK) {
err = ecode.Int(response.Code)
return
}
location = response.Data.Location
return
}
// sign calc appkey and appsecret sign.
func (b *BFS) sign() string {
key := b.conf.HTTPClient.Key
secret := b.conf.HTTPClient.Secret
params := url.Values{}
params.Set(_appKey, key)
params.Set(_ts, strconv.FormatInt(time.Now().Unix(), 10))
tmp := params.Encode()
if strings.IndexByte(tmp, '+') > -1 {
tmp = strings.Replace(tmp, "+", "%20", -1)
}
var buf bytes.Buffer
buf.WriteString(tmp)
buf.WriteString(secret)
mh := md5.Sum(buf.Bytes())
// query
var qb bytes.Buffer
qb.WriteString(tmp)
qb.WriteString("&sign=")
qb.WriteString(hex.EncodeToString(mh[:]))
return qb.String()
}