为了账号安全,请及时绑定邮箱和手机立即绑定

在 NextJS 中的类似 Express 中间件

试图用AI描绘图像的中间件,结果很糟糕

Next.js 框架不仅功能强大,而且功能齐全。它能够快速开发和迭代,拥有强大的支持和活跃的社区。对于习惯使用类似 Express 框架的 Node.js 开发者来说,我经常听到开发者抱怨该框架缺少对个性化路由中间件的支持。

虽然可以通过在基础midware文件中使用路由匹配来实现这一点,这种方法总是让我感觉有点笨拙,,我喜欢定义一个路由,并为这个路由添加一些可以作为中间件运行的方法。

一个类似快递的风格

我想要定义我的路由路径,并设置一些在路由执行前运行的函数。这些函数可以做以下几件事:

  1. 验证用户
  2. 根据角色或权限授予用户权限
  3. 将数据添加到请求
  4. 等等

在 NextJS v14 中,您只需在应用目录中的文件夹内放入一个 route.ts 文件即可定义一个 API 路由。这个 route.ts 文件可以支持基本的 HTTP 方法,比如 GET 和 POST。

  • 获取
  • 更新
  • 新建
  • 删除
  • 部分更新

你会看到它是这样用的

    export async function GET(request) {  
      // 请在此处填写你的逻辑  
    }  

    export async function POST(request, { params }) {  
      // 请在此处填写你的逻辑  
    }

为了在这些路由处理程序运行之前以通用方式运行一些方法,目标是这样做到。这需要几个步骤来实现。

  1. 定义一个通用的路由处理器来处理每个请求,并允许中间件的组合和管理。
  2. 定义中间件功能。
  3. 在路由处理器中调用这些中间件功能。
第一步:通用路由处理器,或者您的中间件处理器

在我的 NextJS 应用中,我会创建一个名为“middleware”的文件夹,其中在该文件夹中创建的第一个文件是 handle-request.ts。该文件的内容如下所示:

    export interface MiddlewareResponse {  
        pass: boolean  
        response?: NextResponse  
        data?: any  
    }  

    export async function handleRequest(  
      request,  
      payload,  
      callback,  
      middleware: Function[] = []  
    ) {  
      for (const middlewareFunction of middleware) {  
        const result: MiddlewareResponse = await middlewareFunction(  
          request,  
          payload  
        );  

        if (result.pass === false) {  
          return result.response;  
        } else if (result.data) {  
          if (!request.data) {  
            request.data = {};  
          }  

          try {  
            // 注意:如果有多个中间件需要数据,这将覆盖前一个中间件的数据。  
            request.data = {  
              ...request.data,  
              ...result.data,  
            };  
          } catch (e) {  
            console.error(e);  
          }  
        }  
      }  

      return callback(request, payload);  
    }

这个功能有几个作用:

  1. 它处理传入的 middleware 函数组成的数组。
  2. 对每个函数,如果成功,则将从中获取的数据附加到请求并继续处理下一个函数。如果失败,则返回该 middleware 函数的失败信息。
  3. 如果所有 middleware 函数都成功,则调用传入的回调函数,也就是您的路由处理函数。

实际上,此方法一般这样使用在路由处理器中:

    import { handleRequest } from "@/middleware/handle-request";  

    export async function GET(request, extra) {  
      return handleRequest(request, extra, handleGet, []);  
    }  

    export async function PUT(request, extra) {  
      return handleRequest(request, extra, handlePut, []);  
    }  

    async function handleGet(request, { params }) {  
      // 处理GET请求  
    }  

    async function handlePut(request, { params }) {  
      // 处理PUT请求  
    }

这可能看起来有点怪,但最后路由处理程序返回的内容会被函数返回,前提是中间件允许通过。

但是,我们在handleRequest函数的最后一个参数中应该放什么呢?我们来看一个简单的auth中间件示例吧。

步骤二:定义中间件(Middleware)。

在我的中间件文件夹里,我将为每个要支持的中间件定义单独的文件。每个中间件一个文件,保持清晰。若您使用的是 NextAuth 并打算支持基本认证,可以创建一个 auth.ts 文件:

    import { NextResponse } from 'next/server'
    import { MiddlewareResponse } from './handle-request'
    import { getServerSession } from "next-auth";

    export async function auth(request, payload): Promise<MiddlewareResponse> {
        const session = await getServerSession(authOptions);

        const userId = session?.user.id;

        if (!userId) {
            return {
                pass: false,
                response: NextResponse.json(
                    { error: '未通过验证' },
                    { status: 401 }
                ),
            }
        }

        const user = /* 你的应用程序逻辑来通过ID查找用户。*/

        if (!user) {
            // 如果有会话ID但找不到对应的用户,可能出了问题。
            return {
                pass: false,
                response: NextResponse.json(
                    { error: '出现错误' },
                    { status: 500 }
                ),
            }
        }

        return {
            pass: true,
            data: { user },
        }
    }

