前言这段时间用Eggjs作为后台服务框架开发了几个项目。项目很小,但是为了进一步了解Eggjs,特意选择了Eggjs作为开发后端服务的框架基础。期间也遇到了一些问题和坑,有几点值得注意。先说一下我这段时间发展的总结吧。Egg.js为企业级框架和应用而生。我们希望Egg.js孕育出更多的上层框架,帮助开发团队和开发者降低开发和维护成本。这是Eggjs文档中对Eggjs的解释。Eggjs的详细介绍和使用,请点击之前的地址;与Egg.js1.x版本的文档相比,有了很大的改进,很多关键点都可以得到比较充分的解释,并有代表性的例子。开始步骤使用的Egg.js版本为2.2.1,对环境有一定的要求。我使用的配置如下:操作系统:macOS运行环境:v9.8.0使用脚手架快速创建项目:$npmiegg-init-g$egg-initegg-example--type=simple$cdegg-example$npmi项目安装完毕,启动项目:$npmrundev$openlocalhost:7001至此,项目已经成功建立并启动。项目结构:(来自文档)egg-project├──package.json├──app.js(可选)├──agent.js(可选)├──app|├──router.js│├──控制器│|└──home.js│├──服务(可选)│|└──user.js│├──中间件(可选)│|└──response_time.js│├──时间表(可选)│|└──my_task.js│├──public(可选)│|└──reset.css│├──视图(可选)│|└──home.tpl│└──extend(可选)│├──helper.js(可选)│├──request.js(可选)│├──response.js(可选)│├──上下文。js(可选)│├──application.js(可选)│└──agent.js(可选)├──config|├──plugin.js|├──config.default.js│├──config.prod.js|├──config.test.js(可选)|├──config.local.js(可选)|└──config.unittest.js(可选)└──测试├──中间件|└──response_time.test.js└──controller└──home.test.js上面的目录也是开发者创建目录的指南,但是根据文档建立的项目目录结构不是那么完整,基本上标记为“optional”的/config目录下只有plugin.js和config.default.js两个文件,其他文件根据需要自行创建。创建一个控制器。初始项目中会有一个示例控制器。创建新的Controller时,可以参考/app/controller/home.js的例子。一般来说,推荐使用module.exports来暴露一个类或者应用返回的参数。一个类的一个函数(文档例子中的一个箭头函数,其他方法我没试过不知道),类中包含了这个业务的一些操作。接下来在控制器文件目录/app/controller/.jscontrollerfile://controllerinheritedfromeggconstController=require('egg').Controller;classUserControllerextendsController{asyncindex(){const{ctx}=这个;const{名称}=ctx。请求体;ctx.body=`嗨,${name}`;}asyncgetUserById(){const{userId}=this.ctx.request.body;//使用业务函数查询用户信息constuserInfo=awaitthis.service.user.findById(userId);this.ctx.body={msgCode:0,message:'success',data:userInfo};}}//注意:一定要暴露controller,否则请求会报找不到controller的错误;module.exports=UserController;在/app目录下添加路由代码,文件名为router.js,添加路由的代码如下://参数app为全局应用的对象module.exports=app=>{const{路由器、控制器、中间件}=应用程序;//这里controller相当于app下的controller文件目录,user为user.js,index为controller类索引方法router.get('/',controller.user.index);};编写业务通常,controller主要处理数据的结构和返回结果,具体涉及的业务由service业务类方法完成。在/app/service/目录下创建user.js文件,并写入代码://同样继承egg的Service类constService=require('egg').Service;classUserServiceextendsService{//搜索方式用户id用户asyncfindUserById(id){constmysql=this.app.mysql;constresult=awaitmysql.get('users',{id});返回结果;}}module.exports=UserService;根据业务需要添加eggjs插件搭建上层框架在我的开发过程中,对使用到的一些插件进行了简单的说明。插件安装:$npmi--saveegg-pluginName在文件/config/plugin.js中添加配置:exports.pluginName={enable:true,package:'egg-pluginName',};在插件初始化配置的情况下,修改/config/config.default.js:config.pluginName={//配置项};egg-mysql因为使用的是mysql数据库,所以需要一个mysql的nodejs运行库。Egg-mysql是基于eggjs选择的。单击此处查看操作文档。基本的数据库增删改查都可以操作,写起来还挺方便的,但是需要写一个接口返回当前用户某段时间的数据,比较蛋疼。找了半天,连egg-mysql封装的库ali-rds查看源码也找不到这样的方法。无奈之下,只能将原来的mysql查询语句组织起来动态组装。虽然不推荐,但是如果你找到更好的方法,你还是愿意重写的。查询指定日期数据的mysql相关参考资料:http://www.jb51.net/article/1...https://www.cnblogs.com/benef...https://www.cnblogs.com/softi...egg-redisredis相当于一个基于内存的微型数据库。它的访问速度非常快,代码执行时几乎没有阻塞。这里使用egg-redis作为项目对redis的运行库。单击此处获取文档。该文档解释了基本的访问和设置操作。更复杂的操作只能查看redis官方文档。对应的小写命令是方法名:rediscommandDECR,对应的方法使用:///app/controller/user.jsconstvalue=awaitthis.app.redis.decr(keyName);扩展内置对象添加几个插件后,发现源码是以扩展内置对象的方式挂载相关库或插件。ctx文档原文:“一般来说,属性的计算只需要在同一个请求中进行一次,所以必须实现缓存,否则,在同一个请求中多次访问属性时,会计算多次,会降低应用性能,推荐使用Symbol+Getter方式。"constjwt=require('jsonwebtoken');constJWT=Symbol('Context#jwt');module.exports={jwt(){如果(!this[JWT]){this[JWT]=jwt;}返回这个[智威汤逊];}};第一次扩展cxt对象的时候,一直不明白为什么要用Symbol+Getter的方式。后来基于这个问题,查找资料,发现这个方法可以避免和其他属性名冲突。上面代码中,ctx的jwt定义为只读。在方便维护的同时,生成一个带有命名空间(Context#jwt)字符串描述的Symbol实例数据作为ctx的一个属性,通过只读属性jwt获取内部的JWT属性。PS:ctx.JWT==ctx[JWT]//falseSymbol的介绍和使用可以参考阮一峰的ES6。app在扩展app对象的时候遇到了一个问题,就是需要获取ctx怎么办?查找文档,发现在扩展app对象时,只需要在函数体中添加一行代码:constctx=app.createAnonymousContext();得到ctx对象,为使用其他函数提供了桥梁。编写中间件eggjs的中间件处理流程沿用koa的洋葱请求模型中间件:module.exports=options=>{returnasyncfunctionmiddleWareFunctionName(ctx,next){//controller之前的业务处理代码//...等待下一个();//controller之后的业务处理代码//...}}中间件主要返回一个处理业务的函数,该函数接收两个参数:ctx和next。ctx是请求级别的对象,next()方法可以让请求进入下一步。特别注意的是:在controller中,如果请求到达下一步之前还有一些操作,可以控制next()在代码流中的位置,也可以处理请求之后的操作。用eggjs写定时任务也很简单。关注业务代码,简单配置即可使用定时任务。下面是一个简单的定时任务,用于业务数据的定时统计:constSubscription=require('egg').Subscription;classStatisticsextendsSubscription{//通过schedule属性设置定时任务的执行间隔staticgetschedule(){return{cron:'005923***',//秒、分、时、日、月、年//interval:'10s',//设置时间间隔触发,单位是秒,ms是毫秒type:'worker',//all指定所有worker都需要执行,worker为某个worker执行};}//subscribe是真正的定时任务执行时运行的函数asyncsubscribe(){//定时任务业务代码//...}}module.exports=Statistics;当程序启动时,会在配置的指定时机执行相关的业务代码。关于配置csrf的讨论(待补充)eggjs在v2之后默认开启了csrf插件。CSRF可以将请求限制在同源网站,即发送请求只有拥有“独占令牌”的网站才会正确响应。这里很容易和jwt的作用混淆,可以看看这篇文章。跨域使用egg-cors;前后端分离用户认证使用jwt验证jwt在认证方式上不同于csrf,jwt可以在前后端交互数据的body中使用token而不使用cookies进行传输,可以还在header中设置相关信息。详情请参考这篇文章。关于日志(待补充)远程机器开发部署文档的写法有一篇《应用部署》,写的很详细。使用egg-script插件在生产环境中启动应用程序。项目生产静默部署,启动使用npmstart,停止使用npmstop。另外,如果在开发环境中要使用pm2管理进程后台启动,--watch会不断打印consolelog,原因不明。生产环境部署启动命令:$npminstall--production$npmstartstop命令:$npmstop优点总结使用eggjs开发企业级应用相当方便。虽然需要根据需求安装,但安装配置步骤非常简单,可以方便快捷地配置很多有用的业务配置,并且可以区分环境,项目结构和调用方式合理。工具功能访问不足,需要手动添加扩展,不写测试。希望下次能补上。
