前言

接着上一次的代码进行更进一步的优化与扩展

完成上个阶段未完成的 IoC(控制反转)

  1. 需要用到的是 awilixawilix-koa
  • 装包 ``` npm install —save awilix npm install —save awilix-koa
  1. - app.js 里面引入两个包,需引入的方法是参照 [awilix-koa GitHub 上的示例来的](https://github.com/jeffijoe/awilix-koa#basic-usage)

import { asClass, asValue, createContainer} from ‘awilix’; import { scopePerRequest } from ‘awilix-koa’;

  1. - 实现思路
  2. > 创建一个容器,将所有的路由都注册进这个容器里
  3. - 先创建一个 nodeuii -> models -> TextService.js 文件并编辑 这个是模拟的一个发送数据的 Model

class TestService{ constructor(){

  1. }
  2. find(){
  3. return "Hello IoC";
  4. }

} export default TestService;

  1. - 之后修改 nideuii -> controllers -> InitController.js 实现自动注入的能力

//初始化路由 import IndexController from ‘./IndexController’; import { makeInvoker } from ‘awilix-koa’;//引入创建 makeApi 的方法:实现调用用的

//koa-simple-router 示例代码片段 https://www.npmjs.com/package/koa-simple-router // app.use(router( => { // .get(‘/‘, (ctx, next) => { // ctx.body = ‘hello’ // }) // _.post(‘/name/:id’, (ctx, next) => { // // … // }) // })

//业务逻辑 控制 app function makeAPI({testService}){ return { find:(ctx,next)=>{ ctx.body = testService.find(); } } } //做了注入的工作 const api = makeInvoker(makeAPI); const IndexControllerIns = new IndexController(); const InitController = { getAllrouters(app, router) { app.use(router(=>{ .get(‘/‘,api(‘find’)) _.get(‘/Index.html’,IndexControllerIns.index()) })); } }; export default InitController;

  1. - 编辑 app.js 文件

import TestService from ‘./models/TestService’;//引入 TestService 这个 model 路由在下面将其注册到 container 上去

//创建灵魂 IoC 容器 const container = createContainer(); //给容器注册 controller container.register({ // Scoped lifetime = new instance per request // Imagine the TodosService needs a user. // class TodosService { constructor({ user }) { } } testService: asClass(TestService).scoped() }); //关键点 将所有的 controller 的 service 服务到每一个路由中去 这个就是所谓的 DI (依赖注入)了 app.use(scopePerRequest(container));

  1. - 之后在命令行窗口重新的编译+热启

npm run build:dev supervisor ./build/app.js

  1. - 之后浏览器打开 8081 端口,显示如下证明已经成功了
  2. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-bf292baedc47f99a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  3. 2. 上面的只是演示过程,实际上还要修改的噢
  4. - InitController.js 删掉 不要了 上面那样写的话太丑
  5. - 修改 IndexController.js 文件,参考的是下图中的代码,这里面引用到了一个 [装饰器](http://es6.ruanyifeng.com/#docs/decorator) 的概念
  6. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-a476a4510af5938d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  7. - 因为装饰器的语法还不兼容所以需要装一个编译的包,也是 babel 的插件 [babel-plugin-transform-decorators-legacy](https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy)

npm install babel-plugin-transform-decorators-legacy —save-dev

  1. - 之后在 gulpfile.js 配置中引入即可

‘transform-decorators-legacy’

  1. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-9b09e2d683f140ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  2. 3. 由于实现 IoC 的过程相当的复杂,就再下面直接上最终的源码了,源码是修改过的文件才贴的噢,还有就是项目结构是对之前的文件进行了删改
  3. - 由于修改的较多下面贴下项目结构截图
  4. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-edd6f1ed242e43c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  5. - nodeuii -> controllers -> HelloController.js

import { route, GET, POST, before } from ‘awilix-koa’;

@route(‘/hello’) export default class HelloAPI { constructor({ testService }) { this.testService = testService }

  1. @GET()
  2. // @before([authenticate()])
  3. async getUser(ctx) {
  4. const result = await this.testService.find();
  5. // console.log("获取到的数据:"+ result);
  6. ctx.body = await ctx.render('index',{data:result});
  7. }

}

  1. - nodeuii -> controllers -> UserController.js

import { route, GET, POST, before } from ‘awilix-koa’; import xcAauthenticate from ‘../middlewares/xcAauthenticate’;//引入一个路由守护的方法

//路由名称叫 users 这样的写法是 ES6 装饰性函数 decorator http://es6.ruanyifeng.com/#docs/decorator @route(‘/users’) export default class UserAPI {//UserAPI 是 users 的API constructor({ userService, user }) { this.userService = userService; this.user = user; }

  1. //这个是注册了一个方法 相当于 users/:id 假设访问的是 users/4 -> 实际上就到了下面的 async 函数里
  2. //拿回来一个 promise 的 API
  3. //use 的方式去 load 的所有的 controllers
  4. @route('/:id')
  5. @GET()
  6. @before([xcAauthenticate()])//这个做的是路由守护的事情 这里守护之后就不会往下面执行了 逻辑就都在 xcAauthenticate 里面写了
  7. async getUser(ctx) {
  8. console.log('贯穿的 user 值: ' + this.user);
  9. const result = await this.userService.get(ctx.params.id);
  10. // console.log("获取到的数据:"+ result);
  11. ctx.body = await ctx.render('index', { data: result });
  12. }

}

  1. - nodeuii -> middlewares -> xcAauthenticate.js

//路由守护相当于是请求的路由在中间做自定义的一些操作 后更方便的进行控制 const xcAauthenticate = () =>{ return (target,proerty,descriptor)=>{//语法参照 http://es6.ruanyifeng.com/#docs/decorator#%E6%96%B9%E6%B3%95%E7%9A%84%E4%BF%AE%E9%A5%B0 console.log(‘路由守护’,proerty);

  1. return proerty();
  2. const result = 'ok';
  3. if(result == 'ok'){
  4. return Property();
  5. }else{
  6. // target.redirect('https://www.baidu.com');//这里测试使页面跳转至百度
  7. target.redirect('/404');
  8. }
  9. }

}; export default xcAauthenticate;

  1. - nodeuii -> models -> TestService.js

class TestService{ constructor(){

  1. }
  2. find(){
  3. return "IoC Hello 路由";
  4. }

} export default TestService;

  1. - nodeuii -> models -> UserService.js

//这个 Model 主要是为了可以随意的去发数据来用的 class IndexService{ constructor(){

  1. }
  2. //构建一个获取数据的方法
  3. get(id){
  4. return new Promise((resolve,reject)=>{
  5. setTimeout(function(){
  6. resolve("Hello World" + "【" + id + "】");// `${id}`
  7. },1000)
  8. })
  9. }

}

export default IndexService;

  1. - nodeuii -> views -> app.js

import Koa from ‘koa’; //引入 koa 面向node.js的表现型HTTP中间件框架,使Web应用程序和API更加令人愉快地编写 (https://www.npmjs.com/package/koa) import router from ‘koa-simple-router’; //引入 koa-simple-router 简单而快速的路由器(https://www.npmjs.com/package/koa-simple-router) import render from ‘koa-swig’; //引入 koa-swig 视图渲染 (https://www.npmjs.com/package/koa-swig) import co from ‘co’; //引入 co 配合 koa v2.x & koa-swig 使用的包 co 就是把所有的Generator自执行 (https://www.npmjs.com/package/co) import serve from ‘koa-static’; //引入 koa-static Koa静态文件服务中间件 (https://www.npmjs.com/package/koa-static) import log4js from ‘log4js’; //引入打印错误日志的包 log4js https://www.npmjs.com/package/log4js import { asClass, createContainer,Lifetime,asValue } from ‘awilix’;//引用可以实现 IoC 的包 https://github.com/jeffijoe/awilix-koa import { loadControllers,scopePerRequest } from ‘awilix-koa’;//引用可以实现 IoC 的包 相应的 koa 版本 https://github.com/jeffijoe/awilix-koa // import dev from ‘./config/dev;//控制开发环境的模拟开关 这个配置只是个点到的东西并没有完善 import config from ‘./config/main’; //引入 端口 配置文件 import ErrorHandler from ‘./middlewares/ErrorHandler’;//引入错误提示的中间件文件 const app = new Koa(); //实例化 //控制开发环境的模拟开关 // env.init();

//创建灵魂 IoC (控制反转(Inversion of Control,英文缩写为IoC)) 容器 const container = createContainer(); //关键点 将所有的 controller 的 service 服务到每一个路由中去 这个就是所谓的 DI (依赖注入)了 //先把所有的 service 注册到容器里面来 container.loadModules([‘models/*.js’], { formatName: ‘camelCase’,//把文件名转换成驼峰的写法来进行 model 调用 例:TestService -> testService resolverOptions: { //这里也可以设置注入时使用的方法 register:asClass 因为默认就是这个设置所以这里就不需要再自行设置了 lifetime: Lifetime.SCOPED//这个就是生命周期 SCOPED 实现的是 当每一个用户第一次过来请求的页面的时候给其新建一个实例,他再来就会用上次生成的缓存不会再新创建实例 } }); //!!!!! Service 中心注入到对应的 container 中去 app.use(scopePerRequest(container)); //awilix 可以传一个贯穿全局的值 app.use((ctx,next)=>{ ctx.state.container.register({ user:asValue(‘di’) }); return next(); });

//配置 render 静态页面的方法 app.context.render = co.wrap(render({ root: config.viewDir, autoescape: true, cache: ‘memory’, // disable, set to false ext: ‘html’, writeBody: false, varControls:[‘[[‘,’]]’]//这个是设置后台往 html 传数据时将默认的 {{}} 的写法 改为 [[]] 避免与 vue 或者是其他框架出现冲突 })); //配置 打印错误日志 的方法 log4js.configure({ appenders: { xclog: { type: ‘file’, filename: ‘./logs/xc.log’ } },//创建一个打印日志的文件 categories: { default: { appenders: [‘xclog’], level: ‘error’ } }//往谁的里面去追加 & 追加的是什么 }); const logger = log4js.getLogger(‘xclog’);

//配置引用页面静态资源的方法 app.use(serve(config.staticDir)); //使用中间件进行容错 ErrorHandler.error(app,logger); //初始化所有路由 这里 use 的目的:保证 ctx 上下文顺利的进行传输 app.use(loadControllers(‘controllers/*.js’, { cwd: __dirname })) //配置程序启动端口 app.listen(config.port, () => { console.log(‘Server is start’); console.log(config); });

  1. - pp.js 这个是用来了解 ES6 的修饰器的小 Demo

//测试 装饰器的写法 了解其运行的过程 //源码网址:http://es6.ruanyifeng.com/#docs/decorator#%E6%96%B9%E6%B3%95%E7%9A%84%E4%BF%AE%E9%A5%B0 function dec(id) { console.log(‘evaluated’, id); return (target, property, descriptor) => { console.log(‘target’,target); console.log(‘property’,property); console.log(‘descriptor’,descriptor); } }

class Example { @dec(1) @dec(2) method() { console.log(‘init’); } } const s = new Example(); s.method(); //下面的控制台的输出 // evaluated 1
// evaluated 2
// target Example {}
// property method
// descriptor { value: [Function: method], // writable: true,
// enumerable: false,
// configurable: true }
// target Example {}
// property method
// descriptor { value: [Function: method], // writable: true,
// enumerable: false,
// configurable: true }
// init

  1. - 项目启动命令行

//编译 npm run build:dev

//进入 build 目录启动程序 cd build supervisor app.js

  1. - 打开浏览器其所相应的两个路由
  2. > [http://localhost:8081/users/:id](http://localhost:8081/users/:id) 这个 :id 指的是任意参数<br />
  3. [http://localhost:8081/hello](http://localhost:8081/hello)
  4. - 浏览器显示效果
  5. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-7563054baf715ed5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)<br />
  6. ![image.png](http://upload-images.jianshu.io/upload_images/9064013-104b6380eb1d86f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  7. - 总结
  8. > 上面是用 awilix awilix-koa 实现的 IoC(控制反转)的功能,如果需要新建路由的话直接按照 models 下面的 js 文件和相应的 controllers 下面的 js 的套路创建即可
  9. 4. 修改下之前的 gulpfile.js 里面的配置

const gulp = require(‘gulp’); const babel = require(‘gulp-babel’);//引入 gulp 的编译工具 https://www.npmjs.com/package/gulp-babel const watch = require(‘gulp-watch’);//引入 gulp 的监听工具 https://www.npmjs.com/package/gulp-watch const rollup = require(‘gulp-rollup’);//引入清洗 node 无用代码片段的包 https://www.npmjs.com/package/gulp-rollup const replace = require(‘rollup-plugin-replace’);//引入可以时 rolup 清洗时识别 ENV 的包 https://www.npmjs.com/package/rollup-plugin-replace // const rollup = require(‘gulp-better-rollup’);//引入可以替代 babel 的rollup 编译工具,出来的东西很干净,唯一不足的是没有 rollup 的清洗能力 https://www.npmjs.com/package/gulp-better-rollup

//开发阶段的 task gulp.task(‘builddev’, () => { return watch(‘./src/nodeuii//*.js’, { ignoreInitial: false }, () => { gulp.src(‘./src/nodeuii//*.js’)//需要编译的文件 .pipe(babel({ babelrc: false,//这个可以不使用全局的 .babelrc 配置文件 然后在下面自定义需要的配置 ‘plugins’: [ ‘transform-es2015-modules-commonjs’,//https://www.npmjs.com/package/babel-plugin-transform-es2015-modules-commonjs ‘transform-decorators-legacy’//转换装饰器的包 https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy ] })) .pipe(gulp.dest(‘./build/‘))//文件编译后储存的目录 }) } );

//上线版本的 task //babel + rollup 流清理 //清洗 nodeuii 目录中其他的 js 文件 gulp.task(‘buildprod’, () => { gulp.src(‘./src/nodeuii//*.js’)//需要编译的文件 .pipe(babel({ babelrc: false,//这个可以不使用全局的 .babelrc 配置文件 然后在下面自定义需要的配置 ignore: ‘./src/nodeuii/config/dev.js’,//排除配置文件 ‘plugins’: [ ‘transform-es2015-modules-commonjs’,//https://www.npmjs.com/package/babel-plugin-transform-es2015-modules-commonjs ‘transform-decorators-legacy’//转换装饰器的包 https://www.npmjs.com/package/babel-plugin-transform-decorators-legacy ] })) .pipe(gulp.dest(‘./build/‘))//文件编译后储存的目录 } ); //单独清洗 nodeuii 目录中的 dev.js 文件 gulp.task(‘buildconfig’, () => gulp.src(‘./src/nodeuii//*.js’)//需要编译的文件 .pipe(rollup({ input: [‘./src/nodeuii/config/dev.js’],//需要清洗的文件 format: ‘cjs’, ‘plugins’: [ replace({//这里是将 env 进程不是 procuction 便会自动不执行了 ‘process.env.NODE_ENV’: JSON.stringify(‘procuction’) }) ] })) .pipe(gulp.dest(‘./build/‘))//文件编译后储存的目录 );

//创建一个接收任务字符串的变量 let _task = [‘builddev’]; if (process.env.NODE_ENV == ‘procuction’) {//判断进程中使用的是 dev 还是 prod 执行相应的任务 _task = [‘buildconfig’, ‘buildprod’]; }

//这个执行一个默认就会执行的 task gulp.task(‘default’, _task);

  1. > 上面只是做了简单的修改:将 task 的任务名字改了,将之前用 rollup 过滤的 app.js 文件改成 src -> nodeuii -> config -> dev.js 文件
  2. - 之后在命令行进行编译

npm run build:prod

  1. - 查看编译后的 build -> config -> dev.js 文件,与左侧的未编译之前的对比效果如下图所示,可以看到清洗的非常的干净
  2. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-aeb57b71acce8b80.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  3. - 下面的这个代码是可以直接执行的多的 {} 是无所谓的

function(){ { console.log(‘procuction’); } }

  1. - 再在 app.js 文件中引入 dev.js 文件 并执行 dev.init();

import dev from ‘./config/dev’;//控制开发环境的模拟开关 这个配置只是个点到的东西并没有完善

//控制开发环境的模拟开关 dev.init();

  1. - 再在命令行的两个窗口中分别进行编译+热启

npm run build:prod

//这个热启是要进入 build 目录下执行的 supervisor app.js

  1. 5. 接下来写下项目的前台页面示例,这里主要是用的 swig 模板来写的
  2. > swig 具体用法参照网站:[Swig 使用指南](http://www.iqianduan.net/blog/how_to_use_swig)
  3. - 在项目中新建下面的结构,是在之前的 src 目录下新建 webapp 目录及其子目录
  4. > ![image.png](http://upload-images.jianshu.io/upload_images/9064013-5afc77633dc1f6e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  5. - 编辑文件
  6. - layout.html

<!DOCTYPE html>

{% block head %} {% endblock %} {% block styles %} {% endblock %}
{% block content %} 网站内容 {% endblock %}
{% block scripts %} {% endblock %}
  1. - Index.html

{% extends ‘./common/layout.html’ %} {% block title %}首页测试页面{% endblock %} {% block head %}

{% parent %} {% endblock %} {% block content %} {% include ‘../widgets/top/top.html’ %} {% endblock %} {% block scripts %}

{% endblock %}

  1. - top.css 这里直接用的是 [cssnext](http://cssnext.io/features/) 的写法

:root{ —mainColor:yellow; } .widgets-top{ background: var(—mainColor); / 开始书写 bem css /

}

  1. - top.html

[[ data ]]

{{ message }}

  1. - top.js

var app5 = new Vue({ el: ‘#app-5’, data: { message: ‘Hello Vue.js!’ }, methods: { reverseMessage: function () { this.message = this.message.split(‘’).reverse().join(‘’) } } })

```

  • webapp 前台的页面就需要配置 webpack 来进行编译了,具体前台的页面资源路径的配置可以在 src -> config -> main.js 中进行自行配置

image.png