upyun.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. var util = require("thinkjs-util");
  2. var request = require("request");
  3. var fs = require("fs");
  4. var path = require("path");
  5. module.exports = util.Class(function () {
  6. return {
  7. /**
  8. * 接口的域名
  9. * @type {String}
  10. */
  11. domain: "v0.api.upyun.com",
  12. /**
  13. * 初始化
  14. * @param {[type]} bucketname [description]
  15. * @param {[type]} username [description]
  16. * @param {[type]} password [description]
  17. * @return {[type]} [description]
  18. */
  19. init: function (bucketname, username, password) {
  20. this.bucketname = bucketname;
  21. this.username = username;
  22. this.password = util.md5(password);
  23. },
  24. /**
  25. * 设置接口的domain
  26. * @param {[type]} domain [description]
  27. */
  28. setDomain: function (domain) {
  29. this.domain = domain;
  30. return this;
  31. },
  32. /**
  33. * 签名
  34. * @param {[type]} method [description]
  35. * @param {[type]} uri [description]
  36. * @param {[type]} date [description]
  37. * @param {[type]} length [description]
  38. * @return {[type]} [description]
  39. */
  40. sign: function (method, uri, date, length) {
  41. var sign = method + '&' + uri + '&' + date + '&' + length + '&' + this.password;
  42. return 'UpYun ' + this.username + ':' + util.md5(sign);
  43. },
  44. /**
  45. * 获取文件或者文件夹的信息
  46. * @param {[type]} file [description]
  47. * @return {[type]} [description]
  48. */
  49. getInfo: function (file) {
  50. return this.request(file, 'HEAD').then(function (response) {
  51. var headers = response.headers;
  52. return {
  53. type: headers['x-upyun-file-type'],
  54. size: headers['x-upyun-file-size'],
  55. date: headers['x-upyun-file-date']
  56. };
  57. });
  58. },
  59. /**
  60. * 查看空间占用信息
  61. * @return {[type]} [description]
  62. */
  63. getUsage: function (path) {
  64. path = path || "/";
  65. return this.request(path + "?usage").then(function (response) {
  66. return parseInt(response.body, 10);
  67. });
  68. },
  69. /**
  70. * 从返回的headers里获取图片的信息
  71. * @param {[type]} response [description]
  72. * @return {[type]} [description]
  73. */
  74. getPicInfo: function (response) {
  75. var headers = response.headers;
  76. return {
  77. width: headers['x-upyun-width'],
  78. height: headers['x-upyun-height'],
  79. frames: headers['x-upyun-frames'],
  80. type: headers['x-upyun-file-type']
  81. };
  82. },
  83. /**
  84. * 上传文件或者文件夹
  85. * @param {[type]} savePath [description]
  86. * @param {[type]} filePath [description]
  87. * @return {[type]} [description]
  88. */
  89. upload: function (savePath, filePath, headers) {
  90. var defaultHeaders = {
  91. mkdir: true
  92. };
  93. if (util.isObject(headers)) {
  94. defaultHeaders = util.extend(defaultHeaders, headers);
  95. } else if (headers) {
  96. defaultHeaders["Content-Secret"] = headers;
  97. }
  98. var self = this;
  99. //文件上传
  100. if (util.isFile(filePath)) {
  101. var stream = fs.readFileSync(filePath);
  102. var filename;
  103. if (!(/\.\w+$/.test(savePath))) {
  104. filename = filePath.split('/');
  105. filename = filename[filename.length - 1];
  106. savePath += '/' + filename;
  107. }
  108. return this.request(savePath, 'PUT', stream, defaultHeaders).then(function (response) {
  109. return self.getPicInfo(response);
  110. }).then(function (data) {
  111. if (filename) {
  112. data.filename = filename;
  113. }
  114. return data;
  115. });
  116. } else if (util.isDir(filePath)) { //文件夹上传
  117. if (savePath.slice(-1) !== '/') {
  118. savePath += '/';
  119. }
  120. if (filePath.slice(-1) !== '/') {
  121. filePath += '/';
  122. }
  123. var promises = [];
  124. var files = fs.readdirSync(filePath);
  125. files.forEach(function (item) {
  126. var nFilePath = filePath + item;
  127. var state = fs.statSync(nFilePath);
  128. if (state.isFile() || state.isDirectory()) {
  129. var promise = self.upload(savePath + item, nFilePath);
  130. promises.push(promise);
  131. }
  132. });
  133. if (promises.length) {
  134. return util.Promise.all(promises);
  135. } else {
  136. return self.mkDir(savePath);
  137. }
  138. } else { //普通内容上传
  139. return this.request(savePath, 'PUT', filePath, defaultHeaders).then(function (response) {
  140. return self.getPicInfo(response);
  141. });
  142. }
  143. },
  144. /**
  145. * 文件或者文件夹下载
  146. * @param {[type]} path [description]
  147. * @param {[type]} savePath [description]
  148. * @return {[type]} [description]
  149. */
  150. download: function (sourcePath, savePath, typeData) {
  151. sourcePath = sourcePath || "/";
  152. //if (savePath && savePath.slice(-1) !== "/") {
  153. // savePath += "/";
  154. //}
  155. var self = this;
  156. var promise = typeData ? util.getPromise(typeData) : this.getInfo(sourcePath);
  157. return promise.then(function (data) {
  158. if (data.type === 'folder') {
  159. if (sourcePath.slice(-1) !== "/") {
  160. sourcePath += "/";
  161. }
  162. return self.readDir(sourcePath).then(function (data) {
  163. var promises = [];
  164. data.forEach(function (item) {
  165. var nPath = sourcePath + item.name;
  166. var promise;
  167. //文件夹
  168. if (item.type === 'F') {
  169. promise = self.download(nPath + "/", savePath + item.name + "/", {
  170. type: 'folder'
  171. });
  172. } else if (item.type) { //文件
  173. promise = self.download(nPath, savePath, {
  174. type: 'file'
  175. });
  176. }
  177. promises.push(promise);
  178. });
  179. return util.Promise.all(promises);
  180. });
  181. } else {
  182. //单个文件
  183. return self.request(sourcePath, 'GET', '', {}, {
  184. encoding: null
  185. }).then(function (response) {
  186. if (!savePath) {
  187. return response.body;
  188. }
  189. var sourceExt = path.extname(sourcePath);
  190. var saveExt = path.extname(savePath);
  191. var fileSavePath = savePath;
  192. if (sourceExt && sourceExt === saveExt) {
  193. util.mkdir(path.dirname(savePath));
  194. } else {
  195. util.mkdir(savePath);
  196. fileSavePath = savePath + path.basename(sourcePath);
  197. }
  198. fs.writeFileSync(fileSavePath, response.body);
  199. });
  200. }
  201. });
  202. },
  203. /**
  204. * 删除文件或者文件夹
  205. * @param {[type]} path [description]
  206. * @param {[type]} force [description]
  207. * @return {[type]} [description]
  208. */
  209. rm: function (path, force) {
  210. if (!path) {
  211. return util.getPromise(new Error("path can't empty"), true);
  212. }
  213. if (path.slice(-1) !== '/') {
  214. path += '/';
  215. }
  216. var self = this;
  217. return this.getInfo(path).then(function (data) {
  218. if (data.type === 'folder') {
  219. if (!force) {
  220. return self.request(path, 'DELETE').then(function (response) {
  221. return response.body;
  222. });
  223. }
  224. return self.readDir(path).then(function (data) {
  225. var promises = [];
  226. data.forEach(function (item) {
  227. var nPath = path + item.name;
  228. var promise;
  229. //文件夹
  230. if (item.type === 'F') {
  231. promise = self.rm(nPath + "/", true);
  232. } else if (item.type) { //文件
  233. promise = self.rm(nPath);
  234. }
  235. promises.push(promise);
  236. });
  237. if (promises.length) {
  238. return util.Promise.all(promises);
  239. }
  240. }).then(function () {
  241. return self.rm(path, false);
  242. });
  243. } else {
  244. return self.request(path, 'DELETE').then(function (response) {
  245. return response.body;
  246. });
  247. }
  248. });
  249. },
  250. /**
  251. * 递归创建目录
  252. * @param {[type]} path [description]
  253. * @return {[type]} [description]
  254. */
  255. mkDir: function (path) {
  256. return this.request(path, 'PUT', '', {
  257. mkdir: true,
  258. folder: true
  259. }).then(function (response) {
  260. return response.body;
  261. });
  262. },
  263. /**
  264. * 读取目录下的文件和子目录
  265. * @param {[type]} dir [description]
  266. * @return {[type]} [description]
  267. */
  268. readDir: function (path, recursive) {
  269. path = path || "/";
  270. if (path.slice(-1) !== '/') {
  271. path += '/';
  272. }
  273. var self = this;
  274. return this.request(path, "GET").then(function (response) {
  275. var dirs = response.body.split("\n");
  276. var result = [];
  277. var promises = [];
  278. for (var i = 0; i < dirs.length; i++) {
  279. var dir = dirs[i];
  280. var attrs = dir.split("\t");
  281. dir = {
  282. name: attrs[0],
  283. type: attrs[1],
  284. size: attrs[2],
  285. time: attrs[3]
  286. };
  287. if (recursive && dir.type === 'F') {
  288. var promise = self.readDir(path + dir.name, true).then(function (data) {
  289. dir.children = data;
  290. });
  291. promises.push(promise);
  292. }
  293. result.push(dir);
  294. }
  295. if (promises.length) {
  296. return util.Promise.all(promises).then(function () {
  297. return result;
  298. });
  299. } else {
  300. return result;
  301. }
  302. });
  303. },
  304. /**
  305. * 请求数据
  306. * @param {[type]} uri [description]
  307. * @param {[type]} method [description]
  308. * @param {[type]} data [description]
  309. * @param {[type]} headers [description]
  310. * @param {[type]} options [description]
  311. * @return {[type]} [description]
  312. */
  313. request: function (uri, method, data, headers, options) {
  314. uri = "/" + this.bucketname + uri;
  315. method = method || "GET";
  316. headers = headers || {};
  317. var length = 0;
  318. if (data) {
  319. length = !util.isBuffer(data) ? Buffer.byteLength(data) : data.length;
  320. }
  321. var date = (new Date()).toUTCString();
  322. var Authorization = this.sign(method, uri, date, length);
  323. headers = util.extend(headers, {
  324. 'Content-Length': length,
  325. 'Date': date,
  326. 'Authorization': Authorization
  327. });
  328. var deferred = util.getDefer();
  329. var opts = util.extend({
  330. url: "http://" + this.domain + uri,
  331. method: method,
  332. body: data || "",
  333. headers: headers
  334. }, options);
  335. request(opts, function (error, response, body) {
  336. if (error || response.statusCode !== 200) {
  337. deferred.reject(error || "statusCode: " + response.statusCode + "; body: " + body);
  338. } else {
  339. deferred.resolve(response);
  340. }
  341. });
  342. return deferred.promise;
  343. }
  344. };
  345. });