什么是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. docker 支持systemctl start|stop|status等操作

    用docker运行centos7容器时候,无法使用systemctl,官方解释是centos7的一个bug,可以有修复的办法: 在docker run的时候,加上--privileged 并且cmd使 ...

  2. 爬虫入门三 scrapy

    title: 爬虫入门三 scrapy date: 2020-03-14 14:49:00 categories: python tags: crawler scrapy框架入门 1 scrapy简介 ...

  3. scu-4445

    Right turn frog is trapped in a maze. The maze is infinitely large and divided into grids. It also c ...

  4. Gym 101464C - 计算几何+二分(uva1463)

    不是很难,但是我觉得对代码能力的要求还是挺高的. 注意模块化. 因为是浮点数,所以二分用的很多很多. 参考 https://blog.csdn.net/njupt_lyy/article/detail ...

  5. HashMap三百问

    文章目录: 一.JDK1.7之HashMap 二.JDK1.8之HashMap 三.Hashtable JDK1.7之HashMap 1. 定义 HashMap实现了Map接口,继承AbstractM ...

  6. codeforces 2C(非原创)

    C. Commentator problem time limit per test 1 second memory limit per test 64 megabytes input standar ...

  7. macOS & PostgreSQL

    macOS & PostgreSQL macOS 上安装 PostgreSQL 后为什么会自动创建一个系统用户账号 https://get.enterprisedb.com/postgresq ...

  8. codepen iframe theme id

    codepen iframe theme id iframe css theme demos See the Pen css margin collapsing (1. 相邻兄弟元素) by xgqf ...

  9. HTML Custom Elements & valid name

    HTML Custom Elements & valid name valid custom element name https://html.spec.whatwg.org/multipa ...

  10. HTTP cache in depth

    HTTP cache in depth HTTP 缓存 https://developers.google.com/web/fundamentals/performance/optimizing-co ...