fix: P0 安全加固 + 前端错误边界 + ESLint 修复

后端:
- 实现登出 API(清除 refresh token)
- 清除 videos.py 中已被 Celery 任务取代的死代码
- 添加速率限制中间件(60次/分钟,登录10次/分钟)
- 添加 SECRET_KEY/ENCRYPTION_KEY 默认值警告
- OSS STS 方法回退到 Policy 签名(不再抛异常)

前端:
- 添加全局 404/error/loading 页面
- 添加三端 error.tsx + loading.tsx 错误边界
- 修复 useId 条件调用违反 Hooks 规则
- 修复未转义引号和 Image 命名冲突
- 添加 ESLint 配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-09 17:18:04 +08:00
co-authored by Claude Opus 4.6
parent a8be7bbca9
commit 8eb8100cf4
25 changed files with 498 additions and 193 deletions
+1
View File
@@ -0,0 +1 @@
{"extends":"next/core-web-vitals"}
+44
View File
@@ -0,0 +1,44 @@
'use client'
import { useEffect } from 'react'
import { AlertTriangle, RefreshCw, Home } from 'lucide-react'
export default function AgencyError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error('Agency section error:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center h-full min-h-[400px] gap-4">
<div className="w-14 h-14 bg-accent-coral/15 rounded-2xl flex items-center justify-center">
<AlertTriangle className="w-7 h-7 text-accent-coral" />
</div>
<h2 className="text-xl font-semibold text-text-primary"></h2>
<p className="text-text-secondary text-sm max-w-sm text-center">
{error.message || '发生未知错误,请重试'}
</p>
<div className="flex gap-3 mt-2">
<button
onClick={() => window.location.href = '/agency'}
className="flex items-center gap-2 px-4 py-2.5 bg-bg-elevated text-text-secondary rounded-xl text-sm font-medium hover:bg-bg-card transition-colors border border-border-subtle"
>
<Home className="w-4 h-4" />
</button>
<button
onClick={reset}
className="flex items-center gap-2 px-4 py-2.5 bg-accent-indigo text-white rounded-xl text-sm font-medium hover:bg-accent-indigo/90 transition-colors"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
</div>
)
}
+10
View File
@@ -0,0 +1,10 @@
export default function AgencyLoading() {
return (
<div className="flex items-center justify-center h-full min-h-[400px]">
<div className="flex flex-col items-center gap-4">
<div className="w-8 h-8 border-2 border-border-subtle border-t-accent-indigo rounded-full animate-spin" />
<p className="text-text-tertiary text-sm">...</p>
</div>
</div>
)
}
+1 -1
View File
@@ -585,7 +585,7 @@ export default function AgencyCompanyPage() {
{isEditing && (
<div className="p-3 rounded-lg bg-accent-indigo/10 border border-accent-indigo/20">
<p className="text-sm text-accent-indigo">
💡 "查询企业"
💡 &ldquo;&rdquo;
</p>
</div>
)}
+44
View File
@@ -0,0 +1,44 @@
'use client'
import { useEffect } from 'react'
import { AlertTriangle, RefreshCw, Home } from 'lucide-react'
export default function BrandError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error('Brand section error:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center h-full min-h-[400px] gap-4">
<div className="w-14 h-14 bg-accent-coral/15 rounded-2xl flex items-center justify-center">
<AlertTriangle className="w-7 h-7 text-accent-coral" />
</div>
<h2 className="text-xl font-semibold text-text-primary"></h2>
<p className="text-text-secondary text-sm max-w-sm text-center">
{error.message || '发生未知错误,请重试'}
</p>
<div className="flex gap-3 mt-2">
<button
onClick={() => window.location.href = '/brand'}
className="flex items-center gap-2 px-4 py-2.5 bg-bg-elevated text-text-secondary rounded-xl text-sm font-medium hover:bg-bg-card transition-colors border border-border-subtle"
>
<Home className="w-4 h-4" />
</button>
<button
onClick={reset}
className="flex items-center gap-2 px-4 py-2.5 bg-accent-indigo text-white rounded-xl text-sm font-medium hover:bg-accent-indigo/90 transition-colors"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
</div>
)
}
+10
View File
@@ -0,0 +1,10 @@
export default function BrandLoading() {
return (
<div className="flex items-center justify-center h-full min-h-[400px]">
<div className="flex flex-col items-center gap-4">
<div className="w-8 h-8 border-2 border-border-subtle border-t-accent-indigo rounded-full animate-spin" />
<p className="text-text-tertiary text-sm">...</p>
</div>
</div>
)
}
+1 -1
View File
@@ -302,7 +302,7 @@ export default function CreateProjectPage() {
)}
<p className="text-xs text-text-tertiary mt-3">
"代理商管理"
&ldquo;&rdquo;
</p>
</div>
@@ -338,7 +338,7 @@ export default function BrandVideoReviewPage() {
} finally {
setLoading(false)
}
}, [taskId])
}, [taskId, toast])
useEffect(() => {
loadTask()
+1 -1
View File
@@ -363,7 +363,7 @@ export default function AppealQuotaPage() {
<div className="flex flex-col gap-1">
<span className="text-sm font-medium text-text-primary"></span>
<span className="text-[13px] text-text-secondary leading-relaxed">
1 "申请增加"
1 &ldquo;&rdquo;
</span>
</div>
</div>
+2 -2
View File
@@ -9,7 +9,7 @@ import {
CheckCircle,
XCircle,
FileText,
Image,
Image as ImageIcon,
Send,
AlertTriangle,
Loader2
@@ -422,7 +422,7 @@ export default function AppealDetailPage() {
className="flex items-center gap-3 px-4 py-3 bg-bg-elevated rounded-xl cursor-pointer hover:bg-bg-page transition-colors"
>
{attachment.type === 'image' ? (
<Image className="w-5 h-5 text-accent-indigo" />
<ImageIcon className="w-5 h-5 text-accent-indigo" />
) : (
<FileText className="w-5 h-5 text-accent-indigo" />
)}
+2 -2
View File
@@ -7,7 +7,7 @@ import {
Upload,
X,
FileText,
Image,
Image as ImageIcon,
AlertTriangle,
CheckCircle,
Loader2
@@ -468,7 +468,7 @@ export default function NewAppealPage() {
className="flex items-center gap-2 px-3 py-2 bg-bg-elevated rounded-lg"
>
{file.type === 'image' ? (
<Image className="w-4 h-4 text-accent-indigo" />
<ImageIcon className="w-4 h-4 text-accent-indigo" />
) : (
<FileText className="w-4 h-4 text-accent-indigo" />
)}
+44
View File
@@ -0,0 +1,44 @@
'use client'
import { useEffect } from 'react'
import { AlertTriangle, RefreshCw, Home } from 'lucide-react'
export default function CreatorError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error('Creator section error:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center h-full min-h-[400px] gap-4">
<div className="w-14 h-14 bg-accent-coral/15 rounded-2xl flex items-center justify-center">
<AlertTriangle className="w-7 h-7 text-accent-coral" />
</div>
<h2 className="text-xl font-semibold text-text-primary"></h2>
<p className="text-text-secondary text-sm max-w-sm text-center">
{error.message || '发生未知错误,请重试'}
</p>
<div className="flex gap-3 mt-2">
<button
onClick={() => window.location.href = '/creator'}
className="flex items-center gap-2 px-4 py-2.5 bg-bg-elevated text-text-secondary rounded-xl text-sm font-medium hover:bg-bg-card transition-colors border border-border-subtle"
>
<Home className="w-4 h-4" />
</button>
<button
onClick={reset}
className="flex items-center gap-2 px-4 py-2.5 bg-accent-indigo text-white rounded-xl text-sm font-medium hover:bg-accent-indigo/90 transition-colors"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
</div>
)
}
+10
View File
@@ -0,0 +1,10 @@
export default function CreatorLoading() {
return (
<div className="flex items-center justify-center h-full min-h-[400px]">
<div className="flex flex-col items-center gap-4">
<div className="w-8 h-8 border-2 border-border-subtle border-t-accent-indigo rounded-full animate-spin" />
<p className="text-text-tertiary text-sm">...</p>
</div>
</div>
)
}
+47
View File
@@ -0,0 +1,47 @@
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error('Application error:', error)
}, [error])
return (
<div className="min-h-screen bg-bg-page flex items-center justify-center">
<div className="text-center max-w-md">
<div className="w-16 h-16 bg-accent-coral/15 rounded-2xl flex items-center justify-center mx-auto mb-6">
<svg className="w-8 h-8 text-accent-coral" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
<h2 className="text-2xl font-semibold text-text-primary mb-2">
</h2>
<p className="text-text-secondary mb-8">
</p>
<div className="flex gap-3 justify-center">
<button
onClick={() => window.location.href = '/'}
className="px-6 py-3 bg-bg-elevated text-text-secondary rounded-xl font-medium hover:bg-bg-card transition-colors border border-border-subtle"
>
</button>
<button
onClick={reset}
className="px-6 py-3 bg-accent-indigo text-white rounded-xl font-medium hover:bg-accent-indigo/90 transition-colors"
>
</button>
</div>
</div>
</div>
)
}
+10
View File
@@ -0,0 +1,10 @@
export default function Loading() {
return (
<div className="min-h-screen bg-bg-page flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="w-10 h-10 border-3 border-border-subtle border-t-accent-indigo rounded-full animate-spin" />
<p className="text-text-tertiary text-sm">...</p>
</div>
</div>
)
}
+23
View File
@@ -0,0 +1,23 @@
import Link from 'next/link'
export default function NotFound() {
return (
<div className="min-h-screen bg-bg-page flex items-center justify-center">
<div className="text-center">
<h1 className="text-8xl font-bold text-accent-indigo mb-4">404</h1>
<h2 className="text-2xl font-semibold text-text-primary mb-2">
</h2>
<p className="text-text-secondary mb-8">
访
</p>
<Link
href="/"
className="inline-flex items-center gap-2 px-6 py-3 bg-accent-indigo text-white rounded-xl font-medium hover:bg-accent-indigo/90 transition-colors"
>
</Link>
</div>
</div>
)
}
+2 -1
View File
@@ -29,7 +29,8 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(({
id,
...props
}, ref) => {
const inputId = id ?? useId();
const generatedId = useId();
const inputId = id ?? generatedId;
return (
<div className={`${fullWidth ? 'w-full' : ''}`}>