diff --git a/src/index.js b/src/index.js index e69de29..d9848b5 100644 --- a/src/index.js +++ b/src/index.js @@ -0,0 +1,174 @@ +/** + * 阿里云oss, 自签名 + * @author yutent + * @date 2020/01/18 14:28:47 + */ + +import { hmac, base64encode } from 'crypto' +import xml2js from './lib/xml2js.js' +import { + APP_ID, + APP_KEY, + MIME_TYPES, + DEFAULT_MIME_TYPE +} from './lib/constants.js' +import { getMimeType, fixFile, str2sign } from './lib/helper.js' + +export default class Alioss { + #bucket = '' + #domain = '' + #api = '' + + constructor(bucket, domain, region) { + this.#bucket = bucket + this.#domain = domain + this.#api = `https://${bucket}.${region}.aliyuncs.com` + } + + // 授权签名, 用于临时下载私有bucket的文件 + auth(key) { + var time = Math.floor(Date.now() / 1000) + 1800 // 半小时内 + + return hmac( + 'SHA-1', + `GET\n\n\n${time}\n/${this.#bucket}/${key}`, + APP_KEY, + 'base64' + ).then(signature => { + return `?OSSAccessKeyId=${APP_ID}&Expires=${time}&Signature=${encodeURIComponent( + signature + )}` + }) + } + + /** + * {生成签名, 需传入 , 大小限制} + * dir: 上传目录名 + * size: 大小限制, 单位 MB 默认10MB + */ + sign(dir = '', size = 10) { + var time = new Date() + var params = { + conditions: [ + ['content-length-range', 0, Math.floor(1024 * 1024 * size)], + ['starts-with', '$key', dir ? dir.replace(/\/+$/, '') + '/' : ''] + ] + } + var policy = '' + + time.setTime(time.getTime() + 60 * 60 * 1000) // 60分钟内有效 + params.expiration = time.toISOString() + + policy = JSON.stringify(params) + policy = btoa(policy) + return hmac('SHA-1', policy, APP_KEY, 'base64').then(signature => { + return { policy, signature, id: APP_ID } + }) + } + + list({ prefix = '', delimiter = '/', max = 1000, token } = {}) { + var time = new Date().toGMTString() + var query = { + 'list-type': 2, + prefix, + delimiter, + 'max-keys': max, + 'continuation-token': token + } + return hmac( + 'SHA-1', + `GET\n\n\n${time}\nx-oss-date:${time}\n/${this.#bucket}/${ + token ? '?continuation-token=' + token : '' + }`, + APP_KEY, + 'base64' + ) + .then(signature => + fetch(this.#api + '?' + query.toParams(), { + headers: { + 'content-type': void 0, + authorization: `OSS ${APP_ID}:${signature}`, + 'x-oss-date': time + } + }) + ) + .then(r => r.text()) + .then(r => xml2js(r)) + } + + /** + * {上传文件} + * auth: 上面的sign签名结果 + * file: 要上传的文件 | + * key: 要保存的文件名, 带完整路径 + */ + upload(auth, file, key) { + var body = new FormData() + + if (!file.type) { + let ext = file.name.split('.').pop() + if (ext && MIME_TYPES[ext]) { + file = fixFile(ext, file) + } + } + + body.append('key', key) // + body.append('policy', auth.policy) + body.append('OSSAccessKeyId', auth.id) + body.append('Signature', auth.signature) + body.append('file', file) + + return fetch(this.#api, { method: 'POST', body }).then( + r => this.#domain + key + ) + } + + copy(origin, target) { + var time = new Date().toGMTString() + var headers = { + 'x-oss-date': time, + 'x-oss-copy-source': `/${this.#bucket}/${encodeURIComponent(origin)}` + } + + return hmac( + 'SHA-1', + str2sign('PUT', this.#bucket, { + time, + headers, + key: target + }), + APP_KEY, + 'base64' + ) + .then(signature => + fetch(`${this.#api}/${target}`, { + method: 'PUT', + headers: { + authorization: `OSS ${APP_ID}:${signature}`, + ...headers + } + }) + ) + .catch(e => console.log(e)) + } + + delete(key) { + var time = new Date().toGMTString() + + return hmac( + 'SHA-1', + `DELETE\n\n\n${time}\nx-oss-date:${time}\n/${this.#bucket}/${key}`, + APP_KEY, + 'base64' + ).then(signature => + fetch(`${this.#api}/${key}`, { + method: 'DELETE', + headers: { + 'content-type': void 0, + authorization: `OSS ${APP_ID}:${signature}`, + 'x-oss-date': time + } + }) + ) + } +} diff --git a/src/lib/constants.js b/src/lib/constants.js new file mode 100644 index 0000000..9e8add3 --- /dev/null +++ b/src/lib/constants.js @@ -0,0 +1,41 @@ +/** + * {} + * @author yutent + * @date 2023/02/17 16:00:06 + */ + +export const DEFAULT_MIME_TYPE = 'application/octet-stream' + +export const MIME_TYPES = { + html: 'text/html', + json: 'application/json', + js: 'application/javascript', + htm: 'text/html', + txt: 'text/plain', + css: 'text/css', + webp: 'image/webp', + jpg: 'image/jpg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + svg: 'image/svg+xml', + ico: 'image/ico', + mp3: 'audio/mpeg', + ogg: 'audio/ogg', + m4a: 'audio/m4a', + amr: 'audio/amr', + mp4: 'video/mp4', + webm: 'video/webm', + wasm: 'application/wasm', + zip: 'application/zip', + '7z': 'application/x-7z-compressed', + eot: 'application/vnd.ms-fontobject', + ttf: 'font/ttf', + otf: 'font/otf', + woff: 'font/woff', + woff2: 'font/woff2', + xls: 'application/vnd.ms-excel', + doc: 'application/msword', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' +} diff --git a/src/lib/helper.js b/src/lib/helper.js new file mode 100644 index 0000000..ddfea92 --- /dev/null +++ b/src/lib/helper.js @@ -0,0 +1,66 @@ +/** + * {} + * @author yutent + * @date 2023/02/17 19:14:08 + */ +import { MIME_TYPES, DEFAULT_MIME_TYPE } from './constants.js' + +// 把文件大小, 转为友好的格式 +export function parseSize(num) { + if (num < 1024) { + return `${num} B` + } else { + num = (num / 1024).toFixed(2) - 0 + + if (num < 1024) { + return `${num} KB` + } else { + num = (num / 1024).toFixed(2) - 0 + return `${num} MB` + } + } +} + +// 获取文件的拓展名 +export function getExt(str = '') { + let tmp = str.split('.') + let ext + + if (tmp.length > 1) { + ext = tmp.pop() + if (ext === 'xz' || ext === 'gz') { + ext = tmp.pop() + ext + } + return ext + } + return 'unknow' +} + +export function getMimeType(name) { + var ext = getExt(name) + return MIME_TYPES[ext] || DEFAULT_MIME_TYPE +} + +export function fixFile(name, data) { + return new File([data], name, { type: getMimeType(name) }) +} + +// 生成要签名的字符串 +export function str2sign( + method = 'GET', + bucket, + { time, headers = {}, key, query } = {} +) { + let arr = [ + method, + '', // 请求内容的md5值, 用于服务端校验文件是否完整. 可以为空 + '', // 请求文件的content-type类型, 可以为空 + time, + Object.keys(headers) + .sort() + .map(k => `${k}:${headers[k]}`) + .join('\n'), + `/${bucket}/${key + (query || '')}` + ] + return arr.join('\n') +}