mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-04 13:52:26 +08:00
使用github的七牛SDK,配置名称Kodo->Qiniu
This commit is contained in:
4
back.go
4
back.go
@@ -64,7 +64,7 @@ func HandleLoginPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
|
||||
logd.Printf("账号或密码错误 %s, %s", user, pwd)
|
||||
logd.Printf("账号或密码错误 %s, %s\n", user, pwd)
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func HandleLoginPost(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetBack() gin.H {
|
||||
return gin.H{"Author": Ei.Username, "Kodo": setting.Conf.Kodo}
|
||||
return gin.H{"Author": Ei.Username, "Qiniu": setting.Conf.Qiniu}
|
||||
}
|
||||
|
||||
// 个人配置
|
||||
|
||||
@@ -53,8 +53,8 @@ google:
|
||||
v: "1"
|
||||
t: pageview
|
||||
# 七牛CDN
|
||||
kodo:
|
||||
name: eiblog
|
||||
qiniu:
|
||||
bucket: eiblog
|
||||
domain: st.deepzz.com
|
||||
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||
|
||||
@@ -6,7 +6,7 @@ $ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.
|
||||
|
||||
2、如果有幸你也是 `Gopher`,相信你会亲自动手,你可以通过:
|
||||
``` sh
|
||||
$ go get -u https://github.com/eiblog/eiblog
|
||||
$ git clone https://github.com/eiblog/eiblog.git
|
||||
```
|
||||
进行源码编译二进制文件运行。
|
||||
|
||||
|
||||
2
front.go
2
front.go
@@ -86,7 +86,7 @@ func GetBase() gin.H {
|
||||
"BTitle": Ei.BTitle,
|
||||
"BeiAn": Ei.BeiAn,
|
||||
"Domain": setting.Conf.Mode.Domain,
|
||||
"Kodo": setting.Conf.Kodo,
|
||||
"Qiniu": setting.Conf.Qiniu,
|
||||
"Disqus": setting.Conf.Disqus,
|
||||
}
|
||||
}
|
||||
|
||||
39
glide.lock
generated
39
glide.lock
generated
@@ -1,5 +1,5 @@
|
||||
hash: bd360fa297ed66950543990f9433cdcdf13c29dd99d9a01b49027e236b2cb9da
|
||||
updated: 2017-07-13T01:29:28.226895963+08:00
|
||||
hash: c733fa4abeda21b59b001578b37a168bd33038d337b61198cc5fd94be8bfdf77
|
||||
updated: 2017-11-05T12:08:01.167405372+08:00
|
||||
imports:
|
||||
- name: github.com/boj/redistore
|
||||
version: 4562487a4bee9a7c272b72bfaeda4917d0a47ab9
|
||||
@@ -15,14 +15,14 @@ imports:
|
||||
- tmpl
|
||||
- uuid
|
||||
- name: github.com/garyburd/redigo
|
||||
version: 9f3a0116c9f72c5a56f958206a43dc881b502c37
|
||||
version: 47dc60e71eed504e3ef8e77ee3c6fe720f3be57f
|
||||
subpackages:
|
||||
- internal
|
||||
- redis
|
||||
- name: github.com/gin-gonic/autotls
|
||||
version: 9261e1c52a0eb595c531ff77c06cdfb6fdb111a4
|
||||
version: 8ca25fbde72bb72a00466215b94b489c71fcb815
|
||||
- name: github.com/gin-gonic/contrib
|
||||
version: d4fc5a96cc0d29cb0e862bb1312dd6f4fedfcaee
|
||||
version: 5aa1e38d1d932e45fa5032bd1b8739e1a548e596
|
||||
subpackages:
|
||||
- sessions
|
||||
- name: github.com/gin-gonic/gin
|
||||
@@ -39,32 +39,29 @@ imports:
|
||||
- name: github.com/gorilla/securecookie
|
||||
version: e59506cc896acb7f7bf732d4fdf5e25f7ccd8983
|
||||
- name: github.com/gorilla/sessions
|
||||
version: 8b6b4cd75f07f7ee036eb37b8127bd40ab1efc49
|
||||
version: a3acf13e802c358d65f249324d14ed24aac11370
|
||||
- name: github.com/manucorporat/sse
|
||||
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
|
||||
version: a5cdd64afdee435007ee3e9f6ed4684af949d568
|
||||
- name: github.com/qiniu/api.v7
|
||||
version: 9c12a67868f8f94d6a75dd6bb59b095db8d40d77
|
||||
version: b7c7d6a2ce0aff8e5e7d14c39c3cde867efa1123
|
||||
subpackages:
|
||||
- api
|
||||
- auth/qbox
|
||||
- conf
|
||||
- kodocli
|
||||
- storage
|
||||
- name: github.com/qiniu/x
|
||||
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
|
||||
subpackages:
|
||||
- bytes.v7
|
||||
- bytes.v7/seekable
|
||||
- ctype.v7
|
||||
- log.v7
|
||||
- rpc.v7
|
||||
- url.v7
|
||||
- xlog.v7
|
||||
- name: github.com/shurcooL/sanitized_anchor_name
|
||||
version: 541ff5ee47f1dddf6a5281af78307d921524bcb5
|
||||
version: 86672fcb3f950f35f2e675df2240550f2a50762f
|
||||
- name: golang.org/x/crypto
|
||||
version: dd85ac7e6a88fc6ca420478e934de5f1a42dd3c6
|
||||
version: bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8
|
||||
subpackages:
|
||||
- acme
|
||||
- acme/autocert
|
||||
@@ -73,7 +70,7 @@ imports:
|
||||
subpackages:
|
||||
- context
|
||||
- name: golang.org/x/sys
|
||||
version: abf9c25f54453410d0c6668e519582a9e1115027
|
||||
version: 8eb05f94d449fdf134ec24630ce69ada5b469c1c
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/go-playground/validator.v8
|
||||
@@ -86,19 +83,13 @@ imports:
|
||||
- internal/sasl
|
||||
- internal/scram
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 1be3d31502d6eabc0dd7ce5b0daab022e14a5538
|
||||
- name: qiniupkg.com/api.v7
|
||||
version: 9c12a67868f8f94d6a75dd6bb59b095db8d40d77
|
||||
subpackages:
|
||||
- kodo
|
||||
- kodocli
|
||||
version: eb3733d160e74a9c7e442f435eb3bea458e1d19f
|
||||
- name: qiniupkg.com/x
|
||||
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
|
||||
version: 946c4a16076d6d98aeb78619e2bd4012357f7228
|
||||
subpackages:
|
||||
- bytes.v7
|
||||
- log.v7
|
||||
- reqid.v7
|
||||
- url.v7
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
@@ -109,6 +100,6 @@ testImports:
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/stretchr/testify
|
||||
version: f390dcf405f7b83c997eac1b06768bb9f44dec18
|
||||
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
||||
subpackages:
|
||||
- assert
|
||||
|
||||
16
glide.yaml
16
glide.yaml
@@ -1,5 +1,6 @@
|
||||
package: github.com/eiblog/eiblog
|
||||
import:
|
||||
- package: github.com/deepzz0/logd
|
||||
- package: github.com/eiblog/blackfriday
|
||||
- package: github.com/eiblog/utils
|
||||
subpackages:
|
||||
@@ -7,19 +8,20 @@ import:
|
||||
- mgo
|
||||
- tmpl
|
||||
- uuid
|
||||
- package: github.com/gin-gonic/autotls
|
||||
- package: github.com/gin-gonic/contrib
|
||||
subpackages:
|
||||
- sessions
|
||||
- package: github.com/gin-gonic/gin
|
||||
version: ~1.1.4
|
||||
- package: github.com/qiniu/api.v7
|
||||
subpackages:
|
||||
- auth/qbox
|
||||
- storage
|
||||
- package: gopkg.in/mgo.v2
|
||||
subpackages:
|
||||
- bson
|
||||
- package: gopkg.in/yaml.v2
|
||||
- package: qiniupkg.com/api.v7
|
||||
testImport:
|
||||
- package: github.com/stretchr/testify
|
||||
subpackages:
|
||||
- kodo
|
||||
- kodocli
|
||||
- package: qiniupkg.com/x
|
||||
subpackages:
|
||||
- url.v7
|
||||
- assert
|
||||
|
||||
74
qiniu.go
74
qiniu.go
@@ -4,20 +4,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/eiblog/eiblog/setting"
|
||||
"qiniupkg.com/api.v7/kodo"
|
||||
"qiniupkg.com/api.v7/kodocli"
|
||||
url "qiniupkg.com/x/url.v7"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var qiniu_cfg = &kodo.Config{
|
||||
AccessKey: setting.Conf.Kodo.AccessKey,
|
||||
SecretKey: setting.Conf.Kodo.SecretKey,
|
||||
Scheme: "https",
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
name string
|
||||
domain string
|
||||
@@ -34,7 +28,7 @@ type PutRet struct {
|
||||
func onProgress(fsize, uploaded int64) {
|
||||
d := int(float64(uploaded) / float64(fsize) * 100)
|
||||
if fsize == uploaded {
|
||||
fmt.Printf("\rUpload completed! ")
|
||||
fmt.Printf("\rUpload completed! \n")
|
||||
} else {
|
||||
fmt.Printf("\r%02d%% uploaded ", int(d))
|
||||
}
|
||||
@@ -42,56 +36,60 @@ func onProgress(fsize, uploaded int64) {
|
||||
|
||||
// 上传文件
|
||||
func FileUpload(name string, size int64, data io.Reader) (string, error) {
|
||||
if setting.Conf.Kodo.AccessKey == "" || setting.Conf.Kodo.SecretKey == "" {
|
||||
if setting.Conf.Qiniu.AccessKey == "" || setting.Conf.Qiniu.SecretKey == "" {
|
||||
return "", errors.New("qiniu config error")
|
||||
}
|
||||
|
||||
// 创建一个client
|
||||
c := kodo.New(0, qiniu_cfg)
|
||||
|
||||
// 设置上传的策略
|
||||
policy := &kodo.PutPolicy{
|
||||
Scope: setting.Conf.Kodo.Name,
|
||||
Expires: 3600,
|
||||
InsertOnly: 1,
|
||||
}
|
||||
|
||||
// 生成一个上传token
|
||||
token := c.MakeUptoken(policy)
|
||||
// 构建一个uploader
|
||||
zone := 0
|
||||
uploader := kodocli.NewUploader(zone, nil)
|
||||
|
||||
key := getKey(name)
|
||||
if key == "" {
|
||||
return "", errors.New("不支持的文件类型")
|
||||
}
|
||||
|
||||
var ret PutRet
|
||||
var extra = kodocli.PutExtra{OnProgress: onProgress}
|
||||
err := uploader.Put(nil, &ret, token, key, data, size, &extra)
|
||||
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
|
||||
// 设置上传的策略
|
||||
putPolicy := &storage.PutPolicy{
|
||||
Scope: setting.Conf.Qiniu.Bucket,
|
||||
Expires: 3600,
|
||||
InsertOnly: 1,
|
||||
}
|
||||
// 上传token
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
|
||||
// 上传配置
|
||||
cfg := &storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// uploader
|
||||
uploader := storage.NewFormUploader(cfg)
|
||||
ret := new(storage.PutRet)
|
||||
putExtra := &storage.PutExtra{OnProgress: onProgress}
|
||||
err := uploader.Put(nil, ret, upToken, key, data, size, putExtra)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := "https://" + setting.Conf.Kodo.Domain + "/" + url.Escape(key)
|
||||
url := "https://" + setting.Conf.Qiniu.Domain + "/" + url.QueryEscape(key)
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
func FileDelete(name string) error {
|
||||
// new一个Bucket管理对象
|
||||
c := kodo.New(0, qiniu_cfg)
|
||||
p := c.Bucket(setting.Conf.Kodo.Name)
|
||||
|
||||
key := getKey(name)
|
||||
if key == "" {
|
||||
return errors.New("不支持的文件类型")
|
||||
}
|
||||
|
||||
// 调用Delete方法删除文件
|
||||
err := p.Delete(nil, key)
|
||||
// 打印返回值以及出错信息
|
||||
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
|
||||
// 上传配置
|
||||
cfg := &storage.Config{
|
||||
Zone: &storage.ZoneHuadong,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// manager
|
||||
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||
// Delete
|
||||
err := bucketManager.Delete(setting.Conf.Qiniu.Bucket, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ type Config struct {
|
||||
V string
|
||||
T string
|
||||
}
|
||||
Kodo struct { // 七牛CDN
|
||||
Name string
|
||||
Qiniu struct { // 七牛CDN
|
||||
Bucket string
|
||||
Domain string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
|
||||
1
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
1
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
@@ -9,6 +9,7 @@ go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
script:
|
||||
|
||||
1
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
1
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
@@ -21,6 +21,7 @@ Documentation
|
||||
|
||||
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
|
||||
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
|
||||
- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
77
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
77
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
@@ -31,7 +31,6 @@ import (
|
||||
|
||||
// conn is the low-level implementation of Conn
|
||||
type conn struct {
|
||||
|
||||
// Shared
|
||||
mu sync.Mutex
|
||||
pending int
|
||||
@@ -76,7 +75,7 @@ type dialOptions struct {
|
||||
dial func(network, addr string) (net.Conn, error)
|
||||
db int
|
||||
password string
|
||||
dialTLS bool
|
||||
useTLS bool
|
||||
skipVerify bool
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
@@ -135,14 +134,22 @@ func DialTLSConfig(c *tls.Config) DialOption {
|
||||
}}
|
||||
}
|
||||
|
||||
// DialTLSSkipVerify to disable server name verification when connecting
|
||||
// over TLS. Has no effect when not dialing a TLS connection.
|
||||
// DialTLSSkipVerify disables server name verification when connecting over
|
||||
// TLS. Has no effect when not dialing a TLS connection.
|
||||
func DialTLSSkipVerify(skip bool) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.skipVerify = skip
|
||||
}}
|
||||
}
|
||||
|
||||
// DialUseTLS specifies whether TLS should be used when connecting to the
|
||||
// server. This option is ignore by DialURL.
|
||||
func DialUseTLS(useTLS bool) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.useTLS = useTLS
|
||||
}}
|
||||
}
|
||||
|
||||
// Dial connects to the Redis server at the given network and
|
||||
// address using the specified options.
|
||||
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||
@@ -158,7 +165,7 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if do.dialTLS {
|
||||
if do.useTLS {
|
||||
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
|
||||
if tlsConfig.ServerName == "" {
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
@@ -202,10 +209,6 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func dialTLS(do *dialOptions) {
|
||||
do.dialTLS = true
|
||||
}
|
||||
|
||||
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
||||
|
||||
// DialURL connects to a Redis server at the given URL using the Redis
|
||||
@@ -257,9 +260,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
|
||||
if u.Scheme == "rediss" {
|
||||
options = append([]DialOption{{dialTLS}}, options...)
|
||||
}
|
||||
options = append(options, DialUseTLS(u.Scheme == "rediss"))
|
||||
|
||||
return Dial("tcp", address, options...)
|
||||
}
|
||||
@@ -344,44 +345,56 @@ func (c *conn) writeFloat64(n float64) error {
|
||||
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
||||
}
|
||||
|
||||
func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
|
||||
func (c *conn) writeCommand(cmd string, args []interface{}) error {
|
||||
c.writeLen('*', 1+len(args))
|
||||
err = c.writeString(cmd)
|
||||
for _, arg := range args {
|
||||
if err != nil {
|
||||
break
|
||||
if err := c.writeString(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, arg := range args {
|
||||
if err := c.writeArg(arg, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
|
||||
switch arg := arg.(type) {
|
||||
case string:
|
||||
err = c.writeString(arg)
|
||||
return c.writeString(arg)
|
||||
case []byte:
|
||||
err = c.writeBytes(arg)
|
||||
return c.writeBytes(arg)
|
||||
case int:
|
||||
err = c.writeInt64(int64(arg))
|
||||
return c.writeInt64(int64(arg))
|
||||
case int64:
|
||||
err = c.writeInt64(arg)
|
||||
return c.writeInt64(arg)
|
||||
case float64:
|
||||
err = c.writeFloat64(arg)
|
||||
return c.writeFloat64(arg)
|
||||
case bool:
|
||||
if arg {
|
||||
err = c.writeString("1")
|
||||
return c.writeString("1")
|
||||
} else {
|
||||
err = c.writeString("0")
|
||||
return c.writeString("0")
|
||||
}
|
||||
case nil:
|
||||
err = c.writeString("")
|
||||
return c.writeString("")
|
||||
case Argument:
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg.RedisArg())
|
||||
err = c.writeBytes(buf.Bytes())
|
||||
default:
|
||||
if argumentTypeOK {
|
||||
return c.writeArg(arg.RedisArg(), false)
|
||||
}
|
||||
// See comment in default clause below.
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg)
|
||||
err = c.writeBytes(buf.Bytes())
|
||||
return c.writeBytes(buf.Bytes())
|
||||
default:
|
||||
// This default clause is intended to handle builtin numeric types.
|
||||
// The function should return an error for other types, but this is not
|
||||
// done for compatibility with previous versions of the package.
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg)
|
||||
return c.writeBytes(buf.Bytes())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type protocolError string
|
||||
|
||||
|
||||
199
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
199
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
@@ -16,6 +16,9 @@ package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
@@ -40,9 +43,34 @@ func (*testConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
func dialTestConn(r io.Reader, w io.Writer) redis.DialOption {
|
||||
return redis.DialNetDial(func(net, addr string) (net.Conn, error) {
|
||||
return &testConn{Reader: r, Writer: w}, nil
|
||||
func dialTestConn(r string, w io.Writer) redis.DialOption {
|
||||
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
|
||||
return &testConn{Reader: strings.NewReader(r), Writer: w}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type tlsTestConn struct {
|
||||
net.Conn
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (c *tlsTestConn) Close() error {
|
||||
c.Conn.Close()
|
||||
<-c.done
|
||||
return nil
|
||||
}
|
||||
|
||||
func dialTestConnTLS(r string, w io.Writer) redis.DialOption {
|
||||
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
|
||||
client, server := net.Pipe()
|
||||
tlsServer := tls.Server(server, &serverTLSConfig)
|
||||
go io.Copy(tlsServer, strings.NewReader(r))
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(w, tlsServer)
|
||||
close(done)
|
||||
}()
|
||||
return &tlsTestConn{Conn: client, done: done}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,6 +82,10 @@ func (t durationArg) RedisArg() interface{} {
|
||||
return t.Seconds()
|
||||
}
|
||||
|
||||
type recursiveArg int
|
||||
|
||||
func (v recursiveArg) RedisArg() interface{} { return v }
|
||||
|
||||
var writeTests = []struct {
|
||||
args []interface{}
|
||||
expected string
|
||||
@@ -94,6 +126,10 @@ var writeTests = []struct {
|
||||
[]interface{}{"SET", "key", durationArg{time.Minute}},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", recursiveArg(123)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"ECHO", true, false},
|
||||
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
|
||||
@@ -103,7 +139,7 @@ var writeTests = []struct {
|
||||
func TestWrite(t *testing.T) {
|
||||
for _, tt := range writeTests {
|
||||
var buf bytes.Buffer
|
||||
c, _ := redis.Dial("", "", dialTestConn(nil, &buf))
|
||||
c, _ := redis.Dial("", "", dialTestConn("", &buf))
|
||||
err := c.Send(tt.args[0].(string), tt.args[1:]...)
|
||||
if err != nil {
|
||||
t.Errorf("Send(%v) returned error %v", tt.args, err)
|
||||
@@ -202,7 +238,7 @@ var readTests = []struct {
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
for _, tt := range readTests {
|
||||
c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil))
|
||||
c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil))
|
||||
actual, err := c.Receive()
|
||||
if tt.expected == errorSentinel {
|
||||
if err == nil {
|
||||
@@ -514,41 +550,85 @@ func TestDialURLHost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLPassword(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
_, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
var dialURLTests = []struct {
|
||||
description string
|
||||
url string
|
||||
r string
|
||||
w string
|
||||
}{
|
||||
{"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
|
||||
{"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"},
|
||||
{"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"},
|
||||
{"no database", "redis://localhost/", "+OK\r\n", ""},
|
||||
}
|
||||
expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"
|
||||
|
||||
func TestDialURL(t *testing.T) {
|
||||
for _, tt := range dialURLTests {
|
||||
var buf bytes.Buffer
|
||||
// UseTLS should be ignored in all of these tests.
|
||||
_, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true))
|
||||
if err != nil {
|
||||
t.Errorf("%s dial error: %v", tt.description, err)
|
||||
continue
|
||||
}
|
||||
if w := buf.String(); w != tt.w {
|
||||
t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {
|
||||
resp, err := c.Do("PING")
|
||||
if err != nil {
|
||||
t.Fatal("ping error:", err)
|
||||
}
|
||||
// Close connection to ensure that writes to buf are complete.
|
||||
c.Close()
|
||||
expected := "*1\r\n$4\r\nPING\r\n"
|
||||
actual := buf.String()
|
||||
if actual != expected {
|
||||
t.Errorf("commands = %q, want %q", actual, expected)
|
||||
}
|
||||
if resp != "PONG" {
|
||||
t.Errorf("resp = %v, want %v", resp, "PONG")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLDatabase(t *testing.T) {
|
||||
var buf3 bytes.Buffer
|
||||
_, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3))
|
||||
if err3 != nil {
|
||||
t.Error("dial error:", err3)
|
||||
const pingResponse = "+PONG\r\n"
|
||||
|
||||
func TestDialURLTLS(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
c, err := redis.DialURL("rediss://example.com/",
|
||||
redis.DialTLSConfig(&clientTLSConfig),
|
||||
dialTestConnTLS(pingResponse, &buf))
|
||||
if err != nil {
|
||||
t.Fatal("dial error:", err)
|
||||
}
|
||||
expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"
|
||||
actual3 := buf3.String()
|
||||
if actual3 != expected3 {
|
||||
t.Errorf("commands = %q, want %q", actual3, expected3)
|
||||
checkPingPong(t, &buf, c)
|
||||
}
|
||||
// empty DB means 0
|
||||
var buf0 bytes.Buffer
|
||||
_, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0))
|
||||
if err0 != nil {
|
||||
t.Error("dial error:", err0)
|
||||
|
||||
func TestDialUseTLS(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
c, err := redis.Dial("tcp", "example.com:6379",
|
||||
redis.DialTLSConfig(&clientTLSConfig),
|
||||
dialTestConnTLS(pingResponse, &buf),
|
||||
redis.DialUseTLS(true))
|
||||
if err != nil {
|
||||
t.Fatal("dial error:", err)
|
||||
}
|
||||
expected0 := ""
|
||||
actual0 := buf0.String()
|
||||
if actual0 != expected0 {
|
||||
t.Errorf("commands = %q, want %q", actual0, expected0)
|
||||
checkPingPong(t, &buf, c)
|
||||
}
|
||||
|
||||
func TestDialTLSSKipVerify(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
c, err := redis.Dial("tcp", "example.com:6379",
|
||||
dialTestConnTLS(pingResponse, &buf),
|
||||
redis.DialTLSSkipVerify(true),
|
||||
redis.DialUseTLS(true))
|
||||
if err != nil {
|
||||
t.Fatal("dial error:", err)
|
||||
}
|
||||
checkPingPong(t, &buf, c)
|
||||
}
|
||||
|
||||
// Connect to local instance of Redis running on the default port.
|
||||
@@ -680,3 +760,64 @@ func BenchmarkDoPing(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var clientTLSConfig, serverTLSConfig tls.Config
|
||||
|
||||
func init() {
|
||||
// The certificate and key for testing TLS dial options was created
|
||||
// using the command
|
||||
//
|
||||
// go run GOROOT/src/crypto/tls/generate_cert.go \
|
||||
// --rsa-bits 1024 \
|
||||
// --host 127.0.0.1,::1,example.com --ca \
|
||||
// --start-date "Jan 1 00:00:00 1970" \
|
||||
// --duration=1000000h
|
||||
//
|
||||
// where GOROOT is the value of GOROOT reported by go env.
|
||||
localhostCert := []byte(`
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
|
||||
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
|
||||
gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+
|
||||
LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+
|
||||
JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw
|
||||
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
|
||||
AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+
|
||||
ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9
|
||||
6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt
|
||||
rrKgNsltzMk=
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
localhostKey := []byte(`
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi
|
||||
bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l
|
||||
SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB
|
||||
AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB
|
||||
Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y
|
||||
HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm
|
||||
KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw
|
||||
KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa
|
||||
m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0
|
||||
pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci
|
||||
Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH
|
||||
diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc
|
||||
Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error creating key pair: %v", err))
|
||||
}
|
||||
serverTLSConfig.Certificates = []tls.Certificate{cert}
|
||||
|
||||
certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error parsing x509 certificate: %v", err))
|
||||
}
|
||||
|
||||
clientTLSConfig.RootCAs = x509.NewCertPool()
|
||||
clientTLSConfig.RootCAs.AddCert(certificate)
|
||||
}
|
||||
|
||||
4
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
4
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
@@ -38,7 +38,7 @@
|
||||
//
|
||||
// n, err := conn.Do("APPEND", "key", "value")
|
||||
//
|
||||
// The Do method converts command arguments to binary strings for transmission
|
||||
// The Do method converts command arguments to bulk strings for transmission
|
||||
// to the server as follows:
|
||||
//
|
||||
// Go Type Conversion
|
||||
@@ -48,7 +48,7 @@
|
||||
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
||||
// bool true -> "1", false -> "0"
|
||||
// nil ""
|
||||
// all other types fmt.Print(v)
|
||||
// all other types fmt.Fprint(w, v)
|
||||
//
|
||||
// Redis command reply types are represented using the following Go types:
|
||||
//
|
||||
|
||||
22
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
22
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
@@ -115,7 +115,6 @@ var (
|
||||
// }
|
||||
//
|
||||
type Pool struct {
|
||||
|
||||
// Dial is an application supplied function for creating and configuring a
|
||||
// connection.
|
||||
//
|
||||
@@ -181,6 +180,26 @@ func (p *Pool) Get() Conn {
|
||||
return &pooledConnection{p: p, c: c}
|
||||
}
|
||||
|
||||
// PoolStats contains pool statistics.
|
||||
type PoolStats struct {
|
||||
// ActiveCount is the number of connections in the pool. The count includes idle connections and connections in use.
|
||||
ActiveCount int
|
||||
// IdleCount is the number of idle connections in the pool.
|
||||
IdleCount int
|
||||
}
|
||||
|
||||
// Stats returns pool's statistics.
|
||||
func (p *Pool) Stats() PoolStats {
|
||||
p.mu.Lock()
|
||||
stats := PoolStats{
|
||||
ActiveCount: p.active,
|
||||
IdleCount: p.idle.Len(),
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// ActiveCount returns the number of connections in the pool. The count includes idle connections and connections in use.
|
||||
func (p *Pool) ActiveCount() int {
|
||||
p.mu.Lock()
|
||||
@@ -249,7 +268,6 @@ func (p *Pool) get() (Conn, error) {
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
// Get idle connection.
|
||||
|
||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
||||
|
||||
11
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
11
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
@@ -92,12 +92,15 @@ func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse in
|
||||
d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
|
||||
}
|
||||
|
||||
if active := p.ActiveCount(); active != open {
|
||||
d.t.Errorf("%s: active=%d, want %d", message, active, open)
|
||||
stats := p.Stats()
|
||||
|
||||
if stats.ActiveCount != open {
|
||||
d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open)
|
||||
}
|
||||
if idle := p.IdleCount(); idle != open-inuse {
|
||||
d.t.Errorf("%s: idle=%d, want %d", message, idle, open-inuse)
|
||||
if stats.IdleCount != open-inuse {
|
||||
d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse)
|
||||
}
|
||||
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
6
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
6
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
@@ -18,7 +18,6 @@ import "errors"
|
||||
|
||||
// Subscription represents a subscribe or unsubscribe notification.
|
||||
type Subscription struct {
|
||||
|
||||
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
||||
Kind string
|
||||
|
||||
@@ -31,7 +30,6 @@ type Subscription struct {
|
||||
|
||||
// Message represents a message notification.
|
||||
type Message struct {
|
||||
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
@@ -41,7 +39,6 @@ type Message struct {
|
||||
|
||||
// PMessage represents a pmessage notification.
|
||||
type PMessage struct {
|
||||
|
||||
// The matched pattern.
|
||||
Pattern string
|
||||
|
||||
@@ -94,6 +91,9 @@ func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
||||
}
|
||||
|
||||
// Ping sends a PING to the server with the specified data.
|
||||
//
|
||||
// The connection must be subscribed to at least one channel or pattern when
|
||||
// calling this method.
|
||||
func (c PubSubConn) Ping(data string) error {
|
||||
c.Conn.Send("PING", data)
|
||||
return c.Conn.Flush()
|
||||
|
||||
16
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
16
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
@@ -40,18 +40,20 @@ type Conn interface {
|
||||
Receive() (reply interface{}, err error)
|
||||
}
|
||||
|
||||
// Argument is implemented by types which want to control how their value is
|
||||
// interpreted when used as an argument to a redis command.
|
||||
// Argument is the interface implemented by an object which wants to control how
|
||||
// the object is converted to Redis bulk strings.
|
||||
type Argument interface {
|
||||
// RedisArg returns the interface that represents the value to be used
|
||||
// in redis commands.
|
||||
// RedisArg returns a value to be encoded as a bulk string per the
|
||||
// conversions listed in the section 'Executing Commands'.
|
||||
// Implementations should typically return a []byte or string.
|
||||
RedisArg() interface{}
|
||||
}
|
||||
|
||||
// Scanner is implemented by types which want to control how their value is
|
||||
// interpreted when read from redis.
|
||||
// Scanner is implemented by an object which wants to control its value is
|
||||
// interpreted when read from Redis.
|
||||
type Scanner interface {
|
||||
// RedisScan assigns a value from a redis value.
|
||||
// RedisScan assigns a value from a Redis value. The argument src is one of
|
||||
// the reply types listed in the section `Executing Commands`.
|
||||
//
|
||||
// An error should be returned if the value cannot be stored without
|
||||
// loss of information.
|
||||
|
||||
156
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
156
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
@@ -243,34 +243,67 @@ func Values(reply interface{}, err error) ([]interface{}, error) {
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
||||
}
|
||||
|
||||
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
makeSlice(len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
if err := assign(i, reply[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case nil:
|
||||
return ErrNil
|
||||
case Error:
|
||||
return reply
|
||||
}
|
||||
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
|
||||
}
|
||||
|
||||
// Float64s is a helper that converts an array command reply to a []float64. If
|
||||
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
|
||||
// converted to 0 in the output slice. Floats64 returns an error if an array
|
||||
// item is not a bulk string or nil.
|
||||
func Float64s(reply interface{}, err error) ([]float64, error) {
|
||||
var result []float64
|
||||
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
|
||||
p, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
|
||||
}
|
||||
f, err := strconv.ParseFloat(string(p), 64)
|
||||
result[i] = f
|
||||
return err
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Strings is a helper that converts an array command reply to a []string. If
|
||||
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
||||
// converted to "" in the output slice. Strings returns an error if an array
|
||||
// item is not a bulk string or nil.
|
||||
func Strings(reply interface{}, err error) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var result []string
|
||||
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
result[i] = v
|
||||
return nil
|
||||
case []byte:
|
||||
result[i] = string(v)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
result := make([]string, len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
p, ok := reply[i].([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
|
||||
}
|
||||
result[i] = string(p)
|
||||
}
|
||||
return result, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
||||
@@ -278,43 +311,64 @@ func Strings(reply interface{}, err error) ([]string, error) {
|
||||
// items are stay nil. ByteSlices returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
result := make([][]byte, len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
p, ok := reply[i].([]byte)
|
||||
var result [][]byte
|
||||
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
|
||||
p, ok := v.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i])
|
||||
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
|
||||
}
|
||||
result[i] = p
|
||||
}
|
||||
return result, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ints is a helper that converts an array command reply to a []int. If
|
||||
// err is not equal to nil, then Ints returns nil, err.
|
||||
// Int64s is a helper that converts an array command reply to a []int64.
|
||||
// If err is not equal to nil, then Int64s returns nil, err. Nil array
|
||||
// items are stay nil. Int64s returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func Int64s(reply interface{}, err error) ([]int64, error) {
|
||||
var result []int64
|
||||
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case int64:
|
||||
result[i] = v
|
||||
return nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(v), 10, 64)
|
||||
result[i] = n
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
|
||||
}
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ints is a helper that converts an array command reply to a []in.
|
||||
// If err is not equal to nil, then Ints returns nil, err. Nil array
|
||||
// items are stay nil. Ints returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func Ints(reply interface{}, err error) ([]int, error) {
|
||||
var ints []int
|
||||
values, err := Values(reply, err)
|
||||
if err != nil {
|
||||
return ints, err
|
||||
var result []int
|
||||
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case int64:
|
||||
n := int(v)
|
||||
if int64(n) != v {
|
||||
return strconv.ErrRange
|
||||
}
|
||||
if err := ScanSlice(values, &ints); err != nil {
|
||||
return ints, err
|
||||
result[i] = n
|
||||
return nil
|
||||
case []byte:
|
||||
n, err := strconv.Atoi(string(v))
|
||||
result[i] = n
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
|
||||
}
|
||||
return ints, nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// StringMap is a helper that converts an array of strings (alternating key, value)
|
||||
|
||||
36
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
36
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
@@ -37,24 +37,44 @@ var replyTests = []struct {
|
||||
expected valueError
|
||||
}{
|
||||
{
|
||||
"ints([v1, v2])",
|
||||
"ints([[]byte, []byte])",
|
||||
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
||||
ve([]int{4, 5}, nil),
|
||||
},
|
||||
{
|
||||
"ints([nt64, int64])",
|
||||
ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),
|
||||
ve([]int{4, 5}, nil),
|
||||
},
|
||||
{
|
||||
"ints([[]byte, nil, []byte])",
|
||||
ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)),
|
||||
ve([]int{4, 0, 5}, nil),
|
||||
},
|
||||
{
|
||||
"ints(nil)",
|
||||
ve(redis.Ints(nil, nil)),
|
||||
ve([]int(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"strings([v1, v2])",
|
||||
"int64s([[]byte, []byte])",
|
||||
ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
||||
ve([]int64{4, 5}, nil),
|
||||
},
|
||||
{
|
||||
"int64s([int64, int64])",
|
||||
ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),
|
||||
ve([]int64{4, 5}, nil),
|
||||
},
|
||||
{
|
||||
"strings([[]byte, []bytev2])",
|
||||
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([]string{"v1", "v2"}, nil),
|
||||
},
|
||||
{
|
||||
"strings(nil)",
|
||||
ve(redis.Strings(nil, nil)),
|
||||
ve([]string(nil), redis.ErrNil),
|
||||
"strings([string, string])",
|
||||
ve(redis.Strings([]interface{}{"v1", "v2"}, nil)),
|
||||
ve([]string{"v1", "v2"}, nil),
|
||||
},
|
||||
{
|
||||
"byteslices([v1, v2])",
|
||||
@@ -62,9 +82,9 @@ var replyTests = []struct {
|
||||
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
|
||||
},
|
||||
{
|
||||
"byteslices(nil)",
|
||||
ve(redis.ByteSlices(nil, nil)),
|
||||
ve([][]byte(nil), redis.ErrNil),
|
||||
"float64s([v1, v2])",
|
||||
ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)),
|
||||
ve([]float64{1.234, 5.678}, nil),
|
||||
},
|
||||
{
|
||||
"values([v1, v2])",
|
||||
|
||||
3
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
3
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
@@ -96,7 +96,8 @@ func (s *Server) watch(r io.Reader, ready chan error) {
|
||||
text = scn.Text()
|
||||
fmt.Fprintf(serverLog, "%s\n", text)
|
||||
if !listening {
|
||||
if strings.Contains(text, "The server is now ready to accept connections on port") {
|
||||
if strings.Contains(text, " * Ready to accept connections") ||
|
||||
strings.Contains(text, " * The server is now ready to accept connections on port") {
|
||||
listening = true
|
||||
ready <- nil
|
||||
}
|
||||
|
||||
1
vendor/github.com/gin-gonic/autotls/.travis.yml
generated
vendored
1
vendor/github.com/gin-gonic/autotls/.travis.yml
generated
vendored
@@ -4,6 +4,7 @@ go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- master
|
||||
|
||||
git:
|
||||
|
||||
2
vendor/github.com/gin-gonic/autotls/doc.go
generated
vendored
2
vendor/github.com/gin-gonic/autotls/doc.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
// Support Let's Encrypt for a Go server application.
|
||||
// Package autotls support Let's Encrypt for a Go server application.
|
||||
//
|
||||
// package main
|
||||
//
|
||||
|
||||
2
vendor/github.com/gin-gonic/contrib/ginrus/example/example.go
generated
vendored
2
vendor/github.com/gin-gonic/contrib/ginrus/example/example.go
generated
vendored
@@ -5,9 +5,9 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/contrib/ginrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
2
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go
generated
vendored
2
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go
generated
vendored
@@ -6,8 +6,8 @@ package ginrus
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.
|
||||
|
||||
6
vendor/github.com/gorilla/sessions/README.md
generated
vendored
6
vendor/github.com/gorilla/sessions/README.md
generated
vendored
@@ -44,14 +44,14 @@ Let's start with an example that shows the sessions API in a nutshell:
|
||||
|
||||
First we initialize a session store calling `NewCookieStore()` and passing a
|
||||
secret key used to authenticate the session. Inside the handler, we call
|
||||
`store.Get()` to retrieve an existing session or a new one. Then we set some
|
||||
session values in session.Values, which is a `map[interface{}]interface{}`.
|
||||
`store.Get()` to retrieve an existing session or create a new one. Then we set
|
||||
some session values in session.Values, which is a `map[interface{}]interface{}`.
|
||||
And finally we call `session.Save()` to save the session in the response.
|
||||
|
||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
||||
with
|
||||
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
|
||||
as or else you will leak memory! An easy way to do this is to wrap the top-level
|
||||
or else you will leak memory! An easy way to do this is to wrap the top-level
|
||||
mux when calling http.ListenAndServe:
|
||||
|
||||
```go
|
||||
|
||||
4
vendor/github.com/gorilla/sessions/store_test.go
generated
vendored
4
vendor/github.com/gorilla/sessions/store_test.go
generated
vendored
@@ -59,6 +59,10 @@ func TestGH2MaxLength(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
session, err := store.New(req, "my session")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create session", err)
|
||||
}
|
||||
|
||||
session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2))
|
||||
err = session.Save(req, w)
|
||||
if err == nil {
|
||||
|
||||
2
vendor/github.com/mattn/go-isatty/isatty_linux.go
generated
vendored
2
vendor/github.com/mattn/go-isatty/isatty_linux.go
generated
vendored
@@ -1,5 +1,5 @@
|
||||
// +build linux
|
||||
// +build !appengine
|
||||
// +build !appengine,!ppc64,!ppc64le
|
||||
|
||||
package isatty
|
||||
|
||||
|
||||
19
vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go
generated
vendored
Normal file
19
vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// +build linux
|
||||
// +build ppc64 ppc64le
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
6
vendor/github.com/qiniu/api.v7/.travis.yml
generated
vendored
6
vendor/github.com/qiniu/api.v7/.travis.yml
generated
vendored
@@ -6,14 +6,8 @@ go:
|
||||
env:
|
||||
global:
|
||||
- QINIU_KODO_TEST=1
|
||||
- QINIU_ACCESS_KEY="QWYn5TFQsLLU1pL5MFEmX3s5DmHdUThav9WyOWOm"
|
||||
- QINIU_SECRET_KEY="Bxckh6FA-Fbs9Yt3i3cbKVK22UPBmAOHJcL95pGz"
|
||||
- QINIU_TEST_BUCKET="gosdk"
|
||||
- QINIU_TEST_DOMAIN="gosdk.qiniudn.com"
|
||||
|
||||
install:
|
||||
- export QINIU_SRC=$HOME/gopath/src
|
||||
- mkdir -p $QINIU_SRC/github.com/qiniu
|
||||
- export TRAVIS_BUILD_DIR=$QINIU_SRC/github.com/qiniu/api.v7
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- go get github.com/qiniu/x
|
||||
17
vendor/github.com/qiniu/api.v7/CHANGELOG.md
generated
vendored
17
vendor/github.com/qiniu/api.v7/CHANGELOG.md
generated
vendored
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
# 7.2.3 (2017-09-25)
|
||||
* 增加Qiniu的鉴权方式
|
||||
* 删除prefop域名检测功能
|
||||
* 暴露分片上传的接口以支持复杂的自定义业务逻辑
|
||||
|
||||
## 7.2.2 (2017-09-19)
|
||||
* 为表单上传和分片上传增加代理支持
|
||||
* 优化表单上传的crc32计算方式,减少内存消耗
|
||||
* 增加网页图片的Base64上传方式
|
||||
|
||||
## 7.2.1 (2017-08-20)
|
||||
* 设置FormUpload默认支持crc32校验
|
||||
* ResumeUpload从API层面即支持crc32校验
|
||||
|
||||
## 7.2.0 (2017-07-28)
|
||||
* 重构了v7 SDK的所有代码
|
||||
|
||||
## 7.1.0 (2016-6-22)
|
||||
|
||||
### 增加
|
||||
|
||||
5
vendor/github.com/qiniu/api.v7/Makefile
generated
vendored
Normal file
5
vendor/github.com/qiniu/api.v7/Makefile
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
test:
|
||||
go test -v ./auth/...
|
||||
go test -v ./conf/...
|
||||
go test -v ./cdn/...
|
||||
go test -v ./storage/...
|
||||
15
vendor/github.com/qiniu/api.v7/README.md
generated
vendored
15
vendor/github.com/qiniu/api.v7/README.md
generated
vendored
@@ -1,7 +1,7 @@
|
||||
github.com/qiniu/api.v7 (Qiniu Go SDK v7.x)
|
||||
===============
|
||||
|
||||
[](https://travis-ci.org/qiniu/api.v7) [](https://godoc.org/github.com/qiniu/api.v7)
|
||||
[](https://travis-ci.org/qiniu/api.v7) [](https://godoc.org/github.com/qiniu/api.v7)
|
||||
|
||||
[](http://qiniu.com/)
|
||||
|
||||
@@ -10,16 +10,11 @@ github.com/qiniu/api.v7 (Qiniu Go SDK v7.x)
|
||||
```
|
||||
go get -u github.com/qiniu/api.v7
|
||||
```
|
||||
如果碰到golang.org/x/net/context 不能下载,请把 http://devtools.qiniu.com/golang.org.x.net.context.tgz 下载到代码目录下并解压到src目录,或者直接下载全部 http://devtools.qiniu.com/qiniu_api_v7.tgz。
|
||||
|
||||
# 使用文档
|
||||
# 文档
|
||||
|
||||
## KODO Blob Storage (七牛对象存储)
|
||||
[七牛SDK文档站](https://developer.qiniu.com/kodo/sdk/1238/go) 或者 [项目WIKI](https://github.com/qiniu/api.v7/wiki)
|
||||
|
||||
* [github.com/qiniu/api.v7/kodo](http://godoc.org/github.com/qiniu/api.v7/kodo)
|
||||
* [github.com/qiniu/api.v7/kodocli](http://godoc.org/github.com/qiniu/api.v7/kodocli)
|
||||
|
||||
如果您是在业务服务器(服务器端)调用七牛云存储的服务,请使用 [github.com/qiniu/api.v7/kodo](http://godoc.org/github.com/qiniu/api.v7/kodo)。
|
||||
|
||||
如果您是在客户端(比如:Android/iOS 设备、Windows/Mac/Linux 桌面环境)调用七牛云存储的服务,请使用 [github.com/qiniu/api.v7/kodocli](http://godoc.org/github.com/qiniu/api.v7/kodocli)。注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,会影响您的数据安全。
|
||||
# 示例
|
||||
|
||||
[参考代码](https://github.com/qiniu/api.v7/tree/master/examples)
|
||||
|
||||
99
vendor/github.com/qiniu/api.v7/api/api.go
generated
vendored
99
vendor/github.com/qiniu/api.v7/api/api.go
generated
vendored
@@ -1,99 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
const DefaultApiHost string = "http://uc.qbox.me"
|
||||
|
||||
var (
|
||||
bucketMu sync.RWMutex
|
||||
bucketCache = make(map[string]BucketInfo)
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*rpc.Client
|
||||
host string
|
||||
scheme string
|
||||
}
|
||||
|
||||
func NewClient(host string, scheme string) *Client {
|
||||
if host == "" {
|
||||
host = DefaultApiHost
|
||||
}
|
||||
client := rpc.DefaultClient
|
||||
return &Client{&client, host, scheme}
|
||||
}
|
||||
|
||||
type BucketInfo struct {
|
||||
UpHosts []string `json:"up"`
|
||||
IoHost string `json:"io"`
|
||||
Expire int64 `json:"expire"` // expire == 0 means no expire
|
||||
}
|
||||
|
||||
func (p *Client) GetBucketInfo(ak, bucketName string) (ret BucketInfo, err error) {
|
||||
key := ak + ":" + bucketName + ":" + p.scheme
|
||||
bucketMu.RLock()
|
||||
bucketInfo, ok := bucketCache[key]
|
||||
bucketMu.RUnlock()
|
||||
if ok && (bucketInfo.Expire == 0 || bucketInfo.Expire > time.Now().Unix()) {
|
||||
ret = bucketInfo
|
||||
return
|
||||
}
|
||||
hostInfo, err := p.bucketHosts(ak, bucketName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret.Expire = time.Now().Unix() + hostInfo.Ttl
|
||||
if p.scheme == "https" {
|
||||
ret.UpHosts = hostInfo.Https["up"]
|
||||
if iohosts, ok := hostInfo.Https["io"]; ok && len(iohosts) != 0 {
|
||||
ret.IoHost = iohosts[0]
|
||||
}
|
||||
} else {
|
||||
ret.UpHosts = hostInfo.Http["up"]
|
||||
if iohosts, ok := hostInfo.Http["io"]; ok && len(iohosts) != 0 {
|
||||
ret.IoHost = iohosts[0]
|
||||
}
|
||||
}
|
||||
bucketMu.Lock()
|
||||
bucketCache[key] = ret
|
||||
bucketMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
type HostsInfo struct {
|
||||
Ttl int64 `json:"ttl"`
|
||||
Http map[string][]string `json:"http"`
|
||||
Https map[string][]string `json:"https"`
|
||||
}
|
||||
|
||||
/*
|
||||
请求包:
|
||||
GET /v1/query?ak=<ak>&&bucket=<bucket>
|
||||
返回包:
|
||||
200 OK {
|
||||
"ttl": <ttl>, // 有效时间
|
||||
"http": {
|
||||
"up": [],
|
||||
"io": [], // 当bucket为global时,我们不需要iohost, io缺省
|
||||
},
|
||||
"https": {
|
||||
"up": [],
|
||||
"io": [], // 当bucket为global时,我们不需要iohost, io缺省
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (p *Client) bucketHosts(ak, bucket string) (info HostsInfo, err error) {
|
||||
ctx := Background()
|
||||
err = p.CallWithForm(ctx, &info, "GET", p.host+"/v1/query", map[string][]string{
|
||||
"ak": []string{ak},
|
||||
"bucket": []string{bucket},
|
||||
})
|
||||
return
|
||||
}
|
||||
2
vendor/github.com/qiniu/api.v7/auth/qbox/doc.go
generated
vendored
Normal file
2
vendor/github.com/qiniu/api.v7/auth/qbox/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// qbox 包提供了该SDK需要的相关鉴权方法
|
||||
package qbox
|
||||
173
vendor/github.com/qiniu/api.v7/auth/qbox/qbox_auth.go
generated
vendored
173
vendor/github.com/qiniu/api.v7/auth/qbox/qbox_auth.go
generated
vendored
@@ -4,61 +4,45 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
. "github.com/qiniu/api.v7/conf"
|
||||
"github.com/qiniu/x/bytes.v7/seekable"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Mac 七牛AK/SK的对象,AK/SK可以从 https://portal.qiniu.com/user/key 获取。
|
||||
type Mac struct {
|
||||
AccessKey string
|
||||
SecretKey []byte
|
||||
}
|
||||
|
||||
// NewMac 构建一个新的拥有AK/SK的对象
|
||||
func NewMac(accessKey, secretKey string) (mac *Mac) {
|
||||
|
||||
if accessKey == "" {
|
||||
accessKey, secretKey = ACCESS_KEY, SECRET_KEY
|
||||
}
|
||||
return &Mac{accessKey, []byte(secretKey)}
|
||||
}
|
||||
|
||||
// Sign 对数据进行签名,一般用于私有空间下载用途
|
||||
func (mac *Mac) Sign(data []byte) (token string) {
|
||||
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
h.Write(data)
|
||||
|
||||
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
return mac.AccessKey + ":" + sign[:27]
|
||||
return fmt.Sprintf("%s:%s", mac.AccessKey, sign)
|
||||
}
|
||||
|
||||
// SignWithData 对数据进行签名,一般用于上传凭证的生成用途
|
||||
func (mac *Mac) SignWithData(b []byte) (token string) {
|
||||
|
||||
blen := base64.URLEncoding.EncodedLen(len(b))
|
||||
|
||||
key := mac.AccessKey
|
||||
nkey := len(key)
|
||||
ret := make([]byte, nkey+30+blen)
|
||||
|
||||
base64.URLEncoding.Encode(ret[nkey+30:], b)
|
||||
|
||||
encodedData := base64.URLEncoding.EncodeToString(b)
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
h.Write(ret[nkey+30:])
|
||||
h.Write([]byte(encodedData))
|
||||
digest := h.Sum(nil)
|
||||
|
||||
copy(ret, key)
|
||||
ret[nkey] = ':'
|
||||
base64.URLEncoding.Encode(ret[nkey+1:], digest)
|
||||
ret[nkey+29] = ':'
|
||||
|
||||
return string(ret)
|
||||
sign := base64.URLEncoding.EncodeToString(digest)
|
||||
return fmt.Sprintf("%s:%s:%s", mac.AccessKey, sign, encodedData)
|
||||
}
|
||||
|
||||
func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err error) {
|
||||
|
||||
// SignRequest 对数据进行签名,一般用于管理凭证的生成
|
||||
func (mac *Mac) SignRequest(req *http.Request) (token string, err error) {
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
|
||||
u := req.URL
|
||||
@@ -68,7 +52,7 @@ func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err
|
||||
}
|
||||
io.WriteString(h, data+"\n")
|
||||
|
||||
if incbody {
|
||||
if incBody(req) {
|
||||
s2, err2 := seekable.New(req)
|
||||
if err2 != nil {
|
||||
return "", err2
|
||||
@@ -77,18 +61,74 @@ func (mac *Mac) SignRequest(req *http.Request, incbody bool) (token string, err
|
||||
}
|
||||
|
||||
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
token = mac.AccessKey + ":" + sign
|
||||
token = fmt.Sprintf("%s:%s", mac.AccessKey, sign)
|
||||
return
|
||||
}
|
||||
|
||||
func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
|
||||
// SignRequestV2 对数据进行签名,一般用于高级管理凭证的生成
|
||||
func (mac *Mac) SignRequestV2(req *http.Request) (token string, err error) {
|
||||
h := hmac.New(sha1.New, mac.SecretKey)
|
||||
|
||||
u := req.URL
|
||||
|
||||
//write method path?query
|
||||
io.WriteString(h, fmt.Sprintf("%s %s", req.Method, u.Path))
|
||||
if u.RawQuery != "" {
|
||||
io.WriteString(h, "?")
|
||||
io.WriteString(h, u.RawQuery)
|
||||
}
|
||||
|
||||
//write host and posrt
|
||||
io.WriteString(h, "\nHost: ")
|
||||
io.WriteString(h, req.Host)
|
||||
if req.URL.Port() != "" {
|
||||
io.WriteString(h, ":")
|
||||
io.WriteString(h, req.URL.Port())
|
||||
}
|
||||
|
||||
//write content type
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
if contentType != "" {
|
||||
io.WriteString(h, "\n")
|
||||
io.WriteString(h, fmt.Sprintf("Content-Type: %s", contentType))
|
||||
}
|
||||
|
||||
io.WriteString(h, "\n\n")
|
||||
|
||||
//write body
|
||||
if incBodyV2(req) {
|
||||
s2, err2 := seekable.New(req)
|
||||
if err2 != nil {
|
||||
return "", err2
|
||||
}
|
||||
h.Write(s2.Bytes())
|
||||
}
|
||||
|
||||
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
token = fmt.Sprintf("%s:%s", mac.AccessKey, sign)
|
||||
return
|
||||
}
|
||||
|
||||
// 管理凭证生成时,是否同时对request body进行签名
|
||||
func incBody(req *http.Request) bool {
|
||||
return req.Body != nil &&
|
||||
req.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
func incBodyV2(req *http.Request) bool {
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
return req.Body != nil && (contentType == "application/x-www-form-urlencoded" ||
|
||||
contentType == "application/json")
|
||||
}
|
||||
|
||||
// VerifyCallback 验证上传回调请求是否来自七牛
|
||||
func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
|
||||
auth := req.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
token, err := mac.SignRequest(req, true)
|
||||
token, err := mac.SignRequest(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -96,76 +136,17 @@ func (mac *Mac) VerifyCallback(req *http.Request) (bool, error) {
|
||||
return auth == "QBox "+token, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
// Sign 一般用于下载凭证的签名
|
||||
func Sign(mac *Mac, data []byte) string {
|
||||
|
||||
if mac == nil {
|
||||
mac = NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
}
|
||||
return mac.Sign(data)
|
||||
}
|
||||
|
||||
// SignWithData 一般用于上传凭证的签名
|
||||
func SignWithData(mac *Mac, data []byte) string {
|
||||
|
||||
if mac == nil {
|
||||
mac = NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
}
|
||||
return mac.SignWithData(data)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
type Transport struct {
|
||||
mac Mac
|
||||
Transport http.RoundTripper
|
||||
// VerifyCallback 验证上传回调请求是否来自七牛
|
||||
func VerifyCallback(mac *Mac, req *http.Request) (bool, error) {
|
||||
return mac.VerifyCallback(req)
|
||||
}
|
||||
|
||||
func incBody(req *http.Request) bool {
|
||||
|
||||
if req.Body == nil {
|
||||
return false
|
||||
}
|
||||
if ct, ok := req.Header["Content-Type"]; ok {
|
||||
switch ct[0] {
|
||||
case "application/x-www-form-urlencoded":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Transport) NestedObject() interface{} {
|
||||
|
||||
return t.Transport
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
|
||||
token, err := t.mac.SignRequest(req, incBody(req))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Authorization", "QBox "+token)
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func NewTransport(mac *Mac, transport http.RoundTripper) *Transport {
|
||||
|
||||
if mac == nil {
|
||||
mac = NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
}
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
t := &Transport{mac: *mac, Transport: transport}
|
||||
return t
|
||||
}
|
||||
|
||||
func NewClient(mac *Mac, transport http.RoundTripper) *http.Client {
|
||||
|
||||
t := NewTransport(mac, transport)
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
8
vendor/github.com/qiniu/api.v7/cdn/anti_leech.go
generated
vendored
8
vendor/github.com/qiniu/api.v7/cdn/anti_leech.go
generated
vendored
@@ -7,10 +7,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateTimestampAntileechURL 构建带时间戳防盗链的链接
|
||||
// encryptKey 七牛防盗链key
|
||||
func CreateTimestampAntileechURL(urlStr string, encryptKey string, durationInSeconds int64) (antileechURL string, err error) {
|
||||
|
||||
// CreateTimestampAntileechURL 用来构建七牛CDN时间戳防盗链的访问链接
|
||||
func CreateTimestampAntileechURL(urlStr string, encryptKey string,
|
||||
durationInSeconds int64) (antileechURL string, err error) {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -27,7 +26,6 @@ func CreateTimestampAntileechURL(urlStr string, encryptKey string, durationInSec
|
||||
if u.RawQuery == "" {
|
||||
antileechURL = u.String() + "?" + q.Encode()
|
||||
} else {
|
||||
|
||||
antileechURL = u.String() + "&" + q.Encode()
|
||||
}
|
||||
|
||||
|
||||
9
vendor/github.com/qiniu/api.v7/cdn/anti_leech_test.go
generated
vendored
9
vendor/github.com/qiniu/api.v7/cdn/anti_leech_test.go
generated
vendored
@@ -18,20 +18,21 @@ func TestCreateTimestampAntiLeech(t *testing.T) {
|
||||
{
|
||||
name: "antileech_1",
|
||||
args: args{
|
||||
urlStr: "http://www.abc.com/abc.jpg?stat",
|
||||
encryptKey: "abc",
|
||||
durationInSeconds: 20,
|
||||
urlStr: "http://www.example.com/testfile.jpg",
|
||||
encryptKey: "abc123",
|
||||
durationInSeconds: 3600,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := CreateTimestampAntileechURL(tt.args.urlStr, tt.args.encryptKey, tt.args.durationInSeconds)
|
||||
targetUrl, err := CreateTimestampAntileechURL(tt.args.urlStr, tt.args.encryptKey, tt.args.durationInSeconds)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CreateTimestampAntiLeech() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
t.Log(targetUrl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
215
vendor/github.com/qiniu/api.v7/cdn/api.go
generated
vendored
215
vendor/github.com/qiniu/api.v7/cdn/api.go
generated
vendored
@@ -3,73 +3,72 @@ package cdn
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
. "github.com/qiniu/api.v7/conf"
|
||||
)
|
||||
|
||||
// Fusion CDN服务域名
|
||||
var (
|
||||
FUSION_HOST = "http://fusion.qiniuapi.com"
|
||||
FusionHost = "http://fusion.qiniuapi.com"
|
||||
)
|
||||
|
||||
/* TrafficReqBody
|
||||
// CdnManager 提供了文件和目录刷新,文件预取,获取域名带宽和流量数据,获取域名日志列表等功能
|
||||
type CdnManager struct {
|
||||
mac *qbox.Mac
|
||||
}
|
||||
|
||||
批量查询带宽/流量 请求内容
|
||||
// NewCdnManager 用来构建一个新的 CdnManager
|
||||
func NewCdnManager(mac *qbox.Mac) *CdnManager {
|
||||
return &CdnManager{mac: mac}
|
||||
}
|
||||
|
||||
StartDate string 开始日期,例如:2016-07-01
|
||||
EndDate string 结束日期,例如:2016-07-03
|
||||
Granularity string 粒度,取值:5min / hour /day
|
||||
Domains string 域名列表,以 ;分割
|
||||
*/
|
||||
type TrafficReqBody struct {
|
||||
// TrafficReq 为批量查询带宽/流量的API请求内容
|
||||
// StartDate 开始日期,格式例如:2016-07-01
|
||||
// EndDate 结束日期,格式例如:2016-07-03
|
||||
// Granularity 取值粒度,取值可选值:5min/hour/day
|
||||
// Domains 域名列表,彼此用 ; 连接
|
||||
type TrafficReq struct {
|
||||
StartDate string `json:"startDate"`
|
||||
EndDate string `json:"endDate"`
|
||||
Granularity string `json:"granularity"`
|
||||
Domains string `json:"domains"`
|
||||
}
|
||||
|
||||
// TrafficResp
|
||||
// 带宽/流量查询响应内容
|
||||
// TrafficResp 为带宽/流量查询响应内容
|
||||
type TrafficResp struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Time []string `json:"time,omitempty"`
|
||||
Data map[string]TrafficRespData `json:"data,omitempty"`
|
||||
Data map[string]TrafficData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// TrafficRespData
|
||||
// 带宽/流量数据
|
||||
type TrafficRespData struct {
|
||||
// TrafficData 为带宽/流量数据
|
||||
type TrafficData struct {
|
||||
DomainChina []int `json:"china"`
|
||||
DomainOversea []int `json:"oversea"`
|
||||
}
|
||||
|
||||
/*
|
||||
// BandWidth
|
||||
|
||||
获取域名访问带宽数据
|
||||
http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
|
||||
|
||||
StartDate string 必须 开始日期,例如:2016-07-01
|
||||
EndDate string 必须 结束日期,例如:2016-07-03
|
||||
Granularity string 必须 粒度,取值:5min / hour /day
|
||||
Domains []string 必须 域名列表
|
||||
*/
|
||||
func GetBandWidthData(startDate, endDate, granularity string, domainList []string) (bandwidthData TrafficResp, err error) {
|
||||
|
||||
// GetBandwidthData 方法用来获取域名访问带宽数据
|
||||
// StartDate string 必须 开始日期,例如:2016-07-01
|
||||
// EndDate string 必须 结束日期,例如:2016-07-03
|
||||
// Granularity string 必须 粒度,取值:5min / hour /day
|
||||
// Domains []string 必须 域名列表
|
||||
func (m *CdnManager) GetBandwidthData(startDate, endDate, granularity string,
|
||||
domainList []string) (bandwidthData TrafficResp, err error) {
|
||||
domains := strings.Join(domainList, ";")
|
||||
reqBody := TrafficReqBody{
|
||||
reqBody := TrafficReq{
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
Granularity: granularity,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/bandwidth", reqBody)
|
||||
resData, reqErr := postRequest(m.mac, "/v2/tune/bandwidth", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
@@ -82,27 +81,22 @@ func GetBandWidthData(startDate, endDate, granularity string, domainList []strin
|
||||
return
|
||||
}
|
||||
|
||||
/* Flux
|
||||
|
||||
获取域名访问流量数据
|
||||
http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
|
||||
|
||||
StartDate string 必须 开始日期,例如:2016-07-01
|
||||
EndDate string 必须 结束日期,例如:2016-07-03
|
||||
Granularity string 必须 粒度,取值:5min / hour /day
|
||||
Domains []string 必须 域名列表
|
||||
*/
|
||||
func GetFluxData(startDate, endDate, granularity string, domainList []string) (fluxData TrafficResp, err error) {
|
||||
|
||||
// GetFluxData 方法用来获取域名访问流量数据
|
||||
// StartDate string 必须 开始日期,例如:2016-07-01
|
||||
// EndDate string 必须 结束日期,例如:2016-07-03
|
||||
// Granularity string 必须 粒度,取值:5min / hour /day
|
||||
// Domains []string 必须 域名列表
|
||||
func (m *CdnManager) GetFluxData(startDate, endDate, granularity string,
|
||||
domainList []string) (fluxData TrafficResp, err error) {
|
||||
domains := strings.Join(domainList, ";")
|
||||
reqBody := TrafficReqBody{
|
||||
reqBody := TrafficReq{
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
Granularity: granularity,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/flux", reqBody)
|
||||
resData, reqErr := postRequest(m.mac, "/v2/tune/flux", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
@@ -117,43 +111,46 @@ func GetFluxData(startDate, endDate, granularity string, domainList []string) (f
|
||||
return
|
||||
}
|
||||
|
||||
// RefreshReq
|
||||
// 缓存刷新请求内容
|
||||
// RefreshReq 为缓存刷新请求内容
|
||||
type RefreshReq struct {
|
||||
Urls []string `json:"urls"`
|
||||
Dirs []string `json:"dirs"`
|
||||
}
|
||||
|
||||
// RefreshResp
|
||||
// 缓存刷新响应内容
|
||||
// RefreshResp 缓存刷新响应内容
|
||||
type RefreshResp struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
InvalidUrls []string `json:"invalidUrls,omitempty"`
|
||||
InvalidDirs []string `json:"invalidDirs,omitempty"`
|
||||
UrlQuotaDay int `json:"urlQuotaDay,omitempty"`
|
||||
UrlSurplusDay int `json:"urlSurplusDay,omitempty"`
|
||||
URLQuotaDay int `json:"urlQuotaDay,omitempty"`
|
||||
URLSurplusDay int `json:"urlSurplusDay,omitempty"`
|
||||
DirQuotaDay int `json:"dirQuotaDay,omitempty"`
|
||||
DirSurplusDay int `json:"dirSurplusDay,omitempty"`
|
||||
}
|
||||
|
||||
/* RefreshUrlsAndDirs
|
||||
|
||||
刷新链接列表,每次最多不可以超过100条链接
|
||||
http://developer.qiniu.com/article/fusion/api/refresh.html
|
||||
|
||||
urls 要刷新的单个url列表,总数不超过100条;单个url,即一个具体的url,例如:http://bar.foo.com/index.html
|
||||
dirs 要刷新的目录url列表,总数不超过10条;目录dir,即表示一个目录级的url,例如:http://bar.foo.com/dir/,也支持在尾部使用通配符,例如:http://bar.foo.com/dir/*
|
||||
*/
|
||||
func RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
|
||||
// RefreshUrlsAndDirs 方法用来刷新文件或目录
|
||||
// urls 要刷新的单个url列表,单次方法调用总数不超过100条;单个url,即一个具体的url,
|
||||
// 例如:http://bar.foo.com/index.html
|
||||
// dirs 要刷新的目录url列表,单次方法调用总数不超过10条;目录dir,即表示一个目录级的url,
|
||||
// 例如:http://bar.foo.com/dir/,
|
||||
func (m *CdnManager) RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
|
||||
if len(urls) > 100 {
|
||||
err = errors.New("urls count exceeds the limit of 100")
|
||||
return
|
||||
}
|
||||
if len(dirs) > 10 {
|
||||
err = errors.New("dirs count exceeds the limit of 10")
|
||||
return
|
||||
}
|
||||
|
||||
reqBody := RefreshReq{
|
||||
Urls: urls,
|
||||
Dirs: dirs,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/refresh", reqBody)
|
||||
resData, reqErr := postRequest(m.mac, "/v2/tune/refresh", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
@@ -167,26 +164,22 @@ func RefreshUrlsAndDirs(urls, dirs []string) (result RefreshResp, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// RefreshUrls
|
||||
// 刷新文件
|
||||
func RefreshUrls(urls []string) (result RefreshResp, err error) {
|
||||
return RefreshUrlsAndDirs(urls, nil)
|
||||
// RefreshUrls 刷新文件
|
||||
func (m *CdnManager) RefreshUrls(urls []string) (result RefreshResp, err error) {
|
||||
return m.RefreshUrlsAndDirs(urls, nil)
|
||||
}
|
||||
|
||||
// RefreshDirs
|
||||
// 刷新目录
|
||||
func RefreshDirs(dirs []string) (result RefreshResp, err error) {
|
||||
return RefreshUrlsAndDirs(nil, dirs)
|
||||
// RefreshDirs 刷新目录
|
||||
func (m *CdnManager) RefreshDirs(dirs []string) (result RefreshResp, err error) {
|
||||
return m.RefreshUrlsAndDirs(nil, dirs)
|
||||
}
|
||||
|
||||
// PrefetchReq
|
||||
// 文件预取请求内容
|
||||
// PrefetchReq 文件预取请求内容
|
||||
type PrefetchReq struct {
|
||||
Urls []string `json:"urls"`
|
||||
}
|
||||
|
||||
// PrefetchResp
|
||||
// 文件预取响应内容
|
||||
// PrefetchResp 文件预取响应内容
|
||||
type PrefetchResp struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
@@ -196,16 +189,18 @@ type PrefetchResp struct {
|
||||
SurplusDay int `json:"surplusDay,omitempty"`
|
||||
}
|
||||
|
||||
// PrefetchUrls
|
||||
// 预取文件链接,每次最多不可以超过100条
|
||||
// http://developer.qiniu.com/article/fusion/api/prefetch.html
|
||||
func PrefetchUrls(urls []string) (result PrefetchResp, err error) {
|
||||
// PrefetchUrls 预取文件链接,每次最多不可以超过100条
|
||||
func (m *CdnManager) PrefetchUrls(urls []string) (result PrefetchResp, err error) {
|
||||
if len(urls) > 100 {
|
||||
err = errors.New("urls count exceeds the limit of 100")
|
||||
return
|
||||
}
|
||||
|
||||
reqBody := PrefetchReq{
|
||||
Urls: urls,
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest("v2/tune/prefetch", reqBody)
|
||||
resData, reqErr := postRequest(m.mac, "/v2/tune/prefetch", reqBody)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
@@ -220,12 +215,59 @@ func PrefetchUrls(urls []string) (result PrefetchResp, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// RequestWithBody
|
||||
// 带body对api发出请求并且返回response body
|
||||
func postRequest(path string, body interface{}) (resData []byte, err error) {
|
||||
// ListLogRequest 日志下载请求内容
|
||||
type ListLogRequest struct {
|
||||
Day string `json:"day"`
|
||||
Domains string `json:"domains"`
|
||||
}
|
||||
|
||||
urlStr := fmt.Sprintf("%s/%s", FUSION_HOST, path)
|
||||
// ListLogResult 日志下载相应内容
|
||||
type ListLogResult struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data map[string][]LogDomainInfo `json:"data"`
|
||||
}
|
||||
|
||||
// LogDomainInfo 日志下载信息
|
||||
type LogDomainInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
ModifiedTime int64 `json:"mtime"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// GetCdnLogList 获取CDN域名访问日志的下载链接
|
||||
func (m *CdnManager) GetCdnLogList(day string, domains []string) (
|
||||
listLogResult ListLogResult, err error) {
|
||||
//new log query request
|
||||
logReq := ListLogRequest{
|
||||
Day: day,
|
||||
Domains: strings.Join(domains, ";"),
|
||||
}
|
||||
|
||||
resData, reqErr := postRequest(m.mac, "/v2/tune/log/list", logReq)
|
||||
if reqErr != nil {
|
||||
err = fmt.Errorf("get response error, %s", reqErr)
|
||||
return
|
||||
}
|
||||
|
||||
if decodeErr := json.Unmarshal(resData, &listLogResult); decodeErr != nil {
|
||||
err = fmt.Errorf("get response error, %s", decodeErr)
|
||||
return
|
||||
}
|
||||
|
||||
if listLogResult.Error != "" {
|
||||
err = fmt.Errorf("get log list error, %d %s", listLogResult.Code, listLogResult.Error)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RequestWithBody 带body对api发出请求并且返回response body
|
||||
func postRequest(mac *qbox.Mac, path string, body interface{}) (resData []byte,
|
||||
err error) {
|
||||
urlStr := fmt.Sprintf("%s%s", FusionHost, path)
|
||||
reqData, _ := json.Marshal(body)
|
||||
req, reqErr := http.NewRequest("POST", urlStr, bytes.NewReader(reqData))
|
||||
if reqErr != nil {
|
||||
@@ -233,8 +275,7 @@ func postRequest(path string, body interface{}) (resData []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(ACCESS_KEY, SECRET_KEY)
|
||||
accessToken, signErr := mac.SignRequest(req, false)
|
||||
accessToken, signErr := mac.SignRequest(req)
|
||||
if signErr != nil {
|
||||
err = signErr
|
||||
return
|
||||
@@ -243,9 +284,9 @@ func postRequest(path string, body interface{}) (resData []byte, err error) {
|
||||
req.Header.Add("Authorization", "QBox "+accessToken)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, httpErr := http.DefaultClient.Do(req)
|
||||
if httpErr != nil {
|
||||
err = httpErr
|
||||
resp, respErr := http.DefaultClient.Do(req)
|
||||
if respErr != nil {
|
||||
err = respErr
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
254
vendor/github.com/qiniu/api.v7/cdn/api_test.go
generated
vendored
254
vendor/github.com/qiniu/api.v7/cdn/api_test.go
generated
vendored
@@ -1,75 +1,86 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/kodo"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
)
|
||||
|
||||
//global variables
|
||||
|
||||
var (
|
||||
ak = os.Getenv("QINIU_ACCESS_KEY")
|
||||
sk = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
testBucketName = os.Getenv("QINIU_TEST_BUCKET")
|
||||
|
||||
testDate = time.Now().AddDate(0, 0, -3).Format("2006-01-02")
|
||||
bucket = newBucket()
|
||||
client *kodo.Client
|
||||
testKey = "fusionTest"
|
||||
testURL string
|
||||
layout = "2006-01-02"
|
||||
now = time.Now()
|
||||
startDate = now.AddDate(0, 0, -2).Format(layout)
|
||||
endDate = now.AddDate(0, 0, -1).Format(layout)
|
||||
logDate = now.AddDate(0, 0, -1).Format(layout)
|
||||
|
||||
testUrls = []string{
|
||||
"http://gosdk.qiniudn.com/qiniu1.png",
|
||||
"http://gosdk.qiniudn.com/qiniu2.png",
|
||||
}
|
||||
testDirs = []string{
|
||||
"http://gosdk.qiniudn.com/dir1/",
|
||||
"http://gosdk.qiniudn.com/dir2/",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
kodo.SetMac(ak, sk)
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
testKey += strconv.Itoa(rand.Int())
|
||||
bucket.PutFile(nil, nil, testKey, "doc.go", nil)
|
||||
testURL = domain + "/" + testKey
|
||||
var mac *qbox.Mac
|
||||
var cdnManager *CdnManager
|
||||
|
||||
func init() {
|
||||
if ak == "" || sk == "" {
|
||||
panic("please run ./test-env.sh first")
|
||||
}
|
||||
mac = qbox.NewMac(ak, sk)
|
||||
cdnManager = NewCdnManager(mac)
|
||||
}
|
||||
|
||||
func TestGetBandWidthData(t *testing.T) {
|
||||
//TestGetBandwidthData
|
||||
func TestGetBandwidthData(t *testing.T) {
|
||||
type args struct {
|
||||
startDate string
|
||||
endDate string
|
||||
granularity string
|
||||
domainList []string
|
||||
}
|
||||
tests := []struct {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
wantTraffic TrafficResp
|
||||
wantErr bool
|
||||
wantCode int
|
||||
}{
|
||||
{
|
||||
name: "BandWidthTest_1",
|
||||
name: "CdnManager_TestGetBandwidthData",
|
||||
args: args{
|
||||
testDate,
|
||||
testDate,
|
||||
startDate,
|
||||
endDate,
|
||||
"5min",
|
||||
[]string{domain},
|
||||
},
|
||||
wantCode: 200,
|
||||
},
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := GetBandWidthData(tt.args.startDate, tt.args.endDate, tt.args.granularity, tt.args.domainList)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetBandWidthData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ret, err := cdnManager.GetBandwidthData(tc.args.startDate, tc.args.endDate,
|
||||
tc.args.granularity, tc.args.domainList)
|
||||
if err != nil || ret.Code != tc.wantCode {
|
||||
t.Errorf("GetBandwidth() error = %v, %v", err, ret.Error)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TestGetFluxData
|
||||
func TestGetFluxData(t *testing.T) {
|
||||
type args struct {
|
||||
startDate string
|
||||
@@ -77,167 +88,160 @@ func TestGetFluxData(t *testing.T) {
|
||||
granularity string
|
||||
domainList []string
|
||||
}
|
||||
tests := []struct {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
wantTraffic TrafficResp
|
||||
wantErr bool
|
||||
wantCode int
|
||||
}{
|
||||
{
|
||||
name: "BandWidthTest_1",
|
||||
name: "CdnManager_TestGetFluxData",
|
||||
args: args{
|
||||
testDate,
|
||||
testDate,
|
||||
startDate,
|
||||
endDate,
|
||||
"5min",
|
||||
[]string{domain},
|
||||
},
|
||||
wantCode: 200,
|
||||
},
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := GetFluxData(tt.args.startDate, tt.args.endDate, tt.args.granularity, tt.args.domainList)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetFluxData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ret, err := cdnManager.GetFluxData(tc.args.startDate, tc.args.endDate,
|
||||
tc.args.granularity, tc.args.domainList)
|
||||
if err != nil || ret.Code != tc.wantCode {
|
||||
t.Errorf("GetFlux() error = %v, %v", err, ret.Error)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshUrlsAndDirs(t *testing.T) {
|
||||
kodo.SetMac(ak, sk)
|
||||
|
||||
type args struct {
|
||||
urls []string
|
||||
dirs []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult RefreshResp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "refresh_test_1",
|
||||
args: args{
|
||||
urls: []string{testURL},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := RefreshUrlsAndDirs(tt.args.urls, tt.args.dirs)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RefreshUrlsAndDirs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TestRefreshUrls
|
||||
func TestRefreshUrls(t *testing.T) {
|
||||
type args struct {
|
||||
urls []string
|
||||
}
|
||||
tests := []struct {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult RefreshResp
|
||||
wantErr bool
|
||||
wantCode int
|
||||
}{
|
||||
{
|
||||
name: "refresh_test_1",
|
||||
name: "CdnManager_TestRefresUrls",
|
||||
args: args{
|
||||
urls: []string{testURL},
|
||||
urls: testUrls,
|
||||
},
|
||||
wantErr: false,
|
||||
wantCode: 200,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := RefreshUrls(tt.args.urls)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RefreshUrls() error = %v, wantErr %v", err, tt.wantErr)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ret, err := cdnManager.RefreshUrls(tc.args.urls)
|
||||
if err != nil || ret.Code != tc.wantCode {
|
||||
t.Errorf("RefreshUrls() error = %v, %v", err, ret.Error)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TestRefreshDirs
|
||||
func TestRefreshDirs(t *testing.T) {
|
||||
type args struct {
|
||||
dirs []string
|
||||
}
|
||||
tests := []struct {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult RefreshResp
|
||||
wantErr bool
|
||||
}{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotResult, err := RefreshDirs(tt.args.dirs)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RefreshDirs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
wantCode int
|
||||
}{
|
||||
{
|
||||
name: "CdnManager_TestRefreshDirs",
|
||||
args: args{
|
||||
dirs: testDirs,
|
||||
},
|
||||
wantCode: 200,
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(gotResult, tt.wantResult) {
|
||||
t.Errorf("RefreshDirs() = %v, want %v", gotResult, tt.wantResult)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ret, err := cdnManager.RefreshDirs(tc.args.dirs)
|
||||
if err != nil || ret.Code != tc.wantCode {
|
||||
if ret.Error == "refresh dir limit error" {
|
||||
t.Logf("RefreshDirs() error=%v", ret.Error)
|
||||
} else {
|
||||
t.Errorf("RefreshDirs() error = %v, %v", err, ret.Error)
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TestPrefetchUrls
|
||||
func TestPrefetchUrls(t *testing.T) {
|
||||
type args struct {
|
||||
urls []string
|
||||
}
|
||||
tests := []struct {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult PrefetchResp
|
||||
wantErr bool
|
||||
wantCode int
|
||||
}{
|
||||
{
|
||||
name: "refresh_test_1",
|
||||
name: "CdnManager_PrefetchUrls",
|
||||
args: args{
|
||||
urls: []string{testURL},
|
||||
urls: testUrls,
|
||||
},
|
||||
wantErr: false,
|
||||
wantCode: 200,
|
||||
},
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := PrefetchUrls(tt.args.urls)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PrefetchUrls() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ret, err := cdnManager.PrefetchUrls(tc.args.urls)
|
||||
if err != nil || ret.Code != tc.wantCode {
|
||||
t.Errorf("PrefetchUrls() error = %v, %v", err, ret.Error)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newBucket() (bucket kodo.Bucket) {
|
||||
|
||||
ak := os.Getenv("QINIU_ACCESS_KEY")
|
||||
sk := os.Getenv("QINIU_SECRET_KEY")
|
||||
if ak == "" || sk == "" {
|
||||
panic("require ACCESS_KEY & SECRET_KEY")
|
||||
//TestGetCdnLogList
|
||||
func TestGetCdnLogList(t *testing.T) {
|
||||
type args struct {
|
||||
date string
|
||||
domains []string
|
||||
}
|
||||
kodo.SetMac(ak, sk)
|
||||
|
||||
testBucketName = os.Getenv("QINIU_TEST_BUCKET")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
if testBucketName == "" || domain == "" {
|
||||
panic("require test env")
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "CdnManager_TestGetCdnLogList",
|
||||
args: args{
|
||||
date: logDate,
|
||||
domains: []string{domain},
|
||||
},
|
||||
},
|
||||
}
|
||||
client = kodo.NewWithoutZone(nil)
|
||||
|
||||
return client.Bucket(testBucketName)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := cdnManager.GetCdnLogList(tc.args.date, tc.args.domains)
|
||||
if err != nil {
|
||||
t.Errorf("GetCdnLogList() error = %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
26
vendor/github.com/qiniu/api.v7/cdn/doc.go
generated
vendored
26
vendor/github.com/qiniu/api.v7/cdn/doc.go
generated
vendored
@@ -1,25 +1,3 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7/cdn 提供了七牛CDN的API功能
|
||||
首先,我们要配置下 AccessKey/SecretKey,
|
||||
import "github.com/qiniu/api.v7/kodo"
|
||||
|
||||
kodo.SetMac("ak", "sk")
|
||||
设置了AccessKey/SecretKey 就可以使用cdn的各类功能
|
||||
|
||||
比如我们要生成一个带时间戳防盗链的链接:
|
||||
q :=url.Values{}// url.Values 请求参数
|
||||
link, err := cdn.CreateTimestampAntileechURL(""http://www.qiniu.com/abc/bcc/aa-s.mp4?x=2&y=3", "encryptedkey", 20)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(link)
|
||||
|
||||
又或者我们要列出CDN日志及其下载地址:
|
||||
resp, err := cdn.GetCdnLogList("2016-12-26", "x-mas.com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(resp)
|
||||
|
||||
*/
|
||||
// cdn 包提供了 Fusion CDN的常见功能。相关功能的文档参考:https://developer.qiniu.com/fusion。
|
||||
// 目前提供了文件和目录刷新,文件预取,获取域名带宽和流量数据,获取域名日志列表等功能。
|
||||
package cdn
|
||||
|
||||
92
vendor/github.com/qiniu/api.v7/cdn/logs.go
generated
vendored
92
vendor/github.com/qiniu/api.v7/cdn/logs.go
generated
vendored
@@ -1,92 +0,0 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
)
|
||||
|
||||
const (
|
||||
LIST_LOG_API = "http://fusion.qiniuapi.com/v2/tune/log/list"
|
||||
)
|
||||
|
||||
// ListLogRequest 日志下载请求内容
|
||||
type ListLogRequest struct {
|
||||
Day string `json:"day"`
|
||||
Domains string `json:"domains"`
|
||||
}
|
||||
|
||||
// ListLogResult 日志下载相应内容
|
||||
type ListLogResult struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
Data map[string][]LogDomainInfo `json:"data"`
|
||||
}
|
||||
|
||||
// LogDomainInfo 日志下载信息
|
||||
type LogDomainInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
ModifiedTime int64 `json:"mtime"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// GetCdnLogList 获取CDN域名访问日志的下载链接
|
||||
// http://developer.qiniu.com/article/fusion/api/log.html
|
||||
func GetCdnLogList(date, domains string) (domainLogs []LogDomainInfo, err error) {
|
||||
|
||||
//new log query request
|
||||
logReq := ListLogRequest{
|
||||
Day: date,
|
||||
Domains: domains,
|
||||
}
|
||||
|
||||
logReqBytes, _ := json.Marshal(&logReq)
|
||||
req, reqErr := http.NewRequest("POST", LIST_LOG_API, bytes.NewReader(logReqBytes))
|
||||
if reqErr != nil {
|
||||
err = fmt.Errorf("New request error, %s", reqErr)
|
||||
return
|
||||
}
|
||||
|
||||
mac := qbox.NewMac("", "")
|
||||
token, signErr := mac.SignRequest(req, false)
|
||||
if signErr != nil {
|
||||
err = signErr
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "QBox "+token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, respErr := http.DefaultClient.Do(req)
|
||||
if respErr != nil {
|
||||
err = fmt.Errorf("Get response error, %s", respErr)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
listLogResult := ListLogResult{}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
if decodeErr := decoder.Decode(&listLogResult); decodeErr != nil {
|
||||
err = fmt.Errorf("Parse response error, %s", decodeErr)
|
||||
return
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("Get log list error, %d %s", listLogResult.Code, listLogResult.Error)
|
||||
return
|
||||
}
|
||||
|
||||
domainItems := strings.Split(domains, ";")
|
||||
|
||||
for _, domain := range domainItems {
|
||||
for _, v := range listLogResult.Data[domain] {
|
||||
domainLogs = append(domainLogs, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
48
vendor/github.com/qiniu/api.v7/cdn/logs_test.go
generated
vendored
48
vendor/github.com/qiniu/api.v7/cdn/logs_test.go
generated
vendored
@@ -1,48 +0,0 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/qiniu/api.v7/kodo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kodo.SetMac(ak, sk)
|
||||
}
|
||||
|
||||
func TestGetCdnLogList(t *testing.T) {
|
||||
type args struct {
|
||||
date string
|
||||
domains string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantDomainLogs []LogDomainInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "getCdnLogListTest",
|
||||
args: args{
|
||||
date: testDate,
|
||||
domains: domain,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotDomainLogs, err := GetCdnLogList(tt.args.date, tt.args.domains)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetCdnLogList() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
fmt.Println(domain, gotDomainLogs)
|
||||
if !reflect.DeepEqual(gotDomainLogs, tt.wantDomainLogs) {
|
||||
t.Errorf("GetCdnLogList() = %v, want %v", gotDomainLogs, tt.wantDomainLogs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
10
vendor/github.com/qiniu/api.v7/conf/conf.go
generated
vendored
10
vendor/github.com/qiniu/api.v7/conf/conf.go
generated
vendored
@@ -9,19 +9,13 @@ import (
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var version = "7.1.0"
|
||||
|
||||
var ACCESS_KEY string
|
||||
var SECRET_KEY string
|
||||
|
||||
// ----------------------------------------------------------
|
||||
var version = "7.2.3"
|
||||
|
||||
const (
|
||||
ctypeAppName = ctype.ALPHA | ctype.DIGIT | ctype.UNDERLINE | ctype.SPACE_BAR | ctype.SUB | ctype.DOT
|
||||
)
|
||||
|
||||
// userApp should be [A-Za-z0-9_\ \-\.]*
|
||||
//
|
||||
func SetAppName(userApp string) error {
|
||||
if userApp != "" && !ctype.IsType(ctypeAppName, userApp) {
|
||||
return syscall.EINVAL
|
||||
@@ -34,5 +28,3 @@ func SetAppName(userApp string) error {
|
||||
func init() {
|
||||
SetAppName("")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
2
vendor/github.com/qiniu/api.v7/conf/doc.go
generated
vendored
Normal file
2
vendor/github.com/qiniu/api.v7/conf/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// conf 包提供了设置APP名称的方法。该APP名称会被放入API请求的UserAgent中,方便后续查询日志分析问题。
|
||||
package conf
|
||||
16
vendor/github.com/qiniu/api.v7/doc.go
generated
vendored
16
vendor/github.com/qiniu/api.v7/doc.go
generated
vendored
@@ -1,16 +1,13 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7 是七牛 Go 语言 SDK v7.x 版本
|
||||
|
||||
七牛对象存储,我们取了一个好听的名字,叫 KODO Blob Storage。要使用它,你主要和以下两个包打交道:
|
||||
包 github.com/qiniu/api.v7 是七牛 Go 语言 SDK v7.x 版本。
|
||||
|
||||
import "github.com/qiniu/api.v7/kodo"
|
||||
import "github.com/qiniu/api.v7/kodocli"
|
||||
主要提供了存储的数据上传,下载,管理以及CDN相关的功能。要求Go语言版本>=1.7.0。
|
||||
|
||||
如果您是在业务服务器(服务器端)调用七牛云存储的服务,请使用 github.com/qiniu/api.v7/kodo。
|
||||
Go SDK 中主要包含几个包:
|
||||
|
||||
auth 包提供鉴权相关方法,conf 包提供配置相关方法,cdn包提供CDN相关的功能,storage包提供存储相关的功能。
|
||||
|
||||
如果您是在客户端(比如:Android/iOS 设备、Windows/Mac/Linux 桌面环境)调用七牛云存储的服务,请使用 github.com/qiniu/api.v7/kodocli。
|
||||
注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,
|
||||
会影响您的数据安全。
|
||||
*/
|
||||
package api
|
||||
|
||||
@@ -18,6 +15,5 @@ import (
|
||||
_ "github.com/qiniu/api.v7/auth/qbox"
|
||||
_ "github.com/qiniu/api.v7/cdn"
|
||||
_ "github.com/qiniu/api.v7/conf"
|
||||
_ "github.com/qiniu/api.v7/kodo"
|
||||
_ "github.com/qiniu/api.v7/kodocli"
|
||||
_ "github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
35
vendor/github.com/qiniu/api.v7/examples/bucket_image_unimage.go
generated
vendored
Normal file
35
vendor/github.com/qiniu/api.v7/examples/bucket_image_unimage.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := storage.Config{}
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
bucketManger := storage.NewBucketManager(mac, &cfg)
|
||||
siteURL := "http://devtools.qiniu.com"
|
||||
|
||||
// 设置镜像存储
|
||||
err := bucketManger.SetImage(siteURL, bucket)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// 取消设置镜像存储
|
||||
err = bucketManger.UnsetImage(bucket)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
}
|
||||
20
vendor/github.com/qiniu/api.v7/examples/cdn_create_timestamp_antileech_url.go
generated
vendored
Normal file
20
vendor/github.com/qiniu/api.v7/examples/cdn_create_timestamp_antileech_url.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/cdn"
|
||||
)
|
||||
|
||||
func main() {
|
||||
urlStr := "http://image.example.com/qiniu_do_not_delete.gif"
|
||||
cryptKey := "your crypt key"
|
||||
deadline := time.Now().Add(time.Second * 3600).Unix()
|
||||
accessUrl, err := cdn.CreateTimestampAntileechURL(urlStr, cryptKey, deadline)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(accessUrl)
|
||||
}
|
||||
31
vendor/github.com/qiniu/api.v7/examples/cdn_get_bandwidth_data.go
generated
vendored
Normal file
31
vendor/github.com/qiniu/api.v7/examples/cdn_get_bandwidth_data.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cdnManager := cdn.NewCdnManager(mac)
|
||||
|
||||
startDate := "2017-07-20"
|
||||
endDate := "2017-07-30"
|
||||
g := "day"
|
||||
data, err := cdnManager.GetBandwidthData(startDate, endDate, g, []string{domain})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", data)
|
||||
}
|
||||
31
vendor/github.com/qiniu/api.v7/examples/cdn_get_flux_data.go
generated
vendored
Normal file
31
vendor/github.com/qiniu/api.v7/examples/cdn_get_flux_data.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cdnManager := cdn.NewCdnManager(mac)
|
||||
|
||||
startDate := "2017-07-20"
|
||||
endDate := "2017-07-30"
|
||||
g := "day"
|
||||
data, err := cdnManager.GetFluxData(startDate, endDate, g, []string{domain})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", data)
|
||||
}
|
||||
37
vendor/github.com/qiniu/api.v7/examples/cdn_get_log_list.go
generated
vendored
Normal file
37
vendor/github.com/qiniu/api.v7/examples/cdn_get_log_list.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cdnManager := cdn.NewCdnManager(mac)
|
||||
|
||||
domains := []string{
|
||||
domain,
|
||||
}
|
||||
day := "2017-07-30"
|
||||
ret, err := cdnManager.GetCdnLogList(day, domains)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
domainLogs := ret.Data
|
||||
for domain, logs := range domainLogs {
|
||||
fmt.Println(domain)
|
||||
for _, item := range logs {
|
||||
fmt.Println(item.Name, item.URL, item.Size, item.ModifiedTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
33
vendor/github.com/qiniu/api.v7/examples/cdn_prefetch_urls.go
generated
vendored
Normal file
33
vendor/github.com/qiniu/api.v7/examples/cdn_prefetch_urls.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cdnManager := cdn.NewCdnManager(mac)
|
||||
|
||||
// 预取链接,单次请求链接不可以超过100个,如果超过,请分批发送请求
|
||||
urlsToPrefetch := []string{
|
||||
"http://if-pbl.qiniudn.com/qiniu.png",
|
||||
"http://if-pbl.qiniudn.com/github.png",
|
||||
}
|
||||
ret, err := cdnManager.PrefetchUrls(urlsToPrefetch)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(ret.Code)
|
||||
fmt.Println(ret.RequestID)
|
||||
}
|
||||
48
vendor/github.com/qiniu/api.v7/examples/cdn_refresh_urls_and_dirs.go
generated
vendored
Normal file
48
vendor/github.com/qiniu/api.v7/examples/cdn_refresh_urls_and_dirs.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cdnManager := cdn.NewCdnManager(mac)
|
||||
|
||||
//刷新链接,单次请求链接不可以超过100个,如果超过,请分批发送请求
|
||||
urlsToRefresh := []string{
|
||||
"http://if-pbl.qiniudn.com/qiniu.png",
|
||||
"http://if-pbl.qiniudn.com/github.png",
|
||||
}
|
||||
ret, err := cdnManager.RefreshUrls(urlsToRefresh)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(ret.Code)
|
||||
fmt.Println(ret.RequestID)
|
||||
|
||||
// 刷新目录,刷新目录需要联系七牛技术支持开通权限
|
||||
// 单次请求链接不可以超过10个,如果超过,请分批发送请求
|
||||
dirsToRefresh := []string{
|
||||
"http://if-pbl.qiniudn.com/images/",
|
||||
"http://if-pbl.qiniudn.com/static/",
|
||||
}
|
||||
ret, err = cdnManager.RefreshDirs(dirsToRefresh)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(ret.Code)
|
||||
fmt.Println(ret.RequestID)
|
||||
fmt.Println(ret.Error)
|
||||
}
|
||||
91
vendor/github.com/qiniu/api.v7/examples/create_uptoken.go
generated
vendored
Normal file
91
vendor/github.com/qiniu/api.v7/examples/create_uptoken.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// 简单上传凭证
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
}
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
|
||||
// 设置上传凭证有效期
|
||||
putPolicy = storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
}
|
||||
putPolicy.Expires = 7200 //示例2小时有效期
|
||||
|
||||
upToken = putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
|
||||
// 覆盖上传凭证
|
||||
// 需要覆盖的文件名
|
||||
keyToOverwrite := "qiniu.mp4"
|
||||
putPolicy = storage.PutPolicy{
|
||||
Scope: fmt.Sprintf("%s:%s", bucket, keyToOverwrite),
|
||||
}
|
||||
upToken = putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
|
||||
// 自定义上传回复凭证
|
||||
putPolicy = storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
ReturnBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
|
||||
}
|
||||
upToken = putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
|
||||
// 带回调业务服务器的凭证(JSON方式)
|
||||
putPolicy = storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
CallbackURL: "http://api.example.com/qiniu/upload/callback",
|
||||
CallbackBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
|
||||
CallbackBodyType: "application/json",
|
||||
}
|
||||
upToken = putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
|
||||
// 带回调业务服务器的凭证(URL方式)
|
||||
putPolicy = storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
CallbackURL: "http://api.example.com/qiniu/upload/callback",
|
||||
CallbackBody: "key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)",
|
||||
}
|
||||
upToken = putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
|
||||
// 带数据处理的凭证
|
||||
saveMp4Entry := base64.URLEncoding.EncodeToString([]byte(bucket + ":avthumb_test_target.mp4"))
|
||||
saveJpgEntry := base64.URLEncoding.EncodeToString([]byte(bucket + ":vframe_test_target.jpg"))
|
||||
//数据处理指令,支持多个指令
|
||||
avthumbMp4Fop := "avthumb/mp4|saveas/" + saveMp4Entry
|
||||
vframeJpgFop := "vframe/jpg/offset/1|saveas/" + saveJpgEntry
|
||||
//连接多个操作指令
|
||||
persistentOps := strings.Join([]string{avthumbMp4Fop, vframeJpgFop}, ";")
|
||||
pipeline := "test"
|
||||
putPolicy = storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
PersistentOps: persistentOps,
|
||||
PersistentPipeline: pipeline,
|
||||
PersistentNotifyURL: "http://api.example.com/qiniu/pfop/notify",
|
||||
}
|
||||
upToken = putPolicy.UploadToken(mac)
|
||||
fmt.Println(upToken)
|
||||
}
|
||||
76
vendor/github.com/qiniu/api.v7/examples/form_upload_simple.go
generated
vendored
Normal file
76
vendor/github.com/qiniu/api.v7/examples/form_upload_simple.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
localFile := "/Users/jemy/Documents/github.png"
|
||||
key := "github-x.png"
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket + ":" + key,
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
cfg := storage.Config{}
|
||||
// 空间对应的机房
|
||||
cfg.Zone = &storage.ZoneHuadong
|
||||
// 是否使用https域名
|
||||
cfg.UseHTTPS = false
|
||||
// 上传是否使用CDN上传加速
|
||||
cfg.UseCdnDomains = false
|
||||
|
||||
//设置代理
|
||||
proxyURL := "http://localhost:8888"
|
||||
proxyURI, _ := url.Parse(proxyURL)
|
||||
|
||||
//绑定网卡
|
||||
nicIP := "100.100.33.138"
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP(nicIP),
|
||||
},
|
||||
}
|
||||
|
||||
//构建代理client对象
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURI),
|
||||
Dial: dialer.Dial,
|
||||
},
|
||||
}
|
||||
|
||||
// 构建表单上传的对象
|
||||
formUploader := storage.NewFormUploaderEx(&cfg, &rpc.Client{Client: &client})
|
||||
ret := storage.PutRet{}
|
||||
// 可选配置
|
||||
putExtra := storage.PutExtra{
|
||||
Params: map[string]string{
|
||||
"x:name": "github logo",
|
||||
},
|
||||
}
|
||||
//putExtra.NoCrc32Check = true
|
||||
err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(ret.Key, ret.Hash)
|
||||
}
|
||||
32
vendor/github.com/qiniu/api.v7/examples/prefop.go
generated
vendored
Normal file
32
vendor/github.com/qiniu/api.v7/examples/prefop.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cfg := storage.Config{
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
operationManager := storage.NewOperationManager(mac, &cfg)
|
||||
persistentId := "z0.597f28b445a2650c994bb208"
|
||||
ret, err := operationManager.Prefop(persistentId)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(ret.String())
|
||||
}
|
||||
127
vendor/github.com/qiniu/api.v7/examples/resume_upload_advanced.go
generated
vendored
Normal file
127
vendor/github.com/qiniu/api.v7/examples/resume_upload_advanced.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"context"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func md5Hex(str string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
type ProgressRecord struct {
|
||||
Progresses []storage.BlkputRet `json:"progresses"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
localFile := "your local file path"
|
||||
key := "your file save key"
|
||||
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
}
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
|
||||
cfg := storage.Config{}
|
||||
// 空间对应的机房
|
||||
cfg.Zone = &storage.ZoneHuadong
|
||||
// 是否使用https域名
|
||||
cfg.UseHTTPS = false
|
||||
// 上传是否使用CDN上传加速
|
||||
cfg.UseCdnDomains = false
|
||||
|
||||
// 必须仔细选择一个能标志上传唯一性的 recordKey 用来记录上传进度
|
||||
// 我们这里采用 md5(bucket+key+local_path+local_file_last_modified)+".progress" 作为记录上传进度的文件名
|
||||
fileInfo, statErr := os.Stat(localFile)
|
||||
if statErr != nil {
|
||||
fmt.Println(statErr)
|
||||
return
|
||||
}
|
||||
|
||||
fileSize := fileInfo.Size()
|
||||
fileLmd := fileInfo.ModTime().UnixNano()
|
||||
recordKey := md5Hex(fmt.Sprintf("%s:%s:%s:%s", bucket, key, localFile, fileLmd)) + ".progress"
|
||||
|
||||
// 指定的进度文件保存目录,实际情况下,请确保该目录存在,而且只用于记录进度文件
|
||||
recordDir := "/Users/jemy/Temp/progress"
|
||||
mErr := os.MkdirAll(recordDir, 0755)
|
||||
if mErr != nil {
|
||||
fmt.Println("mkdir for record dir error,", mErr)
|
||||
return
|
||||
}
|
||||
|
||||
recordPath := filepath.Join(recordDir, recordKey)
|
||||
|
||||
progressRecord := ProgressRecord{}
|
||||
// 尝试从旧的进度文件中读取进度
|
||||
recordFp, openErr := os.Open(recordPath)
|
||||
if openErr == nil {
|
||||
progressBytes, readErr := ioutil.ReadAll(recordFp)
|
||||
if readErr == nil {
|
||||
mErr := json.Unmarshal(progressBytes, &progressRecord)
|
||||
if mErr == nil {
|
||||
// 检查context 是否过期,避免701错误
|
||||
for _, item := range progressRecord.Progresses {
|
||||
if storage.IsContextExpired(item) {
|
||||
fmt.Println(item.ExpiredAt)
|
||||
progressRecord.Progresses = make([]storage.BlkputRet, storage.BlockCount(fileSize))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recordFp.Close()
|
||||
}
|
||||
|
||||
if len(progressRecord.Progresses) == 0 {
|
||||
progressRecord.Progresses = make([]storage.BlkputRet, storage.BlockCount(fileSize))
|
||||
}
|
||||
|
||||
resumeUploader := storage.NewResumeUploader(&cfg)
|
||||
ret := storage.PutRet{}
|
||||
progressLock := sync.RWMutex{}
|
||||
|
||||
putExtra := storage.RputExtra{
|
||||
Progresses: progressRecord.Progresses,
|
||||
Notify: func(blkIdx int, blkSize int, ret *storage.BlkputRet) {
|
||||
progressLock.Lock()
|
||||
defer progressLock.Unlock()
|
||||
//将进度序列化,然后写入文件
|
||||
progressRecord.Progresses[blkIdx] = *ret
|
||||
progressBytes, _ := json.Marshal(progressRecord)
|
||||
fmt.Println("write progress file", blkIdx, recordPath)
|
||||
wErr := ioutil.WriteFile(recordPath, progressBytes, 0644)
|
||||
if wErr != nil {
|
||||
fmt.Println("write progress file error,", wErr)
|
||||
}
|
||||
},
|
||||
}
|
||||
err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//上传成功之后,一定记得删除这个进度文件
|
||||
os.Remove(recordPath)
|
||||
fmt.Println(ret.Key, ret.Hash)
|
||||
}
|
||||
72
vendor/github.com/qiniu/api.v7/examples/resume_upload_simple.go
generated
vendored
Normal file
72
vendor/github.com/qiniu/api.v7/examples/resume_upload_simple.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
localFile := "/Users/jemy/Documents/github.png"
|
||||
key := "qiniu-x.png"
|
||||
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
}
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{}
|
||||
// 空间对应的机房
|
||||
cfg.Zone = &storage.ZoneHuadong
|
||||
// 是否使用https域名
|
||||
cfg.UseHTTPS = false
|
||||
// 上传是否使用CDN上传加速
|
||||
cfg.UseCdnDomains = false
|
||||
|
||||
//设置代理
|
||||
proxyURL := "http://localhost:8888"
|
||||
proxyURI, _ := url.Parse(proxyURL)
|
||||
|
||||
//绑定网卡
|
||||
nicIP := "100.100.33.138"
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP(nicIP),
|
||||
},
|
||||
}
|
||||
|
||||
//构建代理client对象
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURI),
|
||||
Dial: dialer.Dial,
|
||||
},
|
||||
}
|
||||
|
||||
resumeUploader := storage.NewResumeUploaderEx(&cfg, &rpc.Client{Client: &client})
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
|
||||
ret := storage.PutRet{}
|
||||
|
||||
err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(ret.Key, ret.Hash)
|
||||
}
|
||||
64
vendor/github.com/qiniu/api.v7/examples/rs_batch_change_mime.go
generated
vendored
Normal file
64
vendor/github.com/qiniu/api.v7/examples/rs_batch_change_mime.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
chgmKeys := map[string]string{
|
||||
"github1.png": "image/x-png",
|
||||
"github2.png": "image/x-png",
|
||||
"github3.png": "image/x-png",
|
||||
"github4.png": "image/x-png",
|
||||
"github5.png": "image/x-png",
|
||||
}
|
||||
chgmOps := make([]string, 0, len(chgmKeys))
|
||||
for key, newMime := range chgmKeys {
|
||||
chgmOps = append(chgmOps, storage.URIChangeMime(bucket, key, newMime))
|
||||
}
|
||||
rets, err := bucketManager.Batch(chgmOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
vendor/github.com/qiniu/api.v7/examples/rs_batch_change_type.go
generated
vendored
Normal file
67
vendor/github.com/qiniu/api.v7/examples/rs_batch_change_type.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
//每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
|
||||
|
||||
chtypeKeys := map[string]int{
|
||||
"github1.png": 1,
|
||||
"github2.png": 1,
|
||||
"github3.png": 1,
|
||||
"github4.png": 1,
|
||||
"github5.png": 1,
|
||||
}
|
||||
chtypeOps := make([]string, 0, len(chtypeKeys))
|
||||
for key, fileType := range chtypeKeys {
|
||||
chtypeOps = append(chtypeOps, storage.URIChangeType(bucket, key, fileType))
|
||||
}
|
||||
rets, err := bucketManager.Batch(chtypeOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
vendor/github.com/qiniu/api.v7/examples/rs_batch_copy.go
generated
vendored
Normal file
67
vendor/github.com/qiniu/api.v7/examples/rs_batch_copy.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
//每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
|
||||
srcBucket := bucket
|
||||
destBucket := bucket
|
||||
force := true
|
||||
copyKeys := map[string]string{
|
||||
"github1.png": "github1-copy.png",
|
||||
"github2.png": "github2-copy.png",
|
||||
"github3.png": "github3-copy.png",
|
||||
"github4.png": "github4-copy.png",
|
||||
"github5.png": "github5-copy.png",
|
||||
}
|
||||
copyOps := make([]string, 0, len(copyKeys))
|
||||
for srcKey, destKey := range copyKeys {
|
||||
copyOps = append(copyOps, storage.URICopy(srcBucket, srcKey, destBucket, destKey, force))
|
||||
}
|
||||
rets, err := bucketManager.Batch(copyOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
fmt.Printf("%v\n", ret.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
63
vendor/github.com/qiniu/api.v7/examples/rs_batch_delete.go
generated
vendored
Normal file
63
vendor/github.com/qiniu/api.v7/examples/rs_batch_delete.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
//每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
|
||||
keys := []string{
|
||||
"github1.png",
|
||||
"github2.png",
|
||||
"github3.png",
|
||||
"github4.png",
|
||||
"github5.png",
|
||||
}
|
||||
deleteOps := make([]string, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
deleteOps = append(deleteOps, storage.URIDelete(bucket, key))
|
||||
}
|
||||
rets, err := bucketManager.Batch(deleteOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
vendor/github.com/qiniu/api.v7/examples/rs_batch_deleteAfterDays.go
generated
vendored
Normal file
66
vendor/github.com/qiniu/api.v7/examples/rs_batch_deleteAfterDays.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
//每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
|
||||
expireKeys := map[string]int{
|
||||
"github1.png": 7,
|
||||
"github2.png": 8,
|
||||
"github3.png": 9,
|
||||
"github4.png": 10,
|
||||
"github5.png": 11,
|
||||
}
|
||||
expireOps := make([]string, 0, len(expireKeys))
|
||||
for key, expire := range expireKeys {
|
||||
expireOps = append(expireOps, storage.URIDeleteAfterDays(bucket, key, expire))
|
||||
}
|
||||
rets, err := bucketManager.Batch(expireOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
vendor/github.com/qiniu/api.v7/examples/rs_batch_move.go
generated
vendored
Normal file
67
vendor/github.com/qiniu/api.v7/examples/rs_batch_move.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
//每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
|
||||
srcBucket := bucket
|
||||
destBucket := bucket
|
||||
force := true
|
||||
moveKeys := map[string]string{
|
||||
"github1.png": "github1-move.png",
|
||||
"github2.png": "github2-move.png",
|
||||
"github3.png": "github3-move.png",
|
||||
"github4.png": "github4-move.png",
|
||||
"github5.png": "github5-move.png",
|
||||
}
|
||||
moveOps := make([]string, 0, len(moveKeys))
|
||||
for srcKey, destKey := range moveKeys {
|
||||
moveOps = append(moveOps, storage.URIMove(srcBucket, srcKey, destBucket, destKey, force))
|
||||
}
|
||||
rets, err := bucketManager.Batch(moveOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
fmt.Printf("%v\n", ret.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
vendor/github.com/qiniu/api.v7/examples/rs_batch_stat.go
generated
vendored
Normal file
66
vendor/github.com/qiniu/api.v7/examples/rs_batch_stat.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
//每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
|
||||
keys := []string{
|
||||
"github1.png",
|
||||
"github2.png",
|
||||
"github3.png",
|
||||
"github4.png",
|
||||
"github5.png",
|
||||
}
|
||||
statOps := make([]string, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
statOps = append(statOps, storage.URIStat(bucket, key))
|
||||
}
|
||||
rets, err := bucketManager.Batch(statOps)
|
||||
if err != nil {
|
||||
// 遇到错误
|
||||
if _, ok := err.(*rpc.ErrorInfo); ok {
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
if ret.Code != 200 {
|
||||
fmt.Printf("%s\n", ret.Data.Error)
|
||||
} else {
|
||||
fmt.Printf("%v\n", ret.Data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("batch error, %s", err)
|
||||
}
|
||||
} else {
|
||||
// 完全成功
|
||||
for _, ret := range rets {
|
||||
// 200 为成功
|
||||
fmt.Printf("%d\n", ret.Code)
|
||||
fmt.Printf("%v\n", ret.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/qiniu/api.v7/examples/rs_change_mime.go
generated
vendored
Normal file
36
vendor/github.com/qiniu/api.v7/examples/rs_change_mime.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
key := "github.png"
|
||||
newMime := "image/x-png"
|
||||
err := bucketManager.ChangeMime(bucket, key, newMime)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/qiniu/api.v7/examples/rs_change_type.go
generated
vendored
Normal file
36
vendor/github.com/qiniu/api.v7/examples/rs_change_type.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
key := "github.png"
|
||||
fileType := 1 // 0 表示普通存储,1表示低频存储
|
||||
err := bucketManager.ChangeType(bucket, key, fileType)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
42
vendor/github.com/qiniu/api.v7/examples/rs_copy.go
generated
vendored
Normal file
42
vendor/github.com/qiniu/api.v7/examples/rs_copy.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
srcBucket := "if-pbl"
|
||||
srcKey := "github.png"
|
||||
//目标空间可以和源空间相同,但是不能为跨机房的空间
|
||||
destBucket := srcBucket
|
||||
//目标文件名可以和源文件名相同,也可以不同
|
||||
destKey := "github-new.png"
|
||||
//如果目标文件存在,是否强制覆盖,如果不覆盖,默认返回614 file exists
|
||||
force := false
|
||||
err := bucketManager.Copy(srcBucket, srcKey, destBucket, destKey, force)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/qiniu/api.v7/examples/rs_delete.go
generated
vendored
Normal file
35
vendor/github.com/qiniu/api.v7/examples/rs_delete.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
key := "github.png"
|
||||
err := bucketManager.Delete(bucket, key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/qiniu/api.v7/examples/rs_deleteAfterDays.go
generated
vendored
Normal file
36
vendor/github.com/qiniu/api.v7/examples/rs_deleteAfterDays.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
key := "github.png"
|
||||
days := 7
|
||||
err := bucketManager.DeleteAfterDays(bucket, key, days)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
33
vendor/github.com/qiniu/api.v7/examples/rs_download.go
generated
vendored
Normal file
33
vendor/github.com/qiniu/api.v7/examples/rs_download.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
// 公开空间访问
|
||||
domain := "https://image.example.com"
|
||||
key := "这是一个测试文件.jpg"
|
||||
publicAccessURL := storage.MakePublicURL(domain, key)
|
||||
fmt.Println(publicAccessURL)
|
||||
|
||||
// 私有空间访问
|
||||
domain = "https://image.example.com"
|
||||
key = "这是一个测试文件.jpg"
|
||||
deadline := time.Now().Add(time.Second * 3600).Unix() //1小时有效期
|
||||
privateAccessURL := storage.MakePrivateURL(mac, domain, key, deadline)
|
||||
fmt.Println(privateAccessURL)
|
||||
}
|
||||
44
vendor/github.com/qiniu/api.v7/examples/rs_fetch.go
generated
vendored
Normal file
44
vendor/github.com/qiniu/api.v7/examples/rs_fetch.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
resURL := "http://devtools.qiniu.com/qiniu.png"
|
||||
// 指定保存的key
|
||||
fetchRet, err := bucketManager.Fetch(resURL, bucket, "qiniu.png")
|
||||
if err != nil {
|
||||
fmt.Println("fetch error,", err)
|
||||
} else {
|
||||
fmt.Println(fetchRet.String())
|
||||
}
|
||||
// 不指定保存的key,默认用文件hash作为文件名
|
||||
fetchRet, err = bucketManager.FetchWithoutKey(resURL, bucket)
|
||||
if err != nil {
|
||||
fmt.Println("fetch error,", err)
|
||||
} else {
|
||||
fmt.Println(fetchRet.String())
|
||||
}
|
||||
}
|
||||
51
vendor/github.com/qiniu/api.v7/examples/rs_list_files.go
generated
vendored
Normal file
51
vendor/github.com/qiniu/api.v7/examples/rs_list_files.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
limit := 1000
|
||||
prefix := "qiniu"
|
||||
delimiter := ""
|
||||
//初始列举marker为空
|
||||
marker := ""
|
||||
for {
|
||||
entries, _, nextMarker, hashNext, err := bucketManager.ListFiles(bucket, prefix, delimiter, marker, limit)
|
||||
if err != nil {
|
||||
fmt.Println("list error,", err)
|
||||
break
|
||||
}
|
||||
//print entries
|
||||
for _, entry := range entries {
|
||||
fmt.Println(entry.Key)
|
||||
}
|
||||
if hashNext {
|
||||
marker = nextMarker
|
||||
} else {
|
||||
//list end
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
42
vendor/github.com/qiniu/api.v7/examples/rs_move.go
generated
vendored
Normal file
42
vendor/github.com/qiniu/api.v7/examples/rs_move.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
srcBucket := bucket
|
||||
srcKey := "github.png"
|
||||
//目标空间可以和源空间相同,但是不能为跨机房的空间
|
||||
destBucket := srcBucket
|
||||
//目标文件名可以和源文件名相同,也可以不同
|
||||
destKey := "github-new.png"
|
||||
//如果目标文件存在,是否强制覆盖,如果不覆盖,默认返回614 file exists
|
||||
force := false
|
||||
err := bucketManager.Move(srcBucket, srcKey, destBucket, destKey, force)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/qiniu/api.v7/examples/rs_prefetch.go
generated
vendored
Normal file
34
vendor/github.com/qiniu/api.v7/examples/rs_prefetch.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
key := "qiniu.png"
|
||||
err := bucketManager.Prefetch(bucket, key)
|
||||
if err != nil {
|
||||
fmt.Println("fetch error,", err)
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/qiniu/api.v7/examples/rs_stat.go
generated
vendored
Normal file
38
vendor/github.com/qiniu/api.v7/examples/rs_stat.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
cfg := storage.Config{
|
||||
// 是否使用https域名进行资源管理
|
||||
UseHTTPS: false,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
key := "qiniu.png"
|
||||
fileInfo, sErr := bucketManager.Stat(bucket, key)
|
||||
if sErr != nil {
|
||||
fmt.Println(sErr)
|
||||
return
|
||||
}
|
||||
fmt.Println(fileInfo.String())
|
||||
//可以解析文件的PutTime
|
||||
fmt.Println(storage.ParsePutTime(fileInfo.PutTime))
|
||||
}
|
||||
52
vendor/github.com/qiniu/api.v7/examples/video_pfop.go
generated
vendored
Normal file
52
vendor/github.com/qiniu/api.v7/examples/video_pfop.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
accessKey = os.Getenv("QINIU_ACCESS_KEY")
|
||||
secretKey = os.Getenv("QINIU_SECRET_KEY")
|
||||
bucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
// 数据处理的私有队列,必须指定以保障处理速度
|
||||
pipeline = os.Getenv("QINIU_TEST_PIPELINE")
|
||||
)
|
||||
|
||||
func main() {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cfg := storage.Config{
|
||||
UseHTTPS: true,
|
||||
}
|
||||
// 指定空间所在的区域,如果不指定将自动探测
|
||||
// 如果没有特殊需求,默认不需要指定
|
||||
//cfg.Zone=&storage.ZoneHuabei
|
||||
operationManager := storage.NewOperationManager(mac, &cfg)
|
||||
key := "qiniu.mp4"
|
||||
saveBucket := bucket
|
||||
// 处理指令集合
|
||||
fopAvthumb := fmt.Sprintf("avthumb/mp4/s/480x320/vb/500k|saveas/%s",
|
||||
storage.EncodedEntry(saveBucket, "pfop_test_qiniu.mp4"))
|
||||
fopVframe := fmt.Sprintf("vframe/jpg/offset/10|saveas/%s",
|
||||
storage.EncodedEntry(saveBucket, "pfop_test_qiniu.jpg"))
|
||||
fopVsample := fmt.Sprintf("vsample/jpg/interval/20/pattern/%s",
|
||||
base64.URLEncoding.EncodeToString([]byte("pfop_test_$(count).jpg")))
|
||||
fopBatch := []string{fopAvthumb, fopVframe, fopVsample}
|
||||
fops := strings.Join(fopBatch, ";")
|
||||
// 强制重新执行数据处理任务
|
||||
force := true
|
||||
// 数据处理指令全部完成之后,通知该地址
|
||||
notifyURL := "http://api.example.com/pfop/callback"
|
||||
// 数据处理的私有队列,必须指定以保障处理速度
|
||||
persistentId, err := operationManager.Pfop(bucket, key, fops, pipeline, notifyURL, force)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(persistentId)
|
||||
}
|
||||
435
vendor/github.com/qiniu/api.v7/kodo/bucket.go
generated
vendored
435
vendor/github.com/qiniu/api.v7/kodo/bucket.go
generated
vendored
@@ -1,435 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/x/log.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Batch 批量操作。
|
||||
//
|
||||
func (p *Client) Batch(ctx Context, ret interface{}, op []string) (err error) {
|
||||
|
||||
return p.CallWithForm(ctx, ret, "POST", p.RSHost+"/batch", map[string][]string{"op": op})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Bucket struct {
|
||||
api.BucketInfo
|
||||
Conn *Client
|
||||
Name string
|
||||
}
|
||||
|
||||
// Buckets 获取所有地区的所有空间(bucket)
|
||||
//
|
||||
// shared 是否获取所有授权获得空间,true为包含授权空间
|
||||
//
|
||||
func (p *Client) Buckets(ctx Context, shared bool) (buckets []string, err error) {
|
||||
if shared {
|
||||
err = p.Call(ctx, &buckets, "POST", p.RSHost+"/buckets?shared=trye")
|
||||
} else {
|
||||
err = p.Call(ctx, &buckets, "POST", p.RSHost+"/buckets")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Bucket 取七牛空间(bucket)的对象实例。
|
||||
//
|
||||
// name 是创建该七牛空间(bucket)时采用的名称。
|
||||
//
|
||||
func (p *Client) Bucket(name string) Bucket {
|
||||
b, err := p.BucketWithSafe(name)
|
||||
if err != nil {
|
||||
log.Errorf("Bucket(%s) failed: %+v", name, err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// BucketWithSafe 确认空间存在并获取七牛空间(bucket)的对象实例。
|
||||
func (p *Client) BucketWithSafe(name string) (Bucket, error) {
|
||||
var info api.BucketInfo
|
||||
if len(p.UpHosts) == 0 {
|
||||
var err error
|
||||
info, err = p.apiCli.GetBucketInfo(p.mac.AccessKey, name)
|
||||
if err != nil {
|
||||
return Bucket{}, err
|
||||
}
|
||||
} else {
|
||||
info.IoHost = p.IoHost
|
||||
info.UpHosts = p.UpHosts
|
||||
}
|
||||
return Bucket{info, p, name}, nil
|
||||
}
|
||||
|
||||
// Entry 资源元信息
|
||||
type Entry struct {
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Type int `json:"type"`
|
||||
EndUser string `json:"endUser"`
|
||||
}
|
||||
|
||||
// Stat 取文件属性。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要访问的文件的访问路径。
|
||||
//
|
||||
func (p Bucket) Stat(ctx Context, key string) (entry Entry, err error) {
|
||||
err = p.Conn.Call(ctx, &entry, "POST", p.Conn.RSHost+URIStat(p.Name, key))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要删除的文件的访问路径。
|
||||
//
|
||||
func (p Bucket) Delete(ctx Context, key string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIDelete(p.Name, key))
|
||||
}
|
||||
|
||||
// Move 移动一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// keySrc 是要移动的文件的旧路径。
|
||||
// keyDest 是要移动的文件的新路径。
|
||||
//
|
||||
func (p Bucket) Move(ctx Context, keySrc, keyDest string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIMove(p.Name, keySrc, p.Name, keyDest))
|
||||
}
|
||||
|
||||
// MoveEx 跨空间(bucket)移动一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// keySrc 是要移动的文件的旧路径。
|
||||
// bucketDest 是文件的目标空间。
|
||||
// keyDest 是要移动的文件的新路径。
|
||||
//
|
||||
func (p Bucket) MoveEx(ctx Context, keySrc, bucketDest, keyDest string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIMove(p.Name, keySrc, bucketDest, keyDest))
|
||||
}
|
||||
|
||||
// Copy 复制一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// keySrc 是要复制的文件的源路径。
|
||||
// keyDest 是要复制的文件的目标路径。
|
||||
//
|
||||
func (p Bucket) Copy(ctx Context, keySrc, keyDest string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URICopy(p.Name, keySrc, p.Name, keyDest))
|
||||
}
|
||||
|
||||
// ChangeMime 修改文件的MIME类型。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要修改的文件的访问路径。
|
||||
// mime 是要设置的新MIME类型。
|
||||
//
|
||||
func (p Bucket) ChangeMime(ctx Context, key, mime string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIChangeMime(p.Name, key, mime))
|
||||
}
|
||||
|
||||
// ChangeType 修改文件的存储类型。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要修改的文件的访问路径。
|
||||
// fileType 是要设置的新存储类型。0 表示标准存储;1 表示低频存储。
|
||||
//
|
||||
func (p Bucket) ChangeType(ctx Context, key string, fileType int) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIChangeType(p.Name, key, fileType))
|
||||
}
|
||||
|
||||
// Fetch 从网上抓取一个资源并存储到七牛空间(bucket)中。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要存储的文件的访问路径。如果文件已经存在则覆盖。
|
||||
// url 是要抓取的资源的URL。
|
||||
//
|
||||
func (p Bucket) Fetch(ctx Context, key string, url string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.IoHost+uriFetch(p.Name, key, url))
|
||||
}
|
||||
|
||||
// DeleteAfterDays 更新文件生命周期
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// key 是要更新的文件的访问路径。
|
||||
// deleteAfterDays 设置为0表示取消 lifecycle
|
||||
//
|
||||
func (p Bucket) DeleteAfterDays(ctx Context, key string, days int) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.RSHost+URIDeleteAfterDays(p.Name, key, days))
|
||||
}
|
||||
|
||||
// Image 设置镜像源
|
||||
//
|
||||
// srcSiteURL 镜像源的访问域名。必须设置为形如 `http://source.com/` 或 `http://114.114.114.114/` 的字符串
|
||||
// host 回源时使用的 Host 头部值
|
||||
//
|
||||
// 镜像源地址支持两种格式:
|
||||
// 格式 1:`http(s)://绑定域名/源站资源相对路径`
|
||||
// 格式 2:`http(s)://绑定 IP/源站资源相对路径`
|
||||
//
|
||||
func (p Bucket) Image(ctx Context, srcSiteURL, host string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", "http://pu.qbox.me:10200"+URIImage(p.Name, srcSiteURL, host))
|
||||
}
|
||||
|
||||
// UnImage 取消镜像源
|
||||
//
|
||||
func (p Bucket) UnImage(ctx Context) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", "http://pu.qbox.me:10200"+URIUnImage(p.Name))
|
||||
}
|
||||
|
||||
// Prefetch 镜像资源更新
|
||||
//
|
||||
// key 被抓取资源名称
|
||||
//
|
||||
func (p Bucket) Prefetch(ctx Context, key string) (err error) {
|
||||
return p.Conn.Call(ctx, nil, "POST", p.Conn.IoHost+URIPrefetch(p.Name, key))
|
||||
}
|
||||
|
||||
// PfopResult pfop返回信息
|
||||
type PfopResult struct {
|
||||
PersistentID string `json:"persistentId,omitempty"`
|
||||
}
|
||||
|
||||
// FopRet 持久化云处理结果
|
||||
type FopRet struct {
|
||||
ID string `json:"id"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
InputBucket string `json:"inputBucket,omitempty"`
|
||||
InputKey string `json:"inputKey,omitempty"`
|
||||
Pipeline string `json:"pipeline,omitempty"`
|
||||
Reqid string `json:"reqid,omitempty"`
|
||||
Items []FopResult
|
||||
}
|
||||
|
||||
// FopResult 云处理操作列表,包含每个云处理操作的状态信息
|
||||
type FopResult struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Keys []string `json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
// Pfop 持久化数据处理
|
||||
//
|
||||
// bucket 资源空间
|
||||
// key 源资源名
|
||||
// fops 云处理操作列表,用`;``分隔,如:`avthumb/flv;saveas/cWJ1Y2tldDpxa2V5`,是将上传的视频文件转码成flv格式后存储为 qbucket:qkey ,其中 cWJ1Y2tldDpxa2V5 是 qbucket:qkey 的URL安全的Base64编码结果。
|
||||
// notifyURL 处理结果通知接收 URL,七牛将会向你设置的 URL 发起 Content-Type: application/json 的 POST 请求。
|
||||
// pipeline 为空则表示使用公用队列,处理速度比较慢。建议指定私有队列,转码的时候使用独立的计算资源。
|
||||
// force 强制执行数据处理。当服务端发现 fops 指定的数据处理结果已经存在,那就认为已经处理成功,避免重复处理浪费资源。本字段设为 `true`,则可强制执行数据处理并覆盖原结果。
|
||||
//
|
||||
func (p *Client) Pfop(ctx Context, bucket, key, fops, notifyURL, pipeline string, force bool) (persistentID string, err error) {
|
||||
pfopParams := map[string][]string{
|
||||
"bucket": []string{bucket},
|
||||
"key": []string{key},
|
||||
"fops": []string{fops},
|
||||
}
|
||||
if notifyURL != "" {
|
||||
pfopParams["notifyURL"] = []string{notifyURL}
|
||||
}
|
||||
if pipeline != "" {
|
||||
pfopParams["pipeline"] = []string{pipeline}
|
||||
}
|
||||
if force {
|
||||
pfopParams["force"] = []string{"1"}
|
||||
}
|
||||
var ret PfopResult
|
||||
err = p.CallWithForm(ctx, &ret, "POST", "http://api.qiniu.com/pfop/", pfopParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
persistentID = ret.PersistentID
|
||||
return
|
||||
}
|
||||
|
||||
// Prefop 持久化处理状态查询
|
||||
func (p *Client) Prefop(ctx Context, persistentID string) (ret FopRet, err error) {
|
||||
err = p.Call(ctx, &ret, "GET", "http://api.qiniu.com/status/get/prefop?id="+persistentID)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// ListItem List借口返回结果
|
||||
type ListItem struct {
|
||||
Key string `json:"key"`
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
EndUser string `json:"endUser"`
|
||||
}
|
||||
|
||||
// List 首次请求,请将 marker 设置为 ""。
|
||||
// 无论 err 值如何,均应该先看 entries 是否有内容。
|
||||
// 如果后续没有更多数据,err 返回 EOF,markerOut 返回 ""(但不通过该特征来判断是否结束)。
|
||||
//
|
||||
func (p Bucket) List(
|
||||
ctx Context, prefix, delimiter, marker string, limit int) (entries []ListItem, commonPrefixes []string, markerOut string, err error) {
|
||||
|
||||
listUrl := p.makeListURL(prefix, delimiter, marker, limit)
|
||||
|
||||
var listRet struct {
|
||||
Marker string `json:"marker"`
|
||||
Items []ListItem `json:"items"`
|
||||
Prefixes []string `json:"commonPrefixes"`
|
||||
}
|
||||
err = p.Conn.Call(ctx, &listRet, "POST", listUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if listRet.Marker == "" {
|
||||
return listRet.Items, listRet.Prefixes, "", io.EOF
|
||||
}
|
||||
return listRet.Items, listRet.Prefixes, listRet.Marker, nil
|
||||
}
|
||||
|
||||
func (p Bucket) makeListURL(prefix, delimiter, marker string, limit int) string {
|
||||
|
||||
query := make(url.Values)
|
||||
query.Add("bucket", p.Name)
|
||||
if prefix != "" {
|
||||
query.Add("prefix", prefix)
|
||||
}
|
||||
if delimiter != "" {
|
||||
query.Add("delimiter", delimiter)
|
||||
}
|
||||
if marker != "" {
|
||||
query.Add("marker", marker)
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Add("limit", strconv.FormatInt(int64(limit), 10))
|
||||
}
|
||||
return p.Conn.RSFHost + "/list?" + query.Encode()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type BatchStatItemRet struct {
|
||||
Data Entry `json:"data"`
|
||||
Error string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
// BatchStat 批量取文件属性
|
||||
func (p Bucket) BatchStat(ctx Context, keys ...string) (ret []BatchStatItemRet, err error) {
|
||||
|
||||
b := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
b[i] = URIStat(p.Name, key)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
type BatchItemRet struct {
|
||||
Error string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除
|
||||
func (p Bucket) BatchDelete(ctx Context, keys ...string) (ret []BatchItemRet, err error) {
|
||||
|
||||
b := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
b[i] = URIDelete(p.Name, key)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
Src string
|
||||
Dest string
|
||||
}
|
||||
|
||||
// BatchMove 批量移动文件
|
||||
func (p Bucket) BatchMove(ctx Context, entries ...KeyPair) (ret []BatchItemRet, err error) {
|
||||
|
||||
b := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
b[i] = URIMove(p.Name, e.Src, p.Name, e.Dest)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
// BatchCopy 批量复制文件
|
||||
func (p Bucket) BatchCopy(ctx Context, entries ...KeyPair) (ret []BatchItemRet, err error) {
|
||||
|
||||
b := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
b[i] = URICopy(p.Name, e.Src, p.Name, e.Dest)
|
||||
}
|
||||
err = p.Conn.Batch(ctx, &ret, b)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func encodeURI(uri string) string {
|
||||
return base64.URLEncoding.EncodeToString([]byte(uri))
|
||||
}
|
||||
|
||||
func uriFetch(bucket, key, url string) string {
|
||||
return "/fetch/" + encodeURI(url) + "/to/" + encodeURI(bucket+":"+key)
|
||||
}
|
||||
|
||||
func URIDelete(bucket, key string) string {
|
||||
return "/delete/" + encodeURI(bucket+":"+key)
|
||||
}
|
||||
|
||||
func URIStat(bucket, key string) string {
|
||||
return "/stat/" + encodeURI(bucket+":"+key)
|
||||
}
|
||||
|
||||
func URICopy(bucketSrc, keySrc, bucketDest, keyDest string) string {
|
||||
return "/copy/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
|
||||
}
|
||||
|
||||
func URIMove(bucketSrc, keySrc, bucketDest, keyDest string) string {
|
||||
return "/move/" + encodeURI(bucketSrc+":"+keySrc) + "/" + encodeURI(bucketDest+":"+keyDest)
|
||||
}
|
||||
|
||||
func URIChangeMime(bucket, key, mime string) string {
|
||||
return "/chgm/" + encodeURI(bucket+":"+key) + "/mime/" + encodeURI(mime)
|
||||
}
|
||||
|
||||
func URIChangeType(bucket, key string, fileType int) string {
|
||||
return "/chtype/" + encodeURI(bucket+":"+key) + "/type/" + strconv.Itoa(fileType)
|
||||
}
|
||||
|
||||
func URIDeleteAfterDays(bucket, key string, days int) string {
|
||||
return fmt.Sprintf("/deleteAfterDays/%s/%d", encodeURI(bucket+":"+key), days)
|
||||
}
|
||||
|
||||
func URIImage(bucket, srcSiteURL, host string) string {
|
||||
return fmt.Sprintf("/image/%s/from/%s/host/%s", bucket, encodeURI(srcSiteURL), encodeURI(host))
|
||||
}
|
||||
|
||||
func URIUnImage(bucket string) string {
|
||||
return fmt.Sprintf("/unimage/%s", bucket)
|
||||
}
|
||||
|
||||
func URIPrefetch(bucket, key string) string {
|
||||
return fmt.Sprintf("/prefetch/%s", encodeURI(bucket+":"+key))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
167
vendor/github.com/qiniu/api.v7/kodo/bucket_test.go
generated
vendored
167
vendor/github.com/qiniu/api.v7/kodo/bucket_test.go
generated
vendored
@@ -1,167 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
batchTestKey = "abatch"
|
||||
batchTestNewKey1 = "abatch/newkey1"
|
||||
batchTestNewKey2 = "abatch/newkey2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
batchTestKey += strconv.Itoa(rand.Int())
|
||||
batchTestNewKey1 += strconv.Itoa(rand.Int())
|
||||
batchTestNewKey2 += strconv.Itoa(rand.Int())
|
||||
// 删除 可能存在的 key
|
||||
bucket.BatchDelete(nil, batchTestKey, batchTestNewKey1, batchTestNewKey2)
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
//上传一个文件用用于测试
|
||||
err := upFile("bucket_test.go", batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, batchTestKey)
|
||||
|
||||
testBatchStat(t)
|
||||
testBatchCopy(t)
|
||||
testBatchMove(t)
|
||||
testBatchDelete(t)
|
||||
testBatch(t)
|
||||
testClient_MakeUptokenBucket(t)
|
||||
testDeleteAfterDays(t)
|
||||
}
|
||||
|
||||
func testBatchStat(t *testing.T) {
|
||||
|
||||
rets, err := bucket.BatchStat(nil, batchTestKey, batchTestKey, batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal("bucket.BatchStat failed:", err)
|
||||
}
|
||||
|
||||
if len(rets) != 3 {
|
||||
t.Fatal("BatchStat failed: len(rets) = ", 3)
|
||||
}
|
||||
|
||||
stat, err := bucket.Stat(nil, batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal("bucket.Stat failed:", err)
|
||||
}
|
||||
|
||||
if rets[0].Data != stat || rets[1].Data != stat || rets[2].Data != stat {
|
||||
t.Fatal("BatchStat failed : returns err")
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchMove(t *testing.T) {
|
||||
|
||||
stat0, err := bucket.Stat(nil, batchTestKey)
|
||||
if err != nil {
|
||||
t.Fatal("BathMove get stat failed:", err)
|
||||
}
|
||||
|
||||
_, err = bucket.BatchMove(nil, KeyPair{batchTestKey, batchTestNewKey1}, KeyPair{batchTestNewKey1, batchTestNewKey2})
|
||||
if err != nil {
|
||||
t.Fatal("bucket.BatchMove failed:", err)
|
||||
}
|
||||
defer bucket.Move(nil, batchTestNewKey2, batchTestKey)
|
||||
|
||||
stat1, err := bucket.Stat(nil, batchTestNewKey2)
|
||||
if err != nil {
|
||||
t.Fatal("BathMove get stat failed:", err)
|
||||
}
|
||||
|
||||
if stat0.Hash != stat1.Hash {
|
||||
t.Fatal("BatchMove failed : Move err", stat0, stat1)
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchCopy(t *testing.T) {
|
||||
|
||||
_, err := bucket.BatchCopy(nil, KeyPair{batchTestKey, batchTestNewKey1}, KeyPair{batchTestKey, batchTestNewKey2})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, batchTestNewKey1)
|
||||
defer bucket.Delete(nil, batchTestNewKey2)
|
||||
|
||||
stat0, _ := bucket.Stat(nil, batchTestKey)
|
||||
stat1, _ := bucket.Stat(nil, batchTestNewKey1)
|
||||
stat2, _ := bucket.Stat(nil, batchTestNewKey2)
|
||||
if stat0.Hash != stat1.Hash || stat0.Hash != stat2.Hash {
|
||||
t.Fatal("BatchCopy failed : Copy err")
|
||||
}
|
||||
}
|
||||
|
||||
func testBatchDelete(t *testing.T) {
|
||||
|
||||
bucket.Copy(nil, batchTestKey, batchTestNewKey1)
|
||||
bucket.Copy(nil, batchTestKey, batchTestNewKey2)
|
||||
|
||||
_, err := bucket.BatchDelete(nil, batchTestNewKey1, batchTestNewKey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err1 := bucket.Stat(nil, batchTestNewKey1)
|
||||
_, err2 := bucket.Stat(nil, batchTestNewKey2)
|
||||
|
||||
//这里 err1 != nil,否则文件没被成功删除
|
||||
if err1 == nil || err2 == nil {
|
||||
t.Fatal("BatchDelete failed : File do not delete")
|
||||
}
|
||||
}
|
||||
|
||||
func testBatch(t *testing.T) {
|
||||
|
||||
ops := []string{
|
||||
URICopy(bucketName, batchTestKey, bucketName, batchTestNewKey1),
|
||||
URIDelete(bucketName, batchTestKey),
|
||||
URIMove(bucketName, batchTestNewKey1, bucketName, batchTestKey),
|
||||
}
|
||||
|
||||
var rets []BatchItemRet
|
||||
err := client.Batch(nil, &rets, ops)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rets) != 3 {
|
||||
t.Fatal("len(rets) != 3")
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteAfterDays(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err := bucket.DeleteAfterDays(ctx, batchTestNewKey1, 5)
|
||||
if err == nil {
|
||||
t.Fatal("Expect an error")
|
||||
}
|
||||
|
||||
bucket.Copy(ctx, batchTestKey, batchTestNewKey1)
|
||||
|
||||
err = bucket.DeleteAfterDays(ctx, batchTestNewKey1, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
52
vendor/github.com/qiniu/api.v7/kodo/doc.go
generated
vendored
52
vendor/github.com/qiniu/api.v7/kodo/doc.go
generated
vendored
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7/kodo 提供了在您的业务服务器(服务端)调用七牛云存储服务的能力
|
||||
|
||||
首先,我们要配置下 AccessKey/SecretKey,这可以在七牛 Portal 中查到:
|
||||
|
||||
kodo.SetMac("your-access-key", "your-secret-key")
|
||||
|
||||
然后我们创建一个 Client 对象:
|
||||
|
||||
zone := kodo.ZoneZ0 // 您空间(Bucket)所在的区域
|
||||
c := kodo.New(zone, nil) // 用默认配置创建 Client
|
||||
|
||||
有了 Client,你就可以操作您的空间(Bucket)了,比如我们要上传一个文件:
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
bucket := c.Bucket("your-bucket-name")
|
||||
ctx := context.Background()
|
||||
...
|
||||
localFile := "/your/local/image/file.jpg"
|
||||
err := bucket.PutFile(ctx, nil, "foo/bar.jpg", localFile, nil)
|
||||
if err != nil {
|
||||
... // 上传文件失败处理
|
||||
return
|
||||
}
|
||||
// 上传文件成功
|
||||
// 这时登录七牛Portal,在 your-bucket-name 空间就可以看到一个 foo/bar.jpg 的文件了
|
||||
|
||||
当然,除了上传文件,各种空间(Bucket)相关的操作都可以有,最常见自然是增删改查了:
|
||||
|
||||
entry, err := bucket.Stat(ctx, "foo/bar.jpg") // 看看空间中是否存在某个文件,其属性是什么
|
||||
bucket.Delete(ctx, "foo/bar.jpg") // 删除空间中的某个文件
|
||||
bucket.ChangeMime(ctx, "foo/bar.jpg", "image/jpeg") // 修改某个文件的 MIME 属性
|
||||
bucket.Move(ctx, "foo/bar.jpg", "new-name.jpg") // 移动文件
|
||||
bucket.Copy(ctx, "foo/bar.jpg", "new-copy-file.jpg") // 复制文件
|
||||
|
||||
等等... 请问怎么下载文件?如果是公开文件,我们只需要:
|
||||
|
||||
import "net/http"
|
||||
|
||||
domain := "domain-of-your-bucket.com" // 您的空间绑定的域名,这个可以在七牛的Portal中查到
|
||||
baseUrl := kodo.MakeBaseUrl(domain, "foo/bar.jpg") // 得到下载 url
|
||||
resp, err := http.Get(baseUrl)
|
||||
...
|
||||
|
||||
但是对于私有空间,事情要复杂一些,访问上面的 baseUrl 会被拒绝。我们需要多做一步:
|
||||
|
||||
privateUrl := c.MakePrivateUrl(baseUrl, nil) // 用默认的下载策略去生成私有下载的 url
|
||||
resp, err := http.Get(privateUrl)
|
||||
...
|
||||
*/
|
||||
package kodo
|
||||
154
vendor/github.com/qiniu/api.v7/kodo/main.go
generated
vendored
154
vendor/github.com/qiniu/api.v7/kodo/main.go
generated
vendored
@@ -1,154 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/api.v7/conf"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type zoneConfig struct {
|
||||
IoHost string
|
||||
UpHosts []string
|
||||
}
|
||||
|
||||
const (
|
||||
// ZoneZ0 华东机房
|
||||
ZoneZ0 = iota
|
||||
// ZoneZ1 华北机房
|
||||
ZoneZ1
|
||||
// ZoneZ2 华南机房
|
||||
ZoneZ2
|
||||
// ZoneNa0 北美机房
|
||||
ZoneNa0
|
||||
)
|
||||
|
||||
var zones = []zoneConfig{
|
||||
// z0 华东机房:
|
||||
{
|
||||
IoHost: "http://iovip.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up.qiniu.com",
|
||||
"http://upload.qiniu.com",
|
||||
"-H up.qiniu.com http://183.136.139.16",
|
||||
},
|
||||
},
|
||||
// z1 华北机房:
|
||||
{
|
||||
IoHost: "http://iovip-z1.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up-z1.qiniu.com",
|
||||
"http://upload-z1.qiniu.com",
|
||||
"-H up-z1.qiniu.com http://106.38.227.27",
|
||||
},
|
||||
},
|
||||
// z2 华南机房:
|
||||
{
|
||||
IoHost: "http://iovip-z2.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up-z2.qiniu.com",
|
||||
"http://upload-z2.qiniu.com",
|
||||
},
|
||||
},
|
||||
// na0 北美机房:
|
||||
{
|
||||
IoHost: "http://iovip-na0.qbox.me",
|
||||
UpHosts: []string{
|
||||
"http://up-na0.qiniu.com",
|
||||
"http://upload-na0.qiniu.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
defaultRsHost = "http://rs.qbox.me"
|
||||
defaultRsfHost = "http://rsf.qbox.me"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
RSHost string
|
||||
RSFHost string
|
||||
APIHost string
|
||||
Scheme string
|
||||
IoHost string
|
||||
UpHosts []string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type Client struct {
|
||||
rpc.Client
|
||||
mac *qbox.Mac
|
||||
Config
|
||||
|
||||
apiCli *api.Client
|
||||
}
|
||||
|
||||
func New(zone int, cfg *Config) (p *Client) {
|
||||
|
||||
p = new(Client)
|
||||
if cfg != nil {
|
||||
p.Config = *cfg
|
||||
}
|
||||
|
||||
p.mac = qbox.NewMac(p.AccessKey, p.SecretKey)
|
||||
p.Client = rpc.Client{qbox.NewClient(p.mac, p.Transport)}
|
||||
|
||||
if p.RSHost == "" {
|
||||
p.RSHost = defaultRsHost
|
||||
}
|
||||
if p.RSFHost == "" {
|
||||
p.RSFHost = defaultRsfHost
|
||||
}
|
||||
if p.Scheme != "https" {
|
||||
p.Scheme = "http"
|
||||
}
|
||||
if p.APIHost == "" {
|
||||
p.APIHost = api.DefaultApiHost
|
||||
}
|
||||
p.apiCli = api.NewClient(p.APIHost, p.Scheme)
|
||||
|
||||
if zone < 0 || zone >= len(zones) {
|
||||
return
|
||||
}
|
||||
if len(p.UpHosts) == 0 {
|
||||
p.UpHosts = zones[zone].UpHosts
|
||||
}
|
||||
if p.IoHost == "" {
|
||||
p.IoHost = zones[zone].IoHost
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewWithoutZone(cfg *Config) (p *Client) {
|
||||
return New(-1, cfg)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 设置全局默认的 ACCESS_KEY, SECRET_KEY 变量。
|
||||
//
|
||||
func SetMac(accessKey, secretKey string) {
|
||||
|
||||
conf.ACCESS_KEY, conf.SECRET_KEY = accessKey, secretKey
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 设置使用这个SDK的应用程序名。userApp 必须满足 [A-Za-z0-9_\ \-\.]*
|
||||
//
|
||||
func SetAppName(userApp string) error {
|
||||
|
||||
return conf.SetAppName(userApp)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
151
vendor/github.com/qiniu/api.v7/kodo/main_test.go
generated
vendored
151
vendor/github.com/qiniu/api.v7/kodo/main_test.go
generated
vendored
@@ -1,151 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
key = "aa"
|
||||
keyFetch = "afetch"
|
||||
newkey1 = "bbbb"
|
||||
newkey2 = "cccc"
|
||||
fetchURL = "http://www-static.u.qiniucdn.com/public/v1645/img/css-sprite.png"
|
||||
bucketName string
|
||||
domain string
|
||||
client *Client
|
||||
bucket = newBucket()
|
||||
|
||||
QINIU_KODO_TEST string
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
key += strconv.Itoa(rand.Int())
|
||||
keyFetch += strconv.Itoa(rand.Int())
|
||||
newkey1 += strconv.Itoa(rand.Int())
|
||||
newkey2 += strconv.Itoa(rand.Int())
|
||||
bucket.BatchDelete(nil, key, keyFetch, newkey1, newkey2)
|
||||
}
|
||||
|
||||
func newBucket() (bucket Bucket) {
|
||||
|
||||
QINIU_KODO_TEST = os.Getenv("QINIU_KODO_TEST")
|
||||
if skipTest() {
|
||||
println("[INFO] QINIU_KODO_TEST: skipping to test github.com/qiniu/api.v7")
|
||||
return
|
||||
}
|
||||
|
||||
ak := os.Getenv("QINIU_ACCESS_KEY")
|
||||
sk := os.Getenv("QINIU_SECRET_KEY")
|
||||
if ak == "" || sk == "" {
|
||||
panic("require ACCESS_KEY & SECRET_KEY")
|
||||
}
|
||||
SetMac(ak, sk)
|
||||
|
||||
bucketName = os.Getenv("QINIU_TEST_BUCKET")
|
||||
domain = os.Getenv("QINIU_TEST_DOMAIN")
|
||||
if bucketName == "" || domain == "" {
|
||||
panic("require test env")
|
||||
}
|
||||
client = NewWithoutZone(nil)
|
||||
|
||||
return client.Bucket(bucketName)
|
||||
}
|
||||
|
||||
func skipTest() bool {
|
||||
|
||||
return QINIU_KODO_TEST == ""
|
||||
}
|
||||
|
||||
func upFile(localFile, key string) error {
|
||||
|
||||
return bucket.PutFile(nil, nil, key, localFile, nil)
|
||||
}
|
||||
|
||||
func TestFetch(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
err := bucket.Fetch(nil, keyFetch, fetchURL)
|
||||
if err != nil {
|
||||
t.Fatal("bucket.Fetch failed:", err)
|
||||
}
|
||||
|
||||
entry, err := bucket.Stat(nil, keyFetch)
|
||||
if err != nil || entry.MimeType != "image/png" {
|
||||
t.Fatal("bucket.Fetch: Stat failed -", err, "entry:", entry)
|
||||
}
|
||||
fmt.Println(entry)
|
||||
}
|
||||
|
||||
func TestEntry(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
//上传一个文件用用于测试
|
||||
err := upFile("doc.go", key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, key)
|
||||
|
||||
einfo, err := bucket.Stat(nil, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mime := "text/plain"
|
||||
err = bucket.ChangeMime(nil, key, mime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
einfo, err = bucket.Stat(nil, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if einfo.MimeType != mime {
|
||||
t.Fatal("mime type did not change")
|
||||
}
|
||||
|
||||
err = bucket.Copy(nil, key, newkey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enewinfo, err := bucket.Stat(nil, newkey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if einfo.Hash != enewinfo.Hash {
|
||||
t.Fatal("invalid entryinfo:", einfo, enewinfo)
|
||||
}
|
||||
err = bucket.Move(nil, newkey1, newkey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enewinfo2, err := bucket.Stat(nil, newkey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if enewinfo.Hash != enewinfo2.Hash {
|
||||
t.Fatal("invalid entryinfo:", enewinfo, enewinfo2)
|
||||
}
|
||||
|
||||
err = bucket.Delete(nil, newkey2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
119
vendor/github.com/qiniu/api.v7/kodo/token.go
generated
vendored
119
vendor/github.com/qiniu/api.v7/kodo/token.go
generated
vendored
@@ -1,119 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/x/url.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 根据空间(Bucket)的域名,以及文件的 key,获得 baseUrl。
|
||||
// 如果空间是 public 的,那么通过 baseUrl 可以直接下载文件内容。
|
||||
// 如果空间是 private 的,那么需要对 baseUrl 进行私有签名得到一个临时有效的 privateUrl 进行下载。
|
||||
//
|
||||
func MakeBaseUrl(domain, key string) (baseUrl string) {
|
||||
|
||||
return "http://" + domain + "/" + url.Escape(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type GetPolicy struct {
|
||||
Expires uint32
|
||||
}
|
||||
|
||||
func (p *Client) MakePrivateUrl(baseUrl string, policy *GetPolicy) (privateUrl string) {
|
||||
|
||||
var expires int64
|
||||
if policy == nil || policy.Expires == 0 {
|
||||
expires = 3600
|
||||
} else {
|
||||
expires = int64(policy.Expires)
|
||||
}
|
||||
deadline := time.Now().Unix() + expires
|
||||
|
||||
if strings.Contains(baseUrl, "?") {
|
||||
baseUrl += "&e="
|
||||
} else {
|
||||
baseUrl += "?e="
|
||||
}
|
||||
baseUrl += strconv.FormatInt(deadline, 10)
|
||||
|
||||
token := qbox.Sign(p.mac, []byte(baseUrl))
|
||||
return baseUrl + "&token=" + token
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
type PutPolicy struct {
|
||||
Scope string `json:"scope"`
|
||||
Expires uint32 `json:"deadline"` // 截止时间(以秒为单位)
|
||||
InsertOnly uint16 `json:"insertOnly,omitempty"` // 若非0, 即使Scope为 Bucket:Key 的形式也是insert only
|
||||
DetectMime uint8 `json:"detectMime,omitempty"` // 若非0, 则服务端根据内容自动确定 MimeType
|
||||
CallbackFetchKey uint8 `json:"callbackFetchKey,omitempty"`
|
||||
FsizeLimit int64 `json:"fsizeLimit,omitempty"`
|
||||
MimeLimit string `json:"mimeLimit,omitempty"`
|
||||
SaveKey string `json:"saveKey,omitempty"`
|
||||
CallbackUrl string `json:"callbackUrl,omitempty"`
|
||||
CallbackHost string `json:"callbackHost,omitempty"`
|
||||
CallbackBody string `json:"callbackBody,omitempty"`
|
||||
CallbackBodyType string `json:"callbackBodyType,omitempty"`
|
||||
ReturnUrl string `json:"returnUrl,omitempty"`
|
||||
ReturnBody string `json:"returnBody,omitempty"`
|
||||
PersistentOps string `json:"persistentOps,omitempty"`
|
||||
PersistentNotifyUrl string `json:"persistentNotifyUrl,omitempty"`
|
||||
PersistentPipeline string `json:"persistentPipeline,omitempty"`
|
||||
AsyncOps string `json:"asyncOps,omitempty"`
|
||||
EndUser string `json:"endUser,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"` // 格式:<HashName>:<HexHashValue>,目前支持 MD5/SHA1。
|
||||
UpHosts []string `json:"uphosts,omitempty"`
|
||||
DeleteAfterDays int `json:"deleteAfterDays,omitempty"`
|
||||
FileType int `json:"fileType,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Client) MakeUptoken(policy *PutPolicy) string {
|
||||
token, err := p.MakeUptokenWithSafe(policy)
|
||||
if err != nil {
|
||||
fmt.Errorf("makeuptoken failed: policy: %+v, error: %+v", policy, err)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *Client) MakeUptokenWithSafe(policy *PutPolicy) (token string, err error) {
|
||||
var rr = *policy
|
||||
if len(rr.UpHosts) == 0 {
|
||||
bucketName := getBucketNameFromPutPolicy(policy)
|
||||
bucketInfo, err1 := p.GetBucketInfo(bucketName)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
rr.UpHosts = bucketInfo.UpHosts
|
||||
}
|
||||
if rr.Expires == 0 {
|
||||
rr.Expires = 3600
|
||||
}
|
||||
rr.Expires += uint32(time.Now().Unix())
|
||||
b, _ := json.Marshal(&rr)
|
||||
token = qbox.SignWithData(p.mac, b)
|
||||
return
|
||||
}
|
||||
|
||||
func getBucketNameFromPutPolicy(policy *PutPolicy) (bucketName string) {
|
||||
scope := policy.Scope
|
||||
bucketName = strings.Split(scope, ":")[0]
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Client) GetBucketInfo(bucketName string) (bucketInfo api.BucketInfo, err error) {
|
||||
return p.apiCli.GetBucketInfo(p.mac.AccessKey, bucketName)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
70
vendor/github.com/qiniu/api.v7/kodo/token_test.go
generated
vendored
70
vendor/github.com/qiniu/api.v7/kodo/token_test.go
generated
vendored
@@ -1,70 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
// 删除 可能存在的 key
|
||||
bucket.BatchDelete(nil, key)
|
||||
}
|
||||
|
||||
func TestGetPrivateUrl(t *testing.T) {
|
||||
|
||||
if skipTest() {
|
||||
return
|
||||
}
|
||||
|
||||
// 上传一个文件用用于测试
|
||||
err := upFile("token.go", key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer bucket.Delete(nil, key)
|
||||
|
||||
baseUrl := MakeBaseUrl(domain, key)
|
||||
privateUrl := client.MakePrivateUrl(baseUrl, nil)
|
||||
|
||||
resp, err := http.Get(privateUrl)
|
||||
if err != nil {
|
||||
t.Fatal("http.Get failed:", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
h := sha1.New()
|
||||
io.Copy(h, resp.Body)
|
||||
etagExpected := base64.URLEncoding.EncodeToString(h.Sum([]byte{'\x16'}))
|
||||
|
||||
etag := resp.Header.Get("Etag")
|
||||
if etag[1:len(etag)-1] != etagExpected {
|
||||
t.Fatal("http.Get etag failed:", etag, etagExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func testClient_MakeUptokenBucket(t *testing.T) {
|
||||
c := New(0, nil)
|
||||
token := c.MakeUptoken(&PutPolicy{
|
||||
Scope: "gosdk",
|
||||
Expires: 3600,
|
||||
})
|
||||
if token == "" {
|
||||
t.Fatal("nil token")
|
||||
}
|
||||
|
||||
token, err := c.MakeUptokenWithSafe(&PutPolicy{
|
||||
Scope: "NotExistBucket",
|
||||
Expires: 3600,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("make up token fail")
|
||||
}
|
||||
}
|
||||
179
vendor/github.com/qiniu/api.v7/kodo/upload.go
generated
vendored
179
vendor/github.com/qiniu/api.v7/kodo/upload.go
generated
vendored
@@ -1,179 +0,0 @@
|
||||
package kodo
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/api.v7/kodocli"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
type PutExtra kodocli.PutExtra
|
||||
type RputExtra kodocli.RputExtra
|
||||
type PutRet kodocli.PutRet
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Bucket) makeUptoken(key string) string {
|
||||
|
||||
policy := &PutPolicy{
|
||||
Scope: p.Name + ":" + key,
|
||||
Expires: 3600,
|
||||
UpHosts: p.UpHosts,
|
||||
}
|
||||
return p.Conn.MakeUptoken(policy)
|
||||
}
|
||||
|
||||
func (p Bucket) makeUptokenWithoutKey() string {
|
||||
|
||||
policy := &PutPolicy{
|
||||
Scope: p.Name,
|
||||
Expires: 3600,
|
||||
UpHosts: p.UpHosts,
|
||||
}
|
||||
return p.Conn.MakeUptoken(policy)
|
||||
}
|
||||
|
||||
func (p Bucket) makeUploader() kodocli.Uploader {
|
||||
|
||||
c := &http.Client{Transport: p.Conn.Transport}
|
||||
return kodocli.Uploader{Conn: rpc.Client{c}, UpHosts: p.UpHosts, ApiCli: p.Conn.apiCli}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Put 上传一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) Put(
|
||||
ctx Context, ret interface{}, key string, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.Put(ctx, ret, uptoken, key, data, size, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// PutWithoutKey 上传一个文件。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) PutWithoutKey(
|
||||
ctx Context, ret interface{}, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.PutWithoutKey(ctx, ret, uptoken, data, size, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// PutFile 上传一个文件。
|
||||
// 和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) PutFile(
|
||||
ctx Context, ret interface{}, key, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.PutFile(ctx, ret, uptoken, key, localFile, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// PutFileWithoutKey 上传一个文件。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
// 和 PutWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。返回的是 PutRet 结构。可选,可以传 nil 表示不感兴趣。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) PutFileWithoutKey(
|
||||
ctx Context, ret interface{}, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.PutFileWithoutKey(ctx, ret, uptoken, localFile, (*kodocli.PutExtra)(extra))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// Rput 上传一个文件,支持断点续传和分块上传。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口。考虑到需要支持分块上传和断点续传,要的是 io.ReaderAt 接口,而不是 io.Reader。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) Rput(
|
||||
ctx Context, ret interface{}, key string, data io.ReaderAt, size int64, extra *RputExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.Rput(ctx, ret, uptoken, key, data, size, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// RputWithoutKey 上传一个文件,支持断点续传和分块上传。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// data 是文件内容的访问接口。考虑到需要支持分块上传和断点续传,要的是 io.ReaderAt 接口,而不是 io.Reader。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) RputWithoutKey(
|
||||
ctx Context, ret interface{}, data io.ReaderAt, size int64, extra *RputExtra) error {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.RputWithoutKey(ctx, ret, uptoken, data, size, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// RputFile 上传一个文件,支持断点续传和分块上传。
|
||||
// 和 Rput 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.ReaderAt 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) RputFile(
|
||||
ctx Context, ret interface{}, key, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptoken(key)
|
||||
return uploader.RputFile(ctx, ret, uptoken, key, localFile, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// RputFileWithoutKey 上传一个文件,支持断点续传和分块上传。自动以文件的 hash 作为文件的访问路径(key)。
|
||||
// 和 RputWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.ReaderAt 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 RputExtra 结构的描述。
|
||||
//
|
||||
func (p Bucket) RputFileWithoutKey(
|
||||
ctx Context, ret interface{}, localFile string, extra *RputExtra) (err error) {
|
||||
|
||||
uploader := p.makeUploader()
|
||||
uptoken := p.makeUptokenWithoutKey()
|
||||
return uploader.RputFileWithoutKey(ctx, ret, uptoken, localFile, (*kodocli.RputExtra)(extra))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
67
vendor/github.com/qiniu/api.v7/kodocli/doc.go
generated
vendored
67
vendor/github.com/qiniu/api.v7/kodocli/doc.go
generated
vendored
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
包 github.com/qiniu/api.v7/kodocli 提供了在客户端调用七牛云存储部分服务的能力
|
||||
|
||||
客户端,严谨说是非可信环境,主要是指在用户端执行的环境,比如:Android/iOS 设备、Windows/Mac/Linux 桌面环境、也包括浏览器(如果浏览器能够执行 Go 语言代码的话)。
|
||||
|
||||
注意,在这种场合下您不应该在任何地方配置 AccessKey/SecretKey。泄露 AccessKey/SecretKey 如同泄露您的用户名/密码一样十分危险,会影响您的数据安全。
|
||||
|
||||
第一个问题是如何上传文件。因为是在非可信环境,所以我们首先是要授予它有上传文件的能力。答案是给它颁发上传凭证。假设服务端也是 Go 语言,那么:
|
||||
|
||||
import "github.com/qiniu/api.v7/kodo"
|
||||
|
||||
kodo.SetMac("your-access-key", "your-secret-key") // 配置 AccessKey/SecretKey
|
||||
|
||||
zone := kodo.ZoneZ0
|
||||
c := kodo.New(zone, nil) // 创建一个 Client 对象
|
||||
|
||||
bucket := "your-bucket-name"
|
||||
key := "foo/bar.jpg"
|
||||
policy := &kodo.PutPolicy{
|
||||
Scope: bucket + ":" + key, // 上传文件的限制条件,这里限制只能上传一个名为 "foo/bar.jpg" 的文件
|
||||
Expires: 3600, // 这是限制上传凭证(uptoken)的过期时长,3600 是一小时
|
||||
...
|
||||
}
|
||||
uptoken := c.MakeUptoken(policy) // 生成上传凭证
|
||||
|
||||
生成上传凭证之后,通过某种方式将 uptoken 发送到客户端。这样客户端就可以上传文件了:
|
||||
|
||||
zone := 0
|
||||
uploader := kodocli.NewUploader(zone, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
key := "foo/bar.jpg"
|
||||
localFile := "/your/local/image/file.jpg"
|
||||
err := uploader.PutFile(ctx, nil, uptoken, key, localFile, nil)
|
||||
if err != nil {
|
||||
... // 上传文件失败处理
|
||||
return
|
||||
}
|
||||
|
||||
注意,如果客户端上传的 key 不是 uptoken 所要求的 "foo/bar.jpg",那么上传就会被拒绝。
|
||||
|
||||
如果我们希望一个 uptoken 可以上传多个文件,那么服务端颁发 uptoken 的代码需要调整下:
|
||||
|
||||
bucket := "your-bucket-name"
|
||||
policy := &kodo.PutPolicy{
|
||||
Scope: bucket, // 上传文件的限制条件,这里是只限制了要上传到 "your-bucket-name" 空间
|
||||
Expires: 3600, // 这是限制上传凭证(uptoken)的过期时长,3600 是一小时
|
||||
...
|
||||
}
|
||||
uptoken := c.MakeUptoken(policy)
|
||||
|
||||
颁发这样的 uptoken 给客户端,客户端就可以用它上传任意名字(key)的文件,前提是服务器上还没有同名的文件。
|
||||
|
||||
特别需要注意的是,这种情况下服务端会拒绝已经有同名文件的上传请求,而不是覆盖服务器上已有的文件。这是出于数据安全的考虑。我们并不非常推荐 uptoken 复用的做法,除了注意文件名可能冲突外,还需要注意 uptoken 是有时效的,过期就需要重新向服务器申请新的上传凭证。
|
||||
|
||||
搞定文件的上传后,第二个问题是如何下载文件。如果是公开文件,我们只需要:
|
||||
|
||||
import "net/http"
|
||||
|
||||
domain := "domain-of-your-bucket.com" // 您的空间绑定的域名,这个可以在七牛的Portal中查到
|
||||
baseUrl := kodocli.MakeBaseUrl(domain, "foo/bar.jpg") // 得到下载 url
|
||||
resp, err := http.Get(baseUrl)
|
||||
...
|
||||
|
||||
但是对于私有空间,事情要复杂一些,需要客户端向您的业务服务器申请下载该文件。业务服务器确认该用户有权访问,则返回一个临时有效的 privateUrl。具体如何生成 privateUrl,可以看服务端 SDK 相关的文档介绍。
|
||||
*/
|
||||
package kodocli
|
||||
114
vendor/github.com/qiniu/api.v7/kodocli/main.go
generated
vendored
114
vendor/github.com/qiniu/api.v7/kodocli/main.go
generated
vendored
@@ -1,114 +0,0 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qiniu/api.v7/api"
|
||||
"github.com/qiniu/api.v7/conf"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
"github.com/qiniu/x/url.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type zoneConfig struct {
|
||||
UpHosts []string
|
||||
}
|
||||
|
||||
var zones = []zoneConfig{
|
||||
// z0:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://upload.qiniu.com",
|
||||
"http://up.qiniu.com",
|
||||
"-H up.qiniu.com http://183.136.139.16",
|
||||
},
|
||||
},
|
||||
// z1:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://upload-z1.qiniu.com",
|
||||
"http://up-z1.qiniu.com",
|
||||
"-H up-z1.qiniu.com http://106.38.227.27",
|
||||
},
|
||||
},
|
||||
// z2 华南机房:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://up-z2.qiniu.com",
|
||||
"http://upload-z2.qiniu.com",
|
||||
},
|
||||
},
|
||||
// na0 北美机房:
|
||||
{
|
||||
UpHosts: []string{
|
||||
"http://up-na0.qiniu.com",
|
||||
"http://upload-na0.qiniu.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type UploadConfig struct {
|
||||
UpHosts []string
|
||||
Transport http.RoundTripper
|
||||
|
||||
APIHost string
|
||||
Scheme string
|
||||
}
|
||||
|
||||
type Uploader struct {
|
||||
Conn rpc.Client
|
||||
UpHosts []string
|
||||
ApiCli *api.Client
|
||||
}
|
||||
|
||||
func NewUploader(zone int, cfg *UploadConfig) (p Uploader) {
|
||||
|
||||
var uc UploadConfig
|
||||
if cfg != nil {
|
||||
uc = *cfg
|
||||
}
|
||||
if uc.Scheme != "https" {
|
||||
uc.Scheme = "http"
|
||||
}
|
||||
if uc.APIHost == "" {
|
||||
uc.APIHost = api.DefaultApiHost
|
||||
}
|
||||
if len(uc.UpHosts) == 0 {
|
||||
if zone > 0 && zone < len(zones) {
|
||||
uc.UpHosts = zones[zone].UpHosts
|
||||
}
|
||||
}
|
||||
|
||||
p.UpHosts = uc.UpHosts
|
||||
p.Conn.Client = &http.Client{Transport: uc.Transport}
|
||||
p.ApiCli = api.NewClient(uc.APIHost, uc.Scheme)
|
||||
return
|
||||
}
|
||||
|
||||
func NewUploaderWithoutZone(cfg *UploadConfig) (p Uploader) {
|
||||
return NewUploader(-1, cfg)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 根据空间(Bucket)的域名,以及文件的 key,获得 baseUrl。
|
||||
// 如果空间是 public 的,那么通过 baseUrl 可以直接下载文件内容。
|
||||
// 如果空间是 private 的,那么需要对 baseUrl 进行私有签名得到一个临时有效的 privateUrl 进行下载。
|
||||
//
|
||||
func MakeBaseUrl(domain, key string) (baseUrl string) {
|
||||
return "http://" + domain + "/" + url.Escape(key)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 设置使用这个SDK的应用程序名。userApp 必须满足 [A-Za-z0-9_\ \-\.]*
|
||||
//
|
||||
func SetAppName(userApp string) error {
|
||||
|
||||
return conf.SetAppName(userApp)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
172
vendor/github.com/qiniu/api.v7/kodocli/resumable_base.go
generated
vendored
172
vendor/github.com/qiniu/api.v7/kodocli/resumable_base.go
generated
vendored
@@ -1,172 +0,0 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/qiniu/x/bytes.v7"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
"github.com/qiniu/x/xlog.v7"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type uptokenTransport struct {
|
||||
token string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *uptokenTransport) NestedObject() interface{} {
|
||||
return t.Transport
|
||||
}
|
||||
|
||||
func (t *uptokenTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
req.Header.Set("Authorization", t.token)
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func newUptokenTransport(token string, transport http.RoundTripper) *uptokenTransport {
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
return &uptokenTransport{"UpToken " + token, transport}
|
||||
}
|
||||
|
||||
func newUptokenClient(token string, transport http.RoundTripper) *http.Client {
|
||||
t := newUptokenTransport(token, transport)
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) mkblk(
|
||||
ctx Context, uphosts []string, ret *BlkputRet, blockSize int, body io.Reader, size int) error {
|
||||
|
||||
url := uphosts[0] + "/mkblk/" + strconv.Itoa(blockSize)
|
||||
return p.Conn.CallWith(ctx, ret, "POST", url, "application/octet-stream", body, size)
|
||||
}
|
||||
|
||||
func (p Uploader) bput(
|
||||
ctx Context, ret *BlkputRet, body io.Reader, size int) error {
|
||||
|
||||
url := ret.Host + "/bput/" + ret.Ctx + "/" + strconv.FormatUint(uint64(ret.Offset), 10)
|
||||
return p.Conn.CallWith(ctx, ret, "POST", url, "application/octet-stream", body, size)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) resumableBput(
|
||||
ctx Context, uphosts []string, ret *BlkputRet, f io.ReaderAt, blkIdx, blkSize int, extra *RputExtra) (err error) {
|
||||
|
||||
log := xlog.NewWith(ctx)
|
||||
h := crc32.NewIEEE()
|
||||
offbase := int64(blkIdx) << blockBits
|
||||
chunkSize := extra.ChunkSize
|
||||
|
||||
var bodyLength int
|
||||
|
||||
if ret.Ctx == "" {
|
||||
|
||||
if chunkSize < blkSize {
|
||||
bodyLength = chunkSize
|
||||
} else {
|
||||
bodyLength = blkSize
|
||||
}
|
||||
|
||||
body1 := io.NewSectionReader(f, offbase, int64(bodyLength))
|
||||
body := io.TeeReader(body1, h)
|
||||
|
||||
err = p.mkblk(ctx, uphosts, ret, blkSize, body, bodyLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ret.Crc32 != h.Sum32() || int(ret.Offset) != bodyLength {
|
||||
err = ErrUnmatchedChecksum
|
||||
return
|
||||
}
|
||||
extra.Notify(blkIdx, blkSize, ret)
|
||||
}
|
||||
|
||||
for int(ret.Offset) < blkSize {
|
||||
|
||||
if chunkSize < blkSize-int(ret.Offset) {
|
||||
bodyLength = chunkSize
|
||||
} else {
|
||||
bodyLength = blkSize - int(ret.Offset)
|
||||
}
|
||||
|
||||
tryTimes := extra.TryTimes
|
||||
|
||||
lzRetry:
|
||||
h.Reset()
|
||||
body1 := io.NewSectionReader(f, offbase+int64(ret.Offset), int64(bodyLength))
|
||||
body := io.TeeReader(body1, h)
|
||||
|
||||
err = p.bput(ctx, ret, body, bodyLength)
|
||||
if err == nil {
|
||||
if ret.Crc32 == h.Sum32() {
|
||||
extra.Notify(blkIdx, blkSize, ret)
|
||||
continue
|
||||
}
|
||||
log.Warn("ResumableBlockput: invalid checksum, retry")
|
||||
err = ErrUnmatchedChecksum
|
||||
} else {
|
||||
if ei, ok := err.(*rpc.ErrorInfo); ok && ei.Code == InvalidCtx {
|
||||
ret.Ctx = "" // reset
|
||||
log.Warn("ResumableBlockput: invalid ctx, please retry")
|
||||
return
|
||||
}
|
||||
log.Warn("ResumableBlockput: bput failed -", err)
|
||||
}
|
||||
if tryTimes > 1 {
|
||||
tryTimes--
|
||||
log.Info("ResumableBlockput retrying ...")
|
||||
goto lzRetry
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func (p Uploader) mkfile(
|
||||
ctx Context, uphosts []string, ret interface{}, key string, hasKey bool, fsize int64, extra *RputExtra) (err error) {
|
||||
|
||||
url := uphosts[0] + "/mkfile/" + strconv.FormatInt(fsize, 10)
|
||||
|
||||
if extra.MimeType != "" {
|
||||
url += "/mimeType/" + encode(extra.MimeType)
|
||||
}
|
||||
if hasKey {
|
||||
url += "/key/" + encode(key)
|
||||
}
|
||||
for k, v := range extra.Params {
|
||||
url += fmt.Sprintf("/%s/%s", k, encode(v))
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 176*len(extra.Progresses))
|
||||
for _, prog := range extra.Progresses {
|
||||
buf = append(buf, prog.Ctx...)
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
if len(buf) > 0 {
|
||||
buf = buf[:len(buf)-1]
|
||||
}
|
||||
|
||||
return p.Conn.CallWith(
|
||||
ctx, ret, "POST", url, "application/octet-stream", bytes.NewReader(buf), len(buf))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func encode(raw string) string {
|
||||
return base64.URLEncoding.EncodeToString([]byte(raw))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
282
vendor/github.com/qiniu/api.v7/kodocli/upload.go
generated
vendored
282
vendor/github.com/qiniu/api.v7/kodocli/upload.go
generated
vendored
@@ -1,282 +0,0 @@
|
||||
package kodocli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "context"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const (
|
||||
DontCheckCrc = 0
|
||||
CalcAndCheckCrc = 1
|
||||
CheckCrc = 2
|
||||
)
|
||||
|
||||
// 上传的额外可选项
|
||||
//
|
||||
type PutExtra struct {
|
||||
// 可选,用户自定义参数,必须以 "x:" 开头。若不以x:开头,则忽略。
|
||||
Params map[string]string
|
||||
|
||||
// 可选,当为 "" 时候,服务端自动判断。
|
||||
MimeType string
|
||||
|
||||
Crc32 uint32
|
||||
|
||||
// CheckCrc == 0 (DontCheckCrc): 表示不进行 crc32 校验
|
||||
// CheckCrc == 1 (CalcAndCheckCrc): 对于 Put 等同于 CheckCrc = 2;对于 PutFile 会自动计算 crc32 值
|
||||
// CheckCrc == 2 (CheckCrc): 表示进行 crc32 校验,且 crc32 值就是上面的 Crc32 变量
|
||||
CheckCrc uint32
|
||||
|
||||
// 上传事件:进度通知。这个事件的回调函数应该尽可能快地结束。
|
||||
OnProgress func(fsize, uploaded int64)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 如果 uptoken 没有指定 ReturnBody,那么返回值是标准的 PutRet 结构
|
||||
//
|
||||
type PutRet struct {
|
||||
Hash string `json:"hash"`
|
||||
PersistentId string `json:"persistentId"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 上传一个文件。
|
||||
// 和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) PutFile(
|
||||
ctx Context, ret interface{}, uptoken, key, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
return p.putFile(ctx, ret, uptoken, key, true, localFile, extra)
|
||||
}
|
||||
|
||||
// 上传一个文件。文件的访问路径(key)自动生成。
|
||||
// 如果 uptoken 中设置了 SaveKey,那么按 SaveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
// 和 RputWithoutKey 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) PutFileWithoutKey(
|
||||
ctx Context, ret interface{}, uptoken, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
return p.putFile(ctx, ret, uptoken, "", false, localFile, extra)
|
||||
}
|
||||
|
||||
func (p Uploader) putFile(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
f, err := os.Open(localFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fsize := fi.Size()
|
||||
|
||||
if extra != nil && extra.CheckCrc == 1 {
|
||||
extra.Crc32, err = getFileCrc32(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return p.put(ctx, ret, uptoken, key, hasKey, f, fsize, extra, filepath.Base(localFile))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
// 上传一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) Put(
|
||||
ctx Context, ret interface{}, uptoken, key string, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
return p.put(ctx, ret, uptoken, key, true, data, size, extra, path.Base(key))
|
||||
}
|
||||
|
||||
// 上传一个文件。文件的访问路径(key)自动生成。
|
||||
// 如果 uptoken 中设置了 SaveKey,那么按 SaveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p Uploader) PutWithoutKey(
|
||||
ctx Context, ret interface{}, uptoken string, data io.Reader, size int64, extra *PutExtra) error {
|
||||
|
||||
return p.put(ctx, ret, uptoken, "", false, data, size, extra, "filename")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
var defaultPutExtra PutExtra
|
||||
|
||||
func (p Uploader) put(
|
||||
ctx Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, data io.Reader, size int64, extra *PutExtra, fileName string) error {
|
||||
|
||||
uphosts, err := p.getUpHostFromToken(uptoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var b bytes.Buffer
|
||||
writer := multipart.NewWriter(&b)
|
||||
|
||||
if extra == nil {
|
||||
extra = &defaultPutExtra
|
||||
}
|
||||
|
||||
if extra.OnProgress != nil {
|
||||
data = &readerWithProgress{reader: data, fsize: size, onProgress: extra.OnProgress}
|
||||
}
|
||||
|
||||
err = writeMultipart(writer, uptoken, key, hasKey, extra, fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastLine := fmt.Sprintf("\r\n--%s--\r\n", writer.Boundary())
|
||||
r := strings.NewReader(lastLine)
|
||||
|
||||
bodyLen := int64(-1)
|
||||
if size >= 0 {
|
||||
bodyLen = int64(b.Len()) + size + int64(len(lastLine))
|
||||
}
|
||||
mr := io.MultiReader(&b, data, r)
|
||||
|
||||
contentType := writer.FormDataContentType()
|
||||
err = p.Conn.CallWith64(ctx, ret, "POST", uphosts[0], contentType, mr, bodyLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if extra.OnProgress != nil {
|
||||
extra.OnProgress(size, size)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type readerWithProgress struct {
|
||||
reader io.Reader
|
||||
uploaded int64
|
||||
fsize int64
|
||||
onProgress func(fsize, uploaded int64)
|
||||
}
|
||||
|
||||
func (p *readerWithProgress) Read(b []byte) (n int, err error) {
|
||||
|
||||
if p.uploaded > 0 {
|
||||
p.onProgress(p.fsize, p.uploaded)
|
||||
}
|
||||
|
||||
n, err = p.reader.Read(b)
|
||||
p.uploaded += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func writeMultipart(
|
||||
writer *multipart.Writer, uptoken, key string, hasKey bool, extra *PutExtra, fileName string) (err error) {
|
||||
|
||||
//token
|
||||
if err = writer.WriteField("token", uptoken); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//key
|
||||
if hasKey {
|
||||
if err = writer.WriteField("key", key); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//extra.Params
|
||||
if extra.Params != nil {
|
||||
for k, v := range extra.Params {
|
||||
if strings.HasPrefix(k, "x:") {
|
||||
err = writer.WriteField(k, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//extra.CheckCrc
|
||||
if extra.CheckCrc != 0 {
|
||||
err = writer.WriteField("crc32", strconv.FormatInt(int64(extra.Crc32), 10))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//file
|
||||
head := make(textproto.MIMEHeader)
|
||||
head.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, escapeQuotes(fileName)))
|
||||
if extra.MimeType != "" {
|
||||
head.Set("Content-Type", extra.MimeType)
|
||||
}
|
||||
|
||||
_, err = writer.CreatePart(head)
|
||||
return err
|
||||
}
|
||||
|
||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
|
||||
func escapeQuotes(s string) string {
|
||||
return quoteEscaper.Replace(s)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func getFileCrc32(f *os.File) (uint32, error) {
|
||||
|
||||
h := crc32.NewIEEE()
|
||||
_, err := io.Copy(h, f)
|
||||
f.Seek(0, 0)
|
||||
|
||||
return h.Sum32(), err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
172
vendor/github.com/qiniu/api.v7/storage/base64_upload.go
generated
vendored
Normal file
172
vendor/github.com/qiniu/api.v7/storage/base64_upload.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
// Base64Uploader 表示一个Base64上传对象
|
||||
type Base64Uploader struct {
|
||||
client *rpc.Client
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
// NewBase64Uploader 用来构建一个Base64上传的对象
|
||||
func NewBase64Uploader(cfg *Config) *Base64Uploader {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
return &Base64Uploader{
|
||||
client: &rpc.DefaultClient,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBase64UploaderEx 用来构建一个Base64上传的对象
|
||||
func NewBase64UploaderEx(cfg *Config, client *rpc.Client) *Base64Uploader {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
client = &rpc.DefaultClient
|
||||
}
|
||||
|
||||
return &Base64Uploader{
|
||||
client: client,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Base64PutExtra 为Base64上传的额外可选项
|
||||
type Base64PutExtra struct {
|
||||
// 可选,用户自定义参数,必须以 "x:" 开头。若不以x:开头,则忽略。
|
||||
Params map[string]string
|
||||
|
||||
// 可选,当为 "" 时候,服务端自动判断。
|
||||
MimeType string
|
||||
}
|
||||
|
||||
// Put 用来以Base64方式上传一个文件
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 callbackUrl 或 returnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// base64Data 是要上传的Base64数据,一般为图片数据的Base64编码字符串
|
||||
// extra 是上传的一些可选项,可以指定为nil。详细见 Base64PutExtra 结构的描述。
|
||||
//
|
||||
func (p *Base64Uploader) Put(
|
||||
ctx context.Context, ret interface{}, uptoken, key string, base64Data []byte, extra *Base64PutExtra) (err error) {
|
||||
return p.put(ctx, ret, uptoken, key, true, base64Data, extra)
|
||||
}
|
||||
|
||||
// PutWithoutKey 用来以Base64方式上传一个文件,保存的文件名以文件的内容hash作为文件名
|
||||
func (p *Base64Uploader) PutWithoutKey(
|
||||
ctx context.Context, ret interface{}, uptoken string, base64Data []byte, extra *Base64PutExtra) (err error) {
|
||||
return p.put(ctx, ret, uptoken, "", false, base64Data, extra)
|
||||
}
|
||||
|
||||
func (p *Base64Uploader) put(
|
||||
ctx context.Context, ret interface{}, uptoken, key string, hasKey bool, base64Data []byte, extra *Base64PutExtra) (err error) {
|
||||
//get up host
|
||||
ak, bucket, gErr := getAkBucketFromUploadToken(uptoken)
|
||||
if gErr != nil {
|
||||
err = gErr
|
||||
return
|
||||
}
|
||||
|
||||
var upHost string
|
||||
upHost, err = p.upHost(ak, bucket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//set default extra
|
||||
if extra == nil {
|
||||
extra = &Base64PutExtra{}
|
||||
}
|
||||
|
||||
//calc crc32
|
||||
h := crc32.NewIEEE()
|
||||
rawReader := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(base64Data))
|
||||
fsize, decodeErr := io.Copy(h, rawReader)
|
||||
if decodeErr != nil {
|
||||
err = fmt.Errorf("invalid base64 data, %s", decodeErr.Error())
|
||||
return
|
||||
}
|
||||
fCrc32 := h.Sum32()
|
||||
|
||||
postPath := bytes.NewBufferString("/putb64")
|
||||
//add fsize
|
||||
postPath.WriteString("/")
|
||||
postPath.WriteString(strconv.Itoa(int(fsize)))
|
||||
|
||||
//add key
|
||||
if hasKey {
|
||||
postPath.WriteString("/key/")
|
||||
postPath.WriteString(base64.URLEncoding.EncodeToString([]byte(key)))
|
||||
}
|
||||
//add mimeType
|
||||
if extra.MimeType != "" {
|
||||
postPath.WriteString("/mimeType/")
|
||||
postPath.WriteString(base64.URLEncoding.EncodeToString([]byte(extra.MimeType)))
|
||||
}
|
||||
|
||||
//add crc32
|
||||
postPath.WriteString("/crc32/")
|
||||
postPath.WriteString(fmt.Sprintf("%d", fCrc32))
|
||||
|
||||
//add extra params
|
||||
if len(extra.Params) > 0 {
|
||||
for k, v := range extra.Params {
|
||||
if strings.HasPrefix(k, "x:") && v != "" {
|
||||
postPath.WriteString("/")
|
||||
postPath.WriteString(k)
|
||||
postPath.WriteString("/")
|
||||
postPath.WriteString(base64.URLEncoding.EncodeToString([]byte(v)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postURL := fmt.Sprintf("%s%s", upHost, postPath.String())
|
||||
postClient := newUptokenClient(p.client, uptoken)
|
||||
return postClient.CallWith(ctx, ret, "POST", postURL, "application/octet-stream",
|
||||
bytes.NewReader(base64Data), len(base64Data))
|
||||
}
|
||||
|
||||
func (p *Base64Uploader) upHost(ak, bucket string) (upHost string, err error) {
|
||||
var zone *Zone
|
||||
if p.cfg.Zone != nil {
|
||||
zone = p.cfg.Zone
|
||||
} else {
|
||||
if v, zoneErr := GetZone(ak, bucket); zoneErr != nil {
|
||||
err = zoneErr
|
||||
return
|
||||
} else {
|
||||
zone = v
|
||||
}
|
||||
}
|
||||
|
||||
scheme := "http://"
|
||||
if p.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
host := zone.SrcUpHosts[0]
|
||||
if p.cfg.UseCdnDomains {
|
||||
host = zone.CdnUpHosts[0]
|
||||
}
|
||||
|
||||
upHost = fmt.Sprintf("%s%s", scheme, host)
|
||||
return
|
||||
}
|
||||
65
vendor/github.com/qiniu/api.v7/storage/base64_upload_test.go
generated
vendored
Normal file
65
vendor/github.com/qiniu/api.v7/storage/base64_upload_test.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
553
vendor/github.com/qiniu/api.v7/storage/bucket.go
generated
vendored
Normal file
553
vendor/github.com/qiniu/api.v7/storage/bucket.go
generated
vendored
Normal file
@@ -0,0 +1,553 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
// 资源管理相关的默认域名
|
||||
const (
|
||||
DefaultRsHost = "rs.qiniu.com"
|
||||
DefaultRsfHost = "rsf.qiniu.com"
|
||||
DefaultAPIHost = "api.qiniu.com"
|
||||
DefaultPubHost = "pu.qbox.me:10200"
|
||||
)
|
||||
|
||||
// FileInfo 文件基本信息
|
||||
type FileInfo struct {
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Type int `json:"type"`
|
||||
}
|
||||
|
||||
func (f *FileInfo) String() string {
|
||||
str := ""
|
||||
str += fmt.Sprintf("Hash: %s\n", f.Hash)
|
||||
str += fmt.Sprintf("Fsize: %d\n", f.Fsize)
|
||||
str += fmt.Sprintf("PutTime: %d\n", f.PutTime)
|
||||
str += fmt.Sprintf("MimeType: %s\n", f.MimeType)
|
||||
str += fmt.Sprintf("Type: %d\n", f.Type)
|
||||
return str
|
||||
}
|
||||
|
||||
// FetchRet 资源抓取的返回值
|
||||
type FetchRet struct {
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (r *FetchRet) String() string {
|
||||
str := ""
|
||||
str += fmt.Sprintf("Key: %s\n", r.Key)
|
||||
str += fmt.Sprintf("Hash: %s\n", r.Hash)
|
||||
str += fmt.Sprintf("Fsize: %d\n", r.Fsize)
|
||||
str += fmt.Sprintf("MimeType: %s\n", r.MimeType)
|
||||
return str
|
||||
}
|
||||
|
||||
// ListItem 为文件列举的返回值
|
||||
type ListItem struct {
|
||||
Key string `json:"key"`
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Type int `json:"type"`
|
||||
EndUser string `json:"endUser"`
|
||||
}
|
||||
|
||||
func (l *ListItem) String() string {
|
||||
str := ""
|
||||
str += fmt.Sprintf("Hash: %s\n", l.Hash)
|
||||
str += fmt.Sprintf("Fsize: %d\n", l.Fsize)
|
||||
str += fmt.Sprintf("PutTime: %d\n", l.PutTime)
|
||||
str += fmt.Sprintf("MimeType: %s\n", l.MimeType)
|
||||
str += fmt.Sprintf("Type: %d\n", l.Type)
|
||||
str += fmt.Sprintf("EndUser: %s\n", l.EndUser)
|
||||
return str
|
||||
}
|
||||
|
||||
// BatchOpRet 为批量执行操作的返回值
|
||||
// 批量操作支持 stat,copy,delete,move,chgm,chtype,deleteAfterDays几个操作
|
||||
// 其中 stat 为获取文件的基本信息,如果文件存在则返回基本信息,如果文件不存在返回 error 。
|
||||
// 其他的操作,如果成功,则返回 code,不成功会同时返回 error 信息,可以根据 error 信息来判断问题所在。
|
||||
type BatchOpRet struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Data struct {
|
||||
Hash string `json:"hash"`
|
||||
Fsize int64 `json:"fsize"`
|
||||
PutTime int64 `json:"putTime"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Type int `json:"type"`
|
||||
Error string `json:"error"`
|
||||
} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// BucketManager 提供了对资源进行管理的操作
|
||||
type BucketManager struct {
|
||||
client *rpc.Client
|
||||
mac *qbox.Mac
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
// NewBucketManager 用来构建一个新的资源管理对象
|
||||
func NewBucketManager(mac *qbox.Mac, cfg *Config) *BucketManager {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
return &BucketManager{
|
||||
client: NewClient(mac, nil),
|
||||
mac: mac,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBucketManagerEx 用来构建一个新的资源管理对象
|
||||
func NewBucketManagerEx(mac *qbox.Mac, cfg *Config, client *rpc.Client) *BucketManager {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
client = NewClient(mac, nil)
|
||||
}
|
||||
|
||||
return &BucketManager{
|
||||
client: client,
|
||||
mac: mac,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Buckets 用来获取空间列表,如果指定了 shared 参数为 true,那么一同列表被授权访问的空间
|
||||
func (m *BucketManager) Buckets(shared bool) (buckets []string, err error) {
|
||||
ctx := context.TODO()
|
||||
var reqHost string
|
||||
|
||||
scheme := "http://"
|
||||
if m.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
reqHost = fmt.Sprintf("%s%s", scheme, DefaultRsHost)
|
||||
reqURL := fmt.Sprintf("%s/buckets?shared=%v", reqHost, shared)
|
||||
err = m.client.Call(ctx, &buckets, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Stat 用来获取一个文件的基本信息
|
||||
func (m *BucketManager) Stat(bucket, key string) (info FileInfo, err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URIStat(bucket, key))
|
||||
err = m.client.Call(ctx, &info, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 用来删除空间中的一个文件
|
||||
func (m *BucketManager) Delete(bucket, key string) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URIDelete(bucket, key))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Copy 用来创建已有空间中的文件的一个新的副本
|
||||
func (m *BucketManager) Copy(srcBucket, srcKey, destBucket, destKey string, force bool) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(srcBucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URICopy(srcBucket, srcKey, destBucket, destKey, force))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Move 用来将空间中的一个文件移动到新的空间或者重命名
|
||||
func (m *BucketManager) Move(srcBucket, srcKey, destBucket, destKey string, force bool) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(srcBucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URIMove(srcBucket, srcKey, destBucket, destKey, force))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeMime 用来更新文件的MimeType
|
||||
func (m *BucketManager) ChangeMime(bucket, key, newMime string) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URIChangeMime(bucket, key, newMime))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeType 用来更新文件的存储类型,0表示普通存储,1表示低频存储
|
||||
func (m *BucketManager) ChangeType(bucket, key string, fileType int) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URIChangeType(bucket, key, fileType))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteAfterDays 用来更新文件生命周期,如果 days 设置为0,则表示取消文件的定期删除功能,永久存储
|
||||
func (m *BucketManager) DeleteAfterDays(bucket, key string, days int) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, URIDeleteAfterDays(bucket, key, days))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Batch 接口提供了资源管理的批量操作,支持 stat,copy,move,delete,chgm,chtype,deleteAfterDays几个接口
|
||||
func (m *BucketManager) Batch(operations []string) (batchOpRet []BatchOpRet, err error) {
|
||||
if len(operations) > 1000 {
|
||||
err = errors.New("batch operation count exceeds the limit of 1000")
|
||||
return
|
||||
}
|
||||
ctx := context.TODO()
|
||||
scheme := "http://"
|
||||
if m.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s/batch", scheme, DefaultRsHost)
|
||||
params := map[string][]string{
|
||||
"op": operations,
|
||||
}
|
||||
err = m.client.CallWithForm(ctx, &batchOpRet, "POST", reqURL, params)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch 根据提供的远程资源链接来抓取一个文件到空间并已指定文件名保存
|
||||
func (m *BucketManager) Fetch(resURL, bucket, key string) (fetchRet FetchRet, err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.iovipHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, uriFetch(resURL, bucket, key))
|
||||
err = m.client.Call(ctx, &fetchRet, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// FetchWithoutKey 根据提供的远程资源链接来抓取一个文件到空间并以文件的内容hash作为文件名
|
||||
func (m *BucketManager) FetchWithoutKey(resURL, bucket string) (fetchRet FetchRet, err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.iovipHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, uriFetchWithoutKey(resURL, bucket))
|
||||
err = m.client.Call(ctx, &fetchRet, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Prefetch 用来同步镜像空间的资源和镜像源资源内容
|
||||
func (m *BucketManager) Prefetch(bucket, key string) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.iovipHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, uriPrefetch(bucket, key))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// SetImage 用来设置空间镜像源
|
||||
func (m *BucketManager) SetImage(siteURL, bucket string) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqURL := fmt.Sprintf("http://%s%s", DefaultPubHost, uriSetImage(siteURL, bucket))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// SetImageWithHost 用来设置空间镜像源,额外添加回源Host头部
|
||||
func (m *BucketManager) SetImageWithHost(siteURL, bucket, host string) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqURL := fmt.Sprintf("http://%s%s", DefaultPubHost,
|
||||
uriSetImageWithHost(siteURL, bucket, host))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
// UnsetImage 用来取消空间镜像源设置
|
||||
func (m *BucketManager) UnsetImage(bucket string) (err error) {
|
||||
ctx := context.TODO()
|
||||
reqURL := fmt.Sprintf("http://%s%s", DefaultPubHost, uriUnsetImage(bucket))
|
||||
err = m.client.Call(ctx, nil, "POST", reqURL)
|
||||
return err
|
||||
}
|
||||
|
||||
type listFilesRet struct {
|
||||
Marker string `json:"marker"`
|
||||
Items []ListItem `json:"items"`
|
||||
CommonPrefixes []string `json:"commonPrefixes"`
|
||||
}
|
||||
|
||||
// ListFiles 用来获取空间文件列表,可以根据需要指定文件的前缀 prefix,文件的目录 delimiter,循环列举的时候下次
|
||||
// 列举的位置 marker,以及每次返回的文件的最大数量limit,其中limit最大为1000。
|
||||
func (m *BucketManager) ListFiles(bucket, prefix, delimiter, marker string,
|
||||
limit int) (entries []ListItem, commonPrefixes []string, nextMarker string, hasNext bool, err error) {
|
||||
if limit <= 0 || limit > 1000 {
|
||||
err = errors.New("invalid list limit, only allow [1, 1000]")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.rsfHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
|
||||
ret := listFilesRet{}
|
||||
reqURL := fmt.Sprintf("%s%s", reqHost, uriListFiles(bucket, prefix, delimiter, marker, limit))
|
||||
err = m.client.Call(ctx, &ret, "POST", reqURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
commonPrefixes = ret.CommonPrefixes
|
||||
nextMarker = ret.Marker
|
||||
entries = ret.Items
|
||||
if ret.Marker != "" {
|
||||
hasNext = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *BucketManager) rsHost(bucket string) (rsHost string, err error) {
|
||||
var zone *Zone
|
||||
if m.cfg.Zone != nil {
|
||||
zone = m.cfg.Zone
|
||||
} else {
|
||||
if v, zoneErr := GetZone(m.mac.AccessKey, bucket); zoneErr != nil {
|
||||
err = zoneErr
|
||||
return
|
||||
} else {
|
||||
zone = v
|
||||
}
|
||||
}
|
||||
|
||||
scheme := "http://"
|
||||
if m.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
rsHost = fmt.Sprintf("%s%s", scheme, zone.RsHost)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *BucketManager) rsfHost(bucket string) (rsfHost string, err error) {
|
||||
var zone *Zone
|
||||
if m.cfg.Zone != nil {
|
||||
zone = m.cfg.Zone
|
||||
} else {
|
||||
if v, zoneErr := GetZone(m.mac.AccessKey, bucket); zoneErr != nil {
|
||||
err = zoneErr
|
||||
return
|
||||
} else {
|
||||
zone = v
|
||||
}
|
||||
}
|
||||
|
||||
scheme := "http://"
|
||||
if m.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
rsfHost = fmt.Sprintf("%s%s", scheme, zone.RsfHost)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *BucketManager) iovipHost(bucket string) (iovipHost string, err error) {
|
||||
var zone *Zone
|
||||
if m.cfg.Zone != nil {
|
||||
zone = m.cfg.Zone
|
||||
} else {
|
||||
if v, zoneErr := GetZone(m.mac.AccessKey, bucket); zoneErr != nil {
|
||||
err = zoneErr
|
||||
return
|
||||
} else {
|
||||
zone = v
|
||||
}
|
||||
}
|
||||
|
||||
scheme := "http://"
|
||||
if m.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
iovipHost = fmt.Sprintf("%s%s", scheme, zone.IovipHost)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建op的方法,导出的方法支持在Batch操作中使用
|
||||
|
||||
// URIStat 构建 stat 接口的请求命令
|
||||
func URIStat(bucket, key string) string {
|
||||
return fmt.Sprintf("/stat/%s", EncodedEntry(bucket, key))
|
||||
}
|
||||
|
||||
// URIDelete 构建 delete 接口的请求命令
|
||||
func URIDelete(bucket, key string) string {
|
||||
return fmt.Sprintf("/delete/%s", EncodedEntry(bucket, key))
|
||||
}
|
||||
|
||||
// URICopy 构建 copy 接口的请求命令
|
||||
func URICopy(srcBucket, srcKey, destBucket, destKey string, force bool) string {
|
||||
return fmt.Sprintf("/copy/%s/%s/force/%v", EncodedEntry(srcBucket, srcKey),
|
||||
EncodedEntry(destBucket, destKey), force)
|
||||
}
|
||||
|
||||
// URIMove 构建 move 接口的请求命令
|
||||
func URIMove(srcBucket, srcKey, destBucket, destKey string, force bool) string {
|
||||
return fmt.Sprintf("/move/%s/%s/force/%v", EncodedEntry(srcBucket, srcKey),
|
||||
EncodedEntry(destBucket, destKey), force)
|
||||
}
|
||||
|
||||
// URIDeleteAfterDays 构建 deleteAfterDays 接口的请求命令
|
||||
func URIDeleteAfterDays(bucket, key string, days int) string {
|
||||
return fmt.Sprintf("/deleteAfterDays/%s/%d", EncodedEntry(bucket, key), days)
|
||||
}
|
||||
|
||||
// URIChangeMime 构建 chgm 接口的请求命令
|
||||
func URIChangeMime(bucket, key, newMime string) string {
|
||||
return fmt.Sprintf("/chgm/%s/mime/%s", EncodedEntry(bucket, key),
|
||||
base64.URLEncoding.EncodeToString([]byte(newMime)))
|
||||
}
|
||||
|
||||
// URIChangeType 构建 chtype 接口的请求命令
|
||||
func URIChangeType(bucket, key string, fileType int) string {
|
||||
return fmt.Sprintf("/chtype/%s/type/%d", EncodedEntry(bucket, key), fileType)
|
||||
}
|
||||
|
||||
// 构建op的方法,非导出的方法无法用在Batch操作中
|
||||
func uriFetch(resURL, bucket, key string) string {
|
||||
return fmt.Sprintf("/fetch/%s/to/%s",
|
||||
base64.URLEncoding.EncodeToString([]byte(resURL)), EncodedEntry(bucket, key))
|
||||
}
|
||||
|
||||
func uriFetchWithoutKey(resURL, bucket string) string {
|
||||
return fmt.Sprintf("/fetch/%s/to/%s",
|
||||
base64.URLEncoding.EncodeToString([]byte(resURL)), EncodedEntryWithoutKey(bucket))
|
||||
}
|
||||
|
||||
func uriPrefetch(bucket, key string) string {
|
||||
return fmt.Sprintf("/prefetch/%s", EncodedEntry(bucket, key))
|
||||
}
|
||||
|
||||
func uriSetImage(siteURL, bucket string) string {
|
||||
return fmt.Sprintf("/image/%s/from/%s", bucket,
|
||||
base64.URLEncoding.EncodeToString([]byte(siteURL)))
|
||||
}
|
||||
|
||||
func uriSetImageWithHost(siteURL, bucket, host string) string {
|
||||
return fmt.Sprintf("/image/%s/from/%s/host/%s", bucket,
|
||||
base64.URLEncoding.EncodeToString([]byte(siteURL)),
|
||||
base64.URLEncoding.EncodeToString([]byte(host)))
|
||||
}
|
||||
|
||||
func uriUnsetImage(bucket string) string {
|
||||
return fmt.Sprintf("/unimage/%s", bucket)
|
||||
}
|
||||
|
||||
func uriListFiles(bucket, prefix, delimiter, marker string, limit int) string {
|
||||
query := make(url.Values)
|
||||
query.Add("bucket", bucket)
|
||||
if prefix != "" {
|
||||
query.Add("prefix", prefix)
|
||||
}
|
||||
if delimiter != "" {
|
||||
query.Add("delimiter", delimiter)
|
||||
}
|
||||
if marker != "" {
|
||||
query.Add("marker", marker)
|
||||
}
|
||||
if limit > 0 {
|
||||
query.Add("limit", strconv.FormatInt(int64(limit), 10))
|
||||
}
|
||||
return fmt.Sprintf("/list?%s", query.Encode())
|
||||
}
|
||||
|
||||
// EncodedEntry 生成URL Safe Base64编码的 Entry
|
||||
func EncodedEntry(bucket, key string) string {
|
||||
entry := fmt.Sprintf("%s:%s", bucket, key)
|
||||
return base64.URLEncoding.EncodeToString([]byte(entry))
|
||||
}
|
||||
|
||||
// EncodedEntryWithoutKey 生成 key 为null的情况下 URL Safe Base64编码的Entry
|
||||
func EncodedEntryWithoutKey(bucket string) string {
|
||||
return base64.URLEncoding.EncodeToString([]byte(bucket))
|
||||
}
|
||||
|
||||
// MakePublicURL 用来生成公开空间资源下载链接
|
||||
func MakePublicURL(domain, key string) (finalUrl string) {
|
||||
srcUrl := fmt.Sprintf("%s/%s", domain, key)
|
||||
srcUri, _ := url.Parse(srcUrl)
|
||||
finalUrl = srcUri.String()
|
||||
return
|
||||
}
|
||||
|
||||
// MakePrivateURL 用来生成私有空间资源下载链接
|
||||
func MakePrivateURL(mac *qbox.Mac, domain, key string, deadline int64) (privateURL string) {
|
||||
publicURL := MakePublicURL(domain, key)
|
||||
urlToSign := publicURL
|
||||
if strings.Contains(publicURL, "?") {
|
||||
urlToSign = fmt.Sprintf("%s&e=%d", urlToSign, deadline)
|
||||
} else {
|
||||
urlToSign = fmt.Sprintf("%s?e=%d", urlToSign, deadline)
|
||||
}
|
||||
token := mac.Sign([]byte(urlToSign))
|
||||
privateURL = fmt.Sprintf("%s&token=%s", urlToSign, token)
|
||||
return
|
||||
}
|
||||
264
vendor/github.com/qiniu/api.v7/storage/bucket_test.go
generated
vendored
Normal file
264
vendor/github.com/qiniu/api.v7/storage/bucket_test.go
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
)
|
||||
|
||||
var (
|
||||
testAK = os.Getenv("QINIU_ACCESS_KEY")
|
||||
testSK = os.Getenv("QINIU_SECRET_KEY")
|
||||
testBucket = os.Getenv("QINIU_TEST_BUCKET")
|
||||
testBucketPrivate = os.Getenv("QINIU_TEST_BUCKET_PRIVATE")
|
||||
testBucketPrivateDomain = os.Getenv("QINIU_TEST_DOMAIN_PRIVATE")
|
||||
testPipeline = os.Getenv("QINIU_TEST_PIPELINE")
|
||||
|
||||
testKey = "qiniu.png"
|
||||
testFetchUrl = "http://devtools.qiniu.com/qiniu.png"
|
||||
testSiteUrl = "http://devtools.qiniu.com"
|
||||
)
|
||||
|
||||
var mac *qbox.Mac
|
||||
var bucketManager *BucketManager
|
||||
var operationManager *OperationManager
|
||||
var formUploader *FormUploader
|
||||
var resumeUploader *ResumeUploader
|
||||
var base64Uploader *Base64Uploader
|
||||
|
||||
func init() {
|
||||
if testAK == "" || testSK == "" {
|
||||
panic("please run ./test-env.sh first")
|
||||
}
|
||||
mac = qbox.NewMac(testAK, testSK)
|
||||
cfg := Config{}
|
||||
cfg.Zone = &Zone_z0
|
||||
cfg.UseCdnDomains = true
|
||||
bucketManager = NewBucketManager(mac, &cfg)
|
||||
operationManager = NewOperationManager(mac, &cfg)
|
||||
formUploader = NewFormUploader(&cfg)
|
||||
resumeUploader = NewResumeUploader(&cfg)
|
||||
base64Uploader = NewBase64Uploader(&cfg)
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
//Test get zone
|
||||
func TestGetZone(t *testing.T) {
|
||||
zone, err := GetZone(testAK, testBucket)
|
||||
if err != nil {
|
||||
t.Fatalf("GetZone() error, %s", err)
|
||||
}
|
||||
t.Log(zone.String())
|
||||
}
|
||||
|
||||
//Test get bucket list
|
||||
func TestBuckets(t *testing.T) {
|
||||
shared := true
|
||||
buckets, err := bucketManager.Buckets(shared)
|
||||
if err != nil {
|
||||
t.Fatalf("Buckets() error, %s", err)
|
||||
}
|
||||
|
||||
for _, bucket := range buckets {
|
||||
t.Log(bucket)
|
||||
}
|
||||
}
|
||||
|
||||
//Test get file info
|
||||
func TestStat(t *testing.T) {
|
||||
keysToStat := []string{"qiniu.png"}
|
||||
|
||||
for _, eachKey := range keysToStat {
|
||||
info, err := bucketManager.Stat(testBucket, eachKey)
|
||||
if err != nil {
|
||||
t.Logf("Stat() error, %s", err)
|
||||
t.Fail()
|
||||
} else {
|
||||
t.Logf("FileInfo:\n %s", info.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyMoveDelete(t *testing.T) {
|
||||
keysCopyTarget := []string{"qiniu_1.png", "qiniu_2.png", "qiniu_3.png"}
|
||||
keysToDelete := make([]string, 0, len(keysCopyTarget))
|
||||
for _, eachKey := range keysCopyTarget {
|
||||
err := bucketManager.Copy(testBucket, testKey, testBucket, eachKey, true)
|
||||
if err != nil {
|
||||
t.Logf("Copy() error, %s", err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
for _, eachKey := range keysCopyTarget {
|
||||
keyToDelete := eachKey + "_move"
|
||||
err := bucketManager.Move(testBucket, eachKey, testBucket, keyToDelete, true)
|
||||
if err != nil {
|
||||
t.Logf("Move() error, %s", err)
|
||||
t.Fail()
|
||||
} else {
|
||||
keysToDelete = append(keysToDelete, keyToDelete)
|
||||
}
|
||||
}
|
||||
|
||||
for _, eachKey := range keysToDelete {
|
||||
err := bucketManager.Delete(testBucket, eachKey)
|
||||
if err != nil {
|
||||
t.Logf("Delete() error, %s", err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetch(t *testing.T) {
|
||||
ret, err := bucketManager.Fetch(testFetchUrl, testBucket, "qiniu-fetch.png")
|
||||
if err != nil {
|
||||
t.Logf("Fetch() error, %s", err)
|
||||
t.Fail()
|
||||
} else {
|
||||
t.Logf("FetchRet:\n %s", ret.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchWithoutKey(t *testing.T) {
|
||||
ret, err := bucketManager.FetchWithoutKey(testFetchUrl, testBucket)
|
||||
if err != nil {
|
||||
t.Logf("FetchWithoutKey() error, %s", err)
|
||||
t.Fail()
|
||||
} else {
|
||||
t.Logf("FetchRet:\n %s", ret.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAfterDays(t *testing.T) {
|
||||
deleteKey := testKey + "_deleteAfterDays"
|
||||
days := 7
|
||||
bucketManager.Copy(testBucket, testKey, testBucket, deleteKey, true)
|
||||
err := bucketManager.DeleteAfterDays(testBucket, deleteKey, days)
|
||||
if err != nil {
|
||||
t.Logf("DeleteAfterDays() error, %s", err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeMime(t *testing.T) {
|
||||
toChangeKey := testKey + "_changeMime"
|
||||
bucketManager.Copy(testBucket, testKey, testBucket, toChangeKey, true)
|
||||
newMime := "text/plain"
|
||||
err := bucketManager.ChangeMime(testBucket, toChangeKey, newMime)
|
||||
if err != nil {
|
||||
t.Fatalf("ChangeMime() error, %s", err)
|
||||
}
|
||||
|
||||
info, err := bucketManager.Stat(testBucket, toChangeKey)
|
||||
if err != nil || info.MimeType != newMime {
|
||||
t.Fatalf("ChangeMime() failed, %s", err)
|
||||
}
|
||||
bucketManager.Delete(testBucket, toChangeKey)
|
||||
}
|
||||
|
||||
func TestChangeType(t *testing.T) {
|
||||
toChangeKey := fmt.Sprintf("%s_changeType_%d", testKey, rand.Int())
|
||||
bucketManager.Copy(testBucket, testKey, testBucket, toChangeKey, true)
|
||||
fileType := 1
|
||||
err := bucketManager.ChangeType(testBucket, toChangeKey, fileType)
|
||||
if err != nil {
|
||||
t.Fatalf("ChangeType() error, %s", err)
|
||||
}
|
||||
|
||||
info, err := bucketManager.Stat(testBucket, toChangeKey)
|
||||
if err != nil || info.Type != fileType {
|
||||
t.Fatalf("ChangeMime() failed, %s", err)
|
||||
}
|
||||
bucketManager.Delete(testBucket, toChangeKey)
|
||||
}
|
||||
|
||||
func TestPrefetchAndImage(t *testing.T) {
|
||||
err := bucketManager.SetImage(testSiteUrl, testBucket)
|
||||
if err != nil {
|
||||
t.Fatalf("SetImage() error, %s", err)
|
||||
}
|
||||
|
||||
err = bucketManager.Prefetch(testBucket, testKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Prefetch() error, %s", err)
|
||||
}
|
||||
|
||||
err = bucketManager.UnsetImage(testBucket)
|
||||
if err != nil {
|
||||
t.Fatalf("UnsetImage() error, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListFiles(t *testing.T) {
|
||||
limit := 100
|
||||
prefix := "listfiles/"
|
||||
for i := 0; i < limit; i++ {
|
||||
newKey := fmt.Sprintf("%s%s/%d", prefix, testKey, i)
|
||||
bucketManager.Copy(testBucket, testKey, testBucket, newKey, true)
|
||||
}
|
||||
entries, _, _, hasNext, err := bucketManager.ListFiles(testBucket, prefix, "", "", limit)
|
||||
if err != nil {
|
||||
t.Fatalf("ListFiles() error, %s", err)
|
||||
}
|
||||
|
||||
if hasNext {
|
||||
t.Fatalf("ListFiles() failed, unexpected hasNext")
|
||||
}
|
||||
|
||||
if len(entries) != limit {
|
||||
t.Fatalf("ListFiles() failed, unexpected items count, expected: %d, actual: %d", limit, len(entries))
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
t.Logf("ListItem:\n%s", entry.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakePrivateUrl(t *testing.T) {
|
||||
deadline := time.Now().Add(time.Second * 3600).Unix()
|
||||
privateURL := MakePrivateURL(mac, "http://"+testBucketPrivateDomain, testKey, deadline)
|
||||
t.Logf("PrivateUrl: %s", privateURL)
|
||||
resp, respErr := http.Get(privateURL)
|
||||
if respErr != nil {
|
||||
t.Fatalf("MakePrivateUrl() error, %s", respErr)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("MakePrivateUrl() error, %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatch(t *testing.T) {
|
||||
copyCnt := 100
|
||||
copyOps := make([]string, 0, copyCnt)
|
||||
testKeys := make([]string, 0, copyCnt)
|
||||
for i := 0; i < copyCnt; i++ {
|
||||
cpKey := fmt.Sprintf("%s_batchcopy_%d", testKey, i)
|
||||
testKeys = append(testKeys, cpKey)
|
||||
copyOps = append(copyOps, URICopy(testBucket, testKey, testBucket, cpKey, true))
|
||||
}
|
||||
|
||||
_, bErr := bucketManager.Batch(copyOps)
|
||||
if bErr != nil {
|
||||
t.Fatalf("BatchCopy error, %s", bErr)
|
||||
}
|
||||
|
||||
statOps := make([]string, 0, copyCnt)
|
||||
for _, k := range testKeys {
|
||||
statOps = append(statOps, URIStat(testBucket, k))
|
||||
}
|
||||
batchOpRets, bErr := bucketManager.Batch(statOps)
|
||||
_, bErr = bucketManager.Batch(copyOps)
|
||||
if bErr != nil {
|
||||
t.Fatalf("BatchStat error, %s", bErr)
|
||||
}
|
||||
|
||||
t.Logf("BatchStat: %v", batchOpRets)
|
||||
}
|
||||
38
vendor/github.com/qiniu/api.v7/storage/client.go
generated
vendored
Normal file
38
vendor/github.com/qiniu/api.v7/storage/client.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Transport struct {
|
||||
mac qbox.Mac
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *Transport) NestedObject() interface{} {
|
||||
return t.Transport
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
token, err := t.mac.SignRequest(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Authorization", "QBox "+token)
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func NewTransport(mac *qbox.Mac, transport http.RoundTripper) *Transport {
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
t := &Transport{mac: *mac, Transport: transport}
|
||||
return t
|
||||
}
|
||||
|
||||
func NewClient(mac *qbox.Mac, transport http.RoundTripper) *rpc.Client {
|
||||
t := NewTransport(mac, transport)
|
||||
return &rpc.Client{&http.Client{Transport: t}}
|
||||
}
|
||||
8
vendor/github.com/qiniu/api.v7/storage/config.go
generated
vendored
Normal file
8
vendor/github.com/qiniu/api.v7/storage/config.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package storage
|
||||
|
||||
// Config 为文件上传,资源管理等配置
|
||||
type Config struct {
|
||||
Zone *Zone //空间所在的机房
|
||||
UseHTTPS bool //是否使用https域名
|
||||
UseCdnDomains bool //是否使用cdn加速域名
|
||||
}
|
||||
10
vendor/github.com/qiniu/api.v7/storage/doc.go
generated
vendored
Normal file
10
vendor/github.com/qiniu/api.v7/storage/doc.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// storage 包提供了资源的上传,管理,数据处理等功能。其中资源的上传又提供了表单上传的方式以及分片上传的方式,其中分片上传的方式还支持断点续传。
|
||||
//
|
||||
// 该包中提供了 BucketManager 用来进行资源管理,比如获取文件信息,文件复制,删除,重命名等,以及很多高级功能如修改文件类型,
|
||||
// 修改文件的生命周期,修改文件的存储类型等。
|
||||
//
|
||||
// 该包中还提供了 FormUploader 和 ResumeUploader 来分别支持表单上传和分片上传,断点续传等功能,对于较大的文件,比如100MB以上的文件,一般
|
||||
// 建议采用分片上传的方式来保证上传的效率和可靠性。
|
||||
//
|
||||
// 对于数据处理,则提供了 OperationManager,可以使用它来发送持久化数据处理请求,及查询数据处理的状态。
|
||||
package storage
|
||||
345
vendor/github.com/qiniu/api.v7/storage/form_upload.go
generated
vendored
Normal file
345
vendor/github.com/qiniu/api.v7/storage/form_upload.go
generated
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
// PutExtra 为表单上传的额外可选项
|
||||
type PutExtra struct {
|
||||
// 可选,用户自定义参数,必须以 "x:" 开头。若不以x:开头,则忽略。
|
||||
Params map[string]string
|
||||
|
||||
// 可选,当为 "" 时候,服务端自动判断。
|
||||
MimeType string
|
||||
|
||||
// 上传事件:进度通知。这个事件的回调函数应该尽可能快地结束。
|
||||
OnProgress func(fsize, uploaded int64)
|
||||
}
|
||||
|
||||
// PutRet 为七牛标准的上传回复内容。
|
||||
// 如果使用了上传回调或者自定义了returnBody,那么需要根据实际情况,自己自定义一个返回值结构体
|
||||
type PutRet struct {
|
||||
Hash string `json:"hash"`
|
||||
PersistentID string `json:"persistentId"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// FormUploader 表示一个表单上传的对象
|
||||
type FormUploader struct {
|
||||
client *rpc.Client
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
// NewFormUploader 用来构建一个表单上传的对象
|
||||
func NewFormUploader(cfg *Config) *FormUploader {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
return &FormUploader{
|
||||
client: &rpc.DefaultClient,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFormUploaderEx 用来构建一个表单上传的对象
|
||||
func NewFormUploaderEx(cfg *Config, client *rpc.Client) *FormUploader {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
client = &rpc.DefaultClient
|
||||
}
|
||||
|
||||
return &FormUploader{
|
||||
client: client,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// PutFile 用来以表单方式上传一个文件,和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 callbackUrl 或 returnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项,可以指定为nil。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p *FormUploader) PutFile(
|
||||
ctx context.Context, ret interface{}, uptoken, key, localFile string, extra *PutExtra) (err error) {
|
||||
return p.putFile(ctx, ret, uptoken, key, true, localFile, extra)
|
||||
}
|
||||
|
||||
// PutFileWithoutKey 用来以表单方式上传一个文件。不指定文件上传后保存的key的情况下,文件命名方式首先看看
|
||||
// uptoken 中是否设置了 saveKey,如果设置了 saveKey,那么按 saveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
// 和 Put 不同的只是一个通过提供文件路径来访问文件内容,一个通过 io.Reader 来访问。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// localFile 是要上传的文件的本地路径。
|
||||
// extra 是上传的一些可选项。可以指定为nil。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p *FormUploader) PutFileWithoutKey(
|
||||
ctx context.Context, ret interface{}, uptoken, localFile string, extra *PutExtra) (err error) {
|
||||
return p.putFile(ctx, ret, uptoken, "", false, localFile, extra)
|
||||
}
|
||||
|
||||
func (p *FormUploader) putFile(
|
||||
ctx context.Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, localFile string, extra *PutExtra) (err error) {
|
||||
|
||||
f, err := os.Open(localFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fsize := fi.Size()
|
||||
|
||||
if extra == nil {
|
||||
extra = &PutExtra{}
|
||||
}
|
||||
|
||||
return p.put(ctx, ret, uptoken, key, hasKey, f, fsize, extra, filepath.Base(localFile))
|
||||
}
|
||||
|
||||
// Put 用来以表单方式上传一个文件。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 callbackUrl 或 returnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// key 是要上传的文件访问路径。比如:"foo/bar.jpg"。注意我们建议 key 不要以 '/' 开头。另外,key 为空字符串是合法的。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。可以指定为nil。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p *FormUploader) Put(
|
||||
ctx context.Context, ret interface{}, uptoken, key string, data io.Reader, size int64, extra *PutExtra) (err error) {
|
||||
err = p.put(ctx, ret, uptoken, key, true, data, size, extra, path.Base(key))
|
||||
return
|
||||
}
|
||||
|
||||
// PutWithoutKey 用来以表单方式上传一个文件。不指定文件上传后保存的key的情况下,文件命名方式首先看看 uptoken 中是否设置了 saveKey,
|
||||
// 如果设置了 saveKey,那么按 saveKey 要求的规则生成 key,否则自动以文件的 hash 做 key。
|
||||
//
|
||||
// ctx 是请求的上下文。
|
||||
// ret 是上传成功后返回的数据。如果 uptoken 中没有设置 CallbackUrl 或 ReturnBody,那么返回的数据结构是 PutRet 结构。
|
||||
// uptoken 是由业务服务器颁发的上传凭证。
|
||||
// data 是文件内容的访问接口(io.Reader)。
|
||||
// fsize 是要上传的文件大小。
|
||||
// extra 是上传的一些可选项。详细见 PutExtra 结构的描述。
|
||||
//
|
||||
func (p *FormUploader) PutWithoutKey(
|
||||
ctx context.Context, ret interface{}, uptoken string, data io.Reader, size int64, extra *PutExtra) (err error) {
|
||||
err = p.put(ctx, ret, uptoken, "", false, data, size, extra, "filename")
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *FormUploader) put(
|
||||
ctx context.Context, ret interface{}, uptoken string,
|
||||
key string, hasKey bool, data io.Reader, size int64, extra *PutExtra, fileName string) (err error) {
|
||||
|
||||
ak, bucket, gErr := getAkBucketFromUploadToken(uptoken)
|
||||
if gErr != nil {
|
||||
err = gErr
|
||||
return
|
||||
}
|
||||
|
||||
var upHost string
|
||||
upHost, err = p.upHost(ak, bucket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
writer := multipart.NewWriter(&b)
|
||||
|
||||
if extra == nil {
|
||||
extra = &PutExtra{}
|
||||
}
|
||||
|
||||
if extra.OnProgress != nil {
|
||||
data = &readerWithProgress{reader: data, fsize: size, onProgress: extra.OnProgress}
|
||||
}
|
||||
|
||||
err = writeMultipart(writer, uptoken, key, hasKey, extra, fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var dataReader io.Reader
|
||||
|
||||
h := crc32.NewIEEE()
|
||||
dataReader = io.TeeReader(data, h)
|
||||
crcReader := newCrc32Reader(writer.Boundary(), h)
|
||||
//write file
|
||||
head := make(textproto.MIMEHeader)
|
||||
head.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`,
|
||||
escapeQuotes(fileName)))
|
||||
if extra.MimeType != "" {
|
||||
head.Set("Content-Type", extra.MimeType)
|
||||
}
|
||||
|
||||
_, err = writer.CreatePart(head)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lastLine := fmt.Sprintf("\r\n--%s--\r\n", writer.Boundary())
|
||||
r := strings.NewReader(lastLine)
|
||||
|
||||
bodyLen := int64(-1)
|
||||
if size >= 0 {
|
||||
bodyLen = int64(b.Len()) + size + int64(len(lastLine))
|
||||
bodyLen += crcReader.length()
|
||||
}
|
||||
|
||||
mr := io.MultiReader(&b, dataReader, crcReader, r)
|
||||
|
||||
contentType := writer.FormDataContentType()
|
||||
err = p.client.CallWith64(ctx, ret, "POST", upHost, contentType, mr, bodyLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if extra.OnProgress != nil {
|
||||
extra.OnProgress(size, size)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type crc32Reader struct {
|
||||
h hash.Hash32
|
||||
boundary string
|
||||
r io.Reader
|
||||
flag bool
|
||||
nlDashBoundaryNl string
|
||||
header string
|
||||
crc32PadLen int64
|
||||
}
|
||||
|
||||
func newCrc32Reader(boundary string, h hash.Hash32) *crc32Reader {
|
||||
nlDashBoundaryNl := fmt.Sprintf("\r\n--%s\r\n", boundary)
|
||||
header := `Content-Disposition: form-data; name="crc32"` + "\r\n\r\n"
|
||||
return &crc32Reader{
|
||||
h: h,
|
||||
boundary: boundary,
|
||||
nlDashBoundaryNl: nlDashBoundaryNl,
|
||||
header: header,
|
||||
crc32PadLen: 10,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *crc32Reader) Read(p []byte) (int, error) {
|
||||
if r.flag == false {
|
||||
crc32 := r.h.Sum32()
|
||||
crc32Line := r.nlDashBoundaryNl + r.header + fmt.Sprintf("%010d", crc32) //padding crc32 results to 10 digits
|
||||
r.r = strings.NewReader(crc32Line)
|
||||
r.flag = true
|
||||
}
|
||||
return r.r.Read(p)
|
||||
}
|
||||
|
||||
func (r crc32Reader) length() (length int64) {
|
||||
return int64(len(r.nlDashBoundaryNl+r.header)) + r.crc32PadLen
|
||||
}
|
||||
|
||||
func (p *FormUploader) upHost(ak, bucket string) (upHost string, err error) {
|
||||
var zone *Zone
|
||||
if p.cfg.Zone != nil {
|
||||
zone = p.cfg.Zone
|
||||
} else {
|
||||
if v, zoneErr := GetZone(ak, bucket); zoneErr != nil {
|
||||
err = zoneErr
|
||||
return
|
||||
} else {
|
||||
zone = v
|
||||
}
|
||||
}
|
||||
|
||||
scheme := "http://"
|
||||
if p.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
|
||||
host := zone.SrcUpHosts[0]
|
||||
if p.cfg.UseCdnDomains {
|
||||
host = zone.CdnUpHosts[0]
|
||||
}
|
||||
|
||||
upHost = fmt.Sprintf("%s%s", scheme, host)
|
||||
return
|
||||
}
|
||||
|
||||
type readerWithProgress struct {
|
||||
reader io.Reader
|
||||
uploaded int64
|
||||
fsize int64
|
||||
onProgress func(fsize, uploaded int64)
|
||||
}
|
||||
|
||||
func (p *readerWithProgress) Read(b []byte) (n int, err error) {
|
||||
if p.uploaded > 0 {
|
||||
p.onProgress(p.fsize, p.uploaded)
|
||||
}
|
||||
|
||||
n, err = p.reader.Read(b)
|
||||
p.uploaded += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func writeMultipart(writer *multipart.Writer, uptoken, key string, hasKey bool,
|
||||
extra *PutExtra, fileName string) (err error) {
|
||||
|
||||
//token
|
||||
if err = writer.WriteField("token", uptoken); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//key
|
||||
if hasKey {
|
||||
if err = writer.WriteField("key", key); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//extra.Params
|
||||
if extra.Params != nil {
|
||||
for k, v := range extra.Params {
|
||||
if strings.HasPrefix(k, "x:") && v != "" {
|
||||
err = writer.WriteField(k, v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
|
||||
|
||||
func escapeQuotes(s string) string {
|
||||
return quoteEscaper.Replace(s)
|
||||
}
|
||||
31
vendor/github.com/qiniu/api.v7/storage/form_upload_test.go
generated
vendored
Normal file
31
vendor/github.com/qiniu/api.v7/storage/form_upload_test.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testLocalFile = filepath.Join(os.Getenv("TRAVIS_BUILD_DIR"), "Makefile")
|
||||
)
|
||||
|
||||
func TestFormUploadPutFile(t *testing.T) {
|
||||
var putRet PutRet
|
||||
ctx := context.TODO()
|
||||
putPolicy := PutPolicy{
|
||||
Scope: testBucket,
|
||||
DeleteAfterDays: 7,
|
||||
}
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
testKey := fmt.Sprintf("testPutFileKey_%d", rand.Int())
|
||||
|
||||
err := formUploader.PutFile(ctx, &putRet, upToken, testKey, testLocalFile, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("FormUploader#PutFile() error, %s", err)
|
||||
}
|
||||
t.Logf("Key: %s, Hash:%s", putRet.Key, putRet.Hash)
|
||||
}
|
||||
5
vendor/github.com/qiniu/api.v7/storage/init.go
generated
vendored
Normal file
5
vendor/github.com/qiniu/api.v7/storage/init.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
_ "github.com/qiniu/api.v7/conf"
|
||||
)
|
||||
206
vendor/github.com/qiniu/api.v7/storage/pfop.go
generated
vendored
Normal file
206
vendor/github.com/qiniu/api.v7/storage/pfop.go
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/qiniu/api.v7/auth/qbox"
|
||||
"github.com/qiniu/x/rpc.v7"
|
||||
)
|
||||
|
||||
// OperationManager 提供了数据处理相关的方法
|
||||
type OperationManager struct {
|
||||
client *rpc.Client
|
||||
mac *qbox.Mac
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
// NewOperationManager 用来构建一个新的数据处理对象
|
||||
func NewOperationManager(mac *qbox.Mac, cfg *Config) *OperationManager {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
return &OperationManager{
|
||||
client: NewClient(mac, nil),
|
||||
mac: mac,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// NewOperationManager 用来构建一个新的数据处理对象
|
||||
func NewOperationManagerEx(mac *qbox.Mac, cfg *Config, client *rpc.Client) *OperationManager {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
client = NewClient(mac, nil)
|
||||
}
|
||||
|
||||
return &OperationManager{
|
||||
client: client,
|
||||
mac: mac,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// PfopRet 为数据处理请求的回复内容
|
||||
type PfopRet struct {
|
||||
PersistentID string `json:"persistentId,omitempty"`
|
||||
}
|
||||
|
||||
// PrefopRet 为数据处理请求的状态查询回复内容
|
||||
type PrefopRet struct {
|
||||
ID string `json:"id"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
InputBucket string `json:"inputBucket,omitempty"`
|
||||
InputKey string `json:"inputKey,omitempty"`
|
||||
Pipeline string `json:"pipeline,omitempty"`
|
||||
Reqid string `json:"reqid,omitempty"`
|
||||
Items []FopResult
|
||||
}
|
||||
|
||||
func (r *PrefopRet) String() string {
|
||||
strData := fmt.Sprintf("Id: %s\r\nCode: %d\r\nDesc: %s\r\n", r.ID, r.Code, r.Desc)
|
||||
if r.InputBucket != "" {
|
||||
strData += fmt.Sprintln(fmt.Sprintf("InputBucket: %s", r.InputBucket))
|
||||
}
|
||||
if r.InputKey != "" {
|
||||
strData += fmt.Sprintln(fmt.Sprintf("InputKey: %s", r.InputKey))
|
||||
}
|
||||
if r.Pipeline != "" {
|
||||
strData += fmt.Sprintln(fmt.Sprintf("Pipeline: %s", r.Pipeline))
|
||||
}
|
||||
if r.Reqid != "" {
|
||||
strData += fmt.Sprintln(fmt.Sprintf("Reqid: %s", r.Reqid))
|
||||
}
|
||||
|
||||
strData = fmt.Sprintln(strData)
|
||||
for _, item := range r.Items {
|
||||
strData += fmt.Sprintf("\tCmd:\t%s\r\n\tCode:\t%d\r\n\tDesc:\t%s\r\n", item.Cmd, item.Code, item.Desc)
|
||||
if item.Error != "" {
|
||||
strData += fmt.Sprintf("\tError:\t%s\r\n", item.Error)
|
||||
} else {
|
||||
if item.Hash != "" {
|
||||
strData += fmt.Sprintf("\tHash:\t%s\r\n", item.Hash)
|
||||
}
|
||||
if item.Key != "" {
|
||||
strData += fmt.Sprintf("\tKey:\t%s\r\n", item.Key)
|
||||
}
|
||||
if item.Keys != nil {
|
||||
if len(item.Keys) > 0 {
|
||||
strData += "\tKeys: {\r\n"
|
||||
for _, key := range item.Keys {
|
||||
strData += fmt.Sprintf("\t\t%s\r\n", key)
|
||||
}
|
||||
strData += "\t}\r\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
strData += "\r\n"
|
||||
}
|
||||
return strData
|
||||
}
|
||||
|
||||
// FopResult 云处理操作列表,包含每个云处理操作的状态信息
|
||||
type FopResult struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Keys []string `json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
// Pfop 持久化数据处理
|
||||
//
|
||||
// bucket 资源空间
|
||||
// key 源资源名
|
||||
// fops 云处理操作列表,
|
||||
// notifyURL 处理结果通知接收URL
|
||||
// pipeline 多媒体处理队列名称
|
||||
// force 强制执行数据处理
|
||||
//
|
||||
func (m *OperationManager) Pfop(bucket, key, fops, pipeline, notifyURL string,
|
||||
force bool) (persistentID string, err error) {
|
||||
pfopParams := map[string][]string{
|
||||
"bucket": []string{bucket},
|
||||
"key": []string{key},
|
||||
"fops": []string{fops},
|
||||
}
|
||||
|
||||
if pipeline != "" {
|
||||
pfopParams["pipeline"] = []string{pipeline}
|
||||
}
|
||||
|
||||
if notifyURL != "" {
|
||||
pfopParams["notifyURL"] = []string{notifyURL}
|
||||
}
|
||||
|
||||
if force {
|
||||
pfopParams["force"] = []string{"1"}
|
||||
}
|
||||
var ret PfopRet
|
||||
ctx := context.TODO()
|
||||
reqHost, reqErr := m.apiHost(bucket)
|
||||
if reqErr != nil {
|
||||
err = reqErr
|
||||
return
|
||||
}
|
||||
reqURL := fmt.Sprintf("%s/pfop/", reqHost)
|
||||
err = m.client.CallWithForm(ctx, &ret, "POST", reqURL, pfopParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
persistentID = ret.PersistentID
|
||||
return
|
||||
}
|
||||
|
||||
// Prefop 持久化处理状态查询
|
||||
func (m *OperationManager) Prefop(persistentID string) (ret PrefopRet, err error) {
|
||||
ctx := context.TODO()
|
||||
reqHost := m.prefopApiHost(persistentID)
|
||||
reqURL := fmt.Sprintf("%s/status/get/prefop?id=%s", reqHost, persistentID)
|
||||
err = m.client.Call(ctx, &ret, "GET", reqURL)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *OperationManager) apiHost(bucket string) (apiHost string, err error) {
|
||||
var zone *Zone
|
||||
if m.cfg.Zone != nil {
|
||||
zone = m.cfg.Zone
|
||||
} else {
|
||||
if v, zoneErr := GetZone(m.mac.AccessKey, bucket); zoneErr != nil {
|
||||
err = zoneErr
|
||||
return
|
||||
} else {
|
||||
zone = v
|
||||
}
|
||||
}
|
||||
|
||||
scheme := "http://"
|
||||
if m.cfg.UseHTTPS {
|
||||
scheme = "https://"
|
||||
}
|
||||
apiHost = fmt.Sprintf("%s%s", scheme, zone.ApiHost)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *OperationManager) prefopApiHost(persistentID string) (apiHost string) {
|
||||
apiHost = "api.qiniu.com"
|
||||
if m.cfg.Zone != nil {
|
||||
apiHost = m.cfg.Zone.ApiHost
|
||||
}
|
||||
|
||||
if m.cfg.UseHTTPS {
|
||||
apiHost = fmt.Sprintf("https://%s", apiHost)
|
||||
} else {
|
||||
apiHost = fmt.Sprintf("http://%s", apiHost)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
45
vendor/github.com/qiniu/api.v7/storage/pfop_test.go
generated
vendored
Normal file
45
vendor/github.com/qiniu/api.v7/storage/pfop_test.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testVideoKey = "qiniu.mp4"
|
||||
)
|
||||
|
||||
func TestPrefop(t *testing.T) {
|
||||
pid := "na0.597802c092129336c20f3f91"
|
||||
prefopRet, err := operationManager.Prefop(pid)
|
||||
if err != nil {
|
||||
t.Fatalf("Prefop() error, %s", err)
|
||||
}
|
||||
t.Logf("%s", prefopRet.String())
|
||||
}
|
||||
|
||||
func TestPfop(t *testing.T) {
|
||||
saveBucket := testBucket
|
||||
|
||||
fopAvthumb := fmt.Sprintf("avthumb/mp4/s/480x320/vb/500k|saveas/%s",
|
||||
EncodedEntry(saveBucket, "pfop_test_qiniu.mp4"))
|
||||
fopVframe := fmt.Sprintf("vframe/jpg/offset/10|saveas/%s",
|
||||
EncodedEntry(saveBucket, "pfop_test_qiniu.jpg"))
|
||||
fopVsample := fmt.Sprintf("vsample/jpg/interval/20/pattern/%s",
|
||||
base64.URLEncoding.EncodeToString([]byte("pfop_test_$(count).jpg")))
|
||||
|
||||
fopBatch := []string{fopAvthumb, fopVframe, fopVsample}
|
||||
fops := strings.Join(fopBatch, ";")
|
||||
|
||||
force := true
|
||||
notifyURL := ""
|
||||
pid, err := operationManager.Pfop(testBucket, testVideoKey, fops,
|
||||
testPipeline, notifyURL, force)
|
||||
if err != nil {
|
||||
t.Fatalf("Pfop() error, %s", err)
|
||||
}
|
||||
t.Logf("persistentId: %s", pid)
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user