什么是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'
}; //........其他配置............... return config;
};

为了给前端提供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');
}
return user;
} 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');
}
return user.update(updates);
} async del(id) {
const user = await this.ctx.model.User.findByPk(id);
if (!user) {
this.ctx.throw(404, 'user not found');
}
return user.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,
}); return User;
};

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) {
return Object.assign({
result: code ? 'fail' : 'success',
code: code || 0,
message: msg,
data,
}, addition);
},
parseInt(string) {
if (typeof string === 'number') return string;
if (!string) return string;
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开发的时候,总体还是很方便,不过就是有时候一些拼写错误,或者一些配置原因,控制台 提示信息不是很明确,需要自己掌握各种排错的经验才行。

使用egg.js开发后端API接口系统的更多相关文章

  1. Spring Boot入门(四):开发Web Api接口常用注解总结

    本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 在程序员的日常工作中,Web开发应该是占比很重的一部分,至少我工作以来,开发的系统基本都是Web端访问的 ...

  2. 后端API接口的错误信息返回规范

    前言 最近我司要制定开发规范.在讨论接口返回的时候,后端的同事询问我们前端,错误信息的返回,前端有什么意见? 所以做了一些调研给到后端的同事做参考. 错误信息返回 在使用API时无可避免地会因为各种情 ...

  3. 如何使用 Python 编写后端 API 接口

    如何使用 Python 编写后端 API 接口 get API Python3 # coding:utf-8 import json # ModuleNotFoundError: No module ...

  4. 基于 Laravel、Vue.js开发的全新社交系统----ThinkSNS+

    什么是ThinkSNS+ ThinkSNS(简称TS)始于2008年,一款全平台综合性社交系统,为国内外大中小企业和创业者提供社会化软件研发及技术解决方案,目前最新版本为ThinkSNS+.新的产品名 ...

  5. 使用RAP2和Mock.JS实现Web API接口的数据模拟和测试

    最近一直在思考如何对Web API的其接口数据进行独立开发的问题,随着Web API的越来越广泛应用,很多开发也要求前端后端分离,例如统一的Web API接口后,Winform团队.Web前端团队.微 ...

  6. 浅谈PHP与手机APP开发即API接口开发

    API(Application Programming Interface,应用程序接口)架构,已经成为目前互联网产品开发中常见的软件架构模式,并且诞生很多专门API服务的公司,如:聚合数据(http ...

  7. Nuxt+Express后端api接口配置与实现方式

    Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用.本文带你了解在 Nuxt.js 中使用 Express 如何编写实现后端的 api 接口. 创建接口 ...

  8. 浅谈使用 PHP 进行手机 APP 开发(API 接口开发)

    做过 API 的人应该了解,其实开发 API 比开发 WEB 更简洁,但可能逻辑更复杂,因为 API 其实就是数据输出,不用呈现页面,所以也就不存在 MVC(API 只有 M 和 C),那么我们来探讨 ...

  9. 准备开发开放API接口

    准备开发APP开放接口,允许JQUERY直接调用http://blog.csdn.net/wuxiangege/article/details/52238968 SIGN的设计与实现http://bl ...

随机推荐

  1. 数位dp整理 && 例题HDU - 2089 不要62 && 例题 HDU - 3555 Bomb

    数位dp: 数位dp是一种计数用的dp,一般就是要统计一个区间[li,ri]内满足一些条件数的个数.所谓数位dp,字面意思就是在数位上进行dp.数位的含义:一个数有个位.十位.百位.千位......数 ...

  2. centos 7下安装配置Supervisor

    1.安装Supervisor centos下安装yum install supervisor 2. systemctl enable supervisord 开机自启 systemctl start ...

  3. [Golang]-8 工作池、速率限制、原子计数器、互斥锁

    目录 工作池 速率限制 原子计数器 互斥锁 工作池 在这个例子中,我们将看到如何使用 Go 协程和通道实现一个工作池 . func worker(id int, jobs <-chan int, ...

  4. Nginx基础 - 配置代理web服务

    1.反向代理及负载均衡Nginx实现负载均衡用到了proxy_pass代理模块核心配置,将客户端请求代理转发至一组upstream虚拟服务池. 1)upstream配置语法 Syntax: upstr ...

  5. OpenStack Train版-1.安装基础环境&服务

    1. 服务组件的密码 密码名称 描述 ADMIN_PASS admin用户密码 CINDER_DBPASS 块设备存储服务的数据库密码 CINDER_PASS 块设备存储服务的 cinder 密码 D ...

  6. woj1018(HDU4384)KING KONG 循环群

    title: woj1018(HDU4384)KING KONG 循环群 date: 2020-03-19 09:43:00 categories: [acm] tags: [acm,woj,数学] ...

  7. 操作系统 part5

    1.线程安全 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者数据污染. 线程不安全就 ...

  8. 51nod1089 最长回文子串 manacher算法

    0. 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一个字符串正着读和反着读是一样的,那它就是回文串.下面是一些回文串的实例: 12321 a aba abba aaaa ...

  9. XSS脚本汇总

    (1)普通的XSS JavaScript注入<SCRIPT SRC=http://***/XSS/xss.js></SCRIPT> (2)IMG标签XSS使用JavaScrip ...

  10. USB2.0协议学习笔记---USB数据包结构

    USB包类型和传输过程  USB是一种串行总线,因此数据都是一位一位传输的,如同串口那样,但是USB在真实物理电路上却不是TTL电平,而是一种差分信号采用NRZI编码,就是用变化表示0,不变表示1,同 ...