import { App, Button, Form, Input, Select, Switch, Tag } from "antd"; import { Cloud, ShieldCheck } from "lucide-react"; import { useEffect, useState } from "react"; import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints, S3_PRESET_OPTIONS, type OSSConnectionTestResult, type OSSProvider, type S3Preset } from "@/lib/oss-settings"; import { getUserOSSSetting, testUserOSSConnection, updateUserOSSSetting, type UserOSSSetting } from "@/services/api/resources"; import { useUserStore } from "@/stores/use-user-store"; type OSSFormValues = { enabled?: boolean; provider: OSSProvider; s3Preset?: S3Preset; region?: string; endpoint?: string; cdnBaseUrl?: string; bucket?: string; accessKeyId?: string; accessKeySecret?: string; sessionToken?: string; pathPrefix?: string; pathStyle?: boolean; }; export function UserOSSSettingsForm() { const actor = useUserStore((state) => state.user); const { message } = App.useApp(); const [form] = Form.useForm(); const [setting, setSetting] = useState(null); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(null); const [testStale, setTestStale] = useState(false); const savedAt = formatSavedAt(setting?.updatedAt); const provider = Form.useWatch("provider", form) || "aliyun"; const isTencentCOS = provider === "tencent"; const isQiniuKodo = provider === "qiniu"; const isS3 = provider === "s3"; const s3Preset = Form.useWatch("s3Preset", form) || "custom"; const hasCurrentProviderSecret = Boolean(setting && setting.provider === provider && setting.hasAccessKeySecret); const accessKeyIdLabel = isTencentCOS ? "SecretId" : isQiniuKodo ? "AccessKey" : "AccessKey ID"; const accessKeySecretLabel = isTencentCOS ? "SecretKey" : isQiniuKodo ? "SecretKey" : "AccessKey Secret"; useEffect(() => { if (!actor?.id) return; let active = true; setLoading(true); void getUserOSSSetting() .then((data) => { if (!active) return; setSetting(data.setting); form.setFieldsValue(toFormValues(data.setting)); setTestResult(data.setting.testedAt ? { ok: true, testedAt: data.setting.testedAt, testedDigest: data.setting.testedDigest } : null); }) .catch((error) => active && message.error(error instanceof Error ? error.message : "读取个人对象存储配置失败")) .finally(() => active && setLoading(false)); return () => { active = false; }; }, [actor?.id, form, message]); if (!actor) { return
登录后可配置个人对象存储。
; } const save = async () => { const values = await form.validateFields(); if (values.enabled && values.provider === "s3" && !setting?.allowUserS3) return message.error("平台管理员尚未允许个人 S3 兼容存储"); setSaving(true); try { const data = await updateUserOSSSetting({ enabled: values.enabled === true, provider: values.provider || "aliyun", s3Preset: values.s3Preset || "custom", region: values.region?.trim() || "", endpoint: values.endpoint?.trim() || "", cdnBaseUrl: values.cdnBaseUrl?.trim() || "", bucket: values.bucket?.trim() || "", accessKeyId: values.accessKeyId?.trim() || "", accessKeySecret: values.accessKeySecret?.trim() || "", sessionToken: values.sessionToken?.trim() || "", pathPrefix: values.pathPrefix?.trim() || DEFAULT_OSS_PATH_PREFIX, pathStyle: values.pathStyle === true, }); setSetting(data.setting); form.setFieldsValue(toFormValues(data.setting)); setTestResult(data.setting.testedAt ? { ok: true, testedAt: data.setting.testedAt, testedDigest: data.setting.testedDigest } : null); setTestStale(false); message.success(data.setting.enabled ? "个人对象存储已启用,后续上传将优先使用该存储" : "个人对象存储已停用,后续上传将使用平台存储"); } catch (error) { message.error(error instanceof Error ? error.message : "保存个人对象存储配置失败"); } finally { setSaving(false); } }; const testConnection = async () => { const values = await form.validateFields(); setTesting(true); try { const result = await testUserOSSConnection(connectionInput(values)); setTestResult(result); setTestStale(false); result.ok ? message.success(result.message || "连接测试通过") : message.error(result.message || "连接测试失败"); } catch (error) { setTestResult({ ok: false, message: error instanceof Error ? error.message : "连接测试失败" }); setTestStale(false); } finally { setTesting(false); } }; return (
changesRequireOSSRetest(changed) && setTestStale(true)}>
我的对象存储

启用后,新上传和新生成的媒体优先写入你的存储桶;停用时回退到平台存储。

{setting?.enabled ? "已启用" : "未启用"} }> {setting?.hasAccessKeySecret ? "密钥已加密" : "未保存密钥"}
form.setFieldsValue(getS3PresetHints(preset))} /> ) : null} {isS3 ? ( <> ) : null}
{savedAt ? `上次保存:${savedAt}` : "尚未保存个人对象存储配置"}
); } function formatSavedAt(value?: string) { if (!value) return ""; const date = new Date(value); if (Number.isNaN(date.getTime()) || date.getFullYear() < 2000) return ""; return date.toLocaleString("zh-CN"); } function toFormValues(setting: UserOSSSetting): OSSFormValues { return { enabled: setting.enabled, provider: setting.provider || "aliyun", s3Preset: setting.s3Preset || "custom", region: setting.region, endpoint: setting.endpoint, cdnBaseUrl: setting.cdnBaseUrl, bucket: setting.bucket, accessKeyId: setting.accessKeyId, accessKeySecret: "", sessionToken: "", pathPrefix: setting.pathPrefix || DEFAULT_OSS_PATH_PREFIX, pathStyle: setting.pathStyle === true, }; } function connectionInput(values: OSSFormValues) { return { provider: values.provider, s3Preset: values.s3Preset, region: values.region?.trim() || "", endpoint: values.endpoint?.trim() || "", cdnBaseUrl: values.cdnBaseUrl?.trim() || "", bucket: values.bucket?.trim() || "", accessKeyId: values.accessKeyId?.trim() || "", accessKeySecret: values.accessKeySecret?.trim() || "", sessionToken: values.sessionToken?.trim() || "", pathPrefix: values.pathPrefix?.trim() || DEFAULT_OSS_PATH_PREFIX, pathStyle: values.pathStyle === true, }; } function ConnectionTestStatus({ result, stale }: { result: OSSConnectionTestResult | null; stale: boolean }) { if (stale) return 需重新测试; if (!result) return null; return {result.ok ? `测试通过${result.testedAt ? ` · ${formatSavedAt(result.testedAt)}` : ""}` : result.message || "测试失败"}; }