├── qcloud_cos ├── index.js ├── package.json ├── lib │ ├── conf.js │ ├── auth.js │ └── cos.js ├── test │ └── sample.js └── LICENSE └── README.md /qcloud_cos/index.js: -------------------------------------------------------------------------------- 1 | 2 | var libpath = './lib'; 3 | 4 | module.exports = { 5 | auth: require(libpath + '/auth.js'), 6 | conf: require(libpath + '/conf.js'), 7 | cos: require(libpath + '/cos.js'), 8 | }; 9 | -------------------------------------------------------------------------------- /qcloud_cos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qcloud_cos", 3 | "version": "1.0.4", 4 | "description": "node sdk for qcloud ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/tencentyun/cos-nodejs-sdk.git" 12 | }, 13 | "keywords": [ 14 | "qcloud" 15 | ], 16 | "author": "tencentyun", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/tencentyun/cos-nodejs-sdk/issues" 20 | }, 21 | "homepage": "https://github.com/tencentyun/cos-nodejs-sdk#readme", 22 | "engines": [ 23 | "node >= 0.4.7" 24 | ], 25 | "dependencies": { 26 | "formstream": "0.0.7" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /qcloud_cos/lib/conf.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | 5 | // 请到app.qcloud.com查看您对应的appid相关信息并填充 6 | exports.APPID = '您的APPID'; 7 | exports.SECRET_ID = '您的SECRET_ID'; 8 | exports.SECRET_KEY = '您的SECRET_KEY'; 9 | 10 | var pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../', 'package.json'))); 11 | var ua = function() { 12 | return 'cos-node-sdk-'+pkg.version; 13 | } 14 | 15 | // 签名的有效时间 16 | exports.EXPIRED_SECONDS = 60; 17 | 18 | exports.recvTimeout = 30000; 19 | exports.USER_AGENT = ua; 20 | exports.API_COS_END_POINT = 'http://web.file.myqcloud.com/files/v1/'; 21 | 22 | // timeout单位秒 23 | exports.setAppInfo = function(appid, secretId, secretKey, timeout) { 24 | timeout = timeout || 30; 25 | module.exports.APPID = appid; 26 | module.exports.SECRET_ID = secretId; 27 | module.exports.SECRET_KEY = secretKey; 28 | module.exports.recvTimeout = timeout * 1000; 29 | } 30 | -------------------------------------------------------------------------------- /qcloud_cos/test/sample.js: -------------------------------------------------------------------------------- 1 | //var qcloud_cos = require('qcloud_cos'); 2 | var qcloud_cos = require('../'); 3 | 4 | qcloud_cos.conf.setAppInfo('1000000', 'AKIiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', 'wSDlllllllllllllllllllllllllllll'); 5 | 6 | qcloud_cos.cos.deleteFile('bucket01', '123/t.mp4', function(ret) {console.log(ret); 7 | qcloud_cos.cos.upload('./test', 'bucket01', '123/t.mp4', '0666', function(ret) {console.log(ret); 8 | qcloud_cos.cos.updateFile('bucket01', '123/t.mp4', '', function(ret) {console.log(ret); 9 | qcloud_cos.cos.statFile('bucket01', '123/t.mp4', function(ret) {console.log(ret); 10 | qcloud_cos.cos.prefixSearch('bucket01', '123', 'z', function(ret) {console.log(ret); 11 | qcloud_cos.cos.createFolder('bucket01', '/123', function(ret) {console.log(ret); 12 | qcloud_cos.cos.deleteFolder('bucket01', '123/', function(ret) {console.log(ret); 13 | qcloud_cos.cos.upload_slice('./test', 'bucket01', '123/t.mp4', '0666', 512000);});});});});});});}); 14 | -------------------------------------------------------------------------------- /qcloud_cos/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 tencent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /qcloud_cos/lib/auth.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var urlM = require('url'); 3 | var conf = require('./conf'); 4 | 5 | exports.AUTH_URL_FORMAT_ERROR = -1; 6 | exports.AUTH_SECRET_ID_KEY_ERROR = -2; 7 | 8 | exports.signMore = function(bucket, expired) { 9 | return appSign(bucket, '', expired); 10 | } 11 | exports.signOnce = function(bucket, fileid) { 12 | return appSign(bucket, fileid, 0); 13 | } 14 | 15 | function appSign(bucket, fileid, expired) { 16 | 17 | var now = parseInt(Date.now() / 1000); 18 | var rdm = parseInt(Math.random() * Math.pow(2, 32)); 19 | 20 | var secretId = conf.SECRET_ID, secretKey = conf.SECRET_KEY; 21 | 22 | if (!secretId.length || !secretKey.length){ 23 | return AUTH_SECRET_ID_KEY_ERROR; 24 | } 25 | 26 | var plainText = 'a='+conf.APPID+'&k='+secretId+'&e='+expired+'&t='+now+'&r='+rdm+'&f='+fileid+'&b='+bucket; 27 | 28 | var data = new Buffer(plainText,'utf8'); 29 | 30 | var res = crypto.createHmac('sha1',secretKey).update(data).digest(); 31 | 32 | var bin = Buffer.concat([res,data]); 33 | 34 | var sign = bin.toString('base64'); 35 | 36 | return sign; 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qcloud_cos-node 2 | 3 | **该SDK已弃用,请使用[XML SDK](https://github.com/tencentyun/cos-nodejs-sdk-v5)** 4 | 5 | nodejs sdk for [腾讯云COS服务] 6 | 7 | ## 安装 8 | npm install qcloud_cos 9 | 10 | ## 指定您的配置 11 | 修改conf.js中的配置信息或者如下设置 12 | ```javascript 13 | qcloud_cos.conf.setAppInfo('000000', 'xxxxxxxx', 'xxxxxxx'); 14 | ``` 15 | 16 | ## 程序示例 17 | ```javascript 18 | var qcloud = require('qcloud_cos'); 19 | 20 | qcloud.conf.setAppInfo('100000', 'AKIDoooooooooooooooooooooooooooooooo', 'ROllllllllllllllllllllllllllllll'); 21 | 22 | /* 23 | * 各接口的callback 为单个参数的function: function(ret){} 24 | * ret 为 {'code':0,'message':'ok','data':{}} 的对象,其中data的内容依接口有所不同 25 | */ 26 | //上传文件 27 | qcloud.cos.upload('/tmp/test.txt', 'bucket', 'text/1.txt','new myattr',1, function(ret){ 28 | if (ret.code != 0) { 29 | console.log(ret); 30 | }else{ 31 | // 查询文件 32 | qcloud.cos.statFile('bucket', 'text/1.txt', function(ret) { 33 | console.log(ret); 34 | }); 35 | // 删除文件 36 | qcloud.cos.deleteFile('bucket', 'text/1.txt', function(ret) { 37 | console.log(ret); 38 | }); 39 | } 40 | }); 41 | 42 | //创建目录 43 | qcloud.cos.createFolder('bucket', '/firstDir/'); 44 | 45 | //获取指定目录下文件列表 46 | qcloud.cos.list('bucket', '/firstDir/', 20, 'eListFileOnly'); 47 | 48 | //获取bucket下文件列表 49 | qcloud.cos.list('bucket', '/', 20, 'eListFileOnly'); 50 | 51 | //获取指定目录下以'abc'开头的文件 52 | qcloud.cos.prefixSearch('bucket', '/firstDir/', 'abc', 20, 'eListFileOnly'); 53 | 54 | //设置文件权限以及自定义header 55 | var headers = { 56 | "Cache-Control": "no-cache", 57 | "Content-Type" : "application/json", 58 | "Content-Encoding" : "utf-8" 59 | }; 60 | 61 | qcloud_cos.cos.updateFile('0001', '123/test_slice.dat', 'newattr', 'eWRPrivate', headers, function(ret) {console.log(ret)}); 62 | 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /qcloud_cos/lib/cos.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var https = require('https'); 3 | var urlM = require('url'); 4 | var fs = require('fs'); 5 | var crypto = require('crypto'); 6 | var formstream = require('formstream'); 7 | var auth = require('./auth'); 8 | var conf = require('./conf'); 9 | 10 | var COS_PARAMS_ERROR = -1; 11 | var COS_NETWORK_ERROR = -2; 12 | var COS_FOLDER_ERROR = -3; 13 | 14 | //512K 15 | var SLICE_SIZE_512K = 524288; 16 | //1M 17 | var SLICE_SIZE_1M = 1048576; 18 | //2M 19 | var SLICE_SIZE_2M = 2097152; 20 | //3M 21 | var SLICE_SIZE_3M = 3145728; 22 | //20M 大于20M的文件需要进行分片传输 23 | var MAX_UNSLICE_FILE_SIZE = 20971520; 24 | 25 | function buildRequest(options, callback) { 26 | var net = http; 27 | if (options['protocol'] == "https:") { 28 | net = https; 29 | } 30 | var req = net.request(options, 31 | function (res) { 32 | var body = ""; 33 | res.on('data', function (data) { body += data; }) 34 | .on('end', function () { 35 | try { 36 | var ret = JSON.parse(body.toString()); 37 | } catch (err) { 38 | console.log(err) 39 | } 40 | if (ret) { 41 | var result = { 42 | 'code':ret.code, 43 | 'message':ret.message || '', 44 | 'data':{} 45 | } 46 | 47 | if (0 == ret.code && ret.hasOwnProperty('data')) { 48 | result.data = ret.data; 49 | } 50 | 51 | callback(result); 52 | 53 | } else { 54 | callback({'code':COS_NETWORK_ERROR, 'message':'response '+body.toString()+' is not json', 'data':{}}); 55 | } 56 | }); 57 | }).on('error', function(e){ 58 | callback({'code':COS_NETWORK_ERROR, 'message':String(e.message), 'data':{}}); 59 | }); 60 | req.setTimeout(conf.recvTimeout, function(){ 61 | req.end(); 62 | callback({'code':COS_NETWORK_ERROR, 'message':'recv timeout', 'data':{}}); 63 | }); 64 | return req; 65 | } 66 | 67 | /** 68 | * 上传本地文件 69 | * @param {string} filePath 文件本地路径,必须 70 | * @param {string} bucket bucket名称,必须 71 | * @param {string} dstpath 文件存储的路径和名称,必须 72 | * @param {string} bizattr 文件的属性,可选 73 | * @param {int} insertOnly 是否允许覆盖文件,0表示允许,1表示不允许,可选 74 | * @param {Function} callback 用户上传完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 75 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 76 | */ 77 | function upload(filePath, bucket, dstpath, bizattr, insertOnly, callback) { 78 | 79 | if (typeof bizattr === 'function') { 80 | callback = bizattr; 81 | bizattr = null; 82 | }else if(typeof insertOnly === 'function'){ 83 | callback = insertOnly; 84 | insertOnly = undefined; 85 | } else { 86 | callback = callback || function(ret){ console.log(ret); }; 87 | } 88 | 89 | var isExists = fs.existsSync(filePath); 90 | if (isExists && typeof callback === 'function') { 91 | 92 | 93 | var stats = fs.statSync(filePath); 94 | var fileSizeInBytes = stats["size"]; 95 | if(fileSizeInBytes>=MAX_UNSLICE_FILE_SIZE){//大于20M用分片上传 96 | 97 | upload_slice(filePath, bucket, dstpath, bizattr, null, null, insertOnly, callback); 98 | return; 99 | } 100 | 101 | bucket = bucket.strip(); 102 | dstpath = fixPath(dstpath); 103 | var expired = parseInt(Date.now() / 1000) + conf.EXPIRED_SECONDS; 104 | var sign = auth.signMore(bucket, expired); 105 | var url = generateResUrl(bucket, dstpath); 106 | var urlInfo = urlM.parse(url); 107 | 108 | var sha = crypto.createHash('sha1'); 109 | 110 | var fsRS = fs.createReadStream(filePath); 111 | fsRS.on('data', function(d) { sha.update(d); }); 112 | 113 | fsRS.on('end', function() { 114 | var form = formstream() 115 | .field('op', 'upload') 116 | .field('sha', sha.digest('hex')); 117 | 118 | 119 | form.file('filecontent', filePath, fileSizeInBytes); 120 | if (bizattr) { 121 | form.field('biz_attr', bizattr.toString()); 122 | } 123 | if(insertOnly!==undefined){ 124 | form.field('insertOnly', insertOnly); 125 | } 126 | 127 | var headers = form.headers(); 128 | headers['Authorization'] = sign; 129 | headers['User-Agent'] = conf.USER_AGENT(); 130 | 131 | var options = { 132 | protocol: urlInfo.protocol, 133 | hostname: urlInfo.hostname, 134 | port: urlInfo.port, 135 | path: urlInfo.path, 136 | method: 'POST', 137 | headers: headers 138 | }; 139 | 140 | var req = buildRequest(options, callback); 141 | req && form.pipe(req); 142 | }); 143 | 144 | } else { 145 | // error, file not exists 146 | callback({'code':COS_PARAMS_ERROR, 'message':'file '+filePath+' not exists or params error', 'data':{}}); 147 | } 148 | } 149 | 150 | /** 151 | * 分片上传获取size 152 | * @param {int} size 文件分片大小,Bytes 153 | * return {int} size 文件分片大小,Bytes 154 | */ 155 | function getSliceSize(size){ 156 | var res = SLICE_SIZE_1M; 157 | 158 | 159 | if(size<=SLICE_SIZE_512K){ 160 | res = SLICE_SIZE_512K; 161 | }else if(size<=SLICE_SIZE_1M){ 162 | res = SLICE_SIZE_1M; 163 | }else if(size<=SLICE_SIZE_2M){ 164 | res = SLICE_SIZE_2M; 165 | }else if(size<=SLICE_SIZE_3M){ 166 | res = SLICE_SIZE_3M; 167 | }else{ 168 | res = SLICE_SIZE_3M; 169 | } 170 | 171 | 172 | return res; 173 | } 174 | 175 | /** 176 | * 分片上传本地文件 177 | * @param {string} filePath 文件本地路径,必须 178 | * @param {string} bucket bucket目录名称,必须 179 | * @param {string} dstpath 文件存储的路径和名称,必须 180 | * @param {string} bizattr 目录/文件属性,业务端维护,可选 181 | * @param {int} slice_size 指定分片大小,小于3M,可选 182 | * @param {string} session 指定续传session,可选 183 | * @param {int} insertOnly 是否允许覆盖文件,0表示允许,1表示不允许,可选 184 | * @param {Function} callback 用户上传完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 185 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 186 | */ 187 | function upload_slice(filePath, bucket, dstpath, bizattr, slice_size, session, insertOnly, callback) { 188 | 189 | bucket = bucket.strip(); 190 | dstpath = fixPath(dstpath); 191 | slice_size = getSliceSize(slice_size); 192 | if (typeof bizattr === 'function') { 193 | callback = bizattr; 194 | bizattr = null; 195 | } else if (typeof slice_size === 'function') { 196 | callback = slice_size; 197 | slice_size = null; 198 | } else if (typeof session === 'function') { 199 | callback = session; 200 | session = null; 201 | } else if (typeof insertOnly === 'function') { 202 | callback = insertOnly; 203 | insertOnly = null; 204 | } else { 205 | callback = callback || function(ret){ console.log(ret); }; 206 | } 207 | 208 | upload_prepare(filePath, bucket, dstpath, bizattr, slice_size, session, insertOnly, function (rsp){ 209 | if (rsp['code'] != 0) { 210 | return callback(rsp); 211 | } 212 | /*秒传命中*/ 213 | if (rsp.hasOwnProperty('data') && rsp['data'].hasOwnProperty('url')) { 214 | return callback(rsp); 215 | } 216 | var offset = 0; 217 | var data = rsp['data']; 218 | if (data.hasOwnProperty('slice_size')) { 219 | slice_size = data['slice_size']; 220 | } 221 | if (data.hasOwnProperty('offset')) { 222 | offset = data['offset']; 223 | } 224 | if (data.hasOwnProperty('session')) { 225 | session = data['session']; 226 | } 227 | var stats = fs.statSync(filePath); 228 | var size = stats["size"]; 229 | var retry = 0; 230 | var func_upload = function (cb) { 231 | if (size > offset) { 232 | var length = (offset+slice_size>size ? size-offset : slice_size); 233 | upload_data(bucket,dstpath,filePath,offset,length,session,insertOnly,function (ret){ 234 | if (ret['code'] != 0) { 235 | if (retry < 3) { 236 | retry ++; 237 | return func_upload(); 238 | } 239 | return callback(ret); 240 | } 241 | if (ret.hasOwnProperty('data') && ret['data'].hasOwnProperty('url')) { 242 | return callback(ret); 243 | } 244 | offset += slice_size; 245 | retry = 0; 246 | func_upload(); 247 | }); 248 | } 249 | } 250 | func_upload(); 251 | }); 252 | } 253 | 254 | function upload_prepare(filePath, bucket, dstpath, bizattr, slice_size, session, insertOnly, callback) { 255 | var isExists = fs.existsSync(filePath); 256 | if (isExists && typeof callback === 'function') { 257 | var expired = parseInt(Date.now() / 1000) + conf.EXPIRED_SECONDS; 258 | var sign = auth.signMore(bucket, expired); 259 | var url = generateResUrl(bucket, dstpath); 260 | var urlInfo = urlM.parse(url); 261 | 262 | var sha = crypto.createHash('sha1'); 263 | var fsRS = fs.createReadStream(filePath); 264 | fsRS.on('data', function(d) { sha.update(d); }); 265 | 266 | fsRS.on('end', function() { 267 | var form = formstream() 268 | .field('op', 'upload_slice') 269 | .field('sha', sha.digest('hex')); 270 | 271 | var stats = fs.statSync(filePath); 272 | var fileSizeInBytes = stats["size"]; 273 | form.field('filesize', fileSizeInBytes.toString()); 274 | 275 | if (bizattr) { 276 | form.field('biz_attr', bizattr.toString()); 277 | } 278 | if (slice_size) { 279 | form.field('slice_size', slice_size.toString()); 280 | } 281 | if (session) { 282 | form.field('session', session.toString()); 283 | } 284 | if (insertOnly>=0) { 285 | form.field('insertOnly', insertOnly); 286 | } 287 | 288 | 289 | 290 | var headers = form.headers(); 291 | headers['Authorization'] = sign; 292 | headers['User-Agent'] = conf.USER_AGENT(); 293 | 294 | var options = { 295 | protocol: urlInfo.protocol, 296 | hostname: urlInfo.hostname, 297 | port: urlInfo.port, 298 | path: urlInfo.path, 299 | method: 'POST', 300 | headers: headers 301 | }; 302 | 303 | var req = buildRequest(options, callback); 304 | req && form.pipe(req); 305 | }); 306 | } else { 307 | // error, file not exists 308 | callback({'code':COS_PARAMS_ERROR, 'message':'file '+filePath+' not exists or params error', 'data':{}}); 309 | } 310 | } 311 | 312 | 313 | function upload_data(bucket, dstpath, filePath, offset, length, session, insertOnly, callback) { 314 | var expired = parseInt(Date.now() / 1000) + conf.EXPIRED_SECONDS; 315 | var sign = auth.signMore(bucket, expired); 316 | var url = generateResUrl(bucket, dstpath); 317 | var urlInfo = urlM.parse(url); 318 | var form = formstream() 319 | .field('op', 'upload_slice') 320 | .field('session', session.toString()) 321 | .field('offset', offset.toString()); 322 | if(insertOnly>=0){ 323 | form.field('insertOnly',insertOnly); 324 | } 325 | var fstream = fs.createReadStream(filePath, {start:offset, end:offset+length-1}); 326 | form.stream('filecontent', fstream, filePath, length); 327 | 328 | var headers = form.headers(); 329 | headers['Authorization'] = sign; 330 | headers['User-Agent'] = conf.USER_AGENT(); 331 | 332 | var options = { 333 | protocol: urlInfo.protocol, 334 | hostname: urlInfo.hostname, 335 | port: urlInfo.port, 336 | path: urlInfo.path, 337 | method: 'POST', 338 | headers: headers 339 | }; 340 | 341 | var req = buildRequest(options, callback); 342 | req && form.pipe(req); 343 | } 344 | 345 | /** 346 | * 处理路径参数 347 | * @param {string} path 目录路径,必选参数 348 | * @param {string} type 路径类型,可选参数,默认值为'file',如果是目录需要设置为'folder' 349 | * return {string} path 返回处理后的路径 350 | */ 351 | function fixPath(path, type){ 352 | if(!path){ 353 | return; 354 | } 355 | if(type=='folder'){ 356 | path = encodeURIComponent(path.strip() + '/').replace(/%2F/g,'/'); 357 | }else{ 358 | path = encodeURIComponent(path.strip()).replace(/%2F/g,'/'); 359 | } 360 | return path; 361 | } 362 | 363 | /** 364 | * 处理文件夹路径 365 | * @param {string} path 目录路径,必选参数,不可以包含例如: '?' , '*' , ':' , '|' , '\' , '<' , '>' , '"' 366 | * return {boolean} res 返回处理后的结果,true表示OK false表示路径不合法 367 | */ 368 | function checkFolderName(path){ 369 | var regForbidden = /[?*:|\\\\<>"]/; 370 | 371 | if(regForbidden.test(path)) { 372 | return false; 373 | }else{ 374 | return true; 375 | } 376 | } 377 | 378 | /** 379 | * 创建目录 380 | * @param {string} bucket bucket名称,必选参数 381 | * @param {string} path 目录路径,必须以'/'结尾,必选参数 382 | * @param {string} bizattr 目录属性,业务端维护,可选参数 383 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 384 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 385 | */ 386 | function createFolder(bucket, path, bizattr, callback) { 387 | 388 | if (typeof bizattr === 'function') { 389 | callback = bizattr; 390 | bizattr = null; 391 | } 392 | bizattr = bizattr || ''; 393 | callback = callback || function(ret){console.log(ret)}; 394 | 395 | if (typeof callback === 'function') { 396 | bucket = bucket.strip(); 397 | path = fixPath(path,'folder'); 398 | if(!checkFolderName(path)){ 399 | callback({'code':COS_FOLDER_ERROR, 'message':'folder name error', 'data':{}}); 400 | return; 401 | } 402 | var expired = parseInt(Date.now() / 1000) + conf.EXPIRED_SECONDS; 403 | var sign = auth.signMore(bucket, expired); 404 | var url = generateResUrl(bucket, path); 405 | var urlInfo = urlM.parse(url); 406 | 407 | var data = JSON.stringify({'op':'create','biz_attr':bizattr.toString()}); 408 | 409 | var headers = {}; 410 | headers['Authorization'] = sign; 411 | headers['Content-Type'] = 'application/json'; 412 | headers['Content-Length'] = data.length; 413 | headers['User-Agent'] = conf.USER_AGENT(); 414 | 415 | var options = { 416 | protocol: urlInfo.protocol, 417 | hostname: urlInfo.hostname, 418 | port: urlInfo.port, 419 | path: urlInfo.path, 420 | method: 'POST', 421 | headers: headers 422 | }; 423 | 424 | var req = buildRequest(options, callback); 425 | req && req.end(data); 426 | } else { 427 | // error 428 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 429 | } 430 | } 431 | 432 | 433 | /** 434 | * 目录列表 435 | * @param {string} bucket bucket名称,必选参数 436 | * @param {string} path 目录/文件路径,目录必须以'/'结尾,文件不能以'/'结尾,必选参数 437 | * @param {int} num 拉取的总数,可选参数,默认20 438 | * @param {string} pattern 可选参数,可选值为eListBoth, ListDirOnly, eListFileOnly 默认eListBoth 439 | * @param {int} order 可选参数,默认正序(=0), 填1为反序,需要翻页时,正序时0代表下一页,1代表上一页。反续时1代表下一页,0代表上一页。 440 | * @param {string} context 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 441 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 442 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 443 | */ 444 | function list(bucket, path, num, pattern, order, context, callback) { 445 | bucket = bucket.strip(); 446 | path = fixPath(path,'folder'); 447 | listFiles(bucket, path, num, pattern, order, context, callback); 448 | } 449 | 450 | 451 | /** 452 | * 目录列表,前缀搜索 453 | * @param {string} bucket bucket名称,必选参数 454 | * @param {string} path 目录/文件路径,目录必须以'/'结尾,文件不能以'/'结尾,必选参数 455 | * @param {int} num 拉取的总数,可选参数,默认20 456 | * @param {string} pattern 可选参数,可选值为eListBoth, ListDirOnly, eListFileOnly 默认eListBoth 457 | * @param {int} order 可选参数,默认正序(=0), 填1为反序,需要翻页时,正序时0代表下一页,1代表上一页。反续时1代表下一页,0代表上一页。 458 | * @param {string} context 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 459 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 460 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 461 | */ 462 | function listFiles(bucket, path, num, pattern, order, context, callback) { 463 | if (typeof num === 'function') { 464 | callback = num; 465 | num = null; 466 | } else if (typeof pattern === 'function') { 467 | callback = pattern; 468 | pattern = null; 469 | } else if (typeof order === 'function') { 470 | callback = order; 471 | order = null; 472 | } else if (typeof context === 'function') { 473 | callback = context; 474 | context = null; 475 | } 476 | num = num || 20; 477 | pattern = pattern || 'eListBoth'; 478 | order = order || 0; 479 | context = encodeURIComponent(context || ''); 480 | callback = callback || function(ret){console.log(ret)}; 481 | 482 | if (typeof callback === 'function') { 483 | var expired = parseInt(Date.now() / 1000) + conf.EXPIRED_SECONDS; 484 | var sign = auth.signMore(bucket, expired); 485 | var url = generateResUrl(bucket, path); 486 | var urlInfo = urlM.parse(url); 487 | 488 | var headers = {}; 489 | headers['Authorization'] = sign; 490 | headers['User-Agent'] = conf.USER_AGENT(); 491 | 492 | var options = { 493 | protocol: urlInfo.protocol, 494 | hostname: urlInfo.hostname, 495 | port: urlInfo.port, 496 | path: urlInfo.path+'?op=list&num='+num+'&pattern='+pattern+'&order='+order+'&context='+context, 497 | method: 'GET', 498 | headers: headers 499 | }; 500 | 501 | var req = buildRequest(options, callback); 502 | 503 | req && req.end(); 504 | 505 | } else { 506 | // error 507 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 508 | } 509 | } 510 | 511 | /** 512 | * 更新目录 513 | * @param {string} bucket bucket名称,必选参数 514 | * @param {string} path 目录路径,必须以'/'结尾,必选参数 515 | * @param {string} bizattr 目录属性,业务端维护,可选参数 516 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 517 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 518 | */ 519 | function updateFolder(bucket, path, bizattr, callback) { 520 | bucket = bucket.strip(); 521 | path = fixPath(path,'folder'); 522 | update(bucket, path, bizattr, callback); 523 | } 524 | 525 | 526 | /** 527 | * 更新 528 | * @param {string} bucket bucket名称,必选参数 529 | * @param {string} path 目录/文件路径,目录必须以'/'结尾,文件不能以'/'结尾,必选参数 530 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 531 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 532 | */ 533 | function update(bucket, path, bizattr, callback) { 534 | 535 | bizattr = bizattr || ''; 536 | callback = callback || function(ret){console.log(ret)}; 537 | 538 | if (typeof callback === 'function') { 539 | var sign = auth.signOnce(bucket, '/'+conf.APPID+'/'+bucket+'/'+path); 540 | var url = generateResUrl(bucket, path); 541 | var urlInfo = urlM.parse(url); 542 | 543 | var data = {"op":"update"}; 544 | 545 | var flag = 0; 546 | 547 | if(bizattr){ 548 | data['biz_attr'] = bizattr; 549 | flag = flag | 0x01; 550 | } 551 | 552 | if(flag!=0 && flag!=1){ 553 | data['flag'] = flag; 554 | } 555 | 556 | data = JSON.stringify(data); 557 | 558 | 559 | var headers = {}; 560 | headers['Authorization'] = sign; 561 | headers['Content-Type'] = 'application/json'; 562 | headers['User-Agent'] = conf.USER_AGENT(); 563 | headers['Content-Length'] = data.length; 564 | 565 | var options = { 566 | protocol: urlInfo.protocol, 567 | hostname: urlInfo.hostname, 568 | port: urlInfo.port, 569 | path: urlInfo.path, 570 | method: 'POST', 571 | headers: headers 572 | }; 573 | 574 | var req = buildRequest(options, callback); 575 | 576 | req && req.end(data); 577 | 578 | 579 | } else { 580 | // error 581 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 582 | } 583 | } 584 | 585 | 586 | /** 587 | * 查询目录 588 | * @param {string} bucket bucket名称,必选参数 589 | * @param {string} path 目录路径,必须以'/'结尾,必选参数 590 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 591 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 592 | */ 593 | function statFolder(bucket, path, callback) { 594 | bucket = bucket.strip(); 595 | path = fixPath(path, 'folder'); 596 | stat(bucket, path, callback); 597 | } 598 | 599 | /** 600 | * 查询文件或目录属性 601 | * @param {string} bucket bucket名称,必选参数 602 | * @param {string} path 目录/文件路径,目录必须以'/'结尾,文件不能以'/'结尾,必选参数 603 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 604 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 605 | */ 606 | function stat(bucket, path, callback) { 607 | callback = callback || function(ret){console.log(ret)}; 608 | 609 | if (typeof callback === 'function') { 610 | var expired = parseInt(Date.now() / 1000) + conf.EXPIRED_SECONDS; 611 | var sign = auth.signMore(bucket, expired); 612 | var url = generateResUrl(bucket, path); 613 | var urlInfo = urlM.parse(url); 614 | 615 | var headers = {}; 616 | headers['Authorization'] = sign; 617 | headers['User-Agent'] = conf.USER_AGENT(); 618 | 619 | var options = { 620 | protocol: urlInfo.protocol, 621 | hostname: urlInfo.hostname, 622 | port: urlInfo.port, 623 | path: urlInfo.path+'?op=stat', 624 | method: 'GET', 625 | headers: headers 626 | }; 627 | 628 | var req = buildRequest(options, callback); 629 | req && req.end(); 630 | 631 | } else { 632 | // error 633 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 634 | } 635 | } 636 | 637 | 638 | /** 639 | * 删除目录 640 | * @param {string} bucket bucket名称,必选参数 641 | * @param {string} path 目录路径,必须以'/'结尾,必选参数 642 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 643 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 644 | */ 645 | function deleteFolder(bucket, path, callback) { 646 | bucket = bucket.strip(); 647 | path = fixPath(path,'folder'); 648 | del(bucket, path, callback); 649 | } 650 | 651 | 652 | /** 653 | * 删除文件/目录 654 | * @param {string} bucket bucket名称,必选参数 655 | * @param {string} path 目录/文件路径,目录必须以'/'结尾,文件不能以'/'结尾,必选参数 656 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 657 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 658 | */ 659 | function del(bucket, path, callback) { 660 | 661 | callback = callback || function(ret){console.log(ret)}; 662 | 663 | if (path == '' || path == '/' || typeof callback === 'function') { 664 | var sign = auth.signOnce(bucket, '/'+conf.APPID+'/'+bucket+'/'+path); 665 | var url = generateResUrl(bucket, path); 666 | var urlInfo = urlM.parse(url); 667 | 668 | var data = '{"op":"delete"}'; 669 | 670 | var headers = {}; 671 | headers['Authorization'] = sign; 672 | headers['Content-Type'] = 'application/json'; 673 | headers['User-Agent'] = conf.USER_AGENT(); 674 | headers['Content-Length'] = data.length; 675 | 676 | 677 | var options = { 678 | protocol: urlInfo.protocol, 679 | hostname: urlInfo.hostname, 680 | port: urlInfo.port, 681 | path: urlInfo.path, 682 | method: 'POST', 683 | headers: headers 684 | }; 685 | 686 | var req = buildRequest(options, callback); 687 | req && req.end(data); 688 | 689 | } else { 690 | // error 691 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 692 | } 693 | } 694 | 695 | 696 | /** 697 | * 查询文件 698 | * @param {string} bucket bucket名称,必选参数 699 | * @param {string} path 文件路径,必选参数 700 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 701 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 702 | */ 703 | function statFile(bucket, path, callback) { 704 | bucket = bucket.strip(); 705 | path = fixPath(path); 706 | stat(bucket, path, callback); 707 | } 708 | 709 | 710 | /** 711 | * 删除文件 712 | * @param {string} bucket bucket名称,必选参数 713 | * @param {string} path 文件路径,必选参数 714 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 715 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 716 | */ 717 | function deleteFile(bucket, path, callback) { 718 | bucket = bucket.strip(); 719 | path = fixPath(path); 720 | del(bucket, path, callback); 721 | } 722 | 723 | 724 | 725 | /** 726 | * 更新文件 727 | * @param {string} bucket bucket名称,必选参数 728 | * @param {string} path 文件路径,必选参数 729 | * @param {string} bizattr 目录属性,业务端维护,可选参数 730 | * @param {string} authority 权限类型,可选参数,可选值为eInvalid,eWRPrivate,eWPrivateRPublic 731 | * 文件可以与bucket拥有不同的权限类型,已经设置过权限的文件如果想要撤销,直接赋值为eInvalid,则会采用bucket的权限 732 | * @param {Array} custom_headers 自定义header,可选参数 733 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 734 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 735 | */ 736 | function updateFile(bucket, path, bizattr, authority, custom_headers, callback) { 737 | bucket = bucket.strip(); 738 | path = fixPath(path); 739 | if (typeof bizattr === 'function') { 740 | callback = bizattr; 741 | bizattr = null; 742 | } else if (typeof authority === 'function') { 743 | callback = authority; 744 | authority = null; 745 | } else if (typeof custom_headers === 'function') { 746 | callback = custom_headers; 747 | custom_headers = null; 748 | } else { 749 | callback = callback || function(ret){ console.log(ret); }; 750 | } 751 | 752 | if (typeof callback === 'function') { 753 | var sign = auth.signOnce(bucket, '/'+conf.APPID+'/'+bucket+'/'+path); 754 | var url = generateResUrl(bucket, path); 755 | var urlInfo = urlM.parse(url); 756 | 757 | var data = {'op':'update'}; 758 | 759 | var flag = 0; 760 | 761 | if(bizattr){ 762 | data['biz_attr'] = bizattr; 763 | flag = flag | 0x01; 764 | } 765 | 766 | if(authority){ 767 | data['authority'] = authority; 768 | flag = flag | 0x80; 769 | } 770 | 771 | if(custom_headers){ 772 | 773 | custom_headers = JSON.stringify(custom_headers); 774 | data['custom_headers'] = custom_headers; 775 | flag = flag | 0x40; 776 | } 777 | 778 | if(flag!=0 && flag!=1){ 779 | data['flag'] = flag; 780 | } 781 | 782 | data = JSON.stringify(data); 783 | 784 | var headers = {}; 785 | headers['Authorization'] = sign; 786 | headers['Content-Type'] = 'application/json'; 787 | headers['User-Agent'] = conf.USER_AGENT(); 788 | headers['Content-Length'] = data.length; 789 | 790 | var options = { 791 | protocol: urlInfo.protocol, 792 | hostname: urlInfo.hostname, 793 | port: urlInfo.port, 794 | path: urlInfo.path, 795 | method: 'POST', 796 | headers: headers 797 | }; 798 | 799 | var req = buildRequest(options, callback); 800 | req && req.end(data); 801 | 802 | } else { 803 | // error 804 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 805 | } 806 | } 807 | 808 | 809 | /** 810 | * 移动文件 811 | * @param {string} bucket bucket名称,必选参数 812 | * @param {string} path 文件路径,必选参数 813 | * @param {string} destPath 目标路径,默认是当前路径,比如当前path是/123/a.txt,destPath填了456/a.txt则最终会生成/123/456.txt,必选参数 814 | * @param {string} overWrite 是否覆盖重名文件 0表示不覆盖 1表示覆盖 可选参数 815 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 816 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 817 | */ 818 | function moveFile(bucket, path, destPath, overWrite, callback) { 819 | bucket = bucket.strip(); 820 | path = fixPath(path); 821 | 822 | callback = callback || function(ret){ console.log(ret); }; 823 | 824 | if (typeof callback === 'function') { 825 | var sign = auth.signOnce(bucket, '/'+conf.APPID+'/'+bucket+'/'+path); 826 | var url = generateResUrl(bucket, path); 827 | var urlInfo = urlM.parse(url); 828 | 829 | var data = {'op':'move'}; 830 | 831 | data['dest_fileid'] = destPath; 832 | 833 | if(overWrite>=0){ 834 | data['to_over_write'] = overWrite; 835 | } 836 | 837 | data = JSON.stringify(data); 838 | 839 | var headers = {}; 840 | headers['Authorization'] = sign; 841 | headers['Content-Type'] = 'application/json'; 842 | headers['User-Agent'] = conf.USER_AGENT(); 843 | headers['Content-Length'] = data.length; 844 | 845 | var options = { 846 | protocol: urlInfo.protocol, 847 | hostname: urlInfo.hostname, 848 | port: urlInfo.port, 849 | path: urlInfo.path, 850 | method: 'POST', 851 | headers: headers 852 | }; 853 | 854 | var req = buildRequest(options, callback); 855 | req && req.end(data); 856 | 857 | } else { 858 | // error 859 | callback({'code':COS_PARAMS_ERROR, 'message':'params error', 'data':{}}); 860 | } 861 | } 862 | 863 | 864 | 865 | 866 | /** 867 | * 前缀搜索 868 | * @param {string} bucket bucket名称,必选参数 869 | * @param {string} path 目录/文件路径,目录必须以'/'结尾,文件不能以'/'结尾,必选参数 870 | * @param {string} prefix 列出含prefix此前缀的所有文件 871 | * @param {int} num 拉取的总数 872 | * @param {string} pattern 可选参数,可选值为eListBoth, ListDirOnly, eListFileOnly 默认eListBoth 873 | * @param {int} order 默认正序(=0), 填1为反序,需要翻页时,正序时0代表下一页,1代表上一页。反续时1代表下一页,0代表上一页。 874 | * @param {string} context 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 875 | * @param {Function} callback 完毕后执行的回调函数,可选,默认输出日志 格式为 function (ret) {} 876 | * 入参为ret:{'code':0,'message':'ok','data':{...}} 877 | */ 878 | function prefixSearch(bucket, path, prefix, num, pattern, order, context, callback) { 879 | bucket = bucket.strip(); 880 | path = fixPath(path); 881 | if (path == '') { 882 | path = prefix; 883 | } else { 884 | path += '/'+prefix; 885 | } 886 | 887 | listFiles(bucket, path, num, pattern, order, context, callback); 888 | } 889 | 890 | 891 | 892 | function generateResUrl(bucket, path) { 893 | return conf.API_COS_END_POINT+conf.APPID+'/'+bucket+'/'+(path=='/'?"":path); 894 | } 895 | 896 | String.prototype.strip = function(){ 897 | return this.replace(/(^\/*)|(\/*$)/g, ''); 898 | } 899 | String.prototype.lstrip = function(){ 900 | return this.replace(/(^\/*)/g, ''); 901 | } 902 | String.prototype.rstrip = function(){ 903 | return this.replace(/(\/*$)/g, ''); 904 | } 905 | 906 | exports.upload = upload; 907 | exports.upload_slice = upload_slice; 908 | exports.statFile = statFile; 909 | exports.statFolder = statFolder; 910 | exports.deleteFile = deleteFile; 911 | exports.deleteFolder = deleteFolder; 912 | exports.updateFile = updateFile; 913 | exports.updateFolder = updateFolder; 914 | exports.list = list; 915 | exports.prefixSearch = prefixSearch; 916 | exports.createFolder = createFolder; 917 | exports.moveFile = moveFile; 918 | --------------------------------------------------------------------------------