likes
comments
collection
share

使用express+mysql+multer+pm2做后端的个人项目部分总结

作者站长头像
站长
· 阅读数 14

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}` })
            }
        })
    }
}

业务层封装

业务层的封装,与请求层、数据层不同。具体业务具体分析,进行同质化逻辑的函数抽离,以及模块化的文件切割。业务模块的难点还是在于数据库设计、功能切割、以及繁杂的业务逻辑上。这些只能是水磨功夫与经验主义了。而书本上的东西,固然能帮助我们,但是实际的场景中,很多细枝末节的东西依赖的依旧是我们的项目经验,以及大家贡献的相关文章了。

结语

此文颇为仓促,其实本想先优化一下各个模块的代码再写出来,但是由于诸多原因却不得不提前先做出来。

其实一些事情并不好做,无论是项目上的事情,还是其他事情。然而事情纵有难易,只要做下去,终究是有成功的可能。若是不去做,那便只有失败了。所谓道阻且长,行则将至。项目上,生活上,都是如此。莫道浮云终蔽日,总有云开雾散时。