diff --git a/tone-page-web/app/console/(with-menu)/storage/components/UploadManager.tsx b/tone-page-web/app/console/(with-menu)/storage/components/UploadManager.tsx index 0331419..dbaf978 100644 --- a/tone-page-web/app/console/(with-menu)/storage/components/UploadManager.tsx +++ b/tone-page-web/app/console/(with-menu)/storage/components/UploadManager.tsx @@ -16,7 +16,7 @@ import { Progress } from '@/components/ui/progress'; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { OssStore } from "@/lib/oss/OssStore"; -import { Checkpoint } from "ali-oss"; +import OSS, { Checkpoint } from "ali-oss"; interface UploadManagerProps { children: React.ReactNode; @@ -82,7 +82,12 @@ export function UploadManager({ children, ossStore, handleRefreshFileList }: Upl } const [isUploading, setIsUploading] = useState(false); + /** 开始上传文件 */ const handleUpload = async () => { + if (!ossStore) return; + let store: OSS; + try { store = ossStore.getStore(); } catch { return toast.error(`初始化失败`) }; + const needUploadFiles = filesList.filter(f => f.status !== 'finish'); if (needUploadFiles.length === 0) return toast.info('请选择需要上传的文件'); @@ -90,7 +95,7 @@ export function UploadManager({ children, ossStore, handleRefreshFileList }: Upl setIsUploading(true); for (const fileItem of needUploadFiles) { fileItem.status = 'uploading'; - await startUploadFile(fileItem).catch(() => { fileItem.status = 'failed'; failCount++; }); + await startUploadFile(store, fileItem, ossStore.getWorkDir()).catch(() => { fileItem.status = 'failed'; failCount++; }); fileItem.status = 'finish'; } setIsUploading(false); @@ -105,12 +110,11 @@ export function UploadManager({ children, ossStore, handleRefreshFileList }: Upl handleRefreshFileList?.(); } - // 开始上传文件 - const startUploadFile = async (fileItem: UploadFileItem) => { - if (!ossStore) return; - + // 上传单个文件 + const startUploadFile = async (store: OSS, fileItem: UploadFileItem, workDir?: string) => { let checkpoint: Checkpoint | undefined; - await ossStore.storeMeta.store?.multipartUpload(`${ossStore.getWorkDir()}/${fileItem.file.name}`, fileItem.file, { + + await store.multipartUpload(`${workDir ? `${workDir}/` : ''}${fileItem.file.name}`, fileItem.file, { checkpoint: checkpoint, progress: (p, cpt) => { setFileList(currentFileList => { diff --git a/tone-page-web/app/console/(with-menu)/storage/page.tsx b/tone-page-web/app/console/(with-menu)/storage/page.tsx index 8ce26f4..1925f92 100644 --- a/tone-page-web/app/console/(with-menu)/storage/page.tsx +++ b/tone-page-web/app/console/(with-menu)/storage/page.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Delete, Download, RefreshCcw, Upload } from 'lucide-react'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { DropdownMenu, @@ -14,6 +14,8 @@ import { } from "@/components/ui/dropdown-menu" import { UploadManager } from './components/UploadManager'; import { OssObjectItem, OssObjectList, OssStore } from '@/lib/oss/OssStore'; +import { useOssStore } from '@/hooks/admin/web/blog/use-oss-store'; +import OSS from 'ali-oss'; const formatSizeNumber = (n: number) => { @@ -29,17 +31,41 @@ const formatSizeNumber = (n: number) => { } } - +const ossStore = new OssStore(); export default function Page() { const [objectList, setObjectList] = useState(null) - const ossStore = new OssStore({ - prefix: 'tone-page', - prefixAddUserId: true, - objectList: () => objectList, - setObjectList, - }); + ossStore.setSetObjectList(setObjectList); + + const storeMeta = useOssStore(); + + useEffect(() => { + const data = storeMeta.stsTokenData; + if (!data) return; + + const store = new OSS({ + region: 'oss-cn-chengdu', + bucket: 'tone-personal', + accessKeyId: data.AccessKeyId, + accessKeySecret: data.AccessKeySecret, + stsToken: data.SecurityToken, + refreshSTSToken: async () => { + await storeMeta.refresh(); + if (!storeMeta.stsTokenData) throw new Error(); + const { AccessKeyId, AccessKeySecret, SecurityToken } = storeMeta.stsTokenData; + return { + accessKeyId: AccessKeyId, + accessKeySecret: AccessKeySecret, + stsToken: SecurityToken, + }; + }, + }) + + ossStore.setStore(store); + ossStore.setWorkDir(`tone-page/${data.userId}`) + ossStore.loadObjectList(); + }, [storeMeta.stsTokenData]); const handleRefreshFileList = async () => ossStore.loadObjectList().catch(e => toast.error(e.message)); const handleCheckboxChange = ossStore.handleObjectCheckedStateChanged.bind(ossStore); diff --git a/tone-page-web/hooks/admin/web/blog/use-oss-store.ts b/tone-page-web/hooks/admin/web/blog/use-oss-store.ts index 3ef6cb2..92eb16c 100644 --- a/tone-page-web/hooks/admin/web/blog/use-oss-store.ts +++ b/tone-page-web/hooks/admin/web/blog/use-oss-store.ts @@ -3,30 +3,22 @@ import { StsToken } from "@/lib/api/oss"; import OSS from "ali-oss"; import { useEffect } from "react"; -export function useOssStore(options: { region: string; bucket: string; onStsTokenDataChanged?: (data: StsToken | undefined) => void; }) { - const { stsTokenData, isLoading, error } = useOssSts(); +export function useOssStore(options: { onStsTokenDataChanged?: (data: StsToken | undefined) => void; } = {}) { + const { stsTokenData, isLoading, error, mutate } = useOssSts(); useEffect(() => { options.onStsTokenDataChanged?.(stsTokenData); - }, [stsTokenData]) + }, [stsTokenData]); + + /** @todo */ + const refresh = async () => { + await mutate(); + } return { stsTokenData, isLoading, error, - store: stsTokenData ? new OSS({ - region: options.region, - bucket: options.bucket, - accessKeyId: stsTokenData.AccessKeyId, - accessKeySecret: stsTokenData.AccessKeySecret, - stsToken: stsTokenData.SecurityToken, - refreshSTSToken: () => new Promise(resolve => { - resolve({ - accessKeyId: stsTokenData.AccessKeyId, - accessKeySecret: stsTokenData.AccessKeySecret, - stsToken: stsTokenData.SecurityToken, - }) - }), - }) : undefined, + refresh, } } \ No newline at end of file diff --git a/tone-page-web/hooks/oss/use-oss-sts.ts b/tone-page-web/hooks/oss/use-oss-sts.ts index cc0ffa9..e3dde37 100644 --- a/tone-page-web/hooks/oss/use-oss-sts.ts +++ b/tone-page-web/hooks/oss/use-oss-sts.ts @@ -3,12 +3,12 @@ import { toast } from "sonner"; import useSWR from "swr"; export function useOssSts() { - const { data: stsTokenData, isLoading, error } = useSWR( + const { data: stsTokenData, isLoading, error, mutate } = useSWR( '/api/oss/sts', () => OssApi.getStsToken(), { shouldRetryOnError: false, - refreshInterval: 59 * 60 * 1000, + // refreshInterval: 59 * 60 * 1000, revalidateOnFocus: false, onError: (e) => { toast.error(`${e.message || e}`) @@ -20,5 +20,6 @@ export function useOssSts() { stsTokenData, isLoading, error, + mutate, } } \ No newline at end of file diff --git a/tone-page-web/lib/oss/OssStore.ts b/tone-page-web/lib/oss/OssStore.ts index 9759da0..4ebd8e9 100644 --- a/tone-page-web/lib/oss/OssStore.ts +++ b/tone-page-web/lib/oss/OssStore.ts @@ -1,5 +1,7 @@ import { useOssStore } from "@/hooks/admin/web/blog/use-oss-store"; -import { Dispatch, SetStateAction, useState } from "react"; +import { Dispatch, SetStateAction } from "react"; +import { StsToken } from "../api/oss"; +import OSS from "ali-oss"; export interface OssObjectItem { id: string; @@ -14,25 +16,23 @@ export type OssObjectList = OssObjectItem[] | null; export class OssStore { private setObjectList?: Dispatch>; + public store?: OSS; + private workDir: string; - public storeMeta: ReturnType; - - constructor(private options: { - prefix?: string; - prefixAddUserId?: boolean; - objectList?: () => (OssObjectList | null); + constructor(options: { + workDir?: string; setObjectList?: Dispatch>; } = {}) { + this.workDir = options.workDir ?? ''; this.setObjectList = options.setObjectList; + } - this.storeMeta = useOssStore({ - region: 'oss-cn-chengdu', - bucket: 'tone-personal', - onStsTokenDataChanged: (data) => { - if (!data) return; - this.loadObjectList(); - } - }); + public setStore(store: OSS | undefined) { + this.store = store; + } + + public setSetObjectList(setObjectList: Dispatch>) { + this.setObjectList = setObjectList; } public async loadObjectList() { @@ -40,7 +40,7 @@ export class OssStore { throw new Error('setObjectList need provided'); } - const store = await this.getStore(); + const store = this.getStore(); this.setObjectList(null); const workDir = this.getWorkDir(); @@ -70,17 +70,15 @@ export class OssStore { } public async deleteObject(objectItem: OssObjectItem) { - if (!this.storeMeta.store || !this.storeMeta.stsTokenData) { - throw new Error('初始化失败,请刷新界面重试'); - } + const store = this.getStore(); const objectName = this.getObjectNameByLocalname(objectItem.name); - const delRes = await this.storeMeta.store.delete(objectName).catch(() => null); + const delRes = await store.delete(objectName).catch(() => null); if (!delRes) throw new Error('删除失败'); } public async deleteCheckedObjects(objectItems: OssObjectItem[]) { - if (!this.storeMeta.store || !this.storeMeta.stsTokenData) { + if (!this.getStore()) { throw new Error('初始化失败,请刷新界面重试'); } @@ -95,11 +93,12 @@ export class OssStore { } public async downloadObject(objectItem: OssObjectItem) { - if (!this.storeMeta.store || !this.storeMeta.stsTokenData) { + const store = this.getStore(); + if (!store) { throw new Error('初始化失败,请刷新界面重试'); } - const url = this.storeMeta.store.signatureUrl(this.getObjectNameByLocalname(objectItem.name)); + const url = store.signatureUrl(this.getObjectNameByLocalname(objectItem.name)); const a = document.createElement('a'); a.href = url; a.download = objectItem.name; @@ -109,11 +108,14 @@ export class OssStore { document.body.removeChild(a); } - private async getStore() { - if (!this.storeMeta.store || !this.storeMeta.stsTokenData) { + /** + * @throws Error + */ + public getStore() { + if (!this.store) { throw new Error('初始化失败,请刷新界面重试'); } - return this.storeMeta.store; + return this.store; } private getObjectNameByLocalname(localName: string) { @@ -121,13 +123,10 @@ export class OssStore { } public getWorkDir() { - const { stsTokenData } = this.storeMeta; - if (!stsTokenData) return; + return this.workDir; + } - if (this.options.prefixAddUserId) { - return `${this.options.prefix ? `${this.options.prefix}/` : ''}${stsTokenData.userId}`; - } else { - return this.options.prefix; - } + public setWorkDir(dir: string) { + this.workDir = dir; } } \ No newline at end of file