koa开发实践2:为koa项目添加路由模块

news/2024/11/28 10:41:48/
nodeJS server-side-develop
koa开发实践2:为koa项目添加路由模块

上一节:《 koa开发实践2:为koa项目添加路由模块 | 下一节:《 koa开发实践3:在koa项目中使用 swagger 文档

作者李俊才:https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
作者邮箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/129828590

请勿转载,仅发布于我的博客:CSDN jclee95 : https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343



1. 目标概述

我们上一节搭建了一个基于 TypeScript 地开发环境,TypeScript 是强类型语言,这对于我们地开发提供了强大的类型支持,能够给我们代码很多更加智能化的提示,并且由李云我们后期的改错与维护。在上一节中,我们已经实现了一个基本的 koa 服务器的搭建,但是它还有很多不足,比如,它还没有路由,日志记录也需要进行进一步修改等等。

上一节的服务器时一个单一地址的静态页面,我们这一节的目标就是在上一节的基础上添加路由模块,实现 koa web 的路由功能。

2. 路由 与 koa

2.1 路由的概念

2.1.1 起源

路由 一词的来源与其实工程技术并无关系,它很早就有,仅仅是一个生活中很常见的词汇,含以上表示来自哪里。随着二十世纪电气的到来,电气电子相关技术的蓬勃发展,控制技术、通信工程等相关专业应运而生。不论是从最早在电气控制领域的 工业控制网络 到后来的 计算机网络,随着生产力发展的需要都引入了很多来源于生活的概念,路由 就是其中之一。在网络工程领域,路由routing)是指分组从源到目的地时,决定端到端路径的网络范围的进程。后来随着互联网技术的发展,从网络工程领域再次借用和引申了 路由 这一概念。在用户界面系统中,比如我们所熟知的 web,路由简单的来说就是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果。

2.1.2 统一资源定位符(url)

生活中我们以各种形式表达道路,如水路、陆路,通过文字描述我们人类从一个位置到达另外一个位置所经过的道路,就是路由。在网络领域也是类似的,同样需要一个方法来表示信息所经过的位置,这个方法就是 统一资源定位系统URL,uniform resource locator)。

URL 是由一串字母,数字和特殊符号组成的字符所构成的字符串。在不同的使用场景存在不同的 URL 标准协议,比如最常见的有:

  • http Hypertext Transfer Protocol(超文本传输协议);
  • ftp File Transfer protocol(文件传输协议);
  • mailto Electronic mail address(电子邮件地址);
  • file Host-specific file names(特殊主机文件名);

URL的一般语法格式为:

protocol :// hostname[:port] / path / [:parameters][?query]#fragment

其中:

  • protocol 表示协议名,如 http、https、ftp、ed2k 等等;
  • hostname 表示主机名,也可以是域名,只不过域名终将通过 DNS 解析为主机名;
  • port 表示端口号,一般而言,在一个主机上不同的端口号代表了不同的应用,比如 web 应用使用的http服务默认为 80 端口;
  • path 表示具体路径,它是由若干个'/' 号隔开的字符串,一般用来表示主机上的一个目录或文件地址;
  • parameters 表示参数,用于指定特殊参数的可选项,一般通过服务器端程序自行解释,不过目前的前端路由也可以处理参数。
  • query 表示查询,可以有多个。一个查询实际上就是一个 键值对,用于以 url 方式给应用传入相关的参数。每两个查询之间使用'&' 符号隔开,'='符号隔开;
  • fragment 表示信息片断,是一个用于指定网络资源中的片段的字符串。例如一个 Web 中有多个名词解释,可使用fragment直接定位到某一名词解释。在Web的前端路由的 所谓哈希模式中,就是使用 信息片段来区别不同位置的。

2.2 路由的原理

2.3 了解关于 MVC 的概念

在后文中我们需要以MVC模式关联路由和视图,因此有必要先讲解何谓 MVCMVC 是交互系统开发中常用的一种 软件设计模式。 经典MVC架构中,M、V、C 分别表示的是三个开发层级。其中 MModel)代表 数据模型VView)代表视图CController)则是控制器(也称调度器)。

  • Model 他是模型表示业务规则,模型更具体的来说其实就是指数据。
  • View 它是用户看到并与之交互的界面。比如 web 中的html元素组成的网页界面。
  • Controller 它是视图和数据模型之间的桥梁,用于接受用户的输入并调用 Model 和 View 去完成相应的需求。

