最近在开发一个项目时,遇到了一个打包下载文件的需求。并且它不只仅是一个简单粗暴的直接打包,还须要按照目录层级打包。具体以下图:java
你们开发这个需求的第一反应多是去找各类各样的开源库、打包工具,而后写各类复杂的遍历、建目录等。然而在云服务大行其道的今天,合理使用云服务可以很轻松地解决咱们的问题。好比七牛云服务提供的文件打包功能(mkzip),它的好处有如下几点:node
缺点:npm
只支持异步操做。若是想要获取操做结果,须要调用其它api或者给七牛云提供一个回调通知地址。json
在私有化环境当中,若是没法连结外网的话,云服务便没法使用。固然通常的云服务商也都提供私有化版本。api
接下来让咱们具体看看如何使用七牛云打包下载功能。另外本篇文章以实例代码为主,背后理论和原理请看七牛云官方文档:app
持久化数据处理工具
Nodejs SDKpost
Java SDKui
本demo实现的目标是将位于七牛云服务上的两个文件按照目录层级打包成可下载的zip文件。两个文件分别是https://file.demo.com/test1.txt
、https://file.demo.com/test2.txt
。另外咱们要将test1.txt
放置在一级目录/二级目录
文件夹下,而test2.txt
则放置在根目录下。一些共有的参数分别须要注意的是:
Nodejs
npm i qiniu
复制代码
Java
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.0</version>
</dependency>
复制代码
在及其少许文件打包的场景下,建议使用这种方式。相较于大量文件打包,这种方式更简单直接,代码量更少,但同时请求的命令字符串长度不能超过2048字节。
'use strict';
const qiniu = require('qiniu');
const accessKey = 'accessKey';
const secretKey = 'secretKey';
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const config = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z2;
const operManager = new qiniu.fop.OperationManager(mac, config);
// 处理指令集合
const saveBucket = 'demo-bucket';
const files = [
{
url: 'https://file.demo.com/test1.txt',
alias: '一级目录/二级目录/test1.txt',
},
{
url: 'https://file.demo.com/test2.txt',
},
];
let urlAndAlias = '';
files.forEach(file => {
urlAndAlias += `/url/${qiniu.util.urlsafeBase64Encode(file.url)}`;
if (file.alias) {
urlAndAlias += `/alias/${qiniu.util.urlsafeBase64Encode(file.alias)}`;
}
});
const fops = [`mkzip/2${urlAndAlias}|saveas/${qiniu.util.urlsafeBase64Encode(`${saveBucket }:demo.zip`)}`];
const pipeline = 'pipeline';
const srcBucket = 'demo-bucket';
// srcKey不影响实际操做结果,可是根据七牛云规范,须要传入一个该资源空间实际存在的资源key。因此这里可使用已经存在的test1.txt
const srcKey = 'test1.txt';
const options = {
notifyURL: 'http://api.example.com/pfop/callback',
force: true,
};
// 持久化数据处理返回的是任务的persistentId,能够根据这个id查询处理状态
operManager.pfop(srcBucket, srcKey, fops, pipeline, options, (err, respBody, respInfo) => {
if (err) {
throw err;
}
if (respInfo.statusCode === 200) {
console.log(respBody.persistentId);
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
复制代码
List<String> fileUrlList = new ArrayList<>(Arrays.asList("https://file.demo.com/test1.txt", "https://file.demo.com/test2.txt"));
List<String> aliasList = new ArrayList<>(Arrays.asList("一级目录/二级目录/test1.txt", ""));
String urlAndAlias = "";
for(int i = 0 ; i < fileUrlList.size(); i++) {
urlAndAlias += "/url/" + UrlSafeBase64.encodeToString(fileUrlList.get(i));
if (StringUtils.isNotEmpty(aliasList.get(i))) {
urlAndAlias += "/alias/" + UrlSafeBase64.encodeToString(aliasList.get(i));
}
}
//待处理文件所在空间
String srcBucket = "demo-bucket";
String saveBucket = srcBucket;
// srcKey不影响实际操做结果,可是根据七牛云规范,须要传入一个该资源空间实际存在的资源key。因此这里可使用已经存在的test1.txt
String srcKey = "test1.txt";
String zipFileName = "demo_java.zip";
Auth auth = Auth.create(accessKey, secretKey);
//数据处理指令,支持多个指令
String persistentOpfs = String.format("mkzip/2%s|saveas/%s", urlAndAlias, UrlSafeBase64.encodeToString(saveBucket+ ":"+ zipFileName));
//其它参数
StringMap options = new StringMap();
//数据处理队列名称
options.put("pipeline", "pipeline");
//数据处理完成结果通知地址
options.put("notifyURL", "http://api.example.com/qiniu/pfop/notify");
options.put("force", 1);
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
OperationManager operationManager = new OperationManager(auth, cfg);
try {
String persistentId = operationManager.pfop(srcBucket, srcKey, persistentOpfs, options);
//能够根据该 persistentId 查询任务处理进度
System.out.println(persistentId);
} catch (QiniuException e) {
System.err.println(e.response.toString());
}
复制代码
为了将大量文件压缩,能够将待压缩文件url写入一个索引文件,上传至资源空间,再对该索引文件进行的mkzip操做。这一系列操做能够经过2个请求完成,但也能够只经过一个请求完成。咱们这里只介绍第二种最便捷的方法。
'use strict';
const qiniu = require('qiniu');
const fs = require('fs');
const accessKey = 'accessKey';
const secretKey = 'secretKey';
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
// 构造执行打包操做的上传token
const srcBucket = 'demo-bucket';
const zipFileName = 'demo.zip';
const savedZipEntry = qiniu.util.urlsafeBase64Encode(`${srcBucket}:${zipFileName}`);
const persistentOps = `mkzip/4/|saveas/${savedZipEntry}`;
const options = {
scope: srcBucket,
persistentOps,
// 数据处理队列名称,必填
persistentPipeline: 'pipeline',
// 数据处理完成结果通知地址
persistentNotifyUrl: 'http://api.example.com/qiniu/pfop/notify',
};
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);
// 拼装数据写入索引文件
// files不能超过三千个
const files = [
{
url: 'https://file.demo.com/test1.txt',
alias: '一级目录/二级目录/test1.txt',
},
{
url: 'https://file.demo.com/test2.txt',
},
];
let urlAndAlias = '';
files.forEach(file => {
urlAndAlias += `/url/${qiniu.util.urlsafeBase64Encode(file.url)}`;
if (file.alias) {
urlAndAlias += `/alias/${qiniu.util.urlsafeBase64Encode(file.alias)}`;
}
urlAndAlias += '\n';
});
// 为了演示方便,直接在当前目录生成索引文件
const indexFilePath = 'index.txt';
fs.writeFileSync(indexFilePath, urlAndAlias, { encoding: 'utf-8' });
// 上传索引文件,并在云端执行打包操做
const config = new qiniu.conf.Config();
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra();
const key = indexFilePath;
formUploader.putFile(uploadToken, key, indexFilePath, putExtra, (respErr, respBody, respInfo) => {
if (respErr) {
throw respErr;
}
if (respInfo.statusCode === 200) {
// 根据persistentId能够查询任务执行状态
console.log(respBody.persistentId);
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
复制代码
String accessKey = "accessKey";
String secretKey = "secretKey";
String zipFileName = "demo_java.zip";
String saveBucket = "demo-bucket";
String srcBucket = saveBucket;
Auth auth = Auth.create(accessKey, secretKey);
StringMap putPolicy = new StringMap();
//数据处理指令
String zipFop = String.format("mkzip/4/|saveas/%s", UrlSafeBase64.encodeToString(saveBucket + ":" + zipFileName));
putPolicy.put("persistentOps", zipFop);
//数据处理队列名称,必填
putPolicy.put("persistentPipeline", "pipeline");
//数据处理完成结果通知地址
putPolicy.put("persistentNotifyUrl", "http://api.example.com/qiniu/pfop/notify");
long expireSeconds = 3600;
String upToken = auth.uploadToken(srcBucket, null, expireSeconds, putPolicy);
//构造一个带指定Zone对象的配置类
Configuration cfg = new Configuration(Zone.zone2());
//...其余参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//默认不指定key的状况下,以文件内容的hash值做为文件名
String srcKey = "index_java.txt";
List<String> fileUrlList = new ArrayList<>(Arrays.asList("https://file.demo.com/test1.txt", "https://file.demo.com/test2.txt"));
List<String> aliasList = new ArrayList<>(Arrays.asList("一级目录/二级目录/test1.txt", ""));
Path path = null;
try {
// 建立临时索引文件
path = Files.createTempFile("indexTempFile", ".txt");
for(int i = 0 ; i < fileUrlList.size(); i++) {
String line = "/url/" + UrlSafeBase64.encodeToString(fileUrlList.get(i));
if (StringUtils.isNotEmpty(aliasList.get(i))) {
line += "/alias/" + UrlSafeBase64.encodeToString(aliasList.get(i));
}
// 按行写入
Files.write(path,
Arrays.asList(line),
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
}
// 应用退出时删除临时文件
path.toFile().deleteOnExit();
} catch (IOException e) {
}
try {
Response response = uploadManager.put(path.toString(), srcKey, upToken);
//解析上传成功的结果
Map putRet = new Gson().fromJson(response.bodyString(), Map.class);
// 根据persistentId能够查询任务执行状态
System.out.println(putRet.get("persistentId"));
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
}
复制代码
在开发过程中,能够经过使用gethttps://api.qiniu.com/status/get/prefop?id={persistentId}
,查看打包任务的执行情况
以上回包字段与七牛云post开发者提供的notifyURL的请求字段一致。
将域名与zipFileName拼接便可。如http://file.demo.com/demo.zip
。
七牛云不只拥有压缩打包功能,还有音频、视频转码等文件操做功能。在合适的场景下使用云服务能够达到事半功倍的效果。