@cloudbase/manager-node 4.2.4 → 4.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/access/index.js +141 -0
- package/lib/access/types.js +2 -0
- package/lib/billing/index.js +36 -0
- package/lib/cam/index.js +77 -0
- package/lib/cloudBaseRun/index.js +36 -0
- package/lib/cloudBaseRun/types.js +2 -0
- package/lib/common/index.js +39 -0
- package/lib/constant.js +55 -0
- package/lib/context.js +14 -0
- package/lib/database/index.js +244 -0
- package/lib/debug.js +34 -0
- package/lib/env/index.js +288 -0
- package/lib/environment.js +124 -0
- package/lib/environmentManager.js +44 -0
- package/lib/error.js +16 -0
- package/lib/function/index.js +1019 -0
- package/lib/function/packer.js +129 -0
- package/lib/function/types.js +2 -0
- package/lib/hosting/index.js +461 -0
- package/lib/index.js +83 -0
- package/lib/interfaces/base.interface.js +2 -0
- package/lib/interfaces/billing.interface.js +2 -0
- package/lib/interfaces/cam.interface.js +2 -0
- package/lib/interfaces/flexdb.interface.js +2 -0
- package/lib/interfaces/function.interface.js +2 -0
- package/lib/interfaces/index.js +19 -0
- package/lib/interfaces/storage.interface.js +2 -0
- package/lib/interfaces/tcb.interface.js +2 -0
- package/lib/storage/index.js +1051 -0
- package/lib/third/index.js +18 -0
- package/lib/user/index.js +136 -0
- package/lib/user/types.js +2 -0
- package/lib/utils/auth.js +97 -0
- package/lib/utils/cloud-api-request.js +212 -0
- package/lib/utils/cloudbase-request.js +69 -0
- package/lib/utils/envLazy.js +18 -0
- package/lib/utils/fs.js +64 -0
- package/lib/utils/http-request.js +44 -0
- package/lib/utils/index.js +103 -0
- package/lib/utils/parallel.js +69 -0
- package/lib/utils/runenv.js +8 -0
- package/lib/utils/uuid.js +18 -0
- package/package.json +1 -1
- package/types/access/index.d.ts +38 -0
- package/types/access/types.d.ts +42 -0
- package/types/billing/index.d.ts +21 -0
- package/types/cam/index.d.ts +63 -0
- package/types/cloudBaseRun/index.d.ts +12 -0
- package/types/cloudBaseRun/types.d.ts +21 -0
- package/types/common/index.d.ts +18 -0
- package/types/constant.d.ts +44 -0
- package/types/context.d.ts +17 -0
- package/types/database/index.d.ts +66 -0
- package/types/debug.d.ts +1 -0
- package/types/env/index.d.ts +127 -0
- package/types/environment.d.ts +51 -0
- package/types/environmentManager.d.ts +13 -0
- package/types/error.d.ts +18 -0
- package/types/function/index.d.ts +379 -0
- package/types/function/packer.d.ts +37 -0
- package/types/function/types.d.ts +154 -0
- package/types/hosting/index.d.ts +253 -0
- package/types/index.d.ts +52 -0
- package/types/interfaces/base.interface.d.ts +7 -0
- package/types/interfaces/billing.interface.d.ts +18 -0
- package/types/interfaces/cam.interface.d.ts +24 -0
- package/types/interfaces/flexdb.interface.d.ts +67 -0
- package/types/interfaces/function.interface.d.ts +65 -0
- package/types/interfaces/index.d.ts +7 -0
- package/types/interfaces/storage.interface.d.ts +26 -0
- package/types/interfaces/tcb.interface.d.ts +305 -0
- package/types/storage/index.d.ts +324 -0
- package/types/third/index.d.ts +11 -0
- package/types/user/index.d.ts +52 -0
- package/types/user/types.d.ts +20 -0
- package/types/utils/auth.d.ts +8 -0
- package/types/utils/cloud-api-request.d.ts +21 -0
- package/types/utils/cloudbase-request.d.ts +14 -0
- package/types/utils/envLazy.d.ts +1 -0
- package/types/utils/fs.d.ts +7 -0
- package/types/utils/http-request.d.ts +2 -0
- package/types/utils/index.d.ts +20 -0
- package/types/utils/parallel.d.ts +17 -0
- package/types/utils/runenv.d.ts +1 -0
- package/types/utils/uuid.d.ts +2 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.StorageService = void 0;
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const util_1 = __importDefault(require("util"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const make_dir_1 = __importDefault(require("make-dir"));
|
|
17
|
+
const walkdir_1 = __importDefault(require("walkdir"));
|
|
18
|
+
const micromatch_1 = __importDefault(require("micromatch"));
|
|
19
|
+
const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
|
|
20
|
+
const utils_1 = require("../utils");
|
|
21
|
+
const error_1 = require("../error");
|
|
22
|
+
const parallel_1 = require("../utils/parallel");
|
|
23
|
+
const runenv_1 = require("../utils/runenv");
|
|
24
|
+
const constant_1 = require("../constant");
|
|
25
|
+
const BIG_FILE_SIZE = 5242880; // 5MB 1024*1024*5
|
|
26
|
+
class StorageService {
|
|
27
|
+
constructor(environment) {
|
|
28
|
+
this.environment = environment;
|
|
29
|
+
this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 上传文件
|
|
33
|
+
* localPath 为文件夹时,会尝试在文件夹中寻找 cloudPath 中的文件名
|
|
34
|
+
* @param {string} localPath 本地文件的绝对路径
|
|
35
|
+
* @param {string} cloudPath 云端文件路径,如 img/test.png
|
|
36
|
+
* @returns {Promise<any>}
|
|
37
|
+
*/
|
|
38
|
+
async uploadFile(options) {
|
|
39
|
+
const { localPath, cloudPath = '', onProgress } = options;
|
|
40
|
+
const { bucket, region } = this.getStorageConfig();
|
|
41
|
+
return this.uploadFileCustom({
|
|
42
|
+
localPath,
|
|
43
|
+
cloudPath,
|
|
44
|
+
bucket,
|
|
45
|
+
region,
|
|
46
|
+
onProgress
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 批量上传文件,默认并发 5
|
|
51
|
+
* @param options
|
|
52
|
+
*/
|
|
53
|
+
async uploadFiles(options) {
|
|
54
|
+
const { files, onProgress, parallel, onFileFinish, ignore, retryCount, retryInterval } = options;
|
|
55
|
+
const { bucket, region } = this.getStorageConfig();
|
|
56
|
+
return this.uploadFilesCustom({
|
|
57
|
+
files,
|
|
58
|
+
bucket,
|
|
59
|
+
region,
|
|
60
|
+
ignore,
|
|
61
|
+
parallel,
|
|
62
|
+
onProgress,
|
|
63
|
+
onFileFinish,
|
|
64
|
+
retryCount,
|
|
65
|
+
retryInterval
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 上传文件,支持自定义 Bucket 和 Region
|
|
70
|
+
* @param {string} localPath
|
|
71
|
+
* @param {string} cloudPath
|
|
72
|
+
* @param {string} bucket
|
|
73
|
+
* @param {string} region
|
|
74
|
+
*/
|
|
75
|
+
async uploadFileCustom(options) {
|
|
76
|
+
const { localPath, cloudPath, bucket, region, onProgress, fileId = true } = options;
|
|
77
|
+
let localFilePath = '';
|
|
78
|
+
let resolveLocalPath = path_1.default.resolve(localPath);
|
|
79
|
+
utils_1.checkFullAccess(resolveLocalPath, true);
|
|
80
|
+
// 如果 localPath 是一个文件夹,尝试在文件下寻找 cloudPath 中的文件
|
|
81
|
+
const fileStats = fs_1.default.statSync(resolveLocalPath);
|
|
82
|
+
if (fileStats.isDirectory()) {
|
|
83
|
+
const fileName = path_1.default.parse(cloudPath).base;
|
|
84
|
+
const attemptFilePath = path_1.default.join(localPath, fileName);
|
|
85
|
+
if (utils_1.checkFullAccess(attemptFilePath)) {
|
|
86
|
+
localFilePath = path_1.default.resolve(attemptFilePath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
localFilePath = resolveLocalPath;
|
|
91
|
+
}
|
|
92
|
+
if (!localFilePath) {
|
|
93
|
+
throw new error_1.CloudBaseError('本地文件不存在!');
|
|
94
|
+
}
|
|
95
|
+
const cos = this.getCos();
|
|
96
|
+
const putObject = util_1.default.promisify(cos.putObject).bind(cos);
|
|
97
|
+
const sliceUploadFile = util_1.default.promisify(cos.sliceUploadFile).bind(cos);
|
|
98
|
+
let cosFileId;
|
|
99
|
+
// 针对静态托管,fileId 不是必须的
|
|
100
|
+
if (fileId) {
|
|
101
|
+
// 针对文件存储,cosFileId 是必须的,区分上传人员,否则无法获取下载连接
|
|
102
|
+
const res = await this.getUploadMetadata(cloudPath);
|
|
103
|
+
cosFileId = res.cosFileId;
|
|
104
|
+
}
|
|
105
|
+
let res;
|
|
106
|
+
// 小文件,直接上传
|
|
107
|
+
if (fileStats.size < BIG_FILE_SIZE) {
|
|
108
|
+
res = await putObject({
|
|
109
|
+
onProgress,
|
|
110
|
+
Bucket: bucket,
|
|
111
|
+
Region: region,
|
|
112
|
+
Key: cloudPath,
|
|
113
|
+
StorageClass: 'STANDARD',
|
|
114
|
+
ContentLength: fileStats.size,
|
|
115
|
+
Body: fs_1.default.createReadStream(localFilePath),
|
|
116
|
+
'x-cos-meta-fileid': cosFileId
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// 大文件,分块上传
|
|
121
|
+
res = await sliceUploadFile({
|
|
122
|
+
Bucket: bucket,
|
|
123
|
+
Region: region,
|
|
124
|
+
Key: cloudPath,
|
|
125
|
+
FilePath: localFilePath,
|
|
126
|
+
StorageClass: 'STANDARD',
|
|
127
|
+
AsyncLimit: 3,
|
|
128
|
+
onProgress,
|
|
129
|
+
'x-cos-meta-fileid': cosFileId
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (res.statusCode !== 200) {
|
|
133
|
+
throw new error_1.CloudBaseError(`上传文件错误:${JSON.stringify(res)}`);
|
|
134
|
+
}
|
|
135
|
+
return res;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 上传文件夹
|
|
139
|
+
* @param {string} localPath 本地文件夹路径
|
|
140
|
+
* @param {string} cloudPath 云端文件夹
|
|
141
|
+
* @param {number} parallel 并发量
|
|
142
|
+
* @param {number} retryCount 重试次数
|
|
143
|
+
* @param {number} retryInterval 重试时间间隔(毫秒)
|
|
144
|
+
* @param {(string | string[])} ignore
|
|
145
|
+
* @param {(string | string[])} ignore
|
|
146
|
+
* @returns {Promise<void>}
|
|
147
|
+
*/
|
|
148
|
+
async uploadDirectory(options) {
|
|
149
|
+
const { localPath, cloudPath = '', ignore, onProgress, onFileFinish, parallel, retryCount, retryInterval } = options;
|
|
150
|
+
// 此处不检查路径是否存在
|
|
151
|
+
// 绝对路径 /var/blog/xxxx
|
|
152
|
+
const { bucket, region } = this.getStorageConfig();
|
|
153
|
+
return this.uploadDirectoryCustom({
|
|
154
|
+
localPath,
|
|
155
|
+
cloudPath,
|
|
156
|
+
parallel,
|
|
157
|
+
retryCount,
|
|
158
|
+
retryInterval,
|
|
159
|
+
bucket,
|
|
160
|
+
region,
|
|
161
|
+
ignore,
|
|
162
|
+
onProgress,
|
|
163
|
+
onFileFinish
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 上传文件夹,支持自定义 Region 和 Bucket
|
|
168
|
+
* @param {string} localPath
|
|
169
|
+
* @param {string} cloudPath
|
|
170
|
+
* @param {number} parallel
|
|
171
|
+
* @param {number} retryCount
|
|
172
|
+
* @param {number} retryInterval
|
|
173
|
+
* @param {string} bucket
|
|
174
|
+
* @param {string} region
|
|
175
|
+
* @param {IOptions} options
|
|
176
|
+
* @returns {Promise<void>}
|
|
177
|
+
*/
|
|
178
|
+
async uploadDirectoryCustom(options) {
|
|
179
|
+
const { localPath, cloudPath, bucket, region, onProgress, onFileFinish, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
180
|
+
// 此处不检查路径是否存在
|
|
181
|
+
// 绝对路径 /var/blog/xxxx
|
|
182
|
+
const resolvePath = path_1.default.resolve(localPath);
|
|
183
|
+
// 在路径结尾加上 '/'
|
|
184
|
+
const resolveLocalPath = path_1.default.join(resolvePath, path_1.default.sep);
|
|
185
|
+
const filePaths = await this.walkLocalDir(resolveLocalPath, ignore);
|
|
186
|
+
if (!filePaths || !filePaths.length) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const fileStatsList = filePaths.map(filePath => {
|
|
190
|
+
// 处理 windows 路径
|
|
191
|
+
const fileKeyPath = filePath.replace(resolveLocalPath, '').replace(/\\/g, '/');
|
|
192
|
+
// 解析 cloudPath
|
|
193
|
+
let cloudFileKey = path_1.default.join(cloudPath, fileKeyPath).replace(/\\/g, '/');
|
|
194
|
+
if (utils_1.isDirectory(filePath)) {
|
|
195
|
+
cloudFileKey = this.getCloudKey(cloudFileKey);
|
|
196
|
+
return {
|
|
197
|
+
filePath,
|
|
198
|
+
cloudFileKey,
|
|
199
|
+
isDir: true
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
return {
|
|
204
|
+
filePath,
|
|
205
|
+
cloudFileKey,
|
|
206
|
+
isDir: false
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// 创建目录请求
|
|
211
|
+
const creatingDirController = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
212
|
+
const creatingDirTasks = fileStatsList
|
|
213
|
+
.filter(info => info.isDir)
|
|
214
|
+
.map(info => () => this.createCloudDirectroyCustom({
|
|
215
|
+
cloudPath: info.cloudFileKey,
|
|
216
|
+
bucket,
|
|
217
|
+
region
|
|
218
|
+
}));
|
|
219
|
+
creatingDirController.loadTasks(creatingDirTasks);
|
|
220
|
+
await creatingDirController.run();
|
|
221
|
+
// 上传文件对象
|
|
222
|
+
const tasks = fileStatsList
|
|
223
|
+
.filter(stats => !stats.isDir)
|
|
224
|
+
.map(stats => async () => {
|
|
225
|
+
let cosFileId;
|
|
226
|
+
if (fileId) {
|
|
227
|
+
const res = await this.getUploadMetadata(stats.cloudFileKey);
|
|
228
|
+
cosFileId = res.cosFileId;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
Bucket: bucket,
|
|
232
|
+
Region: region,
|
|
233
|
+
Key: stats.cloudFileKey,
|
|
234
|
+
FilePath: stats.filePath,
|
|
235
|
+
'x-cos-meta-fileid': cosFileId
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
// 控制请求并发
|
|
239
|
+
const getMetadataController = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
240
|
+
getMetadataController.loadTasks(tasks);
|
|
241
|
+
const files = await getMetadataController.run();
|
|
242
|
+
// 对文件上传进行处理
|
|
243
|
+
const cos = this.getCos(parallel);
|
|
244
|
+
const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
|
|
245
|
+
const params = {
|
|
246
|
+
files,
|
|
247
|
+
SliceSize: BIG_FILE_SIZE,
|
|
248
|
+
onProgress,
|
|
249
|
+
onFileFinish
|
|
250
|
+
};
|
|
251
|
+
return this.uploadFilesWithRetry({
|
|
252
|
+
uploadFiles,
|
|
253
|
+
options: params,
|
|
254
|
+
times: retryCount,
|
|
255
|
+
interval: retryInterval,
|
|
256
|
+
failedFiles: []
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 批量上传文件
|
|
261
|
+
* @param options
|
|
262
|
+
*/
|
|
263
|
+
async uploadFilesCustom(options) {
|
|
264
|
+
const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
265
|
+
if (!files || !files.length) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
let fileList = files
|
|
269
|
+
.map(item => {
|
|
270
|
+
const { localPath, cloudPath } = item;
|
|
271
|
+
return {
|
|
272
|
+
filePath: localPath,
|
|
273
|
+
cloudFileKey: cloudPath
|
|
274
|
+
};
|
|
275
|
+
})
|
|
276
|
+
.filter(item => ((ignore === null || ignore === void 0 ? void 0 : ignore.length) ? !micromatch_1.default.isMatch(item.filePath, ignore) : true));
|
|
277
|
+
// 生成上传文件属性
|
|
278
|
+
const tasks = fileList.map(stats => async () => {
|
|
279
|
+
let cosFileId;
|
|
280
|
+
if (fileId) {
|
|
281
|
+
const res = await this.getUploadMetadata(stats.cloudFileKey);
|
|
282
|
+
cosFileId = res.cosFileId;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
Bucket: bucket,
|
|
286
|
+
Region: region,
|
|
287
|
+
Key: stats.cloudFileKey,
|
|
288
|
+
FilePath: stats.filePath,
|
|
289
|
+
'x-cos-meta-fileid': cosFileId
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
// 控制请求并发
|
|
293
|
+
const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
294
|
+
asyncTaskController.loadTasks(tasks);
|
|
295
|
+
fileList = await asyncTaskController.run();
|
|
296
|
+
const cos = this.getCos(parallel);
|
|
297
|
+
const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
|
|
298
|
+
const params = {
|
|
299
|
+
files: fileList,
|
|
300
|
+
SliceSize: BIG_FILE_SIZE,
|
|
301
|
+
onProgress,
|
|
302
|
+
onFileFinish
|
|
303
|
+
};
|
|
304
|
+
// return uploadFiles({
|
|
305
|
+
// onProgress,
|
|
306
|
+
// onFileFinish,
|
|
307
|
+
// files: fileList,
|
|
308
|
+
// SliceSize: BIG_FILE_SIZE
|
|
309
|
+
// })
|
|
310
|
+
return this.uploadFilesWithRetry({
|
|
311
|
+
uploadFiles,
|
|
312
|
+
options: params,
|
|
313
|
+
times: retryCount,
|
|
314
|
+
interval: retryInterval,
|
|
315
|
+
failedFiles: []
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* 创建一个空的文件夹
|
|
320
|
+
* @param {string} cloudPath
|
|
321
|
+
*/
|
|
322
|
+
async createCloudDirectroy(cloudPath) {
|
|
323
|
+
const { bucket, region } = this.getStorageConfig();
|
|
324
|
+
await this.createCloudDirectroyCustom({
|
|
325
|
+
cloudPath,
|
|
326
|
+
bucket,
|
|
327
|
+
region
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 创建一个空的文件夹,支持自定义 Region 和 Bucket
|
|
332
|
+
* @param {string} cloudPath
|
|
333
|
+
* @param {string} bucket
|
|
334
|
+
* @param {string} region
|
|
335
|
+
*/
|
|
336
|
+
async createCloudDirectroyCustom(options) {
|
|
337
|
+
const { cloudPath, bucket, region } = options;
|
|
338
|
+
const cos = this.getCos();
|
|
339
|
+
const putObject = util_1.default.promisify(cos.putObject).bind(cos);
|
|
340
|
+
const dirKey = this.getCloudKey(cloudPath);
|
|
341
|
+
const res = await putObject({
|
|
342
|
+
Bucket: bucket,
|
|
343
|
+
Region: region,
|
|
344
|
+
Key: dirKey,
|
|
345
|
+
Body: ''
|
|
346
|
+
});
|
|
347
|
+
if (res.statusCode !== 200) {
|
|
348
|
+
throw new error_1.CloudBaseError(`创建文件夹失败:${JSON.stringify(res)}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 下载文件
|
|
353
|
+
* @param {string} cloudPath 云端文件路径
|
|
354
|
+
* @param {string} localPath 文件本地存储路径,文件需指定文件名称
|
|
355
|
+
* @returns {Promise<NodeJS.ReadableStream>}
|
|
356
|
+
*/
|
|
357
|
+
async downloadFile(options) {
|
|
358
|
+
const { cloudPath, localPath } = options;
|
|
359
|
+
const resolveLocalPath = path_1.default.resolve(localPath);
|
|
360
|
+
const fileDir = path_1.default.dirname(localPath);
|
|
361
|
+
utils_1.checkFullAccess(fileDir, true);
|
|
362
|
+
const urlList = await this.getTemporaryUrl([cloudPath]);
|
|
363
|
+
const { url } = urlList[0];
|
|
364
|
+
const { proxy } = await this.environment.getAuthConfig();
|
|
365
|
+
const res = await utils_1.fetchStream(url, {}, proxy);
|
|
366
|
+
// localPath 不存在时,返回 ReadableStream
|
|
367
|
+
if (!localPath) {
|
|
368
|
+
return res.body;
|
|
369
|
+
}
|
|
370
|
+
const dest = fs_1.default.createWriteStream(resolveLocalPath);
|
|
371
|
+
res.body.pipe(dest);
|
|
372
|
+
// 写完成后返回
|
|
373
|
+
return new Promise(resolve => {
|
|
374
|
+
dest.on('close', () => {
|
|
375
|
+
// 返回文件地址
|
|
376
|
+
resolve(resolveLocalPath);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 下载文件夹
|
|
382
|
+
* @param {string} cloudPath 云端文件路径
|
|
383
|
+
* @param {string} localPath 本地文件夹存储路径
|
|
384
|
+
* @returns {Promise<(NodeJS.ReadableStream | string)[]>}
|
|
385
|
+
*/
|
|
386
|
+
async downloadDirectory(options) {
|
|
387
|
+
const { cloudPath, localPath, parallel = 20 } = options;
|
|
388
|
+
const resolveLocalPath = path_1.default.resolve(localPath);
|
|
389
|
+
utils_1.checkFullAccess(resolveLocalPath, true);
|
|
390
|
+
const cloudDirectoryKey = this.getCloudKey(cloudPath);
|
|
391
|
+
const files = await this.walkCloudDir(cloudDirectoryKey);
|
|
392
|
+
const promises = files.map(file => async () => {
|
|
393
|
+
return this.downloadWithFilePath({ file, cloudDirectoryKey, resolveLocalPath });
|
|
394
|
+
});
|
|
395
|
+
const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
396
|
+
asyncTaskController.loadTasks(promises);
|
|
397
|
+
let res = await asyncTaskController.run();
|
|
398
|
+
const errorIndexArr = [];
|
|
399
|
+
res.map((item, index) => /Error/gi.test(Object.prototype.toString.call(item)) && errorIndexArr.push(index));
|
|
400
|
+
// 重试逻辑
|
|
401
|
+
if (errorIndexArr.length) {
|
|
402
|
+
const errorFiles = errorIndexArr.map(errorIndex => files[errorIndex]);
|
|
403
|
+
asyncTaskController.loadTasks(errorFiles.map(file => async () => {
|
|
404
|
+
return this.downloadWithFilePath({ file, cloudDirectoryKey, resolveLocalPath });
|
|
405
|
+
}));
|
|
406
|
+
res = await asyncTaskController.run();
|
|
407
|
+
}
|
|
408
|
+
const errorResultArr = this.determineDownLoadResultIsError(res);
|
|
409
|
+
if (errorResultArr.length) {
|
|
410
|
+
throw errorResultArr[0];
|
|
411
|
+
}
|
|
412
|
+
return res;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 列出文件夹下的文件
|
|
416
|
+
* @link https://cloud.tencent.com/document/product/436/7734
|
|
417
|
+
* @param {string} cloudPath 云端文件夹,如果为空字符串,则表示根目录
|
|
418
|
+
* @returns {Promise<ListFileInfo[]>}
|
|
419
|
+
*/
|
|
420
|
+
async listDirectoryFiles(cloudPath) {
|
|
421
|
+
return this.walkCloudDir(cloudPath);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* 获取文件临时下载链接
|
|
425
|
+
* @param {((string | ITempUrlInfo)[])} fileList 文件路径或文件信息数组
|
|
426
|
+
* @returns {Promise<{ fileId: string; url: string }[]>}
|
|
427
|
+
*/
|
|
428
|
+
async getTemporaryUrl(fileList) {
|
|
429
|
+
if (!fileList || !Array.isArray(fileList)) {
|
|
430
|
+
throw new error_1.CloudBaseError('fileList 必须是非空的数组');
|
|
431
|
+
}
|
|
432
|
+
const files = fileList.map(item => {
|
|
433
|
+
if (typeof item === 'string') {
|
|
434
|
+
return { cloudPath: item, maxAge: 3600 };
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
return item;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
const invalidData = files.find(item => !item.cloudPath || !item.maxAge || typeof item.cloudPath !== 'string');
|
|
441
|
+
if (invalidData) {
|
|
442
|
+
throw new error_1.CloudBaseError(`非法参数:${JSON.stringify(invalidData)}`);
|
|
443
|
+
}
|
|
444
|
+
const notExistsFiles = [];
|
|
445
|
+
const checkFileRequests = files.map(file => (async () => {
|
|
446
|
+
try {
|
|
447
|
+
await this.getFileInfo(file.cloudPath);
|
|
448
|
+
}
|
|
449
|
+
catch (e) {
|
|
450
|
+
if (e.statusCode === 404) {
|
|
451
|
+
notExistsFiles.push(file.cloudPath);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
})());
|
|
455
|
+
await Promise.all(checkFileRequests);
|
|
456
|
+
// 文件路径不存在
|
|
457
|
+
if (notExistsFiles.length) {
|
|
458
|
+
throw new error_1.CloudBaseError(`以下文件不存在:${notExistsFiles.join(', ')}`);
|
|
459
|
+
}
|
|
460
|
+
const data = files.map(item => ({
|
|
461
|
+
fileid: this.cloudPathToFileId(item.cloudPath),
|
|
462
|
+
max_age: item.maxAge
|
|
463
|
+
}));
|
|
464
|
+
const config = this.environment.getAuthConfig();
|
|
465
|
+
const res = await utils_1.cloudBaseRequest({
|
|
466
|
+
config,
|
|
467
|
+
params: {
|
|
468
|
+
file_list: data,
|
|
469
|
+
action: 'storage.batchGetDownloadUrl'
|
|
470
|
+
},
|
|
471
|
+
method: 'POST'
|
|
472
|
+
});
|
|
473
|
+
const downloadList = res.data.download_list.map(item => ({
|
|
474
|
+
url: item.download_url,
|
|
475
|
+
fileId: item.fileid || item.fileID
|
|
476
|
+
}));
|
|
477
|
+
return downloadList;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 删除文件
|
|
481
|
+
* @param {string[]} cloudPathList 云端文件路径数组
|
|
482
|
+
* @returns {Promise<void>}
|
|
483
|
+
*/
|
|
484
|
+
async deleteFile(cloudPathList) {
|
|
485
|
+
if (!cloudPathList || !Array.isArray(cloudPathList)) {
|
|
486
|
+
throw new error_1.CloudBaseError('fileList必须是非空的数组');
|
|
487
|
+
}
|
|
488
|
+
const hasInvalidFileId = cloudPathList.some(file => !file || typeof file !== 'string');
|
|
489
|
+
if (hasInvalidFileId) {
|
|
490
|
+
throw new error_1.CloudBaseError('fileList的元素必须是非空的字符串');
|
|
491
|
+
}
|
|
492
|
+
const { bucket, env } = this.getStorageConfig();
|
|
493
|
+
const fileIdList = cloudPathList.map(filePath => this.cloudPathToFileId(filePath));
|
|
494
|
+
const config = this.environment.getAuthConfig();
|
|
495
|
+
const res = await utils_1.cloudBaseRequest({
|
|
496
|
+
config,
|
|
497
|
+
params: {
|
|
498
|
+
action: 'storage.batchDeleteFile',
|
|
499
|
+
fileid_list: fileIdList
|
|
500
|
+
},
|
|
501
|
+
method: 'POST'
|
|
502
|
+
});
|
|
503
|
+
const failedList = res.data.delete_list
|
|
504
|
+
.filter(item => item.code !== 'SUCCESS')
|
|
505
|
+
.map(item => `${item.fileID} : ${item.code}`);
|
|
506
|
+
if (failedList.length) {
|
|
507
|
+
throw new error_1.CloudBaseError(`部分删除文件失败:${JSON.stringify(failedList)}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* 删除文件,可以指定 Bucket 和 Region
|
|
512
|
+
* @param {string[]} cloudPathList
|
|
513
|
+
* @param {string} bucket
|
|
514
|
+
* @param {string} region
|
|
515
|
+
* @returns {Promise<void>}
|
|
516
|
+
*/
|
|
517
|
+
async deleteFileCustom(cloudPathList, bucket, region) {
|
|
518
|
+
if (!cloudPathList || !Array.isArray(cloudPathList)) {
|
|
519
|
+
throw new error_1.CloudBaseError('fileList必须是非空的数组');
|
|
520
|
+
}
|
|
521
|
+
const hasInvalidFileId = cloudPathList.some(file => !file || typeof file !== 'string');
|
|
522
|
+
if (hasInvalidFileId) {
|
|
523
|
+
throw new error_1.CloudBaseError('fileList的元素必须是非空的字符串');
|
|
524
|
+
}
|
|
525
|
+
const cos = this.getCos();
|
|
526
|
+
const deleteObject = util_1.default.promisify(cos.deleteObject).bind(cos);
|
|
527
|
+
const promises = cloudPathList.map(async (file) => deleteObject({
|
|
528
|
+
Bucket: bucket,
|
|
529
|
+
Region: region,
|
|
530
|
+
Key: file
|
|
531
|
+
}));
|
|
532
|
+
await Promise.all(promises);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* 获取文件信息
|
|
536
|
+
* @param {string} cloudPath 云端文件路径
|
|
537
|
+
* @returns {Promise<FileInfo>}
|
|
538
|
+
*/
|
|
539
|
+
async getFileInfo(cloudPath) {
|
|
540
|
+
const cos = this.getCos();
|
|
541
|
+
const headObject = util_1.default.promisify(cos.headObject).bind(cos);
|
|
542
|
+
const { bucket, region } = this.getStorageConfig();
|
|
543
|
+
const { headers } = await headObject({
|
|
544
|
+
Bucket: bucket,
|
|
545
|
+
Region: region,
|
|
546
|
+
Key: cloudPath
|
|
547
|
+
});
|
|
548
|
+
if (!headers) {
|
|
549
|
+
throw new error_1.CloudBaseError(`[${cloudPath}] 获取文件信息失败`);
|
|
550
|
+
}
|
|
551
|
+
// 文件大小 KB
|
|
552
|
+
const size = Number(Number(headers['content-length']) / 1024).toFixed(2);
|
|
553
|
+
return {
|
|
554
|
+
Size: size,
|
|
555
|
+
Type: headers['content-type'],
|
|
556
|
+
Date: headers['date'],
|
|
557
|
+
ETag: headers['etag']
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* 删除文件夹
|
|
562
|
+
* @param {string} cloudPath 云端文件夹路径
|
|
563
|
+
* @returns {Promise<void>}
|
|
564
|
+
*/
|
|
565
|
+
async deleteDirectory(cloudPath) {
|
|
566
|
+
const { bucket, region } = this.getStorageConfig();
|
|
567
|
+
return this.deleteDirectoryCustom({
|
|
568
|
+
cloudPath,
|
|
569
|
+
bucket,
|
|
570
|
+
region
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 删除文件,可以指定 bucket 和 region
|
|
575
|
+
* @param {string} cloudPath
|
|
576
|
+
* @param {string} bucket
|
|
577
|
+
* @param {string} region
|
|
578
|
+
* @returns {Promise<void>}
|
|
579
|
+
*/
|
|
580
|
+
async deleteDirectoryCustom(options) {
|
|
581
|
+
const { cloudPath, bucket, region } = options;
|
|
582
|
+
const key = this.getCloudKey(cloudPath);
|
|
583
|
+
const cos = this.getCos();
|
|
584
|
+
const deleteMultipleObject = util_1.default.promisify(cos.deleteMultipleObject).bind(cos);
|
|
585
|
+
// 遍历获取全部文件
|
|
586
|
+
const files = await this.walkCloudDirCustom({
|
|
587
|
+
bucket,
|
|
588
|
+
region,
|
|
589
|
+
prefix: key
|
|
590
|
+
});
|
|
591
|
+
// 文件为空时,不能调用删除接口
|
|
592
|
+
if (!files.length) {
|
|
593
|
+
return {
|
|
594
|
+
Deleted: [],
|
|
595
|
+
Error: []
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
// COS 接口最大一次删除 1000 个 Key
|
|
599
|
+
// 将数组切分为 500 个文件一组
|
|
600
|
+
const sliceGroup = [];
|
|
601
|
+
const total = Math.ceil(files.length / 500);
|
|
602
|
+
for (let i = 0; i < total; i++) {
|
|
603
|
+
sliceGroup.push(files.splice(0, 500));
|
|
604
|
+
}
|
|
605
|
+
const tasks = sliceGroup.map(group => deleteMultipleObject({
|
|
606
|
+
Bucket: bucket,
|
|
607
|
+
Region: region,
|
|
608
|
+
Objects: group.map(file => ({ Key: file.Key }))
|
|
609
|
+
}));
|
|
610
|
+
// 删除多个文件
|
|
611
|
+
const taskRes = await Promise.all(tasks);
|
|
612
|
+
// 合并响应结果
|
|
613
|
+
const Deleted = taskRes.map(_ => _.Deleted).reduce((prev, next) => [...prev, ...next], []);
|
|
614
|
+
const Error = taskRes.map(_ => _.Error).reduce((prev, next) => [...prev, ...next], []);
|
|
615
|
+
return {
|
|
616
|
+
Deleted,
|
|
617
|
+
Error
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* 获取文件存储权限
|
|
622
|
+
* READONLY:所有用户可读,仅创建者和管理员可写
|
|
623
|
+
* PRIVATE:仅创建者及管理员可读写
|
|
624
|
+
* ADMINWRITE:所有用户可读,仅管理员可写
|
|
625
|
+
* ADMINONLY:仅管理员可读写
|
|
626
|
+
* @returns
|
|
627
|
+
*/
|
|
628
|
+
async getStorageAcl() {
|
|
629
|
+
const { bucket, env } = this.getStorageConfig();
|
|
630
|
+
const res = await this.tcbService.request('DescribeStorageACL', {
|
|
631
|
+
EnvId: env,
|
|
632
|
+
Bucket: bucket
|
|
633
|
+
});
|
|
634
|
+
return res.AclTag;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* 设置文件存储权限
|
|
638
|
+
* READONLY:所有用户可读,仅创建者和管理员可写
|
|
639
|
+
* PRIVATE:仅创建者及管理员可读写
|
|
640
|
+
* ADMINWRITE:所有用户可读,仅管理员可写
|
|
641
|
+
* ADMINONLY:仅管理员可读写
|
|
642
|
+
* @param {string} acl
|
|
643
|
+
* @returns
|
|
644
|
+
*/
|
|
645
|
+
async setStorageAcl(acl) {
|
|
646
|
+
const validAcl = ['READONLY', 'PRIVATE', 'ADMINWRITE', 'ADMINONLY'];
|
|
647
|
+
if (!validAcl.includes(acl)) {
|
|
648
|
+
throw new error_1.CloudBaseError('非法的权限类型');
|
|
649
|
+
}
|
|
650
|
+
const { bucket, env } = this.getStorageConfig();
|
|
651
|
+
return this.tcbService.request('ModifyStorageACL', {
|
|
652
|
+
EnvId: env,
|
|
653
|
+
Bucket: bucket,
|
|
654
|
+
AclTag: acl
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* 遍历云端文件夹
|
|
659
|
+
* @param {string} prefix
|
|
660
|
+
* @param {string} [marker] 路径开始标志
|
|
661
|
+
* @returns {Promise<IListFileInfo[]>}
|
|
662
|
+
*/
|
|
663
|
+
async walkCloudDir(prefix, marker) {
|
|
664
|
+
const { bucket, region } = this.getStorageConfig();
|
|
665
|
+
return this.walkCloudDirCustom({
|
|
666
|
+
prefix,
|
|
667
|
+
bucket,
|
|
668
|
+
region,
|
|
669
|
+
marker
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* 遍历云端文件夹,支持自定义 Bucket 和 Region
|
|
674
|
+
* @param {string} prefix
|
|
675
|
+
* @param {string} [marker]
|
|
676
|
+
* @param {string} bucket
|
|
677
|
+
* @param {string} region
|
|
678
|
+
* @returns {Promise<IListFileInfo[]>}
|
|
679
|
+
*/
|
|
680
|
+
async walkCloudDirCustom(options) {
|
|
681
|
+
const { prefix, bucket, region, marker = '/' } = options;
|
|
682
|
+
let fileList = [];
|
|
683
|
+
const cos = this.getCos();
|
|
684
|
+
const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
|
|
685
|
+
const prefixKey = this.getCloudKey(prefix);
|
|
686
|
+
const res = await getBucket({
|
|
687
|
+
Bucket: bucket,
|
|
688
|
+
Region: region,
|
|
689
|
+
Prefix: prefixKey,
|
|
690
|
+
MaxKeys: 100,
|
|
691
|
+
Marker: marker
|
|
692
|
+
});
|
|
693
|
+
fileList.push(...res.Contents);
|
|
694
|
+
let moreFiles = [];
|
|
695
|
+
if (res.IsTruncated === 'true' || res.IsTruncated === true) {
|
|
696
|
+
moreFiles = await this.walkCloudDirCustom({
|
|
697
|
+
bucket,
|
|
698
|
+
region,
|
|
699
|
+
prefix: prefixKey,
|
|
700
|
+
marker: res.NextMarker
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
fileList.push(...moreFiles);
|
|
704
|
+
return fileList;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* 遍历本地文件夹
|
|
708
|
+
* 忽略不包含 dir 路径,即如果 ignore 匹配 dir,dir 也不会被忽略
|
|
709
|
+
* @private
|
|
710
|
+
* @param {string} dir
|
|
711
|
+
* @param {(string | string[])} [ignore]
|
|
712
|
+
* @returns
|
|
713
|
+
*/
|
|
714
|
+
async walkLocalDir(dir, ignore) {
|
|
715
|
+
try {
|
|
716
|
+
return walkdir_1.default.async(dir, {
|
|
717
|
+
filter: (currDir, files) => {
|
|
718
|
+
// NOTE: ignore 为空数组时会忽略全部文件
|
|
719
|
+
if (!ignore || !ignore.length)
|
|
720
|
+
return files;
|
|
721
|
+
return files.filter(item => {
|
|
722
|
+
// 当前文件全路径
|
|
723
|
+
const fullPath = path_1.default.join(currDir, item);
|
|
724
|
+
// 文件相对于传入目录的路径
|
|
725
|
+
const fileRelativePath = fullPath.replace(path_1.default.join(dir, path_1.default.sep), '');
|
|
726
|
+
// 匹配
|
|
727
|
+
return !micromatch_1.default.isMatch(fileRelativePath, ignore);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
throw new error_1.CloudBaseError(e.message);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* 获取文件上传链接属性
|
|
738
|
+
*/
|
|
739
|
+
async getUploadMetadata(path) {
|
|
740
|
+
const config = this.environment.getAuthConfig();
|
|
741
|
+
const res = await utils_1.cloudBaseRequest({
|
|
742
|
+
config,
|
|
743
|
+
params: {
|
|
744
|
+
path,
|
|
745
|
+
action: 'storage.getUploadMetadata'
|
|
746
|
+
},
|
|
747
|
+
method: 'POST'
|
|
748
|
+
});
|
|
749
|
+
if (res.code) {
|
|
750
|
+
throw new error_1.CloudBaseError(`${res.code}: ${res.message || ''}`, {
|
|
751
|
+
requestId: res.requestId
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return res.data;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* 获取静态网站配置
|
|
758
|
+
*/
|
|
759
|
+
async getWebsiteConfig(options) {
|
|
760
|
+
const { bucket, region } = options;
|
|
761
|
+
const cos = this.getCos();
|
|
762
|
+
const getBucketWebsite = util_1.default.promisify(cos.getBucketWebsite).bind(cos);
|
|
763
|
+
const res = await getBucketWebsite({
|
|
764
|
+
Bucket: bucket,
|
|
765
|
+
Region: region
|
|
766
|
+
});
|
|
767
|
+
return res;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* 配置文档
|
|
771
|
+
*/
|
|
772
|
+
async putBucketWebsite(options) {
|
|
773
|
+
const { indexDocument, errorDocument, bucket, region, routingRules } = options;
|
|
774
|
+
const cos = this.getCos();
|
|
775
|
+
const putBucketWebsite = util_1.default.promisify(cos.putBucketWebsite).bind(cos);
|
|
776
|
+
let params = {
|
|
777
|
+
Bucket: bucket,
|
|
778
|
+
Region: region,
|
|
779
|
+
WebsiteConfiguration: {
|
|
780
|
+
IndexDocument: {
|
|
781
|
+
Suffix: indexDocument
|
|
782
|
+
},
|
|
783
|
+
ErrorDocument: {
|
|
784
|
+
Key: errorDocument
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
if (routingRules) {
|
|
789
|
+
params.WebsiteConfiguration.RoutingRules = [];
|
|
790
|
+
for (let value of routingRules) {
|
|
791
|
+
const routeItem = {};
|
|
792
|
+
if (value.keyPrefixEquals) {
|
|
793
|
+
routeItem.Condition = {
|
|
794
|
+
KeyPrefixEquals: value.keyPrefixEquals
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
if (value.httpErrorCodeReturnedEquals) {
|
|
798
|
+
routeItem.Condition = {
|
|
799
|
+
HttpErrorCodeReturnedEquals: value.httpErrorCodeReturnedEquals
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
if (value.replaceKeyWith) {
|
|
803
|
+
routeItem.Redirect = {
|
|
804
|
+
ReplaceKeyWith: value.replaceKeyWith
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
if (value.replaceKeyPrefixWith) {
|
|
808
|
+
routeItem.Redirect = {
|
|
809
|
+
ReplaceKeyPrefixWith: value.replaceKeyPrefixWith
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
params.WebsiteConfiguration.RoutingRules.push(routeItem);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const res = await putBucketWebsite(params);
|
|
816
|
+
return res;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* 查询object列表
|
|
820
|
+
* @param {IGetBucketOpions} options
|
|
821
|
+
* @memberof StorageService
|
|
822
|
+
*/
|
|
823
|
+
async getBucket(options) {
|
|
824
|
+
// const { bucket } = this.getStorageConfig()
|
|
825
|
+
const { prefix, maxKeys, marker, bucket, region } = options;
|
|
826
|
+
const cos = this.getCos();
|
|
827
|
+
const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
|
|
828
|
+
const prefixKey = this.getCloudKey(prefix);
|
|
829
|
+
const res = await getBucket({
|
|
830
|
+
Bucket: bucket,
|
|
831
|
+
Region: region,
|
|
832
|
+
Prefix: prefixKey,
|
|
833
|
+
MaxKeys: maxKeys,
|
|
834
|
+
Marker: marker
|
|
835
|
+
});
|
|
836
|
+
return res;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* 获取 COS 配置
|
|
840
|
+
*/
|
|
841
|
+
getCos(parallel = 20) {
|
|
842
|
+
const { secretId, secretKey, token, proxy } = this.environment.getAuthConfig();
|
|
843
|
+
const cosProxy = process.env.TCB_COS_PROXY;
|
|
844
|
+
const cosConfig = {
|
|
845
|
+
FileParallelLimit: parallel,
|
|
846
|
+
SecretId: secretId,
|
|
847
|
+
SecretKey: secretKey,
|
|
848
|
+
Proxy: cosProxy || proxy,
|
|
849
|
+
SecurityToken: token,
|
|
850
|
+
Domain: constant_1.USE_INTERNAL_ENDPOINT ? "{Bucket}.cos-internal.{Region}.tencentcos.cn" /* INTERNAL */ : "{Bucket}.cos.{Region}.tencentcos.cn" /* PUBLIC */
|
|
851
|
+
};
|
|
852
|
+
if (constant_1.COS_SDK_PROTOCOL) {
|
|
853
|
+
cosConfig.Protocol = constant_1.COS_SDK_PROTOCOL.endsWith(':')
|
|
854
|
+
? constant_1.COS_SDK_PROTOCOL.toLowerCase()
|
|
855
|
+
: constant_1.COS_SDK_PROTOCOL.toLowerCase() + ':';
|
|
856
|
+
}
|
|
857
|
+
// COSSDK 默认开启 KeepAlive,这里提供关闭的方式
|
|
858
|
+
if (constant_1.COS_SDK_KEEPALIVE) {
|
|
859
|
+
cosConfig.KeepAlive = { true: true, false: false }[constant_1.COS_SDK_KEEPALIVE];
|
|
860
|
+
}
|
|
861
|
+
else if (runenv_1.checkIsInScf()) {
|
|
862
|
+
cosConfig.KeepAlive = false;
|
|
863
|
+
}
|
|
864
|
+
return new cos_nodejs_sdk_v5_1.default(cosConfig);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* 将 cloudPath 转换成 cloudPath/ 形式
|
|
868
|
+
*/
|
|
869
|
+
getCloudKey(cloudPath) {
|
|
870
|
+
if (!cloudPath) {
|
|
871
|
+
return '';
|
|
872
|
+
}
|
|
873
|
+
// 单个 / 转换成根目录
|
|
874
|
+
if (cloudPath === '/') {
|
|
875
|
+
return '';
|
|
876
|
+
}
|
|
877
|
+
return cloudPath[cloudPath.length - 1] === '/' ? cloudPath : `${cloudPath}/`;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* 将 cloudPath 转换成 fileId
|
|
881
|
+
*/
|
|
882
|
+
cloudPathToFileId(cloudPath) {
|
|
883
|
+
const { env, bucket } = this.getStorageConfig();
|
|
884
|
+
return `cloud://${env}.${bucket}/${cloudPath}`;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* 获取存储桶配置
|
|
888
|
+
*/
|
|
889
|
+
getStorageConfig() {
|
|
890
|
+
var _a;
|
|
891
|
+
const envConfig = this.environment.lazyEnvironmentConfig;
|
|
892
|
+
const storageConfig = (_a = envConfig === null || envConfig === void 0 ? void 0 : envConfig.Storages) === null || _a === void 0 ? void 0 : _a[0];
|
|
893
|
+
const { Region, Bucket } = storageConfig;
|
|
894
|
+
const region = process.env.TCB_COS_REGION || Region;
|
|
895
|
+
return {
|
|
896
|
+
region,
|
|
897
|
+
bucket: Bucket,
|
|
898
|
+
env: envConfig.EnvId
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* 带重试功能的上传多文件函数
|
|
903
|
+
* @param uploadFiles sdk上传函数
|
|
904
|
+
* @param options sdk上传函数参数
|
|
905
|
+
* @param times 重试次数
|
|
906
|
+
* @param interval 重试时间间隔(毫秒)
|
|
907
|
+
* @param failedFiles 失败文件列表
|
|
908
|
+
* @returns
|
|
909
|
+
*/
|
|
910
|
+
async uploadFilesWithRetry({ uploadFiles, options, times, interval, failedFiles }) {
|
|
911
|
+
const { files, onFileFinish } = options;
|
|
912
|
+
const tempFailedFiles = [];
|
|
913
|
+
let curError = null;
|
|
914
|
+
const res = await uploadFiles(Object.assign(Object.assign({}, options), { files: failedFiles.length
|
|
915
|
+
? files.filter(file => failedFiles.includes(file.Key))
|
|
916
|
+
: files, onFileFinish: (...args) => {
|
|
917
|
+
const error = args[0];
|
|
918
|
+
const fileInfo = args[2];
|
|
919
|
+
if (error) {
|
|
920
|
+
curError = error;
|
|
921
|
+
tempFailedFiles.push(fileInfo.Key);
|
|
922
|
+
}
|
|
923
|
+
onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish.apply(null, args);
|
|
924
|
+
} }));
|
|
925
|
+
// if (!tempFailedFiles?.length || times <= 0) return res
|
|
926
|
+
if (!(tempFailedFiles === null || tempFailedFiles === void 0 ? void 0 : tempFailedFiles.length)) {
|
|
927
|
+
return res;
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
if (times > 0) {
|
|
931
|
+
return await new Promise((resolve, reject) => {
|
|
932
|
+
setTimeout(() => this.uploadFilesWithRetry({
|
|
933
|
+
uploadFiles,
|
|
934
|
+
options,
|
|
935
|
+
times: times - 1,
|
|
936
|
+
interval,
|
|
937
|
+
failedFiles: tempFailedFiles
|
|
938
|
+
}).then(res => resolve(res))
|
|
939
|
+
.catch(err => reject(err)), interval);
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
if (curError) {
|
|
944
|
+
throw curError;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 拼接路径下载单文件
|
|
951
|
+
* @param file
|
|
952
|
+
* @param cloudDirectoryKey
|
|
953
|
+
* @param resolveLocalPath
|
|
954
|
+
* @returns
|
|
955
|
+
*/
|
|
956
|
+
async downloadWithFilePath({ file, cloudDirectoryKey, resolveLocalPath }) {
|
|
957
|
+
const fileRelativePath = file.Key.replace(cloudDirectoryKey, '');
|
|
958
|
+
// 空路径和文件夹跳过
|
|
959
|
+
if (!fileRelativePath || /\/$/g.test(fileRelativePath)) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const localFilePath = path_1.default.join(resolveLocalPath, fileRelativePath);
|
|
963
|
+
// 创建文件的父文件夹
|
|
964
|
+
const fileDir = path_1.default.dirname(localFilePath);
|
|
965
|
+
await make_dir_1.default(fileDir);
|
|
966
|
+
return this.downloadFile({
|
|
967
|
+
cloudPath: file.Key,
|
|
968
|
+
localPath: localFilePath
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* 根据下载结果返回错误列表
|
|
973
|
+
* @param res
|
|
974
|
+
* @returns
|
|
975
|
+
*/
|
|
976
|
+
determineDownLoadResultIsError(res) {
|
|
977
|
+
const resultErrorArr = [];
|
|
978
|
+
res.map(item => /Error/gi.test(Object.prototype.toString.call(item)) && resultErrorArr.push(item));
|
|
979
|
+
return resultErrorArr;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
__decorate([
|
|
983
|
+
utils_1.preLazy()
|
|
984
|
+
], StorageService.prototype, "uploadFile", null);
|
|
985
|
+
__decorate([
|
|
986
|
+
utils_1.preLazy()
|
|
987
|
+
], StorageService.prototype, "uploadFiles", null);
|
|
988
|
+
__decorate([
|
|
989
|
+
utils_1.preLazy()
|
|
990
|
+
], StorageService.prototype, "uploadFileCustom", null);
|
|
991
|
+
__decorate([
|
|
992
|
+
utils_1.preLazy()
|
|
993
|
+
], StorageService.prototype, "uploadDirectory", null);
|
|
994
|
+
__decorate([
|
|
995
|
+
utils_1.preLazy()
|
|
996
|
+
], StorageService.prototype, "uploadDirectoryCustom", null);
|
|
997
|
+
__decorate([
|
|
998
|
+
utils_1.preLazy()
|
|
999
|
+
], StorageService.prototype, "uploadFilesCustom", null);
|
|
1000
|
+
__decorate([
|
|
1001
|
+
utils_1.preLazy()
|
|
1002
|
+
], StorageService.prototype, "createCloudDirectroy", null);
|
|
1003
|
+
__decorate([
|
|
1004
|
+
utils_1.preLazy()
|
|
1005
|
+
], StorageService.prototype, "createCloudDirectroyCustom", null);
|
|
1006
|
+
__decorate([
|
|
1007
|
+
utils_1.preLazy()
|
|
1008
|
+
], StorageService.prototype, "downloadFile", null);
|
|
1009
|
+
__decorate([
|
|
1010
|
+
utils_1.preLazy()
|
|
1011
|
+
], StorageService.prototype, "downloadDirectory", null);
|
|
1012
|
+
__decorate([
|
|
1013
|
+
utils_1.preLazy()
|
|
1014
|
+
], StorageService.prototype, "listDirectoryFiles", null);
|
|
1015
|
+
__decorate([
|
|
1016
|
+
utils_1.preLazy()
|
|
1017
|
+
], StorageService.prototype, "getTemporaryUrl", null);
|
|
1018
|
+
__decorate([
|
|
1019
|
+
utils_1.preLazy()
|
|
1020
|
+
], StorageService.prototype, "deleteFile", null);
|
|
1021
|
+
__decorate([
|
|
1022
|
+
utils_1.preLazy()
|
|
1023
|
+
], StorageService.prototype, "deleteFileCustom", null);
|
|
1024
|
+
__decorate([
|
|
1025
|
+
utils_1.preLazy()
|
|
1026
|
+
], StorageService.prototype, "getFileInfo", null);
|
|
1027
|
+
__decorate([
|
|
1028
|
+
utils_1.preLazy()
|
|
1029
|
+
], StorageService.prototype, "deleteDirectory", null);
|
|
1030
|
+
__decorate([
|
|
1031
|
+
utils_1.preLazy()
|
|
1032
|
+
], StorageService.prototype, "deleteDirectoryCustom", null);
|
|
1033
|
+
__decorate([
|
|
1034
|
+
utils_1.preLazy()
|
|
1035
|
+
], StorageService.prototype, "getStorageAcl", null);
|
|
1036
|
+
__decorate([
|
|
1037
|
+
utils_1.preLazy()
|
|
1038
|
+
], StorageService.prototype, "setStorageAcl", null);
|
|
1039
|
+
__decorate([
|
|
1040
|
+
utils_1.preLazy()
|
|
1041
|
+
], StorageService.prototype, "walkCloudDir", null);
|
|
1042
|
+
__decorate([
|
|
1043
|
+
utils_1.preLazy()
|
|
1044
|
+
], StorageService.prototype, "walkCloudDirCustom", null);
|
|
1045
|
+
__decorate([
|
|
1046
|
+
utils_1.preLazy()
|
|
1047
|
+
], StorageService.prototype, "putBucketWebsite", null);
|
|
1048
|
+
__decorate([
|
|
1049
|
+
utils_1.preLazy()
|
|
1050
|
+
], StorageService.prototype, "getBucket", null);
|
|
1051
|
+
exports.StorageService = StorageService;
|