这还算基础,而且能搞定任务:

  • 它检查是否存在 NextAuth 会话。
  • 如果有,它从会话中提取用户 ID。
  • 它根据会话 ID 查找用户。
  • 它将此信息附加到请求中。
步骤三:在路由处理器中引用该内容

你现在可以在这个路由中使用这个中间件了:

    import { handleRequest } from "@/middleware/handle-request";
    import { auth } from "@/middleware/auth";

    export async function GET(request, extra) {
      return handleRequest(request, extra, handleGet, [auth]);
    }

    export async function PUT(request, extra) {
      return handleRequest(request, extra, handlePut, [auth]);
    }

    async function handleGet(request, { params }) {
      /* 处理GET请求的逻辑 */
    }

    async function handlePut(request, { params }) {
      /* 处理PUT请求的逻辑 */
    }

你的路由现在受到保护了。如果认证失败,你的路由甚至都不会被触发。

如果我想依次运行多个中间件函数,应该怎么做?

很好,这个问题很容易解决。让我们定义一个新的中间件,用来检查某人是否具有特定的角色。

isAdmin中间件

import { NextResponse } from "next/server";
import { MiddlewareResponse } from "./handle-request";

export async function isAdminUser(
  request,
  payload
): Promise<MiddlewareResponse> {
  const { user } = request.data;

  // 我们假设用户对象有一个 "role" 参数。
  if (user.role === "admin") {
    return {
      pass: true,
      data: {
        user,
      },
    };
  }

  return {
    pass: false,
    response: NextResponse.json({ message: "禁止访问" }, { status: 403 }), // 状态码 403 表示禁止访问
  };
}

记住,我们在认证中间件中把 user 对象绑定到请求上,因此我们可以在后续请求中使用它。在我的应用中,按照惯例 auth 总是第一个处理的中间件,因此在后续请求中 user 总是可用的。

如果你想现在把这两个中间件函数加入到你的处理程序里。

路由处理器:

    import { handleRequest } from "@/middleware/handle-request";
    import { auth } from "@/middleware/auth";
    import { isAdminUser } from "@/middleware/isAdminUser";

    export async function GET(request, extra) {
      return handleRequest(request, extra, handleGet, [auth, isAdminUser]);
    }

    export async function PUT(request, extra) {
      return handleRequest(request, extra, handlePut, [auth, isAdminUser]);
    }

    async function handleGet(request, { params }) {
      // 路由处理程序逻辑
    }

    async function handlePut(request, { params }) {
      // 路由处理程序逻辑
    }

它们会一个接一个地运行。

如果我想给我的中间件(这种位于应用软件和操作系统之间的软件)添加动态参数怎么办?

这也是个好问题。有时候你可能希望在不为每个权限都写中间件的情况下,在路由上检查特定权限。

在这种情况下,你只需要记住,类似于 expressJS 中的中间件,只需要提供一个函数即可。你也只需要提供一个返回函数的函数。我们来看一个例子:

hasPermission 权限检查

    import { NextResponse } from "next/server";  
    import { MiddlewareResponse } from "./handle-request";  

    export function 检查权限(permission): (request, payload) => Promise<MiddlewareResponse> {  
      return async function (request, payload): Promise<MiddlewareResponse> {  
        const { user } = request.data;  

        // 我们假设用户有一个“permissions”数组,该数组包含用户的全部权限。这些权限通常会在用户被身份验证中间件获取时设置  
        const hasPermission = user.permissions.some(  
          (p) => p.name === permission  
        );  

        if (!hasPermission) {  
          return {  
            pass: false,  
            response: NextResponse.json({ error: "没有获得授权" }, { status: 401 }),  
          };  
        }  

        return {  
          pass: true,  
          data: { user },  
        };  
      };  
    }

注意以下几点:

  1. 这个中间件提供一个函数。
  2. 那个函数使用了权限参数,因此可以动态检查返回值。

你可以这样用它:

路由处理器

    import { handleRequest } from "@/middleware/handle-request";
    import { auth } from "@/middleware/auth";
    import { isAdminUser } from "@/middleware/hasPermission";

    export async function GET(request, extra) {
      return handleRequest(request, extra, handleGet, [
        auth,
        isAdminUser('manageUsers')
      ]);
    }

    export async function PUT(request, extra) {
      return handleRequest(request, extra, handlePut, [
        auth,
        isAdminUser('manageUsers')
      ]);
    }

    async function handleGet(request, { params }) {
      // 获取用户信息的处理逻辑
    }

    async function handlePut(request, { params }) {
      // 更新用户信息的处理逻辑
    }
摘要

就这样。我认为有更好的方法来实现这一点,但这是在NextJS应用程序中实现中间件而无需绕框架的弯的一种相当直接的方式。这种模式也可以用在其他框架上。

你有关于如何让它变得更好的想法吗?下面分享一下吧!

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消