从0-1搭建基于egg的后台系统
本文涉及知识点
egg、mysql、sequelize、swagger
egg是什么?
Egg.js
是阿里开源的node
后端开发框架,为企业级框架和应用而生,可以帮助开发团队和开发人员降低开发和维护成本。
egg的特点
- 约定优于配置:目录结构清晰简洁,瞄一眼就知道该在哪里写哪类逻辑
- 高可扩展:内置对象、插件中间件的扩展都非常简单、清晰。
- 各种鉴权、安全、数据库支持、RESTful等该有的都有,且稳定可靠;
- 多进程Cluster很好的解决了
JavaScript
单线程的缺陷,大大提升了egg的性能。且Master、Agent、Worker
又很好的解决了进程间的通信问题。 - 开发容易上手、快速敏捷、插件多且成熟;部署简单、快速、体积小资、源消耗少;运行稳定可靠。
- 文档非常友好;
- 框架得到阿里内部大面积应用建设的考验。
起步
我们推荐使用脚手架,快速初始化
mkdir egg-example && cd egg-example
npm init egg --type=simple (一路回车)
npm i
- 项目结构及重要文件介绍
- egg插件配置
'use strict';
/** @type Egg.EggPlugin */
module.exports = {
// had enabled by egg
// static: {
// enable: true,
// }
};
- egg全局配置
/* eslint valid-jsdoc: "off" */
'use strict';
/**
* @param {Egg.EggAppInfo} appInfo app info
*/
module.exports = appInfo => {
/**
* built-in config
* @type {Egg.EggAppConfig}
**/
const config = exports = {};
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1640307948700_2928';
// add your middleware config here
config.middleware = [];
// add your user config here
const userConfig = {
// myAppName: 'egg',
};
return {
...config,
...userConfig,
};
};
- router入口
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
- controller
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.body = 'hi, egg';
}
}
module.exports = HomeController;
然后使用npm run dev
,就可以在浏览器中查看我们的服务了
项目前开发前准备工作(mac安装mysql)
在 Web 应用方面 MySQL 是最常见,最好的关系型数据库之一。非常多网站都选择 MySQL 作为网站数据库。
- mysql 安装
- 本机安装mysql(需要先安装homebrew)
brew install mysql
- 启动mysql
mysql.server start
- 免密登陆
mysql -u root
- 修改默认密码(8.0版本以上)
alter user 'root'@'localhost' identified with mysql_native_password by 'root';
- 控制行出现
Query OK, 0 rows affected (0.00 sec)
表示安装成功 exit
退出,并重新登陆mysql -u root -p
- 推荐使用图形化工具
navicat premium
管理我们的数据库 (百度搜索navicat premium安装)
egg项目中使用sequelize
egg提供
egg-mysql
插件直接操作数据库进行开发,而在一些较为复杂的应用中,我们可能会需要一个 ORM 框架来帮助我们管理数据层的代码。而在 Node.js 社区中,sequelize 是一个广泛使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。
安装并配置 egg-sequelize 插件(它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上)和 mysql2 模块:
npm install --save egg-sequelize mysql2
在 config/plugin.js 中引入 egg-sequelize 插件
exports.sequelize = {
enable: true,
package: 'egg-sequelize',
};
在 config/config.default.js 中编写 sequelize 配置
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'egg-sequelize-doc-default',
};
我们还可以在config下新建多个配置页面(如config/config.unittest.js ),不同的环境使用不同的数据源地址与数据库
exports.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'egg-sequelize-doc-unittest',
};
完成上面的配置之后,sequelize 的就初始化完成了。egg-sequelize 和 sequelize 还支持更多的配置项,可以在他们的文档中找到。
初始化数据库和 Migrations
接下来我们设计和初始化一下我们的数据库。首先我们通过 mysql 命令在本地快速创建开发和测试要用到的两个 database:
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-default`;'
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-unittest`;'
我们可以在数据库中直接创建表(不推荐)
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`name` varchar(30) DEFAULT NULL COMMENT 'user name',
`age` int(11) DEFAULT NULL COMMENT 'user age',
`created_at` datetime DEFAULT NULL COMMENT 'created time',
`updated_at` datetime DEFAULT NULL COMMENT 'updated time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='user';
大型项目都是需要多人协作的,项目迭代过程中最好是记录每一个迭代的数据变化,而且也会经常需要在不同的环境下进行切换,我们可以使用Migrations
来帮助我们进行管理。
sequelize 提供了 sequelize-cli 工具来实现 Migrations,我们也可以在 egg 项目中引入 sequelize-cli。
- 安装 sequelize-cli
npm install --save-dev sequelize-cli
- 在 egg 项目中,我们希望将所有数据库 Migrations 相关的内容都放在 database 目录下,所以我们在项目根目录下新建一个 .sequelizerc 配置文件
'use strict';
const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
- 初始化 Migrations 配置文件和目录
npx sequelize init:config
npx sequelize init:migrations
- 执行完后会生成 database/config.json 文件和 database/migrations 目录,我们修改一下 database/config.json 中的内容,将其改成我们项目中使用的数据库配置
{
"development": {
"username": "root",
"password": null,
"database": "egg-sequelize-doc-default",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "egg-sequelize-doc-unittest",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
此时 sequelize-cli 和相关的配置也都初始化好了,我们可以开始编写项目的第一个 Migration 文件来创建我们的一个 users 表了。npx sequelize migration:generate --name=init-users
执行完后会在 database/migrations 目录下生成一个 migration 文件(${timestamp}-init-users.js),我们修改它来处理初始化 users 表:
'use strict';
module.exports = {
// 在执行数据库升级时调用的函数,创建 users 表
up: async (queryInterface, Sequelize) => {
const { INTEGER, DATE, STRING } = Sequelize;
await queryInterface.createTable('users', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
},
// 在执行数据库降级时调用的函数,删除 users 表
down: async queryInterface => {
await queryInterface.dropTable('users');
},
};
databases/migrations中的文件写好之后,我们执行 migrate 进行数据库变更npx sequelize db:migrate
,此时,我们的数据库初始化就完成了。(这一系列代替我们手工创建)
model层逻辑编写
我们可以在app/model/目录下手工编写model层,但是如果数据库表多的话,写起来非常耗时间
- 使用第三方库自动生成model
npm i sequelize-automate -D
- 新建自动生成model的配置文件
'use strict';
module.exports = {
dbOptions: {
database: 'xxx',
username: 'root',
password: '',
dialect: 'mysql',
host: 'localhost',
port: 3306,
logging: false,
timezone: '+08:00',
dialectOptions: {
dateStrings: true,
typeCast: true,
},
define: {
// 时间戳
timestamps: true,
// 时间驼峰转换
underscored: true,
// 参数停止 Sequelize 执行自动复数化 表名= 模型名称
freezeTableName: true,
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci',
},
},
},
options: {
type: 'egg',
dir: 'app/model',
camelCase: true, // Models 文件中代码是否使用驼峰发命名
fileNameCamelCase: true, // Model 文件名是否使用驼峰法命名,默认文件名会使用表名,如 `user_post.js`;如果为 true,则文件名为 `userPost.js`
},
};
- package的script可以加一个钩子并且在创建完成后自动执行eslint格式化一下
"sequelize-automate -c sequelize-automate.config.js && eslint --ext .js app/model --fix
-
出现上图,则表示成功创建model,可以在我们代码的model中查看了
-
这里是我们model的说明
业务逻辑编写
我们再详细介绍一些app文件夹中的内容
router
: 用于配置 URL 路由规则(可以是文件夹也可以放在一个文件中)controller
:用于存放控制器 接收、校验、处理 HTTP 请求参数 向下调用服务(Service)处理业务 通过 HTTP 将结果响应给用户service
:用于编写业务逻辑层(可选,我们使用service层通过model模型,进行crud)model
:用于存放数据库模型(可选)middleware
: 用于编写中间件 (可选,我们可以添加gzip、慢接口查询等中间件)schedule
: 用于设置定时任务 (可选)public
: 用于放置静态资源 (可选)view
: 用于放置模板文件 (可选,服务端渲染)extend
: 用于框架的扩展 (可选,可以写公共的方法,比如获取当前时间,格式转换等)
基础controller示例
const Controller = require('egg').Controller;
class BaseController extends Controller {
get user() {
return this.ctx.session.user;
}
success(data, msg = '') {
this.ctx.body = { msg, data, code: 200 };
}
error(msg = '', code = -1) {
this.ctx.body = { msg, code };
}
notFound(msg) {
this.ctx.throw(404, msg || 'not found');
}
}
module.exports = BaseController;
业务controller示例
const Controller = require('../core/baseController');
class UserController extends Controller {
async getUser() {
const { ctx } = this;
const params = ctx.request.body;
try {
const data = await ctx.service.user.find(params);
this.success(data);
} catch (e) {
this.error(e.message);
}
}
业务service示例
// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
async find(params) {
const { ctx: { model } } = this;
const startPage = params && params.startPage || 1;
const pageSize = params && params.pageSize || 10;
// 默认查找生效数据
const where = { state: '1' };
// 使用sequelize查找器查找(推荐)
const { count: total, rows } = await model.User.findAndCountAll({
where, // WHERE 条件
orders: [[ 'publishTime', 'desc' ]], // 排序方式
limit: pageSize, // 返回数据量
offset: (startPage - 1) * pageSize, // 数据偏移量
});
return {
total,
list: rows,
};
}
}
}
module.exports = UserService;
至此,这个项目的业务逻辑部分就写完了
swagger
一个完成的项目还需要接口文档,我们使用swagger-egg自动生成
- 安装
npm i swagger-egg
- egg配置与egg插件配置
- controller中加上注释及schema使用
redis引入
服务端复杂的数据库查询会严重降低访问速度,我们需要将一些常用的接口数据进行存储,用户在获取数据的时候,直接将数据从内存中读取,不进行数据库查询,可以大大提高服务器性能。存储方式主要有文件存储和内存存储,文件存储通过fs模块写入读取txt文件,不多说;这里使用redis内存存储。
- 安装
brew install redis
- 配置文件路径:
vim /usr/local/etc/redis.conf
- 手动启动和停止redis:
brew services restart redis
- 启动成功消息:
==> Successfully started
redis(label: homebrew.mxcl.redis)
- 配置文件和插件配置好之后
service
层这样使用(建议封装成RedisService使用)
// 方式一:直接使用
await this.app.redis.set('userInfo', 'xx');
let userInfo = await this.app.redis.get('userInfo');
// 方式二:通过service使用
let userInfo = await this.service.redis.get('userInfo');
if (!userInfo) {
// @TODO 通过数据库获取
const sqlData = await model.user.find('userInfo')
this.service.redis.set('userInfo', sqlData)
}
结尾
基于以上的步骤我们应用就建好了,生产环境直接start运行即可。
后面有时间会讲下环境部署与配置,docker-compose
构建多个服务,docker-swarm
& k8s
部署多台服务器,修改 nginx
,实现负载均衡
参考链接
sequelize中文文档
www.sequelize.com.cn/
egg中文文档
eggjs.org/zh-cn/
sequelize-automate
github.com/nodejh/sequ…
egg-sequelize
github.com/eggjs/egg-s…
egg-redis
github.com/eggjs/egg-r…
转载自:https://juejin.cn/post/7125817342897946632