import { GLOBAL } from '@/static/Global.js'; import JsFn from '@/utils/js-fn.js'; console.log(GLOBAL) const { BlobServiceClient, BlobClient } = require("@azure/storage-blob") //const s = "?sv=2019-12-12&ss=b&srt=sco&st=2020-12-22T10%3A09%3A08Z&se=2020-12-23T10%3A09%3A08Z&sp=rwdxftlacup&sig=EHyNiA5uoBPHQAHfISLei%2BWdWnldHzOYYR7ZYdtFtRo%3D" //获取文件后缀和类型 function getExAndType(fileName) { let ex = fileName.substring(fileName.lastIndexOf('.') + 1) let type = 'other' ex = ex.toUpperCase() for (let key in GLOBAL.CONTENT_TYPES) { if (GLOBAL.CONTENT_TYPES[key].indexOf(ex) != -1) { type = key break } } return { ex, type } } export default class BlobTool { /** * 初始化Blob,需要先调用授权API * @param {string} blobUrl blob地址 * @param {string} container 容器名称 * @param {string} sasString 授权 * */ constructor(blobUrl, container, sasString, scope) { this.initBlob(blobUrl, container, sasString, scope) } /** * 初始化Blob,需要先调用授权API * @param {string} blobUrl blob地址 * @param {string} container 容器名称 * @param {string} sasString 授权 * @param {string} scope 学校(school)/个人(private) 计算空间大小 * */ initBlob(blobUrl, container, sasString, scope) { if (blobUrl && container && sasString, scope) { //初始化containerClient this.blobService = new BlobServiceClient(blobUrl + sasString) let containerClient = this.blobService.getContainerClient(container) if (containerClient) { this.containerClient = containerClient this.container = container this.blobUrl = blobUrl this.sasString = sasString if (scope == 'private') { this.blobSpace = GLOBAL.PRIVATE_SPACE } else if (scope == 'school') { this.blobSpace = GLOBAL.SCHOOL_SPACE } } else { throw new Error("参数错误,初始化失败") } } else { throw new Error("初始化参数不完整") } } /** * 获取容器信息 (授权失败,需要账号级别的授权) * @param {object} */ getProperties(options) { return new Promise((r, j) => { //const blockBlobClient = this.containerClient.getBlockBlobClient('res/基础操作范例_16x9.HTE') //blob获取成功 this.containerClient.getProperties(options).then( res => { console.log('获取信息成功') console.log(res) r(res) }, err => { console.log('获取信息失败') console.log(err) j(err) } ) }) } /** * 上传文件方法,带回调上传进度 * @param {any} file 文件对象 * @param {any} path 文件夹路径 * @param {any} option 官方可配置项 * @param {any} checkSize 上传时是否检查容器空间 官方可配置项。默认需要检查大小 * @returns {object} {url, name,size,createTime,extension,type} */ upload(file, path, option, checkSize = true) { return new Promise(async (r, j) => { //检查容器空间大小 if (checkSize) { if (!this.totalSize) { try { this.totalSize = await this.getContainerSize() } catch (e) { this.totalSize = -1 j({ spaceError: '容器空间计算失败,无法上传文件' }) } } if (this.totalSize == -1) { j({ spaceError: 'Blob空间计算失败,无法上传文件' }) } else if (this.totalSize > this.blobSpace) { j({ spaceError: 'Blob空间已满,无法上传' }) } } const blockBlobClient = this.containerClient.getBlockBlobClient(path + "/" + file.name) blockBlobClient.uploadBrowserData(file, option).then( res => { let url = decodeURIComponent(res._response.request.url) url = url.substring(0, url.lastIndexOf('?')) let info = getExAndType(file.name) if (this.totalSize) { this.totalSize += res._response.request.body.size } r({ url: url, blob: '/' + path + "/" + file.name, name: file.name, size: res._response.request.body.size, createTime: res.lastModified.getTime(), extension: info.ex, type: info.type }) }, err => { j(err) } ) }) } /** * 处理HTEX文件类型 * @param {blobList} fileList */ handleHTEXFile(fileList) { let parseRes = [] let names = [] fileList.forEach((item, index) => { if (item.url.indexOf('/res/') > 0) { let fileItem = {} let startIndex = JsFn.findChartIndex(item.blob, '/', 1) let endIndex = JsFn.findChartIndex(item.blob, '/', 2) let name = item.blob.substring(startIndex + 1, endIndex) let nameIndex = names.indexOf(name) if (nameIndex == -1) { fileItem.url = this.blobUrl+ '/' + this.container +'/res/' + name fileItem.blob = `/res/${name}/index.json` fileItem.name = name + '.HTEX' fileItem.size = item.size fileItem.createTime = item.createTime fileItem.extension = 'HTEX' fileItem.type = 'res' names.push(name) parseRes.push(fileItem) } else { parseRes[nameIndex].size += item.size } } else { parseRes.push(item) } }) return parseRes } /** * 列出所有(查询) * @param {Object} option ContainerListBlobsOptions * eg: option.prefix = 'res' 只查res文件夹下的blob * @returns {object} {blobList, continuationToken} */ async listBlob(option, hendleHTEX = true) { return new Promise(async (r, j) => { let blobList = [] if (this.containerClient) { let iter = this.containerClient.listBlobsFlat(option ? option : {}); let blobItem = await iter.next(); while (!blobItem.done) { let blobName = blobItem.value.name let info = getExAndType(blobItem.value.name) blobList.push( { url: this.blobUrl + '/' + this.container + '/' + blobName, blob: '/' + blobName, // name: blobName.substring(JsFn.findChartIndex(blobName, '/', 0) + 1), name: blobName.substring(blobName.lastIndexOf('/') + 1), size: blobItem.value.properties.contentLength, createTime: blobItem.value.properties.createdOn.getTime(), extension: info.ex, type: info.type } ) blobItem = await iter.next(); } if (hendleHTEX) { blobList = this.handleHTEXFile(blobList) } console.log('----', blobList) r({ blobList, continuationToken: 'end' }) } else { j('containerClient 错误') } }) } /** * 分页列出(查询) * @param {Object} option ContainerListBlobsOptions * eg: option.prefix = 'res' 只查res文件夹下的blob * @param {Object} pageInfo * eg: pageInfo.maxPageSize 当前请求条数 * eg: pageInfo.continuationToken 首次请求不需要,后面需要(首次请求会返回,下次需要传入) * @returns {object} {blobList, continuationToken} */ async listBlobByPage(option, pageInfo) { return new Promise(async (r, j) => { let page = {} let blobList = [] if (pageInfo && JSON.stringify(pageInfo) != '{}') { page.maxPageSize = pageInfo.maxPageSize ? pageInfo.maxPageSize : 50 if (pageInfo.continuationToken) { page.continuationToken = pageInfo.continuationToken ? pageInfo.continuationToken : '' } } if (this.containerClient) { let iterator = this.containerClient.listBlobsFlat(option ? option : {}).byPage(page) let response = (await iterator.next()).value let prefixLen = response.prefix ? response.prefix.length + 1 : 0 for (const blob of response.segment.blobItems) { let info = getExAndType(blob.name) blobList.push( { url: response.serviceEndpoint + response.containerName + '/' + blob.name, blob: '/' + blob.name, name: blob.name.substring(prefixLen), size: blob.properties.contentLength, createTime: blob.properties.lastModified.getTime(), extension: info.ex, type: info.type } ) } let continuationToken = response.continuationToken ? response.continuationToken : 'end' r({ blobList, continuationToken }) } else { j('containerClient 错误') } }) } /** * 删除Blob * @param {string} filePath 文件url + 容器 之后的路径 */ deleteBlob(filePath) { if (filePath) { filePath = filePath.substring(1) } return new Promise((r, j) => { this.containerClient.deleteBlob(filePath).then( res => { r(res) }, err => { j(err) } ) }) } /** * 批量删除Blob 官方API授权失败(需要账号级别的授权,批量删除请访问后端API) * @param {string} files 文件url + 容器 之后的路径 */ deleteBlobs(files) { this.getProperties() let blobBatchClient = this.blobService.getBlobBatchClient() return new Promise((r, j) => { for (let i in files) { files[i] = files[i].substring(0, files[i].lastIndexOf('?')) } blobBatchClient.deleteBlobs(files, this.blobService.credential).then( res => { console.log('批量删除成功') }, err => { console.log('批量删除失败') } ) }) } /** * 批量删除Blob 循环操作 * @param {string} files */ deleteBlobBatch(files) { return new Promise((r, j) => { let promises = [] for (let item of files) { let f = item.substring(1) promises.push(this.containerClient.deleteBlob(f)) } Promise.all(promises).then( res => { r(res) }, err => { j(err) } ) }) } /** * 复制单个Blob * @param {string} targetUrl * @param {string} sourceUrl * 1、目标url(sourceUrl)不能出现中文或UTF-8码的字符, 如果有需要进行URI编码,否则会报错; * 2、目标url(sourceUrl)如果需要授权的容器,则url需要凭借授权。 */ copyBlob(targetUrl, sourceUrl, sas) { return new Promise((r, j) => { let newBlob = this.containerClient.getBlobClient(targetUrl) let encodeUrl = encodeURI(sourceUrl) // sas = "?sv=2020-02-10&st=2021-01-18T11%3A29%3A36Z&se=2021-01-19T11%3A59%3A36Z&sr=c&sp=rcwdl&sig=BXbsiVZ6ZogeWfyr3u2PgPVdeLlMMPOqvsh3%2BDEwnq4%3D" newBlob.beginCopyFromURL(encodeUrl + sas).then( res => { r(200) }, err => { j(500) } ) }) } /** * 复制‘文件夹’ * @param {string} targetFolder eg:'exam/评测id/paper/8b94c6b6-2572-41e5-89b9-a82fcf13891e/' 注意开头不加‘/’, 结尾需要加‘/’ * @param {string} sourceFolder eg:'paper/JEFF組卷測試01' * @param {BlobTool} blobTool 非必传参数, 当目标文件和源文件不在同一个容器的时候,需要传源文件容器初始化的BlobTool */ copyFolder(targetFolder, sourceFolder, blobTool) { return new Promise(async (r, j) => { let blobs = undefined let sasString = '' if (blobTool) { blobs = await blobTool.listBlob({ prefix: sourceFolder + '/' }, false) sasString = blobTool.sasString } else { blobs = await this.listBlob({ prefix: sourceFolder + '/' }, false) sasString = this.sasString } if (blobs && blobs.blobList) { let count = 0 blobs.blobList.forEach(blobItem => { let newUrl = targetFolder + blobItem.name let newBlob = this.containerClient.getBlobClient(newUrl) let resourceUrl = encodeURI(blobItem.url) newBlob.beginCopyFromURL(resourceUrl + sasString).then().finally(() => { count++ if (count == (blobs.blobList.length)) { r('复制结束后') } }) }) } }) } /** * 判断文件是否存在 * @param {string} filePath 文件路径 正确 'res/基础操作范例_16x9.HTE ' 错误 '/res/基础操作范例_16x9.HTE' * @param {object} * return true/false */ exists(filePath, options) { const blockBlobClient = this.containerClient.getBlockBlobClient(filePath) return new Promise((r, j) => { blockBlobClient.exists(options).then( res => { r(res) }, err => { j(err) } ) }) } /** * 计算容器的空间大小 * return size */ async getContainerSize() { return new Promise((r, j) => { this.listBlob({},false).then( res => { let totalSize = res.blobList.reduce((total, item) => { return total + parseInt(item.size) }, 0) this.totalSize = totalSize r(totalSize) }, err => { j(err) } ) }) } /** * 判断容器的空间是否已满 * @param {string} scope 校本/个人 school / private * return true/false */ async isContainerFull() { return new Promise(async (r, j) => { try { let size = await this.getContainerSize() if (size > this.blobSpace) { r(true) } else { r(false) } } catch (e) { j('容器空间判断失败!') } }) } /** * 计算指定文件夹的空间大小,如果不传文件夹,则查询整个容器空间 * @param {string} * @param {object} * return true/false */ getSize(option) { return new Promise((r, j) => { this.listBlob(option,false).then( res => { let sizeInfo = { total: 0, //所有 image: 0, //内容模块图片 res: 0, //内容模块教材 video: 0, //内容模块视频 audio: 0, //内容模块音频 doc: 0, //内容模块文档 other: 0, //内容模块其他文件 data: 0 //除了内容模块之外的文件 } let length = res.blobList.reduce((total, item) => { return total + parseInt(item.size) }, 0) sizeInfo.total = length for (let item of res.blobList) { let folder = item.blob.substring(1, JsFn.findChartIndex(item.blob, '/', 1)) switch (folder) { case 'res': sizeInfo.res += item.size break case 'image': sizeInfo.image += item.size break case 'video': sizeInfo.video += item.size break case 'audio': sizeInfo.audio += item.size break case 'doc': sizeInfo.doc += item.size break case 'other': sizeInfo.other += item.size break default: sizeInfo.data += item.size break } } r(sizeInfo) }, err => { j(err) } ) }) } }