gongxiangp2/middleware.ts

146 lines
5.8 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'
import {
getAuthSessionCookieCandidates,
resolveSecureAuthCookieFromRequest,
} from '@/lib/auth-cookie'
// 注意:不在middleware中启动内存监控
// middleware使用Edge Runtime,不支持Node.js API
// 内存监控会在lib/memory-monitor.ts模块加载时自动启动(仅生产环境)
// 允许的来源列表(内网和公网)
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 preferredSecureCookie = resolveSecureAuthCookieFromRequest(request)
const cookieCandidates = getAuthSessionCookieCandidates(preferredSecureCookie)
let token = null
for (const cookieName of cookieCandidates) {
token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
cookieName,
secureCookie: cookieName.startsWith('__Secure-'),
})
if (token) {
break
}
}
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*'],
}