前言
作为前端工程师的我们,经常想打破前端的次元壁(不想只是写页面调接口辣),想去学习一门后端语言,建立起自己的服务,往全栈方向冲冲冲。那么个人觉得,没有比 Node.js
更合适我们的了吧。没有学习新语言的成本,但是后端之路个人感觉不是会写简单的接口就可以了, Node.js
虽然不需要我们去学习一门新的语言,但它里面的思想如果学习者没有一些 OS
、网络、数据库相关的知识,也是一知半解的。
兴趣作为驱动,咱们先来构建自己的服务,写出自己的 Restful
接口找到自信心之后,再去深入学习吧!
对于 Express
,官网如是描述:
基于
Node.js
平台,快速、开放、极简的Web
开发框架
麻雀虽小五脏俱全,保留了最小规模的灵活的 Node.js
开发框架。那么今天咱们就一起盘一盘它,构建属于咱们自己的服务。
PS:第一第二节不会描述过多原理性的东西,所有的原理咱们放在第三节实现自己的
Express
中再来讲解
安装
工欲善其事必先利其器,在开发之前咱们先来安装好该有的东西。官网中通过生成器工具来快速构建项目,生成的工程中已经包含好该有的所有依赖。
npm install -g express-generator
安装好之后就可以初始化一个 Express
应用了。
express express-app
工程目录一览
express-app
│ app.js
│ package.json
│
├─bin
│ www
│
├─public
│ ├─images
│ ├─javascripts
│ └─stylesheets
│ style.css
│
├─routes
│ index.js
│ users.js
│
└─views
error.jade
index.jade
layout.jade
app.js
应用初始化文件,包括引入所有应用的依赖项、设置视图模板引擎、静态资源路径、引入路由、配置中间件等bin/www
启动文件,设置监听端口、启动http
服务等public
静态文件目录routes
路由文件,响应用户的http
请求并返回结果views
视图文件
接口开发
在正式开发之前,咱们先跑一下工程自带的代码。
Hello Express
启动程序
cd bin
node www
PS:在 www
里面定义了如下,默认不改动的话程序就处于 3000
端口
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
启动完之后打开 localhost:3000
,即可看到欢迎页面。
启动流程
- 首先
Node.js
启动了http
服务器 Express
内部实现了路由分发,根据路由方法和路径分发到具体的controller
中的action
逻辑- 输入
localhost:3000
那么路由就是 ‘/’ ,在app.js
中有如下两句代码
对应的是路由组的注册,最后匹配到的就是app.use('/', indexRouter); app.use('/users', usersRouter);
routes/index.js
里面的方法router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); });
- 路由里面渲染了一个静态页面
index
,并把模板变量传了进去
对应的便是所看到的的欢迎页面。extends layout block content h1= title p Welcome to #{title}
连接数据库
在开发之前,还是得新建一个数据库,本文用的是 MySQL
。先安装好 MySQL
驱动。
npm install mysql --save
根目录下新建一个 db
文件夹,目录结构如下
db
│ dbConfig.js
│ index.js
index.js
就是我们自己写的 ORM
类了。下文的 MySQL
操作都会基于它,感兴趣的同学可以移步为Node.js加一个DB类
dbConfig.js
中写入 MySQL
配置
module.exports = {
mysql: {
host: 'localhost',
user: 'root',
password: '******',
database: 'express_test',
port: 3306
}
};
接下来创建一个数据库
CREATE DATABASE IF NOT EXISTS express_test;
再来创建一个 user
表
USE express_test;
CREATE TABLE IF NOT EXISTS user (
u_id INT PRIMARY KEY AUTO_INCREMENT,
u_account VARCHAR(100) NOT NULL,
u_password VARCHAR(100) NOT NULL
);
app.js
中加入如下代码
const Db = require('./db/index')
global.Db = new Db.Orm();
Restful API开发
有了上面的准备工作后,就可以进行我们的接口开发了。我们开发两个接口,登录接口和注册接口。登录接口与注册接口传的参数如下
参数 | 必选 | 类型 | 说明 |
---|---|---|---|
account | true | string | 用户名 |
password | true | string | 密码 |
注册接口
在 routes/user.js
中新增如下代码:
- 获取
account
、password
参数 - 对参数做一些校验
- 是否用户已存在
- 是否数据插入成功
function validate(account, password) {
if (!account) {
return {
status: false,
msg: '用户名不可为空'
}
}
if (!password) {
return {
status: false,
msg: '密码不可为空'
}
}
if (account.length > 24) {
return {
status: false,
msg: '用户名不可大于24位'
}
}
if (password.length > 24) {
return {
status: false,
msg: '密码不可大于24位'
}
}
return {
status: true,
msg: null
}
}
router.post('/register', async (req, res, next) => {
const {
account,
password
} = req.body
let userValidate = validate(account, password),
result, user
if (!userValidate.status) {
res.end(userValidate.msg)
return
}
let result
try {
result = await Db.table('user').where('u_account', account).count()
} catch (error) {
throw new Error(error)
}
if (result > 0) {
res.end('该用户名已存在')
return
} else {
let data = {
u_account: account,
u_password: md5(password)
}
let user
try {
user = await Db.table('user').insert(data)
} catch (error) {
throw new Error(error)
}
if (user.affectedRows > 0) {
res.json({
msg: '注册成功',
status: true
})
} else {
res.end('error');
}
}
})
附带一个 Node.js
的 md5
实现
function md5(data) {
var Buffer = require("buffer").Buffer;
var buf = new Buffer.from(data);
var str = buf.toString("binary");
var crypto = require("crypto");
return crypto.createHash("md5WithRSAEncryption").update(str).digest("hex");
}
登录接口
实现了注册接口之后,咱们再来实现登录接口。如法炮制的在 routes/users.js
加上如下代码:
- 获取参数并校验
- 根据用户传入的
account
去查询 md5
用户的密码后对比- 登录后的登录态咱们放在第二节来讲叭
router.post('/login', async (req, res, next) => {
const {
account,
password
} = req.body
let userValidate = validate(account, password),
result, dbPassword
if (!userValidate.status) {
res.end(userValidate.msg)
return
}
try {
result = await Db.table('user').where('u_account', account).find();
} catch (error) {
throw new Error(error)
}
dbPassword = result.length > 0 ? result[0].u_password : null
if (dbPassword === md5(password)) {
res.json({
msg: '登陆成功',
status: true
})
} else {
res.json({
msg: '请检查用户名或密码',
status: false
})
}
})
上传文件
上传文件也是我们在开发接口的时候常用的功能,接下来就用 Node
实现一个上传文件的功能。这里我们用到一个中间件-- Multer
。
Multer
是一个 node.js
中间件,用于处理 multipart/form-data
类型的表单数据,它主要用于上传文件。先来安装它: npm install multer --save
。在 routes/users.js
加入如下路由:
- 使用了
multer
之后,文件信息放在了req.files
里面 - 基于
Promise
封装一个上传单个文件的方法 - 然后用
Promise.all
统一管理返回 - 在读写文件时若发生错误,这里的处理是
resolve(err)
而不是reject(err)
,保证Promise.all
调用成功,至于上传失败的错误提示,就在前台展示吧~
var multer = require('multer');
var fs = require('fs')
var upload = multer({
dest: '../public/upload_tmp/'
});
router.post('/upload', upload.any(), (req, res) => {
let files = [...req.files],
pros = []
files.forEach(file => pros.push(uploadSingleFile(file)))
Promise.all(pros).then(uploadFiles => {
res.json(uploadFiles)
}).catch(err => {
res.json(err)
})
})
function uploadSingleFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file.path, (err, data) => {
if (err) {
resolve(err)
} else {
let timestamp = +new Date()
let path = `../public/images/${timestamp}-${file.originalname}`
fs.writeFile(path, data, err => {
if (err) {
resolve(err)
} else {
resolve({
originalname: file.originalname,
filename: `${timestamp}-${file.originalname}`
})
}
})
}
})
})
}
访问静态文件
上传完文件之后该如何访问呢,Express
也已经实现了静态文件访问逻辑。 app.js
中有这么一句代码 app.use(express.static(path.join(__dirname, 'public')));
所有我们只要知道文件名字,如下访问即可,浏览器地址输入以下:
http://localhost:3000/images/xxx.png
即可访问到我们上传的文件~
PM2
PM2
是 node
进程管理工具,可以利用它来简化很多 node
应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。
常用命令有如下:
$ pm2 start app.js # 启动app.js应用程序
$ pm2 start app.js -i 4 # cluster mode 模式启动4个app.js的应用实例
# 4个应用程序会自动进行负载均衡
$ pm2 start app.js --name="api" # 启动应用程序并命名为 "api"
$ pm2 start app.js --watch # 当文件变化时自动重启应用
$ pm2 start script.sh # 启动 bash 脚本
$ pm2 list # 列表 PM2 启动的所有的应用程序
$ pm2 monit # 显示每个应用程序的CPU和内存占用情况
$ pm2 show [app-name] # 显示应用程序的所有信息
$ pm2 logs # 显示所有应用程序的日志
$ pm2 logs [app-name] # 显示指定应用程序的日志
pm2 flush
$ pm2 stop all # 停止所有的应用程序
$ pm2 stop 0 # 停止 id为 0的指定应用程序
$ pm2 restart all # 重启所有应用
$ pm2 reload all # 重启 cluster mode下的所有应用
$ pm2 gracefulReload all # Graceful reload all apps in cluster mode
$ pm2 delete all # 关闭并删除所有应用
$ pm2 delete 0 # 删除指定应用 id 0
$ pm2 scale api 10 # 把名字叫api的应用扩展到10个实例
$ pm2 reset [app-name] # 重置重启数量
$ pm2 startup # 创建开机自启动命令
$ pm2 save # 保存当前应用列表
$ pm2 resurrect # 重新加载保存的应用列表
$ pm2 update # Save processes, kill PM2 and restore processes
$ pm2 generate # Generate a sample json configuration file
pm2 start app.js --node-args="--max-old-space-size=1024"
安装了 pm2
之后,我们可以这样启动:pm2 start www -i max
nodemon
开发的时候,需要频繁的重启代码,十分繁琐。我们可以使用 nodemon
这个工具,它的作用是监听代码文件的变动,当代码改变之后,自动重启。
npm install -g nodemon
nodemon www
最后
个人觉得前端到后端的跨越不仅仅是语言上的差异,语法上的差异是可以很快弥补的。而缺少的更多是对数据库的设计、对后台逻辑的处理、对资源的处理等思维。仅以此文,抛砖引玉,前端之路漫漫,吾将上下而求索。
行文至此,感谢阅读,如果您喜欢的话,可以帮忙点个like哟~