因此总的来看 MVC 模式将交互系统分层了三个层次,分别是 数据层视图层调度层。 使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

3. koa-router

3.1 概述

express 不一样的是,koa 自身连路由系统也没有,需要额外安装路由中间件插件。这给开发者带来了更多的选择,你既可以使用 koa 官方团队提供的路由中间件 koa-router,也可以选择其它的方式实现 Koa Web 的路由。也就是说,是否使用官方的 koa-router 其实并非强制的。

如上所述 koa-router 是一款由 koa 官方以 中间件 形式提供的路由模块,它能完成我们一般的路由功能,并且由于是官方所提供的插件,很多第三方插件更倾向于基于 koa-router 进行推出,因此在本文中也使用它进行路由的讲解,这对于 Web 后端类项目的初学者也更加友好。

另外, koa-routerexpress 路由风格的路由,这大概由于 koaexpress 是同一个团队进行开发的有较大关系。对于熟悉 express 的读者可以直接入手。

3.2 配置 koa-router

要使用 koa-router 需要先进行安装。目前 koa 团队规范了 路由模块 的项目名称,从旧版的 koa-router 迁移到了新的 @koa/router。因此依据当前最新的文档,你可以使用如下方式对 koa-router 进行安装:

npm i @koa/router
# or
yarn add @koa/router
# or
pnpm i @koa/router

(依据你项目所使用的包管理工具进行选择)

3.3 路由的 TypeScript 支持

由于我们使用 TypeScript 进行开发,而 koa-router 本身不包含 TypeScript 源码或者相应的类型声明文件。因此为了更好地获得类型的支持,还需要独立安装对应的类型模块。

koa-router 项目名迁移到 @koa/router 后,对应的类型模块项目名为 @types/koa__router。因此你可以通过下面的方式为路由添加 TypeScript 支持:

npm install @types/koa__router -D
# or
yarn add -D @types/koa__router
# or
pnpm install @types/koa__router -D

(依据你项目所使用的包管理工具进行选择)

3.4 koa-router 的编程接口解析

我们用的主要就是 Router 对象,它是一个类,其类型签名如下:

