From b50f19bcb43f1fb015e2abaddd0d02a51c40b0a0 Mon Sep 17 00:00:00 2001 From: shiyu Date: Sun, 31 Aug 2025 12:38:21 +0800 Subject: [PATCH] feat: Add application domain and file domain configuration --- api/routes/config.py | 4 +++- api/routes/virtual_fs.py | 9 ++++++++- web/src/api/config.ts | 2 ++ web/src/api/vfs.ts | 2 +- web/src/apps/OfficeViewer/OfficeViewer.tsx | 6 ++++-- .../components/Modals/DirectLinkModal.tsx | 3 +-- .../FileExplorerPage/components/Modals/ShareModal.tsx | 5 ++++- web/src/pages/SharePage.tsx | 5 ++++- web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx | 6 +++--- 9 files changed, 30 insertions(+), 12 deletions(-) diff --git a/api/routes/config.py b/api/routes/config.py index e8e66ce..2f11718 100644 --- a/api/routes/config.py +++ b/api/routes/config.py @@ -41,7 +41,9 @@ async def get_system_status(): "version": VERSION, "title": await ConfigCenter.get("APP_NAME", "Foxel"), "logo": await ConfigCenter.get("APP_LOGO", "/logo.svg"), - "is_initialized": await has_users() + "is_initialized": await has_users(), + "app_domain": await ConfigCenter.get("APP_DOMAIN"), + "file_domain": await ConfigCenter.get("FILE_DOMAIN"), } return success(system_info) diff --git a/api/routes/virtual_fs.py b/api/routes/virtual_fs.py index 2bfa8e7..255afd7 100644 --- a/api/routes/virtual_fs.py +++ b/api/routes/virtual_fs.py @@ -19,6 +19,7 @@ from services.virtual_fs import ( from services.thumbnail import is_image_filename, get_or_create_thumb, is_raw_filename from schemas import MkdirRequest, MoveRequest from api.response import success +from services.config import ConfigCenter router = APIRouter(prefix='/api/fs', tags=["virtual-fs"]) @@ -151,7 +152,13 @@ async def get_temp_link( """获取文件的临时公开访问令牌""" full_path = '/' + full_path if not full_path.startswith('/') else full_path token = await generate_temp_link_token(full_path, expires_in=expires_in) - return success({"token": token, "path": full_path}) + file_domain = await ConfigCenter.get("FILE_DOMAIN") + if file_domain: + file_domain = file_domain.rstrip('/') + url = f"{file_domain}/api/fs/public/{token}" + else: + url = f"/api/fs/public/{token}" + return success({"token": token, "path": full_path, "url": url}) @router.get("/public/{token}") diff --git a/web/src/api/config.ts b/web/src/api/config.ts index e428a6f..0d4b48c 100644 --- a/web/src/api/config.ts +++ b/web/src/api/config.ts @@ -20,6 +20,8 @@ export interface SystemStatus { title: string; logo: string; is_initialized: boolean; + app_domain?: string; + file_domain?: string; } export async function status() { diff --git a/web/src/api/vfs.ts b/web/src/api/vfs.ts index 1363488..30826b1 100644 --- a/web/src/api/vfs.ts +++ b/web/src/api/vfs.ts @@ -51,7 +51,7 @@ export const vfsApi = { streamUrl: (path: string) => `${API_BASE_URL}/fs/stream/${encodeURI(path.replace(/^\/+/, ''))}`, stat: (path: string) => request(`/fs/stat/${encodeURI(path.replace(/^\/+/, ''))}`), getTempLinkToken: (path: string, expiresIn: number = 3600) => - request<{token: string}>(`/fs/temp-link/${encodeURI(path.replace(/^\/+/, ''))}?expires_in=${expiresIn}`), + request<{token: string, path: string, url: string}>(`/fs/temp-link/${encodeURI(path.replace(/^\/+/, ''))}?expires_in=${expiresIn}`), getTempPublicUrl: (token: string) => `${API_BASE_URL}/fs/public/${token}`, uploadStream: (fullPath: string, file: File, overwrite: boolean = true, onProgress?: (loaded: number, total: number) => void) => { const enc = encodeURI(fullPath.replace(/^\/+/, '')); diff --git a/web/src/apps/OfficeViewer/OfficeViewer.tsx b/web/src/apps/OfficeViewer/OfficeViewer.tsx index 9147d4a..db852c9 100644 --- a/web/src/apps/OfficeViewer/OfficeViewer.tsx +++ b/web/src/apps/OfficeViewer/OfficeViewer.tsx @@ -2,8 +2,10 @@ import React, { useEffect, useState } from 'react'; import { vfsApi } from '../../api/client'; import type { AppComponentProps } from '../types'; import { Spin, Result, Button } from 'antd'; +import { useSystemStatus } from '../../contexts/SystemContext'; export const OfficeViewerApp: React.FC = ({ filePath, onRequestClose }) => { + const systemStatus = useSystemStatus(); const [url, setUrl] = useState(); const [loading, setLoading] = useState(true); const [err, setErr] = useState(); @@ -17,8 +19,8 @@ export const OfficeViewerApp: React.FC = ({ filePath, onReque vfsApi.getTempLinkToken(filePath.replace(/^\/+/, '')) .then(res => { if (cancelled) return; - // 注意:vfsApi.getTempPublicUrl 返回的是相对路径,我们需要构建完整的 URL - const fullUrl = new URL(vfsApi.getTempPublicUrl(res.token), window.location.origin).href; + const baseUrl = systemStatus?.file_domain || window.location.origin; + const fullUrl = new URL(res.url, baseUrl).href; const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(fullUrl)}`; setUrl(officeUrl); }) diff --git a/web/src/pages/FileExplorerPage/components/Modals/DirectLinkModal.tsx b/web/src/pages/FileExplorerPage/components/Modals/DirectLinkModal.tsx index 29a4dd6..efbc397 100644 --- a/web/src/pages/FileExplorerPage/components/Modals/DirectLinkModal.tsx +++ b/web/src/pages/FileExplorerPage/components/Modals/DirectLinkModal.tsx @@ -29,8 +29,7 @@ export const DirectLinkModal = memo(function DirectLinkModal({ entry, path, open try { const fullPath = (path === '/' ? '' : path) + '/' + entry.name; const res = await vfsApi.getTempLinkToken(fullPath, expiresIn); - const tempLink = `${window.location.origin}/api/fs/public/${res.token}`; - setLink(tempLink); + setLink(res.url); } catch (e: any) { message.error(e.message || '生成链接失败'); } finally { diff --git a/web/src/pages/FileExplorerPage/components/Modals/ShareModal.tsx b/web/src/pages/FileExplorerPage/components/Modals/ShareModal.tsx index e9fed15..a7de17f 100644 --- a/web/src/pages/FileExplorerPage/components/Modals/ShareModal.tsx +++ b/web/src/pages/FileExplorerPage/components/Modals/ShareModal.tsx @@ -3,6 +3,7 @@ import { Modal, Form, Input, Radio, InputNumber, message, Button, Typography } f import { CopyOutlined } from '@ant-design/icons'; import type { VfsEntry, ShareInfoWithPassword } from '../../../../api/client'; import { shareApi } from '../../../../api/share'; +import { useSystemStatus } from '../../../../contexts/SystemContext'; interface ShareModalProps { entries: VfsEntry[]; @@ -13,6 +14,7 @@ interface ShareModalProps { } export const ShareModal = memo(function ShareModal({ entries, path, open, onOk, onCancel }: ShareModalProps) { + const systemStatus = useSystemStatus(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [accessType, setAccessType] = useState('public'); @@ -66,7 +68,8 @@ export const ShareModal = memo(function ShareModal({ entries, path, open, onOk, message.success('已复制到剪贴板'); }; - const shareUrl = createdShare ? `${window.location.origin}/share/${createdShare.token}` : ''; + const baseUrl = systemStatus?.app_domain || window.location.origin; + const shareUrl = createdShare ? new URL(`/share/${createdShare.token}`, baseUrl).href : ''; const renderForm = () => (
diff --git a/web/src/pages/SharePage.tsx b/web/src/pages/SharePage.tsx index 6ccd1a0..d68a412 100644 --- a/web/src/pages/SharePage.tsx +++ b/web/src/pages/SharePage.tsx @@ -4,8 +4,10 @@ import PageCard from '../components/PageCard'; import { shareApi, type ShareInfo } from '../api/share'; import { format, parseISO } from 'date-fns'; import { LinkOutlined, CopyOutlined, DeleteOutlined } from '@ant-design/icons'; +import { useSystemStatus } from '../contexts/SystemContext'; const SharePage = memo(function SharePage() { + const systemStatus = useSystemStatus(); const [loading, setLoading] = useState(false); const [data, setData] = useState([]); @@ -24,7 +26,8 @@ const SharePage = memo(function SharePage() { useEffect(() => { fetchList(); }, [fetchList]); const doCopy = (rec: ShareInfo) => { - const shareUrl = `${window.location.origin}/share/${rec.token}`; + const baseUrl = systemStatus?.app_domain || window.location.origin; + const shareUrl = new URL(`/share/${rec.token}`, baseUrl).href; navigator.clipboard.writeText(shareUrl); message.success('链接已复制'); }; diff --git a/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx b/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx index ee82332..a07ce3c 100644 --- a/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx +++ b/web/src/pages/SystemSettingsPage/SystemSettingsPage.tsx @@ -2,13 +2,13 @@ import { Form, Input, Button, message, Tabs, Space, Card } from 'antd'; import { useEffect, useState } from 'react'; import PageCard from '../../components/PageCard'; import { getAllConfig, setConfig } from '../../api/config'; -import { API_BASE_URL } from '../../api/client'; import { AppstoreOutlined, RobotOutlined } from '@ant-design/icons'; -const APP_CONFIG_KEYS = [ +const APP_CONFIG_KEYS: {key: string, label: string, default?: string}[] = [ { key: 'APP_NAME', label: '应用名称' }, { key: 'APP_LOGO', label: 'LOGO地址' }, - { key: 'SERVER_URL', label: '服务端URL', default: API_BASE_URL }, + { key: 'APP_DOMAIN', label: '应用域名' }, + { key: 'FILE_DOMAIN', label: '文件域名' }, ]; const VISION_CONFIG_KEYS = [