路由参数
- 我们将创建一个关于用户的 HTTP API 同样的,创建一个
src/controller/user.ts
文件,这次我们会增加一个路由前缀,以及增加更多的请求类型
我们以用户类型举例,先增加一个用户类型,我们一般将定义的内容放在src/interface.ts
文件中
➜ my_midway_app tree
.
├── src
│ ├── controller
│ │ ├── user.ts
│ │ └── home.ts
│ └── interface.ts
├── test
├── package.json
└── tsconfig.json
- interface.ts
// src/interface.ts
export interface IUser {
id: number;
name: string;
age: number;
}
再添加一个路由前缀以及对应的控制器。
// src/controller/user.ts
import { Controller } from "@midwayjs/core";
@Controller("/api/user")
export class UserController {
// xxxx
}
接下去,我们要针对不同的请求类型,调用不同的处理逻辑。
除了请求类型之外,请求的数据一般都是动态的,会在 HTTP 的不同位置来传递,比如常见的 Query,Body 等。
装饰器参数约定
MidWay 添加了常见的动态取值的装饰器,我们以@Query
装饰器举例,@Query
装饰器会获取到 URL 中的 Query 参数部分
并且将赋值给函数入参,下面的示例中
id 会从路由的Query
参数上拿,如果 URL 为?id=1
则 id 的值为 1,同时这个路由会返回User
类型的对象
// src/controller/user.ts
import { Controller, Get, Query } from "@midwayjs/core";
import { IUser } from "../interface";
@Controller("/api/user")
export class UserController {
@Get("/")
async getdata(@Query("id") id: string): Promise<IUser> {
//xxxxx
return {
id: 2,
name: "jsopy",
age: 36,
};
}
}
@Query
装饰器的有参数,可以传入一个指定的字符串key
,获取对应的值,赋值给入参,如果不传入,则默认返回整个 Query 对象
// URL = /?id=1
async getUser(@Query('id') id: string) // id = 1
async getUser(@Query() queryData) // {"id": "1"}
MidWay 提供了更多从 Query,Body,Header 等位置获取值的装饰器,这些都是开箱即用,并且适配于不同的上层 web 框架
下面是这些装饰器,以及对应的等价框架取值方式
装饰器 | Express 对应的方法 | Koa/EggJS 对应的方法 |
---|---|---|
@Session(key?: string) | req.session / req.session[key] | ctx.session / ctx.session[key] |
@Param(key?: string) | req.params / req.params[key] | ctx.params / ctx.params[key] |
@Body(key?: string) | req.body / req.body[key] | ctx.request.body / ctx.request.body[key] |
@Query(key?: string) | req.query / req.query[key] | ctx.query / ctx.query[key] |
@Queries(key?: string) | 无 | 无 / ctx.queries[key] |
@Headers(name?: string) | req.headers / req.headers[name] | ctx.headers / ctx.headers[name] |
警告
注意 EGG 和其他框架不同,@Query
和@Queries
是有区别的
Queries 会将相同的 key 聚合到一起,变为数组,当用户访问的接口参数为?name=a&name=b
@Queries 会返回{name:[a,b]}
而@Query 会返回{name:b}
Query
在 URL 中?
后面的部分是一个 Query String,这一部分经常用于 GET 类型的请求中传递参数,例如
GET /user?uid=1&sex=male
就是用户传递过来的参数
示例从装饰器获取
- 第一种获取单个
// src/controller/user.ts
import { Controller, Get, Query } from "@midwayjs/core";
@Controller("/user")
export class UserController {
@Get("/")
async getUser(@Query("uid") uid: string): Promise<User> {
// xxxx
}
}
- 第二种获取全部
import { Controller, Get, Query } from "@midwayjs/core";
import { IUser } from "../interface";
@Controller("/api/user")
export class UserController {
@Get("/")
async getdata(@Query() queryall): Promise<IUser> {
//xxxxx
return {
id: Number(queryall.id),
name: queryall.name,
age: Number(queryall.age),
};
}
}
示例从上下文获取
// src/controller/user.ts
import { Controller, Get, Inject } from "@midwayjs/core";
import { IUser } from "../interface";
// 引入上下文
import { Context } from "@midwayjs/koa";
@Controller("/api/user")
export class UserController {
// 注入上下文
@Inject()
ctx: Context;
// Get请求
@Get("/")
async getdata(): Promise<IUser> {
//xxxxx
console.log(this.ctx.query);
return {
id: Number(this.ctx.query.id),
name: String(this.ctx.query.name),
age: Number(this.ctx.query.age),
};
}
}
警告
注意 EggJS 和其他框架不同,在当 Query String 中的 key 重复的时候,只取 key 第一次出现时的值,后面再出现的都会被忽略. 比如GET/user?uid=12345&uid=2
通过ctx.query
拿到的值就是{uid:'1'}
Body
虽然我们可以通过 URL 传递参数,但是还是有诸多限制
浏览器中会对 URL 的长度有所限制,如果需要传递的参数过多就会无法传递。
服务端经常会将访问的完整 URL 记录到日志文件中,有一些敏感数据通过 URL 传递会不安全。
在前面的 HTTP
请求报文示例中,我们看到在 header 之后还有一个 body 部分,我们通常会在这个部分传递 POST
、PUT
和 DELETE
等方法的参数。一般请求中有 body
的时候,客户端(浏览器)会同时发送 Content-Type
告诉服务端这次请求的 body
是什么格式的。Web 开发中数据传递最常用的两类格式分别是 JSON
和 Form
。
框架内置了 bodyParser
中间件来对这两类格式的请求 body
解析成 object
挂载到 ctx.request.body
上。HTTP 协议中并不建议在通过 GET
、HEAD
方法访问时传递 body
,所以我们无法在 GET
、HEAD
方法中按照此方法获取到内容。
示例从装饰器获取
- 单个 body 获取
// Post请求
@Post('/post')
async postdata(@Body('id') id: number): Promise<IUser> {
return {
id: id,
name: '呵呵',
age: 18,
};
}
- 获取全部 body
// 获取POST全部参数
@Post('/post1')
async postdataall(@Body() bodyall: any): Promise<IUser> {
return {
id: bodyall.id,
name: bodyall.name,
age: bodyall.age,
};
}
示例从上下文获取
// 从上下文获取
@Post('/post2')
async postdataall2(): Promise<IUser> {
const bodyall = this.ctx.request.body as IUser;
console.log(bodyall);
return {
id: bodyall.id,
name: bodyall.name,
age: bodyall.age,
};
}
同时获取 body 和 query 参数
// 同时获取query和body
@Post('/post3')
async postdataall3(@Query() query: any): Promise<IUser> {
const bodyall = this.ctx.request.body as IUser;
return {
id: bodyall.id,
name: bodyall.name,
age: bodyall.age,
sex: query.sex,
};
}
框架对 bodyParser
设置了一些默认参数,配置好之后拥有以下特性:
当请求的 Content-Type 为 application/json
,application/json-patch+json
,application/vnd.api+json
和 application/csp-report
时,会按照 json 格式对请求 body 进行解析,并限制 body 最大长度为 1mb。 当请求的 Content-Type
为 application/x-www-form-urlencoded
时,会按照 form 格式对请求 body 进行解析,并限制 body 最大长度为 1mb。 如果解析成功,body 一定会是一个 Object(可能是一个数组)。
警告
常见错误: ctx.request.body
和 ctx.body
混淆,后者其实是 ctx.response.body
的简写。
Router Params
如果路由上使用 :xxx
的格式来声明路由,那么参数可以通过 ctx.params
获取到。
示例 从装饰器获取
// src/controller/user.ts
// GET /user/1
import { Controller, Get, Param } from "@midwayjs/core";
@Controller("/user")
export class UserController {
@Get("/:uid")
async getUser(@Param("uid") uid: string): Promise<User> {
// xxxx
}
}
从上下文获取
// src/controller/user.ts
// GET /user/1
import { Controller, Get, Inject } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
@Controller("/user")
export class UserController {
@Inject()
ctx: Context;
@Get("/:uid")
async getUser(): Promise<User> {
const params = this.ctx.params;
// {
// uid: '1',
// }
}
}
Header
除了从 URL
和请求 body
上获取参数之外,还有许多参数是通过请求 header
传递的。框架提供了一些辅助属性和方法来获取。
ctx.headers,ctx.header,ctx.request.headers,ctx.request.header:这几个方法是等价的,都是获取整个 header 对象。
ctx.get(name),ctx.request.get(name):获取请求 header 中的一个字段的值,如果这个字段不存在,会返回空字符串。
我们建议用 ctx.get(name) 而不是 ctx.headers['name'],因为前者会自动处理大小写。
从装饰器获取
// src/controller/user.ts
// GET /user/1
import { Controller, Get, Headers } from "@midwayjs/core";
@Controller("/user")
export class UserController {
@Get("/:uid")
async getUser(@Headers("cache-control") cacheSetting: string): Promise<User> {
// no-cache
// ...
}
}
从上下文获取
// src/controller/user.ts
// GET /user/1
import { Controller, Get, Inject } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
@Controller("/user")
export class UserController {
@Inject()
ctx: Context;
@Get("/:uid")
async getUser(): Promise<User> {
const cacheSetting = this.ctx.get("cache-control");
// no-cache
}
}
Cookie
HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)
将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上(浏览器也会遵循协议,只在访问符合 Cookie
指定规则的网站时带上对应的 Cookie
来保证安全性)。
通过 ctx.cookies
,我们可以在 Controller
中便捷、安全的设置和读取 Cookie
。
import { Inject, Controller, Get, Provide } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
@Controller("/")
export class HomeController {
@Inject()
ctx: Context;
@Get("/")
async home() {
// set cookie
this.ctx.cookies.set("foo", "bar", { encrypt: true });
// get cookie
this.ctx.cookies.get("foo", { encrypt: true });
}
}
Session
通过 Cookie,我们可以给每一个用户设置一个 Session,用来存储用户身份相关的信息,这份信息会加密后存储在 Cookie 中,实现跨请求的用户身份保持。
框架内置了 Session
插件,给我们提供了 ctx.session
来访问或者修改当前用户 Session 。
import { Inject, Controller, Get, Provide } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
@Controller("/")
export class HomeController {
@Inject()
ctx: Context;
@Get("/")
async home() {
// 获取 Session 上的内容
const userId = this.ctx.session.userId;
const posts = await this.ctx.service.post.fetch(userId);
// 修改 Session 的值
this.ctx.session.visited = ctx.session.visited
? ctx.session.visited + 1
: 1;
// ...
}
}
Session 的使用方法非常直观,直接读取它或者修改它就可以了,如果要删除它,直接将它赋值为 null:
ctx.session = null;
上传的文件
上传的文件一般使用 multipart/form-data
协议头,由 @Files 装饰器获取,由于上传功能由 upload 组件提供,具体可以参考 upload 组件。
其他的参数
还有一些比较常见的参数装饰器,以及它们的对应方法。
装饰器 | Express 对应的方法 | Koa/EggJS 对应的方法 |
---|---|---|
@RequestPath | req.baseurl | ctx.path |
@RequestIP | req.ip | ctx.ip |
示例:获取 body 、path 和 ip
@Post('/')
async updateUser(
@Body('id') id: string,
@RequestPath() p: string,
@RequestIP() ip: string): Promise<User> {
}
自定义请求参数装饰器
你可以快速通过 createRequestParamDecorator
创建自定义请求参数装饰器。
import { createRequestParamDecorator } from "@midwayjs/core";
// 实现装饰器
export const Token = () => {
return createRequestParamDecorator((ctx) => {
return ctx.headers.token;
});
};
// 使用装饰器
export class UserController {
async invoke(@Token() token: string) {
console.log(token);
}
}
请求类型转换
如果是简单类型,Midway 会自动将参数转换为用户声明的类型。
比如:
数字类型
@Get('/')
async getUser(@Query('id') id: number): Promise<User> {
console.log(typeof id) // number
}
布尔类型
- 当值为 0,"0", "false" 则转为 false,其余返回 Boolean(value) 的值
@Get('/')
async getUser(@Query('id') id: boolean): Promise<User> {
console.log(typeof id) // boolean
}
Class
如果是复杂类型,如果指定的类型是 Class,将会自动转换为该类的实例。
// class
class UserDTO {
name: string;
getName() {
return this.name;
}
}
@Get('/')
async getUser(@Query() query: UserDTO): Promise<User> {
// query.getName()
}
如果不希望被转换,可以使用 Interface。
interface User {
name: string;
}
@Get('/')
async getUser(@Query() query: User): Promise<User> {
// ...
}
参数校验
后续会说