gongxiangp2/middleware.ts
chenpei ea9076df45 feat: 实现统一的下载错误处理系统并修复产品全案数据库Schema
- 创建统一的下载错误处理模块 (lib/download-errors.ts)
- 实现13种错误类型分类和中文错误消息
- 后端统一使用 createDownloadError 创建标准化错误响应
- 前端统一使用 parseDownloadError 解析错误并显示
- 修复正式系统数据库Schema,添加产品全案相关字段和表
- 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段
- 创建 material_assets, api_keys, api_call_logs 表
2026-01-05 17:52:42 +08:00

124 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { getToken } from 'next-auth/jwt'
// 允许的来源列表(内网和公网)
const ALLOWED_ORIGINS = [
'http://192.168.58.20:4320',
'http://180.97.221.140:2320',
'http://localhost:3000',
'http://localhost:4320',
]
export async function middleware(request: NextRequest) {
const { pathname, origin } = request.nextUrl
const requestOrigin = request.headers.get('origin')
// 处理API路由的CORS
if (pathname.startsWith('/api/')) {
// 处理OPTIONS预检请求
if (request.method === 'OPTIONS') {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Origin': requestOrigin || origin,
'Access-Control-Allow-Methods': 'GET,DELETE,PATCH,POST,PUT,OPTIONS',
'Access-Control-Allow-Headers': 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization, Cookie',
'Access-Control-Max-Age': '86400',
},
})
}
// 为所有API响应添加CORS头
const response = NextResponse.next()
response.headers.set('Access-Control-Allow-Credentials', 'true')
response.headers.set('Access-Control-Allow-Origin', requestOrigin || origin)
response.headers.set('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT,OPTIONS')
response.headers.set('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization, Cookie')
return response
}
// 处理需要认证的页面
if (pathname === '/' || pathname.startsWith('/admin')) {
const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET })
if (!token) {
const url = new URL('/login', request.url)
const redirectResponse = NextResponse.redirect(url)
// 为重定向响应也添加缓存控制头
redirectResponse.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0')
redirectResponse.headers.set('Pragma', 'no-cache')
redirectResponse.headers.set('Expires', '0')
return redirectResponse
}
// 管理员页面需要管理员权限
if (pathname.startsWith('/admin')) {
if ((token as any)?.role !== 'ADMIN') {
const url = new URL('/', request.url)
const redirectResponse = NextResponse.redirect(url)
// 为重定向响应也添加缓存控制头
redirectResponse.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0')
redirectResponse.headers.set('Pragma', 'no-cache')
redirectResponse.headers.set('Expires', '0')
return redirectResponse
}
}
}
// 检查是否有新版本部署(通过环境变量控制)
// 只有在部署新版本时才禁用缓存,否则允许浏览器缓存以提高性能
const disableCache = process.env.DISABLE_CACHE === 'true'
const deploymentVersion = process.env.DEPLOYMENT_VERSION || process.env.BUILD_ID || ''
// 获取客户端存储的版本号(通过 cookie)
const clientVersion = request.cookies.get('app-version')?.value || ''
// 判断是否需要禁用缓存:有新版本部署且客户端版本与服务器版本不同
const shouldDisableCache = disableCache && deploymentVersion && clientVersion !== deploymentVersion
const response = NextResponse.next()
// 如果检测到新版本,设置版本号 cookie
if (shouldDisableCache && deploymentVersion) {
response.cookies.set('app-version', deploymentVersion, {
path: '/',
maxAge: 60 * 60 * 24 * 365, // 1年
sameSite: 'lax',
})
}
// 对于 HTML 页面,根据是否有新版本决定缓存策略
if (pathname === '/' || pathname.startsWith('/admin') || pathname.startsWith('/share') || pathname.startsWith('/edit') || pathname.endsWith('.html')) {
if (shouldDisableCache) {
// 有新版本:禁用缓存,强制浏览器获取最新代码
response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0')
response.headers.set('Pragma', 'no-cache')
response.headers.set('Expires', '0')
} else {
// 无新版本:允许浏览器缓存,提高性能
response.headers.set('Cache-Control', 'public, max-age=3600, must-revalidate') // 缓存1小时
}
}
// 对于 JS/CSS 等静态资源,使用版本号或时间戳来强制更新
if (pathname.match(/\.(js|css|json|ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/)) {
if (shouldDisableCache) {
// 有新版本:禁用缓存
response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0')
} else {
// 无新版本:长期缓存(Next.js 会自动添加版本号到文件名)
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable')
}
}
return response
}
export const config = {
matcher: ['/', '/admin/:path*', '/api/:path*'],
}