摘要:项目都很小,但为了进一步了解,特意选择了作为框架基础开发后端服务。能将请求限制在同源网站,即只有拥有专有令牌的网站发送请求才会正确响应。项目生产静默部署,启动使用,停止使用。不足工具函数的访问需要自己手动添加扩展另没有写测试,希望下次补上。
前言
这段时间,用Eggjs作为后端服务框架开发了几个项目。项目都很小,但为了进一步了解Eggjs,特意选择了Eggjs作为框架基础开发后端服务。期间也遇到过一些问题和坑,还有几个值得注意的点,下面来讲一下我这段时间开发的总结。
Egg.js 为企业级框架和应用而生 ,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。
这个是Eggjs文档对Eggjs的解释,关于Eggjs的详细介绍和使用请点解前面的地址;相对于Egg.js 1.x版本的文档,已经有很大的改进了,很多关键的地方都可以比较完整讲解和带有代表性的实例。
步骤 开始用的Egg.js版本是2.2.1,对环境有一定的要求,本人用的配置如下:
操作系统:macOS
运行环境:v9.8.0
使用脚手架快速创建项目:
$ npm i egg-init -g $ egg-init egg-example --type=simple $ cd egg-example $ npm i
项目安装完毕,启动项目:
$ npm run dev $ open localhost:7001
至此,项目顺利建立及启动完毕。
项目结构:(摘自文档)
egg-project ├── package.json ├── app.js (可选) ├── agent.js (可选) ├── app | ├── router.js │ ├── controller │ | └── home.js │ ├── service (可选) │ | └── user.js │ ├── middleware (可选) │ | └── response_time.js │ ├── schedule (可选) │ | └── my_task.js │ ├── public (可选) │ | └── reset.css │ ├── view (可选) │ | └── home.tpl │ └── extend (可选) │ ├── helper.js (可选) │ ├── request.js (可选) │ ├── response.js (可选) │ ├── context.js (可选) │ ├── application.js (可选) │ └── agent.js (可选) ├── config | ├── plugin.js | ├── config.default.js │ ├── config.prod.js | ├── config.test.js (可选) | ├── config.local.js (可选) | └── config.unittest.js (可选) └── test ├── middleware | └── response_time.test.js └── controller └── home.test.js
上述目录也是一个给开发者一个目录创建的指南,但按照文档建立的项目目录结构没有那么全,基本上标注为“可选”的都是初始没有的,在/config目录里也只有plugin.js和config.default.js两个文件,其他文件要自己根据需求创建。
建立控制器Controller初始项目里会有一个示例Controller,在创建一个新的Controller可以参考/app/controller/home.js的示例,一般而言,推荐使用module.exports暴露出一个类或者参数为app返回一个类的函数(文档示例中为箭头函数,其他方式没试过不清楚),类里面包含着这块业务的一些操作,下面在控制器文件目录/app/controller/里新建一个文件名为user.js的控制器文件:
// 继承egg的控制器 const Controller = require("egg").Controller; class UserController extends Controller { async index() { const { ctx } = this; const { name } = ctx.request.body; ctx.body = `hi, ${name}`; } async getUserById() { const { userId } = this.ctx.request.body; // 使用业务函数查询用户信息 const userInfo = await this.service.user.findById(userId); this.ctx.body = { msgCode: 0, message: "成功", data: userInfo }; } } // 注意:一定要将控制器暴露出去,否则请求的时候会报找不到该controller的错误; module.exports = UserController;添加路由
路由代码在/app目录之下,文件名router.js,添加路由的代码如下:
// 参数app为全局应用的对象 module.exports = app => { const { router, controller, middleware } = app; // 在这里controller相当于app下的controller文件目录,user为user.js,index为控制器类的index方法 router.get("/", controller.user.index); };编写业务
通常,controller主要处理数据的结构和处理返回的结果,具体的涉及的业务由service业务类方法完成,编写service,在目录/app/service/下建立user.js文件,并编写代码:
// 同样要继承egg的Service类 const Service = require("egg").Service; class UserService extends Service { // 根据用户id查找用户 async findUserById(id) { const mysql = this.app.mysql; const result = await mysql.get("users", { id }); return result; } } module.exports = UserService;添加插件
eggjs simple 版本旨在根据业务需求添加eggjs的插件来搭建上层框架。在本人开发过程中,用到的一些插件做简要说明。
插件安装:
$ npm i --save egg-pluginName
在文件/config/plugin.js添加配置:
exports.pluginName = { enable: true, package: "egg-pluginName", };
需要插件初始化配置的情况下,修改/config/config.default.js:
config.pluginName = { // 配置项 };egg-mysql
egg-redis因使用mysql数据库,需要一个nodejs对mysql的操作库,基于eggjs选择了egg-mysql ,操作文档点击这里。基本的数据库增删查改都能操作,写起来还挺方便,但是有个需求,编写某个接口,返回当前用户某一段时间的数据,这就比较蛋疼了,找了很久,就连egg-mysql封装的库ali-rds查看了源码也找不到这类方法,无奈之下,只能通过组织原生的mysql查询语句去动态拼凑,虽然不推荐,不过如果找到更好的方法,还是愿意改写的。
查询指定日期数据的mysql相关参考资料:
http://www.jb51.net/article/1...
https://www.cnblogs.com/benef...
https://www.cnblogs.com/softi...
redis相当于基于内存的一个微型数据库,其存取速度非常快,代码执行的时候几乎感觉不到阻塞,这里使用egg-redis作为项目对redis的操作库,文档点击这里。文档说明解析了基本的存取和设置操作,对于较为复杂的操作只能通过查看redis的官方文档,对应的命令小写即为方法名:
redis命令DECR,对应的方法使用:
// /app/controller/user.js const value = await this.app.redis.decr(keyName);扩展内置对象
添加过几个插件之后,发现其中的源代码都是以扩展内置对象的方式去挂载相关的库或者插件的。
ctx文档原文:“一般来说属性的计算在同一次请求中只需要进行一次,那么一定要实现缓存,否则在同一次请求中多次访问属性时会计算多次,这样会降低应用性能。
推荐的方式是使用 Symbol + Getter 的模式。”
const jwt = require("jsonwebtoken"); const JWT = Symbol("Context#jwt"); module.exports = { get jwt() { if (!this[JWT]) { this[JWT] = jwt; } return this[JWT]; } };
第一次扩展cxt对象的时候,不明白为何要使用Symbol + Getter的模式,后来基于这个问题,查找资料,发现这种方式更能避免和其他属性名发生冲突,上述代码中,ctx的jwt定义为只读方式。在方便维护的同时,生成一个带有命名空间(Context#jwt)字符串描述的Symbol实例数据, 作为ctx的属性,通过只读属性jwt来获取内部的JWT属性。
PS:
ctx.JWT == ctx[JWT] // false
关于Symbol的介绍和使用请参考阮一峰的ES6。
app在扩展app对象的时候,遇到个问题,就是如果需要获取ctx怎么办?
查找文档,找到了在扩展app对象时,只需要在函数体里添加一句代码:
const ctx = app.createAnonymousContext();
就可以获取ctx对象,这对于使用其他函数提供了一道桥梁。
编写中间件eggjs的中间件处理流程遵循koa的洋葱式请求模型
中间件的写法:
module.exports = options => { return async function middleWareFunctionName (ctx, next) { // 控制器之前业务处理代码 // ... await next(); //控制器之后业务处理代码 // ... } }
中间件以返回一个处理业务的函数为主体,函数接收两个参数:ctx、next。ctx则是请求级别的对象,next()方法可以让请求进入下一个步骤。特别注意的是:在一个控制器中,有对请求到达下一步之前做一些操作的,可以控制next()在代码流程中的位置,其后也可以处理请求之后的操作。
制定定时任务在eggjs写定时任务也是非常简单的,关注于业务代码,加以简单的配置,即可使用定时任务。
下面是一个简单的定时统计业务数据的定时任务:
const Subscription = require("egg").Subscription; class Statistics extends Subscription { // 通过 schedule 属性来设置定时任务的执行间隔等配置 static get schedule() { return { cron: "00 59 23 * * *", // 秒 分 时 日 月 年 // interval: "10s", // 设置时间间隔触发,单位s为秒,ms为毫秒 type: "worker", // all 指定所有的 worker 都需要执行, worker 为某一个 worker 执行 }; } // subscribe 是真正定时任务执行时被运行的函数 async subscribe() { // 定时任务业务代码 // ... } } module.exports = Statistics;
在程序启动的时候,就会在配置的指定时机执行相关的业务代码。
配置(待补充) csrf的讨论eggjs在v2.x版本之后默认开启了csrf插件,已确保基于cookie存储验证信息的网站信息安全。
csrf能将请求限制在同源网站,即只有拥有“专有令牌”的网站发送请求才会正确响应。此处容易与jwt的作用混淆,可以看看这篇文章。
跨域使用egg-cors;前后端分离用户验证
使用jwt验证
jwt则在认证方式上跟csrf上有所不同,jwt可以在不使用cookie的情况下,以token的方式在前后端交互数据的body里传输,也可以在header里设置相关信息,详细可以参考这篇文章。
日志(待补充)类的写法远程机开发部署
文档中,有《应用部署》一文,里面介绍的很详细的。使用egg-script插件启动生产环境中的应用程序。项目生产静默部署,启动使用npm start,停止使用npm stop。另,在开发环境里想要使用pm2管理进程后台启动,--watch会不断打印控制台日志,原因不清楚。生产环境部署
启动命令:
$ npm install --production $ npm start
停止命令:
$ npm stop总结 优点
使用eggjs开发企业级应用还是相当方便的,虽然说要根据需求装,但安装和配置步骤非常简单,很多有用的业务配置都能够很方便快速配置好,还可以区分环境,项目结构和调用方式很合理。不足
工具函数的访问需要自己手动添加扩展另
没有写测试,希望下次补上。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95471.html
摘要:今天就简要说说下的实现。主要的原因是的文档写的不太清楚,方便新人快速上手。导致我们一目十行去扫文档的时候,有时总会觉得有些莫名,的实现就是其中之一。其实这和我本身对的了解不深入有关,但是也没有文档和我说实现啊。 这两天真的是宅的骨头都发霉了,春困秋乏夏打盹,也是醉了。今天就简要说说eggjs下Restful API的实现。主要的原因是egg的文档写的不太清楚,方便新人快速上手。话说eg...
摘要:最近学习,学习过程中使用官方推荐的库,感觉官方库不太好用,基础的没问题。介绍这个轮子其实是很早以前就造好的,主要参考的数据库操作方式。将设置表名设置查询字段联表等操作进行链式操作,给人一种语义化操作数据库的感觉。 最近学习eggjs,学习过程中使用官方推荐的MySQL库,感觉官方库不太好用,基础的CURD没问题。但是复杂点的操作就不行了,虽然官方还有一个egg-sequelize,但是...
摘要:总所周知,的策略让每次都要发送码验证,为了方便,我在的里作了前置拦截。结果不幸从此发生最开始没有看官方文档,以为应该加在里面,又没有考虑到要上传格式的文档,所以直接结果发送的是。这很正常,阅读源码知为时会自动添加的头。不加又以上传了。 总所周知,egg的csrf策略让post每次都要发送token码验证,为了方便,我在axios的interceptor里作了前置拦截。 结果不幸从此发生...
摘要:前两天将一个部署到服务器时,用就是启动不了,错误信息为无权限创建目录。查看工作目录权限,当前用户是有权限的,看了源码,原来是用的包的问题。首先,在生产环境下的启动是通过启动的。 前两天将一个egg部署到服务器时,用npm start就是启动不了,错误信息为无权限创建log目录。查看工作目录权限,当前用户是有权限的,看了源码,原来是用的npm包的问题。这里简单记录下解决过程。 首先,在生...
阅读 1714·2021-11-22 15:33
阅读 2084·2021-10-08 10:04
阅读 3542·2021-08-27 13:12
阅读 3418·2019-08-30 13:06
阅读 1466·2019-08-29 16:43
阅读 1390·2019-08-29 16:40
阅读 785·2019-08-29 16:15
阅读 2745·2019-08-29 14:13