得益於 JavaScript 加入的 decorator 特性,可以使我們跟 Java/C 一樣,更加直觀自然的,做面向切麵編程。而隨著 TypeScript 的成熟,類型系統也讓我們增強了信心,面對複雜的業務邏輯,也更有底氣。 "egg controller" 是集合了一些在 Controller ...
得益於 JavaScript 加入的 decorator 特性,可以使我們跟 Java/C# 一樣,更加直觀自然的,做面向切麵編程。而隨著 TypeScript 的成熟,類型系統也讓我們增強了信心,面對複雜的業務邏輯,也更有底氣。
egg-controller 是集合了一些在 Controller 層開發中常見問題解決方案的插件。
Controller 路由定義
export class HomeController {
@route('/api/xxx', { name: '獲取XXX數據' })
async getXXX(size: number, page: number) {
return 'homeIndex';
}
}
可以看到,使用 decorator 的形式來聲明 Controller 非常直觀,而且方便擴展,添加/修改 Controller 規則直接修改 decorator 的類型定義就好。這種形式也是 Java/C# 的常規操作。
這裡的改進除了使用 decorator 替代了 router.js 來進行 Controller 聲明以外,還添加了出入參支持,省去了需要手動讀寫 ctx 的過程,非常直觀的聲明 Controller 函數的參數需求,以及返回數據類型。
基於 decorator 的寫法與之前最大的區別是,在 Controller 這個橫切麵,之前只有 loader 可以掌控,而現在可以在 decorator 中加以集中控制,再結合 TypeScript 的元信息,可以做出很多擴展,比如:
參數格式化
在 eggjs 中,因為沒有類型信息的原因,從 params 和 query 中獲取的信息都會是字元串類型,都需要在 Controller 中手動轉換。而改造之後的寫法,參數直接暴露在函數入參里,我們就可以直接拿寫在入參的類型定義作為格式化的依據,根據類型嘗試轉換,保證參數類型正確,可以初步防止類型不符的參數進入到 Controller,省去手動判斷、轉換的邏輯。
參數校驗
參數格式化只能保證參數的類型一致性,而我們的需求不止這些,比如必選參數為空時需要攔截,有時參數是複雜對象為了防止惡意構造數據,需要對數據格式做深度檢測,所以這裡引入了參數校驗庫,parameter,通過它來解決複雜的校驗問題。
export class HomeController {
@route('/api/xxx', { name: '獲取XXX數據', validateMetaInfo: [{
name: 'data',
rule: {
type: 'object',
str: { type: 'string', max: 20 },
count: { type: 'number', max: 10, required: false },
},
}] })
async getXXX(data: { str: string, count?: number }>) {
return data.str;
}
}
這裡有個問題,在類型是複雜類型時,TypeScript 預設生成的元數據里,獲取不到類型的具體欄位屬性信息,而且前端直接引入復用後端的類型定義也比較麻煩。所以,想要在定義類型的同時,復用類型的定義,只能在編譯時做工作,TypeScript 也開放出了編譯時插件API,在不用編譯時插件的情況下,就需要單獨寫一份規則的數據。
有插件後:
export class HomeController {
@route('/api/xxx', { name: '獲取XXX數據' })
async getXXX(data: BaseValidateRule<{
type: 'object',
rule: {
str: { type: 'string', max: 20 },
count: { type: 'number', max: 10, required: false },
},
}>) {
return data.str;
}
}
路由級中間件
函數類型跟 egg 定義稍有不同:
(app: Application, typeInfo: RouteType) => (ctx: any, next: any) => any
egg 已經定義了中間件,為什麼在路由上還定義一個?在路由定義的中間件跟全局的中間件區別在於範圍,全局中間件更適合大規模的統一處理,用來統一處理特定業務功能介面就大材小用了,還需要設置過濾邏輯,甚至需要在 config 中設置黑白名單。而路由級中間件適合只有部分介面需要的統一處理,配合從 @route 上收集的類型信息處理更佳。
API文檔 & 前端SDK
既然已經收集到了那麼多元數據,根據這些數據生成API文檔就很簡單了無非就是前端的展示,也可以把數據轉換對接其他的API文檔平臺。
更近一步,直接生成前端調用SDK?當然沒問題。本插件支持通過模板生成,如果沒有找到模板,會在SDK生成目錄生成預設模板。
// Controller
export class MetaController {
@route({ url: '/meta/index', name: '首頁' })
async index(id: string, n: number, e: 'enumA' | 'enumB', d: Date) {
return 'metaIndex';
}
}
// 生成代碼
export class MetaService extends Base {
/** 首頁 */
async index(id: string, n: number, e: string, d: Date) {
const __data = { id, n, e, d };
return await this.request({
method: `get`,
url: `/meta/index`,
data: __data,
});
}
}
export const metaService = new MetaService();
export default new MetaService();
開發時
在配置中開啟即可,根據需要自定義其他配置。當 Controller 中文件修改時,會同時重新生成對應的前端SDK文件。
構建打包時
在 前端打包流程前 可以使用 egg-controller gensdk
命令生成前端sdk,需要註意,如果為 TypeScript 項目,需要先將 TypeScript 編譯,然後執行生成命令。
Controller 作為請求的起點,這隻是個開始。