declare class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {opts: Router.RouterOptions;methods: string[];params: object;stack: Router.Layer[];/*** 创建新路由器。*/constructor(opt?: Router.RouterOptions);/*** 使用给定的中间件。** 中间件按照 `.use()` 定义的顺序运行。它们被顺序调用,请求从第一个中间件开始,沿着中间件堆栈 "down"(向下) 传递。*/use(...middleware: Array<Router.Middleware<StateT, ContextT>>): Router<StateT, ContextT>;/*** 使用给定的中间件。** 中间件按照 `.use()` 义的顺序运行。它们被顺序调用,请求从第一个中间件开始,沿着中间件堆栈"down"(向下)传递。*/use(path: string | string[] | RegExp,...middleware: Array<Router.Middleware<StateT, ContextT>>): Router<StateT, ContextT>;/*** HTTP get 方法*/get<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP get 方法*/get<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP post 方法*/post<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP post 方法*/post<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP put 方法*/put<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP put 方法*/put<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP link 方法*/link<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP link 方法*/link<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP unlink 方法*/unlink<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP unlink 方法*/unlink<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP delete 方法*/delete<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP delete 方法*/delete<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/***  `router.delete()` 的别名,因为delete是保留字*/del<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/***  `router.delete()` 的别名,因为delete是保留字*/del<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP head 方法*/head<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP head 方法*/head<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP options 方法*/options<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP options 方法*/options<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP patch 方法*/patch<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** HTTP patch 方法*/patch<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** 用所有方法注册路由。*/all<T = {}, U = {}, B = unknown>(name: string,path: string | RegExp,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** 用所有方法注册路由。*/all<T = {}, U = {}, B = unknown>(path: string | RegExp | Array<string | RegExp>,...middleware: Array<Router.Middleware<StateT & T, ContextT & U, B>>): Router<StateT, ContextT>;/*** 为已经初始化的路由器实例设置路径前缀。** @example** ```javascript* router.prefix('/things/:thing_id')* ```*/prefix(prefix: string): Router<StateT, ContextT>;/*** 返回路由器中间件,它分派与请求匹配的路由。*/routes(): Router.Middleware<StateT, ContextT>;/*** 返回路由器中间件,它分派与请求匹配的路由。*/middleware(): Router.Middleware<StateT, ContextT>;/*** 返回单独的中间件,用于响应带有包含允许方法的 `Allow` 请求头的 `OPTIONS` 请求,以及适当地响应“405 方法不允许” 和 “501 未实现”。** @example** ```javascript* var Koa = require('koa');* var Router = require('koa-router');** var app = new Koa();* var router = new Router();** app.use(router.routes());* app.use(router.allowedMethods());* ```** **使用[Boom](https://github.com/hapijs/boom)的例子:**** ```javascript* var Koa = require('koa');* var Router = require('koa-router');* var Boom = require('boom');** var app = new Koa();* var router = new Router();** app.use(router.routes());* app.use(router.allowedMethods({*   throw: true,*   notImplemented: () => new Boom.notImplemented(),*   methodNotAllowed: () => new Boom.methodNotAllowed()* }));* ```*/allowedMethods(options?: Router.RouterAllowedMethodsOptions): Router.Middleware<StateT, ContextT>;/*** 使用可选的 30x 状态 `code` 将 `source` 重定向到 `destination` URL。** `source` 和 `destination` 都可以是路由名.** ```javascript* router.redirect('/login', 'sign-in');* ```** 这相当于:** ```javascript* router.all('/login', ctx => {*   ctx.redirect('/sign-in');*   ctx.status = 301;* });* ```*/redirect(source: string, destination: string, code?: number): Router<StateT, ContextT>;/*** 创建并注册一个 route。*/register(path: string | RegExp,methods: string[],middleware: Router.Middleware<StateT, ContextT> | Array<Router.Middleware<StateT, ContextT>>,opts?: Router.LayerOptions,): Router.Layer;/*** 具有给定 `name` 的 Lookup route。*/route(name: string): Router.Layer | boolean;/*** 为 route 生成URL。接受命名`params` 的映射或一系列参数(对于正则表达式路由)** router = new Router();* router.get('user', "/users/:id", ...** router.url('user', { id: 3 });* // => "/users/3"** 可以从第三个参数生成查询(query):** router.url('user', { id: 3 }, { query: { limit: 1 } });* // => "/users/3?limit=1"** router.url('user', { id: 3 }, { query: "limit=1" });* // => "/users/3?limit=1"**/url(name: string, params?: any, options?: Router.UrlOptionsQuery): Error | string;/*** 匹配给定的 `path` 并返回相应的routes。*/match(path: string, method: string): Router.RoutesMatch;/*** 为命名的路由参数运行中间件。适用于自动加载或验证。** @example** ```javascript* router*   .param('user', (id, ctx, next) => {*     ctx.user = users[id];*     if (!ctx.user) return ctx.status = 404;*     return next();*   })*   .get('/users/:user', ctx => {*     ctx.body = ctx.user;*   })*   .get('/users/:user/friends', ctx => {*     return ctx.user.getFriends().then(function(friends) {*       ctx.body = friends;*     });*   })*   // /users/3 => {"id": 3, "name": "Alex"}*   // /users/3/friends => [{"id": 4, "name": "TJ"}]* ```*/param<BodyT = unknown>(param: string, middleware: Router.ParamMiddleware<StateT, ContextT, BodyT>): Router<StateT, ContextT>;/*** 为路由生成URL。接受一个路径名和一个名为 `params` 的 map。** @example** ```javascript* router.get('user', '/users/:id', (ctx, next) => {*   // ...* });** router.url('user', 3);* // => "/users/3"** router.url('user', { id: 3 });* // => "/users/3"** router.use((ctx, next) => {*   // 重定向到命名路由*   ctx.redirect(ctx.router.url('sign-in'));* })** router.url('user', { id: 3 }, { query: { limit: 1 } });* // => "/users/3?limit=1"** router.url('user', { id: 3 }, { query: "limit=1" });* // => "/users/3?limit=1"* ```*/static url(path: string | RegExp, params: object): string;}

3.5 关于路由的一些补充

3.5.1 HTTP 动词(Verbs)和 koa-router verb 方法

HTTP 定义了一组请求方法, 以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作。HTTP 动词 是用于 HTTP 请求方法中的一系列的值,包括了:

HTTP请求方法动词描述
GETGET 方法请求一个指定资源的表示形式。使用GET的请求应该只被用于获取数据.
HEADHEAD 方法请求一个与GET请求的响应相同的响应,但没有响应体。
POSTPOST 方法用于将实体提交到指定的资源,通常导致状态或服务器上的副作用的更改。
PUTPUT 方法用请求有效载荷替换目标资源的所有当前表示。
DELETEDELETE 方法删除指定的资源。
CONNECTCONNECT 方法建立一个到由目标资源标识的服务器的隧道。
OPTIONSOPTIONS 方法用于描述目标资源的通信选项。
TRACETRACE 方法沿着到目标资源的路径执行一个消息环回测试。
PATCHPATCH 方法用于对资源应用部分修改。

koa-router 插件为我们提供了一些列 router.verb() 方法其中 verb只某个 HTTP 动词,例如:router.get()router.post()。另外 router.all() 方法可以匹配所有的这类方法。详细类型签名参见 3.4 小节。

当路径匹配时,其路径在ctx._matchedRoute处可用。如果路由被命名(参考 3.5.2 命名路由),该名称可在 ctx._matchedRouteName 获得。

koa-router 内部使用 path-to-regexp 模块将路由路径转换为正则表达式。在匹配请求时不会考虑 url 的 query 字符串。

3.5.2 命名路由

顾名思义,所谓 命名路由 就是给一个路由起别名。也就是说,路由可以有名称,这允许我们在开发过程中生成URL和简单地去重命名URL。

get 请求为例,Router 对象上的 get 是两个 重载 的方法,当其中一个重载方法参数为 path...middleware,而另外一个为 namepath...middleware。这就是说我们可以给 get 方法描述的路由在第一个参数的位置多指定一个 name 字符串参数来表示路由名,比如:

router.get('user', '/users/:id', (ctx, next) => {// ...
});

这里的 'user' 对应于 name,即该条路由被命名为 usrt。定义了名字自然是为了更方便地来进行使用地。

Router 对象上的 url 方法可以为路由生成URL,它接受的第一个参数就是 命名路由的路由名name 。该方法的类型签名为:

url(name: string, params?: any, options?: Router.UrlOptionsQuery): Error | string;

我们就用它来试一下:

router.url('user', 3);  // => "/users/3"

其中,这里就是把 param 值 (3)赋给了 名字为 user 的路由中后面的 parameter/:id),因此就是/users/3

