From f5f4be2986a470185829a05d980e0b79828eafd1 Mon Sep 17 00:00:00 2001 From: wwwwwwwww <526125649@qq.com> Date: Sat, 11 Jul 2026 11:05:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=96=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- video-gen-admin/src/api/index.ts | 4 - video-gen-admin/src/pages/AdminSettings.tsx | 87 +------------- video-gen-api/app/api/v1/admin.py | 35 ------ video-gen-api/app/api/v1/auth.py | 7 -- video-gen-api/app/main.py | 39 +----- video-gen-api/app/middleware/site_status.py | 125 -------------------- video-gen-app/src/App.tsx | 40 +------ video-gen-app/src/api/client.ts | 13 -- video-gen-app/src/api/index.ts | 2 +- video-gen-app/src/pages/MaintenancePage.tsx | 103 ---------------- 10 files changed, 5 insertions(+), 450 deletions(-) delete mode 100644 video-gen-api/app/middleware/site_status.py delete mode 100644 video-gen-app/src/pages/MaintenancePage.tsx diff --git a/video-gen-admin/src/api/index.ts b/video-gen-admin/src/api/index.ts index 7576e2e5..ec4c4191 100644 --- a/video-gen-admin/src/api/index.ts +++ b/video-gen-admin/src/api/index.ts @@ -224,10 +224,6 @@ export async function updateSystemConfig(id: string, value: string): Promise { - return api.post('/admin/system-configs/regenerate-dev-token'); -} - export async function getGlobalResourceCapacity(): Promise { return api.get('/admin/resource-capacity/global'); } diff --git a/video-gen-admin/src/pages/AdminSettings.tsx b/video-gen-admin/src/pages/AdminSettings.tsx index cdcb32d9..3afe6226 100644 --- a/video-gen-admin/src/pages/AdminSettings.tsx +++ b/video-gen-admin/src/pages/AdminSettings.tsx @@ -1,10 +1,9 @@ import React, { useEffect, useState } from 'react'; import { - Button, Card, Form, Input, InputNumber, message, Popconfirm, Select, Space, Switch, Typography, Upload, + Button, Card, Form, Input, InputNumber, message, Select, Space, Switch, Typography, Upload, } from 'antd'; import { SettingOutlined, SaveOutlined, UploadOutlined, FilePdfOutlined, EyeOutlined, DatabaseOutlined, - PoweroffOutlined, ReloadOutlined, CopyOutlined, } from '@ant-design/icons'; import { getGlobalResourceCapacity, @@ -13,7 +12,6 @@ import { updateSystemConfig, uploadLogo, uploadPdf, - regenerateDevToken, } from '../api'; import type { ResourceCapacityUnit, SystemConfig } from '../types'; @@ -44,8 +42,6 @@ const AdminSettings: React.FC = () => { setConfigs(data); const formValues: Record = {}; data.forEach(c => { formValues[c.key] = c.value; }); - // 网站开关布尔值转换 - formValues.site_enabled = siteEnabledConfig?.value !== 'false'; formValues.resource_capacity_enabled = capacity.enabled; formValues.resource_capacity_limit_value = capacity.limitValue || '1.000'; formValues.resource_capacity_limit_unit = capacity.limitUnit || 'GB'; @@ -67,13 +63,6 @@ const AdminSettings: React.FC = () => { await updateSystemConfig(config.id, String(newVal ?? '')); } } - // 保存网站开关(布尔值转为字符串) - if (siteEnabledConfig) { - const newEnabled = values.site_enabled ? 'true' : 'false'; - if (newEnabled !== siteEnabledConfig.value) { - await updateSystemConfig(siteEnabledConfig.id, newEnabled); - } - } await saveGlobalResourceCapacity({ enabled: !!values.resource_capacity_enabled, limitValue: String(values.resource_capacity_limit_value ?? '1.000'), @@ -88,20 +77,6 @@ const AdminSettings: React.FC = () => { } }; - const handleRegenerateDevToken = async () => { - try { - const result = await regenerateDevToken(); - message.success('开发者令牌已重新生成'); - await load(); - // 更新显示的令牌 - if (result?.token) { - form.setFieldsValue({ site_dev_access_token: result.token }); - } - } catch (e: any) { - message.error(e?.message || '重新生成失败'); - } - }; - const handleUpload = async (file: File, configKey: string) => { setUploading(configKey); try { @@ -143,11 +118,8 @@ const AdminSettings: React.FC = () => { return false; }; - const siteEnabledConfig = configs.find(c => c.key === 'site_enabled'); - const devTokenConfig = configs.find(c => c.key === 'site_dev_access_token'); - const groupedConfigs: Record = { - '站点信息': configs.filter(c => c.key.startsWith('site_') && c.key !== 'site_enabled' && c.key !== 'site_dev_access_token'), + '站点信息': configs.filter(c => c.key.startsWith('site_')), '协议配置': configs.filter(c => c.key === 'user_agreement_privacy_url'), 'SEO 设置': configs.filter(c => c.key.startsWith('seo_')), '用户积分配置': configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')), @@ -325,61 +297,6 @@ const AdminSettings: React.FC = () => {
- {/* 网站访问控制 */} -
- - 访问控制 - - {siteEnabledConfig ? ( - - - - ) : ( - - 系统配置 site_enabled 未初始化,请通过数据库手动添加。 - - )} - - {devTokenConfig && ( - - - - - - - - - - )} -
- {Object.entries(groupedConfigs).map(([group, items]) => (
diff --git a/video-gen-api/app/api/v1/admin.py b/video-gen-api/app/api/v1/admin.py index 861a0c5d..f37562c2 100644 --- a/video-gen-api/app/api/v1/admin.py +++ b/video-gen-api/app/api/v1/admin.py @@ -1622,41 +1622,6 @@ async def update_system_config( return config -@router.post("/system-configs/regenerate-dev-token") -async def regenerate_dev_token( - admin: User = Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - """重新生成开发者访问令牌。""" - import secrets - result = await db.execute( - select(SystemConfig).where(SystemConfig.key == "site_dev_access_token").limit(1) - ) - config = result.scalar_one_or_none() - new_token = secrets.token_urlsafe(32) - if config: - config.value = new_token - else: - config = SystemConfig( - id=generate_id(), - key="site_dev_access_token", - value=new_token, - description="开发者访问令牌(网站关闭时用于绕过限制)", - ) - db.add(config) - await db.flush() - await log_operation( - db, - admin.id, - admin.username, - "重新生成开发者访问令牌", - "POST", - "/admin/system-configs/regenerate-dev-token", - ) - await db.commit() - return {"token": new_token} - - # ── Operation Logs ────────────────────────────────────── @router.get("/operation-logs") diff --git a/video-gen-api/app/api/v1/auth.py b/video-gen-api/app/api/v1/auth.py index 09d8eb97..0a16d8cb 100644 --- a/video-gen-api/app/api/v1/auth.py +++ b/video-gen-api/app/api/v1/auth.py @@ -345,19 +345,12 @@ async def get_site_info(db: AsyncSession = Depends(get_db)): base_url = settings.BASE_URL.rstrip("/") return f"{base_url}{path}" - # 读取网站开关状态(不存在时默认开启) - site_enabled_result = await db.execute( - select(SystemConfig.value).where(SystemConfig.key == "site_enabled").limit(1) - ) - site_enabled_value = site_enabled_result.scalar_one_or_none() - return { "site_name": info.get("site_name", "VideoGen.AI"), "site_logo": to_full_url(info.get("site_logo")), "user_agreement_privacy_url": to_full_url(info.get("user_agreement_privacy_url")), "site_copyright": info.get("site_copyright", "© 2024 民众智创 版权所有"), "operation_manual": info.get("operation_manual", ""), - "site_enabled": site_enabled_value != "false", } diff --git a/video-gen-api/app/main.py b/video-gen-api/app/main.py index 7782bed9..6fe1afae 100644 --- a/video-gen-api/app/main.py +++ b/video-gen-api/app/main.py @@ -16,7 +16,6 @@ from app.middleware.logging import RequestLoggingMiddleware from app.middleware.anti_crawler import AntiCrawlerMiddleware from app.middleware.rate_limit import RateLimitMiddleware from app.middleware.request_encrypt import RequestEncryptMiddleware -from app.middleware.site_status import SiteStatusMiddleware from app.services.log_config import decrypt_data logging.basicConfig(level=logging.INFO if settings.DEBUG else logging.WARNING) @@ -30,7 +29,6 @@ async def lifespan(app: FastAPI): os.makedirs(settings.UPLOAD_LOCAL_PATH, exist_ok=True) await init_database() await init_redis() - await _ensure_site_configs() # await _seed_data() # Start task queue (handles both video and image generation) @@ -515,40 +513,6 @@ async def _seed_data(): await db.commit() -async def _ensure_site_configs() -> None: - """确保网站开关相关配置项存在(不存在则自动创建)。""" - import secrets - from app.models.base import async_session - from app.models.system_config import SystemConfig - from app.utils.id_gen import generate_id - from sqlalchemy import select - - async with async_session() as db: - # site_enabled - existing = await db.execute( - select(SystemConfig).where(SystemConfig.key == "site_enabled").limit(1) - ) - if not existing.scalar_one_or_none(): - db.add(SystemConfig( - id=generate_id(), - key="site_enabled", - value="true", - description="网站访问开关(true=开启,false=关闭)", - )) - # site_dev_access_token - existing_token = await db.execute( - select(SystemConfig).where(SystemConfig.key == "site_dev_access_token").limit(1) - ) - if not existing_token.scalar_one_or_none(): - db.add(SystemConfig( - id=generate_id(), - key="site_dev_access_token", - value=secrets.token_urlsafe(32), - description="开发者访问令牌(网站关闭时用于绕过限制)", - )) - await db.commit() - - def create_app() -> FastAPI: application = FastAPI( title=settings.APP_NAME, @@ -558,8 +522,7 @@ def create_app() -> FastAPI: redoc_url="/internal/api-redoc", ) - # Middleware (outermost first) — SiteStatus 放最外层,早于其他中间件拦截 - application.add_middleware(SiteStatusMiddleware) + # Middleware (outermost first) application.add_middleware(RequestLoggingMiddleware) application.add_middleware(AntiCrawlerMiddleware) application.add_middleware(RateLimitMiddleware) diff --git a/video-gen-api/app/middleware/site_status.py b/video-gen-api/app/middleware/site_status.py deleted file mode 100644 index 98800baa..00000000 --- a/video-gen-api/app/middleware/site_status.py +++ /dev/null @@ -1,125 +0,0 @@ -import logging -import time - -from fastapi import Request, Response -from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint -from starlette.responses import JSONResponse - -logger = logging.getLogger("videogen") - -# 网站关闭时仍然放行的路径前缀 -_WHITELIST_PREFIXES = ( - "/api/auth/", # 登录/注册/短信/验证码 - "/api/admin/", # 后台管理(确保管理员能登录并重新开启网站) - "/api/captcha/", # 图形验证码 - "/internal/", # 健康检查/文档 - "/uploads/", # 静态文件 - "/api/generation-records/callback", # 火山引擎回调 - "/api/payments/wechat/callback", # 微信回调 - "/api/payments/alipay/callback", # 支付宝回调 -) - -# 完全匹配的白名单路径 -_WHITELIST_EXACT = ( - "/api/auth/site-info", - "/internal/health", -) - -# site_enabled 缓存有效期(秒) -_CACHE_TTL = 5.0 - - -class SiteStatusMiddleware(BaseHTTPMiddleware): - """网站访问开关中间件。 - - 检查 SystemConfig 中 site_enabled 的值: - - 配置不存在或值不为 "false" → 放行 - - 值为 "false" 且不在白名单 → 检查开发者令牌,不匹配则返回 503 { code: "SITE_CLOSED" } - """ - - def __init__(self, app): - super().__init__(app) - self._cache_enabled = True # 缓存的站点状态(True=开启) - self._cache_at: float = 0.0 # 缓存时间戳 - - async def _is_site_enabled(self) -> bool: - """查询 site_enabled 配置,带短缓存避免每个请求查 DB。""" - now = time.time() - if now - self._cache_at < _CACHE_TTL: - return self._cache_enabled - - try: - from app.models.base import async_session - from app.models.system_config import SystemConfig - from sqlalchemy import select - - async with async_session() as db: - result = await db.execute( - select(SystemConfig.value).where(SystemConfig.key == "site_enabled").limit(1) - ) - value = result.scalar_one_or_none() - self._cache_enabled = value != "false" - self._cache_at = now - except Exception: - # 查询异常时默认放行,避免数据库故障导致全站不可用 - logger.exception("Failed to read site_enabled config, defaulting to enabled") - self._cache_enabled = True - self._cache_at = now - - return self._cache_enabled - - @staticmethod - def _is_whitelisted(path: str) -> bool: - """检查路径是否在白名单中。""" - if path in _WHITELIST_EXACT: - return True - return any(path.startswith(prefix) for prefix in _WHITELIST_PREFIXES) - - @staticmethod - async def _check_dev_token(token: str) -> bool: - """校验开发者访问令牌是否匹配配置。""" - try: - from app.models.base import async_session - from app.models.system_config import SystemConfig - from sqlalchemy import select - - async with async_session() as db: - result = await db.execute( - select(SystemConfig.value) - .where(SystemConfig.key == "site_dev_access_token") - .limit(1) - ) - expected = result.scalar_one_or_none() - return expected is not None and token == expected - except Exception: - logger.exception("Failed to verify dev access token") - return False - - async def dispatch( - self, request: Request, call_next: RequestResponseEndpoint - ) -> Response: - path = request.url.path - - # 白名单直接放行 - if self._is_whitelisted(path): - return await call_next(request) - - # 网站开启时放行 - if await self._is_site_enabled(): - return await call_next(request) - - # 网站已关闭 — 校验开发者令牌 - token = request.query_params.get("dev_access") or request.headers.get("x-dev-access") - if token and await self._check_dev_token(token): - return await call_next(request) - - # 拦截请求,返回维护状态 - return JSONResponse( - status_code=503, - content={ - "detail": { - "code": "SITE_CLOSED", - "message": "系统正在升级维护,请稍后再试", - } - }, - ) diff --git a/video-gen-app/src/App.tsx b/video-gen-app/src/App.tsx index f1abaee7..d166aefc 100644 --- a/video-gen-app/src/App.tsx +++ b/video-gen-app/src/App.tsx @@ -1,11 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { ConfigProvider, App as AntApp, Spin } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; dayjs.locale('zh-cn'); -import MaintenancePage from './pages/MaintenancePage'; import AppLayout from './components/Layout/AppLayout'; import LoginPage from './pages/LoginPage'; import ProjectsPage from './pages/ProjectsPage'; @@ -62,47 +61,11 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { const App = () => { const { checkAuth } = useAuthStore(); - const [siteReady, setSiteReady] = useState(false); useEffect(() => { - // 处理 URL 中的开发者访问令牌 - const params = new URLSearchParams(window.location.search); - const devToken = params.get('dev_access'); - if (devToken) { - localStorage.setItem('dev_access_token', devToken); - // 清除 URL 中的令牌参数 - params.delete('dev_access'); - const newSearch = params.toString(); - window.history.replaceState({}, '', window.location.pathname + (newSearch ? `?${newSearch}` : '')); - } - - // 检查网站状态(仅当用户没有有效 dev token 时) checkAuth(); - if (!localStorage.getItem('dev_access_token')) { - import('./api').then(({ getSiteInfo }) => { - getSiteInfo() - .then((info) => { - if (!info.siteEnabled && !window.location.pathname.startsWith('/maintenance')) { - window.location.href = '/maintenance'; - } - }) - .catch(() => { /* 请求失败时默认放行 */ }) - .finally(() => setSiteReady(true)); - }); - } else { - setSiteReady(true); - } }, []); - // 网站状态检查中显示 loading - if (!siteReady && !localStorage.getItem('dev_access_token')) { - return ( -
- -
- ); - } - return ( { } /> - } /> } /> } /> (path: string, options: RequestOptions = {}): if (token) headers['Authorization'] = `Bearer ${token}`; } - // 开发者访问令牌(网站关闭时用于绕过限制) - const devAccessToken = localStorage.getItem('dev_access_token'); - if (devAccessToken) { - headers['X-Dev-Access'] = devAccessToken; - } - let bodyStr: string | undefined; if (body !== undefined) { const json = JSON.stringify(body); @@ -116,13 +110,6 @@ export async function apiRequest(path: string, options: RequestOptions = {}): // Handle error responses if (!res.ok) { - // 网站关闭 → 跳转到维护页面 - if (res.status === 503 && parsed?.detail?.code === 'SITE_CLOSED') { - if (!window.location.pathname.startsWith('/maintenance')) { - window.location.href = '/maintenance'; - } - throw new Error(parsed?.detail?.message || '系统正在升级维护'); - } let msg = parsed?.detail?.message || parsed?.message || `请求失败 (${res.status})`; if (typeof parsed?.detail === 'string') { msg = parsed.detail; diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index 6c591651..8e11da08 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -290,7 +290,7 @@ export async function verifyCaptcha(captchaId: string, x: number): Promise { +export async function getSiteInfo(): Promise<{ siteName: string; siteLogo: string; userAgreementPrivacyUrl: string; siteCopyright: string; operationManual: string }> { if (USE_MOCK) return { siteName: 'VideoGen.AI', siteLogo: '', userAgreementPrivacyUrl: '', siteCopyright: '© 2024 民众智创 版权所有', operationManual: '' }; return api.get('/auth/site-info', false); } diff --git a/video-gen-app/src/pages/MaintenancePage.tsx b/video-gen-app/src/pages/MaintenancePage.tsx deleted file mode 100644 index c0d8eef1..00000000 --- a/video-gen-app/src/pages/MaintenancePage.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Button, Input, Space, Typography, message } from 'antd'; -import { ToolOutlined, SafetyOutlined } from '@ant-design/icons'; -import { getSiteInfo } from '../api'; - -const MaintenancePage: React.FC = () => { - const [devToken, setDevToken] = useState(''); - const [showDevInput, setShowDevInput] = useState(false); - const [siteName, setSiteName] = useState(''); - - useEffect(() => { - // site-info 在网站关闭时仍可访问(白名单) - getSiteInfo().then((info) => { - if (info.siteName) setSiteName(info.siteName); - }).catch(() => {}); - }, []); - - const handleDevAccess = () => { - const token = devToken.trim(); - if (!token) { - message.warning('请输入开发者访问令牌'); - return; - } - localStorage.setItem('dev_access_token', token); - message.success('开发者访问已启用,正在刷新...'); - window.location.href = '/'; - }; - - return ( -
- {/* 开发者入口 — 右下角 */} -
- {!showDevInput ? ( - setShowDevInput(true)} - style={{ fontSize: 12, color: '#94a3b8' }} - > - 开发者入口 - - ) : ( - - setDevToken(e.target.value)} - onPressEnter={handleDevAccess} - style={{ width: 200 }} - /> - - - )} -
- - {/* 主内容 */} -
- -
- - {siteName && ( - - {siteName} - - )} - - - 系统正在升级维护 - - - - 为了提供更好的服务,系统正在进行升级维护。 -
- 请稍后再来,感谢您的理解与支持! -
-
- ); -}; - -export default MaintenancePage;