在构建中大型的 Next.js 应用时,我们往往需要在请求进入应用之前做一些预处理,比如用户认证、国际化设置、重定向等。Next.js 从 12 版本开始支持 Middleware,它运行于边缘节点,可以在请求到达页面组件之前拦截和处理请求。

本文将逐步讲解如何在 Next.js 中实现多个中间件的逻辑,并且保持代码的清晰与模块化。


一、Middleware 的基础概念

在 Next.js 中,你只需要在项目根目录创建一个名为 middleware.tsmiddleware.js 的文件即可启用 Middleware。这个文件会自动被 Next.js 识别,不需要额外配置。

一个最基础的中间件看起来如下:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  return NextResponse.next();
}

该中间件会对所有请求生效,并简单地允许请求继续往下走。


二、实现多个中间件逻辑的需求

假设我们现在有两个独立的功能需要处理:

  1. authMiddleware: 用于判断用户是否登录,未登录则重定向到 /login
  2. i18nMiddleware: 用于设置用户语言偏好

我们希望它们都在每个请求前依次执行。如何做到这点呢?


三、模块化中间件逻辑

为了保持代码整洁,我们将每个功能模块拆分到单独文件中:

middlewares/authMiddleware.ts

import { NextRequest, NextResponse } from 'next/server';

