前言
作为前端工程师的我们,经常想打破前端的次元壁(不想只是写页面调接口辣),想去学习一门后端语言,建立起自己的服务,往全栈方向冲冲冲。那么个人觉得,没有比 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哟~