# 自定义中间件

URL: https://caijiao.org/express/chapter7/1-custom
Source: docs/express/chapter7/1-custom.md
Description: 本章节介绍如何在 Express 应用中自定义中间件，以实现更灵活的请求处理、日志记录、认证等功能。

在 Express 中，中间件是处理请求和响应的核心。它们是处理请求生命周期中的不同阶段的函数。通过中间件，我们可以执行很多操作，比如请求预处理、错误处理、日志记录、认证、授权等。本章节将介绍如何创建和使用自定义中间件，以增强应用的功能和灵活性。

## 中间件基础

中间件函数是具有三个参数的函数：`req`、`res` 和 `next`。其中：

- `req`：表示请求对象，包含请求的详细信息。
- `res`：表示响应对象，可以用它发送响应给客户端。
- `next`：是一个函数，调用它会将控制权交给下一个中间件函数或路由处理程序。

```js
function myMiddleware(req, res, next) {
  console.log('请求到达了自定义中间件');
  next(); // 调用 next()，将控制权交给下一个中间件
}

app.use(myMiddleware); // 注册中间件
````

## 自定义中间件示例

### 1. 请求日志中间件

一个简单的日志中间件可以记录每个请求的 HTTP 方法和 URL 地址。你可以将这个中间件应用于所有请求。

```js
function requestLogger(req, res, next) {
  const method = req.method;
  const url = req.url;
  const time = new Date().toISOString();
  console.log(`[${time}] ${method} ${url}`);
  next(); // 调用 next()，将控制权交给下一个中间件
}

app.use(requestLogger); // 在应用中使用日志中间件
```

当访问应用时，它将输出类似下面的日志：

```
[2025-06-07T10:00:00.000Z] GET /user/johndoe
```

### 2. 身份验证中间件

身份验证是 Web 应用中常见的功能。我们可以通过自定义中间件来检查请求是否携带有效的身份认证信息，例如 JWT（JSON Web Token）。

```js
function authenticate(req, res, next) {
  const token = req.headers['authorization'];

  if (!token) {
    return res.status(403).send('权限不足，请提供认证令牌');
  }

  // 验证令牌（假设验证方法是 verifyToken）
  verifyToken(token, (err, decoded) => {
    if (err) {
      return res.status(403).send('无效的认证令牌');
    }
    req.user = decoded; // 将解码后的用户信息存储在 req.user 中
    next(); // 调用 next()，继续处理请求
  });
}

// 保护某些路由
app.get('/protected', authenticate, (req, res) => {
  res.send('你已经通过身份验证');
});
```

### 3. 错误处理中间件

在 Express 中，错误处理中间件是特殊的。它有四个参数：`err`、`req`、`res` 和 `next`。错误处理中间件捕获请求处理过程中发生的任何错误，并根据需要返回适当的响应。

```js
function errorHandler(err, req, res, next) {
  console.error('发生了错误:', err.stack);
  res.status(500).send('服务器错误');
}

// 错误处理中间件需要放在所有路由和中间件之后
app.use(errorHandler);
```

### 4. 路由特定中间件

有时，我们只需要在某些特定的路由上使用自定义中间件。你可以通过将中间件作为路由处理的参数来实现这一点。

```js
// 只有访问 /user 路由时才会执行此中间件
app.use('/user', function (req, res, next) {
  console.log('访问了 /user 路由');
  next(); // 继续处理请求
});

app.get('/user', (req, res) => {
  res.send('用户信息');
});
```

### 5. 链式中间件

Express 允许我们将多个中间件函数按顺序链接，处理请求。在每个中间件函数中，可以根据需求执行不同的操作，最终将控制权传递给下一个中间件。

```js
app.use((req, res, next) => {
  console.log('中间件 1');
  next(); // 调用下一个中间件
});

app.use((req, res, next) => {
  console.log('中间件 2');
  next();
});

app.use((req, res) => {
  console.log('最后的中间件');
  res.send('请求结束');
});
```

### 6. 中间件顺序

Express 中间件的执行顺序是非常重要的。中间件是按照注册的顺序执行的，因此确保将中间件按照合适的顺序放置非常重要。例如，身份验证中间件应该放在处理请求的路由之前，错误处理中间件应该放在所有路由和其他中间件的后面。

```js
app.use(requestLogger); // 先记录请求日志
app.use(authenticate);  // 后进行身份验证
app.get('/user', (req, res) => {  // 最后是具体的路由处理
  res.send('用户信息');
});
```

## 全局中间件与局部中间件

* **全局中间件**：应用于所有路由的中间件，通常在应用启动时使用 `app.use()` 注册。

  ```js
  app.use((req, res, next) => {
    console.log('全局中间件');
    next();
  });
  ```

* **局部中间件**：只应用于特定路由的中间件，通过在路由定义时指定中间件来实现。

  ```js
  app.get('/user', (req, res, next) => {
    console.log('局部中间件');
    next();
  }, (req, res) => {
    res.send('用户信息');
  });
  ```

## 中间件的性能考量

虽然中间件提供了很大的灵活性，但它们也可能对性能产生影响。尤其是在高流量的应用中，以下几点需要注意：

* **避免不必要的中间件**：只在需要的路由上使用中间件，避免全局中间件影响性能。
* **异步中间件**：对于处理异步操作的中间件，确保它们处理完后调用 `next()`，否则请求将被挂起。
* **缓存**：对于不常变动的资源，使用缓存中间件可以提高性能。

## 总结

在本章节中，我们学习了如何在 Express 应用中创建和使用自定义中间件。通过中间件，我们可以在请求处理的不同阶段执行各种操作，包括日志记录、身份验证、错误处理等。

* **中间件基础**：`req`、`res` 和 `next` 是中间件的基本参数。
* **常见中间件示例**：包括日志记录、身份验证、错误处理等。
* **中间件顺序**：中间件的顺序非常重要，应该按照逻辑顺序注册。
* **局部与全局中间件**：全局中间件适用于所有路由，而局部中间件只应用于特定路由。

通过灵活使用中间件，能够使 Express 应用更加模块化、可维护，并实现更强的功能扩展。
