前言
本期我们将从0开始搭建一个小型的具备crud功能的web服务,并且不只是在本地开发,而是部署到真实线上服务器。语言使用node.js,无语言切换成本,适合前端入门。
这期我们期望实现的效果如下:
其中web服务能够支持以下功能:
- 增、删、改、查;
- 查询支持模糊匹配、按更新时间倒序排序;
准备工具
- 一台centsOS的腾讯云服务器
- MySQLWorkbench
服务器安装MySQL
首先我们通过ssh进入自己的服务器
ssh root@110.42.172.19 # 购买服务器后会自动创建一个root用户,把这个ip换成你自己的服务器公网ip即可
然后查看本机系统版本,以确定下载哪个版本的mySQL
cat /etc/redhat-release
比如像我本机是Linux 8的,进入dev.mysql.com/downloads/r… 页面,找到Linux8对应的rpm包地址
开始下载并安装rpm包:
wget https://dev.mysql.com/get/mysql80-community-release-el8-4.noarch.rpm # 这里要拼接一下url
rpm -ivh mysql80-community-release-el8-4.noarch.rpm
然后下载mysql服务器
yum install mysql-server # 这一步会比较漫长,耐心等待就好
服务器MySQL启动与初始化设置
下载完成后,我们开始启动mysql:
systemctl start mysqld.service
启动完成后,我们可以通过systemctl status mysqld.service
命令来查看mysql是否已经成功启动,如果看到如下的running小绿点,说明mysql已经成功启动了。
密码重置
接下来我们要进入mysql,进行root用户的密码重置:
mysql -u root -p # 初次进入是没有密码的,直接回车就可以进去
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '12345'; # 把12345换成你自己期望修改的密码即可
这里之所以使用mysql_native_password
来修改是因为我们安装的是mysql8,但是我们的node.js项目大部分不支持mysql8的最新默认加密方式,所以需要显式地指定加密方式。
允许外部连接
mysql本身只支持本地连接,如果要让外部也能访问,需要在默认的mysql数据进行如下配置:
show databases; # 这里会展示默认的几个数据库,mysql就是其中之一
use mysql; # 进入mysql数据库
select host from user where user='root'; # 查看host,默认值为localhost
update user set host = '%' where user ='root'; # 修改host的值
flush privileges; # 刷新配置
完成设置后,通过以下命令退出mysql命令行:
exit;
注意,mysql命令行里面的指令最好显示在末尾添加分号,告诉mysql指令已经输完
本机连接MySQL,建库建表
由于笔者用的是macOS系统,所以暂时没发现怎么像windows一样通过打补丁的形式使用盗版Navicat,无奈选择了MySQLWorkbench这个软件,虽然界面不好看,但是能用。
进入MySQLWorkbench,新建对数据库的连接。
但是当我们输入密码后,做连接测试的却发现连接失败,聪明的我们很快想到是防火墙的问题,于是我们登录腾讯云官网,为3306端口放行。
连接成功后,我们开始可以愉快滴建库建表了:
比如这里,我建立的库名为easy-server-main
,里面包含一张名为device_controllers
的表。
- 建库(database),输个库名就行;
- 建表(Table),除了输入个名字以外,还需要对字段类型进行定义。
这里包括定义主键、为字段选择整型/字符串/日期类型,需要对mysql有一丢丢基础了解即可。简单来说,一张表就类似于我们WPS里面的excel表格。
初始化egg.js项目
建好了库表之后,我们开始使用egg.js在电脑本地初始化一个项目:
cd ~/tech # 换成你自己电脑的任意文件地址即可
mkdir easy-server && cd easy-server
npm init egg --type=simple # 模版选择simple即可
yarn
这里使用egg.js的原因有两个:
- egg.js是阿里开发,有完善的中文文档,查文档方便;
- egg.js使用起来非常简单。
通过yarn安装完依赖后,在项目终端输入yarn dev
,打开http://127.0.0.1:7001/
即可看到一个默认的接口返回。
但是我们的目的不只于此,我们需要连接到远程的数据库,所以我们在本地安装egg-sequelize
:
yarn add egg-sequelize
这里解释一下为什么没有用egg官方提供的egg-mysql作为连接工具,因为egg-mysql本身功能支持得比较少,遇到一些稍微复杂的查询是hold不住的,所以选用基于sequelize的egg-sequelize是一个非常稳健的选择。
在config/config.default.js
中加入以下配置:
config.sequelize = {
dialect: 'mysql',
host: '110.42.172.19',
port: 3306,
database: 'easy-server-main',
username: 'root',
password: '12345',
timezone: '+8:00', // 时区改成东8区
};
在config/plugin.js
中加入如下配置:
sequelize: {
enable: true,
package: 'egg-sequelize',
},
这样就完成了服务端项目对远程数据库的连接。
controller、model、service的关系
接下来我们会来写业务接口,传统的接口业务开发,一般由三部分组成:
-
model
,包含对mysql具体表结构的定义; -
service
,直接使用model对表进行数据查询,具体的查询逻辑可以写在这里; -
controller
,接收前端的入参,并调用相关的service方法,返回结果给前端;
使用ORM (egg-sequelize) 开发接口与测试
了解完这几个概念后,我们现在model层定义要连接的表:
module.exports = app => {
const { STRING, INTEGER } = app.Sequelize;
const Main = app.model.define(
'device_controller',
{
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
active: INTEGER,
ip: STRING(20),
name: STRING(50),
},
{ timestamps: true }
);
return Main;
};
在controller部分针对增删改查各写一个接口:
const Controller = require('egg').Controller;
class HomeController extends Controller {
// 获取控制器列表
async getDeviceControllerList() {
const { ctx } = this;
const res = await ctx.service.home.getDeviceControllerList(ctx.query);
ctx.body = res;
}
// 新增控制器
async addNewDeviceController() {
const { ctx } = this;
const res = await ctx.service.home.addNewDeviceController(ctx.request.body);
ctx.body = res;
}
// 编辑控制器
async updateDeviceController() {
const { ctx } = this;
const res = await ctx.service.home.updateDeviceController(ctx.request.body);
ctx.body = res;
}
// 删除控制器
async removeDeviceController() {
const { ctx } = this;
const res = await ctx.service.home.removeDeviceController(
ctx.request.body.id
);
ctx.body = res;
}
}
以及在service层书写具体查询逻辑:
const Service = require('egg').Service;
const Op = require('sequelize').Op;
const { isNil } = require('lodash');
const getResponseBody = result => {
return {
code: 200,
data: result,
msg: 'success',
};
};
class HomeService extends Service {
async getDeviceControllerList(query) {
const { ctx } = this;
const { active, ip, name } = query;
const params = {};
if (!isNil(active)) {
params.active = active;
}
if (ip) {
params.ip = {
[Op.like]: `%${ip}%`,
};
}
if (name) {
params.name = {
[Op.like]: `%${name}%`, // 模糊查询的写法
};
}
const res = await ctx.model.Home.findAll({
where: params,
order: [[ 'updated_at', 'DESC' ]], // 按更新时间倒序排
});
return getResponseBody(res || []);
}
async addNewDeviceController(body) {
const { ctx } = this;
const { active, ip, name } = body;
await ctx.model.Home.create({ active, ip, name });
return getResponseBody();
}
async updateDeviceController(record) {
const { ctx } = this;
const { id, ip, name, active } = record || {};
await ctx.model.Home.update(
{ ip, name, active },
{
where: { id },
}
);
return getResponseBody();
}
async removeDeviceController(id) {
const { ctx } = this;
await ctx.model.Home.destroy({
where: { id },
});
return getResponseBody();
}
}
module.exports = HomeService;
最后,在路由文件router.js
中将我们写好的接口注册上去就大功告成了:
module.exports = app => {
const { router, controller } = app;
router.get('/device_controller/list', controller.home.getDeviceControllerList);
router.post('/device_controller/add', controller.home.addNewDeviceController);
router.post(
'/device_controller/update',
controller.home.updateDeviceController
);
router.post(
'/device_controller/remove',
controller.home.removeDeviceController
);
};
配置跨域
要让前端项目访问到,还需要配置跨域,我们在项目中安装egg-cors
这个插件即可:
yarn add egg-cors
因为我们这次只是做个CRUD的服务示例,没有涉及到权限、认证等环节,在config/config.default.js
加入如下配置:
config.security = {
csrf: {
enable: false,
},
};
config.cors = {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST',
};
然后在config/plugin.js
中使用:
cors: {
enable: true,
package: 'egg-cors',
},
部署与启动
在本地,我们可以使用postman进行接口测试,测试完毕后手动部署到远程服务器:
因为我们打算使用PM2来做进程管理,所以先在根目录增加serve.js文件,书写内容如下:
const egg = require("egg");
const workers = Number(process.argv[2] || require("os").cpus().length);
egg.startCluster({
workers,
baseDir: __dirname,
});
然后进入终端,将项目压缩后传到远程服务器:
tar -zcvf ./release.tgz . # 压缩项目文件
scp ./release.tgz root@110.42.172.19:/usr/backend/easy-server # 通过scp传到远程服务器,具体存放地址看个人喜欢
重新通过ssh登录远程服务器,如果没有npm的话,通过如下命令安装npm:
yum -y install npm
然后再通过npm全局安装pm2
npm install pm2 -g
进入我们刚刚传上来压缩文件的目录/usr/backend/easy-server
,通过如下tar命令进行解压到当前目录:
tar -zxf release.tgz ./
然后通过pm2启动项目:
pm2 start server.js --name easy-server
可以通过pm2 list
来查看当前应用的运行状况。
至此,一切已经大功告成,效果如下:
源码地址
前端(前端是用umi3搭的,基于react,用vue的同学自行用vue项目测试即可):github.com/zhangnan24/…
服务端(数据库mysql密码已改):github.com/zhangnan24/…