控制器(Controller)
yunfly
路由底层框架库为 routing-controllers
,使用 Typescript
注解能力来进行路由的开发。
Controller
主要用于参加校验, 简单逻辑处理, 调用 Service
方法。
初始化项目
-
- 安装
yunfly
框架
- 安装
yarn add @yunflyjs/yunfly
-
- 配置
src/config.default.ts
中routingControllersOptions
配置项
- 配置
config.routingControllersOptions = {
currentUserChecker,
defaultErrorHandler: false,
controllers: [path.join(__dirname, '../controller/*')],
middlewares: [path.join(__dirname, '../middleware/*')],
defaults: {
nullResultCode: 200, // 204 | 404
undefinedResultCode: 200 // 204 | 404
}
};
-
tsconfig.json
中需要设置以下配置项
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
快速使用
-
src/controller
下新建ExampleController.ts
文件
import { Controller, Param, Body, Get, Post, Put, Delete } from '@yunflyjs/yunfly';
@Controller()
export class ExampleController {
@Get('/users')
getAll() {
return 'This action returns all users';
}
@Get('/users/:id')
getOne(@Param('id') id: number) {
return 'This action returns user #' + id;
}
@Post('/users')
post(@Body() user: any) {
return 'Saving user...';
}
@Put('/users/:id')
put(@Param('id') id: number, @Body() user: any) {
return 'Updating a user...';
}
@Delete('/users/:id')
remove(@Param('id') id: number) {
return 'Removing user...';
}
}
-
- 启动项目
// 启动
yarn dev
// 监听模式启动
yarn watch:dev
-
- 访问
# 浏览器将显示 This action returns all users。
http://localhost:3000/users。
# 将显示 This action returns user #1
http://localhost:3000/users/1
使用 JSON
对于一个总是返回 JSON
的 REST API
,建议用 @JsonController
代替 @Controller
。 @JsonController
装饰的控制器路由的响应数据将自动转换为 JSON
类型且 Content-Type
被设置为 application/json
。
同时请求的 application/json
头信息也可以被解释,请求 Body
将解析为 JSON
。
import { JsonController, Param, Body, Get, Post, Put, Delete } from '@yunflyjs/yunfly';
@JsonController()
export class ExampleController {
@Get('/users')
getAll() {
return userRepository.findAll();
}
@Get('/users/:id')
getOne(@Param('id') id: number) {
return userRepository.findById(id);
}
@Post('users')
post(@Body() user: User) {
return userRepository.insert(user);
}
}
返回 Promise
返回一个 Promise
,响应将等待该 Promise
回执后返回其结果。
import { JsonController, Param, Body, Get, Post, Put, Delete } from '@yunflyjs/yunfly';
@JsonController()
export class ExampleController {
@Get('/users')
async getAll() {
return await userRepository.findAll();
}
@Get('/users/:id')
async getOne(@Param('id') id: number) {
return await userRepository.findById(id);
}
@Post('/users')
async post(@Body() user: User) {
return await userRepository.insert(user);
}
@Put('/users/:id')
async put(@Param('id') id: number, @Body() user: User) {
return await userRepository.updateById(id, user);
}
@Delete('/users/:id')
async remove(@Param('id') id: number) {
return await userRepository.removeById(id);
}
}
从目录加载控制器
在 config.routingControllersOptions.controllers
中指定文件夹,即可加载该目录下所有控制器:
config.routingControllersOptions = {
controllers: [path.join(__dirname, '../controller/*')],
};
从目录加载中间件
在 config.routingControllersOptions.middlewares
中指定文件夹,即可加载该目录下所有全局中间件:
config.routingControllersOptions = {
middlewares: [path.join(__dirname, '../middleware/*')],
};
路由前缀
- 全局路由前缀
要为所有路由添加前缀,比如 /api
,可以使用 routePrefix
配置项:
config.routingControllersOptions = {
routePrefix: '/api',
}
- 指定控制器路由前缀
向控制器装饰器传递根路由参数,控制器下的路由将添加该跟路由前缀:
import { Controller } from "@yunflyjs/yunfly";
@Controller('/users')
export class UserController {
// ...
}
Method 装饰器
如下图,我们需要对控制器下的不同 HTTP
方法做不同处理。
如 @Get('/users/:id')
会为 GET /users/:id
请求生成路由映射,这意味着如果 GET /users/:id
请求则会走 handleGet
逻辑。
同理,@Post('/users')
会为 POST /users
请求生成路由映射,POST /users
请求则会走 handlePost
逻辑。
以此类推,我们针对其他 HTTP
方法 PUT DELETE PATCH HEAD
分别注入路由处理逻辑。
import { Controller, Param, Body, Get, Patch, Post, Head, Put, Delete } from '@yunflyjs/yunfly';
@Controller()
export class ExampleController {
@Get('/users/:id')
handleGet(@Param('id') id: number) {
return userRepository.findById(id);
}
@Post('/users')
handlePost(@Body() user: User) {
return userRepository.insert(user);
}
@Put('/users/:id')
handlePut(@Param('id') id: number, @Body() user: User) {
return userRepository.updateById(id, user);
}
@Delete('/users/:id')
handleDelete(@Param('id') id: number) {
return userRepository.removeById(id);
}
@Patch('/users/:id')
handlePatch() {
return userRepository.updateById(id, user);
}
@Head('/users/:id')
handleHead() {
return userRepository.removeById(id);
}
}
更多方法装饰器说明:
标识 | 示例 | 描述 |
---|---|---|
@Get(route: string|RegExp) | @Get("/users") all() | 匹配 HTTP GET 方法 |
@Post(route: string|RegExp) | @Post("/users") save() | 匹配 HTTP Post 方法 |
@Put(route: string|RegExp) | @Put("/users/:id") update() | 匹配 HTTP Put 方法 |
@Patch(route: string|RegExp) | @Patch("/users/:id") patch() | 匹配 HTTP Patch 方法 |
@Delete(route: string|RegExp) | @Delete("/users/:id") delete() | 匹配 HTTP Delete 方法 |
@Head(route: string|RegExp) | @Head("/users/:id") head() | 匹配 HTTP Head 方法 |
@All(route: string|RegExp) | @All("/users/me") rewrite() | 匹配所有 HTTP 方法 |
动态路由
controller
动态路由
如果要匹配动态路由的话,可通过:[参数]
的方式注入。如:
import { Controller } from "@yunflyjs/yunfly";
@Controller('/users/:id')
export class ExampleController {
// ...
}
method
动态路由
import { Controller, Get, Param } from "@yunflyjs/yunfly";
@Controller('/users')
export class ExampleController {
@Get("/:id")
getOne(@Param("id") id: number) {
// ...
}
}
使用 Req 和 Res 对象
直接使用框架的 Request 对象和 Response 对象。 如果想自己处理响应,可以在方法中返回该 Response 对象。
import { Controller, Req, Res, Get } from '@yunflyjs/yunfly';
@Controller()
export class ExampleController {
@Get('/users')
getAll(
@Req() request: any,
@Res() response: any
) {
return response.send('Hello response!');
}
}
@Req()
装饰器注入了一个Request
对象,@Res()
装饰器注入了一个Response
对象。 如果安装了对应的类型声明,也可以对它们进行声明:
获取 Param 参数
- 用于获取动态路由参数
@Param
@Get("/users/:id")
getOne(
@Param("id") id: number
) {
// 这里可以访问 id
}
@Params
@Get("/users/:id/:name")
getOne(
@Params() params: {id: number; name: string }
) {
// 这里可以访问 params.id, params.name
}
获取 Query 参数
- 获取通过
get
传递的参数
@QueryParam
@Get("/users")
getUsers(
@QueryParam("name") name: string
) {
// 这里可以访问 name
}
@QueryParams
@Get("/users")
getUsers(
@QueryParams() query: {name: string; age: number}
) {
// 这里可以访问query.name, query.age
}
get请求单参多值
可以使用 config.sameQueryParam
对 get
参数进行处理
- 获取参数值始终为字符串
config.sameQueryParam = {
ebable: true,
isArray: false,
}
@Get("/users/by-multiple-ids")
getUsers(@QueryParam("ids", { isArray: true}) ids: string[]) {
}
// GET /users/by-multiple-ids?ids=a → ids = 'a'
// GET /users/by-multiple-ids?ids=a&ids=b → ids = 'a,b'
- 获取参数值始终为数组
config.sameQueryParam = {
ebable: true,
isArray: true,
}
@Get("/users/by-multiple-ids")
getUsers(@QueryParam("ids", { isArray: true}) ids: string[]) {
}
// GET /users/by-multiple-ids?ids=a → ids = ['a']
// GET /users/by-multiple-ids?ids=a&ids=b → ids = ['a', 'b']
获取 Post 参数
- 获取通过
post
传递的参数
@Body
@Post("/users")
saveUser(
@Body() user:{name: string; age: number}
) {
// 这里可以访问 user.name, user.age
}
@BodyParam
@Post("/users")
saveUser(
@BodyParam("name") name: string,
@BodyParam("age") age: number,
) {
// 这里可以访问 name, age
}
获取 Header 参数
- 获得
htttp
header
头部信息
@HeaderParam
@Post("/users")
saveUser(
@HeaderParam("authorization") token: string
) {
// 这里可以获得 token
}
@HeaderParams
@Post("/users")
saveUser(
@HeaderParams() header: {token: string}
) {
// 这里可以获得 header.token
}
获得 Cookie 信息
- 用于获取
http
cookie
头信息
@CookieParam
@Get("/users")
getUsers(
@CookieParam("username") username: string
) {
// 这里可以获得 username
}
@CookieParams
@Get("/users")
getUsers(
@CookieParams() cookies: { username: string }
) {
// 这里可以获得 cookies.username
}
获得 Session 信息
- 用于获取
session
值
@SessionParam
@Get("/login")
savePost(
@SessionParam("user") user: User,
@Body() post: Post
) {}
@Session
@Get("/login")
savePost(
@Session() session: any,
@Body() post: Post
) {}
@Session
装饰器装饰的参数默认为必填。如果你的方法中该参数是可选的,需要手动标记为非必填
action(
@Session("user", { required: false }) user: User
){}
限制必填参数
在装饰器配置 required: true
限制参数为必填:
@Post("/users")
save(
@Body({
required: true
}) user: any
) {
// 如果请求内没有user参数,该方法不会执行
}
可以在其它任何参数装饰器中限制必填参数,如 @QueryParam
, @BodyParam
等。 如果请求中没有必填参数,yunfly
框架将抛出一个错误。
设置 ContentType
为路由设置 ContentType
:
@Get("/users")
@ContentType("text/cvs")
getUsers() {
// ...
}
设置 Location
为路由设置 Location
:
@Get("/users")
@Location("http://github.com")
getUsers() {
// ...
}
设置重定向
为路由设置重定向:
@Get("/users")
@Redirect("http://github.com")
getUsers() {
// ...
}
通过返回字符串覆写重定向地址:
@Get("/users")
@Redirect("http://github.com")
getUsers() {
return "https://www.google.com";
}
使用模板生成重定向:
@Get("/users")
@Redirect("http://github.com/:owner/:repo")
getUsers() {
return {
owner: "pleerock",
repo: "@yunflyjs/yunfly"
};
}
设置 HTTP 响应代码
可以显式设置 HTTP 响应代码:
@HttpCode(201)
@Post("/users")
saveUser(@Body() user: User) {
// ...
}
管理空响应
对于返回 void
或 Promise<void>
或 undefined
的控制器方法,将自动向客户端抛出 404
错误。 @OnUndefined
装饰器可用于设置这种情况下的状态码。
@Delete("/users/:id")
@OnUndefined(204)
async remove(@Param("id") id: number): Promise<void> {
return userRepository.removeById(id);
}
对于返回值可能为 undefined
的情况,@OnUndefined
也可以发挥作用。
下面例子中,当用户 id
不存在时 findOneById
返回 undefined
,该路由将返回 404
代码,如果存在则返回 200
代码:
@Get("/users/:id")
@OnUndefined(404)
getOne(@Param("id") id: number) {
return userRepository.findOneById(id);
}
当结果为 undefined
时也可以返回一个错误类:
import { HttpError } from '@yunflyjs/yunfly';
export class UserNotFoundError extends HttpError {
constructor() {
super(404, 'User not found!');
}
}
@Get("/users/:id")
@OnUndefined(UserNotFoundError)
saveUser(@Param("id") id: number) {
return userRespository.findOneById(id);
}
如果控制器方法返回 null
可以用 @OnNull
装饰器替代。
自定义 Header
定义任意 Header
信息:
@Get("/users/:id")
@Header("Catch-Control", "none")
getOne(@Param("id") id: number){
// ...
}
抛出 HTTP 错误
抛出 HTTP 错误请参考 错误类使用章节:错误类使用
使用中间件
@Middleware
装饰器用于自定义中间件, @UseBefore
和 @UseAfter
装饰器使用任何已有的或自定义的 Koa
中间件。
@UseBefore
中间件执行之前做什么事情
import { Controller, Get, UseBefore } from "@yunflyjs/yunfly";
// ...
@Get("/users/:id")
@UseBefore(CheckParams)
getOne(@Param("id") id: number) {
// ...
}
@UseAfter
中间件执行之后做什么事情
import { Controller, Get, UseBefore } from "@yunflyjs/yunfly";
// ...
@Get("/users/:id")
@UseAfter(RecordLog)
getOne(@Param("id") id: number) {
// ...
}
在方法中使用中间件
import { Controller, Get, UseBefore } from "@yunflyjs/yunfly";
let compression = require("compression");
// ...
@Get("/users/:id")
@UseBefore(compression())
getOne(@Param("id") id: number) {
// ...
}
在控制器中使用中间件
import { Controller, UseBefore } from '@yunflyjs/yunfly';
let compression = require('compression');
@Controller()
@UseBefore(compression())
export class UserController {}
自定义中间件
定义一个普通中间件
import { KoaMiddlewareInterface } from "@yunflyjs/yunfly";
export class MyMiddleware implements KoaMiddlewareInterface {
async use(context: any, next: (err?: any) => Promise<any>): Promise<any> {
console.log("do something before execution...");
return await next()
console.log("do something after execution...");
}
}
普通中间件应用
- Controller 中应用
import {Controller, UseBefore, UseAfter} from "@yunflyjs/yunfly";
import {MyMiddleware} from "./MyMiddleware";
import {loggingMiddleware} from "./loggingMiddleware";
//...
@Controller()
@UseBefore(MyMiddleware)
@UseAfter(loggingMiddleware)
export class UserController {
// ...
}
- 方法中使用
import { Get, UseBefore, UseAfter} from "@yunflyjs/yunfly";
import {MyMiddleware} from "./MyMiddleware";
import {loggingMiddleware} from "./loggingMiddleware";
@Get("/users/:id")
@UseBefore(MyMiddleware)
@UseAfter(loggingMiddleware)
getOne(@Param("id") id: number) {
// ...
}
定义一个全局中间件
import { KoaMiddlewareInterface,Middleware } from "@yunflyjs/yunfly";
@Middleware({ type: 'before' })
export class MyMiddleware implements KoaMiddlewareInterface {
async use(context: any, next: (err?: any) => Promise<any>): Promise<any> {
console.log("do something before execution...");
await next()
console.log("do something after execution...");
}
}
全局中间件应用
config.routingControllersOptions = {
middlewares: [path.join(__dirname, '../middleware/*')],
};
更多中间件装饰器说明
标识 | 示例 | 描述 |
---|---|---|
@Middleware({type: "before"|"after"}) | @Middleware({ type: "before"}) class SomeMiddleware | 注册全局中间件 |
@UseBefore() | @UseBefore(CompressionMiddleware) | 请求开始前调用 |
@UseAfter() | @UseAfter(CompressionMiddleware) | 请求结束后调用 |
@Interceptor() | @Interceptor() class SomeInterceptor | 注册全局拦截器 |
@UseInterceptor() | @UseInterceptor(BadWordsInterceptor) | 拦截 Controller / Action ,替换某些值 |
其他装饰器
有时候我们需要对接口进行鉴权,这时就需要用到 @Authorized
装饰器了。
如下图,我们示范如何在 yunfly
使用 @Authorized
。
-
config.default.ts
增加对routingControllersOptions
配置:
{
config.routingControllersOptions = {
// 代码Demo
authorizationChecker: async (action: Action, roles: string[]) => {
// 这里可以使用action中的request/response对象
// 如果装饰器定义了可以访问action角色
// 也可以使用它们来提供详细的鉴权
// checker必须返回boolean类型(true or false)或者Promise(回执也必须是boolean)
const token = action.request.headers['authorization'];
const user = await getEntityManager().findOneByToken(User, token);
if (user && !roles.length) return true;
if (user && roles.find(role => user.roles.indexOf(role) !== -1)) return true;
return false;
})
}
}
-
- 在路由中使用 @Authorized
import { Controller, Post, Authorized, Body } from '@yunflyjs/yunfly';
@Controller()
export class UserController {
@Authorized()
@Post('/questions')
save(@Body() question: Question) {}
@Authorized('POST_MODERATOR') // 指定角色或角色数组
@Post('/posts')
save(@Body() post: Post) {}
}
更多装饰器说明
标识 | 示例 | 描述 |
---|---|---|
@Authorized(roles?: string|string[]) | @Authorized("SUPER_ADMIN") get() | 授权检查 |
@CurrentUser(options?: { required?: boolean }) | get(@CurrentUser({ required: true }) user: User) | 注入当前授权的用户 |
@Header(headerName: string, headerValue: string) | @Header("Cache-Control", "private") get() | 自定义相应头部信息 |
@ContentType(contentType: string) | @ContentType("text/csv") get() | 自定义响应头部 HTTP Content-Type 信息 |
@Location(url: string) | @Location("http://github.com (opens in a new tab)") get() | 自定义响应头部 HTTP Location 信息 |
@Redirect(url: string) | @Redirect("http://github.com (opens in a new tab)") get() | 自定义响应头部 HTTP Redirect 信息 |
@HttpCode(code: number) | @HttpCode(201) post() | 自定义响应 HTTP code |
@OnNull(codeOrError: number|Error) | @OnNull(201) post() | 当真实响应的 HTTP code 为 null 时,设置 HTTP code |
@OnUndefined(codeOrError: number|Error) | @OnUndefined(201) post() | 当真实响应的 HTTP code 为 undefined 时,设置 HTTP code |
@Render(template: string) | @Render("user-list.html") get() | 渲染给定的 HTML 模板,控制器返回的数据用作模板变量 |
自定义装饰器
您也可以通过包装现有装饰器或者采用 @yunflyjs/yunfly
提供的方法创建新的装饰器,如下:
-
- 使用
@yunflyjs/yunfly
提供的createParamDecorator
创建参数装饰器
- 使用
import { createParamDecorator } from '@yunflyjs/yunfly';
export function UserFromSession(options?: { required?: boolean }) {
return createParamDecorator({
required: options && options.required ? true : false,
value: action => {
const token = action.request.headers['authorization'];
return database.findUserByToken(token);
},
});
}
-
- 通过包装现有装饰器
import { UseAfter } from '@yunflyjs/yunfly';
import { IResponseType } from '../types';
import { handleResponseType } from './handle';
export const ResponseType = function (type: IResponseType) {
// 进行包装
return UseAfter(handleResponseType(type, true));
};
更多详细用法请参考:https://www.npmjs.com/package/routing-controllers#using-middlewares (opens in a new tab)