路由
网站用户通过 /
, /about
或者 posts/1
这样的 URL 来访问网站应用,为了保证这些 URL 能正确的工作,我们需要将其定义为路由。
路由通常使用 Route 模块被定义在 start/routes.ts
文件中。
一个典型的路由接收路由式样作为第一个参数,处理方法作为第二个参数。例:
import Route from '@ioc:Adonis/Core/Route'
Route.get('/', () => {
return 'Hello world'
})
路由的处理方法也可以引用 控制器 的方法。
import Route from '@ioc:Adonis/Core/Route'
Route.get('posts', 'PostsController.index')
默认路由文件#
通常来说,路由都是在 start/routes.ts
文件中注册,AdonisJS 在启动时会 预加载 这个文件。然而这并不是一个硬性限制,你也可以将路由分散定义在不同的路由文件中。
下面是一些构造和加载来自其他文件路由的方法。
在 routes.ts
中 import#
一种方法是在不同模块中定义不同的路由,然后在 start/routes.ts
文件中全部 import。
// title: start/routes.ts
import 'App/Modules/User/routes'
import 'App/Modules/Cart/routes'
import 'App/Modules/Product/routes'
注册一个预加载文件#
另一种方法是完全去掉路由文件,使用自定义的文件去注册路由。在这种情况下,请确保自定义文件的路径在 .adonisrc.json
中的 preloads
数组中。
// title: .adonisrc.json
{
"preloads": [
"./start/kernel",
"add-path-to-your-routes-file"
]
}
查看路由#
你可以通过运行以下 Ace 命令查看已注册的路由。
node ace list:routes
默认情况下,路由在结构化表中显示得很漂亮。但是,您也可以通过添加 --json
标志以 JSON 字符串的形式进行访问。
node ace list:routes --json > routes.json
HTTP 方法#
AdonisJS 提供速记方法来为常用 HTTP 动作注册路由。例如:
Post 动作#
Route.post('posts', async () => {})
Put 动作#
Route.put('posts/:id', async () => {})
Patch 动作#
Route.patch('posts/:id', async () => {})
Delete 动作#
Route.delete('posts/:id', async () => {})
其余的 HTTP 动作#
对于其余的 HTTP 动作,你可以使用 Route.route
方法。
Route.route('/', ['OPTIONS', 'HEAD'], async () => {})
所有常见 HTTP 动作的路由#
Route.any
方法通过注册路由以处理以下所有 HTTP 动作的请求。
- HEAD
- OPTIONS
- GET
- POST
- PUT
- PATCH
- DELETE
Route.any('csp-report', async () => {})
路由参数#
路由参数提供了一种注册 URL 的方法,这些 URL 可以接受动态值作为 URL 的一部分。
参数始终以冒号:
开头,后跟参数名称。例如:
Route.get('/posts/:id', async ({ params }) => {
return `Viewing post with id ${params.id}`
})
可选参数#
参数也可以通过将问号 ?
附加到变量名后来标记为可选。但是,请确保可选参数位于必需参数之后。
Route.get('/posts/:id?', async ({ params }) => {
if (params.id) {
return `Viewing post with id ${params.id}`
}
return 'Viewing all posts'
})
通配符参数#
你还可以使用 *
关键字定义通配符参数。通配符参数捕获所有 URI 片段。例如:
Route.get('docs/*', ({ params }) => {
console.log(params['*'])
})
URL | 通配符参数 |
---|---|
/docs/http/introduction |
['http', 'introduction'] |
/docs/api/sql/orm |
['api', 'sql', 'orm'] |
你还可以在通配符参数旁边使用命名参数。但是,请确保通配符参数位于命名参数之后。
Route.get('docs/:category/*', ({ params }) => {
console.log(params.category)
console.log(params['*'])
})
参数匹配器#
参数匹配器允许你根据给定的正则表达式验证参数。如果检查失败,将跳过该路由。
假设我们想通过 id
和 slug
来查找一个帖子:
Route
.get('/posts/:id', async ({ params }) => {
return `Viewing post using id ${params.id}`
})
.where('id', /^[0-9]+$/)
Route
.get('/posts/:slug', async ({ params }) => {
return `Viewing post using slug ${params.slug}`
})
.where('slug', /^[a-z0-9_-]+$/)
- 包含数字 id 的请求传递到
/posts
时将被转发到第一个路由。例如:/posts/1
或/posts/300
- 匹配 slug 正则表达式的请求将被转发到第二个路由。例如:
/posts/hello_world
或/posts/adonis-101
。 - 找不到匹配项时返回 404。
你还可以使用 Route.where
方法全局定义参数匹配器。除非在路由级别专门进行覆盖,否则全局匹配器将应用于所有路由。
Route.where('id', /^[0-9]+$/)
参数转换#
URL 的参数部分始终表示为字符串。例如:在 URL /posts/1
中,值 1
是字符串而不是数字,因为没有直接的方法来推断 URI 段的数据类型。
但是,你可以通过使用参数匹配器定义 cast
属性来手动将参数转换为它们实际的 JavaScript 数据类型。
注意:
使用cast
函数时,最好使用match
属性验证参数。
Route
.get('posts/:id', 'PostsController.show')
.where('id', {
match: /^[0-9]+$/,
cast: (id) => Number(id),
})
内置匹配器#
路由模块附带了以下常用数据类型的内置匹配器。
// 验证 id 为数字 + 强制转换为数字类型
Route.where('id', Route.matchers.number())
// 验证 id 是否为有效的 uuid
Route.where('id', Route.matchers.uuid())
// 验证 slug 以匹配给定的 slug 正则表达式:regexr.com/64su0
Route.where('slug', Route.matchers.slug())
URL 生成#
注意:
当无法查找路由时,用于生成 URL 的 API 会引发异常。
您可以利用 URL 生成 API 为预注册的路由生成 URL,而不是在应用程序中的其他地方硬编码 URL。例如:
Route.get('/users/:id', 'UsersController.show')
// 位置参数
const url = Route.makeUrl('/users/:id', [1])
// 命名对象键
const url = Route.makeUrl('/users/:id', { id: 1 })
您还可以使用 Controller.method
来引用路由。
const url = Route.makeUrl('UsersController.show', { id: 1 })
或者使用路由的唯一名称进行引用。
Route
.get('/users/:id', 'UsersController.show')
.as('showUser') // 👈 路由名
// 生成 URL
const url = Route.makeUrl('showUser', { id: 1 })
添加查询字符串#
你可以通过将第三个参数传递给 makeUrl
方法,将查询字符串追加到生成的 URL。
const url = Route.makeUrl('showUser', [1], {
qs: {
verified: true,
},
})
在域内查找#
由于 AdonisJS 允许你为不同的域注册路由,因此你也可以限制 makeUrl
以进行特定域的搜索。
Route
.get('/users/:id', 'UsersController.show')
.domain(':tenant.adonisjs.com')
// 制作 URL
const url = Route.makeUrl('UsersController.show', [1], {
domain: ':tenant.adonisjs.com',
})
为域添加前缀#
生成的 URL 始终是没有任何域名的相对路径。但是,您可以使用 prefixUrl
属性定义一个。
const url = Route.makeUrl('UsersController.show', [1], {
prefixUrl: 'https://foo.com',
})
URL 生成器#
URL builder 是 makeUrl
方法的替代方法,提供了一个流畅的 API 来制作 URL。
const url = Route.builder()
.params({ id: 1 })
.qs({ verified: true })
.prefixUrl('https://foo.com')
.make('UsersController.show')
针对特定域制作 URL
const url = Route.builderForDomain(':tenant.adonisjs.com')
.params({ id: 1 })
.qs({ verified: true })
.prefixUrl('https://foo.com')
.makeUrl('UsersController.show')
在视图中生成 URL#
你可以在模板文件中使用 route
帮助程序来生成 URL。route
具有与 makeUrl
方法相同的 API。
Route.post('posts', 'PostsController.store').as('posts.create')
<form method="POST" action="{{ route('posts.create') }}">
</form>
重定向期间的 URL 生成#
你还可以在重定向请求时针对预注册路由生成 URL。redirect().toRoute()
具有与 makeUrl
方法相同的 API。
Route
.get('/users/:id', 'UsersController.show')
.as('users.show')
Route.post('users', async ({ response }) => {
// 创建用户
response.redirect().toRoute('users.show', { id: user.id })
})
SPA 路由#
当提供 SPA 的路由和你的 AdonisJS 应用程序中的路由来自于相同的路由层时,流程可能如下所示。
- 第一个请求命中 AdonisJS 应用程序。
- 你使用前端脚本和样式加载 HTML 布局。
- 然后,路由和渲染由前端框架处理。
有了这个流程,您会希望 AdonisJS 始终加载相同的 HTML 文件,而不管 URL 是什么,因为路由逻辑放置在前端应用程序中。
您可以通过定义通配符路由来实现。
// 文件名: start/routes.ts
Route.get('*', async ({ view }) => {
return view.render('app')
})
// 简单版本
Route.on('*').render('app')
所有其他 AdonisJS 特定路由 (可能是您的 API) 都应该定义在通配符路由之上。
Route.get('/api/users', 'UsersController.index')
Route.get('/api/posts', 'PostsController.index')
// SPA 路由
Route.on('*').render('app')
最好使用 /api
前缀对 API 路由进行分组。
Route.group(() => {
Route.get('/users', 'UsersController.index')
Route.get('/posts', 'PostsController.index')
}).prefix('/api')
// SPA 路由
Route.on('*').render('app')
Route 组#
AdonisJS 提供了一种很好的方法来分组多个相似性质的路由并批量配置它们,而不需要在每个路由上重新定义相同的属性。
通过将闭包传递给 Route.group
方法来创建组。在闭包内声明的路由是周围组的一部分。
Route.group(() => {
// 这里所有的路由都是组的一部分
})
你还可以创建嵌套组,AdonisJS 将根据应用设置进行合并或覆盖属性。
路由前缀#
以下所有在组闭包内的路由都将以 /api
字符串为前缀。
Route
.group(() => {
Route.get('/users', 'UsersController.index')
Route.get('/posts', 'PostsController.index')
})
.prefix('/api')
在嵌套组的情况下,前缀将从外部组应用到内部组。
Route.group(() => {
Route.group(() => {
Route.get('/users', 'UsersController.index') // /api/v1/users
Route.get('/posts', 'PostsController.index') // /api/v1/posts
}).prefix('/v1')
}).prefix('/api')
采用中间件#
你可以使用 .middleware
方法将中间件应用于一组路由。组中间件在路由的中间件之前执行。
Route.group(() => {
Route.get('users', async () => {
return 'handled'
}).middleware('can:view_users')
}).middleware('auth')
命名路由#
命名一个组将为其所有路由添加给定名称的前缀。例如:
// 命名为 users.index, users.store 等
Route.resource('users', 'UserController')
Route
.group(() => {
// 命名为 api.users.index, api.users.store
Route.resource('users', 'UserController')
})
.prefix('v1')
.as('api')
路由域#
使用路由模块,你还可以为特定域或子域定义路由。在以下示例中,仅当当前 请求主机名 为 blog.adonisjs.com
时,路由才会匹配。
注意:
你仍然需要配置代理服务器来处理注册子域的请求并将它们转发到你的 AdonisJS 服务器。
Route
.group(() => {
Route.get('/', 'PostsController.index')
Route.get('/:id', 'PostsController.show')
})
.domain('blog.adonisjs.com')
域也可以接受动态参数。例如,接受 tenant 子域的域。
Route
.group(() => {
Route.get('/', ({ subdomains }) => {
console.log(subdomains.tenant)
})
})
.domain(':tenant.adonisjs.com')
简便路由#
简便路由是在没有任何显式的路由处理程序的情况下定义的。您可以将它们视为某些行为的捷径。
渲染#
在下面的示例中,我们通过链接.render
方法来渲染 welcome
视图。
Route.on('/').render('welcome')
.render
接受模板数据作为第二个参数。
Route.on('/').render('welcome', { greeting: 'Hello world' })
重定向#
.redirect
方法将请求重定向到预定义的路由。它将使用来自实际请求的 route params 来制作重定向路由的 URL。
Route.on('/posts/:id').redirect('/articles/:id')
// 内联参数
Route.on('/posts/:id').redirect('/articles/:id', { id: 1 })
// 自定义状态
Route.on('/posts/:id').redirect('/articles/:id', undefined, 301)
redirectToUrl 方法#
要重定向到绝对 URL 路径,你可以使用 redirectToUrl
方法。
Route.on('/posts/:id').redirectToUrl('https://medium.com/my-blog')
// 自定义状态
Route.on('/posts/:id').redirectToUrl('https://medium.com/my-blog', 301)
访问已注册的路由#
你可以通过调用 Route.toJSON
方法访问已注册的路由。但是,在 routes 文件 中调用此方法会返回一个空数组,因为路由是在启动 HTTP 服务器之前编译的。
你可以在 middleware、controller 或 service providers start
方法 中运行 Route.toJSON()
方法。通常需要避免在 HTTP 服务器准备好之前访问路由。
// 文件名: providers/AppProvider.ts
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
public static needsApplication = true
constructor(protected app: ApplicationContract) {}
public async ready() {
const Route = this.app.container.use('Adonis/Core/Route')
console.log(Route.toJSON())
}
}
扩展路由#
路由是 多个类 的组合,可以使用 宏 或 getters 为所有类添加自定义属性 / 方法。
扩展路由的最佳位置是在自定义服务的 boot
方法中。打开 providers/AppProvider.ts
文件并在其中写入以下代码。
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
public static needsApplication = true
constructor(protected app: ApplicationContract) {}
public async boot() {
const Route = this.app.container.use('Adonis/Core/Route')
Route.Route.macro('mustBeSigned', function () {
this.middleware(async (ctx, next) => {
if (!ctx.request.hasValidSignature()) {
ctx.response.badRequest('Invalid signature')
return
}
await next()
})
return this
})
}
}
在上面的示例中,我们在 Route 类中添加了 mustBeSigned
方法,该方法在内部注册了一个中间件来验证 请求签名。
现在,打开 start/routes.ts
文件以使用此方法。
// 文件名: start/routes.ts
Route
.get('email/verify', 'OnboardingController.verifyEmail')
.mustBeSigned()
通知 TypeScript 该方法#
mustBeSigned
属性是在运行时添加的,因此 TypeScript 不知道它。为了通知 TypeScript,我们将使用 声明合并 并将属性添加到 RouteContract
接口。
在路径 contracts/route.ts
处创建一个新文件 (文件名不重要) 并将以下内容粘贴到其中。
// 文件名: contracts/route.ts
declare module '@ioc:Adonis/Core/Route' {
interface RouteContract {
mustBeSigned(): this
}
}
扩展路由资源#
你可以按如下方式扩展 RouteResource
类:
// 主题: 添加宏
Route.RouteResource.macro('yourMacroName', fn)
// 主题:扩展接口
declare module '@ioc:Adonis/Core/Route' {
interface RouteResourceContract {
yourMacroName(): this
}
}
// 主题: 使用宏
Route.resource().yourMacroName()
扩展路由组#
你可以按如下方式扩展 RouteGroup
类:
// 主题:添加宏
Route.RouteGroup.macro('yourMacroName', fn)
// 主题:扩展接口
declare module '@ioc:Adonis/Core/Route' {
interface RouteGroupContract {
yourMacroName(): this
}
}
// 主题:使用宏
Route.group().yourMacroName()
扩展简便路由#
你可以按如下方式扩展 BriskRoute
类:
// 主题:添加宏
Route.BriskRoute.macro('yourMacroName', fn)
// 主题:扩展接口
declare module '@ioc:Adonis/Core/Route' {
interface BriskRouteContract {
yourMacroName(): this
}
}
// 主题:使用宏
Route.on('/').yourMacroName()
补充阅读#
以下是一些附加指南,用于了解有关本文档未涵盖的主题的更多信息。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。