基础篇八 Nuxt4 中间件进阶:请求拦截与权限校验

文章目录

个人网站

前面我们学过路由守卫,用中间件控制页面访问权限。今天深入聊聊中间件的高级用法,包括请求拦截、权限校验、数据预加载等实战技巧。

一、中间件执行流程

理解执行顺序很重要:

复制代码
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 能力。

相关文章

入门篇三:Nuxt4组件自动导入:写代码少敲一半字

入门篇二:Nuxt 4路由自动生成:告别手动配置路由的日子

延伸阅读

nuxt4完整系列,持续更新中。。,欢迎来逛逛


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

相关推荐
San30.2 小时前
前端进阶:从浏览器渲染原理到网络请求全链路解析
前端·网络·网络请求·浏览器渲染机制
feng_you_ying_li2 小时前
C++11可变模板参数,包扩展,emplace系列和push系列的区别
前端·c++·算法
hashiqimiya2 小时前
npm查看依赖
前端·npm·node.js
Ticnix2 小时前
NestJs--Prisma 7的安装与数据库配置(超完整)
前端·nestjs
Bacon2 小时前
CDP、Puppeteer 与无头浏览器:它们到底什么关系?
前端·javascript·node.js
kyriewen2 小时前
你的网站被“下毒”了?XSS和CSRF:前端安全的两大“毒瘤”
前端·javascript·安全
Irene19912 小时前
Web前端开发转行大数据开发,可行性分析及学习路线
大数据·前端·转行
咸鱼翻身了么2 小时前
大文件上传-spark-md5
前端·后端
API快乐传递者2 小时前
Python 爬虫获取 1688 商品详情 API 接口实战指南
java·前端·python