租房小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

421 lines
12 KiB

3 months ago
  1. const debug = require('debug')('ali-oss:object');
  2. const fs = require('fs');
  3. const is = require('is-type-of');
  4. const copy = require('copy-to');
  5. const path = require('path');
  6. const mime = require('mime');
  7. const callback = require('./common/callback');
  8. const { Transform } = require('stream');
  9. const pump = require('pump');
  10. const { isBuffer } = require('./common/utils/isBuffer');
  11. const { retry } = require('./common/utils/retry');
  12. const { obj2xml } = require('./common/utils/obj2xml');
  13. const proto = exports;
  14. /**
  15. * Object operations
  16. */
  17. /**
  18. * append an object from String(file path)/Buffer/ReadableStream
  19. * @param {String} name the object key
  20. * @param {Mixed} file String(file path)/Buffer/ReadableStream
  21. * @param {Object} options
  22. * @return {Object}
  23. */
  24. proto.append = async function append(name, file, options) {
  25. options = options || {};
  26. if (options.position === undefined) options.position = '0';
  27. options.subres = {
  28. append: '',
  29. position: options.position
  30. };
  31. options.method = 'POST';
  32. const result = await this.put(name, file, options);
  33. result.nextAppendPosition = result.res.headers['x-oss-next-append-position'];
  34. return result;
  35. };
  36. /**
  37. * put an object from String(file path)/Buffer/ReadableStream
  38. * @param {String} name the object key
  39. * @param {Mixed} file String(file path)/Buffer/ReadableStream
  40. * @param {Object} options
  41. * {Object} [options.callback] The callback parameter is composed of a JSON string encoded in Base64
  42. * {String} options.callback.url the OSS sends a callback request to this URL
  43. * {String} [options.callback.host] The host header value for initiating callback requests
  44. * {String} options.callback.body The value of the request body when a callback is initiated
  45. * {String} [options.callback.contentType] The Content-Type of the callback requests initiated
  46. * {Boolean} [options.callback.callbackSNI] Whether OSS sends SNI to the origin address specified by callbackUrl when a callback request is initiated from the client
  47. * {Object} [options.callback.customValue] Custom parameters are a map of key-values, e.g:
  48. * customValue = {
  49. * key1: 'value1',
  50. * key2: 'value2'
  51. * }
  52. * @return {Object}
  53. */
  54. proto.put = async function put(name, file, options) {
  55. let content;
  56. options = options || {};
  57. name = this._objectName(name);
  58. if (isBuffer(file)) {
  59. content = file;
  60. } else if (is.string(file)) {
  61. const stats = fs.statSync(file);
  62. if (!stats.isFile()) {
  63. throw new Error(`${file} is not file`);
  64. }
  65. options.mime = options.mime || mime.getType(path.extname(file));
  66. options.contentLength = await this._getFileSize(file);
  67. const getStream = () => fs.createReadStream(file);
  68. const putStreamStb = (objectName, makeStream, configOption) => {
  69. return this.putStream(objectName, makeStream(), configOption);
  70. };
  71. return await retry(putStreamStb, this.options.retryMax, {
  72. errorHandler: err => {
  73. const _errHandle = _err => {
  74. const statusErr = [-1, -2].includes(_err.status);
  75. const requestErrorRetryHandle = this.options.requestErrorRetryHandle || (() => true);
  76. return statusErr && requestErrorRetryHandle(_err);
  77. };
  78. if (_errHandle(err)) return true;
  79. return false;
  80. }
  81. })(name, getStream, options);
  82. } else if (is.readableStream(file)) {
  83. return await this.putStream(name, file, options);
  84. } else {
  85. throw new TypeError('Must provide String/Buffer/ReadableStream for put.');
  86. }
  87. options.headers = options.headers || {};
  88. this._convertMetaToHeaders(options.meta, options.headers);
  89. const method = options.method || 'PUT';
  90. const params = this._objectRequestParams(method, name, options);
  91. callback.encodeCallback(params, options);
  92. params.mime = options.mime;
  93. params.content = content;
  94. params.successStatuses = [200];
  95. const result = await this.request(params);
  96. const ret = {
  97. name,
  98. url: this._objectUrl(name),
  99. res: result.res
  100. };
  101. if (params.headers && params.headers['x-oss-callback']) {
  102. ret.data = JSON.parse(result.data.toString());
  103. }
  104. return ret;
  105. };
  106. /**
  107. * put an object from ReadableStream. If `options.contentLength` is
  108. * not provided, chunked encoding is used.
  109. * @param {String} name the object key
  110. * @param {Readable} stream the ReadableStream
  111. * @param {Object} options
  112. * @return {Object}
  113. */
  114. proto.putStream = async function putStream(name, stream, options) {
  115. options = options || {};
  116. options.headers = options.headers || {};
  117. name = this._objectName(name);
  118. if (options.contentLength) {
  119. options.headers['Content-Length'] = options.contentLength;
  120. } else {
  121. options.headers['Transfer-Encoding'] = 'chunked';
  122. }
  123. this._convertMetaToHeaders(options.meta, options.headers);
  124. const method = options.method || 'PUT';
  125. const params = this._objectRequestParams(method, name, options);
  126. callback.encodeCallback(params, options);
  127. params.mime = options.mime;
  128. const transform = new Transform();
  129. // must remove http stream header for signature
  130. transform._transform = function _transform(chunk, encoding, done) {
  131. this.push(chunk);
  132. done();
  133. };
  134. params.stream = pump(stream, transform);
  135. params.successStatuses = [200];
  136. const result = await this.request(params);
  137. const ret = {
  138. name,
  139. url: this._objectUrl(name),
  140. res: result.res
  141. };
  142. if (params.headers && params.headers['x-oss-callback']) {
  143. ret.data = JSON.parse(result.data.toString());
  144. }
  145. return ret;
  146. };
  147. proto.getStream = async function getStream(name, options) {
  148. options = options || {};
  149. if (options.process) {
  150. options.subres = options.subres || {};
  151. options.subres['x-oss-process'] = options.process;
  152. }
  153. const params = this._objectRequestParams('GET', name, options);
  154. params.customResponse = true;
  155. params.successStatuses = [200, 206, 304];
  156. const result = await this.request(params);
  157. return {
  158. stream: result.res,
  159. res: {
  160. status: result.status,
  161. headers: result.headers
  162. }
  163. };
  164. };
  165. proto.putMeta = async function putMeta(name, meta, options) {
  166. return await this.copy(name, name, {
  167. meta: meta || {},
  168. timeout: options && options.timeout,
  169. ctx: options && options.ctx
  170. });
  171. };
  172. proto.list = async function list(query, options) {
  173. // prefix, marker, max-keys, delimiter
  174. const params = this._objectRequestParams('GET', '', options);
  175. params.query = query;
  176. params.xmlResponse = true;
  177. params.successStatuses = [200];
  178. const result = await this.request(params);
  179. let objects = result.data.Contents || [];
  180. const that = this;
  181. if (objects) {
  182. if (!Array.isArray(objects)) {
  183. objects = [objects];
  184. }
  185. objects = objects.map(obj => ({
  186. name: obj.Key,
  187. url: that._objectUrl(obj.Key),
  188. lastModified: obj.LastModified,
  189. etag: obj.ETag,
  190. type: obj.Type,
  191. size: Number(obj.Size),
  192. storageClass: obj.StorageClass,
  193. owner: {
  194. id: obj.Owner.ID,
  195. displayName: obj.Owner.DisplayName
  196. }
  197. }));
  198. }
  199. let prefixes = result.data.CommonPrefixes || null;
  200. if (prefixes) {
  201. if (!Array.isArray(prefixes)) {
  202. prefixes = [prefixes];
  203. }
  204. prefixes = prefixes.map(item => item.Prefix);
  205. }
  206. return {
  207. res: result.res,
  208. objects,
  209. prefixes,
  210. nextMarker: result.data.NextMarker || null,
  211. isTruncated: result.data.IsTruncated === 'true'
  212. };
  213. };
  214. proto.listV2 = async function listV2(query = {}, options = {}) {
  215. const continuation_token = query['continuation-token'] || query.continuationToken;
  216. delete query['continuation-token'];
  217. delete query.continuationToken;
  218. if (continuation_token) {
  219. options.subres = Object.assign(
  220. {
  221. 'continuation-token': continuation_token
  222. },
  223. options.subres
  224. );
  225. }
  226. const params = this._objectRequestParams('GET', '', options);
  227. params.query = Object.assign({ 'list-type': 2 }, query);
  228. delete params.query['continuation-token'];
  229. delete query.continuationToken;
  230. params.xmlResponse = true;
  231. params.successStatuses = [200];
  232. const result = await this.request(params);
  233. let objects = result.data.Contents || [];
  234. const that = this;
  235. if (objects) {
  236. if (!Array.isArray(objects)) {
  237. objects = [objects];
  238. }
  239. objects = objects.map(obj => {
  240. let owner = null;
  241. if (obj.Owner) {
  242. owner = {
  243. id: obj.Owner.ID,
  244. displayName: obj.Owner.DisplayName
  245. };
  246. }
  247. return {
  248. name: obj.Key,
  249. url: that._objectUrl(obj.Key),
  250. lastModified: obj.LastModified,
  251. etag: obj.ETag,
  252. type: obj.Type,
  253. size: Number(obj.Size),
  254. storageClass: obj.StorageClass,
  255. owner
  256. };
  257. });
  258. }
  259. let prefixes = result.data.CommonPrefixes || null;
  260. if (prefixes) {
  261. if (!Array.isArray(prefixes)) {
  262. prefixes = [prefixes];
  263. }
  264. prefixes = prefixes.map(item => item.Prefix);
  265. }
  266. return {
  267. res: result.res,
  268. objects,
  269. prefixes,
  270. isTruncated: result.data.IsTruncated === 'true',
  271. keyCount: +result.data.KeyCount,
  272. continuationToken: result.data.ContinuationToken || null,
  273. nextContinuationToken: result.data.NextContinuationToken || null
  274. };
  275. };
  276. /**
  277. * Restore Object
  278. * @param {String} name the object key
  279. * @param {Object} options {type : Archive or ColdArchive}
  280. * @returns {{res}}
  281. */
  282. proto.restore = async function restore(name, options = { type: 'Archive' }) {
  283. options = options || {};
  284. options.subres = Object.assign({ restore: '' }, options.subres);
  285. if (options.versionId) {
  286. options.subres.versionId = options.versionId;
  287. }
  288. const params = this._objectRequestParams('POST', name, options);
  289. if (options.type === 'ColdArchive') {
  290. const paramsXMLObj = {
  291. RestoreRequest: {
  292. Days: options.Days ? options.Days : 2,
  293. JobParameters: {
  294. Tier: options.JobParameters ? options.JobParameters : 'Standard'
  295. }
  296. }
  297. };
  298. params.content = obj2xml(paramsXMLObj, {
  299. headers: true
  300. });
  301. params.mime = 'xml';
  302. }
  303. params.successStatuses = [202];
  304. const result = await this.request(params);
  305. return {
  306. res: result.res
  307. };
  308. };
  309. proto._objectUrl = function _objectUrl(name) {
  310. return this._getReqUrl({ bucket: this.options.bucket, object: name });
  311. };
  312. /**
  313. * generator request params
  314. * @return {Object} params
  315. *
  316. * @api private
  317. */
  318. proto._objectRequestParams = function (method, name, options) {
  319. if (!this.options.bucket && !this.options.cname) {
  320. throw new Error('Please create a bucket first');
  321. }
  322. options = options || {};
  323. name = this._objectName(name);
  324. const params = {
  325. object: name,
  326. bucket: this.options.bucket,
  327. method,
  328. subres: options && options.subres,
  329. additionalHeaders: options && options.additionalHeaders,
  330. timeout: options && options.timeout,
  331. ctx: options && options.ctx
  332. };
  333. if (options.headers) {
  334. params.headers = {};
  335. copy(options.headers).to(params.headers);
  336. }
  337. return params;
  338. };
  339. proto._objectName = function (name) {
  340. return name.replace(/^\/+/, '');
  341. };
  342. proto._statFile = function (filepath) {
  343. return new Promise((resolve, reject) => {
  344. fs.stat(filepath, (err, stats) => {
  345. if (err) {
  346. reject(err);
  347. } else {
  348. resolve(stats);
  349. }
  350. });
  351. });
  352. };
  353. proto._convertMetaToHeaders = function (meta, headers) {
  354. if (!meta) {
  355. return;
  356. }
  357. Object.keys(meta).forEach(k => {
  358. headers[`x-oss-meta-${k}`] = meta[k];
  359. });
  360. };
  361. proto._deleteFileSafe = function (filepath) {
  362. return new Promise(resolve => {
  363. fs.exists(filepath, exists => {
  364. if (!exists) {
  365. resolve();
  366. } else {
  367. fs.unlink(filepath, err => {
  368. if (err) {
  369. debug('unlink %j error: %s', filepath, err);
  370. }
  371. resolve();
  372. });
  373. }
  374. });
  375. });
  376. };