mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-12 09:22:27 +08:00
436 lines
13 KiB
Go
436 lines
13 KiB
Go
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))
|
||
}
|
||
|
||
// ----------------------------------------------------------
|