export function authMiddleware(request: NextRequest): NextResponse | void {
  const isAuthenticated = true; // 示例逻辑
  if (!isAuthenticated) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

middlewares/i18nMiddleware.ts

import { NextRequest, NextResponse } from 'next/server';

export function i18nMiddleware(request: NextRequest): NextResponse | void {
  const locale = request.headers.get('accept-language')?.split(',')[0] || 'en';
  request.headers.set('x-locale', locale);
}

四、在 middleware.ts 中组合这些中间件

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { authMiddleware } from './middlewares/authMiddleware';
import { i18nMiddleware } from './middlewares/i18nMiddleware';

export function middleware(request: NextRequest): NextResponse {
  const authResponse = authMiddleware(request);
  if (authResponse) return authResponse;

  const i18nResponse = i18nMiddleware(request);
  if (i18nResponse) return i18nResponse;

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

我们依次执行每一个中间件函数:

  • 如果某个中间件返回了 NextResponse,表示它希望拦截或终止请求流程,例如重定向;
  • 如果返回 undefined,表示它只是修改了请求状态或头部,不影响流程,继续执行下一个。

这种模式非常直观,也方便调试和扩展。


五、为什么使用 return 会中止后续中间件?

很多开发者初看可能会疑惑:

"既然我要执行多个中间件,为什么用 return 会跳过后面的逻辑?"

这是因为我们在实现“链式中间件”的处理逻辑——每个中间件有权决定是否终止请求流程。

举例来说,用户未认证时我们希望立即重定向到 /login,那么其他逻辑(比如设置语言、加载用户数据)就不需要再执行。

这种设计思路与很多后端框架如 Express.js 的中间件模型类似。


六、总结

Next.js 的 Middleware 是一个强大且灵活的工具,允许你在请求生命周期的早期阶段拦截和处理逻辑。

  • 你只需一个 middleware.ts 文件
  • 将每个中间件功能模块化,方便复用与测试
  • 在主中间件函数中按顺序调用它们,并根据是否返回 NextResponse 控制流程

这样的设计既保持了结构的清晰,又便于日后扩展。如果你在构建应用时需要实现用户权限、国际化、AB 测试等功能,Middleware 是一个值得深入使用的机制。

希望本文对你理解和使用 Next.js Middleware 有所帮助!

在 Go 语言中,flag 是一个用于解析命令行参数的标准库。通过这个库,你可以很方便地从命令行中获取用户传入的参数,比如字符串、整数、布尔值等。

主要用途:

处理命令行参数,例如:

go run main.go -name=Tom -age=25 -debug=true

基本使用示例:

package main

import (
    "flag"
    "fmt"
)

func main() {
    // 定义命令行参数
    name := flag.String("name", "default", "请输入用户名")
    age := flag.Int("age", 18, "请输入年龄")
    debug := flag.Bool("debug", false, "是否开启调试模式")

    // 解析命令行参数
    flag.Parse()

    fmt.Println("name:", *name)
    fmt.Println("age:", *age)
    fmt.Println("debug:", *debug)
}

常用函数:

函数用途
flag.String(name, default, usage)定义字符串参数
flag.Int(name, default, usage)定义整数参数
flag.Bool(name, default, usage)定义布尔参数
flag.Parse()开始解析命令行参数
flag.Args()获取非 flag 的参数
flag.NArg()获取非 flag 参数个数

需要注意,flag 默认只支持 -key=value-key value 的形式,不支持 --key 的双横线写法(这在某些 CLI 工具中是常见的)。

使用上面的写法获取到的变量是一个指针,所以在访问的时候如果要访问对应的值需要在变量之前加一个*号

如果想要直接得到变量也可以是用下面这种写法

var name string
flag.StringVar(&name, "name", "default", "请输入用户名")

这种写法是自己定义一个变量,然后将它的地址传进去


flag 后面的提示文字(也叫 usage 或说明文本)会在以下几种情况下自动显示:

1. 用户执行程序时传入了 h-help

这是最常见的触发方式,flag 包会自动帮你注册 -h--help 参数,触发时会显示所有参数的说明信息。例如:

go run main.go -h

输出会像这样:

Usage of /tmp/go-build.../main:
  -age int
        请输入年龄 (default 18)
  -debug
        是否开启调试模式 (default false)
  -name string
        请输入用户名 (default "default")

2. 程序中手动调用 flag.Usage()

你可以在代码里手动触发帮助信息显示,例如参数验证失败时:

if *name == "" {
    fmt.Println("name 参数不能为空")
    flag.Usage()
    os.Exit(1)
}

3. 覆盖默认的帮助输出内容(可选)

你也可以自定义 flag.Usage 的行为,比如美化输出:

flag.Usage = func() {
    fmt.Println("用法说明:")
    fmt.Println("  -name string    请输入用户名 (默认值:default)")
    fmt.Println("  -age int        请输入年龄 (默认值:18)")
    fmt.Println("  -debug          是否开启调试模式 (默认值:false)")
}

总结一下:

方式是否自动显示帮助信息
运行时使用 -h--help✅ 自动显示
手动调用 flag.Usage()✅ 手动触发
用户没有传必要参数❌ 不会自动显示(需要你手动判断并触发)

在 Bash 脚本中,if...then...fi 是最基础也是最常用的条件语句结构,类似于其他语言中的 if...else 结构。

🧱 基本语法格式

if 条件
then
    命令1
    命令2
fi

✅ 示例:

if [ -f /etc/passwd ]
then
    echo "文件存在"
fi
这个例子判断 /etc/passwd 是否存在并且是个普通文件。

✨ 加上 else 的格式:

if 条件
then
    命令1
else
    命令2
fi

示例:

if [ "$USER" = "root" ]
then
    echo "你是超级用户"
else
    echo "你不是 root 用户"
fi

🌈 多条件:if...elif...else...fi

if 条件1
then
    命令1
elif 条件2
then
    命令2
else
    命令3
fi

示例:

if [ "$1" = "start" ]
then
    echo "开始服务"
elif [ "$1" = "stop" ]
then
    echo "停止服务"
else
    echo "未知命令"
fi

🧠 条件表达式说明(最常用):

表达式含义
[ -f FILE ]文件存在且是普通文件
[ -d DIR ]目录存在
[ -z STRING ]字符串长度为 0
[ STRING1 = STRING2 ]两个字符串相等
[ NUM1 -eq NUM2 ]两个整数相等
[ NUM1 -lt NUM2 ]NUM1 小于 NUM2
[ 条件 ] && [ 条件 ]多个条件 "并且"

❗注意事项:

  • 方括号两边一定要留空格,如 [ "$USER" = "root" ](否则报错)
  • fiif 的结束标志(fiif 反过来)

🔧 小脚本示例:

#!/bin/bash

read -p "请输入一个数字: " num

if [ "$num" -gt 100 ]; then
    echo "大于 100"
elif [ "$num" -eq 100 ]; then
    echo "等于 100"
else
    echo "小于 100"
fi

grep 是 Linux 系统中非常强大且常用的命令行工具,用于在文件中搜索指定的字符串或正则表达式。其名称来自 "global regular expression print",意思是全局正则表达式打印。

基本语法:

grep [选项] '模式' 文件名

常用选项:

选项说明
-i忽略大小写
-v反向匹配,显示不包含匹配内容的行
-r-R递归搜索目录中的所有文件
-n显示匹配行的行号
-l只列出包含匹配内容的文件名
-c统计匹配的行数
--color=auto高亮显示匹配的部分(大多数 Linux 发行版默认启用)

使用示例:

  1. 在文件中查找字符串

    grep "hello" file.txt

    查找 file.txt 文件中包含 "hello" 的行。

  2. 忽略大小写

    grep -i "hello" file.txt
  3. 查找不包含某字符串的行

    grep -v "error" log.txt
  4. 递归查找目录中匹配内容

    grep -r "main" ./src
  5. 查找支持正则表达式

    grep "^start" file.txt

    匹配以 "start" 开头的行。


小技巧:

  • 可以与 pipe (|) 命令结合使用,从其他命令的输出中筛选信息:

    dmesg | grep usb
  • 与正则表达式配合,处理文本非常灵活。

grep -q

grep -qgrep 命令的一个非常实用的选项,意思是 安静模式(quiet / silent),它不会输出任何匹配结果,只通过返回码(退出状态码)来表示是否匹配成功。


✅ 功能说明:

  • 当使用 q 选项时,grep 不会在终端显示任何匹配的文本。
  • 仅通过退出码来判断是否有匹配行

🔢 退出状态码说明:

退出码含义
0找到匹配项
1未找到匹配项
2出现错误(如文件不存在等)

🧪 使用示例:

  1. 判断一个文件是否包含某个字符串:

    if grep -q "error" log.txt; then
        echo "日志中包含错误"
    else
        echo "日志中未发现错误"
    fi
    
  2. 配合 &&|| 使用:

    grep -q "success" result.txt && echo "任务成功"
    grep -q "fail" result.txt || echo "任务未失败"