文章目录
前面我们学过路由守卫,用中间件控制页面访问权限。今天深入聊聊中间件的高级用法,包括请求拦截、权限校验、数据预加载等实战技巧。
一、中间件执行流程
理解执行顺序很重要:
1. 全局中间件(按字母排序)
2. 页面定义的中间件
3. 布局中间件
目录结构:
middleware/
├── 01-auth.global.ts # 全局,先执行
├── 02-logging.global.ts # 全局,后执行
├── admin.ts # 页面级
└── guest.ts # 页面级
二、登录状态检查
最常见的场景:
ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const userStore = useUserStore()
// 未登录,跳转登录页
if (!userStore.isLoggedIn) {
// 保存原目标路径,登录后跳回
return navigateTo({
path: '/login',
query: { redirect: to.fullPath }
})
}
})
登录页面:
vue
<script setup lang="ts">
const route = useRoute()
const userStore = useUserStore()
const form = reactive({
email: '',
password: ''
})
const login = async () => {
await userStore.login(form.email, form.password)
// 登录成功,跳回原页面
const redirect = route.query.redirect as string
navigateTo(redirect || '/')
}
</script>
三、角色权限校验
不同用户有不同权限:
ts
// middleware/role.ts
type Role = 'admin' | 'editor' | 'user'
export default defineNuxtRouteMiddleware((to, from) => {
const userStore = useUserStore()
// 从路由 meta 获取需要的角色
const requiredRole = to.meta.role as Role
if (!requiredRole) return
// 未登录或权限不足
if (!userStore.user || !hasPermission(userStore.user.role, requiredRole)) {
return navigateTo('/403')
}
})
// 权限等级
const roleLevel: Record<Role, number> = {
admin: 3,
editor: 2,
user: 1
}
const hasPermission = (userRole: Role, requiredRole: Role) => {
return roleLevel[userRole] >= roleLevel[requiredRole]
}
页面中使用:
vue
<script setup lang="ts">
definePageMeta({
middleware: 'role',
meta: {
role: 'admin' // 只有管理员能访问
}
})
</script>
四、页面数据预加载
中间件可以预加载数据,避免页面闪烁:
ts
// middleware/fetch-user.global.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
const userStore = useUserStore()
const token = useCookie('token')
// 有 token 但没有用户数据,尝试获取
if (token.value && !userStore.user) {
try {
await userStore.fetchUser()
} catch {
// token 失效,清除
token.value = null
}
}
})
五、访问日志记录
记录页面访问:
ts
// middleware/logging.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
const userStore = useUserStore()
// 只在客户端记录
if (import.meta.client) {
const logData = {
userId: userStore.user?.id || 'anonymous',
from: from.path,
to: to.path,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
}
// 发送到日志服务(不阻塞导航)
$fetch('/api/logs', {
method: 'POST',
body: logData
}).catch(console.error)
}
})
六、IP 黑名单(服务端)
ts
// middleware/ip-block.global.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
// 只在服务端执行
if (import.meta.server) {
const event = useRequestEvent()
const ip = getRequestIP(event, { xForwardedFor: true })
// 检查 IP 是否被封禁
const blocked = await checkBlockedIP(ip)
if (blocked) {
return navigateTo('/blocked')
}
}
})
const checkBlockedIP = async (ip: string): Promise<boolean> => {
// 实际项目中可以从数据库或 Redis 查询
const blockedIPs = ['192.168.1.100', '10.0.0.50']
return blockedIPs.includes(ip)
}
七、API 请求拦截
服务端 API 路由也可以用中间件:
ts
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
const url = getRequestURL(event)
// 只拦截需要认证的接口
if (!url.pathname.startsWith('/api/protected/')) {
return
}
const token = getHeader(event, 'Authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
try {
const user = await verifyToken(token)
event.context.user = user
} catch {
throw createError({
statusCode: 401,
message: 'Invalid token'
})
}
})
API 接口中使用:
ts
// server/api/protected/profile.ts
export default defineEventHandler((event) => {
const user = event.context.user
return {
id: user.id,
name: user.name,
email: user.email
}
})
八、请求限流
防止接口被滥用:
ts
// server/middleware/rate-limit.ts
const requests = new Map<string, number[]>()
export default defineEventHandler((event) => {
const ip = getRequestIP(event)
const now = Date.now()
const windowMs = 60 * 1000 // 1 分钟
const maxRequests = 100 // 最多 100 次请求
// 获取该 IP 的请求记录
const ipRequests = requests.get(ip) || []
// 过滤掉窗口外的请求
const recentRequests = ipRequests.filter(time => now - time < windowMs)
// 检查是否超限
if (recentRequests.length >= maxRequests) {
throw createError({
statusCode: 429,
message: 'Too many requests'
})
}
// 记录本次请求
recentRequests.push(now)
requests.set(ip, recentRequests)
})
九、CORS 配置
ts
// server/middleware/cors.ts
export default defineEventHandler((event) => {
const allowedOrigins = [
'https://example.com',
'https://admin.example.com'
]
const origin = getHeader(event, 'origin') || ''
if (allowedOrigins.includes(origin)) {
setResponseHeaders(event, {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true'
})
}
// 处理预检请求
if (getMethod(event) === 'OPTIONS') {
setResponseStatus(event, 204)
return ''
}
})
十、全局错误处理
ts
// server/middleware/error-handler.ts
export default defineEventHandler((event) => {
// 设置错误处理
event.node.res.on('finish', () => {
const status = getResponseStatus(event)
if (status >= 400) {
const url = getRequestURL(event)
console.error(`Error ${status}: ${url}`)
// 发送到错误监控服务
}
})
})
总结
中间件高级用法:
| 场景 | 实现方式 |
|---|---|
| 登录检查 | 全局中间件 + token 判断 |
| 角色权限 | to.meta.role + 权限比较 |
| 数据预加载 | 中间件中调用 store 方法 |
| 访问日志 | 全局中间件 + 异步上报 |
| IP 黑名单 | 服务端中间件 + IP 检查 |
| API 拦截 | server/middleware/ |
| 请求限流 | Map 记录 + 时间窗口 |
下一篇聊聊插件系统,学习如何扩展 Nuxt 能力。
相关文章
延伸阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