前端中間件Midway的使用教程
一、 關(guān)于midway
Midway 是阿里巴巴 - 淘寶前端架構(gòu)團隊,基于漸進式理念研發(fā)的 Node.js 框架,通過自研的依賴注入容器,搭配各種上層模塊,組合出適用于不同場景的解決方案。
Midway 基于 TypeScript 開發(fā),結(jié)合了面向?qū)ο螅∣OP + Class + IoC)與函數(shù)式(FP + Function + Hooks)兩種編程范式,并在此之上支持了 Web / 全棧 / 微服務(wù) / RPC / Socket / Serverless 等多種場景,致力于為用戶提供簡單、易用、可靠的 Node.js 服務(wù)端研發(fā)體驗。
1. 解決什么痛點
以往的開發(fā)中,前端直接項目中直接調(diào)取后臺服務(wù)接口,就會從在過度依賴后臺數(shù)據(jù),或者只能請求服務(wù)后再到渲染層進行數(shù)據(jù)加工大大影響開發(fā)效率,或者存在多個部門協(xié)同開發(fā),接口數(shù)據(jù)格式達不到統(tǒng)一,前端數(shù)據(jù)處理任務(wù)量加重等溝通問題。
2. 期望達到什么效果
在項目/產(chǎn)品開發(fā)中存在某種中間件進行服務(wù)接口的二次加工或者轉(zhuǎn)發(fā)以達到前端所需的統(tǒng)一數(shù)據(jù)結(jié)構(gòu)。比如如下分工:
數(shù)據(jù):負責(zé)數(shù)據(jù)開發(fā),對外提供服務(wù)數(shù)據(jù)接口
后端:負責(zé)業(yè)務(wù)邏輯開發(fā),對外提供服務(wù)業(yè)務(wù)邏輯接口
中間層:根據(jù)前端需要,調(diào)用多端不同的服務(wù)接口并進行拼接、加工數(shù)據(jù),對前端提供加工后接口
前端:負責(zé)數(shù)據(jù)渲染
二、創(chuàng)建應(yīng)用并使用
1. 創(chuàng)建midway應(yīng)用
$npm init midway
選擇 koa-v3 項目進行初始化創(chuàng)建,項目名可以自定,比如 weather-sample。
現(xiàn)在可以啟動應(yīng)用來體驗下。
$ npm run dev
則創(chuàng)建了一個類似下面結(jié)構(gòu)的文件
.
├── src ## midway 項目源碼
│ └── controller ## Web Controller 目錄
│ └── home.controller.ts
├── test
├── package.json
└── tsconfig.json
整個項目包括了一些最基本的文件和目錄。
• src 整個 Midway 項目的源碼目錄,你之后所有的開發(fā)源碼都將存放于此
• test 項目的測試目錄,之后所有的代碼測試文件都在這里
• package.json Node.js 項目基礎(chǔ)的包管理配置文件
• tsconfig.json TypeScript 編譯配置文件
2. 認識Midway
2.1 目錄結(jié)構(gòu)
• controller Web Controller 目錄
• middleware 中間件目錄
• filter 過濾器目錄
• aspect 攔截器
• service 服務(wù)邏輯目錄
• entity 或 model 數(shù)據(jù)庫實體目錄
• config 業(yè)務(wù)的配置目錄
• util 工具類存放的目錄
• decorator 自定義裝飾器目錄
• interface.ts 業(yè)務(wù)的 ts 定義文件
2.2 Controller
控制器常用于對用戶的請求參數(shù)做一些校驗,轉(zhuǎn)換,調(diào)用復(fù)雜的業(yè)務(wù)邏輯,拿到相應(yīng)的業(yè)務(wù)結(jié)果后進行數(shù)據(jù)組裝,然后返回。
在 Midway 中,控制器 也承載了路由的能力,每個控制器可以提供多個路由,不同的路由可以執(zhí)行不同的操作。
import { Controller, Get } from '@midwayjs/decorator'; @Controller('/') export class WeatherController { // 這里是裝飾器,定義一個路由 @Get('/weather') async getWeatherInfo(): Promise<string> { // 這里是 http 的返回,可以直接返回字符串,數(shù)字,JSON,Buffer 等 return 'Hello Weather!'; } }
@Controller 裝飾器告訴框架,這是一個 Web 控制器類型的類,而 @Get 裝飾器告訴框架,被修飾的 home 方法,將被暴露為 / 這個路由,可以由 GET 請求來訪問
通過訪問 /weather 接口返回數(shù)據(jù)了;整個方法返回了一個字符串,在瀏覽器中你會收到 text/plain 的響應(yīng)類型,以及一個 200 的狀態(tài)碼。
2.3 路由
上面創(chuàng)建了一個 GET 路由。一般情況下,我們會有其他的 HTTP Method,Midway 提供了更多的路由方法裝飾器。
例如:
import { Controller, Get, Post } from '@midwayjs/decorator'; @Controller('/') export class HomeController { @Get('/') async home() { return 'Hello Midwayjs!'; } @Post('/update') async updateData() { return 'This is a post method' } }
Midway 還提供了其他的裝飾器, @Get 、 @Post 、 @Put() 、@Del() 、 @Patch() 、 @Options() 、 @Head() 和 @All() ,表示各自的 HTTP 請求方法。
@All 裝飾器比較特殊,表示能接受以上所有類型的 HTTP Method。
你可以將多個路由綁定到同一個方法上。
@Get('/') @Get('/main') async home() { return 'Hello Midwayjs!'; }
返回內(nèi)容類型將定義的內(nèi)容放在 src/interface.ts 文件中
例如:
export interface User { id: number; name: string; age: number; }
使用:下方粗下劃線處
import { Controller, Get, Query } from "@midwayjs/decorator"; @Controller('/api/user') export class UserController { @Get('/') async getUser(@Query('id') id: string): Promise<User> { // xxxx } }
2.4 獲取請求參數(shù)
請求的數(shù)據(jù)一般都是動態(tài)的,會在 HTTP 的不同位置來傳遞,比如常見的 Query,Body 等。
第一種 query
@Query 裝飾器的有參數(shù),可以傳入一個指定的字符串 key,獲取對應(yīng)的值,賦值給入?yún)?,如果不傳入,則默認返回整個 Query 對象。
// URL = /?id=1 async getUser(@Query('id') id: string) // id = 1 async getUser(@Query() queryData) // {"id": "1"} 如果通過api獲取query中的參數(shù) import { Controller, Get, Inject } from "@midwayjs/decorator"; import { Context } from '@midwayjs/koa'; @Controller('/user') export class UserController { @Inject() ctx: Context; @Get('/') async getUser(): Promise<User> { const query = this.ctx.query; // { // uid: '1', // sex: 'male', // } } }
注意:
當 Query String 中的 key 重復(fù)時,ctx.query 只取 key 第一次出現(xiàn)時的值,后面再出現(xiàn)的都會被忽略。
比如 GET /user?uid=1&uid=2 通過 ctx.query 拿到的值是 { uid: ‘1’ }。
第二種 body
為什么要用body’傳遞參數(shù)?
• 瀏覽器中會對 URL 的長度有所限制,如果需要傳遞的參數(shù)過多就會無法傳遞。
• 服務(wù)端經(jīng)常會將訪問的完整 URL 記錄到日志文件中,有一些敏感數(shù)據(jù)通過 URL 傳遞會不安全。
注意:
框架內(nèi)置了 bodyParser 中間件來對這兩類格式的請求 body 解析成 object 掛載到 ctx.request.body 上。HTTP 協(xié)議中并不建議在通過 GET、HEAD 方法訪問時傳遞 body,所以我們無法在 GET、HEAD 方法中按照此方法獲取到內(nèi)容。
框架對 bodyParser 設(shè)置了一些默認參數(shù),配置好之后擁有以下特性:
• 當請求的 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(可能是一個數(shù)組)。
獲取單個 body
? // src/controller/user.ts // POST /user/ HTTP/1.1 // Host: localhost:3000 // Content-Type: application/json; charset=UTF-8 // // {"uid": "1", "name": "harry"} import { Controller, Post, Body } from "@midwayjs/decorator"; @Controller('/user') export class UserController { @Post('/') async updateUser(@Body('uid') uid: string): Promise<User> { // id 等價于 ctx.request.body.uid } }
獲取整個 body
? // src/controller/user.ts // POST /user/ HTTP/1.1 // Host: localhost:3000 // Content-Type: application/json; charset=UTF-8 // // {"uid": "1", "name": "harry"} import { Controller, Post, Body } from "@midwayjs/decorator"; @Controller('/user') export class UserController { @Post('/') async updateUser(@Body() user: User): Promise<User> { // user 等價于 ctx.request.body 整個 body 對象 // => output user // { // uid: '1', // name: 'harry', // } } }
從 API 獲取
? // src/controller/user.ts // POST /user/ HTTP/1.1 // Host: localhost:3000 // Content-Type: application/json; charset=UTF-8 // // {"uid": "1", "name": "harry"} import { Controller, Post, Inject } from "@midwayjs/decorator"; import { Context } from '@midwayjs/koa'; @Controller('/user') export class UserController { @Inject() ctx: Context; @Post('/') async getUser(): Promise<User> { const body = this.ctx.request.body; // { // uid: '1', // name: 'harry', // } } }
此外裝飾器還可以組合使用,獲取 query 和 body 參數(shù)
@Post('/') async updateUser(@Body() user: User, @Query('pageIdx') pageIdx: number): Promise<User> { // user 從 body 獲取 // pageIdx 從 query 獲取 }
第三種 Params
如果路由上使用 :xxx 的格式來聲明路由,那么參數(shù)可以通過 ctx.params 獲取到。
示例:從裝飾器獲取
// src/controller/user.ts // GET /user/1 import { Controller, Get, Param } from "@midwayjs/decorator"; @Controller('/user') export class UserController { @Get('/:uid') async getUser(@Param('uid') uid: string): Promise<User> { // xxxx } }
示例:從 API 獲取
// src/controller/user.ts // GET /user/1 import { Controller, Get, Inject } from "@midwayjs/decorator"; 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', // } } }
2.5 Web中間件
Web 中間件是在控制器調(diào)用 之前 和 之后(部分)調(diào)用的函數(shù)。 中間件函數(shù)可以訪問請求和響應(yīng)對象。
import { IMiddleware } from '@midwayjs/core'; import { Middleware } from '@midwayjs/decorator'; import { NextFunction, Context } from '@midwayjs/koa'; @Middleware() export class ReportMiddleware implements IMiddleware<Context, NextFunction> { resolve() { return async (ctx: Context, next: NextFunction) => { // 控制器前執(zhí)行的邏輯 const startTime = Date.now(); // 執(zhí)行下一個 Web 中間件,最后執(zhí)行到控制器 // 這里可以拿到下一個中間件或者控制器的返回值 const result = await next(); // 控制器之后執(zhí)行的邏輯 console.log(Date.now() - startTime); // 返回給上一個中間件的結(jié)果 return result; }; } static getName(): string { return 'report'; } }
例如:
export class ErrorMiddleware implements IWebMiddleware { resolve() { return async (ctx: Context, next: IMidwayWebNext) => { try { await next() } catch (err: any) { const errorInter = RestCode.INTERNAL_SERVER_ERROR; console.info('錯誤信息' + err.name, err.message, err.status); const status = err.status || errorInter; // 生產(chǎn)環(huán)境時 500 錯誤的詳細錯誤內(nèi)容不返回給客戶端,因為可能包含敏感信息 const errorMsg = status === errorInter && ctx.app.config.env === 'prod' ? 'Internal Server Error' : err.message; ctx.body = { code: err.name === 'ValidationError' ? RestCode.VALIDATE_ERROR : status, error: errorMsg.replaceAll('\"',''), } if (status === 422) { ctx.body.detail = err.errors; } ctx.status = 200 } }; } }
全局使用中間件
所有的路由都會執(zhí)行的中間件,比如 cookie、session 等等
// src/configuration.ts import { App, Configuration } from '@midwayjs/decorator'; import * as koa from '@midwayjs/koa'; import { ReportMiddleware } from './middleware/user.middleware'; @Configuration({ imports: [koa] // ... }) export class AutoConfiguration { @App() app: koa.Application; async onReady() { this.app.useMiddleware([ReportMiddleware1, ReportMiddleware2]); } }
路由使用中間件
單個/部分路由會執(zhí)行的中間件,比如某個路由的前置校驗,數(shù)據(jù)處理等等
import { Controller } from '@midwayjs/decorator'; import { ReportMiddleware } from '../middleware/report.middlweare'; @Controller('/', { middleware: [ ReportMiddleware ] }) export class HomeController { }
忽略和匹配路由
在中間件執(zhí)行時,我們可以添加路由忽略的邏輯。
ignore(ctx: Context): boolean { // 下面的路由將忽略此中間件 return ctx.path === '/' || ctx.path === '/api/auth' || ctx.path === '/api/login'; }
同理,也可以添加匹配的路由,只有匹配到的路由才會執(zhí)行該中間件。ignore 和 match 同時只有一個會生效。
match(ctx: Context): boolean { // 下面的匹配到的路由會執(zhí)行此中間件 if (ctx.path === '/api/index') { return true; } }
2.6 組件使用
參數(shù)校驗
Midway 提供了 Validate 組件。 配合 @Validate 和 @Rule 裝飾器,用來快速定義校驗的規(guī)則,幫助用戶減少這些重復(fù)的代碼。
注意:從 v3 開始,@Rule 和 @Validate 裝飾器從 @midwayjs/validate 中導(dǎo)出。
1、 安裝依賴:$ npm i @midwayjs/validate@3 –save
2、 開啟組件:
在 configuration.ts 中增加組件。
import * as validate from ‘@midwayjs/validate'; import { join } from ‘path'; @Configuration({ imports: [ validate], importConfigs: [join(__dirname, ‘./config')], }) }
3、 定義檢查規(guī)則:
為了方便后續(xù)處理,我們將 user 放到一個 src/dto 目錄中。
例如:
// src/dto/user.ts import { Rule, RuleType } from ‘@midwayjs/validate'; export class UserDTO { @Rule(RuleType.number().required()) id: number; @Rule(RuleType.string().required()) firstName: string; @Rule(RuleType.string().max(10)) lastName: string; @Rule(RuleType.number().max(60)) age: number; }
4、 應(yīng)用:
定義完類型之后,就可以直接在業(yè)務(wù)代碼中使用了,開啟校驗?zāi)芰€需要 @Validate 裝飾器。
// src/controller/home.ts import { Controller, Get, Provide } from ‘@midwayjs/decorator'; import { UserDTO } from ‘./dto/user'; @Controller(‘/api/user') export class HomeController { @Post(‘/') async updateUser(@Body() user: UserDTO ) { // user.id } }
Swagger-ui
基于最新的 OpenAPI 3.0.3 實現(xiàn)了新版的 Swagger 組件。
1、 安裝依賴:
npm install @midwayjs/swagger@3 --save
npm install swagger-ui-dist --save-dev
2、 開啟組件:
import { Configuration } from ‘@midwayjs/decorator'; import * as swagger from ‘@midwayjs/swagger'; @Configuration({ imports: [ { component: swagger, enabledEnvironment: [‘local'] //只在 local 環(huán)境下啟用 }]}) export class MainConfiguration { }
然后啟動項目,訪問地址:
• UI: http://127.0.0.1:7001/swagger-ui/index.html
• JSON: http://127.0.0.1:7001/swagger-ui/index.json
2.7 服務(wù)(service)
在業(yè)務(wù)中,只有控制器(Controller)的代碼是不夠的,一般來說會有一些業(yè)務(wù)邏輯被抽象到一個特定的邏輯單元中,我們一般稱為服務(wù)(Service)。
提供這個抽象有以下幾個好處:
• 保持 Controller 中的邏輯更加簡潔。
• 保持業(yè)務(wù)邏輯的獨立性,抽象出來的 Service 可以被多個 Controller 重復(fù)調(diào)用。
• 將邏輯和展現(xiàn)分離,更容易編寫測試用例。
創(chuàng)建服務(wù)
一般會存放到 src/service 目錄中。我們來添加一個 user 服務(wù)。
import { Provide, App, Inject } from '@midwayjs/decorator'; import { Application } from 'egg'; import { HttpService } from '@midwayjs/axios'; var fs = require('fs'); var path = require('path'); @Provide() //拋出服務(wù) export class UserService { @App() app: Application; @Inject() //依賴注入 httpService: HttpService; async getUser(options: any) { const url = 'https://172.30.154.46:9998/samp/v1/auth/login'; const { data } = await this.httpService.post(url, options); return data; } }
使用服務(wù)
在 Controller 處,我們需要來調(diào)用這個服務(wù)。傳統(tǒng)的代碼寫法,我們需要初始化這個 Class(new),然后將實例放在需要調(diào)用的地方。在 Midway 中,你不需要這么做,只需要編寫我們提供的 “依賴注入” 的代碼寫法。
import { Inject, Controller, Get, Provide, Query } from '@midwayjs/decorator'; import { UserService } from '../service/user'; @Controller('/api/user') export class APIController { @Inject()//引入服務(wù) userService: UserService; @Get('/') async getUser(@Query('id') uid) { const user = await this.userService.getUser(uid); return {success: true, message: 'OK', data: user}; } }
三、寫到最后
Midway 框架是在內(nèi)部已經(jīng)使用使用 5 年以上的 Node.js 框架,有著長期投入和持續(xù)維護的團隊做后盾,已經(jīng)在每年的大促場景經(jīng)過考驗,穩(wěn)定性無須擔心,并且有著豐富的組件和擴展能力,例如數(shù)據(jù)庫,緩存,定時任務(wù),進程模型,部署以及 Web,Socket 甚至 Serverless 等新場景的支持。一體化調(diào)用方案可以方便快捷和前端頁面協(xié)同開發(fā)和良好的 TypeScript 定義支持。
所以在項目中應(yīng)用Midway, 能夠為應(yīng)用提供更優(yōu)雅的架構(gòu)。
到此這篇關(guān)于前端中間件Midway的使用的文章就介紹到這了,更多相關(guān)Midway的使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實現(xiàn)郵箱地址自動匹配功能代碼
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)E-mail郵箱地址自動匹配功能代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11bootstrap模態(tài)框跳轉(zhuǎn)到當前模板頁面 框消失了而背景存在問題的解決方法
這篇文章主要介紹了bootstrap模態(tài)框跳轉(zhuǎn)到當前模板頁面,框消失了,而背景依然存在問題的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12JavaScript中實現(xiàn)異步編程模式的4種方法
這篇文章主要介紹了JavaScript中實現(xiàn)異步編程模式的4種方法,本文講解了回調(diào)函數(shù)、事件監(jiān)聽、發(fā)布/訂閱、Promises對象4種方法,需要的朋友可以參考下2014-09-09