Initial commit: ERP system with advance verification fixes

This commit is contained in:
System Administrator
2026-03-25 23:55:36 +07:00
commit 563ca12d76
5920 changed files with 828689 additions and 0 deletions
@@ -0,0 +1,16 @@
language: node_js
node_js:
- '8'
install:
- npm install
script:
- npm test
notifications:
email:
recipients:
- wjielai@tencent.com
- fysntian@tencent.com
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present 腾讯云
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,53 @@
# cos-nodejs-sdk-v5
腾讯云 COS Nodejs SDK[XML API](https://cloud.tencent.com/document/product/436/7751)
[releases and changelog](https://github.com/tencentyun/cos-nodejs-sdk-v5/releases)
## install
[npm 地址](https://www.npmjs.com/package/cos-nodejs-sdk-v5)
```
npm i cos-nodejs-sdk-v5 --save
```
## demo
```javascript
// 引入模块
var COS = require('cos-nodejs-sdk-v5');
// 创建实例
var cos = new COS({
SecretId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
SecretKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
});
// 存储桶名称,由bucketname-appid 组成,appid必须填入,可以在COS控制台查看存储桶名称。 https://console.cloud.tencent.com/cos5/bucket
var Bucket = 'test-1250000000';
// 存储桶Region可以在COS控制台指定存储桶的概览页查看 https://console.cloud.tencent.com/cos5/bucket/
// 关于地域的详情见 https://cloud.tencent.com/document/product/436/6224
var Region = 'ap-guangzhou';
// 高级上传
cos.uploadFile(
{
Bucket: Bucket,
Region: Region,
Key: '1.zip',
FilePath: './1.zip', // 本地文件地址,需自行替换
SliceSize: 1024 * 1024 * 5, // 触发分块上传的阈值,超过5MB使用分块上传,非必须
},
function (err, data) {
console.log(err, data);
}
);
```
## 说明文档
[使用例子](demo/demo.js)
[快速入门](https://cloud.tencent.com/document/product/436/8629)
[接口文档](https://cloud.tencent.com/document/product/436/12264)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
/**
* 提供 npm 包引用的入口
*/
var COS = require('./sdk/cos');
module.exports = COS;
@@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../../../fast-xml-parser/src/cli/cli.js" "$@"
ret=$?
else
node "$basedir/../../../fast-xml-parser/src/cli/cli.js" "$@"
ret=$?
fi
exit $ret
@@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\..\..\fast-xml-parser\src\cli\cli.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\..\..\fast-xml-parser\src\cli\cli.js" %*
)
@@ -0,0 +1,50 @@
{
"name": "cos-nodejs-sdk-v5",
"version": "2.15.4",
"description": "cos nodejs sdk v5",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"prettier": "prettier --write sdk demo/demo.js test/test.js index.d.ts",
"demo": "node demo/demo.js",
"demo-sts": "node demo/demo-sts.js",
"demo-sts-scope": "node demo/demo-sts-scope.js",
"test": "mocha test/test.js",
"cov": "istanbul cover _mocha -- -u exports 'test/test.js'",
"csp": "mocha test/csp.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tencentyun/cos-nodejs-sdk-v5.git"
},
"keywords": [
"tencent",
"tencent cloud",
"qcloud",
"cos",
"cos-sdk"
],
"author": "carsonxu",
"license": "ISC",
"bugs": {
"url": "https://github.com/tencentyun/cos-nodejs-sdk-v5/issues"
},
"homepage": "https://github.com/tencentyun/cos-nodejs-sdk-v5#readme",
"dependencies": {
"conf": "^9.0.0",
"fast-xml-parser": "4.2.5",
"mime-types": "^2.1.24",
"request": "^2.88.2"
},
"devDependencies": {
"@types/node": "^14.14.20",
"batch": "^0.6.1",
"crc64-ecma182.js": "^1.0.0",
"mocha": "^4.0.1",
"prettier": "^3.0.1",
"qcloud-cos-sts": "^3.1.1"
},
"engines": {
"node": ">= 6"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,59 @@
var eachLimit = function (arr, limit, iterator, callback) {
callback = callback || function () {};
if (!arr.length || limit <= 0) {
return callback();
}
var completed = 0;
var started = 0;
var running = 0;
(function replenish() {
if (completed >= arr.length) {
return callback();
}
while (running < limit && started < arr.length) {
started += 1;
running += 1;
iterator(arr[started - 1], function (err) {
if (err) {
callback(err);
callback = function () {};
} else {
completed += 1;
running -= 1;
if (completed >= arr.length) {
callback();
} else {
replenish();
}
}
});
}
})();
};
var retry = function (times, iterator, callback) {
var next = function (index) {
iterator(function (err, data) {
if (err && index < times) {
next(index + 1);
} else {
callback(err, data);
}
});
};
if (times < 1) {
callback();
} else {
next(1);
}
};
var async = {
eachLimit: eachLimit,
retry: retry,
};
module.exports = async;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,147 @@
'use strict';
var util = require('./util');
var event = require('./event');
var task = require('./task');
var base = require('./base');
var advance = require('./advance');
var pkg = require('../package.json');
var defaultOptions = {
AppId: '', // AppId 已废弃,请拼接到 Bucket 后传入,例如:test-1250000000
SecretId: '',
SecretKey: '',
SecurityToken: '', // 使用临时密钥需要注意自行刷新 Token
ChunkRetryTimes: 2,
FileParallelLimit: 3,
ChunkParallelLimit: 3,
ChunkSize: 1024 * 1024,
SliceSize: 1024 * 1024,
CopyChunkParallelLimit: 20,
CopyChunkSize: 1024 * 1024 * 10,
CopySliceSize: 1024 * 1024 * 10,
MaxPartNumber: 10000,
ProgressInterval: 1000,
Domain: '',
ServiceDomain: '',
Protocol: '',
CompatibilityMode: false,
ForcePathStyle: false,
UseRawKey: false,
Timeout: 0, // 单位毫秒,0 代表不设置超时时间
CorrectClockSkew: true,
SystemClockOffset: 0, // 单位毫秒,ms
UploadCheckContentMd5: false,
UploadQueueSize: 1000,
UploadIdCacheLimit: 500,
Proxy: '',
Tunnel: undefined,
Ip: '',
StrictSsl: true,
KeepAlive: true,
FollowRedirect: false,
UseAccelerate: false,
UserAgent: '',
ConfCwd: '',
ForceSignHost: true, // 默认将host加入签名计算,关闭后可能导致越权风险,建议保持为true
AutoSwitchHost: true,
CopySourceParser: null, // 自定义拷贝源解析器
ObjectKeySimplifyCheck: true, // 开启合并校验 getObject Key
// 动态秘钥,优先级Credentials > SecretId/SecretKey。注意Cred内是小写的secretId、secretKey
Credentials: {
secretId: '',
secretKey: '',
},
};
const watch = (obj, name, callback) => {
let value = obj[name];
Object.defineProperty(obj, name, {
get() {
return value;
},
set(newValue) {
value = newValue;
callback();
},
});
};
// 对外暴露的类
var COS = function (options) {
this.options = util.extend(util.clone(defaultOptions), options || {});
this.options.FileParallelLimit = Math.max(1, this.options.FileParallelLimit);
this.options.ChunkParallelLimit = Math.max(1, this.options.ChunkParallelLimit);
this.options.ChunkRetryTimes = Math.max(0, this.options.ChunkRetryTimes);
this.options.ChunkSize = Math.max(1024 * 1024, this.options.ChunkSize);
this.options.CopyChunkParallelLimit = Math.max(1, this.options.CopyChunkParallelLimit);
this.options.CopyChunkSize = Math.max(1024 * 1024, this.options.CopyChunkSize);
this.options.CopySliceSize = Math.max(0, this.options.CopySliceSize);
this.options.MaxPartNumber = Math.max(1024, Math.min(10000, this.options.MaxPartNumber));
this.options.Timeout = Math.max(0, this.options.Timeout);
if (this.options.AppId) {
console.warn(
'warning: AppId has been deprecated, Please put it at the end of parameter Bucket(E.g: "test-1250000000").'
);
}
// 云API SDK 用小写密钥,这里兼容并 warning
if (this.options.secretId || this.options.secretKey) {
if (this.options.secretId && !this.options.SecretId) this.options.SecretId = this.options.secretId;
if (this.options.secretKey && !this.options.SecretKey) this.options.SecretKey = this.options.secretKey;
console.warn('warning: Please change options secretId/secretKey to SecretId/SecretKey.');
}
// 支持外部传入Cred动态秘钥
if (this.options.Credentials.secretId && this.options.Credentials.secretKey) {
this.options.SecretId = this.options.Credentials.secretId || '';
this.options.SecretKey = this.options.Credentials.secretKey || '';
}
if (this.options.SecretId && this.options.SecretId.indexOf(' ') > -1) {
console.error('error: SecretId格式错误,请检查');
console.error('error: SecretId format is incorrect. Please check');
}
if (this.options.SecretKey && this.options.SecretKey.indexOf(' ') > -1) {
console.error('error: SecretKey格式错误,请检查');
console.error('error: SecretKey format is incorrect. Please check');
}
if (util.isWeb()) {
console.log('Tip: 使用 electron 等跨平台技术可正常使用Nodejs SDK,请忽略下方浏览器环境警告');
console.warn(
'warning: cos-nodejs-sdk-v5 不支持浏览器使用,请改用 cos-js-sdk-v5,参考文档: https://cloud.tencent.com/document/product/436/11459'
);
console.warn(
'warning: cos-nodejs-sdk-v5 does not support browsers. Please use cos-js-sdk-v5 instead, See: https://cloud.tencent.com/document/product/436/11459'
);
}
if (this.options.ForcePathStyle) {
console.warn(
'cos-nodejs-sdk-v5不再支持使用path-style,仅支持使用virtual-hosted-style,参考文档:https://cloud.tencent.com/document/product/436/96243'
);
throw new Error('ForcePathStyle is not supported');
}
event.init(this);
task.init(this);
// 支持动态秘钥,监听到cred里secretId、secretKey变化时,主动给cos替换秘钥
watch(this.options.Credentials, 'secretId', () => {
console.log('Credentials secretId changed');
this.options.SecretId = this.options.Credentials.secretId;
});
watch(this.options.Credentials, 'secretKey', () => {
console.log('Credentials secretKey changed');
this.options.SecretKey = this.options.Credentials.secretKey;
});
};
base.init(COS, task);
advance.init(COS, task);
COS.util = {
md5: util.md5,
xml2json: util.xml2json,
json2xml: util.json2xml,
encodeBase64: util.encodeBase64,
};
COS.getAuthorization = util.getAuth;
COS.version = pkg.version;
module.exports = COS;
@@ -0,0 +1,34 @@
var initEvent = function (cos) {
var listeners = {};
var getList = function (action) {
!listeners[action] && (listeners[action] = []);
return listeners[action];
};
cos.on = function (action, callback) {
if (action === 'task-list-update') {
console.warn('warning: Event "' + action + '" has been deprecated. Please use "list-update" instead.');
}
getList(action).push(callback);
};
cos.off = function (action, callback) {
var list = getList(action);
for (var i = list.length - 1; i >= 0; i--) {
callback === list[i] && list.splice(i, 1);
}
};
cos.emit = function (action, data) {
var list = getList(action).map(function (cb) {
return cb;
});
for (var i = 0; i < list.length; i++) {
list[i](data);
}
};
};
var EventProxy = function () {
initEvent(this);
};
module.exports.init = initEvent;
module.exports.EventProxy = EventProxy;
@@ -0,0 +1,181 @@
var { Transform } = require('stream');
var sysUtil = require('util');
var util = require('./util');
function SelectStream(options) {
if (!(this instanceof SelectStream)) return new SelectStream(options);
Transform.call(this, options);
Object.assign(this, {
totalLength: 0, // current message block's total length
headerLength: 0, // current message block's header length
payloadRestLength: 0, // current message block's rest payload length
header: null, // current message block's header
chunk: Buffer.alloc(0), // the data chunk being parsed
callback: null, // current _transform function's callback
});
}
SelectStream.prototype = {
/**
* process data chunk
* concat the last chunk and current chunk
* try to parse current message block's totalLength and headerLength
* try to parse current message block's header
* try to parse current message block's payload
*/
processChunk(chunk, encoding, callback) {
Object.assign(this, {
chunk: Buffer.concat([this.chunk, chunk], this.chunk.length + chunk.length),
encoding,
callback,
});
this.parseLength();
this.parseHeader();
this.parsePayload();
},
/**
* try to parse current message block's totalLength and headerLength
*/
parseLength() {
if (!this.callback) {
return;
}
if (this.totalLength && this.headerLength) {
return;
}
if (this.chunk.length >= 12) {
this.totalLength = this.chunk.readInt32BE(0);
this.headerLength = this.chunk.readInt32BE(4);
this.payloadRestLength = this.totalLength - this.headerLength - 16;
this.chunk = this.chunk.slice(12);
} else {
this.callback();
this.callback = null;
}
},
/**
* try to parse current message block's header
* if header[':message-type'] is error, callback the error, emit error to next stream
*/
parseHeader() {
if (!this.callback) {
return;
}
if (!this.headerLength || this.header) {
return;
}
if (this.chunk.length >= this.headerLength) {
var header = {};
var offset = 0;
while (offset < this.headerLength) {
var headerNameLength = this.chunk[offset] * 1;
var headerName = this.chunk.toString('ascii', offset + 1, offset + 1 + headerNameLength);
var headerValueLength = this.chunk.readInt16BE(offset + headerNameLength + 2);
var headerValue = this.chunk.toString(
'ascii',
offset + headerNameLength + 4,
offset + headerNameLength + 4 + headerValueLength
);
header[headerName] = headerValue;
offset += headerNameLength + 4 + headerValueLength;
}
this.header = header;
this.chunk = this.chunk.slice(this.headerLength);
this.checkErrorHeader();
} else {
this.callback();
this.callback = null;
}
},
/**
* try to parse current message block's payload
*/
parsePayload() {
var self = this;
if (!this.callback) {
return;
}
if (this.chunk.length <= this.payloadRestLength) {
this.payloadRestLength -= this.chunk.length;
this.pushData(this.chunk);
this.chunk = Buffer.alloc(0);
} else if (this.chunk.length < this.payloadRestLength + 4) {
this.pushData(this.chunk.slice(0, this.payloadRestLength));
this.chunk = this.chunk.slice(this.payloadRestLength);
this.payloadRestLength = 0;
} else {
this.pushData(this.chunk.slice(0, this.payloadRestLength));
this.chunk = this.chunk.slice(this.payloadRestLength + 4);
this.totalLength = 0;
this.headerLength = 0;
this.payloadRestLength = 0;
this.header = null;
}
if (this.chunk.length && !(this.payloadRestLength === 0 && this.chunk.length < 4)) {
process.nextTick(function () {
self.processChunk(Buffer.alloc(0), self.encoding, self.callback);
});
} else {
this.callback();
this.callback = null;
}
},
/**
* if header[':event-type'] is Records, pipe payload to next stream
*/
pushData(content) {
if (this.header[':event-type'] === 'Records') {
this.push(content);
this.emit('message:records', content);
} else if (this.header[':event-type'] === 'Progress') {
var progress = util.xml2json(content.toString()).Progress;
this.emit('message:progress', progress);
} else if (this.header[':event-type'] === 'Stats') {
var stats = util.xml2json(content.toString()).Stats;
this.emit('message:stats', stats);
} else if (this.header[':event-type'] === 'error') {
var errCode = this.header[':error-code'];
var errMessage = this.header[':error-message'];
var err = new Error(errMessage);
err.message = errMessage;
err.name = err.code = errCode;
this.emit('message:error', err);
} else {
// 'Continuation', 'End'
this.emit('message:' + this.header[':event-type'].toLowerCase());
}
},
/**
* if header[':message-type'] is error, callback the error, emit error to next stream
*/
checkErrorHeader() {
if (this.header[':message-type'] === 'error') {
this.callback(this.header);
this.callback = null;
}
},
/**
* Transform Stream's implementations
*/
_transform(chunk, encoding, callback) {
this.processChunk(chunk, encoding, callback);
},
_flush(callback) {
this.processChunk(Buffer.alloc(0), this.encoding, callback);
},
};
sysUtil.inherits(SelectStream, Transform);
module.exports = SelectStream;
@@ -0,0 +1,126 @@
var util = require('./util');
// 按照文件特征值,缓存 UploadId
var cacheKey = 'cos_sdk_upload_cache';
var expires = 30 * 24 * 3600;
var store;
var cache;
var timer;
var getCache = function () {
var val,
opt = { configName: 'cos-nodejs-sdk-v5-storage' };
if (this.options.ConfCwd) opt.cwd = this.options.ConfCwd;
try {
var Conf = require('conf');
store = new Conf(opt);
val = store.get(cacheKey);
} catch (e) {}
if (!val || !(val instanceof Array)) val = [];
cache = val;
};
var setCache = function () {
try {
if (cache.length) store.set(cacheKey, cache);
else store.delete(cacheKey);
} catch (e) {}
};
var init = function () {
if (cache) return;
getCache.call(this);
// 清理太老旧的数据
var changed = false;
var now = Math.round(Date.now() / 1000);
for (var i = cache.length - 1; i >= 0; i--) {
var mtime = cache[i][2];
if (!mtime || mtime + expires < now) {
cache.splice(i, 1);
changed = true;
}
}
changed && setCache();
};
// 把缓存存到本地
var save = function () {
if (timer) return;
timer = setTimeout(function () {
setCache();
timer = null;
}, 400);
};
var mod = {
using: {},
// 标记 UploadId 正在使用
setUsing: function (uuid) {
mod.using[uuid] = true;
},
// 标记 UploadId 已经没在使用
removeUsing: function (uuid) {
delete mod.using[uuid];
},
// 用上传参数生成哈希值
getFileId: function (FileStat, ChunkSize, Bucket, Key) {
if (FileStat && FileStat.FilePath && FileStat.size && FileStat.ctime && FileStat.mtime && ChunkSize) {
return (
util.md5([FileStat.FilePath].join('::')) +
'-' +
util.md5([FileStat.size, FileStat.ctime, FileStat.mtime, ChunkSize, Bucket, Key].join('::'))
);
} else {
return null;
}
},
// 用上传参数生成哈希值
getCopyFileId: function (copySource, sourceHeaders, ChunkSize, Bucket, Key) {
var size = sourceHeaders['content-length'];
var etag = sourceHeaders.etag || '';
var lastModified = sourceHeaders['last-modified'];
if (copySource && ChunkSize) {
return util.md5([copySource, size, etag, lastModified, ChunkSize, Bucket, Key].join('::'));
} else {
return null;
}
},
// 获取文件对应的 UploadId 列表
getUploadIdList: function (uuid) {
if (!uuid) return null;
init.call(this);
var list = [];
for (var i = 0; i < cache.length; i++) {
if (cache[i][0] === uuid) list.push(cache[i][1]);
}
return list.length ? list : null;
},
// 缓存 UploadId
saveUploadId: function (uuid, UploadId, limit) {
init.call(this);
if (!uuid) return;
// 清理没用的 UploadId
var part1 = uuid.substr(0, uuid.indexOf('-') + 1);
for (var i = cache.length - 1; i >= 0; i--) {
var item = cache[i];
if (item[0] === uuid && item[1] === UploadId) {
cache.splice(i, 1);
} else if (uuid !== item[0] && item[0].indexOf(part1) === 0) {
// 文件路径相同,但其他信息不同,说明文件改变了或上传参数(存储桶、路径、分片大小)变了,直接清理掉
cache.splice(i, 1);
}
}
cache.unshift([uuid, UploadId, Math.round(Date.now() / 1000)]);
if (cache.length > limit) cache.splice(limit);
save();
},
// UploadId 已用完,移除掉
removeUploadId: function (UploadId) {
init.call(this);
delete mod.using[UploadId];
for (var i = cache.length - 1; i >= 0; i--) {
if (cache[i][1] === UploadId) cache.splice(i, 1);
}
save();
},
};
module.exports = mod;
@@ -0,0 +1,255 @@
var session = require('./session');
var util = require('./util');
var originApiMap = {};
var transferToTaskMethod = function (apiMap, apiName) {
originApiMap[apiName] = apiMap[apiName];
apiMap[apiName] = function (params, callback) {
if (params.SkipTask) {
originApiMap[apiName].call(this, params, callback);
} else {
this._addTask(apiName, params, callback);
}
};
};
var initTask = function (cos) {
var queue = [];
var tasks = {};
var uploadingFileCount = 0;
var nextUploadIndex = 0;
// 接口返回简略的任务信息
var formatTask = function (task) {
var t = {
id: task.id,
Bucket: task.Bucket,
Region: task.Region,
Key: task.Key,
FilePath: task.FilePath,
state: task.state,
loaded: task.loaded,
size: task.size,
speed: task.speed,
percent: task.percent,
hashPercent: task.hashPercent,
error: task.error,
};
if (task.FilePath) t.FilePath = task.FilePath;
return t;
};
var emitListUpdate = (function () {
var timer;
var emit = function () {
timer = 0;
cos.emit('task-list-update', { list: util.map(queue, formatTask) });
cos.emit('list-update', { list: util.map(queue, formatTask) });
};
return function () {
if (!timer) timer = setTimeout(emit);
};
})();
var clearQueue = function () {
if (queue.length <= cos.options.UploadQueueSize) return;
for (
var i = 0;
i < nextUploadIndex && // 小于当前操作的 index 才清理
i < queue.length && // 大于队列才清理
queue.length > cos.options.UploadQueueSize; // 如果还太多,才继续清理
) {
var isActive = queue[i].state === 'waiting' || queue[i].state === 'checking' || queue[i].state === 'uploading';
if (!queue[i] || !isActive) {
tasks[queue[i].id] && delete tasks[queue[i].id];
queue.splice(i, 1);
nextUploadIndex--;
} else {
i++;
}
}
emitListUpdate();
};
var startNextTask = function () {
// 检查是否允许增加执行进程
if (uploadingFileCount >= cos.options.FileParallelLimit) return;
// 跳过不可执行的任务
while (queue[nextUploadIndex] && queue[nextUploadIndex].state !== 'waiting') nextUploadIndex++;
// 检查是否已遍历结束
if (nextUploadIndex >= queue.length) return;
// 上传该遍历到的任务
var task = queue[nextUploadIndex];
nextUploadIndex++;
uploadingFileCount++;
task.state = 'checking';
task.params.onTaskStart && task.params.onTaskStart(formatTask(task));
!task.params.UploadData && (task.params.UploadData = {});
var apiParams = util.formatParams(task.api, task.params);
originApiMap[task.api].call(cos, apiParams, function (err, data) {
if (!cos._isRunningTask(task.id)) return;
if (task.state === 'checking' || task.state === 'uploading') {
task.state = err ? 'error' : 'success';
err && (task.error = err);
uploadingFileCount--;
emitListUpdate();
startNextTask();
task.callback && task.callback(err, data);
if (task.state === 'success') {
if (task.params) {
delete task.params.UploadData;
delete task.params.Body;
delete task.params;
}
delete task.callback;
}
}
clearQueue();
});
emitListUpdate();
// 异步执行下一个任务
setTimeout(startNextTask);
};
var killTask = function (id, switchToState) {
var task = tasks[id];
if (!task) return;
var waiting = task && task.state === 'waiting';
var running = task && (task.state === 'checking' || task.state === 'uploading');
if (
(switchToState === 'canceled' && task.state !== 'canceled') ||
(switchToState === 'paused' && waiting) ||
(switchToState === 'paused' && running)
) {
if (switchToState === 'paused' && task.params.Body && typeof task.params.Body.pipe === 'function') {
console.error('stream not support pause');
return;
}
task.state = switchToState;
cos.emit('inner-kill-task', { TaskId: id, toState: switchToState });
try {
var UploadId = task && task.params && task.params.UploadData.UploadId;
} catch (e) {}
if (switchToState === 'canceled' && UploadId) session.removeUsing(UploadId);
emitListUpdate();
if (running) {
uploadingFileCount--;
startNextTask();
}
if (switchToState === 'canceled') {
if (task.params) {
delete task.params.UploadData;
delete task.params.Body;
delete task.params;
}
delete task.callback;
}
}
clearQueue();
};
cos._addTasks = function (taskList) {
util.each(taskList, function (task) {
cos._addTask(task.api, task.params, task.callback, true);
});
emitListUpdate();
};
var isTaskReadyWarning = true;
cos._addTask = function (api, params, callback, ignoreAddEvent) {
// 复制参数对象
params = util.formatParams(api, params);
// 生成 id
var id = util.uuid();
params.TaskId = id;
params.onTaskReady && params.onTaskReady(id);
if (params.TaskReady) {
params.TaskReady(id);
isTaskReadyWarning &&
console.warn('warning: Param "TaskReady" has been deprecated. Please use "onTaskReady" instead.');
isTaskReadyWarning = false;
}
var task = {
// env
params: params,
callback: callback,
api: api,
index: queue.length,
// task
id: id,
Bucket: params.Bucket,
Region: params.Region,
Key: params.Key,
FilePath: params.FilePath || '',
state: 'waiting',
loaded: 0,
size: 0,
speed: 0,
percent: 0,
hashPercent: 0,
error: null,
};
var onHashProgress = params.onHashProgress;
params.onHashProgress = function (info) {
if (!cos._isRunningTask(task.id)) return;
task.hashPercent = info.percent;
onHashProgress && onHashProgress(info);
emitListUpdate();
};
var onProgress = params.onProgress;
params.onProgress = function (info) {
if (!cos._isRunningTask(task.id)) return;
task.state === 'checking' && (task.state = 'uploading');
task.loaded = info.loaded;
task.speed = info.speed;
task.percent = info.percent;
onProgress && onProgress(info);
emitListUpdate();
};
// 异步获取 filesize
util.getFileSize(api, params, function (err, size) {
// 开始处理上传
if (err) return callback(util.error(err)); // 如果获取大小出错,不加入队列
// 获取完文件大小再把任务加入队列
tasks[id] = task;
queue.push(task);
task.size = size;
!ignoreAddEvent && emitListUpdate();
startNextTask();
clearQueue();
});
return id;
};
cos._isRunningTask = function (id) {
var task = tasks[id];
return !!(task && (task.state === 'checking' || task.state === 'uploading'));
};
cos.getTaskList = function () {
return util.map(queue, formatTask);
};
cos.cancelTask = function (id) {
killTask(id, 'canceled');
};
cos.pauseTask = function (id) {
killTask(id, 'paused');
};
cos.restartTask = function (id) {
var task = tasks[id];
if (task && (task.state === 'paused' || task.state === 'error')) {
task.state = 'waiting';
emitListUpdate();
nextUploadIndex = Math.min(nextUploadIndex, task.index);
startNextTask();
}
};
cos.isUploadRunning = function () {
return uploadingFileCount || nextUploadIndex < queue.length;
};
};
module.exports.transferToTaskMethod = transferToTaskMethod;
module.exports.init = initTask;
@@ -0,0 +1,855 @@
'use strict';
var fs = require('fs');
var crypto = require('crypto');
var { XMLParser, XMLBuilder } = require('fast-xml-parser');
var xmlParser = new XMLParser({
ignoreDeclaration: true, // 忽略 XML 声明
ignoreAttributes: true, // 忽略属性
parseTagValue: false, // 关闭自动解析
trimValues: false, // 关闭默认 trim
});
var xmlBuilder = new XMLBuilder();
function camSafeUrlEncode(str) {
return encodeURIComponent(str)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
}
var getObjectKeys = function (obj, forKey) {
var list = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
list.push(forKey ? camSafeUrlEncode(key).toLowerCase() : key);
}
}
return list.sort(function (a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a === b ? 0 : a > b ? 1 : -1;
});
};
/**
* obj转为string
* @param {Object} obj 需要转的对象,必须
* @param {Boolean} lowerCaseKey key是否转为小写,默认false,非必须
* @return {String} data 返回字符串
*/
var obj2str = function (obj, lowerCaseKey) {
var i, key, val;
var list = [];
var keyList = getObjectKeys(obj);
for (i = 0; i < keyList.length; i++) {
key = keyList[i];
val = obj[key] === undefined || obj[key] === null ? '' : '' + obj[key];
key = lowerCaseKey ? camSafeUrlEncode(key).toLowerCase() : camSafeUrlEncode(key);
val = camSafeUrlEncode(val) || '';
list.push(key + '=' + val);
}
return list.join('&');
};
// 可以签入签名的headers
var signHeaders = [
'content-disposition',
'content-encoding',
'content-length',
'content-md5',
'expect',
'expires',
'host',
'if-match',
'if-modified-since',
'if-none-match',
'if-unmodified-since',
'origin',
'range',
'transfer-encoding',
'pic-operations',
];
var getSignHeaderObj = function (headers) {
var signHeaderObj = {};
for (var i in headers) {
var key = i.toLowerCase();
if (key.indexOf('x-cos-') > -1 || key.indexOf('x-ci-') > -1 || signHeaders.indexOf(key) > -1) {
signHeaderObj[i] = headers[i];
}
}
return signHeaderObj;
};
//测试用的key后面可以去掉
var getAuth = function (opt) {
opt = opt || {};
var SecretId = opt.SecretId;
var SecretKey = opt.SecretKey;
var KeyTime = opt.KeyTime;
var method = (opt.method || opt.Method || 'get').toLowerCase();
var queryParams = clone(opt.Query || opt.params || {});
var headers = getSignHeaderObj(clone(opt.Headers || opt.headers || {}));
var Key = opt.Key || '';
var pathname;
if (opt.UseRawKey) {
pathname = opt.Pathname || opt.pathname || '/' + Key;
} else {
pathname = opt.Pathname || opt.pathname || Key;
pathname.indexOf('/') !== 0 && (pathname = '/' + pathname);
}
// ForceSignHost明确传入false才不加入host签名
var forceSignHost = opt.ForceSignHost === false ? false : true;
// 如果有传入存储桶,那么签名默认加 Host 参与计算,避免跨桶访问
if (!headers.Host && !headers.host && opt.Bucket && opt.Region && forceSignHost)
headers.Host = opt.Bucket + '.cos.' + opt.Region + '.myqcloud.com';
if (!SecretId) throw new Error('missing param SecretId');
if (!SecretKey) throw new Error('missing param SecretKey');
// 签名有效起止时间
var now = Math.round(getSkewTime(opt.SystemClockOffset) / 1000) - 1;
var exp = now;
var Expires = opt.Expires || opt.expires;
if (Expires === undefined) {
exp += 900; // 签名过期时间为当前 + 900s
} else {
exp += Expires * 1 || 0;
}
// 要用到的 Authorization 参数列表
var qSignAlgorithm = 'sha1';
var qAk = SecretId;
var qSignTime = KeyTime || now + ';' + exp;
var qKeyTime = KeyTime || now + ';' + exp;
var qHeaderList = getObjectKeys(headers, true).join(';').toLowerCase();
var qUrlParamList = getObjectKeys(queryParams, true).join(';').toLowerCase();
// 签名算法说明文档:https://www.qcloud.com/document/product/436/7778
// 步骤一:计算 SignKey
var signKey = crypto.createHmac('sha1', SecretKey).update(qKeyTime).digest('hex');
// 步骤二:构成 FormatString
var formatString = [method, pathname, obj2str(queryParams, true), obj2str(headers, true), ''].join('\n');
formatString = Buffer.from(formatString, 'utf8');
// 步骤三:计算 StringToSign
var res = crypto.createHash('sha1').update(formatString).digest('hex');
var stringToSign = ['sha1', qSignTime, res, ''].join('\n');
// 步骤四:计算 Signature
var qSignature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');
// 步骤五:构造 Authorization
var authorization = [
'q-sign-algorithm=' + qSignAlgorithm,
'q-ak=' + qAk,
'q-sign-time=' + qSignTime,
'q-key-time=' + qKeyTime,
'q-header-list=' + qHeaderList,
'q-url-param-list=' + qUrlParamList,
'q-signature=' + qSignature,
].join('&');
return authorization;
};
var getV4Auth = function (opt) {
if (!opt.SecretId) return console.error('missing param SecretId');
if (!opt.SecretKey) return console.error('missing param SecretKey');
if (!opt.Bucket) return console.error('missing param Bucket');
var longBucket = opt.Bucket;
var ShortBucket = longBucket.substr(0, longBucket.lastIndexOf('-'));
var AppId = longBucket.substr(longBucket.lastIndexOf('-') + 1);
var random = Math.round(Math.random() * Math.pow(2, 32));
var now = Math.round(Date.now() / 1000);
var e = now + (opt.Expires === undefined ? 900 : opt.Expires);
var path =
'/' +
AppId +
'/' +
ShortBucket +
'/' +
encodeURIComponent((opt.Key || '').replace(/(^\/*)/g, '')).replace(/%2F/g, '/');
var plainText =
'a=' + AppId + '&b=' + ShortBucket + '&k=' + opt.SecretId + '&t=' + now + '&e=' + e + '&r=' + random + '&f=' + path;
var signKey = crypto.createHmac('sha1', opt.SecretKey).update(plainText).digest();
var sign = Buffer.concat([signKey, Buffer.from(plainText)]).toString('base64');
return sign;
};
var getSourceParams = function (source) {
var parser = this.options.CopySourceParser;
if (parser) return parser(source);
var m = source.match(/^([^.]+-\d+)\.cos(v6|-cdc|-internal)?\.([^.]+)\.((myqcloud\.com)|(tencentcos\.cn))\/(.+)$/);
if (!m) return null;
return { Bucket: m[1], Region: m[3], Key: m[7] };
};
var noop = function () {};
// 清除对象里值为的 undefined 或 null 的属性
var clearKey = function (obj) {
var retObj = {};
for (var key in obj) {
if (obj.hasOwnProperty(key) && obj[key] !== undefined && obj[key] !== null) {
retObj[key] = obj[key];
}
}
return retObj;
};
// 删掉不需要的#text
var textNodeName = '#text';
var deleteTextNodes = function (obj) {
if (!isObject(obj)) return;
for (let i in obj) {
var item = obj[i];
if (typeof item === 'string') {
if (i === textNodeName) {
delete obj[i];
}
} else if (Array.isArray(item)) {
item.forEach(function (i) {
deleteTextNodes(i);
});
} else if (isObject(item)) {
deleteTextNodes(item);
}
}
};
// XML 对象转 JSON 对象
var xml2json = function (bodyStr) {
var json = xmlParser.parse(bodyStr);
deleteTextNodes(json);
return json;
};
// JSON 对象转 XML 对象
var json2xml = function (json) {
var xml = xmlBuilder.build(json);
return xml;
};
// 计算 MD5
var md5 = function (str, encoding) {
return crypto
.createHash('md5')
.update(str)
.digest(encoding || 'hex');
};
// 获取文件分片
var fileSlice = function (FilePath, start, end, callback) {
if (FilePath) {
try {
var readStream = fs.createReadStream(FilePath, { start: start, end: end - 1 });
readStream.isSdkCreated = true;
callback(readStream);
} catch (e) {}
} else {
callback(null);
}
};
// 获取文件内容的 MD5
var getBodyMd5 = function (UploadCheckContentMd5, Body, callback) {
callback = callback || noop;
if (UploadCheckContentMd5) {
if (Body instanceof Buffer || typeof Body === 'string') {
callback(util.md5(Body));
} else {
callback();
}
} else {
callback();
}
};
// 获取文件 md5 值
var getFileMd5 = function (readStream, callback) {
var md5 = crypto.createHash('md5');
readStream.on('data', function (chunk) {
md5.update(chunk);
});
readStream.on('error', function (err) {
callback(util.error(err));
});
readStream.on('end', function () {
var hash = md5.digest('hex');
callback(null, hash);
});
};
function clone(obj) {
return map(obj, function (v) {
return typeof v === 'object' && v !== null ? clone(v) : v;
});
}
function attr(obj, name, defaultValue) {
return obj && name in obj ? obj[name] : defaultValue;
}
function extend(target, source) {
each(source, function (val, key) {
target[key] = source[key];
});
return target;
}
function isArray(arr) {
return arr instanceof Array;
}
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function isInArray(arr, item) {
var flag = false;
for (var i = 0; i < arr.length; i++) {
if (item === arr[i]) {
flag = true;
break;
}
}
return flag;
}
function makeArray(arr) {
return isArray(arr) ? arr : [arr];
}
function each(obj, fn) {
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
fn(obj[i], i);
}
}
}
function map(obj, fn) {
var o = isArray(obj) ? [] : {};
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = fn(obj[i], i);
}
}
return o;
}
function filter(obj, fn) {
var iaArr = isArray(obj);
var o = iaArr ? [] : {};
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
if (fn(obj[i], i)) {
if (iaArr) {
o.push(obj[i]);
} else {
o[i] = obj[i];
}
}
}
}
return o;
}
var binaryBase64 = function (str) {
var i,
len,
char,
arr = [];
for (i = 0, len = str.length / 2; i < len; i++) {
char = parseInt(str[i * 2] + str[i * 2 + 1], 16);
arr.push(char);
}
return Buffer.from(arr).toString('base64');
};
var uuid = function () {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
};
var hasMissingParams = function (apiName, params) {
var Bucket = params.Bucket;
var Region = params.Region;
var Key = params.Key;
if (
apiName.indexOf('Bucket') > -1 ||
apiName === 'deleteMultipleObject' ||
apiName === 'multipartList' ||
apiName === 'listObjectVersions'
) {
if (!Bucket) return 'Bucket';
if (!Region) return 'Region';
} else if (
apiName.indexOf('Object') > -1 ||
apiName.indexOf('multipart') > -1 ||
apiName === 'sliceUploadFile' ||
apiName === 'abortUploadTask' ||
apiName === 'uploadFile'
) {
if (!Bucket) return 'Bucket';
if (!Region) return 'Region';
if (!Key) return 'Key';
}
return false;
};
var formatParams = function (apiName, params) {
// 复制参数对象
params = extend({}, params);
// 统一处理 Headers
if (apiName !== 'getAuth' && apiName !== 'getV4Auth' && apiName !== 'getObjectUrl') {
var Headers = params.Headers || {};
if (params && typeof params === 'object') {
(function () {
for (var key in params) {
if (params.hasOwnProperty(key) && key.indexOf('x-cos-') > -1) {
Headers[key] = params[key];
}
}
})();
var headerMap = {
// params headers
'x-cos-mfa': 'MFA',
'Content-MD5': 'ContentMD5',
'Content-Length': 'ContentLength',
'Content-Type': 'ContentType',
Expect: 'Expect',
Expires: 'Expires',
'Cache-Control': 'CacheControl',
'Content-Disposition': 'ContentDisposition',
'Content-Encoding': 'ContentEncoding',
Range: 'Range',
'If-Modified-Since': 'IfModifiedSince',
'If-Unmodified-Since': 'IfUnmodifiedSince',
'If-Match': 'IfMatch',
'If-None-Match': 'IfNoneMatch',
'x-cos-copy-source': 'CopySource',
'x-cos-copy-source-Range': 'CopySourceRange',
'x-cos-metadata-directive': 'MetadataDirective',
'x-cos-copy-source-If-Modified-Since': 'CopySourceIfModifiedSince',
'x-cos-copy-source-If-Unmodified-Since': 'CopySourceIfUnmodifiedSince',
'x-cos-copy-source-If-Match': 'CopySourceIfMatch',
'x-cos-copy-source-If-None-Match': 'CopySourceIfNoneMatch',
'x-cos-acl': 'ACL',
'x-cos-grant-read': 'GrantRead',
'x-cos-grant-write': 'GrantWrite',
'x-cos-grant-full-control': 'GrantFullControl',
'x-cos-grant-read-acp': 'GrantReadAcp',
'x-cos-grant-write-acp': 'GrantWriteAcp',
'x-cos-storage-class': 'StorageClass',
'x-cos-traffic-limit': 'TrafficLimit',
'x-cos-mime-limit': 'MimeLimit',
// SSE-C
'x-cos-server-side-encryption-customer-algorithm': 'SSECustomerAlgorithm',
'x-cos-server-side-encryption-customer-key': 'SSECustomerKey',
'x-cos-server-side-encryption-customer-key-MD5': 'SSECustomerKeyMD5',
// SSE-COS、SSE-KMS
'x-cos-server-side-encryption': 'ServerSideEncryption',
'x-cos-server-side-encryption-cos-kms-key-id': 'SSEKMSKeyId',
'x-cos-server-side-encryption-context': 'SSEContext',
// 上传时图片处理
'Pic-Operations': 'PicOperations',
};
util.each(headerMap, function (paramKey, headerKey) {
if (params[paramKey] !== undefined) {
Headers[headerKey] = params[paramKey];
}
});
params.Headers = clearKey(Headers);
}
}
return params;
};
var apiWrapper = function (apiName, apiFn) {
return function (params, callback) {
var self = this;
// 处理参数
if (typeof params === 'function') {
callback = params;
params = {};
}
// 整理参数格式
params = formatParams(apiName, params);
// 代理回调函数
var formatResult = function (result) {
if (result && result.headers) {
result.headers['x-ci-request-id'] && (result.RequestId = result.headers['x-ci-request-id']);
result.headers['x-cos-request-id'] && (result.RequestId = result.headers['x-cos-request-id']);
result.headers['x-cos-version-id'] && (result.VersionId = result.headers['x-cos-version-id']);
result.headers['x-cos-delete-marker'] && (result.DeleteMarker = result.headers['x-cos-delete-marker']);
}
return result;
};
var _callback = function (err, data) {
callback && callback(formatResult(err), formatResult(data));
};
var checkParams = function () {
if (apiName !== 'getService' && apiName !== 'abortUploadTask') {
// 判断参数是否完整
var missingResult = hasMissingParams(apiName, params);
if (missingResult) {
return 'missing param ' + missingResult;
}
// 判断 region 格式
if (params.Region) {
if (params.Region.indexOf('cos.') > -1) {
return 'param Region should not be start with "cos."';
} else if (!/^([a-z\d-]+)$/.test(params.Region)) {
return 'Region format error.';
}
// 判断 region 格式
if (
!self.options.CompatibilityMode &&
params.Region.indexOf('-') === -1 &&
params.Region !== 'yfb' &&
params.Region !== 'default' &&
params.Region !== 'accelerate'
) {
console.warn(
'warning: param Region format error, find help here: https://cloud.tencent.com/document/product/436/6224'
);
}
}
// 兼容不带 AppId 的 Bucket
if (params.Bucket) {
if (!/^([a-z\d-]+)-(\d+)$/.test(params.Bucket)) {
if (params.AppId) {
params.Bucket = params.Bucket + '-' + params.AppId;
} else if (self.options.AppId) {
params.Bucket = params.Bucket + '-' + self.options.AppId;
} else {
return 'Bucket should format as "test-1250000000".';
}
}
if (params.AppId) {
console.warn(
'warning: AppId has been deprecated, Please put it at the end of parameter Bucket(E.g Bucket:"test-1250000000" ).'
);
delete params.AppId;
}
}
// 如果 Key 是 / 开头,强制去掉第一个 /
if (!self.options.UseRawKey && params.Key && params.Key.substr(0, 1) === '/') {
params.Key = params.Key.substr(1);
}
}
};
var errMsg = checkParams();
var isSync = ['getAuth', 'getV4Auth', 'getObjectUrl'].includes(apiName) || apiName.indexOf('Stream') > -1;
if (Promise && !isSync && !callback) {
return new Promise(function (resolve, reject) {
callback = function (err, data) {
err ? reject(err) : resolve(data);
};
if (errMsg) return _callback(util.error(new Error(errMsg)));
apiFn.call(self, params, _callback);
});
} else {
if (errMsg) return _callback(util.error(new Error(errMsg)));
var res = apiFn.call(self, params, _callback);
if (isSync) return res;
}
};
};
var throttleOnProgress = function (total, onProgress) {
var self = this;
var size0 = 0;
var size1 = 0;
var time0 = Date.now();
var time1;
var timer;
function update() {
timer = 0;
if (onProgress && typeof onProgress === 'function') {
time1 = Date.now();
var speed = Math.max(0, Math.round(((size1 - size0) / ((time1 - time0) / 1000)) * 100) / 100) || 0;
var percent;
if (size1 === 0 && total === 0) {
percent = 1;
} else {
percent = Math.floor((size1 / total) * 100) / 100 || 0;
}
time0 = time1;
size0 = size1;
try {
onProgress({ loaded: size1, total: total, speed: speed, percent: percent });
} catch (e) {}
}
}
return function (info, immediately) {
if (info) {
size1 = info.loaded;
total = info.total;
}
if (immediately) {
clearTimeout(timer);
update();
} else {
if (timer) return;
timer = setTimeout(update, self.options.ProgressInterval);
}
};
};
var getFileSize = function (api, params, callback) {
var size;
if (api === 'sliceUploadFile') {
if (params.FilePath) {
fs.stat(params.FilePath, function (err, fileStats) {
if (err) {
if (params.ContentLength !== undefined) {
size = params.ContentLength;
} else {
return callback(err);
}
} else {
params.FileStat = fileStats;
params.FileStat.FilePath = params.FilePath;
size = fileStats.isDirectory() ? 0 : fileStats.size;
}
params.ContentLength = size = size || 0;
callback(null, size);
});
return;
} else {
callback(util.error(new Error('missing param FilePath')));
return;
}
} else {
if (params.Body !== undefined) {
if (typeof params.Body === 'string') {
params.Body = global.Buffer.from(params.Body);
}
if (params.Body instanceof global.Buffer) {
size = params.Body.length;
} else if (typeof params.Body.pipe === 'function') {
if (params.ContentLength === undefined) {
size = undefined;
} else {
size = params.ContentLength;
}
} else {
callback(util.error(new Error('params Body format error, Only allow Buffer|Stream|String.')));
return;
}
} else {
callback(util.error(new Error('missing param Body')));
return;
}
}
params.ContentLength = size;
callback(null, size);
};
// 获取调正的时间戳
var getSkewTime = function (offset) {
return Date.now() + (offset || 0);
};
// 重写 callback,等待流结束后才 callback
var callbackAfterStreamFinish = function (stream, callback) {
if (!stream) return callback;
var err,
data,
count = 2,
loaded = false;
var cb = function (e, d) {
if (loaded) return;
// 如果有数据,且没有错误,清理 设置错误
if ((d && !data) || e || err) {
data = d;
}
if (e && !err) {
err = e;
data = null;
}
if (err || --count === 0) {
loaded = true;
callback(err, data);
}
};
stream.on('error', function (err) {
cb(err);
});
stream.on('finish', function () {
cb();
});
return cb;
};
var error = function (err, opt) {
var sourceErr = err;
err.message = err.message || null;
if (typeof opt === 'string') {
err.error = opt;
err.message = opt;
} else if (typeof opt === 'object' && opt !== null) {
extend(err, opt);
if (opt.code || opt.name) err.code = opt.code || opt.name;
if (opt.message) err.message = opt.message;
if (opt.stack) err.stack = opt.stack;
}
if (typeof Object.defineProperty === 'function') {
Object.defineProperty(err, 'name', { writable: true, enumerable: false });
Object.defineProperty(err, 'message', { enumerable: true });
}
err.name = (opt && opt.name) || err.name || err.code || 'Error';
if (!err.code) err.code = err.name;
if (!err.error) {
var objectType = Object.prototype.toString.call(err);
if (objectType === '[object Object]') {
// 兼容老的错误格式
err.error = clone(sourceErr);
} else if (objectType === '[object Error]') {
// 有环境报出[object Error]对象的情况,兼容处理一下
err = {
code: err.code || err.name || 'Error',
name: err.name || err.code || 'Error',
message: err.reason || err.message || 'Error',
};
}
}
return err;
};
var isWeb = function () {
return typeof window === 'object';
};
var isCIHost = function (url) {
return /^https?:\/\/([^/]+\.)?ci\.[^/]+/.test(url);
};
var encodeBase64 = function (str, safe) {
let base64Str = Buffer.from(str).toString('base64');
// 万象使用的安全base64格式需要特殊处理
if (safe) {
base64Str = base64Str.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
}
return base64Str;
};
var simplifyPath = function (path) {
const names = path.split('/');
const stack = [];
for (const name of names) {
if (name === '..') {
if (stack.length) {
stack.pop();
}
} else if (name.length && name !== '.') {
stack.push(name);
}
}
return '/' + stack.join('/');
};
// 解析响应体,兼容 xml、json
var parseResBody = function (responseBody) {
var json;
if (responseBody && typeof responseBody === 'string') {
var trimBody = responseBody.trim();
var isXml = trimBody.indexOf('<') === 0;
var isJson = trimBody.indexOf('{') === 0;
if (isXml) {
// xml 解析,解析失败返回{}
json = util.xml2json(responseBody) || {};
} else if (isJson) {
// json解析,解析失败返回原始 Body
try {
// 替换 json 中的换行符为空格,否则解析会出错
var formatBody = responseBody.replace(/\n/g, ' ');
var parsedBody = JSON.parse(formatBody);
// 确保解析出 json 对象
if (Object.prototype.toString.call(parsedBody) === '[object Object]') {
json = parsedBody;
} else {
json = responseBody;
}
} catch (e) {
json = responseBody;
}
} else {
json = responseBody;
}
} else {
json = responseBody || {};
}
return json;
};
var util = {
noop: noop,
formatParams: formatParams,
apiWrapper: apiWrapper,
xml2json: xml2json,
json2xml: json2xml,
md5: md5,
clearKey: clearKey,
fileSlice: fileSlice,
getBodyMd5: getBodyMd5,
getFileMd5: getFileMd5,
binaryBase64: binaryBase64,
extend: extend,
isArray: isArray,
isInArray: isInArray,
makeArray: makeArray,
each: each,
map: map,
filter: filter,
clone: clone,
attr: attr,
uuid: uuid,
camSafeUrlEncode: camSafeUrlEncode,
throttleOnProgress: throttleOnProgress,
getFileSize: getFileSize,
getSkewTime: getSkewTime,
error: error,
getAuth: getAuth,
callbackAfterStreamFinish: callbackAfterStreamFinish,
getV4Auth: getV4Auth,
isBrowser: false,
obj2str: obj2str,
isWeb: isWeb,
isCIHost: isCIHost,
getSourceParams: getSourceParams,
encodeBase64: encodeBase64,
simplifyPath: simplifyPath,
parseResBody: parseResBody,
};
module.exports = util;