C/C++ SDK
本 SDK 使用符合 C89 标准的 C 语言实现。由于 C 语言的普适性,原则上此 SDK 可以跨所有主流平台,不仅可以直接在 C 和 C++ 的工程中使用,也可以用于与 C 语言交互性较好的语言中,例如 C#(使用 P/Invoke 交互)、Java(使用 JNI 交互)、Lua 等。
本开发指南假设开发者使用的开发语言是 C/C++,C-SDK 以开源方式提供。开发者可以从本文档提供的下载地址查看和下载 SDK 的源代码,并按自己的工程现状进行合理使用,例如编译为静态库或者动态库后进行链接,或者直接将 SDK 的源代码加入到自己的工程中一起编译,以保持工程设置的简单性。
从 v5.0.0 版本开始,我们对 SDK 的内容进行了精简。所有管理操作,
例如创建/删除 bucket,为 bucket 绑定域名 (publish),设置数据处理的样式分隔符,新增数据处理样式等都去除了,建议统一到七牛开发者平台完成。另外,此前服务端还有自己独有的上传 API,现在也推荐统一成基于客户端上传的工作方式。
从内容上来说,C-SDK 主要包含如下几方面的内容:
- 公共部分,所有情况下都用到:qiniu/base.c、qiniu/conf.c、qiniu/http.c
- 客户端上传文件:qiniu/base_io.c、qiniu/io.c
- 客户端断点续上传:qiniu/base_io.c、qiniu/io.c、qiniu/multipart_upload.c
- 数据处理:qiniu/fop.c
- 服务端操作:qiniu/auth_mac.c(授权),qiniu/rs.c(资源管理,上传凭证 uptoken/下载凭证 dntoken 生成)
- cdn 相关功能:qiniu/cdn.c
安装
C-SDK 使用cURL进行网络相关操作。无论是作为客户端还是服务端,都需要依赖 cURL。如果作为服务端,C-SDK 因为需要用 HMAC 进行数字签名做授权(简称签名授权),所以依赖了OpenSSL库。C-SDK 并没有带上这 2 个外部库,因此在使用 C-SDK 之前,首先确认您的当前开发环境中是否已经安装了这 2 个外部库,然后检查这 2 个外部库的头文件目录和库文件目录是否都已经加入到了项目工程的设置。
在主流的 *nix 环境下,通常cURL和OpenSSL都已经随系统默认安装到了 /usr/include 和 /usr/lib 目录下。如果您的系统还没有这些库,请自行安装。
如果你使用 gcc 进行编译,服务端典型的链接选项是:-lcurl -lssl -lcrypto -lm,客户端则是:-lcurl -lm。
如果在项目构建过程中出现环境相关的编译错误和链接错误,请确认这些选项是否都已经正确配置,以及所依赖的库是否都已经正确安装。
ACCESS_KEY 和 SECRET_KEY
如果你的服务端采用 C-SDK,那么使用 C-SDK 前,您需要拥有一对有效的 AccessKey 和 SecretKey 用来进行签名授权。可以通过如下步骤获得:
C-SDK 的 conf.h 文件中声明了对应的两个变量:QINIU_ACCESS_KEY 和 QINIU_SECRET_KEY。您需要在启动程序之初初始化这两个变量为七牛颁发的 AccessKey 和 SecretKey。
初始化
对于服务端而言,常规程序流程是:
Qiniu_Client client; QINIU_ACCESS_KEY = "<Please apply your access key>"; QINIU_SECRET_KEY = "<Dont send your secret key to anyone>"; Qiniu_Servend_Init(-1); /* 全局初始化函数,整个进程只需要调用一次 */ Qiniu_Client_InitMacAuth(&client, 1024, NULL); /* HTTP客户端初始化。HTTP客户端是线程不安全的,不要在多个线程间共用 */ Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ ... Qiniu_Client_Cleanup(&client); /* 每个HTTP客户端使用完后释放 */ Qiniu_Servend_Cleanup(); /* 全局清理函数,只需要在进程退出时调用一次 */ 对于客户端而言,常规程序流程是:
Qiniu_Client client; Qiniu_Global_Init(-1); /* 全局初始化函数,整个进程只需要调用一次 */ Qiniu_Client_InitNoAuth(&client, 1024); /* HTTP客户端初始化。HTTP客户端是线程不安全的,不要在多个线程间共用 */ Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ ... Qiniu_Client_Cleanup(&client); /* 每个HTTP客户端使用完后释放 */ Qiniu_Global_Cleanup(); /* 全局清理函数,只需要在进程退出时调用一次 */ 两者主要的区别在于:
-
客户端没有 QINIU_ACCESS_KEY、QINIU_SECRET_KEY 变量(不需要初始化)。
-
客户端没有签名授权,所以初始化 Qiniu_Client 对象应该用 Qiniu_Client_InitNoAuth 而不是 Qiniu_Client_InitMacAuth。
-
客户端初始化/清理用 Qiniu_Global_Init/Cleanup,而服务端用 Qiniu_Servend_Init/Cleanup 这对函数。
内存管理
在 C-SDK 中,有一些函数会涉及到内存的动态分配。这些函数的一惯处理方式是在函数内部申请内存,并以指针的形式直接返回。这就要求函数调用者在得到指针后,需要在恰当的时机去释放这些内存。对于特殊的结构体,C-SDK 都会提供特定的函数来释放内存,例如 Qiniu_Buffer 提供了 Qiniu_Buffer_Cleanup 函数。而对于其他基本数据类型的指针,则由 Qiniu_Free 函数来负责释放不再使用的内存。
HTTP 客户端
在 C-SDK 中,HTTP 客户端叫 Qiniu_Client。在某些语言环境中,这个类是线程安全的,多个线程可以共享同一份实例,但在 C-SDK 中它被设计为线程不安全的。一个重要的原因是我们试图简化内存管理的负担。HTTP 请求结果的生命周期被设计成由 Qiniu_Client 负责,在下一次请求时会自动释放上一次 HTTP 请求的结果。如果某个 HTTP 请求结果的数据需要长期使用,建议您复制一份,如:
void stat(Qiniu_Client* client, const char* bucket, const char* key) { Qiniu_RS_StatRet ret; Qiniu_Error err = Qiniu_RS_Stat(client, &ret, bucket, key); if (err.code != 200) { debug(client, err); return; } printf("hash: %s, fsize: %lld, mimeType: %s\n", ret.hash, ret.fsize, ret.mimeType); } 这个例子中,Qiniu_RS_Stat 请求返回了 Qiniu_Error 和 Qiniu_RS_StatRet 两个结构体。其中的 Qiniu_Error 类型如下:
typedef struct _Qiniu_Error { int code; const char* message; } Qiniu_Error; Qiniu_RS_StatRet 类型如下:
typedef struct _Qiniu_RS_StatRet { const char* hash; const char* mimeType; Qiniu_Int64 fsize; Qiniu_Int64 putTime; Qiniu_Int64 type; } Qiniu_RS_StatRet; **注意:**Qiniu_Error.message、Qiniu_RS_StatRet.hash、Qiniu_RS_StatRet.mimeType 都声明为 const char* 类型,是个只读字符串,并不管理字符串内容的生命周期。
这些字符串什么时候失效?下次 Qiniu_Client 发生网络 API 请求时失效。如果您需要长久使用,建议您复制一份,如:
hash = strdup(ret.hash); 错误处理与调试
在 HTTP 请求出错的时候,C-SDK 统一返回了一个 Qiniu_Error 结构体:
typedef struct _Qiniu_Error { int code; const char* message; } Qiniu_Error; 即一个错误码和对应的描述消息。这个错误码有可能是 cURL 的错误码,表示请求发送环节发生了意外,或者是一个 HTTP 错误码,表示请求发送正常,服务器端处理请求后返回了 HTTP 错误码。
如果一切正常,code 应该是 200,即 HTTP 的 OK 状态码。如果不是 200,则需要对 code 值进行相应分析。对于低于 200 的值,可以查看cURL 错误码,否则应查看七牛HTTP 状态码。
如果 message 指示的信息还不够友好,也可以尝试把整个 HTTP 返回包打印出来看看:
void debug(Qiniu_Client* client, Qiniu_Error err) { printf("\nerror code: %d, message: %s\n", err.code, err.message); printf("respose header:\n%s", Qiniu_Buffer_CStr(&client->respHeader)); printf("respose body:\n%s", Qiniu_Buffer_CStr(&client->b)); } 文件上传
上传流程
在七牛云存储中,整个上传流程大致分为以下几步:
- 业务服务器颁发上传凭证给客户端(终端用户)
- 客户端凭借上传凭证上传文件到七牛
- 在七牛获得完整数据后,发起一个 HTTP 请求回调到业务服务器
- 业务服务器保存相关信息,并返回一些信息给七牛
- 七牛原封不动地将这些信息转发给客户端(终端用户)
**注意:**回调到业务服务器的过程是可选的,它取决于业务服务器颁发的上传凭证。如果没有回调,七牛会返回一些标准的信息(例如文件的 hash)给客户端。
如果上传发生在业务服务器,以上流程简化为:
- 业务服务器生成上传凭证
- 凭借上传凭证上传文件到七牛
- 后续工作,例如保存一些相关信息
上传凭证
上传凭证实际上是用 AccessKey/SecretKey 进行数字签名的上传策略 Qiniu_RS_PutPolicy,它控制着整个上传流程的行为。具体上传策略参数的解释可见 上传凭证
typedef struct _Qiniu_RS_PutPolicy { const char *scope; const char *saveKey; Qiniu_Uint32 isPrefixalScope; const char *callbackUrl; const char *callbackHost; const char *callbackBody; const char *callbackBodyType; const char *callbackFetchKey; const char *returnUrl; const char *returnBody; const char *endUser; const char *persistentOps; const char *persistentNotifyUrl; const char *persistentPipeline; const char *persistentWorkflowTemplateID; const char *mimeLimit; Qiniu_Uint64 fsizeLimit; Qiniu_Uint64 fsizeMin; Qiniu_Uint32 detectMime; Qiniu_Uint32 insertOnly; Qiniu_Uint32 expires; Qiniu_Uint32 deleteAfterDays; Qiniu_Uint32 fileType; Qiniu_Uint32 persistentType; } Qiniu_RS_PutPolicy; 在构建上传凭证之前,我们首先要定义鉴权对象 Qiniu_mac
Qiniu_Mac mac; mac.accessKey = "your access key"; mac.secretKey = "your secret key"; 简单上传的凭证
最简单的上传凭证只需要AccessKey,SecretKey和Bucket就可以。
//简单上传凭证 char *bucket="your bucket name"; Qiniu_RS_PutPolicy putPolicy; Qiniu_Zero(putPolicy); putPolicy.scope = bucket; char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("simple:\t%s\n\n", uptoken); Qiniu_Free(uptoken); 默认情况下,在不指定上传凭证的有效时间情况下,默认有效期为 1 个小时。也可以自行指定上传凭证的有效期,例如:
//自定义凭证有效期(示例2小时,expires单位为秒,为上传凭证的有效时间) Qiniu_RS_PutPolicy putPolicy; Qiniu_Zero(putPolicy); putPolicy.scope = bucket; putPolicy.expires = 7200; //单位秒 char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("returnBody:\t%s\n\n", uptoken); 覆盖上传的凭证
覆盖上传除了需要简单上传所需要的信息之外,还需要想进行覆盖的文件名称,这个文件名称同时可是客户端上传代码中指定的文件名,两者必须一致。
//覆盖上传 Qiniu_RS_PutPolicy putPolicy; Qiniu_Zero(putPolicy); char *keyToOverwrite = "qiniu.png"; putPolicy.scope = Qiniu_String_Concat3(bucket, ":",keyToOverwrite); char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("overwrite:\t%s\n\n", uptoken); 自定义上传回复的凭证
默认情况下,文件上传到七牛之后,在没有设置returnBody或者回调相关的参数情况下,七牛返回给上传端的回复格式为hash和key,例如:
{"hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","key":"qiniu.jpg"} 有时候我们希望能自定义这个返回的 JSON 格式的内容,可以通过设置returnBody参数来实现,在returnBody中,我们可以使用七牛支持的魔法变量和自定义变量。
//自定义返回值 Qiniu_RS_PutPolicy putPolicy; Qiniu_Zero(putPolicy); putPolicy.scope = bucket; putPolicy.returnBody = "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"fsize\":$(fsize),\"bucket\":\"$(bucket)\",\"name\":\"$(x:name)\"}"; char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("returnBody:\t%s\n\n", uptoken); 则文件上传到七牛之后,收到的回复内容如下:
{"key":"qiniu.jpg","hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","bucket":"if-bc","fsize":39335,"name":"qiniu"} 带回调业务服务器的凭证
上面生成的自定义上传回复的上传凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景之下,有时候客户端需要在文件上传到七牛之后,从业务服务器获取相关的信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。
Qiniu_RS_PutPolicy putPolicy; Qiniu_Zero(putPolicy); putPolicy.scope = bucket; putPolicy.callbackUrl = "http://api.example.com/upload/callback"; putPolicy.callbackBodyType = "application/json"; putPolicy.callbackBody = "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"fsize\":$(fsize),\"bucket\":\"$(bucket)\",\"name\":\"$(x:name)\"}"; char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("callback(json):\t%s\n\n", uptoken); 在使用了上传回调的情况下,客户端收到的回复就是业务服务器响应七牛的 JSON 格式内容。
通常情况下,我们建议使用application/json格式来设置callbackBody,保持数据格式的统一性。实际情况下,callbackBody也支持application/x-www-form-urlencoded格式来组织内容,这个主要看业务服务器在接收到callbackBody的内容时如何解析。例如:
Qiniu_RS_PutPolicy putPolicy; Qiniu_Zero(putPolicy); putPolicy.scope = bucket; putPolicy.callbackUrl = "http://api.example.com/upload/callback"; //putPolicy.callbackBodyType = "application/json"; putPolicy.callbackBody ="key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)" ; char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("callback(json):\t%s\n\n", uptoken);
带数据处理的凭证
七牛支持在文件上传到七牛之后,立即对其进行多种指令的数据处理,这个只需要在生成的上传凭证中指定相关的处理参数即可。
//持久化数据处理的上传凭证 char *saveMp4Entry = Qiniu_String_Concat3(bucket, ":", "avthumb_test_target.mp4"); char *saveMp4EntryEncoded = Qiniu_String_Encode(saveMp4Entry); char *saveJpgEntry = Qiniu_String_Concat3(bucket, ":", "vframe_test_target.jpg"); char *saveJpgEntryEncoded = Qiniu_String_Encode(saveJpgEntry); Qiniu_Free(saveMp4Entry); Qiniu_Free(saveJpgEntry); char *avthumbMp4Fop = Qiniu_String_Concat2("avthumb/mp4|saveas/", saveMp4EntryEncoded); char *vframeJpgFop = Qiniu_String_Concat2("vframe/jpg/offset/1|saveas/", saveJpgEntryEncoded); Qiniu_Free(saveMp4EntryEncoded); Qiniu_Free(saveJpgEntryEncoded); char *fops[] = {avthumbMp4Fop, vframeJpgFop}; char *persistentFops = Qiniu_String_Join(";", fops, 2); putPolicy.scope = bucket; putPolicy.persistentPipeline = "sdktest"; putPolicy.persistentNotifyUrl = "http://api.example.com/pfop/notify"; putPolicy.persistentOps = persistentFops; putPolicy.persistentType = 0; // 任务类型:0: 普通任务 1: 闲时任务(一旦指定闲时任务,就不能指定 persistentPipeline) uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("persistent:\t%s\n\n", uptoken); 也可以支持使用工作流模版替代数据处理指令。工作流模板是预先编排好的一系列媒体处理流程(如转码、截图、视频拼接等各类处理),登录 对象存储控制台 进行创建,详情参考工作流模板操作指南,persistentWorkflowTemplateID 对应工作流模板列表的名称字段
putPolicy.scope = bucket; putPolicy.persistentPipeline = "sdktest"; putPolicy.persistentNotifyUrl = "http://api.example.com/pfop/notify"; putPolicy.persistentWorkflowTemplateID = "tempname"; putPolicy.persistentType = 0; // 任务类型:0: 普通任务 1: 闲时任务(一旦指定闲时任务,就不能指定 persistentPipeline) uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); printf("persistent:\t%s\n\n", uptoken); - 需要注意的是,
persistentWorkflowTemplateID和persistentOps两个参数只能二选一。
带自定义参数的凭证
存储支持客户端上传文件的时候定义一些自定义参数,这些参数可以在returnBody和callbackBody里面和七牛内置支持的魔法变量(即系统变量)通过相同的方式来引用。这些自定义的参数名称必须以x:开头。例如客户端上传的时候指定了自定义的参数x:name和x:age分别是string和int类型。那么可以通过下面的方式引用:
returnBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}' } 或者
callbackBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}', } 综合上传凭证
上面的生成上传凭证的方法,都是通过设置上传策略 🔗相关的参数来支持的,这些参数可以通过不同的组合方式来满足不同的业务需求,可以灵活地组织你所需要的上传凭证。
服务端直传
服务端直传是指客户利用七牛服务端 SDK 从服务端直接上传文件到七牛云,交互的双方一般都在机房里面,所以服务端可以自己生成上传凭证,然后利用 SDK 中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义returnBody来获取自定义的回复内容。
构建配置流程
存储支持空间创建在不同的区域,并且支持 http 和 https 上传。
在使用七牛的 C-SDK 中的Qiniu_Client_EnableAutoQuery(&client, Qiniu_True)函数可以开启client的区域自动检测功能,参数Qiniu_True代表使用 https 方式上传。此外也可以手动指定区域,通过使用Qiniu_Region *region = Qiniu_Use_Region(regionId, Qiniu_True)函数可以生成对应的上传空间到region对象,参数regionId为七牛区域 ID 字符串,参数Qiniu_True代表使用 https 方式上传。然后调用Qiniu_Client_SpecifyRegion(&client, region)方法将region对象设置到client上,必须注意,在client被使用完毕后,需要额外调用Qiniu_Region_Free(region)回收region对象,否则会造成内存泄漏。
其中关于Region对象和区域的关系如下:
| 区域 | 对应函数 |
|---|---|
| 华东-浙江 | Qiniu_Use_Region("z0", Qiniu_True) |
| 华北-河北 | Qiniu_Use_Region("z1", Qiniu_True) |
| 华南-广东 | Qiniu_Use_Region("z2", Qiniu_True) |
| 北美-洛杉矶 | Qiniu_Use_Region("na0", Qiniu_True) |
| 亚太-新加坡 | Qiniu_Use_Region("as0", Qiniu_True) |
| 华东-浙江 2 | Qiniu_Use_Region("cn-east-2", Qiniu_True) |
文件上传(表单方式)
最简单的就是上传本地文件,直接指定文件的完整路径即可上传。
Qiniu_Global_Init(-1); Qiniu_Mac mac; mac.accessKey = "your access key"; mac.secretKey = "your secret key"; char *bucket = "your bucket name"; char *key = "qiniu.mp4"; char *localFile = "/Users/jemy/Documents/qiniu.mp4"; Qiniu_Io_PutRet putRet; Qiniu_Client client; Qiniu_RS_PutPolicy putPolicy; Qiniu_Io_PutExtra putExtra; Qiniu_Zero(putPolicy); Qiniu_Zero(putExtra); putPolicy.scope = bucket; char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac); Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_Io_PutFile(&client, &putRet, uptoken, key, localFile, &putExtra); if (error.code != 200) { printf("upload file %s:%s error.\n", bucket, key); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过statRet变量查询一些关于这个文件的信息*/ printf("upload file %s:%s success.\n\n", bucket, key); printf("key:\t%s\n",putRet.key); printf("hash:\t%s\n", putRet.hash); } Qiniu_Free(uptoken); Qiniu_Client_Cleanup(&client); 文件分片上传(断点续传)
除了基本的上传外,七牛还支持将文件切成若干块。可以选择分片上传版本,推荐 Qiniu_Multipart_PutFile,表示 分片上传 v2 版,Qiniu_Rio_PutFile,表示 分片上传 v1 版。
断点续上传、分块上传的基本样例:
分片上传 v2 版 (v7.1.0 之上版本支持)
int resumable_upload(Qiniu_Client* client, char* uptoken, const char* key, const char* localFile, Qiniu_MultipartUpload_Result* putRet) { Qiniu_Error err; Qiniu_Recorder recorder; err = Qiniu_FileSystem_Recorder_New("/tmp", &recorder); if (err.code != 200) { return err.code; } Qiniu_Multipart_PutExtra extra; Qiniu_Zero(extra); extra.recorder = &recorder; // 支持断点续传 extra.partSize = 4 << 20 // 分片上传 v2 支持自定义分片大小,公有云片的大小范围在 1MB ~ 1GB 。 err = Qiniu_Multipart_PutFile(client, uptoken, key, localFile, &extra, putRet); recorder.free(&recorder); return err.code; } 相比普通上传,断点续上传代码没有变复杂。基本上就只是将 Qiniu_Io_PutExtra 改为 Qiniu_Multipart_PutExtra,Qiniu_Io_PutFile 改为 Qiniu_Multipart_PutFile。
实际上 Qiniu_Multipart_PutFile 多了不少配置项,其中最重要的是:recorder,它被用来设置断点的存储方式,例如可以用 Qiniu_FileSystem_Recorder_New 来创建基于文件系统的断点存储,你也可以基于 Qiniu_Recorder 对象定义自己的断点存储方式。
分片上传 v1 版
int resumable_upload(Qiniu_Client* client, char* uptoken, const char* key, const char* localFile) { Qiniu_Error err; Qiniu_Recorder recorder; err = Qiniu_FileSystem_Recorder_New("/tmp", &recorder); if (err.code != 200) { return err.code; } Qiniu_Rio_PutExtra extra; Qiniu_Zero(extra); extra.bucket = bucket; extra.recorder = &recorder; // 支持断点续传 err = Qiniu_Rio_PutFile(client, NULL, uptoken, key, localFile, &extra); recorder.free(&recorder); return err.code; } 解析自定义回复内容
有些情况下,七牛返回给上传端的内容不是默认的hash和key形式,这种情况下,可能出现在自定义returnBody或者自定义了callbackBody的情况下,前者一般是服务端直传的场景,而后者则是接受上传回调的场景,这两种场景之下,都涉及到需要将自定义的回复进行内容解析,一般建议在交互过程中,都采用JSON的方式,这样处理起来方法比较一致。
业务服务器验证存储服务回调
在上传策略里面设置了上传回调相关参数的时候,七牛在文件上传到服务器之后,会主动地向callbackUrl发送 POST 请求的回调,回调的内容为callbackBody模版所定义的内容,如果这个模版里面引用了魔法变量或者自定义变量,那么这些变量会被自动填充对应的值,然后在发送给业务服务器。
注意:业务端服务器回调鉴权的 Content-Type 类型应该与上传策略中指定的 callbackBodyType 相同,默认为 application/x-www-form-urlencoded,当 Content-Type 为 application/x-www-form-urlencoded 时,签名内容必须包括请求内容。当 Content-Type 为 application/json 时,签名内容不包括请求内容。
业务服务器在收到来自七牛的回调请求的时候,可以根据请求头部的Authorization字段来进行验证,查看该请求是否是来自七牛的未经篡改的请求,具体可以参考七牛的回调鉴权。
下载文件
公开空间
每个 bucket 都会绑定一个或多个域名 (domain)。如果这个 bucket 是公开的,那么该 bucket 中的所有文件可以通过一个公开的下载 url 访问:
http://domain/key 其中 domain 是 bucket 对应的域名。七牛云存储为每一个 bucket 提供一个默认域名。默认域名可以到七牛开发者平台中的存储空间查询。
假设某个 bucket 既绑定了七牛的二级域名,如:hello.qiniudn.com,也绑定了自定义域名(需要备案),如:hello.com。那么该 bucket 中 key 为 test.mp4 的文件可以通过 http://hello.qiniudn.com/qiniu.mp4 或 http://hello.com/qiniu.mp4 中任意一个 url 进行访问。
私有空间
如果某个 bucket 是私有的,那么这个 bucket 中的所有文件只能通过一个临时有效的 downloadUrl 访问:
http://domain/key?e=<deadline>&token=<dntoken> 其中 dntoken 是由业务服务器签发的一个临时下载凭证,deadline 是其有效期。下载凭证不需要单独生成,C-SDK 提供了生成完整 downloadUrl 的方法(包含了 dntoken),示例代码如下:
char* downloadUrl(const char* domain, const char* key, Qiniu_Mac* mac) { char* url = 0; char* baseUrl = 0; Qiniu_RS_GetPolicy getPolicy; Qiniu_Zero(getPolicy); baseUrl = Qiniu_RS_MakeBaseUrl(domain, key); url = Qiniu_RS_GetPolicy_MakeRequest(&getPolicy, baseUrl, mac); Qiniu_Free(baseUrl); return url; } 生成 downloadUrl 后,服务端下发 downloadUrl 给客户端。客户端收到 downloadUrl 后,和公有资源类似,直接用任意的 HTTP 客户端就可以下载该资源了。需要注意的是,在 downloadUrl 失效却还没有完成下载时,需要重新向服务器申请授权。
资源管理
资源管理包括的主要功能有:
资源管理包括对存储在七牛云存储上的文件进行查看、删除、复制和移动处理。同时七牛云存储也支持对文件进行相应的批量操作。所有操作都会返回一个 Qiniu_Error 的结构体,用于记录本次操作的成功/失败信息。
- 获取文件信息
- 修改文件 MimeType
- 修改文件存储类型
- 移动或重命名文件
- 复制文件副本
- 删除空间中的文件
- 设置或更新文件生存时间
- 获取指定前缀文件列表
- 抓取网络资源到空间
- 更新镜像存储空间中文件内容
- 资源管理批量操作
获取文件信息
通过调用 Qiniu_RS_Stat,可以得到指定文件的属性信息。除了会返回一个 Qiniu_Error 结构体外,Qiniu_RS_Stat 还会返回 Qiniu_RS_StatRet 结构体,其中记录了被查询文件的一些属性信息。
char *bucket="if-pbl"; char *key="qiniu.mp4"; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_Stat(&client, &statRet, bucket, key); if (error.code != 200) { printf("stat file %s:%s error.\n", bucket, key); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过statRet变量查询一些关于这个文件的信息*/ printf("stat file \t%s:%s success.\n\n", bucket, key); printf("file hash: \t%s\n", statRet.hash); printf("file size: \t%lld\n", statRet.fsize); printf("file put time: \t%lld\n", statRet.putTime); printf("file mime type: \t%s\n", statRet.mimeType); printf("file type: \t%lld\n", statRet.type); } Qiniu_Client_Cleanup(&client); 修改文件 MimeType
char *bucket="if-pbl"; char *key="qiniu.mp4"; char *newMime="video/x-mp4"; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_ChangeMime(&client, bucket, key, newMime); if (error.code != 200) { printf("change file mime %s:%s error.\n", bucket, key); debug_log(&client, error); } else { printf("change file mime %s:%s success.\n\n", bucket, key); } Qiniu_Client_Cleanup(&client); 修改文件存储类型
char *bucket = "if-pbl"; char *key = "qiniu.mp4"; //fileType 为 0 表示普通存储,fileType 为 1 表示低频存储,fileType 为 2 表示归档存储,fileType 为 3 表示深度归档存储,fileType 为 4 表示归档直读存储 int fileType = 1; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_ChangeType(&client, bucket, key, fileType); if (error.code != 200) { printf("change file type %s:%s error.\n", bucket, key); debug_log(&client, error); } else { printf("change file type %s:%s success.\n\n", bucket, key); } Qiniu_Client_Cleanup(&client); 移动或重命名文件
移动操作本身支持移动文件到相同,不同空间中,在移动的同时也可以支持文件重命名。唯一的限制条件是,移动的源空间和目标空间必须在同一个区域。
| 源空间 | 目标空间 | 源文件名 | 目标文件名 | 描述 |
|---|---|---|---|---|
| BucketA | BucketA | KeyA | KeyB | 相当于同空间文件重命名 |
| BucketA | BucketB | KeyA | KeyA | 移动文件到 BucketB,文件名一致 |
| BucketA | BucketB | KeyA | KeyB | 移动文件到 BucketB,文件名变成 KeyB |
move操作支持强制覆盖选项,即如果目标文件已存在,可以设置强制覆盖选项force来覆盖那个文件的内容。
char *srcbucket="if-pbl"; char *srcKey = "qiniu.png"; char *destBucket = srcBucket; char *destKey = "qiniu_move.png"; Qiniu_Bool force = Qiniu_True; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_Move(&client, srcBucket, srcKey, destBucket, destKey, force); if (error.code != 200) { printf("move file %s:%s -> %s:%s error.\n", srcBucket, srcKey, destBucket, destKey); debug_log(&client, error); } else { printf("move file %s:%s -> %s:%s success.\n", srcBucket, srcKey, destBucket, destKey); } Qiniu_Client_Cleanup(&client); 复制文件副本
文件的复制和文件移动其实操作一样,主要的区别是移动后源文件不存在了,而复制的结果是源文件还存在,只是多了一个新的文件副本。
char *srcBucket="if-pbl"; char *srcKey = "qiniu.png"; char *destBucket = srcBucket; char *destKey = "qiniu_copy.png"; Qiniu_Bool force = Qiniu_True; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_Copy(&client, srcBucket, srcKey, destBucket, destKey, force); if (error.code != 200) { printf("copy file %s:%s -> %s:%s error.\n", srcBucket, srcKey, destBucket, destKey); debug_log(&client, error); } else { printf("copy file %s:%s -> %s:%s success.\n", srcBucket, srcKey, destBucket, destKey); } Qiniu_Client_Cleanup(&client); 删除空间中的文件
char *bucket="if-pbl"; char *key="qiniu.mp4"; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_Delete(&client, bucket, key); if (error.code != 200) { printf("delete file %s:%s error.\n", bucket, key); debug_log(&client, error); } else { printf("delete file %s:%s file success.\n", bucket, key); } Qiniu_Client_Cleanup(&client); 设置或更新文件的生存时间
可以给已经存在于空间中的文件设置文件生存时间,或者更新已设置了生存时间但尚未被删除的文件的新的生存时间。
char *bucket="if-pbl"; char *key="qiniu.mp4"; int days=7; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_DeleteAfterDays(&client, bucket, key, days); if (error.code != 200) { printf("deleteAfterDays file %s:%s error.\n", bucket, key); debug_log(&client, error); } else { printf("deleteAfterDays file %s:%s file success.\n", bucket, key); } Qiniu_Client_Cleanup(&client); 获取指定空间的文件列表
// @param options 列举操作的可选参数 // prefix 列举的文件前缀 // marker 上一次列举返回的位置标记,作为本次列举的起点信息 // limit 每次返回的最大列举文件数量 // delimiter 指定目录分隔符 char *bucket="if-pbl"; char **commonPrefixes = NULL; Qiniu_RSF_ListItem *items = NULL; char *prefix = ""; char *delimiter = "/"; char *marker = ""; char *nextMarker = marker; Qiniu_Bool isNewMarker = Qiniu_False; Qiniu_RSF_ListRet listRet; int limit = 10; int i; //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ do { Qiniu_Error error = Qiniu_RSF_ListFiles(&client, &listRet, bucket, prefix, delimiter, nextMarker, limit); if (error.code != 200) { printf("list files of bucket %s error.\n", bucket); debug_log(&client, error); nextMarker = ""; } else { /*200, 正确返回了, 你可以通过listRet变量查询文件列表信息*/ printf("list files of bucket %s success.\n\n", bucket); //check next marker if (isNewMarker == Qiniu_True) { free(nextMarker); } if (!str_empty(listRet.marker)) { nextMarker = strdup(listRet.marker); isNewMarker = Qiniu_True; } else { nextMarker = NULL; } printf("next marker: %s\n", nextMarker); //common prefixes commonPrefixes = listRet.commonPrefixes; for (i = 0; i < listRet.commonPrefixesCount; i++) { printf("commonPrefix: %s\n", *commonPrefixes); ++commonPrefixes; } //items items = listRet.items; for (i = 0; i < listRet.itemsCount; i++) { Qiniu_RSF_ListItem item = listRet.items[i]; printf("key: %s, hash: %s, fsize: %lld, mime: %s, putTime: %lld, endUser: %s, type: %lld\n", item.key, item.hash, item.fsize, item.mimeType, item.putTime, item.endUser, item.type); } //free Qiniu_RSF_ListRet_Cleanup(&listRet); } } while (!str_empty(nextMarker)); Qiniu_Client_Cleanup(&client); 抓取网络资源到空间
//fetch with key Qiniu_RS_FetchRet fetchRet; char *bucket="if-pbl"; char *resURL = "http://devtools.qiniu.com/qiniu.png"; char *key = "qiniu.png"; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_Fetch(&client, &fetchRet, resURL, bucket, key); if (error.code != 200) { printf("fetch file %s -> %s:%s error.\n", resURL, bucket, key); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过fetchRet变量查询一些关于这个文件的信息*/ printf("fetch file %s -> %s:%s success.\n", resURL, bucket, key); printf("file key: \t%s\n", fetchRet.key); printf("file hash: \t%s\n", fetchRet.hash); printf("file size: \t%lld\n", fetchRet.fsize); printf("file mime type: \t%s\n", fetchRet.mimeType); } Qiniu_Client_Cleanup(&client); 更新镜像空间中存储的文件内容
char *bucket="if-pbl"; char *key="qiniu.mp4"; Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_Prefetch(&client, bucket, key); if (error.code != 200) { printf("prefetch file %s:%s error.\n", bucket, key); debug_log(&client, error); } else { printf("prefetch file \t%s:%s success.\n\n", bucket, key); } Qiniu_Client_Cleanup(&client); 资源管理批量操作
** 批量获取文件信息 **
Qiniu_RS_BatchStatRet *statRets; int i; char *bucket="if-pbl"; char *key="qiniu.mp4"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryPath *entries = (Qiniu_RS_EntryPath *) malloc(sizeof(Qiniu_RS_EntryPath) * entryCount); for (i = 0; i < entryCount; i++) { Qiniu_RS_EntryPath entry; entry.bucket = bucket; size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); entry.key = Qiniu_String_Concat2(key, indexStr); Qiniu_Free(indexStr); entries[i] = entry; } statRets = (Qiniu_RS_BatchStatRet *) malloc(sizeof(Qiniu_RS_BatchStatRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchStat(&client, statRets, entries, entryCount); if (error.code / 100 != 2) { printf("batch stat file error.\n"); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过statRets变量查询一些关于这个文件的信息*/ printf("batch stat file success.\n\n"); for (i = 0; i < entryCount; i++) { Qiniu_RS_StatRet data = statRets[i].data; int code = statRets[i].code; if (code == 200) { printf("file hash: \t%s\n", data.hash); printf("file size: \t%lld\n", data.fsize); printf("file put time: \t%lld\n", data.putTime); printf("file mime type: \t%s\n", data.mimeType); printf("file type: \t%lld\n", data.type); } else { printf("error: %s\n", statRets[i].error); } } } Qiniu_Client_Cleanup(&client); 其中,entries 是一个指向 Qiniu_RS_EntryPath 结构体数组的指针,entryCount 为数组 entries 的长度。结构体 Qiniu_RS_EntryPath 中填写每个文件相应的 bucket 和 key:
typedef struct _Qiniu_RS_EntryPath { const char* bucket; const char* key; } Qiniu_RS_EntryPath; Qiniu_RS_BatchStat 会将文件信息(及成功/失败信息)依次写入一个由结构体 Qiniu_RS_BatchStatRet 组成的数组空间 rets。因此,调用之前,需要先给 rets 申请好相应长度的内存空间。
其中结构体 Qiniu_RS_BatchStatRet 的组成如下:
typedef struct _Qiniu_RS_BatchStatRet { Qiniu_RS_StatRet data; const char* error; int code; }Qiniu_RS_BatchStatRet; 批量修改文件类型
Qiniu_RS_BatchItemRet *itemRets; Qiniu_Client client; int i; char *key="qiniu.mp4"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryChangeMime *entries = (Qiniu_RS_EntryChangeMime *) malloc( sizeof(Qiniu_RS_EntryChangeMime) * entryCount); for (i = 0; i < entryCount; i++) { Qiniu_RS_EntryChangeMime entry; entry.bucket = bucket; size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); entry.key = Qiniu_String_Concat2(key, indexStr); Qiniu_Free(indexStr); entry.mime = "image/x-png"; entries[i] = entry; } itemRets = (Qiniu_RS_BatchItemRet *) malloc(sizeof(Qiniu_RS_BatchItemRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchChangeMime(&client, itemRets, entries, entryCount); if (error.code / 100 != 2) { printf("batch change file mime error.\n"); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过itemRets变量查询一些关于这个文件的信息*/ printf("batch change file mime success.\n\n"); for (i = 0; i < entryCount; i++) { int code = itemRets[i].code; if (code == 200) { printf("success: %d\n", code); } else { printf("code: %d, error: %s\n", code, itemRets[i].error); } } } Qiniu_Client_Cleanup(&client); 批量删除文件
Qiniu_RS_BatchItemRet *itemRets; Qiniu_Client client; int i; char *key = "qiniu.png"; char *bucket="if-pbl"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryPath *entries = (Qiniu_RS_EntryPath *) malloc(sizeof(Qiniu_RS_EntryPath) * entryCount); for (i = 0; i < entryCount; i++) { Qiniu_RS_EntryPath entry; entry.bucket = bucket; size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); entry.key = Qiniu_String_Concat2(key, indexStr); entries[i] = entry; } itemRets = (Qiniu_RS_BatchItemRet *) malloc(sizeof(Qiniu_RS_BatchItemRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchDelete(&client, itemRets, entries, entryCount); if (error.code / 100 != 2) { printf("batch delete file error.\n"); debug_log(&client, error); } else { printf("batch delete file success.\n\n"); for (i = 0; i < entryCount; i++) { int code = itemRets[i].code; if (code == 200) { printf("success: %d\n", code); } else { printf("code: %d, error: %s\n", code, itemRets[i].error); } } } Qiniu_Client_Cleanup(&client); 批量复制文件
Qiniu_RS_BatchItemRet *itemRets; Qiniu_Client client; int i; char *key = "qiniu.png"; char *bucket="if-pbl"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryPathPair *entryPairs = (Qiniu_RS_EntryPathPair *) malloc(sizeof(Qiniu_RS_EntryPathPair) * entryCount); for (i = 0; i < entryCount; i++) { //src Qiniu_RS_EntryPath srcEntry; srcEntry.bucket = bucket; srcEntry.key = key; //dest Qiniu_RS_EntryPath destEntry; destEntry.bucket = bucket; size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); destEntry.key = Qiniu_String_Concat2(key, indexStr); Qiniu_Free(indexStr); //pack Qiniu_RS_EntryPathPair entryPair; entryPair.src = srcEntry; entryPair.dest = destEntry; entryPair.force = Qiniu_True; entryPairs[i] = entryPair; } itemRets = (Qiniu_RS_BatchItemRet *) malloc(sizeof(Qiniu_RS_BatchItemRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchCopy(&client, itemRets, entryPairs, entryCount); if (error.code / 100 != 2) { printf("batch copy file error.\n"); debug_log(&client, error); } else { printf("batch copy file success.\n\n"); for (i = 0; i < entryCount; i++) { int code = itemRets[i].code; if (code == 200) { printf("success: %d\n", code); } else { printf("code: %d, error: %s\n", code, itemRets[i].error); } } } Qiniu_Client_Cleanup(&client); 批量移动或重命名文件
Qiniu_RS_BatchItemRet *itemRets; Qiniu_Client client; int i; char *bucket="if-pbl"; char *key = "qiniu.png"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryPathPair *entryPairs = (Qiniu_RS_EntryPathPair *) malloc(sizeof(Qiniu_RS_EntryPathPair) * entryCount); for (i = 0; i < entryCount; i++) { size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); //src Qiniu_RS_EntryPath srcEntry; srcEntry.bucket = bucket; srcEntry.key = Qiniu_String_Concat2(key, indexStr); //dest Qiniu_RS_EntryPath destEntry; destEntry.bucket = bucket; destEntry.key = Qiniu_String_Concat3(key, indexStr, "-move"); Qiniu_Free(indexStr); //pack Qiniu_RS_EntryPathPair entryPair; entryPair.src = srcEntry; entryPair.dest = destEntry; entryPair.force = Qiniu_True; entryPairs[i] = entryPair; } itemRets = (Qiniu_RS_BatchItemRet *) malloc(sizeof(Qiniu_RS_BatchItemRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchMove(&client, itemRets, entryPairs, entryCount); if (error.code / 100 != 2) { printf("batch move file error.\n"); debug_log(&client, error); } else { printf("batch move file success.\n\n"); for (i = 0; i < entryCount; i++) { int code = itemRets[i].code; if (code == 200) { printf("success: %d\n", code); } else { printf("code: %d, error: %s\n", code, itemRets[i].error); } } } Qiniu_Client_Cleanup(&client); 批量更新文件的有效期
Qiniu_RS_BatchItemRet *itemRets; Qiniu_Client client; int i; char *bucket="if-pbl"; char *key = "qiniu.png"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryDeleteAfterDays *entries = (Qiniu_RS_EntryDeleteAfterDays *) malloc( sizeof(Qiniu_RS_EntryDeleteAfterDays) * entryCount); for (i = 0; i < entryCount; i++) { Qiniu_RS_EntryDeleteAfterDays entry; entry.bucket = bucket; size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); entry.key = Qiniu_String_Concat2(key, indexStr); Qiniu_Free(indexStr); entry.days = 7;//设置7天有效期 entries[i] = entry; } itemRets = (Qiniu_RS_BatchItemRet *) malloc(sizeof(Qiniu_RS_BatchItemRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchDeleteAfterDays(&client, itemRets, entries, entryCount); if (error.code / 100 != 2) { printf("batch deleteAfterDays file error.\n"); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过itemRets变量查询一些关于这个文件的信息*/ printf("batch deleteAfterDays file success.\n\n"); for (i = 0; i < entryCount; i++) { int code = itemRets[i].code; if (code == 200) { printf("success: %d\n", code); } else { printf("code: %d, error: %s\n", code, itemRets[i].error); } } } Qiniu_Client_Cleanup(&client); 批量更新文件存储类型
Qiniu_RS_BatchItemRet *itemRets; Qiniu_Client client; int i; char *key = "qiniu.png"; char *bucket="if-pbl"; Qiniu_ItemCount entryCount = 10; Qiniu_RS_EntryChangeType *entries = (Qiniu_RS_EntryChangeType *) malloc( sizeof(Qiniu_RS_EntryChangeType) * entryCount); for (i = 0; i < entryCount; i++) { Qiniu_RS_EntryChangeType entry; entry.bucket = bucket; size_t indexLen = snprintf(NULL, 0, "%d", i) + 1; char *indexStr = (char *) calloc(sizeof(char), indexLen); snprintf(indexStr, indexLen, "%d", i); entry.key = Qiniu_String_Concat2(key, indexStr); Qiniu_Free(indexStr); entry.fileType = 1;//改为低频存储 entries[i] = entry; } itemRets = (Qiniu_RS_BatchItemRet *) malloc(sizeof(Qiniu_RS_BatchItemRet) * entryCount); //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_Error error = Qiniu_RS_BatchChangeType(&client, itemRets, entries, entryCount); if (error.code / 100 != 2) { printf("batch change file type error.\n"); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过itemRets变量查询一些关于这个文件的信息*/ printf("batch change file type success.\n\n"); for (i = 0; i < entryCount; i++) { int code = itemRets[i].code; if (code == 200) { printf("success: %d\n", code); } else { printf("code: %d, error: %s\n", code, itemRets[i].error); } } } Qiniu_Client_Cleanup(&client);
持久化数据处理
发送数据处理请求(数据处理指令)
对于已经保存到七牛空间的文件,可以通过发送持久化的数据处理指令来进行处理,这些指令支持七牛官方提供的指令,也包括客户自己开发的自定义数据处理的指令。数据处理的结果还可以通过七牛主动通知的方式告知业务服务器。
闲时任务的功能介绍、使用场景、定价,详见 闲时任务策略说明。
char *bucket="if-pbl"; char *key = "qiniu.mp4"; char *pipeline = "sdktest"; char *notifyURL = NULL; int force = 0; //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ char *saveMp4Entry = Qiniu_String_Concat3(bucket, ":", "avthumb_test_target.mp4"); char *saveMp4EntryEncoded = Qiniu_String_Encode(saveMp4Entry); Qiniu_Free(saveMp4Entry); char *saveJpgEntry = Qiniu_String_Concat3(bucket, ":", "vframe_test_target.jpg"); char *saveJpgEntryEncoded = Qiniu_String_Encode(saveJpgEntry); Qiniu_Free(saveJpgEntry); char *avthumbMp4Fop = Qiniu_String_Concat2("avthumb/mp4|saveas/", saveMp4EntryEncoded); char *vframeJpgFop = Qiniu_String_Concat2("vframe/jpg/offset/1|saveas/", saveJpgEntryEncoded); Qiniu_Free(saveMp4EntryEncoded); Qiniu_Free(saveJpgEntryEncoded); char *fops[] = {avthumbMp4Fop, vframeJpgFop}; Qiniu_FOP_PfopParams params = { .bucket = bucket, .key = key, .pipeline = pipeline, .notifyURL = notifyURL, .fops = fops, .fopCount = 2, .force = force, .type = 0, // 任务类型:0: 普通任务 1: 闲时任务(一旦指定闲时任务,就不能指定 pipeline) }; Qiniu_Error error = Qiniu_FOP_Pfop_v2(&client, &pfopRet, ¶ms); if (error.code != 200) { printf("video file pfop %s:%s error.\n", bucket, key); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过pfopRet变量查询任务ID*/ printf("video file pfop %s:%s success, persistentId: %s .\n\n", bucket, key, pfopRet.persistentId); } Qiniu_Client_Cleanup(&client); 发送数据处理请求(工作流模版)
也可以支持使用工作流模版替代数据处理指令。工作流模板是预先编排好的一系列媒体处理流程(如转码、截图、视频拼接等各类处理),登录 对象存储控制台 进行创建,详情参考工作流模板操作指南,workflowTemplateID 对应工作流模板列表的名称字段
char *bucket="if-pbl"; char *key = "qiniu.mp4"; char *workflowTemplateID = "tempname"; char *pipeline = "sdktest"; char *notifyURL = NULL; int force = 0; //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); Qiniu_Client_EnableAutoQuery(&client, Qiniu_True); /* 启用区域自动查询,无需手动设置区域 */ Qiniu_FOP_PfopParams params = { .bucket = bucket, .key = key, .pipeline = pipeline, .notifyURL = notifyURL, .workflowTemplateID = workflowTemplateID, .force = force, .type = 0, // 任务类型:0: 普通任务 1: 闲时任务(一旦指定闲时任务,就不能指定 pipeline) }; Qiniu_Error error = Qiniu_FOP_Pfop_v2(&client, &pfopRet, ¶ms); if (error.code != 200) { printf("video file pfop %s:%s error.\n", bucket, key); debug_log(&client, error); } else { /*200, 正确返回了, 你可以通过pfopRet变量查询任务ID*/ printf("video file pfop %s:%s success, persistentId: %s .\n\n", bucket, key, pfopRet.persistentId); } Qiniu_Client_Cleanup(&client); CDN 相关功能
CDN 的主要函数主要可以在 cdn.h 中看到,下面列举了主要的功能。
文件刷新
Qiniu_CDN_RefreshRet ret; Qiniu_Error error; char **p; int i; //刷新链接,单次请求链接不可以超过100个,如果超过,请分批发送请求 //urls to refresh char *urls[] = { "http://csdk.qiniudn.com/qiniu1.png", "http://csdk.qiniudn.com/qiniu2.png", "http://csdk.qiniudn.com/qiniu3.png" }; //init Qiniu_Zero(ret); Qiniu_Client_InitMacAuth(&client, 1024, &mac); error = Qiniu_CDN_RefreshUrls(&client, &ret, urls, 3); if (error.code != 200) { printf("refresh urls error.\n"); debug_log(&client, error); } else { printf("refresh urls success.\n"); printf("Code: %d\n", ret.code); printf("Error: %s\n", ret.error); printf("RequestId: %s\n", ret.requestId); p = ret.invalidUrls; for (i = 0; i < ret.invalidUrlsCount; i++) { printf("InvalidUrl: %s\n", *p); ++p; } p = ret.invalidDirs; for (i = 0; i < ret.invalidDirsCount; i++) { printf("InvalidDir: %s\n", *p); ++p; } printf("UrlQuotaDay: %d\n", ret.urlQuotaDay); printf("UrlSurplusDay: %d\n", ret.urlSurplusDay); printf("DirQuotaDay: %d\n", ret.dirQuotaDay); printf("DirSurplusDay: %d\n", ret.dirSurplusDay); Qiniu_Free_CDNRefreshRet(&ret); } Qiniu_Client_Cleanup(&client); 目录刷新
Qiniu_CDN_RefreshRet ret; Qiniu_Error error; char **p; int i; //刷新目录,刷新目录需要联系七牛技术支持开通权限 //单次请求链接不可以超过10个,如果超过,请分批发送请求 char *dirs[] = { "http://csdk.qiniudn.com/image1/", "http://csdk.qiniudn.com/image2/", "http://csdk.qiniudn.com/image3/" }; //init Qiniu_Client_InitMacAuth(&client, 1024, &mac); error = Qiniu_CDN_RefreshDirs(&client, &ret, dirs, 3); if (error.code != 200) { printf("refresh dirs error.\n"); debug_log(&client, error); } else { printf("refresh dirs success.\n"); printf("Code: %d\n", ret.code); printf("Error: %s\n", ret.error); printf("RequestId: %s\n", ret.requestId); p = ret.invalidUrls; for (i = 0; i < ret.invalidUrlsCount; i++) { printf("InvalidUrl: %s\n", *p); ++p; } p = ret.invalidDirs; for (i = 0; i < ret.invalidDirsCount; i++) { printf("InvalidDir: %s\n", *p); ++p; } printf("UrlQuotaDay: %d\n", ret.urlQuotaDay); printf("UrlSurplusDay: %d\n", ret.urlSurplusDay); printf("DirQuotaDay: %d\n", ret.dirQuotaDay); printf("DirSurplusDay: %d\n", ret.dirSurplusDay); Qiniu_Free_CDNRefreshRet(&ret); } Qiniu_Client_Cleanup(&client); 文件预取
Qiniu_CDN_PrefetchRet ret; Qiniu_Error error; char **p; int i; //预取链接,单次请求链接不可以超过100个,如果超过,请分批发送请求 //urls to refresh char *urls[] = { "http://csdk.qiniudn.com/qiniu1.png", "http://csdk.qiniudn.com/qiniu2.png", "http://csdk.qiniudn.com/qiniu3.png" }; //init Qiniu_Zero(ret); Qiniu_Client_InitMacAuth(&client, 1024, &mac); error = Qiniu_CDN_PrefetchUrls(&client, &ret, urls, 3); if (error.code != 200) { printf("prefetch urls error.\n"); debug_log(&client, error); } else { printf("prefetch urls success.\n"); printf("Code: %d\n", ret.code); printf("Error: %s\n", ret.error); printf("RequestId: %s\n", ret.requestId); p = ret.invalidUrls; for (i = 0; i < ret.invalidUrlsCount; i++) { printf("InvalidUrl: %s\n", *p); ++p; } printf("QuotaDay: %d\n", ret.quotaDay); printf("SurplusDay: %d\n", ret.surplusDay); Qiniu_Free_CDNPrefetchRet(&ret); } Qiniu_Client_Cleanup(&client); 获取域名流量
Qiniu_CDN_FluxRet ret; Qiniu_Error error; int i, j; //urls to refresh char *domains[] = { "csdk.qiniudn.com", "javasdk.qiniudn.com" }; int domainsCount = 2; char *startDate = "2017-08-01"; char *endDate = "2017-08-09"; char *g = "day"; //init Qiniu_Zero(ret); Qiniu_Client_InitMacAuth(&client, 1024, &mac); error = Qiniu_CDN_GetFluxData(&client, &ret, startDate, endDate, g, domains, domainsCount); if (error.code != 200) { printf("get domain flux error.\n"); debug_log(&client, error); } else { printf("get domain flux success.\n\n"); printf("Code: %d\n", ret.code); printf("Error: %s\n", ret.error); printf("Domains: %d\n", ret.domainsCount); printf("-----------\n"); for (i = 0; i < ret.timeCount; i++) { printf("%s\t", ret.time[i]); } printf("\n"); //data for (i = 0; i < ret.domainsCount; i++) { printf("Domain:%s\n", domains[i]); Qiniu_CDN_FluxData fluxData = ret.data[i]; if (fluxData.chinaCount == 0) { printf("China: no flux data\n"); } else { printf("China: flux data\n"); for (j = 0; j < fluxData.chinaCount; j++) { printf("%lld\t", fluxData.china[j]); } printf("\n"); } if (fluxData.overseaCount == 0) { printf("Oversea: no flux data\n"); } else { printf("Oversea: flux data\n"); for (j = 0; j < fluxData.overseaCount; j++) { printf("%lld\t", fluxData.oversea[j]); } printf("\n"); } printf("-----------\n"); } Qiniu_Free_CDNFluxRet(&ret); } Qiniu_Client_Cleanup(&client); 获取域名带宽
Qiniu_CDN_BandwidthRet ret; Qiniu_Error error; int i, j; //urls to refresh char *domains[] = { "csdk.qiniudn.com", "javasdk.qiniudn.com" }; int domainsCount = 2; char *startDate = "2017-08-01"; char *endDate = "2017-08-09"; char *g = "day"; //init Qiniu_Zero(ret); Qiniu_Client_InitMacAuth(&client, 1024, &mac); error = Qiniu_CDN_GetBandwidthData(&client, &ret, startDate, endDate, g, domains, domainsCount); if (error.code != 200) { printf("get domain bandwidth error.\n"); debug_log(&client, error); } else { printf("get domain bandwidth success.\n\n"); printf("Code: %d\n", ret.code); printf("Error: %s\n", ret.error); printf("Domains: %d\n", ret.domainsCount); printf("-----------\n"); for (i = 0; i < ret.timeCount; i++) { printf("%s\t", ret.time[i]); } printf("\n"); //data for (i = 0; i < ret.domainsCount; i++) { printf("Domain:%s\n", domains[i]); Qiniu_CDN_BandwidthData bandData = ret.data[i]; if (bandData.chinaCount == 0) { printf("China: no bandwidth data\n"); } else { printf("China: bandwidth data\n"); for (j = 0; j < bandData.chinaCount; j++) { printf("%lld\t", bandData.china[j]); } printf("\n"); } if (bandData.overseaCount == 0) { printf("Oversea: no bandwidth data\n"); } else { printf("Oversea: bandwidth data\n"); for (j = 0; j < bandData.overseaCount; j++) { printf("%lld\t", bandData.oversea[j]); } printf("\n"); } printf("-----------\n"); } Qiniu_Free_CDNBandwidthRet(&ret); } Qiniu_Client_Cleanup(&client); 获取日志下载链接
Qiniu_CDN_LogListRet ret; Qiniu_Error error; int i, j; //urls to refresh char *domains[] = { "csdk.qiniudn.com", "javasdk.qiniudn.com" }; int domainsCount = 2; char *day = "2017-08-07"; //init Qiniu_Zero(ret); Qiniu_Client_InitMacAuth(&client, 1024, &mac); error = Qiniu_CDN_GetLogList(&client, &ret, domains, domainsCount,day); if (error.code != 200) { printf("get domain logs error.\n"); debug_log(&client, error); } else { printf("get domain logs success.\n\n"); printf("Code: %d\n", ret.code); printf("Error: %s\n", ret.error); printf("Domains: %d\n", ret.domainsCount); printf("-----------\n"); //data for (i = 0; i < ret.domainsCount; i++) { Qiniu_CDN_LogListData logData = ret.data[i]; if (logData.itemsCount == 0) { printf("domain: %s, no log data\n", logData.domain); printf("-----------\n"); } else { printf("domain: %s have %d log files\n", logData.domain, logData.itemsCount); for (j = 0; j < logData.itemsCount; j++) { printf("name: %s\n", logData.items[j].name); } printf("-----------\n"); } } Qiniu_Free_CDNLogListRet(&ret); } Qiniu_Client_Cleanup(&client); 构建时间戳防盗链访问链接
具体算法可以参考:时间戳防盗链
#include "../qiniu/cdn.h" #include "../qiniu/tm.h" #include <stdio.h> int main(int argc, char *argv[]) { char *host = "http://v1.cdn.example.com"; char *fileName = "video/caipu/201708/0600/s/5985f37b37025.mp4/eGgxMDgwcA.m3u8"; char *cryptKey = "xxx"; char *queryStr = "name=七牛&age=27"; char *finalUrl; Qiniu_Uint64 deadline = Qiniu_Tm_LocalTime() + 3600; finalUrl = Qiniu_CDN_CreateTimestampAntiLeechURL(host, fileName, queryStr, deadline, cryptKey); printf("%s\n", finalUrl); Qiniu_Free(finalUrl); } API 参考
常见问题
- API 的使用,可以参考我们为大家精心准备的使用实例。
相关资源
如果您有任何关于我们文档或产品的建议和想法,欢迎您通过以下方式与我们互动讨论:
- 技术论坛 - 在这里您可以和其他开发者愉快的讨论如何更好的使用七牛云服务
- 提交工单 - 如果您的问题不适合在论坛讨论或希望及时解决,您也可以提交一个工单,我们的技术支持人员会第一时间回复您
- 博客 - 这里会持续更新发布市场活动和技术分享文章
- 微博
- 常见问题
贡献代码
-
Fork
-
创建您的特性分支 git checkout -b my-new-feature
-
提交您的改动 git commit -am ‘Added some feature’
-
将您的修改记录提交到远程 git 仓库 git push origin my-new-feature
-
然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request
许可证
Copyright © 2014 qiniu.com
基于 MIT 协议发布: