试图用AI描绘图像的中间件,结果很糟糕
Next.js 框架不仅功能强大,而且功能齐全。它能够快速开发和迭代,拥有强大的支持和活跃的社区。对于习惯使用类似 Express 框架的 Node.js 开发者来说,我经常听到开发者抱怨该框架缺少对个性化路由中间件的支持。
虽然可以通过在基础midware文件中使用路由匹配来实现这一点,这种方法总是让我感觉有点笨拙,,我喜欢定义一个路由,并为这个路由添加一些可以作为中间件运行的方法。
一个类似快递的风格我想要定义我的路由路径,并设置一些在路由执行前运行的函数。这些函数可以做以下几件事:
- 验证用户
- 根据角色或权限授予用户权限
- 将数据添加到请求
- 等等
在 NextJS v14 中,您只需在应用目录中的文件夹内放入一个 route.ts
文件即可定义一个 API 路由。这个 route.ts
文件可以支持基本的 HTTP 方法,比如 GET 和 POST。
- 获取
- 更新
- 新建
- 删除
- 部分更新
你会看到它是这样用的
export async function GET(request) {
// 请在此处填写你的逻辑
}
export async function POST(request, { params }) {
// 请在此处填写你的逻辑
}
为了在这些路由处理程序运行之前以通用方式运行一些方法,目标是这样做到。这需要几个步骤来实现。
- 定义一个通用的路由处理器来处理每个请求,并允许中间件的组合和管理。
- 定义中间件功能。
- 在路由处理器中调用这些中间件功能。
在我的 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);
}
这个功能有几个作用:
- 它处理传入的
middleware
函数组成的数组。 - 对每个函数,如果成功,则将从中获取的数据附加到请求并继续处理下一个函数。如果失败,则返回该 middleware 函数的失败信息。
- 如果所有 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
中间件示例吧。
在我的中间件文件夹里,我将为每个要支持的中间件定义单独的文件。每个中间件一个文件,保持清晰。若您使用的是 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 },
};
};
}
注意以下几点:
- 这个中间件提供一个函数。
- 那个函数使用了权限参数,因此可以动态检查返回值。
你可以这样用它:
路由处理器
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应用程序中实现中间件而无需绕框架的弯的一种相当直接的方式。这种模式也可以用在其他框架上。
你有关于如何让它变得更好的想法吗?下面分享一下吧!