摘要:即為裝飾器函數的這里主要為了獲取路由路徑的前綴,為請求方法,為請求路徑,為請求執行的函數。下邊是設置路由路徑前綴和塞入內容的裝飾器函數就不多說了,就是掛載前綴路徑到類的原型對象上,這里需要注意的是作用于類,所以是被修飾的類本身。
很多面對象語言中都有裝飾器(Decorator)函數的概念,Javascript語言的ES7標準中也提及了Decorator,個人認為裝飾器是和async/await一樣讓人興奮的的變化。正如其“裝飾器”的叫法所表達的,他可以對一些對象進行裝飾包裝然后返回一個被包裝過的對象,可以裝飾的對象包括:類,屬性,方法等。
Node.js目前已經支持了async/await語法,但decorator還需要babel的插件支持,具體的配置不在敘述。(截至發稿時間2018-12-29)
下面是引用的關于decorator語法的一個示例:
@testable class Person { @readonly @nonenumerable name() { return `${this.first} ${this.last}` } }
從上面代碼中,我們一眼就能看出,Person類是可測試的,而name方法是只讀和不可枚舉的。
期望效果關于 Decorator 的詳細介紹參見下面兩篇文章:
阮一峰《ECMAScript 6 入門》 -- Decorator
知乎 -- 《Decorators in ES7》
關于Node.js中的路由,大家應該都很熟悉了,無論是在自己寫的http/https服務中,還是在Express、Koa等框架中。我們要為路由提供請求的URL和其他需要的GET及POST等參數,隨后路由需要根據這些數據來執行相應的代碼。
關于Decorator和路由的結合我們這次希望寫出類似下面的代碼:
@Controller("/tags") export default class TagRouter { @Get(":/id") @Login @admin(["developer", "adminWebsite"]) @require(["phone", "password"]) @Log async getTagDetail(ctx, next) { //... } }
關于這段代碼的解釋:
第一行,通過Controller裝飾TagRouter類,為類下的路由函數添加統一路徑前綴/tags。
第二行,創建并導出TagRouter類。
第三行,通過裝飾器為getTagDetail方法添加路徑和請求方法。
第四行,通過裝飾器限制發起請求需要用戶登錄。
第五行,通過裝飾器限制發起請求的用戶必須擁有開發者或者網站管理員權限。
第六行,通過裝飾器檢查請求參數必須包含phone和password字段。
第七行,通過裝飾器為請求打印log。
第八行,路由真正執行的方法。
這樣不僅簡化、規范化了路由的寫法,減少了代碼的冗余和錯誤,還使代碼含義一目了然,無需注釋也能通俗易懂,便于維護、交接等事宜。
具體實現下面就著手寫一個關于movies的路由具體實例,示例采用koa2 + koa-router為基礎組織代碼。
文件路徑:/server/routers/movies.js
import mongoose from "mongoose"; import { Controller, Get, Log } from "../decorator/router"; import { getAllMovies, getSingleMovie, getRelativeMovies } from "../service/movie"; @Controller("/movies") export default class MovieRouter { @Get("/all") @Log async getMovieList(ctx, next) { const type = ctx.query.type; const year = ctx.query.year; const movies = await getAllMovies(type, year); ctx.body = { data: movies, success: true, }; } @Get("/detail/:id") @Log async getMovieDetail(ctx, next) { const id = ctx.params.id; const movie = await getSingleMovie(id); const relativeMovies = await getRelativeMovies(movie); ctx.body = { data: { movie, relativeMovies, }, success: true, } } }
代碼中Controller為路由添加統一前綴,Get指定請求方法和路徑,Log打印日志,參考上面的預期示例。
關于mongodb以及獲取數據的代碼這里就不貼出了,畢竟只是示例而已,大家可以根據自己的資源,自行修改為自己的邏輯。
重點我們看一下,GET /movies/all以及GET /movies//detail/:id這兩個路由的裝飾器實現。
文件路徑:/server/decorator/router.js
import KoaRouter from "koa-router"; import { resolve } from "path"; import glob from "glob"; // 使用shell模式匹配文件 export class Route { constructor(app, routesPath) { this.app = app; this.router = new KoaRouter(); this.routesPath = routesPath; } init = () => { const {app, router, routesPath} = this; glob.sync(resolve(routesPath, "./*.js")).forEach(require); // 具體處理邏輯 app.use(router.routes()); app.use(router.allowedMethods()); } };
首先,導出一個Route類,提供給外部使用,Route類的構造函數接收兩個參數app和routesPath,app即為koa2實例,routesPath為路由文件路徑,如上面movies.js的routesPath為/server/routers/。
然后,提供一個初始化函數init,初始化邏輯中。引用所有routesPath下的路由,并use路由實例。
這樣的話我們就可以在外部這樣調用Route類:
import {Route} from "../decorator/router"; import {resolve} from "path"; export const router = (app) => { const routesPath = resolve(__dirname, "../routes"); const instance = new Route(app, routesPath); instance.init(); }
好了,基本框架搭好了,來看具體邏輯的實現。
先補充完init方法:
文件路徑:/server/decorator/router.js
const pathPrefix = Symbol("pathPrefix"); init = () => { const {app, router, routesPath} = this; glob.sync(resolve(routesPath, "./*.js")).forEach(require); R.forEach( // R為"ramda"方法庫,類似"lodash" ({target, method, path, callback}) => { const prefix = resolvePath(target[pathPrefix]); router[method](prefix + path, ...callback); } )(routeMap) app.use(router.routes()); app.use(router.allowedMethods()); }
為了加載路由,需要一個路由列表routeMap,然后遍歷routeMap,掛載路由,init工作就完成了。
下邊的重點就是向routeMap中塞入數據,這里每個路由對象采用object的形式有四個key,分別為target, method, path, callback。
target即為裝飾器函數的target(這里主要為了獲取路由路徑的前綴),method為請求方法,path為請求路徑,callback為請求執行的函數。
下邊是設置路由路徑前綴和塞入routeMap內容的裝飾器函數:
export const Controller = path => (target, key, descriptor) => { target.prototype[pathPrefix] = path; return descriptor; } export const setRouter = method => path => (target, key, descriptor) => { routeMap.push({ target, method, path: resolvePath(path), callback: changeToArr(target[key]), }); return descriptor; }
Controller就不多說了,就是掛載前綴路徑到類的原型對象上,這里需要注意的是Controller作用于類,所以target是被修飾的類本身。
setRouter函數也很簡單把接受到的路徑格式化處理,把路由處理函數包裝成數組,之后與target、method一起構造城對象塞入routeMap。
這里有兩個輔助函數,簡單貼下代碼看下:
import R from "ramda"; // 類似"lodash"的方法庫 // 如果路徑是以/開頭直接返回,否則補充/后返回 const resolvePath = R.unless( R.startsWith("/"), R.curryN(2, R.concat)("/"), ); // 如果參數是函數直接返回,否則包裝成數組返回 const changeToArr = R.unless( R.is(Array), R.of, );
接下來是get、post、put、delete方法的具體實現,其實就是調用setRouter就行了:
export const Get = setRouter("get"); export const Post = setRouter("post"); export const Put = setRouter("put"); export const Delete = setRouter("delete");
至此,主要的功能就全部實現了,接下來是一些輔助Decorator,大家可以參考和使用core-decorators.js,它是一個第三方模塊,提供了幾個常見的修飾器,通過它也可以更好地理解修飾器。
下面以Log為示例,實現一個輔助Decorator,其他Decorator大家自己發揮:
let logTimes = 0; export const convert = middleware => (target, key, descriptor) => { target[key] = R.compose( R.concat( changeToArr(middleware) ), changeToArr, )(target[key]); return descriptor; } export const Log = convert(async (ctx, next) => { logTimes++; console.time(`${logTimes}: ${ctx.method} - ${ctx.url}`); await next(); console.timeEnd(`${logTimes}: ${ctx.method} - ${ctx.url}`); })
convert是一個輔助函數,首先把普通函數轉換成數組,然后跟其他中間件函數合并。此輔助函數也可用于其他輔助Decorator。
好了,到此文章就結束了,大家多交流,本人github
下一篇:分享koa2源碼解讀
最后貼出關鍵的/server/decorator/router.js的完整代碼
import R from "ramda"; import KoaRouter from "koa-router"; import glob from "glob"; import {resolve} from "path"; const pathPrefix = Symbol("pathPrefix") const routeMap = []; let logTimes = 0; const resolvePath = R.unless( R.startsWith("/"), R.curryN(2, R.concat)("/"), ); const changeToArr = R.unless( R.is(Array), R.of, ); export class Route { constructor(app, routesPath) { this.app = app; this.router = new KoaRouter(); this.routesPath = routesPath; } init = () => { const {app, router, routesPath} = this; glob.sync(resolve(routesPath, "./*.js")).forEach(require); R.forEach( ({target, method, path, callback}) => { const prefix = resolvePath(target[pathPrefix]); router[method](prefix + path, ...callback); } )(routeMap) app.use(router.routes()); app.use(router.allowedMethods()); } }; export const Controller = path => (target, key, descriptor) => { console.log(target); target.prototype[pathPrefix] = path; return descriptor; } export const setRouter = method => path => (target, key, descriptor) => { console.log("setRouter"); routeMap.push({ target, method, path: resolvePath(path), callback: changeToArr(target[key]), }); return descriptor; } export const Get = setRouter("get"); export const Post = setRouter("post"); export const Put = setRouter("put"); export const Delete = setRouter("delete"); export const convert = middleware => (target, key, descriptor) => { target[key] = R.compose( R.concat( changeToArr(middleware) ), changeToArr, )(target[key]); return descriptor; } export const Log = convert(async (ctx, next) => { logTimes++; console.time(`${logTimes}: ${ctx.method} - ${ctx.url}`); await next(); console.timeEnd(`${logTimes}: ${ctx.method} - ${ctx.url}`); })
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109545.html
摘要:這是設計模式系列的第二篇,系列文章目錄如下用一句話總結那些殊途同歸的設計模式工廠策略模版方法美顏相機中的設計模式裝飾者模式幾乎所有的設計模式都是通過增加一層抽象來解決問題。 這是設計模式系列的第二篇,系列文章目錄如下: 用一句話總結那些殊途同歸的設計模式:工廠=?策略=?模版方法 美顏相機中的設計模式——裝飾者模式 幾乎所有的設計模式都是通過增加一層抽象來解決問題。 上一篇中提...
摘要:在方法裝飾器的編寫上,由于裝飾器的行為相似,因此我們可以編寫一個抽象函數,用來生成不同請求方法的不同裝飾器。文章博客地址關于路由管理的幾種自動化方法 前言 我們平時在使用express寫代碼的過程中,會根據類別,將路由分為多個不同的文件,然后在項目的入口文件(例如app.js)中將其依次掛載,例如: const index = require(./routes/index) const...
摘要:本文首發于用控制路由在中長這樣還有上的框架兩者都用來控制路由,這樣寫的好處是更簡潔更優雅更清晰。反觀或上的路由完全差了一個檔次從開始就有了,只是瀏覽器和都還沒有支持。 本文首發于:用Decorator控制Koa路由 showImg(https://segmentfault.com/img/remote/1460000015348698); 在Spring中Controller長這樣 @...
摘要:最近看到一個關于的題文章其中的一個是裝飾器的順序問題就想寫篇博客回顧下裝飾器首先強烈推薦很久之前看的一篇博文翻譯理解中的裝飾器關于什么是裝飾器看這篇文章就好了這里主要想寫關于多個裝飾器的執行流程裝飾順序示例代碼初始化初始化輸出結果初始化初始 最近看到一個關于Flask的CTF(RealWorld CTF 2018 web題bookhub)文章其中的一個trick是裝飾器的順序問題,就想...
摘要:前言今天閑來時看了看中的新標準之一,裝飾器。過程中忽覺它和中的注解有一些類似之處,并且當前版本的中已經支持它了,所以,就動手在一個應用中嘗鮮初體驗了一番。另外,由于裝飾器目前還是中的一個提案,其中具體細節可能還會更改。 前言 今天閑來時看了看ES7中的新標準之一,裝飾器(Decorator)。過程中忽覺它和Java中的注解有一些類似之處,并且當前版本的TypeScript中已經支持它了...
閱讀 3462·2021-09-08 09:36
閱讀 2549·2019-08-30 15:54
閱讀 2352·2019-08-30 15:54
閱讀 1768·2019-08-30 15:44
閱讀 2390·2019-08-26 14:04
閱讀 2443·2019-08-26 14:01
閱讀 2877·2019-08-26 13:58
閱讀 1328·2019-08-26 13:47