前言
接着上一次的代码进行更进一步的优化与扩展
正文
之前完成的基础框架所需要优化的地方
controller
每次设置都需要手动的去写,而且 mock(模拟)数据时也是比较的繁琐的
- 实现思路
参考着 Angular 的方式,使用 DI(依赖注入) 的方式实现控制反转(IOC),将所有的类都注册进来,让所有的路由就用装饰器就自动的注册进来
- 之前忘了在 nodeuii 目录下创建 views 目录了 先行补上
目录结构截图
- 接着在项目中用起来 swig ,下面是具体的修改文件
- nodeuii -> config -> main.js => 引入 path 包,给 config 设置页面(viewDir)以及静态资源(staticDir)路径
这里说下之前的 lodash 的用法是错误的需要传两个参数 (原对象,扩展的对象) 这样才能扩展成功 官方文档 ``` //上线环境的配置 import _ from ‘lodash’;//引入 lodash (https://www.npmjs.com/package/lodash) lodash 中文网 import local from ‘./local’;//引入本地开发环境的配置 import path from ‘path’;//引入 path 路径包 (https://www.npmjs.com/package/path)
//声明一个上线端口配置的对象 const server = { ‘port’:80//设置上线版本的端口号 一般都是 80 };
//声明一个程序主要配置的对象 let config = { ‘viewDir’:path.join(dirname,’../views’),//页面目录 ‘staticDir’:path.join(dirname,’../assets’),//静态资源目录 ‘env’:process.env.NODEENV//程序进程相应名称 development(本地) procuction(线上) }; if(config.env == ‘procuction’){//判断如果进程是上线状态 config = .extend(config,server); }else{ config = _.extend(config,local); } export default config;
- nodeuii -> app.js => 引入 co 包,设置 render 的具体参数
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 config from ‘./config/main’;//引入 端口 配置文件 import InitController from ‘./controllers/InitController’;//引入 InitController const app = new Koa();//实例化
app.context.render = co.wrap(render({ root: config.viewDir, autoescape: true, cache: ‘memory’, // disable, set to false ext: ‘html’, writeBody: false }));
InitController.getAllrouters(app, router);//初始化所有路由 app.listen(config.port, () => { console.log(‘Server is start’); console.log(config); });
- nodeuii -> controllers -> IndexController.js => 将之前写死的 '123' 改为 render 出来的相应页面名称即可
import IndexModel from ‘../models/IndexModel’; class IndexController{ constructor(){ } index(){ return async(ctx,next) =>{ ctx.body = await ctx.render(‘index’); } } } export default IndexController;
- 接下来由于并未配置 html 相关的设置,所以这里现在 build 目录下手动新建一个 views -> Index.html 文件
<!DOCTYPE html>
首页测试
3. 上面的代码写完之后在命令行中进行编译与启动,这里我开了两个窗口分别执行
npm run build:dev supervisor ./build/app.js
4. 之后打开浏览器输入页面路径 [http://localhost:8081/](http://localhost:8081/) 或者是 [http://localhost:8081/index.html](2418203c14378d19c142d70ac581f543) 就都可以打开该页面了
> <br />

5. 接下来测试下页面是否可以引用到静态文件
- 在 build 目录下新建结构如下
> build -> assets -> styles -> index.css
- 简单编辑 index.css 文件
h1{ color: red; }
- 再简单编辑 src -> 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 config from ‘./config/main’;//引入 端口 配置文件 import InitController from ‘./controllers/InitController’;//引入 InitController const app = new Koa();//实例化
//配置 render app.context.render = co.wrap(render({ root: config.viewDir, autoescape: true, cache: ‘memory’, // disable, set to false ext: ‘html’, writeBody: false })); //配置静态资源路径 app.use(serve(config.staticDir)); //初始化所有路由 InitController.getAllrouters(app, router); //配置程序启动端口 app.listen(config.port, () => { console.log(‘Server is start’); console.log(config); });
> 
- 再简单编辑下 build -> views -> Index.html 将 style 引入一下
<!DOCTYPE html>
Hello World
- 在命令行工具的两个窗口中分别进行重新的编译和热启
npm run build:dev
supervisor ./build/app.js
- 在浏览器中刷新刚才的页面显示如下证明已经可以成功的引用静态资源了
> 
6. 接下来开始模拟传递数据
- 编辑主要的起到数据传输作用的文件 src -> nodeuii -> models -> IndexModel.js
//这个 Model 主要是为了可以随意的去发数据来用的 class IndexModel{ constructor(){
}
//构建一个获取数据的方法
getData(){
return new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("Hello World");
},1000)
})
}
}
export default IndexModel;
- 再编辑 src -> nodeuii -> controllers -> IndexController.js 这个是用来接收上面的 IndexModel.js 文件传递出的数据将其 render 传递给页面来用的
import IndexModel from ‘../models/IndexModel’; class IndexController{ constructor(){ } index(){ return async(ctx,next) =>{ var indexModelIns = new IndexModel(); //await 后面记得一定要接一个 promise const result = await indexModelIns.getData(); ctx.body = await ctx.render(‘index’,{data:result}); } } } export default IndexController;
> <br />
主要修改的位置
- 接着再在 build -> views -> Index.html 文件中接收上面的 IndexController.js 文件 render 出来的 data 数据,稍作修改
> 
7. 上半部分总结
- IndexController.js 源码 供下面总结剖析参考用
> 
- 下面的文字是照着上面的 IndexController.js 里的源码进行剖析的:
> 到了上面为止就是比较简单的完成了:<br />
应用层的 用路由去引你的 model 然后你的 model 去引别的 model 这样的一个简单东西。
> 但是上面的其实是下面的一些问题的:<br />
第一点是 上面的 model 是自己模拟出来的,但是测试的时候像这样的 model 是模拟不来的,所以在测试的时候基本上算是小挂了。<br />
第二点是 像是上面的 model 这样的每一个都得非常详细的知道他自己得去引谁这样的话,如果 model 数量非常的多的话,那样就得在顶部一次又一次的 import 引入每一个 model 这样相当的繁琐
8. 接着优化 IndexController.js 文件
- 思路:
> node 的后端代码一定要实现 IOC(控制反转)<br />
IOC 是通过 DI(依赖注入)来实现的<br />
这个依赖注入实现实际上就需要两步:第一步是把引用的类抽出去,第二步是把 async 时实例化的过程抽出去,这两个东西就是需要控制反转做到的东西<br />
说明白点 DI 其实就是:从类里面剔除那些引用复杂的逻辑代码这样的一个过程,剔除完之后不重要还要给人默默地给 new 好了放进构造函数里<br />
实现的思维逻辑:首先需要将你的所有的 model 对应的类注入到一个容器里,之后就是取--取有两种方式 一种是直接通过配置文件获取,还有一种是直接通过代码去注入
9. 再次使用栗子解释 IoC 控制反转
- IoC 栗子概述:
> 假设我想要你给小王打电话,这个时候你得先找我让我把小王的电话号码告诉你,但是 IoC 可以实现的能力是:我把这个电话提前的灌到你的电话本里,你想给谁打电话就都可以了,就是我先给你灌好了,你什么时候想打拿着电话本自己找就行了,这个就是我主动的将所有的电话都灌到电话本里去了,不再需要你主动的过来找我了,这个时候你就有了一种能力想给谁打就给谁打,因为我都给你注册好了
- 再提下之前上面完成的代码
> 对于简单的 MVC 架构来说之前的就是标准的代码了,实现的功能也是很完备了,算是一套很完备的 MVC 体系了,但是如果一家公司这么长期的用下去你也可以离职了(呵呵),因为构建这种框架的人他没有想应的思维,他也不太懂后端的具体流程,他只知道用哪个就引哪个,但是这样做就会很傻,原因还是上面总结的两点:在做单元测试的时候 model 还得自己再去构建一遍,还有就是每次都是去找想应的 model 太费劲了 每次都要 import ,万一数量太多某个东西单词拼错导致报错了的话那么这整个的一个文件就废掉了;所以就是需要 不需要你自己再去找而是在你找之前就注入好给你了的这样的容器。你想要谁就在 constructor 里面自动的给你就完了
10. 实现 IoC 需要在 IndexController.js 中修改的代码
// import IndexModel from ‘../models/IndexModel’; class IndexController{ constructor(opts){ this.indexservice = opts.service.indexModelIns; } index(){ return async(ctx,next) =>{ // var indexModelIns = new IndexModel(); //await 后面记得一定要接一个 promise // const result = await indexModelIns.getData(); const result = await this.indexservice.getData(); ctx.body = await ctx.render(‘index’,{data:result}); } } } export default IndexController;
- 修改的地方
> 
- 将来上线的代码就是这样的,一个类就是一个类
> 
11. 拓展 -- 实现 DI(依赖注入)的小 Demo (这个是比较基础的一个使入门人了解 DI 这个过程的 Demo)
- 这里用的是 [Awilix](https://github.com/jeffijoe/awilix) 这个就是一个比较好的实现 IoC 控制反转的非常小的能力较强一个库了,他也有想应的[ koa 版本](https://github.com/jeffijoe/awilix-koa)的 它是用 TypeScript 写的<br />
引用 [Awilix](https://github.com/jeffijoe/awilix) 实现一个 DI Demo
- 在命令行新开个窗口敲命令
cd Desktop mkdir dihello cd dihello npm init -y npm install awilix —save
- 之后在 dihello 项目中新建一个 app.js 启动文件 编辑 app.js
const awilix = require(‘awilix’);//帮助你去实现 DI 的库
// Create the container and set the injectionMode to PROXY (which is also the default).
//创建一个容器 实现通过将 mode 注入的方式
const container = awilix.createContainer({
injectionMode: awilix.InjectionMode.PROXY
});
//实现一个基本的 controller
class UserController {
// We are using constructor injection.
//以后的注入的方式都是通过 constructor 来实现的 它会将实例化后的类注入到你的类里
constructor(opts) {
// Save a reference to our dependency.
//构建一个 userService = 参数里面注入进的 userService
this.userService = opts.userService;
}
getUser(ctx) {
//发起一个打电话的过程
return this.userService.getUser(22);
}
}
//把 UserController 这个类 注册进 container
container.register({
// Here we are telling Awilix how to resolve a
// userController: by instantiating a class.
userController: awilix.asClass(UserController)
})
//实现一个 model
// Let’s try with a factory function.
const makeUserService = () => {
//这个东西就是一个服务 他是为了将来去注入用的
return {
getUser: id => {
return id + 44;
}
}
}
//把 makeUserService 这个方法 注册进 container
container.register({
// the userService
is resolved by
// invoking the function
//上面的注释是说:如果 makeUserService 是被定义成方法了就是 awilix.asFunction(makeUserService) 这样的写法
//如果是一个类的话就和上面的 UserController一样 就用 asClass
//这个过程是把电话放进了你的电话本
userService: awilix.asFunction(makeUserService)
})
//测试是否执行 console.log(container.resolve(‘userController’).getUser());
- 之后在控制台中启动服务 `node app.js`,如下图所示成功输出了 "66"
> 
- 回顾上面的实现
> `UserController` 这个类和 `makeUserService` 这个方法他们两个是完全分离的,完全靠的是`container` 来将来进行一个相互注入的过程
12. 拓展 -- 实现 DI 的另一个 Demo
- 这里用的是 [bearcat](https://github.com/bearcatjs/bearcat) 这个库
- 用这个需要了解 [POJO](https://baike.baidu.com/item/POJO/3311958?fr=aladdin) 这个需要一定的 JAVA 基础
> POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。<br />
POJO是Plain OrdinaryJava Object的缩写不错,但是它通指没有使用Entity Beans的普通java对象,可以把POJO作为支持业务逻辑的协助类。<br />
POJO实质上可以理解为简单的实体类,顾名思义POJO类的作用是方便程序员使用数据库中的数据表,对于广大的程序员,可以很方便的将POJO类当做对象来进行使用,当然也是可以方便的调用其get,set方法。POJO类也给我们在struts框架中的配置带来了很大的方便。
使用POJO名称是为了避免和[EJB](https://baike.baidu.com/item/EJB)混淆起来, 而且简称比较直接. 其中有一些属性及其getter setter方法的类,没有业务逻辑,有时可以作为[VO](https://baike.baidu.com/item/VO)(value -object)或[dto](https://baike.baidu.com/item/dto)(Data Transform Object)来使用.当然,如果你有一个简单的运算属性也是可以的,但不允许有业务方法,也不能携带有connection之类的方法。
- 接着用上面的那个命令行窗口敲命令
cd .. mkdir diworld cd diworld npm init -y
- 先来个初始版本的源码,在项目中新建三个文件
- app.js
var Hello = require(‘./hello’); var World = require(‘./world’); var XiaochuanClass = function(){ this.Hello = new Hello(); this.World = new World(); } XiaochuanClass.prototype.dingding = function(){ this.Hello.say(); this.World.say(); console.log(‘上课了’); } var xiaochuanClassIns = new XiaochuanClass(); xiaochuanClassIns.dingding(); module.exports = XiaochuanClass;
- hello.js
var Hello = function(){ } Hello.prototype.say = function(){ console.log(‘Hello’); }
module.exports = Hello;
- world.js
var World = function(){ } World.prototype.say = function(){ console.log(‘World’); }
module.exports =World;
- 之后再命令行中启动程序 `node app.js` 显示如下证明成功了
> 
- 总结上面初始版本的示例出现的问题,其实和上面这个项目进度出现的问题是一样的都是那两个
> 现在初步的写法所表现出来的问题<br />
1.找具体类的时候 完全需要知道类的地址 文件名 exports 的形式 一旦文件夹文件名或者是exports 形式发生了变化 然后就会找不到相应的文件了 关系对了复杂了 代码就会乱套了<br />
2.做单元测试的时候 模拟 hello world 就难了 得自己再去书写 app.js 里面的代码 要不就是把这个 hello world 给注释了 要不就得 mock (自己再去模拟数据)
- 接着在命令行中装包
npm install bearcat —save
- 完善代码
- app.js 将之前的注释掉选择使用 bearcat 这个包来实现注入
var Bearcat = require(‘bearcat’); var contextPath = require(‘./context.json’); // var Hello = require(‘./hello’); // var World = require(‘./world’); // var XiaochuanClass = function(){ // this.Hello = new Hello(); // this.World = new World(); // } function Xiaochuan(){} Xiaochuan.prototype.dingding = function(Hello){ //把一个Hello当成一个参数传递 this.Hello.say(); this.World.say(); console.log(‘上课了’); } var bearcat = Bearcat.createApp([contextPath]); bearcat.start = function(){ // var xiaochuanClassIns = new Xiaochuan; var xiaochuanClassIns = bearcat.getBean(‘xiaochuan’); xiaochuanClassIns.dingding(); };
- context.json 主要的基于 javabean 的配置文件
{ “name”:”diworld”, “bean”:[{ “id”:”Xiaochuan”, “func”:”Xiaochuan”, “props”:[{ “name”:”hello”, “ref”:”hello” }], “args”:[{ “name”:”world”, “ref”:”world” }] },{ “id”:”hello”, “func”:”Xiaochuan” },{ “id”:”world”, “func”:”world” }] }
- hello.js 和 world.js 两个文件和之前的不变
- code.txt 笔记
现在初步的写法所表现出来的问题 1.找具体类的时候 完全需要知道类的地址 文件名 exports 的形式 一旦文件夹文件名或者是exports 形式发生了变化 然后就会找不到相应的文件了 关系对了复杂了 代码就会乱套了 2.做单元测试的时候 模拟 hello world 就难了 得自己再去书写 app.js 里面的代码 要不就是把这个 hello world 给注释了 要不就得 mock (自己再去模拟数据) 3.这里修复上面的问题是用的 bearcat (https://github.com/bearcatjs/bearcat) 要配合着 javabean 来使用 JavaBean (https://baike.baidu.com/item/javaBean/529577?fr=aladdin) 他是一个小组件 可重用组件 可以通过 js 的反射 组件内部的值获取到 方法、属性等等的一些东西
- 诶 o.o 这个示例完成不了了 这个包的时间太久了,以前的方法实现不了,官网上也没有 api 了 有些遗憾吖 /摊手
13. 接着回到主项目中
- 前言
> 没有实现 IoC 的之前的项目是基于 MVC 的那种逻辑实现的比较的传统的框架<br />
使用 IoC 是一种对项目整个的拔高<br />
Node 写到后面的话东西都是需要很多的逻辑的<br />
上面用的是参照 PHP 的逻辑( 跟 PPH 框架 YII 的逻辑一模一样),也用到了写 java 的逻辑,实际上也可以把 .net 的逻辑也拿来丰富你的项目,所以到了后面限制自己的肯定不是语法而是逻辑,像是怎么去反射啦、实现 DI(依赖注入) 啦等等其他的一些思想<br />
上面主项目整个项目架构的文件夹名称什么的没有人会说这个错了怎么怎么样的,走到哪里都是可以用的
14. 接下来先完善之前的项目增加一个容错的功能
- 在 nodeuii 路径下新建一个 middlewares 文件夹,这个就是一个中间件,这个中间件最早是 java 提出来的叫做消息中间件,就是所有的后台的数据包括前端传过来的数据都得通过这个中间件,叫做消息中间件是因为就是那些固有的套路想要传来传去的都得通过 xml 如果你前端想把数据写进去的话要写到这个 xml 里面去然后把这个东西再交给后端,后端想吐什么东西要交给你前端,所以这个叫消息中间件,上面的都是很多年的概念了。<br />
在 nodejs 里他也一样有相应的消息中间件,消息中间件要做的事情也是很明白的,他就是用来承载中间的消息的
- 在 middlewares 文件夹下新建一个 ErrorHandler.js 文件并进行编辑 初步创建并导出一个打印错误日志的类
const ErrorHandler = { error(app,logger){ //打印错误日志
}
} export default ErrorHandler;
- 之后再在 app.js 文件中引用 ErrorHandler.js 文件
import ErrorHandler from ‘./middlewares/ErrorHandler’;//引入错误提示的中间件文件
//使用中间件进行容错 ErrorHandler.error(app);
> 
- 因为需要打印日志这里推荐使用 [log4js](https://www.npmjs.com/package/log4js) 这个库来使用,<br />
log4js 是标准的对于前端开发来讲进行错误日志打印的一个程序,它是这么多年的一个大名鼎鼎的适合于各种各样开发的库
- 在命令行安装 log4js 包
npm install log4js —save
- 之后在 app.js 文件中配置 log4
import log4js from ‘log4js’; //引入打印错误日志的包 log4js https://www.npmjs.com/package/log4js
//配置 打印错误日志 的方法 log4js.configure({ appenders: { xclog: { type: ‘file’, filename: ‘./logs/xc.log’ } },//创建一个打印日志的文件 categories: { default: { appenders: [‘xclog’], level: ‘error’ } }//往谁的里面去追加 & 追加的是什么 }); const logger = log4js.getLogger(‘xclog’);
//使用中间件进行容错 ErrorHandler.error(app,logger);
> <br />

- 再回去将 ErrorHandler.js 里面的配置进行完善
const ErrorHandler = { error(app, logger) { //打印错误日志 app.use(async (ctx, next) => { try { await next(); } catch (err) { logger.error(err); ctx.status = err.status || 500; ctx.body = 500; } }); app.use(async (ctx, next) => { await next();//这里石打个标记还要回来的 if(404 != ctx.status) return; ctx.status = 404; ctx.body = 404; }); } } export default ErrorHandler;
- 之后可自行的在控制台中重新编译+热启 在浏览器中随意输入一个没有的路由,会看自动的在项目中创建了一个 logs -> xc.log
> 
15. 之后就可以尽情的在项目中使用 Vue 或者 React 其他的库进行前台数据的接收传递了
- 这里需要在 app.js 中在 使用 swig 向前台 render 页面时增加一个 `varControls:['[[',']]']` 设置,目的是避免与 vue 或者是其他框架出现冲突
//配置 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 或者是其他框架出现冲突 }));
> 
- 之后就可以在前台页面使用 [[ data ]] 是格式进行数据的传递了,下面修改下 build -> views -> index.html 中的一个写法
> 
- 在之后在控制台重新编译+热启 在浏览器中打开 8081 端口进行查看
> 
- 在项目中引入 vue 进行 copy 其教程小 demo 进行测试,编辑 build -> views -> index.html
> [ Vue 逆转消息 小Demo](https://vuejs.bootcss.com/v2/guide/#%E5%A4%84%E7%90%86%E7%94%A8%E6%88%B7%E8%BE%93%E5%85%A5)
<!DOCTYPE html>
[[ data ]]
{{ message }}
```
- 控制台重新操作一遍,再刷新浏览器
温馨提示
上面没有完成主项目的 IoC (控制反转) 会在下一阶段完成滴