|
@@ -0,0 +1,734 @@
|
|
|
|
+// import { GLOBAL } from '@/static/Global.js'
|
|
|
|
+import JsFn from '@/utils/js-fn.js'
|
|
|
|
+import Tools from '@/utils/public.js'
|
|
|
|
+import API from '@/api/index.js'
|
|
|
|
+// import store from '@/store'
|
|
|
|
+// import FileSaver from "file-saver";
|
|
|
|
+// import JSZip from "jszip";
|
|
|
|
+
|
|
|
|
+const BLOB_PATH = ['jointexam', 'avatar', 'audio', 'doc', 'exam', 'image', 'elegant', 'item', 'notice', 'other', 'paper', 'syllabus', 'res', 'records', 'student', 'survey', 'temp', 'thum', 'video', 'vote', 'jyzx', 'train', 'yxpt', 'homework', 'policy', 'public', 'art', 'activity', 'banner']
|
|
|
|
+const { BlobServiceClient } = require("@azure/storage-blob")
|
|
|
|
+
|
|
|
|
+//获取文件后缀和类型
|
|
|
|
+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 {
|
|
|
|
+ /**
|
|
|
|
+ * BlobTool生产方法
|
|
|
|
+ * @param {string} scope 学校(school)/个人(private)
|
|
|
|
+ * @param {object} options 初始化所需参数 host、container、sas
|
|
|
|
+ */
|
|
|
|
+ static CreateBlobTool(scope, options) {
|
|
|
|
+ if (!scope) {
|
|
|
|
+ throw new Error("CreateBlobTool参数(scope)错误,创建BlobTool失败")
|
|
|
|
+ }
|
|
|
|
+ //优先使用传入参数初始化
|
|
|
|
+ if (options) {
|
|
|
|
+ let { host, container, sas } = options
|
|
|
|
+ if (host && container && sas) {
|
|
|
|
+ return new BlobTool(host, container, '?' + sas, scope)
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error("CreateBlobTool参数(options)异常,创建BlobTool失败")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ let sas = scope == 'school' ? store.state?.user?.schoolProfile?.blob_sas : store.state?.user?.userProfile?.blob_sas
|
|
|
|
+ let blobUrl = scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8"))?.blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8"))?.blob_uri
|
|
|
|
+ let host = blobUrl?.substring(0, blobUrl?.lastIndexOf('/'))
|
|
|
|
+ let cont = blobUrl?.substring(blobUrl?.lastIndexOf('/') + 1)
|
|
|
|
+ return new BlobTool(host, cont, '?' + sas, scope)
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 公共读写容器 BlobTool生产方法
|
|
|
|
+ * @param {object} options 初始化所需参数 host、container
|
|
|
|
+ */
|
|
|
|
+ static CreatePublicBlobTool(options) {
|
|
|
|
+ if (!options) {
|
|
|
|
+ throw new Error("CreatePublicBlobTool参数(options)异常,创建BlobTool失败")
|
|
|
|
+ }
|
|
|
|
+ //优先使用传入参数初始化
|
|
|
|
+ let { host, container } = options
|
|
|
|
+ if (host && container) {
|
|
|
|
+ return new BlobTool(host, container, '', 'public')
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error("CreatePublicBlobTool参数(options)异常,创建BlobTool失败")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 初始化Blob,需要先调用授权API
|
|
|
|
+ * @param {string} blobUrl blob地址
|
|
|
|
+ * @param {string} container 容器名称
|
|
|
|
+ * @param {string} sasString 授权 需要前面有 '?'
|
|
|
|
+ * @param {string} scope 学校(school)/个人(private) 计算空间大小
|
|
|
|
+ * */
|
|
|
|
+ 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 && 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
|
|
|
|
+ this.scope = scope
|
|
|
|
+ if (scope == 'private') {
|
|
|
|
+ let user_profile = localStorage.getItem('user_profile')
|
|
|
|
+ this.blobSpace = user_profile ? JSON.parse(decodeURIComponent(user_profile, "utf-8")).total : 0
|
|
|
|
+ } else if (scope == 'school') {
|
|
|
|
+ let school_profile_str = localStorage.getItem('school_profile') || '{}'
|
|
|
|
+ let school_profile = JSON.parse(decodeURIComponent(school_profile_str, "utf-8"))
|
|
|
|
+ this.blobSpace = school_profile && school_profile.school_base ? school_profile.school_base.size : 0
|
|
|
|
+ } else if(scope == 'area') {
|
|
|
|
+ this.blobSpace = 0
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error("initBlob参数错误,初始化失败")
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error("initBlob初始化参数不完整,初始化失败")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取容器信息 (授权失败,需要账号级别的授权)
|
|
|
|
+ * @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('获取信息成功')
|
|
|
|
+ r(res)
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ console.log('获取信息失败')
|
|
|
|
+ j(err)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取指定分块的md5值
|
|
|
|
+ * @param {any} url 相对路径 eg: video/test.mp4
|
|
|
|
+ */
|
|
|
|
+ getBlockMD5(url) {
|
|
|
|
+ let blobClient = this.containerClient.getBlockBlobClient(url)
|
|
|
|
+ return blobClient.download(0, 4 * 1024 * 1024 - 100, { rangeGetContentMD5: true })
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 通过地址下载文件
|
|
|
|
+ * @param {any} url 相对路径 eg: video/test.mp4
|
|
|
|
+ */
|
|
|
|
+ downloadToFile(url) {
|
|
|
|
+ let blobClient = this.containerClient.getBlockBlobClient(url)
|
|
|
|
+ blobClient.download().then(
|
|
|
|
+ res => {
|
|
|
|
+ console.log('返回信息', res)
|
|
|
|
+ res.blobBody.then(
|
|
|
|
+ blobRes => {
|
|
|
|
+ console.log('blob结果', blobRes)
|
|
|
|
+ let a = document.createElement("a")
|
|
|
|
+ let url = window.URL.createObjectURL(blobRes)
|
|
|
|
+ a.href = url
|
|
|
|
+ a.download = 'test'
|
|
|
|
+ a.click()
|
|
|
|
+ window.URL.revokeObjectURL(url)
|
|
|
|
+ },
|
|
|
|
+ blobErr => {
|
|
|
|
+ console.error('blob结果', blobErr)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ console.error('下载失败')
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 上传文件方法,带回调上传进度
|
|
|
|
+ * @param {File} file 文件对象
|
|
|
|
+ * @param {Object} config 配置项 {path,root,checkExist,checkSize}
|
|
|
|
+ * {string} 必填 path 文件夹路径 只需要文件夹名称 前后都不需要加‘/’
|
|
|
|
+ * {string} 选填 root path验证的根目录 默认为'/'
|
|
|
|
+ * {boolean} 选填 checkSize 上传时是否检查容器空间 默认为 true
|
|
|
|
+ * {boolean} 选填 checkExist 是否检查文件重复 默认为 false(直接覆盖) 如果为ture会重命名上传
|
|
|
|
+ *
|
|
|
|
+ * @param {any} option 官方可配置项
|
|
|
|
+ * @returns {object} {url, name,size,createTime,extension,type}
|
|
|
|
+ */
|
|
|
|
+ // upload(file, path, option = {}, checkSize = true, root = '/') { //原来参数格式
|
|
|
|
+ upload(file, config = {}, option = {}) {
|
|
|
|
+ let { path, root, checkSize, checkExist } = config
|
|
|
|
+ console.log(config)
|
|
|
|
+ //验证和初始化参数
|
|
|
|
+ if (!path) throw new Error('上传失败:config.path必传')
|
|
|
|
+ root = root || '/'
|
|
|
|
+ checkSize = checkSize == undefined ? true : checkSize
|
|
|
|
+ checkExist = checkExist == undefined ? false : checkExist
|
|
|
|
+
|
|
|
|
+ let checkPath = root === '/' ? path : path.replace(root, '')
|
|
|
|
+ if (!BLOB_PATH.includes(checkPath.split('/')[0])) throw new Error('上传路径不合法,请检查上传路径:' + path)
|
|
|
|
+
|
|
|
|
+ return new Promise(async (r, j) => {
|
|
|
|
+ //检查容器空间大小
|
|
|
|
+ let isFull = false
|
|
|
|
+ if (checkSize) {
|
|
|
|
+ try {
|
|
|
|
+ isFull = await this.isContainerFull(this.scope)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ j({ spaceError: '容器空间计算失败,无法上传文件' })
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (isFull) {
|
|
|
|
+ j({
|
|
|
|
+ code: 1,
|
|
|
|
+ spaceError: 'Blob空间已满,无法上传'
|
|
|
|
+ })
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //检查文件是否存在
|
|
|
|
+ let fileName = file.name
|
|
|
|
+ let isExist = false
|
|
|
|
+ if (checkExist) {
|
|
|
|
+ let index = 1
|
|
|
|
+ while (await this.exists(path + '/' + fileName)) {
|
|
|
|
+ fileName = `${file.name.slice(0, file.name.lastIndexOf('.'))}(${index})${file.name.slice(file.name.lastIndexOf('.'))}`
|
|
|
|
+ isExist = true
|
|
|
|
+ index++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ const blockBlobClient = this.containerClient.getBlockBlobClient(path + "/" + fileName)
|
|
|
|
+ blockBlobClient.uploadBrowserData(file, option).then(
|
|
|
|
+ async res => {
|
|
|
|
+ //设置blob MD5 (解决大文件分块上传没有MD5的问题)
|
|
|
|
+ let md5value = res.contentMD5
|
|
|
|
+ if (!md5value) {
|
|
|
|
+ try {
|
|
|
|
+ md5value = await Tools.getFileMD5(file)
|
|
|
|
+ let option = {
|
|
|
|
+ blobContentMD5: md5value
|
|
|
|
+ }
|
|
|
|
+ this.setFileProperties(path + "/" + fileName, option).then(
|
|
|
|
+ setRes => {
|
|
|
|
+ console.log('MD5设置成功', setRes)
|
|
|
|
+ },
|
|
|
|
+ setErr => {
|
|
|
|
+ console.error('MD5设置失败', setErr)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error('前端获取MD5失败', e)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ let url = decodeURIComponent(res._response.request.url)
|
|
|
|
+ url = url.substring(0, url.lastIndexOf('?'))
|
|
|
|
+ let info = getExAndType(fileName)
|
|
|
|
+ r({
|
|
|
|
+ url: url,
|
|
|
|
+ md5: md5value,
|
|
|
|
+ blob: '/' + path + "/" + fileName,
|
|
|
|
+ name: fileName,
|
|
|
|
+ size: file.size,
|
|
|
|
+ createTime: res.lastModified.getTime(),
|
|
|
|
+ extension: info.ex,
|
|
|
|
+ type: info.type,
|
|
|
|
+ isExist: isExist
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ j(err)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 处理HTEX文件类型
|
|
|
|
+ * @param {blobList} fileList
|
|
|
|
+ */
|
|
|
|
+ handleHTEXFile(fileList) {
|
|
|
|
+ let parseRes = []
|
|
|
|
+ let names = []
|
|
|
|
+ fileList.forEach((item, index) => {
|
|
|
|
+ if (item.url.indexOf('/res/') > 0 && item.url.indexOf('.HTE') < 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 {string} folder
|
|
|
|
+ * @param {number} options
|
|
|
|
+ * {
|
|
|
|
+ * fileName:'xxxx', 打包下载的文件名(必填)
|
|
|
|
+ * exclude:'aaa', 在文件夹内但是不需要下载的内容
|
|
|
|
+ * }
|
|
|
|
+ * @returns
|
|
|
|
+ */
|
|
|
|
+ downloadFolder(folder, options) {
|
|
|
|
+ const { fileName, exclude } = options
|
|
|
|
+ this.listBlob({
|
|
|
|
+ prefix: folder
|
|
|
|
+ }).then(
|
|
|
|
+ async res => {
|
|
|
|
+ const zip = new JSZip()
|
|
|
|
+ for (const blob of res.blobList) {
|
|
|
|
+ try {
|
|
|
|
+ if (exclude && blob.blob?.includes(exclude)) {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ let blobPath = blob.blob.substring(1)
|
|
|
|
+ let blobClient = this.containerClient.getBlockBlobClient(blobPath)
|
|
|
|
+ const dwRes = await blobClient.download()
|
|
|
|
+ const blobRes = await dwRes.blobBody
|
|
|
|
+ const filePath = blobPath.replace(folder, '')
|
|
|
|
+ zip.file(filePath, blobRes, {
|
|
|
|
+ binary: true
|
|
|
|
+ }) // 逐个添加文件
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error('下载失败')
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ zip.generateAsync({
|
|
|
|
+ type: "blob"
|
|
|
|
+ }).then(content => {
|
|
|
|
+ // 生成二进制流
|
|
|
|
+ FileSaver.saveAs(content, fileName + ".zip"); // 利用file-saver保存文件
|
|
|
|
+ }).catch(err => {
|
|
|
|
+ console.log(err);
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 列出目录结构
|
|
|
|
+ * @param {string} delimiter 分割符 默认'/'
|
|
|
|
+ * @param {object} option 配置项 eg: option.prefix = 'res' 查某个目录的下级目录
|
|
|
|
+ * @returns
|
|
|
|
+ */
|
|
|
|
+ listFolder(option, delimiter = '/') {
|
|
|
|
+ return new Promise(async (r, j) => {
|
|
|
|
+ let folderList = []
|
|
|
|
+ if (this.containerClient) {
|
|
|
|
+ let iter = this.containerClient.listBlobsByHierarchy(delimiter, option ? option : {})
|
|
|
|
+ try {
|
|
|
|
+ let blobItem = await iter.next()
|
|
|
|
+ while (!blobItem.done) {
|
|
|
|
+ if (blobItem.value.kind === 'prefix') {
|
|
|
|
+ folderList.push(blobItem.value.name)
|
|
|
|
+ }
|
|
|
|
+ blobItem = await iter.next()
|
|
|
|
+ }
|
|
|
|
+ r(folderList)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error(e)
|
|
|
|
+ j('获取目录结构异常')
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ j('初始化异常')
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 列出所有(查询)
|
|
|
|
+ * @param {Object} option ContainerListBlobsOptions
|
|
|
|
+ * eg: option.prefix = 'res' 只查res文件夹下的blob
|
|
|
|
+ * @returns {object} {blobList, continuationToken}
|
|
|
|
+ */
|
|
|
|
+ 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)
|
|
|
|
+ }
|
|
|
|
+ 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(
|
|
|
|
+ async res => {
|
|
|
|
+ r(200)
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ j(err)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error("filePath参数错误")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 批量删除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} url //blob完整路径包含授权 eg:'doc/醍摩豆账号.xlsx'
|
|
|
|
+ */
|
|
|
|
+ getFileProperties(url) {
|
|
|
|
+ let blobClient = this.containerClient.getBlockBlobClient(url)
|
|
|
|
+ return blobClient.getProperties()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 设置文件header
|
|
|
|
+ * @param {string} url //blob完整路径包含授权 eg:'doc/醍摩豆账号.xlsx'
|
|
|
|
+ * @param {BlobHTTPHeaders} option //设置blob的属性
|
|
|
|
+ * eg:
|
|
|
|
+ * {
|
|
|
|
+ * blobContentMD5: new Uint8Array(16)
|
|
|
|
+ * }
|
|
|
|
+ */
|
|
|
|
+ setFileProperties(url, option) {
|
|
|
|
+ let blobClient = this.containerClient.getBlockBlobClient(url)
|
|
|
|
+ return blobClient.setHTTPHeaders(option)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 复制单个Blob
|
|
|
|
+ * @param {string} targetUrl
|
|
|
|
+ * @param {string} sourceUrl
|
|
|
|
+ * @param {string} sas
|
|
|
|
+ * 1、目标url(targetUrl)容器之后的文件路径 eg:'paper/預設試卷名稱00/gfWxV2p8lx07QGBSz5k401041200fIJd0E010.mp4'
|
|
|
|
+ * 2、源文件url(sourceUrl)1、完整路径(包括Host); 2、如果需要授权的容器,则url需要凭借授权。
|
|
|
|
+ */
|
|
|
|
+ copyBlob(targetUrl, sourceUrl, sas) {
|
|
|
|
+ return new Promise((r, j) => {
|
|
|
|
+ console.log(...arguments)
|
|
|
|
+ let newBlob = this.containerClient.getBlobClient(targetUrl)
|
|
|
|
+ let encodeUrl = encodeURI(sourceUrl)
|
|
|
|
+ newBlob.beginCopyFromURL(encodeUrl + sas).then(
|
|
|
|
+ res => {
|
|
|
|
+ console.log('复制成功返回数据', res)
|
|
|
|
+ r(200)
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ console.log('11111111111', 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
|
|
|
|
+ * @param {boolean} handleSize 是否内部处理blobsize
|
|
|
|
+ */
|
|
|
|
+ copyFolder(targetFolder, sourceFolder, blobTool, handleSize = true) {
|
|
|
|
+ console.log(...arguments);
|
|
|
|
+ return new Promise(async (r, j) => {
|
|
|
|
+ try {
|
|
|
|
+ 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
|
|
|
|
+ }
|
|
|
|
+ //上传之前检查文件夹大小
|
|
|
|
+ let beforeSize = 0
|
|
|
|
+ let cont = ''
|
|
|
|
+ if (handleSize) {
|
|
|
|
+ cont = this.container
|
|
|
|
+ }
|
|
|
|
+ if (blobs && blobs.blobList.length) {
|
|
|
|
+ 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(
|
|
|
|
+ async res => {
|
|
|
|
+ if (++count == (blobs.blobList.length)) {
|
|
|
|
+ if (handleSize) {
|
|
|
|
+ //复制之后更新大小
|
|
|
|
+ }
|
|
|
|
+ r(blobItem)
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ j('copy error')
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ j('sourceBlob error or 404 : ' + sourceFolder)
|
|
|
|
+ }
|
|
|
|
+ } catch {
|
|
|
|
+ j('copy error')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 判断文件是否存在
|
|
|
|
+ * @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)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 判断容器的空间是否已满
|
|
|
|
+ * @params {containerName} 容器名称
|
|
|
|
+ * @params {scope} 'school' or 'private'
|
|
|
|
+ * return true/false
|
|
|
|
+ */
|
|
|
|
+ async isContainerFull(scope) {
|
|
|
|
+ return new Promise(async (r, j) => {
|
|
|
|
+ try {
|
|
|
|
+ let sizeRes = await BlobTool.getContainerSize(this.container, scope)
|
|
|
|
+ console.log(scope)
|
|
|
|
+ console.log(sizeRes)
|
|
|
|
+ if (sizeRes) {
|
|
|
|
+ r(sizeRes.total > this.blobSpace * 1024 * 1024 * 1024)
|
|
|
|
+ } else {
|
|
|
|
+ j('容器空间判断失败!')
|
|
|
|
+ }
|
|
|
|
+ } catch (e) {
|
|
|
|
+ j('容器空间判断失败!')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 新版获取blob空间大小
|
|
|
|
+ * @params {containerName} 容器名称
|
|
|
|
+ * @params {scope} 'school' or 'private'
|
|
|
|
+ */
|
|
|
|
+ static getContainerSize(containerName, scope) {
|
|
|
|
+ if (containerName, scope) {
|
|
|
|
+ return new Promise((r, j) => {
|
|
|
|
+ API.blob.getContainerSize({
|
|
|
|
+ scope,
|
|
|
|
+ containerName
|
|
|
|
+ }).then(
|
|
|
|
+ res => {
|
|
|
|
+ if (res) {
|
|
|
|
+ let contentSize = 0
|
|
|
|
+ let defined = ['image', 'res', 'video', 'audio', 'doc', 'other']
|
|
|
|
+ for (let key in res.catalog) {
|
|
|
|
+ if (defined.includes(key)) {
|
|
|
|
+ contentSize += res.catalog[key]
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ res.catalog.appData = res.size - contentSize
|
|
|
|
+ res.catalog.total = res.size
|
|
|
|
+ res.catalog.teachSpace = res.teach ? res.teach * 1024 * 1024 * 1024 : 0 //查询学校空间的时候,返回此学校已分配给教室的空间;查询个人容器返回为零,不需要处理
|
|
|
|
+ r(res.catalog)
|
|
|
|
+ } else {
|
|
|
|
+ j('API error')
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ err => {
|
|
|
|
+ j('API error')
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error("参数不完整")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+}
|