3.5.3 嵌套路由

顾名思义,所谓 嵌套路由就是在接续一个路由以同样地方式定义子路由。从实现层面上看,路由地使用是通过中间件使用上的嵌套。
例如:

const father = new Router();
const child = new Router();child.get('/', (ctx, next) => {...});
child.get('/:pid', (ctx, next) => {...});routers1.use('/father/:fid/child', posts.routes(),routers2.allowedMethods()
);// 响应于 "/father/123/child" 
// 和 "/father/123/child/123"
app.use(routers1.routes());

3.5.4 路由前缀

嵌套的子路由自动地被继承其了父路由作为它地前缀,不过还可以手动在实例化 Router 对象时为路由添加前缀。例如:

const router = new Router({prefix: '/users'
});router.get('/', ...); // responds to "/users"
router.get('/:id', ...); // responds to "/users/:id"

3.5.5 url 参数

我们在 2.1.2 统一资源定位符(url) 小节已提到过什么是 url 参数,一旦在需要使用到 url参数 时,koa-router 地动词函数中可以很简单地去用它,例如:

router.get('/:category/:title', (ctx, next) => {console.log(ctx.params);// => { category: 'programming', title: 'how-to-node' }
});

4. 搭建应用为中心的路由系统

4.1 来源于 Django 的启发

4.1.1 Django 中的路由调度思路

一九年的时候我自学了 Django, 这是一款基于 Python 语言的重型 Web 框架。之所以称之为 “重型”框架是因为它具有相当强大、丰富的开箱即用功能,其中就包括路由系统。按照 Django 官方的说法,他是一款 MVT 类型的架构,不过究其本质,实际上还是广义上的 MVC 模式,之所以硬要叫做 MVT 大概是因为 Django 框架中具有强大的 模板系统。所谓模板系统,对于具有前端开发经验的读者来说是不陌生的,因为前端框架最主要部分就是模板语言,比如 vue,他们都以自己的语法形式提供了视图层面开发的模板语言。不过这与本文的主题关系不大,因而不做过多展开讲解。

