219 lines
4.8 KiB
Go
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()
|
|
}
|