最近在開發一個項目時,遇到了一個打包下載文件的需求。並且它不只僅是一個簡單粗暴的直接打包,還須要按照目錄層級打包。具體以下圖: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
。
七牛雲不只擁有壓縮打包功能,還有音頻、視頻轉碼等文件操做功能。在合適的場景下使用雲服務能夠達到事半功倍的效果。