Django 有一个典型的特点,就是一般而言它是由一个个子应用组成的,甚至为你提供了在创建应用的脚手架(CLI,命令行工具)。在每一个应用中通过一个用作 调度器的 路由模块控将当前应用的路由分配到每一个视图上。一个视图由若干个视图对象构成,这些视图对象既可以是基于类的,也可以是基于函数的,但是都必须通过调度器分配路由并且以某种形式返回一个HTTP响应对象才是有效的。

从整体上看子应用当然是需要集中管理的,这就意味着需要一个 中央调度器,它一般负责从根路由开始将路由分配到各个应用(虽然这也不是强制性的,你可以直接分配视图,但这往往是破坏项目的优雅性和易维护性)。因此从整体上看,不论是 子应用 还是 子路由,看起来都是一个树形结构的。

4.1.2 构建基于 Koa 的 MVC 架构体系

相对于 Django 而言, Koa 是一款极度轻型的框架(express也是),它不仅没有模板语言、后台系统、权限管理等等相对复杂的功能,甚至连路由都需要额外模块安装并以作为中间件的形式手动引入。在我们配置好 koa-router 后,也可以仿照 Django 的方式搭建围绕应用的路由。

这也就是说,首先我们整个项目管理的主体是应用,每一个应用都是独立的(至少是尽最大可能将应用之间的耦合度降到最低)——去耦合往往也是代码可读性的要求。在每一个应用内,通过一个应用的 调度器 处理 url 和视图之间的关系,这个调度器也就是我们的应用级路由模块。

4.2 实践

现在我们在 前一篇文章 的基础上创建一个 src/settings.ts 文件用于放项目的一些全局配置、一个 src/libs/url.ts 文件用于放与路由相关的工具,再创建一个 src/apps 目录用于存放应用,现在我们在该文件夹下创建两个应用文件夹,一个为auth,另外一个为 home

现在我们希望自动地获取每个应用下 的 Router 对象合并到一个总的 Router 对象作为 中间件给 Koa 实例使用。

先在 setting.ts 中定义一些常量备用:

import path from "path";export const devPort = 3000;
export const BASE_DIR = path.resolve(__dirname,'.');
export const APPS_DIR = path.join(BASE_DIR,'apps');

于是在 libs\conf.ts 中编写一个 getRouters 函数:

