upload.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package bfs
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/md5"
  6. "encoding/hex"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "mime/multipart"
  11. "net/http"
  12. "net/url"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "go-common/library/ecode"
  17. xhttp "go-common/library/net/http/blademaster"
  18. xtime "go-common/library/time"
  19. )
  20. const (
  21. _appKey = "appkey"
  22. _ts = "ts"
  23. )
  24. var (
  25. _defaultHost = "http://api.bilibili.co"
  26. _pathUpload = "/x/internal/upload"
  27. _pathGenWatermark = "/x/internal/image/gen"
  28. _defaultHTTPClientConfig = &xhttp.ClientConfig{
  29. App: &xhttp.App{
  30. Key: "3c4e41f926e51656",
  31. Secret: "26a2095b60c24154521d24ae62b885bb",
  32. },
  33. Dial: xtime.Duration(1 * time.Second),
  34. Timeout: xtime.Duration(1 * time.Second),
  35. }
  36. errBucket = errors.New("bucket is empty")
  37. errFile = errors.New("file is empty")
  38. )
  39. // Config bfs upload config
  40. type Config struct {
  41. Host string
  42. HTTPClient *xhttp.ClientConfig
  43. }
  44. // Request bfs upload request
  45. type Request struct {
  46. Bucket string
  47. Dir string
  48. ContentType string
  49. Filename string
  50. File []byte
  51. WMKey string
  52. WMText string
  53. WMPaddingX uint32
  54. WMPaddingY uint32
  55. WMScale float64
  56. }
  57. // verify verify bfs request.
  58. func (r *Request) verify() error {
  59. if r.Bucket == "" {
  60. return errBucket
  61. }
  62. if r.File == nil || len(r.File) == 0 {
  63. return errFile
  64. }
  65. return nil
  66. }
  67. // BFS bfs instance
  68. type BFS struct {
  69. conf *Config
  70. client *xhttp.Client
  71. }
  72. // New new a bfs client.
  73. func New(c *Config) *BFS {
  74. if c == nil {
  75. c = &Config{
  76. Host: _defaultHost,
  77. HTTPClient: _defaultHTTPClientConfig,
  78. }
  79. }
  80. return &BFS{
  81. conf: c,
  82. client: xhttp.NewClient(c.HTTPClient),
  83. }
  84. }
  85. // Upload bfs internal upload.
  86. func (b *BFS) Upload(ctx context.Context, req *Request) (location string, err error) {
  87. var (
  88. buf = &bytes.Buffer{}
  89. bw io.Writer
  90. request *http.Request
  91. response *struct {
  92. Code int `json:"code"`
  93. Data struct {
  94. ETag string `json:"etag"`
  95. Location string `json:"location"`
  96. } `json:"data"`
  97. Message string `json:"message"`
  98. }
  99. url = b.conf.Host + _pathUpload + "?" + b.sign()
  100. )
  101. if err = req.verify(); err != nil {
  102. return
  103. }
  104. w := multipart.NewWriter(buf)
  105. if bw, err = w.CreateFormFile("file", "bfs-upload"); err != nil {
  106. return
  107. }
  108. if _, err = bw.Write(req.File); err != nil {
  109. return
  110. }
  111. w.WriteField("bucket", req.Bucket)
  112. if req.Filename != "" {
  113. w.WriteField("file_name", req.Filename)
  114. }
  115. if req.Dir != "" {
  116. w.WriteField("dir", req.Dir)
  117. }
  118. if req.WMText != "" {
  119. w.WriteField("wm_text", req.WMKey)
  120. }
  121. if req.WMKey != "" {
  122. w.WriteField("wm_key", req.WMKey)
  123. }
  124. if req.WMPaddingX > 0 {
  125. w.WriteField("wm_padding_x", fmt.Sprint(req.WMPaddingX))
  126. }
  127. if req.WMPaddingY > 0 {
  128. w.WriteField("wm_padding_y", fmt.Sprint(req.WMPaddingY))
  129. }
  130. if req.WMScale > 0 {
  131. w.WriteField("wm_scale", strconv.FormatFloat(req.WMScale, 'f', 2, 64))
  132. }
  133. if req.ContentType != "" {
  134. w.WriteField("content_type", req.ContentType)
  135. }
  136. if err = w.Close(); err != nil {
  137. return
  138. }
  139. if request, err = http.NewRequest(http.MethodPost, url, buf); err != nil {
  140. return
  141. }
  142. request.Header.Set("Content-Type", w.FormDataContentType())
  143. if err = b.client.Do(ctx, request, &response); err != nil {
  144. return
  145. }
  146. if !ecode.Int(response.Code).Equal(ecode.OK) {
  147. err = ecode.Int(response.Code)
  148. return
  149. }
  150. location = response.Data.Location
  151. return
  152. }
  153. // GenWatermark create watermark image by key and text.
  154. func (b *BFS) GenWatermark(ctx context.Context, uploadKey, wmKey, wmText string, vertical bool, distance int) (location string, err error) {
  155. var (
  156. params = url.Values{}
  157. uri = b.conf.Host + _pathGenWatermark
  158. response *struct {
  159. Code int `json:"code"`
  160. Data struct {
  161. Location string `json:"location"`
  162. Width int `json:"width"`
  163. Height int `json:"height"`
  164. MD5 string `json:"md5"`
  165. } `json:"data"`
  166. Message string `json:"message"`
  167. }
  168. )
  169. params.Set("upload_key", uploadKey)
  170. params.Set("wm_key", wmKey)
  171. params.Set("wm_text", wmText)
  172. params.Set("wm_vertical", fmt.Sprint(vertical))
  173. params.Set("distance", fmt.Sprint(distance))
  174. if err = b.client.Post(ctx, uri, "", params, &response); err != nil {
  175. return
  176. }
  177. if !ecode.Int(response.Code).Equal(ecode.OK) {
  178. err = ecode.Int(response.Code)
  179. return
  180. }
  181. location = response.Data.Location
  182. return
  183. }
  184. // sign calc appkey and appsecret sign.
  185. func (b *BFS) sign() string {
  186. key := b.conf.HTTPClient.Key
  187. secret := b.conf.HTTPClient.Secret
  188. params := url.Values{}
  189. params.Set(_appKey, key)
  190. params.Set(_ts, strconv.FormatInt(time.Now().Unix(), 10))
  191. tmp := params.Encode()
  192. if strings.IndexByte(tmp, '+') > -1 {
  193. tmp = strings.Replace(tmp, "+", "%20", -1)
  194. }
  195. var buf bytes.Buffer
  196. buf.WriteString(tmp)
  197. buf.WriteString(secret)
  198. mh := md5.Sum(buf.Bytes())
  199. // query
  200. var qb bytes.Buffer
  201. qb.WriteString(tmp)
  202. qb.WriteString("&sign=")
  203. qb.WriteString(hex.EncodeToString(mh[:]))
  204. return qb.String()
  205. }