import { useState, useEffect } from 'react' import { Card, Form, Input, Switch, Button, message, Space, Tag } from 'antd' import { CloudUploadOutlined, SaveOutlined, ApiOutlined } from '@ant-design/icons' import request from '../utils/request' interface CosConfigForm { id?: number secretId: string secretKey: string bucket: string region: string pathPrefix: string domain: string enabled: boolean } export default function CosConfig() { const [form] = Form.useForm() const [loading, setLoading] = useState(false) const [testLoading, setTestLoading] = useState(false) const [migrateLoading, setMigrateLoading] = useState(false) const [migratedCount, setMigratedCount] = useState(null) const [hasSecretKey, setHasSecretKey] = useState(false) const loadConfig = async () => { setLoading(true) try { const res: any = await request.get('/v1/archive/cos-config') const data = res.data if (data) { // 后端返回的 secretKey 是脱敏的,不能作为表单默认值,否则保存时会把掩码存回去 const { secretKey, ...rest } = data form.setFieldsValue({ ...rest, enabled: data.enabled === 1, }) // 如果返回的 SecretKey 是脱敏的(包含*),说明后端有值 setHasSecretKey(secretKey && secretKey.includes('*')) } else { form.setFieldsValue({ pathPrefix: 'archive/media/', enabled: false, }) } } finally { setLoading(false) } } useEffect(() => { loadConfig() }, []) const handleSave = async (values: CosConfigForm) => { const payload = { ...values, enabled: values.enabled ? 1 : 0, } // 如果 SecretKey 为空且已有配置,则保留原值由后端处理 if (!payload.secretKey && hasSecretKey) { payload.secretKey = undefined as any } try { await request.post('/v1/archive/cos-config', payload) message.success('保存成功') loadConfig() } catch (e: any) { message.error(e.response?.data?.message || '保存失败') } } const handleTest = async () => { const values = form.getFieldsValue() if (!values.secretId || !values.bucket || !values.region) { message.warning('请填写 SecretId、Bucket、Region') return } // 如果 SecretKey 为空且已有原值,提示用户输入 SecretKey 后再测试 if (!values.secretKey && hasSecretKey) { message.warning('已保存 SecretKey,但测试连接需要重新输入 SecretKey') return } setTestLoading(true) try { const res: any = await request.post('/v1/archive/cos-config/test', { secretId: values.secretId, secretKey: values.secretKey, bucket: values.bucket, region: values.region, }) if (res.data === true) { message.success('COS 连接测试成功') } else { message.error('COS 连接测试失败,请检查配置') } } catch (e: any) { message.error(e.response?.data?.message || '测试失败') } finally { setTestLoading(false) } } const handleMigrate = async () => { setMigrateLoading(true) setMigratedCount(null) try { const res: any = await request.post('/v1/archive/cos-config/migrate?batchSize=50') setMigratedCount(res.data || 0) message.success(`批量迁移完成,共迁移 ${res.data || 0} 个文件`) } catch (e: any) { message.error(e.response?.data?.message || '迁移失败') } finally { setMigrateLoading(false) } } return (

将已下载到本地的会话存档媒体文件批量上传到腾讯云 COS。上传完成后,后续访问将直接走 COS 链接。

{migratedCount !== null && ( 本次迁移 {migratedCount} 个文件 )}
  • 启用 COS 后,新产生的媒体文件会在下载完成后自动上传到 COS。
  • 媒体访问接口 /api/v1/archive/media/{id} 会优先返回 COS URL(302 重定向)。
  • 如果 COS 未启用或上传失败,会回退到本地文件读取。
  • 自定义域名需以 http:// 或 https:// 开头,保存时会自动处理。
) }