用工小程序前端代码
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.

428 lines
12 KiB

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