Skip to content

大家好,我是村长!

本节我们讲一下中间件,什么是中间件?大家如果接触过 express、koa 这类 node.js 框架,应该都会知道它们其实就是 请求拦截器 ,当一个请求过来时,通过中间件可以增加自己的处理逻辑,相当于流水线上加一道“工序”。在 Nuxt 中也存在这种中间件机制,只不过被细分为两种:

  • Route middleware:路由中间件;
  • Server middleware:服务器中间件。

路由中间件

路由中间件并不是一定运行在客户端的中间件,而是运行在 Nuxt 应用中 Vue 渲染部分,路由中间件会在进入路由之前被调用,如果是 SSR,这个执行时刻既可能在服务端(首屏),也可能在客户端。

中间件类型

路由中间件根据影响范围和使用方式的不同,又分为三种:

  • 匿名中间件:只影响一个页面,不可复用;
  • 具名中间件:指定若干影响页面,可复用、组合;
  • 全局中间件:影响所有页面,文件名需要加后缀 global。

中间件使用

匿名中间件用法,mid.vue:

typescript
    definePageMeta({
      middleware(to, from) {
        console.log('匿名中间件,具体页面执行');
      }
    })

具名中间件用法,mid.vue:

typescript
    definePageMeta({
      middleware: ['amid', 'bmid']
    })

定义具名中间件,amid.ts:

typescript
    export default defineNuxtRouteMiddleware((to, from) => {
      console.log('具名中间件a,影响指定页面:' + to.path); 
    })

全局中间件,创建 cmid.global.ts:

typescript
    export default defineNuxtRouteMiddleware((to, from) => {
      console.log('全局中间件c,影响所有页面');
    })

效果如下:

参数和工具方法

中间件可以获取目标路由 to 和来源路由 from,还有两个很常用的工具方法:

  • abortNavigation(error):跳过,留在 from;
  • navigateTo(route):指定跳转目标。
typescript
    export default defineNuxtRouteMiddleware((to, from) => {
      if (to.params.id === '1') {
        return abortNavigation()
      }
      return navigateTo('/')
    })

范例:路由守卫

下面给应用加一个路由守卫功能:如果没有登录则不能访问详情页 [id].vue。

首先创建 middleware/auth.ts:

typescript
    export default defineNuxtRouteMiddleware((to, from) => {
      const store = useUser();
      // 未登录,导航到登录页
      if (!store.isLogin) {
        return navigateTo("/login?callback=" + to.path)
      }
    })

页面中注册一下中间件,[id].vue:

typescript
    definePageMeta({
      middleware: ['auth']
    })
    
    // 现在留言不需要在页内判断登录态

服务端中间件

每当请求到达服务器时,会在处理其他路由之前先执行中间件。因此可以用服务端中间件做一些诸如:请求头检测、请求日志、扩展请求上下文对象等等任务。

服务端中间件使用

Nuxt 会自动读取 ~/server/middleware 中的文件作为服务端中间件,例如下面请求日志中间件:

typescript
    export default defineEventHandler((event) => {
      console.log('New request: ' + event.node.req.url)
    })

中间件还可以执行审查、扩展上下文或抛出错误:

typescript
    export default defineEventHandler((event) => {
      // 扩展上下文对象
      event.context.userInfo = { user: ‘cunzhang’ }
      // 审查请求信息
      const id = parseInt(event.context.params.id) as number
      if (!Number.isInteger(id)) {
        // 抛出错误
        return sendError(createError({
          statusCode: 400,
          statusMessage: 'ID should be an integer',
        }))
      }
    })

范例:保护指定服务端接口

下面我们完成一个接口保护的需求:用户如果未登录,不能调用 /api/detail/xxx

首先实现一个 server 中间件,检查指定接口请求中是否包含 token,~/server/middleware/auth.ts:

typescript
    import { H3Event } from "h3";
    
    export default defineEventHandler((event) => {
      // 请求不被允许时返回响应错误
      const isAllowed = protectAuthRoute(event);
      if (!isAllowed) {
        return sendError(
          event,
          createError({ statusCode: 401, statusMessage: "Unauthorized" })
        );
      }
    });
    
    function protectAuthRoute(event: H3Event) {
      const protectedRoutes = ["/api/detail"];
      // path 不以 protectedRoutes 中任何项为起始则允许请求
      if (
        !event?.path ||
        !protectedRoutes.some((route) => event.path?.startsWith(route))
      ) {
        return true;
      }
      return authCheck(event);
    }
    
    // 检查是否已认证
    function authCheck(event: H3Event) { 
      const token = getHeader(event, "token");
      if (!token) {
        return false;
      }
      return true;
    }

对应的,客户端详情页 [id].vue 不再需要中间件保护,同时需要在请求时携带令牌,并且处理可能的响应错误:

typescript
    // definePageMeta({
    //   middleware: ["auth"],
    // });
    
    const route = useRoute();
    const router = useRouter();
    const store = useUser();
    const fetchPost = () =>
      $fetch(`/api/detail/${route.params.id}`, {
        // 如果已登录,请求时携带令牌
        headers: store.isLogin ? { token: "abc" } : {},
        onResponseError: ({ response }) => {
          // 如果响应 401 错误,重新登录
          if (response.status === 401) {
            router.push("/login?callback=" + route.path);
          }
        },
      });

效果如下:

总结

至此,中间件的使用就介绍完了。可以看见中间件类似拦截器,前端拦截页面路由,后端拦截请求。路由中间件非常灵活,可以很容易控制作用范围和组合;服务端中间件则只能全局作用,在使用时要注意是不是指向作用于部分路由,如果是的话,要做一些额外的判断逻辑。

下次预告

除了中间件,Nuxt 还提供了另一种扩展框架能力的手段:插件,下次内容我们将给大家介绍插件的使用。

Released under the MIT License.