什么是Egg.js

Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。详细的了解可以参考Egg.js的官网:
https://eggjs.org/zh-cn/intro/

Egg.js 奉行『约定优于配置』,按照一套统一的约定进行应用开发,Egg 有很高的扩展性,可以按照团队的约定定制框架,团队内部采用这种方式可以减少开发人员的学习成本。

可以理解Egg.js是一个Node框架,同时它也是基于Koa框架基础上的框架,我们大概了解一下它的前身和主要特点即可。

它的特点有:

本篇随笔不是细说Egg.js 的详细内容,毕竟官网介绍还是比较清晰的,我们主要说使用它来做一个后端的API接口系统,后端肯定需要对数据库进行各种操作,用一个JS的方式来访问数据库,利用egg-sequelize插件,创建和数据库表进行绑定的模型进行操作,还是比较新鲜的,用了会发现确实很方便。用Egg.js来开发后端系统,相当于用前端的语言、做法,来开发后端系统了(虽然Egg.js 也可以用来做前端)。

我们知道,常规的Asp.net或者WebAPI 应用里面,一般有MVC,模型、视图、控制器这些对象,Egg.js 里面也有类似的概念,我们这里没有用用来做前端,那么可以不用它的视图(Egg.js 视图就是一个带变量的模板文件);
控制器就是我们这里用到需要为前端提供API入口和返回JSON的地方,类似我们Web API里面的控制器概念;模型这里可以理解为对数据库对象的封装对象吧;另外和我们常规前端开发一样(类似Vue+Element系统),获取数据的操作逻辑,我们可以封装在Service层,这样可以降低我们控制器里面的逻辑代码,同时也方便重用逻辑处理函数。MVC+Service的关系,大概如下所示。

2、 使用egg.js开发后端API接口系统所需插件

我依照官网的简单案例进行快速初始化,如下所示。

我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(
npm >=6.1.0
):

$ mkdir egg-example && cd egg-example
$ npm init egg
--type=simple
$ npm i

启动项目:

$ npm run dev

其实我们还需要一些额外的插件来跑起来,我的包依赖文件如下所示。

package.json