import path from "path";
import Router from '@koa/router';
import { APPS_DIR } from "../settings";export declare type URLResolver = { path: string | string[] | RegExp, include: string }export async function getRouters(urlpatterns: URLResolver[]): Promise<Router> {const router = new Router();for (let index = 0; index < urlpatterns.length; index++) {const item = urlpatterns[index];const app_urls = await import(path.resolve(APPS_DIR, ...item.include.split('.')));router.use(item.path, app_urls.router.routes(), app_urls.router.allowedMethods());}return router

编写根路由文件 src/urls.ts

import type { URLResolver } from './lib/conf';const urlpatterns: URLResolver[] = [{ path: '/', include: 'home.urls' },{ path: '/auth', include: 'auth.urls' },
]export {urlpatterns
}
  • path 表示以一个路由的值,include 所包含进来的路由文件中定义的路由都将继承于path定义的路由值;
  • include 表示包含的一个子路由文件,. 好分割父子目录。其中最左侧第一个目录是 APPS_DIR 的直接子目录。

接着在homeauth 两个应用下的 urls.ts 文件中简单定义路由:
src/apps/home/urls.ts

import Router from '@koa/router';
const router:Router = new Router();router.get('/', async (ctx) => {ctx.type = 'html';ctx.body = '<h1>这是 Home 页!(\'/\')</h1>';
})export {router
}

src/apps/auth/urls.ts

import Router from '@koa/router';const router:Router = new Router();router.get("/", async (ctx) => {ctx.type = 'html';ctx.body = '<h1>这是Auth页(/auth)!</h1>';
})export {router
}

src/app.ts做出相应调整:

import Koa from 'koa';
import { logger } from './logger';
import { urlpatterns } from './urls';
import Boom from 'boom';
import { getRouters } from './lib/conf';
import { devPort } from './settings';async function bootstrap() {const app = new Koa();const router = await getRouters(urlpatterns);app.use(router.routes());app.use(router.allowedMethods({throw: true,notImplemented: () => Boom.notImplemented(),methodNotAllowed: () => Boom.methodNotAllowed()}));app.listen(devPort,()=>{logger.debug(`app started at: http://localhost:${devPort}`);logger.debug(`swagger pages at: http://localhost:${devPort}/swagger/index.html`);});
}bootstrap()

现在我们重新运行,并在浏览器中访问:

在这里插入图片描述

在这里插入图片描述

5. 小结

本文在前一篇文章的基础上使用官方的koa-router(已改名为@koa/router)中间件为我们的 Koa 项目添加了路由。为了使各部分功能看起来更独立,我们使用不同的应用名来区分功能,在每个因公子目录下添加一个路由文件,不过我们目前还并没有要求 根路由所包含的文件必须是src/apps/xxx 下的文件,毕竟这只是为了后期的去耦合,这些将在之后的文章中做更多的处理。目前我们的路由还是 koa-router 的中间件形式,其实这对于视图的调度并不是那么直观,这也将在后续文章中做更多地改进。


http://www.ppmy.cn/news/39267.html

相关文章

Android 事件分发源码解析(基于API31)

文章目录事件分发从何说起&#xff1f;以一个什么框架去学习&#xff1f;重点关于事件事件是由一系列事件组成的事件流。这里先总览一下这三个方法都要做些什么&#xff1a;View的事件分发先明确一下框架中提到的问题&#xff1a;View 的事件分发源码分析总结ViewGroup的事件分…

@Lookup与@Bean@Scope获取原型实例,谁更胜一筹

那必然是 Lookup 呀 &#x1f6fc;他是谁 | 运行时查找 bean示例 | 谁更好呢作用 | 妙用多多他是谁 | 运行时查找 bean Lookup是Spring框架的一部分&#xff0c;它允许在运行时动态查找bean。使用Lookup注解的方法必须返回一个bean&#xff0c;它将在每次调用该方法时创建一个…

redis实战---乐观锁与悲观锁

乐观锁和悲观锁故事背景概念乐观锁悲观锁乐观锁示例悲观锁示例总结提升故事背景 概念 Redis是一个内存中的键值存储系统&#xff0c;支持多种数据结构&#xff0c;如字符串、哈希、列表等。Redis提供了两种锁机制&#xff0c;即乐观锁和悲观锁。 乐观锁 乐观锁是一种乐观的…

Properties

Properties概述&#xff1a; 是一个Map体系的集合类 Properties可以保存到流中或从流中加载 练习&#xff1a;Properties作为Map集合的使用 package com.aynu13;//练习&#xff1a;Properties作为Map集合的使用import java.util.Properties; import java.util.Set;public cla…

3年外包离奇被裁,痛定思痛24K上岸字节跳动....

三年前&#xff0c;我刚刚从大学毕业&#xff0c;来到了一家外包公司工作。这份工作对于我来说是个好的起点&#xff0c;因为它让我接触到了真正的企业项目和实际的开发流程。但是&#xff0c;随着时间的流逝&#xff0c;我发现这份工作并没有给我带来足够的成长和挑战。 三年…

YOLO系列(YOLOv5/YOLOv7/YOLOv8)算法训练数据集保姆级教程

本博文教大家如何快速便捷有效的跑通YOLO系列算法。如果有需要更正的请留言&#xff0c;我会进一步更新修正。 一、实验环境 关于实验环境&#xff0c;YOLO系列算法官网源码用的是PyTorch框架写的&#xff0c;所以需要大家在自己电脑安装PyTorch环境&#xff0c;不同YOLO算法或…

c++异常处理

异常处理输出异常捕获异常处理和恢复标准的异常异常重载捕获不同错误输出异常 C异常处理是一种用于在程序执行期间捕获和处理错误的机制。当发生异常时&#xff0c;程序将跳转到与该异常匹配的catch块&#xff0c;并执行指定的代码来处理该异常。C中的异常处理分为三个步骤&am…

python turtle库

turtle库是 Python提供的一组函数&#xff0c;可以用来将一个或多个文本文件转换成指定的文件格式&#xff0c;比如 jpg、 png、 bmp等。 比如将 jpg文件转换成 png或者 bmp文件。我们可以通过对文本进行操作&#xff0c;比如修改字体大小、调整字体颜色、添加文字阴影等&#…