🔨 Refactor: refactored of some manage api

This commit is contained in:
萌萌哒赫萝
2023-08-07 07:58:43 -07:00
parent cb131d5250
commit ecf42fab96
8 changed files with 338 additions and 342 deletions
-1
View File
@@ -47,7 +47,6 @@ class RemoteNoticeHandler {
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8') const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
this.remoteNoticeLocalCountStorage = localCountStorage this.remoteNoticeLocalCountStorage = localCountStorage
} catch (e) { } catch (e) {
console.log(e)
this.remoteNoticeLocalCountStorage = localCountStorage this.remoteNoticeLocalCountStorage = localCountStorage
} }
} }
+107 -133
View File
@@ -1,18 +1,37 @@
// Axios
import axios from 'axios' import axios from 'axios'
// 加密函数、获取文件 MIME 类型、错误格式化函数、新的下载器、并发异步任务池
import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '../utils/common' import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// 快速 XML 解析器
import { XMLParser } from 'fast-xml-parser' import { XMLParser } from 'fast-xml-parser'
// 阿里云 OSS 客户端库
import OSS from 'ali-oss' import OSS from 'ali-oss'
// 路径处理库
import path from 'path' import path from 'path'
// 是否为图片的判断函数
import { isImage } from '~/renderer/manage/utils/common' import { isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
import UpDownTaskQueue,
{ // 上传下载任务队列
uploadTaskSpecialStatus, import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
commonTaskStatus
} from '../datastore/upDownTaskQueue' // 日志记录器
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static' import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
// 坑爹阿里云 返回数据类型标注和实际各种不一致 // 坑爹阿里云 返回数据类型标注和实际各种不一致
@@ -49,22 +68,20 @@ class AliyunApi {
} }
formatFile (item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any { formatFile (item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
const result = { const fileName = item.name.replace(slicedPrefix, '')
return {
...item, ...item,
key: item.name, key: item.name,
rawUrl: `${urlPrefix}/${item.name}`, fileName,
fileName: item.name.replace(slicedPrefix, ''),
fileSize: item.size, fileSize: item.size,
formatedTime: new Date(item.lastModified).toLocaleString(), formatedTime: new Date(item.lastModified).toLocaleString(),
isDir: false, isDir: false,
checked: false, checked: false,
match: false, match: false,
isImage: isImage(item.name.replace(slicedPrefix, '')) isImage: isImage(fileName),
rawUrl: item.url,
url: `${urlPrefix}/${item.name}`
} }
const temp = result.rawUrl
result.rawUrl = result.url
result.url = temp
return result
} }
getCanonicalizedOSSHeaders (headers: IStringKeyMap) { getCanonicalizedOSSHeaders (headers: IStringKeyMap) {
@@ -100,47 +117,30 @@ class AliyunApi {
* 获取存储桶列表 * 获取存储桶列表
*/ */
async getBucketList (): Promise<any> { async getBucketList (): Promise<any> {
const formatItem = (item: OSS.Bucket) => { const getBuckets = async (marker?: string) => {
return { const res = await this.ctx.listBuckets({
marker,
'max-keys': 1000
}) as IStringKeyMap
if (res.res.statusCode !== 200 || !res.buckets) return { result: [], isTruncated: false }
const formattedBuckets = res.buckets.map((item: OSS.Bucket) => ({
Name: item.name, Name: item.name,
Location: item.region, Location: item.region,
CreationDate: item.creationDate CreationDate: item.creationDate
} }))
} return { result: formattedBuckets, isTruncated: res.isTruncated, nextMarker: res.nextMarker }
const res = await this.ctx.listBuckets({
'max-keys': 1000
}) as IStringKeyMap
const result = [] as IStringKeyMap[]
let NextMarker = ''
if (res.res.statusCode === 200) {
if (res.buckets) {
result.push(...res.buckets.map((item: OSS.Bucket) => formatItem(item)))
let isTruncated = res.isTruncated
NextMarker = res.nextMarker
while (isTruncated) {
const res = await this.ctx.listBuckets({
marker: NextMarker,
'max-keys': 1000
}) as IStringKeyMap
if (res.res.statusCode === 200) {
if (res.buckets) {
result.push(...res.buckets.map((item: OSS.Bucket) => formatItem(item)))
isTruncated = res.isTruncated
NextMarker = res.nextMarker
} else {
isTruncated = false
}
} else {
isTruncated = false
}
}
return result
} else {
return []
}
} else {
return []
} }
const result: IStringKeyMap[] = []
let NextMarker: string | undefined
let isTruncated: boolean
do {
const { result: buckets, isTruncated: truncated, nextMarker } = await getBuckets(NextMarker)
result.push(...buckets)
NextMarker = nextMarker
isTruncated = truncated
} while (isTruncated)
return result
} }
/** /**
@@ -151,6 +151,7 @@ class AliyunApi {
Date: new Date().toUTCString() Date: new Date().toUTCString()
} }
const authorization = this.authorization('GET', `/${param.bucketName}/?cname`, headers, '', '') const authorization = this.authorization('GET', `/${param.bucketName}/?cname`, headers, '', '')
const res = await axios({ const res = await axios({
url: `https://${param.bucketName}.${param.region}.aliyuncs.com/?cname`, url: `https://${param.bucketName}.${param.region}.aliyuncs.com/?cname`,
method: 'GET', method: 'GET',
@@ -159,25 +160,22 @@ class AliyunApi {
Authorization: authorization Authorization: authorization
} }
}) })
if (res.status === 200) { if (res.status === 200) {
const parser = new XMLParser() const parser = new XMLParser()
const result = parser.parse(res.data) const result = parser.parse(res.data)
if (result.ListCnameResult && result.ListCnameResult.Cname) {
if (Array.isArray(result.ListCnameResult.Cname)) { if (result.ListCnameResult?.Cname) {
const cnameList = [] as string[] const cnames = Array.isArray(result.ListCnameResult.Cname)
result.ListCnameResult.Cname.forEach((item: IStringKeyMap) => { ? result.ListCnameResult.Cname
item.Status === 'Enabled' && cnameList.push(item.Domain) : [result.ListCnameResult.Cname]
})
return cnameList return cnames
} else { .filter((item: IStringKeyMap) => item.Status === 'Enabled')
return result.ListCnameResult.Cname.Status === 'Enabled' ? [result.ListCnameResult.Cname.Domain] : [] .map((item: IStringKeyMap) => item.Domain)
}
} else {
return []
} }
} else {
return []
} }
return []
} }
/** /**
@@ -329,16 +327,10 @@ class AliyunApi {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
const slicedPrefix = prefix.slice(1) const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com` const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let res = {} as any
const result = {
fullList: <any>[],
isTruncated: false,
nextMarker: '',
success: false
}
const client = this.getNewCtx(region, bucket) const client = this.getNewCtx(region, bucket)
res = await client.listV2({ const res = await client.listV2({
prefix: slicedPrefix === '' ? undefined : slicedPrefix, prefix: slicedPrefix || undefined,
delimiter: '/', delimiter: '/',
'max-keys': itemsPerPage.toString(), 'max-keys': itemsPerPage.toString(),
'continuation-token': marker 'continuation-token': marker
@@ -347,18 +339,24 @@ class AliyunApi {
}) as any }) as any
// prefixes can be null // prefixes can be null
// objects will be [] when no file // objects will be [] when no file
if (res && res.res.statusCode === 200) { if (res?.res.statusCode !== 200) {
res.prefixes && res.prefixes.forEach((item: string) => { return {
result.fullList.push(this.formatFolder(item, slicedPrefix)) fullList: [],
}) isTruncated: false,
res.objects && res.objects.forEach((item: OSS.ObjectMeta) => { nextMarker: '',
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) success: false
}) }
result.isTruncated = res.isTruncated }
result.nextMarker = res.nextContinuationToken || '' const fullList = [
result.success = true ...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix)) || []),
...(res.objects?.filter((item: OSS.ObjectMeta) => item.size !== 0).map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
]
return {
fullList,
isTruncated: res.isTruncated,
nextMarker: res.nextContinuationToken || '',
success: true
} }
return result
} }
/** /**
@@ -374,16 +372,15 @@ class AliyunApi {
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> { async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap const { bucketName, region, oldKey, newKey } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = await client.copy( const copyRes = await client.copy(
newKey, newKey,
oldKey oldKey
) as any ) as any
if (res && res.res.statusCode === 200) { if (copyRes?.res.statusCode === 200) {
const res2 = await client.delete(oldKey) as any const deleteRes = await client.delete(oldKey) as any
return res2 && res2.res.statusCode === 204 return deleteRes?.res.statusCode === 204
} else {
return false
} }
return false
} }
/** /**
@@ -399,7 +396,7 @@ class AliyunApi {
const { bucketName, region, key } = configMap const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName) const client = this.getNewCtx(region, bucketName)
const res = await client.delete(key) as any const res = await client.delete(key) as any
return res && res.res.statusCode === 204 return res?.res.statusCode === 204
} }
/** /**
@@ -415,62 +412,39 @@ class AliyunApi {
CommonPrefixes: [] as any[], CommonPrefixes: [] as any[],
Contents: [] as any[] Contents: [] as any[]
} }
let res = await client.listV2({ do {
prefix: key, const res = await client.listV2({
delimiter: '/', prefix: key,
'max-keys': '1000' delimiter: '/',
}, { 'max-keys': '1000',
timeout: this.timeOut 'continuation-token': marker
}) as any }, {
if (res && res.res.statusCode === 200) { timeout: this.timeOut
}) as any
if (res?.res.statusCode !== 200) return false
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes) res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
res.objects.length > 0 && allFileList.Contents.push(...res.objects) res.objects?.length > 0 && allFileList.Contents.push(...res.objects)
isTruncated = res.isTruncated isTruncated = res.isTruncated
marker = res.nextContinuationToken marker = res.nextContinuationToken
while (isTruncated) { } while (isTruncated)
res = await client.listV2({
prefix: key,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
}, {
timeout: this.timeOut
}) as any
if (res && res.res.statusCode === 200) {
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
res.objects.length > 0 && allFileList.Contents.push(...res.objects)
isTruncated = res.isTruncated
marker = res.nextContinuationToken
} else {
return false
}
}
} else {
return false
}
if (allFileList.CommonPrefixes.length > 0) { if (allFileList.CommonPrefixes.length > 0) {
for (const item of allFileList.CommonPrefixes) { for (const item of allFileList.CommonPrefixes) {
res = await this.deleteBucketFolder({ const successfully = await this.deleteBucketFolder({
bucketName, bucketName,
region, region,
key: item key: item
}) })
if (!res) { if (!successfully) return false
return false
}
} }
} }
if (allFileList.Contents.length > 0) { if (allFileList.Contents.length > 0) {
const cycle = Math.ceil(allFileList.Contents.length / 1000) const cycle = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycle; i++) { for (let i = 0; i < cycle; i++) {
res = await client.deleteMulti( const deleteRes = await client.deleteMulti(
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => { allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)) as any
return item.name if (deleteRes?.res.statusCode !== 200) return false
})
) as any
if (!(res && res.res.statusCode === 200)) {
return false
}
} }
} }
return true return true
+4 -4
View File
@@ -9,13 +9,13 @@ import UpyunApi from './upyun'
import WebdavplistApi from './webdavplist' import WebdavplistApi from './webdavplist'
export default { export default {
TcyunApi,
AliyunApi, AliyunApi,
QiniuApi,
UpyunApi,
SmmsApi,
GithubApi, GithubApi,
ImgurApi, ImgurApi,
QiniuApi,
S3plistApi, S3plistApi,
SmmsApi,
TcyunApi,
UpyunApi,
WebdavplistApi WebdavplistApi
} }
+33 -19
View File
@@ -1,16 +1,34 @@
// HTTP 请求库
import got from 'got' import got from 'got'
// 日志记录器
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
// HTTP 代理格式化函数、是否为图片的判断函数
import { formatHttpProxy, isImage } from '~/renderer/manage/utils/common' import { formatHttpProxy, isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// got 上传函数、路径处理函数、新的下载器、获取请求代理、获取请求选项、并发异步任务池、错误格式化函数
import { gotUpload, trimPath, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '../utils/common' import { gotUpload, trimPath, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '../utils/common'
import UpDownTaskQueue,
{ // 上传下载任务队列
commonTaskStatus import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
} from '../datastore/upDownTaskQueue'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
// 路径处理库
import path from 'path' import path from 'path'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static' import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class GithubApi { class GithubApi {
@@ -252,34 +270,32 @@ class GithubApi {
*/ */
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap const { bucketName: repo, githubBranch: branch, key } = configMap
// get sha of the branch
const refRes = await got( const refRes = await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any ) as any
if (refRes.statusCode !== 200) { if (refRes.statusCode !== 200) return false
return false
}
const refSha = refRes.body.object.sha const refSha = refRes.body.object.sha
// get sha of the root tree
const rootRes = await got( const rootRes = await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`, `${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`,
getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy) getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy)
) as any ) as any
if (rootRes.statusCode !== 200) { if (rootRes.statusCode !== 200) return false
return false
}
const rootSha = rootRes.body.commit.commit.tree.sha const rootSha = rootRes.body.commit.commit.tree.sha
// TODO: if there are more than 10000 files in the folder, it will be truncated // TODO: if there are more than 10000 files in the folder, it will be truncated
// Rare cases, not considered for now // Rare cases, not considered for now
// get sha of the folder tree
const treeRes = await got( const treeRes = await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/(^\/+|\/+$)/g, '')}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/(^\/+|\/+$)/g, '')}`,
getOptions('GET', this.commonHeaders, { getOptions('GET', this.commonHeaders, {
recursive: true recursive: true
}, 'json', undefined, undefined, this.proxy) }, 'json', undefined, undefined, this.proxy)
) as any ) as any
if (treeRes.statusCode !== 200) { if (treeRes.statusCode !== 200) return false
return false
}
const oldTree = treeRes.body.tree const oldTree = treeRes.body.tree
// create a new tree
const newTree = oldTree.filter((item: any) => item.type === 'blob') const newTree = oldTree.filter((item: any) => item.type === 'blob')
.map((item:any) => ({ .map((item:any) => ({
path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`, path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`,
@@ -294,10 +310,9 @@ class GithubApi {
tree: newTree tree: newTree
}), undefined, this.proxy) }), undefined, this.proxy)
) as any ) as any
if (newTreeShaRes.statusCode !== 201) { if (newTreeShaRes.statusCode !== 201) return false
return false
}
const newTreeSha = newTreeShaRes.body.sha const newTreeSha = newTreeShaRes.body.sha
// create a new commit
const commitRes = await got( const commitRes = await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/commits`, `${this.baseUrl}/repos/${this.username}/${repo}/git/commits`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({ getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
@@ -306,10 +321,9 @@ class GithubApi {
parents: [refSha] parents: [refSha]
}), undefined, this.proxy) }), undefined, this.proxy)
) as any ) as any
if (commitRes.statusCode !== 201) { if (commitRes.statusCode !== 201) return false
return false
}
const commitSha = commitRes.body.sha const commitSha = commitRes.body.sha
// update the branch
const updateRefRes = await got( const updateRefRes = await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`, `${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({ getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({
+6 -10
View File
@@ -81,16 +81,12 @@ class ImgurApi {
result.push(...res.body.data) result.push(...res.body.data)
initPage++ initPage++
} while (res.body.data.length > 0) } while (res.body.data.length > 0)
const finalResult = [] as any[] const finalResult = result.map((item: any) => ({
for (let i = 0; i < result.length; i++) { ...item,
const item = result[i] Name: item.title,
finalResult.push({ Location: item.id,
...item, CreationDate: item.datetime
Name: item.title, })) as any[]
Location: item.id,
CreationDate: item.datetime
})
}
finalResult.push({ finalResult.push({
Name: '全部', Name: '全部',
Location: 'unclassified', Location: 'unclassified',
+69 -80
View File
@@ -1,17 +1,34 @@
// Axios
import axios from 'axios' import axios from 'axios'
// 加密函数、获取文件 MIME 类型、新的下载器、错误格式化函数、并发异步任务池
import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '../utils/common' import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '../utils/common'
// 七牛云客户端库
import qiniu from 'qiniu/index' import qiniu from 'qiniu/index'
// 路径处理库
import path from 'path' import path from 'path'
// 是否为图片的判断函数
import { isImage } from '~/renderer/manage/utils/common' import { isImage } from '~/renderer/manage/utils/common'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
import UpDownTaskQueue,
{ // 上传下载任务队列
uploadTaskSpecialStatus, import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
commonTaskStatus
} from '../datastore/upDownTaskQueue' // 日志记录器
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static' import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
class QiniuApi { class QiniuApi {
@@ -49,16 +66,17 @@ class QiniuApi {
} }
formatFile (item: any, slicedPrefix: string, urlPrefix: string) { formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
const fileName = item.key.replace(slicedPrefix, '')
return { return {
...item, ...item,
fileName: item.key.replace(slicedPrefix, ''), fileName,
url: `${urlPrefix}/${item.key}`, url: `${urlPrefix}/${item.key}`,
fileSize: item.fsize, fileSize: item.fsize,
formatedTime: new Date(parseInt(item.putTime.toString().slice(0, -4), 10)).toLocaleString(), formatedTime: new Date(parseInt(item.putTime.toString().slice(0, -4), 10)).toLocaleString(),
isDir: false, isDir: false,
checked: false, checked: false,
match: false, match: false,
isImage: isImage(item.key.replace(slicedPrefix, '')) isImage: isImage(fileName)
} }
} }
@@ -71,22 +89,20 @@ class QiniuApi {
contentType: string, contentType: string,
xQiniuHeaders?: IStringKeyMap xQiniuHeaders?: IStringKeyMap
) { ) {
let signStr = `${method.toUpperCase()} ${urlPath}` let signStr = `${method.toUpperCase()} ${urlPath}${query ? `?${query}` : ''}\nHost: ${host}`
query && (signStr += `?${query}`)
signStr += `\nHost: ${host}`
contentType && (signStr += `\nContent-Type: ${contentType}`) contentType && (signStr += `\nContent-Type: ${contentType}`)
let xQiniuHeaderStr = ''
if (xQiniuHeaders) { if (xQiniuHeaders) {
const xQiniuHeaderKeys = Object.keys(xQiniuHeaders).sort() const xQiniuHeaderStr = Object.keys(xQiniuHeaders)
xQiniuHeaderKeys.forEach((key) => { .sort()
xQiniuHeaderStr += `\n${key}:${xQiniuHeaders[key]}` .map((key) => `\n${key}:${xQiniuHeaders[key]}`)
}) .join('')
signStr += xQiniuHeaderStr signStr += xQiniuHeaderStr
} }
signStr += '\n\n' signStr += '\n\n'
if (contentType !== 'application/octet-stream' && body) {
signStr += body if (contentType !== 'application/octet-stream' && body) signStr += body
}
return `Qiniu ${this.accessKey}:${hmacSha1Base64(this.secretKey, signStr).replace(/\+/g, '-').replace(/\//g, '_')}` return `Qiniu ${this.accessKey}:${hmacSha1Base64(this.secretKey, signStr).replace(/\+/g, '-').replace(/\//g, '_')}`
} }
@@ -103,28 +119,21 @@ class QiniuApi {
}, },
timeout: this.timeout timeout: this.timeout
}) })
if (res && res.status === 200) { if (res?.status === 200 && res?.data?.length) {
if (res.data && res.data.length) { const result = [] as any[]
const result = [] as any[] for (let i = 0; i < res.data.length; i++) {
for (let i = 0; i < res.data.length; i++) { const info = await this.getBucketInfo({ bucketName: res.data[i] })
const info = await this.getBucketInfo({ bucketName: res.data[i] }) if (!info.success) return []
if (!info.success) { result.push({
return [] Name: res.data[i],
} Location: info.zone,
result.push({ CreationDate: new Date().toISOString(),
Name: res.data[i], Private: info.private
Location: info.zone, })
CreationDate: new Date().toISOString(),
Private: info.private
})
}
return result
} else {
return []
} }
} else { return result
return []
} }
return []
} }
/** /**
@@ -148,17 +157,15 @@ class QiniuApi {
}, },
timeout: this.timeout timeout: this.timeout
}) })
if (res && res.status === 200) { return res?.status === 200
return { ? {
success: true, success: true,
private: res.data.private, private: res.data.private,
zone: res.data.zone zone: res.data.zone
} }
} else { : {
return {
success: false success: false
} }
}
} }
/** /**
@@ -178,11 +185,7 @@ class QiniuApi {
}, },
timeout: this.timeout timeout: this.timeout
}) })
if (res && res.status === 200) { return res?.status === 200 && res?.data?.length ? res.data : []
return res.data && res.data.length ? res.data : []
} else {
return []
}
} }
/** /**
@@ -209,7 +212,7 @@ class QiniuApi {
}, },
timeout: this.timeout timeout: this.timeout
}) })
return res && res.status === 200 return res?.status === 200
} }
/** /**
@@ -222,8 +225,7 @@ class QiniuApi {
* } * }
*/ */
async createBucket (configMap: IStringKeyMap): Promise<boolean> { async createBucket (configMap: IStringKeyMap): Promise<boolean> {
const { BucketName, region } = configMap const { BucketName, region, acl } = configMap
const { acl } = configMap
const urlPath = `/mkbucketv3/${BucketName}/region/${region}` const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json') const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
const res = await axios({ const res = await axios({
@@ -236,15 +238,12 @@ class QiniuApi {
}, },
timeout: this.timeout timeout: this.timeout
}) })
if (res && res.status === 200) { return res?.status === 200
const changeAclRes = await this.setBucketAclPolicy({ ? await this.setBucketAclPolicy({
bucketName: BucketName, bucketName: BucketName,
isPrivate: !acl isPrivate: !acl
}) })
return changeAclRes : false
} else {
return false
}
} }
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> { async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
@@ -407,19 +406,19 @@ class QiniuApi {
} }
}) })
}) })
if (res && res.respInfo.statusCode === 200) { if (res?.respInfo?.statusCode === 200) {
if (res.respBody && res.respBody.commonPrefixes) { if (res.respBody?.commonPrefixes) {
res.respBody.commonPrefixes.forEach((item: string) => { res.respBody.commonPrefixes.forEach((item: string) => {
result.fullList.push(this.formatFolder(item, slicedPrefix)) result.fullList.push(this.formatFolder(item, slicedPrefix))
}) })
} }
if (res.respBody && res.respBody.items) { if (res.respBody?.items) {
res.respBody.items.forEach((item: any) => { res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)) item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
}) })
} }
result.isTruncated = !!(res.respBody && res.respBody.marker) result.isTruncated = !!(res.respBody?.marker)
result.nextMarker = res.respBody && res.respBody.marker ? res.respBody.marker : '' result.nextMarker = res.respBody?.marker ? res.respBody.marker : ''
result.success = true result.success = true
} }
return result return result
@@ -450,11 +449,7 @@ class QiniuApi {
} }
}) })
}) as any }) as any
if (res && res.respInfo.statusCode === 200) { return res?.respInfo?.statusCode === 200
return true
} else {
return false
}
} }
/** /**
@@ -487,12 +482,12 @@ class QiniuApi {
} }
}) })
}) as any }) as any
if (res && res.respInfo.statusCode === 200) { if (res?.respInfo?.statusCode === 200) {
if (res.respBody && res.respBody.items) { if (res.respBody?.items) {
allFileList.Contents = allFileList.Contents.concat(res.respBody.items) allFileList.Contents = allFileList.Contents.concat(res.respBody.items)
} }
isTruncated = !!(res.respBody && res.respBody.marker) isTruncated = !!(res.respBody?.marker)
marker = res.respBody && res.respBody.marker ? res.respBody.marker : '' marker = res.respBody?.marker ? res.respBody.marker : ''
} else { } else {
return false return false
} }
@@ -514,9 +509,7 @@ class QiniuApi {
} }
}) })
}) as any }) as any
if (!(res && res.respInfo.statusCode === 200)) { if (res?.respInfo?.statusCode !== 200) return false
return false
}
} }
return true return true
} }
@@ -549,7 +542,7 @@ class QiniuApi {
} }
}) })
}) as any }) as any
return res && res.respInfo.statusCode === 200 return res?.respInfo?.statusCode === 200
} }
/** /**
@@ -671,11 +664,7 @@ class QiniuApi {
} }
}) })
}) as any }) as any
if (res && res.respInfo.statusCode === 200) { return res?.respInfo?.statusCode === 200
return true
} else {
return false
}
} }
/** /**
+78 -67
View File
@@ -1,3 +1,4 @@
// AWS S3 相关
import { import {
S3Client, S3Client,
ListBucketsCommand, ListBucketsCommand,
@@ -13,24 +14,48 @@ import {
DeleteObjectsCommand, DeleteObjectsCommand,
PutObjectCommand PutObjectCommand
} from '@aws-sdk/client-s3' } from '@aws-sdk/client-s3'
// AWS S3 上传和进度
import { Upload, Progress } from '@aws-sdk/lib-storage' import { Upload, Progress } from '@aws-sdk/lib-storage'
// AWS S3 请求签名
import { getSignedUrl } from '@aws-sdk/s3-request-presigner' import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
// HTTP 和 HTTPS 模块
import https from 'https' import https from 'https'
import http from 'http' import http from 'http'
// 日志记录器
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
// 端点地址格式化函数、错误格式化函数、获取请求代理、获取文件 MIME 类型、新的下载器、并发异步任务池
import { formatEndpoint, formatError, getAgent, getFileMimeType, NewDownloader, ConcurrencyPromisePool } from '../utils/common' import { formatEndpoint, formatError, getAgent, getFileMimeType, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
// 是否为图片的判断函数、HTTP 代理格式化函数
import { isImage, formatHttpProxy } from '@/manage/utils/common' import { isImage, formatHttpProxy } from '@/manage/utils/common'
// HTTP 和 HTTPS 代理库
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent' import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
import UpDownTaskQueue,
{ // 上传下载任务队列
uploadTaskSpecialStatus, import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus } from '../datastore/upDownTaskQueue'
commonTaskStatus
} from '../datastore/upDownTaskQueue' // 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
// 路径处理库
import path from 'path' import path from 'path'
// 取消下载任务的加载文件列表、刷新下载文件传输列表
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static' import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
interface S3plistApiOptions { interface S3plistApiOptions {
@@ -79,18 +104,14 @@ class S3plistApi {
} }
setAgent (proxy: string | undefined, sslEnabled: boolean) : HttpProxyAgent | HttpsProxyAgent | undefined { setAgent (proxy: string | undefined, sslEnabled: boolean) : HttpProxyAgent | HttpsProxyAgent | undefined {
if (sslEnabled) { const protocol = sslEnabled ? 'https' : 'http'
const agent = getAgent(proxy, true).https const agent = getAgent(proxy, sslEnabled)[protocol]
return agent ?? new https.Agent({ const commonOptions = { keepAlive: true }
keepAlive: true, const extraOptions = sslEnabled ? { rejectUnauthorized: false } : {}
rejectUnauthorized: false return agent ?? new (sslEnabled ? https.Agent : http.Agent)({
}) ...commonOptions,
} else { ...extraOptions
const agent = getAgent(proxy, false).http })
return agent ?? new http.Agent({
keepAlive: true
})
}
} }
logParam = (error:any, method: string) => logParam = (error:any, method: string) =>
@@ -111,17 +132,18 @@ class S3plistApi {
} }
formatFile (item: _Object, slicedPrefix: string, urlPrefix: string): any { formatFile (item: _Object, slicedPrefix: string, urlPrefix: string): any {
const fileName = item.Key?.replace(slicedPrefix, '')
return { return {
...item, ...item,
key: item.Key, key: item.Key,
url: `${urlPrefix}/${item.Key}`, url: `${urlPrefix}/${item.Key}`,
fileName: item.Key?.replace(slicedPrefix, ''), fileName,
fileSize: item.Size, fileSize: item.Size,
formatedTime: new Date(item.LastModified!).toLocaleString(), formatedTime: new Date(item.LastModified!).toLocaleString(),
isDir: false, isDir: false,
checked: false, checked: false,
match: false, match: false,
isImage: isImage(item.Key?.replace(slicedPrefix, '') || '') isImage: isImage(fileName || '')
} }
} }
@@ -130,50 +152,43 @@ class S3plistApi {
*/ */
async getBucketList (): Promise<any> { async getBucketList (): Promise<any> {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig const options = Object.assign({}, this.baseOptions) as S3ClientConfig
const result = [] as IStringKeyMap[] const result: IStringKeyMap[] = []
const endpoint = options.endpoint as string || '' as string const endpoint = options.endpoint as string || ''
options.region = endpoint.indexOf('cloudflarestorage') !== -1 ? 'auto' : 'us-east-1' options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
try { try {
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListBucketsCommand({}) const data = await client.send(new ListBucketsCommand({}))
const data = await client.send(command)
if (data.$metadata.httpStatusCode === 200) { if (data.$metadata.httpStatusCode !== 200) {
if (data.Buckets) { this.logParam(data, 'getBucketList')
if (endpoint.indexOf('cloudflarestorage') !== -1) { return result
data.Buckets.forEach((bucket) => { }
result.push({
Name: bucket.Name, if (data.Buckets) {
CreationDate: bucket.CreationDate, if (endpoint.includes('cloudflarestorage')) {
Location: 'auto' result.push(...data.Buckets.map(bucket => ({
}) Name: bucket.Name,
CreationDate: bucket.CreationDate,
Location: 'auto'
})))
} else {
for (const bucket of data.Buckets) {
const bucketName = bucket.Name
const bucketConfig = await client.send(new GetBucketLocationCommand({
Bucket: bucketName
}))
result.push({
Name: bucketName,
CreationDate: bucket.CreationDate,
Location: bucketConfig.$metadata.httpStatusCode === 200
? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
: 'us-east-1'
}) })
} else { if (bucketConfig.$metadata.httpStatusCode !== 200) {
for (let i = 0; i < data.Buckets.length; i++) { this.logParam(bucketConfig, 'getBucketList')
const bucket = data.Buckets[i]
const bucketName = bucket.Name
const command = new GetBucketLocationCommand({
Bucket: bucketName
})
const bucketConfig = await client.send(command)
if (bucketConfig.$metadata.httpStatusCode === 200) {
result.push({
Name: bucketName,
CreationDate: bucket.CreationDate,
Location: bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
})
} else {
this.logParam(bucketConfig, 'getBucketList')
result.push({
Name: bucketName,
CreationDate: bucket.CreationDate,
Location: 'us-east-1'
})
}
} }
} }
} }
} else {
this.logParam(data, 'getBucketList')
} }
} catch (error) { } catch (error) {
this.logParam(error, 'getBucketList') this.logParam(error, 'getBucketList')
@@ -312,8 +327,7 @@ class S3plistApi {
success: false success: false
} }
try { try {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig const options = Object.assign({}, { ...this.baseOptions, region: region || 'us-east-1' }) as S3ClientConfig
options.region = region || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
Bucket: bucket, Bucket: bucket,
@@ -324,12 +338,10 @@ class S3plistApi {
}) })
const data = await client.send(command) const data = await client.send(command)
if (data.$metadata.httpStatusCode === 200) { if (data.$metadata.httpStatusCode === 200) {
data.CommonPrefixes && data.CommonPrefixes.forEach((item: CommonPrefix) => { result.fullList = [
result.fullList.push(this.formatFolder(item, slicedPrefix)) ...(data.CommonPrefixes?.map(item => this.formatFolder(item, slicedPrefix)) || []),
}) ...(data.Contents?.map(item => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
data.Contents && data.Contents.forEach((item: _Object) => { ]
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
result.isTruncated = data.IsTruncated || false result.isTruncated = data.IsTruncated || false
result.nextMarker = data.NextContinuationToken || '' result.nextMarker = data.NextContinuationToken || ''
result.success = true result.success = true
@@ -354,8 +366,7 @@ class S3plistApi {
const { bucketName, region, oldKey, newKey } = configMap const { bucketName, region, oldKey, newKey } = configMap
let result = false let result = false
try { try {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig const options = Object.assign({}, { ...this.baseOptions, region: region || 'us-east-1' }) as S3ClientConfig
options.region = region || 'us-east-1'
const client = new S3Client(options) const client = new S3Client(options)
const command = new CopyObjectCommand({ const command = new CopyObjectCommand({
Bucket: bucketName, Bucket: bucketName,
+41 -28
View File
@@ -1,13 +1,34 @@
// 是否为图片的判断函数
import { isImage } from '@/manage/utils/common' import { isImage } from '@/manage/utils/common'
// Axios 和 Axios 实例类型声明
import axios, { AxiosInstance } from 'axios' import axios, { AxiosInstance } from 'axios'
// 窗口管理器
import windowManager from 'apis/app/window/windowManager' import windowManager from 'apis/app/window/windowManager'
// 枚举类型声明
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
// Electron 相关
import { ipcMain, IpcMainEvent } from 'electron' import { ipcMain, IpcMainEvent } from 'electron'
// 表单数据库
import FormData from 'form-data' import FormData from 'form-data'
// 文件系统库
import fs from 'fs-extra' import fs from 'fs-extra'
// 获取文件 MIME 类型、got 上传函数、新的下载器、并发异步任务池、错误格式化函数
import { getFileMimeType, gotUpload, NewDownloader, ConcurrencyPromisePool, formatError } from '../utils/common' import { getFileMimeType, gotUpload, NewDownloader, ConcurrencyPromisePool, formatError } from '../utils/common'
// 路径处理库
import path from 'path' import path from 'path'
// 上传下载任务队列
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue' import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
// 日志记录器
import { ManageLogger } from '../utils/logger' import { ManageLogger } from '../utils/logger'
class SmmsApi { class SmmsApi {
@@ -99,7 +120,7 @@ class SmmsApi {
return return
} }
marker++ marker++
} while (!cancelTask[0] && res && res.status === 200 && res.data && res.data.success && res.data.CurrentPage < res.data.TotalPages) } while (!cancelTask[0] && res?.status === 200 && res?.data?.success && res.data.CurrentPage < res.data.TotalPages)
result.success = !cancelTask[0] result.success = !cancelTask[0]
result.finished = true result.finished = true
window.webContents.send('refreshFileTransferList', result) window.webContents.send('refreshFileTransferList', result)
@@ -121,16 +142,14 @@ class SmmsApi {
* customUrl: string * customUrl: string
* } * }
*/ */
async getBucketFileList (configMap: IStringKeyMap): Promise<any> { async getBucketFileList ({ currentPage }: IStringKeyMap): Promise<any> {
const { currentPage } = configMap
let res = {} as any
const result = { const result = {
fullList: <any>[], fullList: <any>[],
isTruncated: false, isTruncated: false,
nextMarker: '', nextMarker: '',
success: false success: false
} }
res = await this.axiosInstance( const res = await this.axiosInstance(
'/upload_history', '/upload_history',
{ {
method: 'GET', method: 'GET',
@@ -142,21 +161,17 @@ class SmmsApi {
} }
} }
) )
if (res && res.status === 200 && res.data && res.data.success) { if (res?.status !== 200 || !res?.data?.success) return result
if (res.data.Count === 0) {
result.success = true if (res.data.Count === 0) return { ...result, success: true }
return result
} res.data.data.forEach((item: any) => {
res.data.data.forEach((item: any) => { result.fullList.push(this.formatFile(item))
result.fullList.push(this.formatFile(item)) })
}) result.isTruncated = res.data.CurrentPage < res.data.TotalPages
result.isTruncated = res.data.CurrentPage < res.data.TotalPages result.nextMarker = res.data.CurrentPage + 1
result.nextMarker = res.data.CurrentPage + 1 result.success = true
result.success = true return result
return result
} else {
return result
}
} }
/** /**
@@ -169,20 +184,18 @@ class SmmsApi {
* DeleteHash: string * DeleteHash: string
* } * }
*/ */
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> { async deleteBucketFile ({ DeleteHash }: IStringKeyMap): Promise<boolean> {
const { DeleteHash } = configMap
const params = {
hash: DeleteHash,
format: 'json'
}
const res = await this.axiosInstance( const res = await this.axiosInstance(
`/delete/${DeleteHash}`, `/delete/${DeleteHash}`,
{ {
method: 'GET', method: 'GET',
params params: {
hash: DeleteHash,
format: 'json'
}
} }
) )
return res && res.status === 200 && res.data && res.data.success return res?.status === 200 && res?.data?.success
} }
/** /**