中间件
中间件允许你在请求完成之前运行代码。然后,根据传入的请求,你可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。
¥Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.
中间件在缓存内容和路由匹配之前运行。详细信息请参见 匹配路径。
¥Middleware runs before cached content and routes are matched. See Matching Paths for more details.
用例
¥Use Cases
将中间件集成到你的应用中可以显着提高性能、安全性和用户体验。中间件特别有效的一些常见场景包括:
¥Integrating Middleware into your application can lead to significant improvements in performance, security, and user experience. Some common scenarios where Middleware is particularly effective include:
-
认证与授权:在授予对特定页面或 API 路由的访问权限之前,请确保用户身份并检查会话 cookie。
¥Authentication and Authorization: Ensure user identity and check session cookies before granting access to specific pages or API routes.
-
服务器端重定向:根据某些条件(例如区域设置、用户角色)在服务器级别重定向用户。
¥Server-Side Redirects: Redirect users at the server level based on certain conditions (e.g., locale, user role).
-
路径重写:通过根据请求属性动态重写 API 路由或页面的路径,支持 A/B 测试、功能推出或旧版路径。
¥Path Rewriting: Support A/B testing, feature rollouts, or legacy paths by dynamically rewriting paths to API routes or pages based on request properties.
-
机器人检测:通过检测和阻止机器人流量来保护你的资源。
¥Bot Detection: Protect your resources by detecting and blocking bot traffic.
-
日志记录和分析:在由页面或 API 处理之前,捕获并分析请求数据以获取见解。
¥Logging and Analytics: Capture and analyze request data for insights before processing by the page or API.
-
功能标记:动态启用或禁用功能以实现无缝功能部署或测试。
¥Feature Flagging: Enable or disable features dynamically for seamless feature rollouts or testing.
认识到中间件可能不是最佳方法的情况也同样重要。以下是一些需要注意的场景:
¥Recognizing situations where middleware may not be the optimal approach is just as crucial. Here are some scenarios to be mindful of:
-
复杂的数据获取和操作:中间件不是为直接数据获取或操作而设计的,这应该在路由处理程序或服务器端实用程序中完成。
¥Complex Data Fetching and Manipulation: Middleware is not designed for direct data fetching or manipulation, this should be done within Route Handlers or server-side utilities instead.
-
繁重的计算任务:中间件应该是轻量级的并且响应快速,否则可能会导致页面加载延迟。繁重的计算任务或长时间运行的进程应在专用的路由处理程序中完成。
¥Heavy Computational Tasks: Middleware should be lightweight and respond quickly or it can cause delays in page load. Heavy computational tasks or long-running processes should be done within dedicated Route Handlers.
-
广泛的会话管理:虽然中间件可以管理基本会话任务,但广泛的会话管理应由专用身份验证服务或在路由处理程序内管理。
¥Extensive Session Management: While Middleware can manage basic session tasks, extensive session management should be managed by dedicated authentication services or within Route Handlers.
-
直接数据库操作:不建议在中间件中执行直接数据库操作。数据库交互应在路由处理程序或服务器端实用程序中完成。
¥Direct Database Operations: Performing direct database operations within Middleware is not recommended. Database interactions should be done within Route Handlers or server-side utilities.
惯例
¥Convention
使用项目根目录中的文件 middleware.ts
(或 .js
)来定义中间件。例如,与 pages
或 app
处于同一级别,或者在 src
内部(如果适用)。
¥Use the file middleware.ts
(or .js
) in the root of your project to define Middleware. For example, at the same level as pages
or app
, or inside src
if applicable.
注意:虽然每个项目仅支持一个
middleware.ts
文件,但你仍然可以模块化地组织中间件逻辑。将中间件函数分解为单独的.ts
或.js
文件,并将它们导入到主middleware.ts
文件中。这样可以对特定于路由的中间件进行更清晰的管理,这些中间件聚合在middleware.ts
中以进行集中控制。通过强制执行单个中间件文件,它可以简化配置,防止潜在冲突,并通过避免多个中间件层来优化性能。¥Note: While only one
middleware.ts
file is supported per project, you can still organize your middleware logic modularly. Break out middleware functionalities into separate.ts
or.js
files and import them into your mainmiddleware.ts
file. This allows for cleaner management of route-specific middleware, aggregated in themiddleware.ts
for centralized control. By enforcing a single middleware file, it simplifies configuration, prevents potential conflicts, and optimizes performance by avoiding multiple middleware layers.
示例
¥Example
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
匹配路径
¥Matching Paths
将为项目中的每个路由调用中间件。鉴于此,使用匹配器来精确定位或排除特定路由至关重要。以下是执行顺序:
¥Middleware will be invoked for every route in your project. Given this, it's crucial to use matchers to precisely target or exclude specific routes. The following is the execution order:
-
headers
从next.config.js
¥
headers
fromnext.config.js
-
redirects
从next.config.js
¥
redirects
fromnext.config.js
-
中间件(
rewrites
、redirects
等)¥Middleware (
rewrites
,redirects
, etc.) -
beforeFiles
(rewrites
) 从next.config.js
¥
beforeFiles
(rewrites
) fromnext.config.js
-
文件系统路由(
public/
、_next/static/
、pages/
、app/
等)¥Filesystem routes (
public/
,_next/static/
,pages/
,app/
, etc.) -
afterFiles
(rewrites
) 从next.config.js
¥
afterFiles
(rewrites
) fromnext.config.js
-
动态路由 (
/blog/[slug]
)¥Dynamic Routes (
/blog/[slug]
) -
fallback
(rewrites
) 从next.config.js
¥
fallback
(rewrites
) fromnext.config.js
有两种方法可以定义中间件将在哪些路径上运行:
¥There are two ways to define which paths Middleware will run on:
匹配器
¥Matcher
matcher
允许你过滤中间件以在特定路径上运行。
¥matcher
allows you to filter Middleware to run on specific paths.
export const config = {
matcher: '/about/:path*',
}
你可以使用数组语法匹配单个路径或多个路径:
¥You can match a single path or multiple paths with an array syntax:
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
matcher
配置允许完整的正则表达式,因此支持负向查找或字符匹配等匹配。可以在此处查看用于匹配除特定路径之外的所有路径的负前瞻示例:
¥The matcher
config allows full regex so matching like negative lookaheads or character matching is supported. An example of a negative lookahead to match all except specific paths can be seen here:
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
你还可以使用 missing
或 has
数组或两者的组合来绕过某些请求的中间件:
¥You can also bypass Middleware for certain requests by using the missing
or has
arrays, or a combination of both:
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
很高兴知道:
matcher
值需要是常量,以便可以在构建时对其进行静态分析。诸如变量之类的动态值将被忽略。¥Good to know: The
matcher
values need to be constants so they can be statically analyzed at build-time. Dynamic values such as variables will be ignored.
配置的匹配器:
¥Configured matchers:
-
必须以
/
开头¥MUST start with
/
-
可以包含命名参数:
/about/:path
匹配/about/a
和/about/b
,但不匹配/about/a/c
¥Can include named parameters:
/about/:path
matches/about/a
and/about/b
but not/about/a/c
-
可以对命名参数进行修饰符(以
:
开头):/about/:path*
与/about/a/b/c
匹配,因为*
为零或更大。?
为零或一,+
为一或多个¥Can have modifiers on named parameters (starting with
:
):/about/:path*
matches/about/a/b/c
because*
is zero or more.?
is zero or one and+
one or more -
可以使用括号括起来的正则表达式:
/about/(.*)
与/about/:path*
相同¥Can use regular expression enclosed in parenthesis:
/about/(.*)
is the same as/about/:path*
阅读有关 path-to-regexp 文档的更多详细信息。
¥Read more details on path-to-regexp documentation.
很高兴知道:为了向后兼容,Next.js 始终将
/public
视为/public/index
。因此,/public/:path
的匹配器将匹配。¥Good to know: For backward compatibility, Next.js always considers
/public
as/public/index
. Therefore, a matcher of/public/:path
will match.
条件语句
¥Conditional Statements
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
NextResponse
API 允许你:
¥The NextResponse
API allows you to:
-
redirect
传入请求到不同的 URL¥
redirect
the incoming request to a different URL -
rewrite
通过显示给定 URL 进行响应¥
rewrite
the response by displaying a given URL -
设置 API 路由、
getServerSideProps
和rewrite
目标的请求标头¥Set request headers for API Routes,
getServerSideProps
, andrewrite
destinations -
设置响应 cookie
¥Set response cookies
-
设置响应头
¥Set response headers
要从中间件生成响应,你可以:
¥To produce a response from Middleware, you can:
-
¥
rewrite
to a route (Page or Route Handler) that produces a response -
直接返回
NextResponse
。见 产生响应¥return a
NextResponse
directly. See Producing a Response
使用 Cookie
¥Using Cookies
Cookie 是常规标头。在 Request
上,它们存储在 Cookie
标头中。在 Response
上,它们位于 Set-Cookie
标头中。Next.js 通过 NextRequest
和 NextResponse
上的 cookies
扩展提供了一种便捷的方式来访问和操作这些 cookie。
¥Cookies are regular headers. On a Request
, they are stored in the Cookie
header. On a Response
they are in the Set-Cookie
header. Next.js provides a convenient way to access and manipulate these cookies through the cookies
extension on NextRequest
and NextResponse
.
-
对于传入的请求,
cookies
带有以下方法:get
、getAll
、set
和delete
cookie。你可以检查has
的 cookie 是否存在,或者删除clear
的所有 cookie。¥For incoming requests,
cookies
comes with the following methods:get
,getAll
,set
, anddelete
cookies. You can check for the existence of a cookie withhas
or remove all cookies withclear
. -
对于传出响应,
cookies
有以下方法get
、getAll
、set
和delete
。¥For outgoing responses,
cookies
have the following methodsget
,getAll
,set
, anddelete
.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Setting cookies on the response using the `ResponseCookies` API
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Setting cookies on the response using the `ResponseCookies` API
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header.
return response
}
设置标题
¥Setting Headers
你可以使用 NextResponse
API 设置请求和响应标头(自 Next.js v13.0.0 起可以设置请求标头)。
¥You can set request and response headers using the NextResponse
API (setting request headers is available since Next.js v13.0.0).
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.next
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.next
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
很高兴知道:避免设置大标头,因为它可能会导致 431 请求标头字段太大 错误,具体取决于你的后端 Web 服务器配置。
¥Good to know: Avoid setting large headers as it might cause 431 Request Header Fields Too Large error depending on your backend web server configuration.
CORS
你可以在中间件中设置 CORS 标头以允许跨域请求,包括 simple 和 preflighted 请求。
¥You can set CORS headers in Middleware to allow cross-origin requests, including simple and preflighted requests.
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
import { NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
很高兴知道:你可以为 路由处理程序 中的各个路由配置 CORS 标头。
¥Good to know: You can configure CORS headers for individual routes in Route Handlers.
产生响应
¥Producing a Response
你可以通过返回 Response
或 NextResponse
实例直接从中间件进行响应。(从 Next.js v13.1.0 年开始可用)
¥You can respond from Middleware directly by returning a Response
or NextResponse
instance. (This is available since Next.js v13.1.0)
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
和 NextFetchEvent
¥waitUntil
and NextFetchEvent
NextFetchEvent
对象扩展了原生 FetchEvent
对象,并包含 waitUntil()
方法。
¥The NextFetchEvent
object extends the native FetchEvent
object, and includes the waitUntil()
method.
waitUntil()
方法将 Promise 作为参数,并延长中间件的生命周期,直到 Promise 解决。这对于在后台执行工作很有用。
¥The waitUntil()
method takes a promise as an argument, and extends the lifetime of the Middleware until the promise settles. This is useful for performing work in the background.
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
高级中间件标志
¥Advanced Middleware Flags
在 Next.js 的 v13.1
中,为中间件引入了两个附加标志 skipMiddlewareUrlNormalize
和 skipTrailingSlashRedirect
来处理高级用例。
¥In v13.1
of Next.js two additional flags were introduced for middleware, skipMiddlewareUrlNormalize
and skipTrailingSlashRedirect
to handle advanced use cases.
skipTrailingSlashRedirect
禁用 Next.js 重定向以添加或删除尾部斜杠。这允许中间件内部的自定义处理来维护某些路径的尾部斜杠,而不是其他路径,这可以使增量迁移更容易。
¥skipTrailingSlashRedirect
disables Next.js redirects for adding or removing trailing slashes. This allows custom handling inside middleware to maintain the trailing slash for some paths but not others, which can make incremental migrations easier.
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
return NextResponse.redirect(
new URL(`${req.nextUrl.pathname}/`, req.nextUrl)
)
}
}
skipMiddlewareUrlNormalize
允许禁用 Next.js 中的 URL 规范化,以使直接访问和客户端转换的处理相同。在某些高级情况下,此选项通过使用原始 URL 提供完全控制。
¥skipMiddlewareUrlNormalize
allows for disabling the URL normalization in Next.js to make handling direct visits and client-transitions the same. In some advanced cases, this option provides full control by using the original URL.
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
单元测试(实验性)
¥Unit Testing (experimental)
从 Next.js 15.1 开始,next/experimental/testing/server
包包含帮助对中间件文件进行单元测试的实用程序。单元测试中间件可以帮助确保它只在所需的路径上运行,并且自定义路由逻辑在代码投入生产之前按预期工作。
¥Starting in Next.js 15.1, the next/experimental/testing/server
package contains utilities to help unit test middleware files. Unit testing middleware can help ensure that it's only run on desired paths and that custom routing logic works as intended before code reaches production.
unstable_doesMiddlewareMatch
函数可用于断言中间件是否会为提供的 URL、标头和 cookie 运行。
¥The unstable_doesMiddlewareMatch
function can be used to assert whether middleware will run for the provided URL, headers, and cookies.
import { unstable_doesMiddlewareMatch } from 'next/experimental/testing/server'
expect(
unstable_doesMiddlewareMatch({
config,
nextConfig,
url: '/test',
})
).toEqual(false)
整个中间件函数也可以测试。
¥The entire middleware function can also be tested.
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
const request = new NextRequest('https://next.nodejs.cn/docs')
const response = await middleware(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// getRedirectUrl could also be used if the response were a redirect
运行时
¥Runtime
中间件目前仅支持与 边缘运行时 兼容的 API。Node.js 独有的 API 是 unsupported。
¥Middleware currently only supports APIs compatible with the Edge runtime. APIs exclusive to Node.js are unsupported.
版本历史
¥Version History
版本 | 变化 |
---|---|
v13.1.0 | 添加了高级中间件标志 |
v13.0.0 | 中间件可以修改请求头、响应头、发送响应 |
v12.2.0 | 中间件稳定,请参见 升级指南 |
v12.0.9 | 在 Edge Runtime 中强制使用绝对 URL (PR) |
v12.0.0 | 添加了中间件(测试版) |