UNPKG

qiao-cos

Version:

tencent cos upload tool on nodejs

488 lines (424 loc) 11.9 kB
'use strict'; var COS = require('cos-nodejs-sdk-v5'); var STS = require('qcloud-cos-sts'); var qiao_log_js = require('qiao.log.js'); var qiaoFile = require('qiao-file'); var Debug = require('debug'); var progress = require('progress'); // md5 const { md5 } = require('qiao-encode'); /** * sign img * @param {*} key * @param {*} filepath * @param {*} timeout s * @returns */ const cdnSign = (key, filepath, timeout) => { // check if (!key) { console.log('qiao-cos / cdnSign / need config.key'); return; } if (!filepath) { console.log('qiao-cos / cdnSign / need filepath'); return; } // str const now = parseInt(Date.now() / 1000) + (timeout || 0); const str = key + filepath + now; // md5 const md5Str = md5(str, 'hex'); // url return `${filepath}?sign=${md5Str}&t=${now}`; }; // sts const logger = qiao_log_js.Logger('qiao-cos'); /** * getCredential * @param {*} durationSeconds * @param {*} allowPrefix * @param {*} config */ const getCredential = (durationSeconds, allowPrefix, config) => { const methodName = 'getCredential'; logger.info(methodName, 'durationSeconds', durationSeconds); logger.info(methodName, 'allowPrefix', allowPrefix); logger.info(methodName, 'config', config); // options const options = { secretId: config.SecretId, secretKey: config.SecretKey, bucket: config.Bucket, region: config.Region, durationSeconds: durationSeconds || 1800, allowPrefix: allowPrefix || '', }; const appId = options.bucket.substr(1 + options.bucket.lastIndexOf('-')); logger.info(methodName, 'options', options); logger.info(methodName, 'appId', appId); // policy const policy = { version: '2.0', statement: [ { action: [ // https://cloud.tencent.com/document/product/436/31923 'name/cos:PutObject', 'name/cos:PostObject', 'name/cos:InitiateMultipartUpload', 'name/cos:ListMultipartUploads', 'name/cos:ListParts', 'name/cos:UploadPart', 'name/cos:CompleteMultipartUpload', ], effect: 'allow', principal: { qcs: ['*'] }, resource: ['qcs::cos:' + options.region + ':uid/' + appId + ':' + options.bucket + '/' + options.allowPrefix], }, ], }; logger.info(methodName, 'policy', policy); // getCredential return new Promise((resolve, reject) => { STS.getCredential( { secretId: options.secretId, secretKey: options.secretKey, durationSeconds: options.durationSeconds, region: options.region, policy: policy, }, function (err, credential) { return err ? reject(err) : resolve(credential); }, ); }); }; /** * listBuckets * @returns */ const listBuckets = (app) => { return new Promise((resolve, reject) => { // check if (!app || !app.client) return reject(new Error('need app, app.client')); // list bucket app.client.getService(function (err, data) { if (err) return reject(err); if (!data || data.statusCode !== 200) return reject(new Error('list buckets error')); resolve(data); }); }); }; /** * listObjects * @param {*} app * @param {*} prefix * @param {*} max * @param {*} marker * @returns */ const listObjects = (app, prefix, max, marker) => { return new Promise((resolve, reject) => { // check if (!app || !app.client || !app.config) return reject(new Error('need app, app.client, app.config')); // options const options = { Bucket: app.config.Bucket, Region: app.config.Region, }; if (prefix) options.Prefix = prefix; if (max) options.MaxKeys = max; if (marker) options.Marker = marker; // list object app.client.getBucket(options, (err, data) => { if (err) return reject(err); if (!data || data.statusCode !== 200) return reject(new Error('list buckets error')); resolve(data); }); }); }; /** * listObjectsAll * @param {*} app * @param {*} prefix * @param {*} max * @param {*} marker * @returns */ const listObjectsAll = async (app, prefix, max) => { const list = []; await getObjects(list, app, prefix, max); return list; }; // get objects async function getObjects(list, app, prefix, max, marker) { try { // get const res = await listObjects(app, prefix, max, marker); console.log('qiao-cos / getObjects / IsTruncated', res.IsTruncated); console.log('qiao-cos / getObjects / NextMarker', res.NextMarker); console.log('qiao-cos / getObjects / Contents', res.Contents.length); list.push(...res.Contents); // go if (res.IsTruncated === 'true') await getObjects(list, app, prefix, max, res.NextMarker); } catch (error) { console.log(error); } } /** * delObject * @returns */ const delObject = (app, key) => { return new Promise((resolve, reject) => { // check if (!app || !app.client) return reject(new Error('need app, app.client')); // del object app.client.deleteObject( { Bucket: app.config.Bucket, Region: app.config.Region, Key: key, }, function (err, data) { if (err) return reject(err); if (!data || data.statusCode !== 204) return reject(new Error('del object error')); resolve(data); }, ); }); }; // file const debug = Debug('qiao-cos'); /** * uploadFile * @param {*} app * @param {*} dest * @param {*} source * @param {*} options * @returns */ const uploadFile = (app, dest, source, options) => { // check if (!app || !app.client || !app.config) { return Promise.reject(new Error('need app, app.client, app.config')); } // upload return new Promise((resolve, reject) => { options = options || {}; options.callback = (err, data) => { return err ? reject(err) : resolve(data); }; uploadFileWithCallback(app, dest, source, options); }); }; /** * uploadFileWithCallback * @param {*} app * @param {*} dest * @param {*} source * @param {*} options * @returns */ const uploadFileWithCallback = (app, dest, source, options) => { // check if (!app || !app.client || !app.config) { if (options.callback) options.callback(new Error('need app, app.client, app.config')); return; } // is absolute if (!qiaoFile.path.isAbsolute(source)) { if (options.callback) options.callback(new Error('source file path must be absolute')); return; } // log debug(`from ${source} to ${dest}`); // options const finalOptions = { Bucket: app.config.Bucket, Region: app.config.Region, Key: dest, FilePath: source, SliceSize: options.sliceSize || 1024 * 1024 * 5, }; if (options.onTaskReady) finalOptions.onTaskReady = options.onTaskReady; if (options.onProgress) finalOptions.onProgress = options.onProgress; if (options.onFileFinish) finalOptions.onFileFinish = options.onFileFinish; if (options.Headers) finalOptions.Headers = options.Headers; debug('finalOptions', finalOptions); // upload app.client.uploadFile(finalOptions, (err, data) => { if (options.callback) options.callback(err, data); }); }; // progress /** * upload folder * @param {*} app * @param {*} destFolder * @param {*} sourceFolder * @returns */ const uploadFolder = async (app, destFolder, sourceFolder) => { // check if (!app || !app.client || !app.config) { return Promise.reject(new Error('need app, app.client, app.config')); } // is absolute if (!qiaoFile.path.isAbsolute(sourceFolder)) { return Promise.reject(new Error('source file path must be absolute')); } // time console.time('total use'); // files const paths = await qiaoFile.lsdir(sourceFolder); const files = paths.files; const bar = new progress('uploading files... :current/:total', { total: files.length, }); // vars const allFiles = []; const sucFiles = []; const failFiles = []; // upload return new Promise((resolve, reject) => { const options = {}; options.callback = (err, data) => { allFiles.push(data); if (err || !data || data.statusCode != 200) { failFiles.push(err || data); } else { sucFiles.push(data); } bar.tick(); if (bar.complete) { const obj = {}; obj.paths = paths; obj.all = allFiles; obj.suc = sucFiles; obj.fail = failFiles; console.log(); console.timeEnd('total use'); console.log('all files:', allFiles.length); console.log('fail files:', failFiles.length); console.log('success files:', sucFiles.length); console.log(); if (allFiles.length === sucFiles.length) { resolve(obj); } else { reject(new Error('some files upload failed')); } } }; for (let i = 0; i < files.length; i++) { const file = files[i].path; const dest = destFolder + file.split(sourceFolder)[1]; uploadFileWithCallback(app, dest, file, options); } }); }; // cos /** * reviewTxt * @param {*} app * @param {*} txt * @returns */ const reviewTxt = (app, txt) => { return new Promise((resolve, reject) => { // check if (!app || !app.client) return reject(new Error('need app, app.client')); if (!txt) return reject(new Error('need txt')); // url const host = app.config.Bucket + '.ci.' + app.config.Region + '.myqcloud.com'; const url = 'https://' + host + '/text/auditing'; // body const txtBase64 = Buffer.from(txt).toString('base64'); const body = COS.util.json2xml({ Request: { Input: { Content: txtBase64, }, Conf: { BizType: '', }, }, }); // review txt app.client.request( { Bucket: app.config.Bucket, Region: app.config.Region, Method: 'POST', Url: url, Key: '/text/auditing', // * 固定值,必须 ContentType: 'application/xml', // * 固定值,必须 Body: body, }, function (err, data) { if (err) return reject(err); if (!data || data.statusCode !== 200) return reject(new Error('review txt error')); resolve(data); }, ); }); }; // cos /** * init app * @param {*} config * @returns */ const init = (config) => { // check if (!config) throw new Error('need config params'); if (!config.SecretId) throw new Error('need config.SecretId params'); if (!config.SecretKey) throw new Error('need config.SecretKey params'); if (!config.Region) throw new Error('need config.Region params'); if (!config.Bucket) throw new Error('need config.Bucket params'); // app const app = {}; app.config = config; app.client = new COS({ SecretId: config.SecretId, SecretKey: config.SecretKey, }); // cdn app.cdnSign = (filepath, timeout) => { return cdnSign(config.signKey, filepath, timeout); }; // sts app.getCredential = async (durationSeconds, allowPrefix) => { return await getCredential(durationSeconds, allowPrefix, config); }; // bucket app.listBuckets = async () => { return await listBuckets(app); }; app.listObjects = async (prefix, max, marker) => { return await listObjects(app, prefix, max, marker); }; app.listObjectsAll = async (prefix, max) => { return await listObjectsAll(app, prefix, max); }; // object app.delObject = async (key) => { return await delObject(app, key); }; // upload app.uploadFile = async (dest, source, options) => { return await uploadFile(app, dest, source, options); }; app.uploadFolder = async (destFolder, sourceFolder) => { return await uploadFolder(app, destFolder, sourceFolder); }; // review app.reviewTxt = async (txt) => { return await reviewTxt(app, txt); }; // return return app; }; module.exports = init;