{"name": "example","version": "1.0.0","description": "## Development","dependencies": {"egg": "^2.10.0",
"egg-cors": "^2.2.3",
"egg-jwt": "^3.1.7",
"egg-mysql": "^3.0.0",
"egg-redis": "^2.4.0",
"egg-scripts": "^2.5.0",
"egg-sequelize": "^4.0.2",
"egg-view-nunjucks": "^2.3.0",
"moment": "^2.29.1",
"mysql2": "^2.2.5",
"node": "^15.10.0"
},"devDependencies": {"autod": "^3.0.1","autod-egg": "^1.0.0","egg-bin": "^4.15.0","egg-mock": "^3.19.2","eslint": "^4.18.1","eslint-config-egg": "^7.0.0","factory-girl": "^5.0.2","sequelize-cli": "^4.0.0"},

我们来看看红色部分的内容,其中

egg 是本身的框架需要的插件,这个是整个框架的核心基础;egg-scripts 这是部署eggjs项目的工具;

egg-corss 是跨域处理所需要的,用于设置csrf的配置等;

egg-jwt 是用于对用户身份认证的处理插件;

egg-mysql + Mysql2 是我们做Mysql数据库处理说需要的插件;

egg-redis 是我们用到redis操作,所需要的插件,可选。

egg-sequelize 是我们操作数据库的一个插件,提供很多方便的接口进行处理,可以搭配Mysql或者PostgreSQL、MS SQLServer数据库插件进行处理的

egg-view-nunjucks 是展示视图模板的一个插件。

moment 是一个日期处理插件,可以处理各种日期格式、转换的一个插件库。

大概就是这些,如果需要结合前端JS的处理插件,可以引入更多的内容,不过我们这里主要介绍后端访问Mysql数据库的处理操作,提供JSON数据接口的,基本上这些也够了。
另外,我们需要知道egg.js的目录很多是约定位置的,因此我们需要知道常规的几个文件夹的意义。我们简单了解下目录约定规范。
egg-project
├── package
.json
├── app
.js (可选)├── agent.js (可选)├── app
| ├── router
.js
│ ├── controller
│ | └── home
.js
│ ├── service
(可选)│ | └── user.js
│ ├── middleware
(可选)│ | └── response_time.js
│ ├──
view (可选)│ | └── home.tpl
│ └── extend
(可选)│ ├── helper.js (可选)├── config
| ├── plugin
.js
| ├── config
.default.js
│ ├── config
.prod.js

我们这里大概知道以上文件夹和文件的意思即可。

  • app/router.js
    用于配置 URL 路由规则,具体参见
    Router
  • app/controller/**
    用于解析用户的输入,处理后返回相应的结果,具体参见
    Controller
  • app/service/**
    用于编写业务逻辑层,可选,建议使用,具体参见
    Service
  • app/middleware/**
    用于编写中间件,可选,具体参见
    Middleware
  • app/extend/**
    用于框架的扩展,可选,具体参见
    框架扩展
  • config/config.{env}.js
    用于编写配置文件,具体参见
    配置
  • config/plugin.js
    用于配置需要加载的插件,具体参见
    插件
1)插件的配置
我们引入的插件模块,需要在app/plugin.js里面启用,如下代码所示。
app/plugin.js
'use strict';

exports.sequelize
={
enable:
true,
package:
'egg-sequelize',
};
exports.mysql
={
enable:
true,
package:
'egg-mysql',
};

exports.nunjucks
={
enable:
true,
package:
'egg-view-nunjucks'};
exports.redis
={
enable:
true,
package:
'egg-redis',
};
exports.jwt
={
enable:
true,
package:
'egg-jwt',
};

exports.cors
={
enable:
true,
package:
'egg-cors',
};

为了访问Mysql数据库,我们还需要在config/config.default.js文件中配置好对应的关系。

config/config.default.js

'use strict';

module.exports
= appInfo =>{
const config
= exports ={};//use for cookie sign key, should change to your own and keep security config.keys = appInfo.name + '_{{keys}}';

config.jwt
={
secret:
'123456', //自定义token的加密条件字符串,可按各自的需求填写 };//Mysql config.sequelize ={
dialect:
'mysql',
host:
'localhost',
port:
3306,
database:
'myprojectdb',
username:
'root',
password:
'123456',
define: {
//freezeTableName默认值为false,会自动在表名后加s freezeTableName: true,//timestamps默认值为true,会自动添加create_time和update_time timestamps: false}
};
//csrf 安全配置 config.security ={
csrf: {
enable:
false,
ignoreJSON:
true},//允许访问接口的白名单 domainWhiteList: ['*'] //['http://localhost:8080'] };
config.cors
={
origin:
'*',
allowMethods:
'GET, HEAD, PUT, POST, DELETE, PATCH'};//........其他配置............... returnconfig;
};

为了给前端提供Web API接口,我们需要为不同的业务对象提供路由入口,路由定义,统一在app/route.js文件中定义。

app/route.js

 module.exports = app =>{
const { router, controller, jwt }
=app;

router.get(
'/', controller.home.index);
router.get(
'/news', controller.news.list);
router.post(
'/login', controller.users.login); //登录并生成Token router.resources('users', '/users', controller.users);
};

以上我们users 是RESTful 的方式来定义路由, 我们提供了
app.router.resources('routerName', 'pathMatch', controller)
快速在一个路径上生成
CRUD
路由结构。

类似RESTful定义

router.resources('posts', '/api/posts', controller.posts);

我们只需要在
posts.js
里面实现对应的函数就可以了。

我这里的users实现了上面部分的接口,以提供列表展示-L、创建-C、获取-R、更新-U、删除-D等操作。

app\controller\users.js

'use strict';

const Controller
= require('egg').Controller;//控制器类入口//实现路由几个常规函数,包括列表及CRUD的操作 class UserController extends Controller {

async index() {
//展示列表数据-L const ctx = this.ctx;const query={
limit: ctx.helper.parseInt(ctx.query.limit),
offset: ctx.helper.parseInt(ctx.query.offset),
};
var data =await ctx.service.user.list(query);var json =ctx.helper.json(data)
ctx.body
=json
}

async show() {
//显示某记录具体的数据-R const ctx = this.ctx;
ctx.body
=await ctx.service.user.find(ctx.helper.parseInt(ctx.params.id));
}

async create() {
//新增一个记录-C const ctx = this.ctx;
const user
=await ctx.service.user.create(ctx.request.body);
ctx.status
= 201;
ctx.body
=user;
}

async update() {
//更新指定的记录-U const ctx = this.ctx;
const id
=ctx.helper.parseInt(ctx.params.id);
const body
=ctx.request.body;
ctx.body
=await ctx.service.user.update({
id,
updates: body
});
}

async destroy() {
//删除指定的记录-D const ctx = this.ctx;
const id
=ctx.helper.parseInt(ctx.params.id);
await ctx.service.user.del(id);
ctx.status
= 200;
}
}

module.exports
= UserController;

这里UserController 控制器没有直接访问数据库,而是间接通过service对象进行操作数据库的。service中的user.js代码如下所示。

app\service\user.js

'use strict';

const Service
= require('egg').Service;//服务类入口,用于封装具体的数据库访问 class User extends Service {

async login(usernameOrEmail, password) {
var user = await this.ctx.model.User.findOne({
where: {
$or: [
{ username: usernameOrEmail },
{ emailaddress: usernameOrEmail }
]
}
});
var success = false;var error = "";if(user) {
success
= true}return{
success,
error
}
}

async list({ offset
= 0, limit = 10}) {return this.ctx.model.User.findAndCountAll({
offset,
limit,
order: [[
'creationtime', 'desc' ], [ 'id', 'desc']],
});
}

async find(id) {
const user
= await this.ctx.model.User.findByPk(id);if (!user) {this.ctx.throw(404, 'user not found');
}
returnuser;
}

async create(user) {
return this.ctx.model.User.create(user);
}

async update({ id, updates }) {
const user
= await this.ctx.model.User.findByPk(id);if (!user) {this.ctx.throw(404, 'user not found');
}
returnuser.update(updates);
}

async del(id) {
const user
= await this.ctx.model.User.findByPk(id);if (!user) {this.ctx.throw(404, 'user not found');
}
returnuser.destroy();
}
}

module.exports
= User;

而Service中,访问数据库主要通过
egg-sequelize
插件中提供的 this.ctx.model.User 对象进行操作数据库的

sequelize
是一个广泛使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。

app\model\user.js

'use strict';

module.exports
= app =>{
const { STRING, INTEGER, DATE }
=app.Sequelize;

const User
= app.model.define('abpusers', {
id: { type: INTEGER, primaryKey:
true, autoIncrement: true},
name: STRING(
64),
username: STRING(
64),
phonenumber: STRING(
64),
creationtime: DATE,
lastmodificationtime: DATE,
});
returnUser;
};

sequelize
定义了数据库不同的类型,它的类型定义如下所示。

Sequelize.STRING                      //VARCHAR(255)
Sequelize.STRING(1234)                //VARCHAR(1234)
Sequelize.STRING.BINARY               //VARCHAR BINARY
Sequelize.TEXT                        //TEXT
Sequelize.TEXT('tiny')                //TINYTEXT
Sequelize.CITEXT                      //CITEXT      PostgreSQL and SQLite only.
Sequelize.INTEGER//INTEGER
Sequelize.BIGINT                      //BIGINT
Sequelize.BIGINT(11)                  //BIGINT(11)
Sequelize.FLOAT//FLOAT
Sequelize.FLOAT(11)                   //FLOAT(11)
Sequelize.FLOAT(11, 10)               //FLOAT(11,10)
Sequelize.REAL//REAL        PostgreSQL only.
Sequelize.REAL(11)                    //REAL(11)    PostgreSQL only.
Sequelize.REAL(11, 12)                //REAL(11,12) PostgreSQL only.
Sequelize.DOUBLE//DOUBLE
Sequelize.DOUBLE(11)                  //DOUBLE(11)
Sequelize.DOUBLE(11, 10)              //DOUBLE(11,10)
Sequelize.DECIMAL//DECIMAL
Sequelize.DECIMAL(10, 2)              //DECIMAL(10,2)
Sequelize.DATE//DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.DATE(6)                     //DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
Sequelize.DATEONLY                    //DATE without time.
Sequelize.BOOLEAN                     //TINYINT(1)
Sequelize.ENUM('value 1', 'value 2')  //An ENUM with allowed values 'value 1' and 'value 2'
Sequelize.ARRAY(Sequelize.TEXT)       //Defines an array. PostgreSQL only.
Sequelize.ARRAY(Sequelize.ENUM)       //Defines an array of ENUM. PostgreSQL only.
Sequelize.JSON//JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB                       //JSONB column. PostgreSQL only.
Sequelize.BLOB//BLOB (bytea for PostgreSQL)
Sequelize.BLOB('tiny')                //TINYBLOB (bytea for PostgreSQL. Other options are medium and long)
Sequelize.UUID//UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically)
Sequelize.CIDR//CIDR datatype for PostgreSQL
Sequelize.INET                        //INET datatype for PostgreSQL
Sequelize.MACADDR                     //MACADDR datatype for PostgreSQL
Sequelize.RANGE(Sequelize.INTEGER)//Defines int4range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.BIGINT)     //Defined int8range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATE)       //Defines tstzrange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATEONLY)   //Defines daterange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DECIMAL)    //Defines numrange range. PostgreSQL only.
Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE))//Defines array of tstzrange ranges. PostgreSQL only.
Sequelize.GEOMETRY//Spatial column.  PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT')           //Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT', 4326)     //Spatial column with geometry type and SRID.  PostgreSQL (with PostGIS) or MySQL only.

关于它的接口,可以参考下文档
https://itbilu.com/nodejs/npm/sequelize-docs-v5.html
了解下。

另外,我们可以在app\extend\helper.js中定义一些常规的辅助函数,方便在控制器或者service对象中使用。

app\extend\helper.js

'use strict';
const moment
= require('moment');

module.exports
={
json(data, code, msg, addition) {
returnObject.assign({
result: code
? 'fail' : 'success',
code: code
|| 0,
message: msg,
data,
}, addition);
},
parseInt(string) {
if (typeof string === 'number') returnstring;if (!string) returnstring;return parseInt(string) || 0;
},

changeTime(time) {
return moment(time * 1000).format('YYYY-MM-DD HH:mm:ss');
},
relativeTime(time) {
return moment(new Date(time * 1000)).fromNow()
},

最后,我们使用npm run dev跑项目

测试下我们用户列表部分的处理。

其他CRUD接口,可以结合C#代码进行客户端的测试,也可以在一个新建的Vue+Element前端项目中进行axios的调用,获取对应的JSON进行测试。

在使用egg.js开发的时候,总体还是很方便,不过就是有时候一些拼写错误,或者一些配置原因,控制台 提示信息不是很明确,需要自己掌握各种排错的经验才行。

标签: none

添加新评论