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

7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 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. };