使用express+mysql+multer+pm2做后端的个人项目部分总结
6月伊始有朋友问我能否做一个类商城性质的小程序。我思考许久,本想推脱,但她言辞恳切,几番波折后,决定为她做出这个小程序,一方面是出于帮助,另一方面,则是为了使用node单独完成整个后端的搭建。然事之所成,非心之所至,乃行之所至。幸而业内才俊如过江之鲫,诸君硕果亦浩若烟云。不才引江海而成细流,故终有所成,亦不负初衷。几经思虑,以文记之。
简述
项目之初,与友人沟通,其言语颇泛。故甲方之言多以为象。象者,形也。然我等之所为,化虚为实,造意成象。若甲方之象无误,那便事成。若甲方自己尚且难以名状,那我等说不得要绞尽脑汁,几经思虑了。甚至返工,重做亦有可能。
此项目,为虚拟产品经营。我以express+mysql+multer+pm2为技术框架。
express:express为基于 Node.js 平台,快速、开放、极简的 Web 开发框架。其上手极为简单,初用此框架,我便想到了PHP的thinkphp框架,两者都是开箱即用,新手上手很快,若有前端小伙伴想玩票Node.js开发,可以使用此框架练习练习。
pm2:pm2是一个基于Node.js的守护进程管理器,可以协助管理和守护应用程序。上手同样很快,若是使用Node.js程序的话,推荐使用pm2做守护进程,当然也可以配合docker使用
mysql依赖包:mysql依赖包为一个基于Node.js的一个mysql驱动程序。上手难度同样不高,只需了解下mysql的语法,以及阅读下mysql依赖包的文档即可。
multer:multer是一个用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。其学习难度同样很简单。
基础封装
做项目与做事情是一样的道理,做事情要有方法,而做项目自然要有章法。此项目之中,最大的三个部分则是数据读写、业务逻辑、接口抛出,基于此三块则分别进行数据层,业务层,请求层的封装,数据层与请求层以AOP的形式进行统一封装,在业务层进行具体逻辑的使用与调用即可,如此一来,若有基于整体业务逻辑,或单个业务逻辑的调整也较为方便,使用一个策略模式即可。
数据层封装
数据层内,首先自然是要引入mysql依赖包了,之后便是基于mysql依赖包封装链接的建立与数据库的操作方法。
第一步,建立一个Mysql的类
const mysql = require("mysql");
class Mysql {
constructor() {
this.connection = {}
this.createConnection()
}
}
第二步,在类中声明链接数据库的方法
createConnection() {
let connection = {
host: '******',//数据库地址
user: '******',//登录账号
password: '******',//登录密码
database: '******'//链接的库表
}
this.connection = mysql.createConnection(connection)
}
第三步,进行数据库连接,语句执行与链接关闭的方法封装
// 数据连接
connect(name, sql, fn) {
this.createConnection()
this.connection.connect()
this.connection[name](sql, (err, rows, fields) => {
if (err) throw err
return fn(rows, fields)
})
this.connection.end()
}
// 数据库语句执行
query(sql, fn) {
return this.connect('query', sql, fn)
}
第四步,封装各类数据库语句方法,其例如下
// 数据库查询所有数据的方法
selectAll(name, fn) {
let sql = `SELECT * FROM ${name}`
this.query(sql, (row, field) => {
let arr = this.switch(name, row)
fn(arr, field)
})
}
请求层封装
请求层内,可封装关于请求头的拦截以及各类请求的方法。
首先,自然是进行请求公共部分的封装了,在此父类中,可进行请求头等公共部分的封装。
const multer = require("multer")
const path = require("path")
var express = require('express');
var router = express.Router();
class RouterController {
constructor(params = {}) {
}
}
第二步,则是封装各类请求,get请求、post请求、upload请求等。
get请求,封装如下:
//GET请求
class GetRouter extends RouterController {
constructor(params = {}) {
super(params)
}
send(path, fn) {
router.get(path, (req, res, next) => {
let openid = req.headers.openid ? req.headers.openid : req.query.openid
let { bool, code } = filterBool(path, req.query)
if (bool == false) {
res.send(code)
return
}
if (this.require.length === 0) {
return fn(res, { ...req.query, openid })
}
let examine = this.require.reduce((current, item) => {
if (req.query[item.key]) {
current.success[item.key] = req.query[item.key]
} else {
current.error.push(item)
}
return current
}, { error: [], success: { ...req.query, openid } })
if (examine.error.length > 0) {
res.send({ warn: examine.error })
} else {
fn(res, examine.success)
}
})
}
}
post请求,封装如下:
//POST请求
class PostRouter extends RouterController {
constructor(params = {}) {
super(params)
}
send(path, fn) {
router.post(path, (req, res, next) => {
let openid = req.headers.openid ? req.headers.openid : req.body.openid
let examine = this.require.reduce((current, item) => {
if (req.body[item.key]) {
current.success[item.key] = req.body[item.key]
} else {
current.error.push(item)
}
return current
}, { error: [], success: { ...req.body, openid } })
if (examine.error.length > 0) {
res.send({ warn: examine.error })
} else {
fn(res, examine.success)
}
})
}
}
upload请求,封装如下:
class Upload extends RouterController {
constructor(params = {}) {
super(params)
this.http = "*****"
this.load = this.multer()
this.url = params.url || "../../downloads/tieQuan"
this.name = params.name || "file"
this.format = this.filter(params.type)
}
filter(arr = ["*"]) {
let typeFilter = {
image: ['image/jpeg', 'image/png'],
}
return (val) => {
return arr.map(item => {
if (typeFilter[item]) {
return typeFilter[item].indexOf(val) > -1
}
return true
}).filter(item => item).length > 0
}
}
multer() {
var storage = multer.diskStorage({
// 配置文件上传后存储的路径
destination: (req, file, cb) => {
if (this.format(file.mimetype)) {
// NodeJS的两个全局变量
cb(null, "C:/serve/image")
} else {
cb({ error: 'Mime type not supported' })
}
},
// 配置文件上传后存储的路径和文件名
filename: function (req, file, cb) {
cb(null, Date.now() + path.extname(file.originalname))
}
})
return multer({ storage: storage })
}
sendField(path, fn) {
const cpUpload = this.load.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery',maxCount: 8 }])
router.post(path, cpUpload, (req, res, next) => {
if (fn) {
fn(res, req.body)
}
});
}
sendList(path, fn) {
router.post(path, this.load.array('files', 10), (req, res, next) => {
if (fn) {
fn(res, req.body)
}
});
}
send(path, fn) {
router.post(path, this.load.single(this.name), (req, res, next) => {
let examine = this.require.reduce((current, item) => {
if (req.body[item.key]) {
current.success[item.key] = req.query[item.key]
} else {
current.error.push(item)
}
return current
}, { error: [], success: {} })
if (examine.error.length > 0) {
res.send({ warn: examine.error })
} else {
fn(res, { path: `${this.http}${req.file.filename}` })
}
})
}
}
业务层封装
业务层的封装,与请求层、数据层不同。具体业务具体分析,进行同质化逻辑的函数抽离,以及模块化的文件切割。业务模块的难点还是在于数据库设计、功能切割、以及繁杂的业务逻辑上。这些只能是水磨功夫与经验主义了。而书本上的东西,固然能帮助我们,但是实际的场景中,很多细枝末节的东西依赖的依旧是我们的项目经验,以及大家贡献的相关文章了。
结语
此文颇为仓促,其实本想先优化一下各个模块的代码再写出来,但是由于诸多原因却不得不提前先做出来。
其实一些事情并不好做,无论是项目上的事情,还是其他事情。然而事情纵有难易,只要做下去,终究是有成功的可能。若是不去做,那便只有失败了。所谓道阻且长,行则将至。项目上,生活上,都是如此。莫道浮云终蔽日,总有云开雾散时。
转载自:https://juejin.cn/post/7263035160622956602