koa2使用es7 的装饰器decorator
本文主要讲述我在做项目中使用装饰器(decorator)来动态加载koa-router的路由的一个基础架构。
目前JavaScript 对decorator 是不支持,但是可以用babel 来编译
既然是koa2结合decorator 使用,首先是要起一个koa2 项目。
环境要求: node >7.6
1.建立文件夹名为koa-decorator ,在该目录下运行 npm init 初始化一个项目(直接默认回车)
- npm init
2.安装koa的基本依赖包,koa,koa-router
- npm install koa,koa-router;
3.构建基本项目目录
- ├── dist----------------------------------- 编译后的
- ├── src ----------------------------------- 项目的所有代码
- │ ├──config ----------------------------- 配置文件
- │ ├──controller ------------------------- 控制器
- │ ├──lib -------------------------------- 一些项目的核心文件(如路由的装饰器文件就在这里)
- │ ├──logic ------------------------------ 一些数据校验
- │ ├──middleware ------------------------- 中间件
- │ ├──models------------------------------ 操作数据表相关逻辑代码(根据项目复杂度可以再分Service层)
- │ ├──util-------------------------------- 相关的工具文件
- │ ├──index.js---------------------------- 项目的入口文件
- ├── theme --------------------------------- 一些静态文件(上传的图片)
- ├── .babelrc ------------------------------ babelrc 的相关配置
- ├── .gitignore ---------------------------- git 的忽略配置文件
- ├── dev.js -------------------------------- 开发环境的启动文件
- ├── production.js ------------------------- 生产环境的启动文件
4.安装babel ,与装饰器的编译依赖(只需要要开发环境安装) babel-cli,babel-core,babel-register,babel-plugin-transform-decorators-legacy
- npm install babel-cli,babel-core,babel-register,babel-plugin-transform-decorators-legacy --save-dev;
5.配置 .babelrc 文件让 其能使用装饰器
- {
- "presets": [],
- "plugins": [
- "transform-decorators-legacy"
- ]
- }
6. 编写开发环境dev.js和 生产环境的production.js 的启动文件
- 1. dev.js
- require("babel-register");
- process.env.NODE_ENV = "development";
- require("./src");
- 2. production.js
- process.env.NODE_ENV = "production";
- require("./dist");
你会发现这两个文件很简单,主要是区别用来开发运行和生产打包编译的,生产环境运行的打包后的dist 目录的代码
7.配置package.json 使项目能修改后自动重启热加载,这里开发环境我使用 supervisor,有人使用nodenom ,生产环境用pm2
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "build": "babel src --out-dir dist",
- "dev": "set NODE_ENV=development && supervisor --watch src dev.js",
- "start": "npm run build && set NODE_ENV=production && supervisor --watch dist production.js",
- "pm2": "pm2 start production.js --name 'wx-node' --env NODE_ENV='production' --output ./logs/logs-out.log --error ./logs/logs-error.log --watch dist"
- },
7.1 运行 npm run build : 是用babel 直接将src 目录编译在dist 目录
7.2 运行 npm run dev : 是设置环境变量为development 并且监听src目录,启动dev.js 运行,为开发环境
7.3 运行 npm run start : 是 运行第一个命令npm run build 并且设置环境变量为production 监听dist 目录,启动production.js运行,为生产或者测试环境
7.4 运行npm run pm2: 这是使用pm2来守护项目进程,并且设置环境变量和日志记录
8.编写入口文件index.js 让服务跑起来
- src/index.js
- const koa = require("koa");
- const http = require("http");
- const App = new koa();
- // 定义端口常量
- const port = 3000;
- App.use(async (ctx,next)=>{
- ctx.body = await "this is koa"
- await next();
- })
- // 启动服务
- var httpApp = http.createServer(App.callback()).listen(port,'0.0.0.0');//获取ip 为ip4 格式(192.168.5.109),默认是ip6 格式(::ffff:192.168.5.109);
- httpApp.on("listening",()=>{
- console.log(`http server start runing in port ${port}...`)
- })
- App.on("error",(err,ctx)=>{
- console.log("server error: "+err.stack);
- ctx.throw(500, 'server error')
- })
9.重点:编写装饰器的路由文件,本文核心内容就是在这里
9.1 引入相关依赖包 和定义所有的请求方法
- src/lib/decoratorRouter/index.js
- const koaRouter = require("koa-router");
- const router = new koaRouter();
- const routerPrefix ="/api" //定义接口前缀
- //声明所有接口的方式的映射,下面会用到
- const RequestMethod = {
- GET: 'get',
- POST: 'post',
- PUT: 'put',
- DELETE: 'delete',
- ALL: "all"
- }
9.2 编写装饰类class 的函数,主要作用是对类的拦截,然后实例化该类,并获取和调用该类下所有实例方法,由于es6 的class的方法是不迭代的,所以使用了Object.getOwnPropertyDescriptors(object.prototype)
- src/lib/decoratorRouter/index.js
- //定义controller 的函数,这是装饰类class 的函数,接受一个参数(和路由前缀并接一起)
- function Controller(prefix) {
- router.prefixed =routerPrefix+(prefix ? prefix.replace(/\/+$/g, "") : '');
- //对 类 class 进行拦截操作,返回一个函数,该函数实际接受三个参数(拦截目标targer,目标的key,key 的描述)
- return (target) => {
- //把路由router 挂载在拦截目标,作为静态属性
- target.router = router;
- //实例化该类 class
- let obj = new target;
- // 获取该实例下的所有实例方法,进行 迭代调用,除了构造函数 和一个前置函数(后面会说得如何实现和作用)
- let actionList = Object.getOwnPropertyDescriptors(target.prototype);
- for (let key in actionList) {
- if (key !== "constructor") {
- var fn = actionList[key].value;
- if (typeof fn == "function" && fn.name != "__before") {
- fn.call(obj, router, obj);//保证在类中能正确访问this,调用该方法是用call,还有两个参数是 router 和 obj 实例
- }
- }
- }
- }
- }
9.3 编写装饰 实例方法的函数,当我们对类class 进行装饰的时候,其实例方法会全部自动被调用,这时候继续对实例方法进行拦截,拦截的目的就是给该实例方法与路由结合一起
- /src/lib/decoratorRouter/index.js
- //该装饰函数接受两个参数,请求url 和请求方式
- function Request(option = {url, method}) {
- //拦截该实例方法,参数三个
- return function (target, value, dec) {
- //声明fn 缓存原来的 函数体 dev.value
- let fn = dec.value;
- //然后重写该函数,参数两个,在 controller 装饰类的时候自动调用转入的两个参数
- dec.value = (routers, targets) => {
- //这里,才是真正调用koa-router 路由的时候
- routers[option.method](routers.prefixed + option.url, async (ctx, next) => {
- //这里写了一个前置函数,判断前置函数存在
- if (target.__before && typeof target.__before == "function") {
- // 如果class 有__before 前置函数,//再默认装饰一次
- var beforeRes = await target.__before.call(target,ctx, next, target);
- //前置函数如果没有返回内容,继续执行实例方法,否则直接响应 body,不执行实例方法
- if (!beforeRes) {
- return await fn.call(target, ctx, next, target)
- }else{
- return ctx.body = await beforeRes
- }
- } else {
- // 没有前置函数,直接调回原来的实例函数执行,使用call ,传入的参数就有ctx,next,实例targe
- await fn.call(target, ctx, next, target)
- }
- })
- }
- }
- }
9.4 整合所有的请求方法并导出接口
- /src/lib/decoratorRouter/index.js
- // post 请求
- function POST(url) {
- return Request({url, method: RequestMethod.POST})
- }
- //get 请求
- function GET(url) {
- return Request({url, method: RequestMethod.GET})
- }
- //PUT 请求
- function PUT(url) {
- return Request({url, method: RequestMethod.PUT})
- }
- //DEL请求
- function DEL(url) {
- return Request({url, method: RequestMethod.DELETE})
- }
- //ALL 请求
- function ALL(url) {
- return Request({url, method: RequestMethod.ALL})
- }
- module.exports = {
- Controller,POST,GET,PUT,DEL,ALL
- }
10 .装饰koa-router 的核心内容写完了,那么如何做到自动加载呢,按照项目目录架构,controller 目录是处理接口目录,使用内置的文件系统模块fs 处理文件自动载入
- /src/lib/loadRouter/index.js
- const fs = require("fs");
- const {resolve} = require("path")
- //这里很重要,区别环境变量,确定调用是 dist/controller (编译后),还是调用 src/controller (开发)
- let entryPath = process.env.NODE_ENV==="development"?"src":"dist";
- console.log(process.env.NODE_ENV+"环境:执行目录"+entryPath)//这是controller 的入口根目录
- let controllerPath = resolve(entryPath,'controller');
- //对外导出一个函数,并接收app 实例作为参数,
- module.exports = (App)=>{
- let loadCtroller = (rootPaths)=>{
- try {
- var allfile = fs.readdirSync(rootPaths); //加载目录下的所有文件进行遍历
- allfile.forEach((file)=>{
- var filePath = resolve(rootPaths,file)// 获取遍历文件的路径
- if(fs.lstatSync(filePath).isDirectory()){ //判断该文件是否是文件夹,如果是递归继续遍历读取文件
- loadCtroller(filePath)
- }else{
- //如果是文件就使用require 导入,(controller下文件都是对外导出的class),在使用 @controller 装饰函数的时候,将koa-router 的实例作为装饰对象class 的静态属性
- let r = require(filePath);
- if(r&&r.router&&r.router.routes){ //如果有koa-routr 的实例说明装饰成功,直接调用app.use()
- try {
- App
- .use(r.router.routes())
- } catch (error) {
- console.log(filePath)
- }
- }else{
- // console.log("miss routes:--filename:"+filePath)
- }
- }
- })
- } catch (error) {
- console.log(error)
- console.log("no such file or dir :---- "+rootPaths)
- }
- }
- //调用自动加载路由
- loadCtroller(controllerPath);
- }
11. 在index.js 入口文件载入 /src/lib/loadRouter/index.js 文件
- const koa = require("koa");
- const http = require("http");
- const App = new koa();
- // 定义端口常量
- const port = 3000;
- require("./lib/loadRouter/index")(App) // 载入自动加载路由文件
- // 启动服务
- var httpApp = http.createServer(App.callback()).listen(port,'0.0.0.0');//获取ip 为ip4 格式(192.168.5.109),默认是ip6 格式(::ffff:192.168.5.109);
- httpApp.on("listening",()=>{
- console.log(`http server start runing in port ${port}...`)
- })
- App.on("error",(err,ctx)=>{
- console.log("server error: "+err.stack);
- ctx.throw(500, 'server error')
- })
12.然后编写controller 下的文件,新建index.js
- /src/controller/index.js
- const {Controller,GET,POST} = require("../lib/decoratorRouter")
- //访问路径 :路由前缀 + controller 参数 + 请求方式的参数 => 域名:端口/api/index/add
- @Controller("/index")
- class index{
- @GET("/")
- async index(ctx,next){
- ctx.body = await "this is index"
- }
- @POST("/add")
- async add(ctx,next){
- ctx.body = await "this is add"
- }
- }
- module.exports = index;
运行: http://127.0.01:3000/api/index/ 成功访问显示 this is index ,到此基本完毕 了
源码git 地址: https://github.com/1119879311/npm_module/tree/master/node-decorator
对于要多层继续装饰,做拦截,class继承,还有前置函数的使用
可以参考该项目的用法:https://github.com/1119879311/koa2-decorator
在此,完毕,篇幅内容有点多,看不懂可以留言,谢谢大家
koa2使用es7 的装饰器decorator的更多相关文章
- python 装饰器(decorator)
装饰器(decorator) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语 ...
- python语法32[装饰器decorator](转)
一 装饰器decorator decorator设计模式允许动态地对现有的对象或函数包装以至于修改现有的职责和行为,简单地讲用来动态地扩展现有的功能.其实也就是其他语言中的AOP的概念,将对象或函数的 ...
- Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析
装饰器 / Decorator 目录 关于闭包 装饰器的本质 语法糖 装饰器传入参数 1 关于闭包 / About Closure 装饰器其本质是一个闭包函数,为此首先理解闭包的含义. 闭包(Clos ...
- Python_高阶函数、装饰器(decorator)
一.变量: Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来. 对变量赋值x = y是把变量 ...
- python 语法之 装饰器decorator
装饰器 decorator 或者称为包装器,是对函数的一种包装. 它能使函数的功能得到扩充,而同时不用修改函数本身的代码. 它能够增加函数执行前.执行后的行为,而不需对调用函数的代码做任何改变. 下面 ...
- python函数编程-装饰器decorator
函数是个对象,并且可以赋值给一个变量,通过变量也能调用该函数: >>> def now(): ... print('2017-12-28') ... >>> l = ...
- 【Angular专题】 (3)装饰器decorator,一块语法糖
目录 一. Decorator装饰器 二. Typescript中的装饰器 2.1 类装饰器 2.2 方法装饰器 2.3 访问器装饰器 2.4 属性装饰器 2.5 参数装饰器 三. 用ES5代码模拟装 ...
- 就谈个py 的装饰器 decorator
很早很早就知道有这么个 装饰器的东西,叫的非常神秘. 包括c# 和 java 中都有这个东西, c#中叫做attribut 特性,java中叫做Annotation 注解,在偷偷学习c#教程的时候, ...
- Day4 闭包、装饰器decorator、迭代器与生成器、面向过程编程、三元表达式、列表解析与生成器表达式、序列化与反序列化
一.装饰器 一.装饰器的知识储备 1.可变长参数 :*args和**kwargs def index(name,age): print(name,age) def wrapper(*args,**k ...
随机推荐
- Vue+ElementUI项目使用webpack输出MPA【华为云分享】
[摘要] Vue+ElementUI多页面打包改造 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目 ...
- 17.Django学习之django自带的contentType表
通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/aaronthon/article/details/81714496 ...
- iOS textView的使用总结
转自:http://blog.csdn.net/zhaopenghhhhhh/article/details/11597887 在.h文件中声明: @interface ProtocolViewCon ...
- Node.js 中 __dirname 和 ./ 的区别
概要 __dirname 总是指向被执行 js 文件的绝对路径 在 /d1/d2/myscript.js 文件中写了 __dirname, 它的值就是 /d1/d2 . ./ 会返回你执行 node ...
- HDU-1027Ignatius and princess II
Now our hero finds the door to the BEelzebub feng5166. He opens the door and finds feng5166 is about ...
- ARTS-S docker安装miniconda
FROM centos:centos7.3.1611 MAINTAINER zhouyang3 <aaa@qq.com> WORKDIR /usr/local ADD ./ /usr/lo ...
- 【系列专题】JavaScript 重温系列(22篇全)
JavaScript 初级篇 [JS]120-重温基础:语法和数据类型 [JS]121-重温基础:流程控制和错误处理 [JS]122-重温基础:循环和迭代 [JS]123-重温基础:函数 [JS]12 ...
- 每周一练 之 数据结构与算法(LinkedList)
这是第三周的练习题,原本应该先发第二周的,因为周末的时候,我的母亲大人来看望她的宝贝儿子,哈哈,我得带她看看厦门这座美丽的城市呀. 这两天我抓紧整理下第二周的题目和答案,下面我把之前的也列出来: 1. ...
- CCF-CSP题解 201512-4 送货
求字典序最小欧拉路. 似乎不能用\(Fluery\)算法(\(O(E^2)\)).\(Fluery\)算法的思路是:延申的边尽可能不是除去已走过边的图的桥(割).每走一步都要判断是否是割,应当会超时. ...
- 剑指Offer-46.孩子们的游戏(圆圈中最后剩下的数)(C++/Java)
题目: 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指定 ...