MVC模式构建NodeJS+Express+Mysql纯后端项目

3,679 阅读4分钟

前言

最近在自己撸一个完整项目,突然想用node来写后端的接口。
找了找网上的资料,发现大部分都是ssr,很少有人写类似的文章来说纯node的后端项目目录结构应该如何搭建。
因此尝试在这里采用传统后端的MVC模式,结合自己的项目总结一篇从零搭建的文章,如有不足,还请大佬指教🙏。

1、MVC模式/目录初始化

关于MVC模式大家应该都不陌生,也就是Model-View-Controller(模型-视图-控制器) 模式。因此在本项目中:

  • Model:数据层
  • Controller:控制层

项目目录的初始化结构定义如下:

因为使用expressrouter来处理请求,因此在view中定义的router.js来统一管理接口。

由于项目还需要连接数据库,因此增加libsconfig目录用于管理数据库的连接和配置,再增加static文件夹用来存放静态资源,utils存放工具函数,最终项目的目录结构如下(由于项目逻辑不是很复杂,暂定如此,希望得到大家的指正):

2、Model/View

由于view层只用于管理接口,所以比较简单,具体的逻辑放在Controller层处理

// view/router.js
const express = require('express');
const router = express.Router();
const controller = require('../controller');

// 查询
router.get('/login', controller.user.login);

关于Model层的初始化,可以先定义数据库的连接,为了方便后期更改配置,将数据库的配置单独放在config中:

// config/index.js
module.exports = {
    DB_HOST: 'localhost',
    DB_USER: 'root',
    DB_PASSWORD: '*****',
    DB_PORT: '3306',
    DB_NAME: '*****'
};

然后在libs下初始化数据库的连接

const mysql = require('mysql');
const DB_CONFIG = require('../config');
const co = require('co-mysql');

const pool = mysql.createPool({
    host: DB_CONFIG.DB_HOST,
    user: DB_CONFIG.DB_USER,
    password: DB_CONFIG.DB_PASSWORD,
    port: DB_CONFIG.DB_PORT,
    database: DB_CONFIG.DB_NAME,
});

module.exports = co(pool);

这里使用了co-mysql库配合async await来处理node-mysql的地狱回调。原理很简单,短短的几行代码,感兴趣的同学可以去GitHub上看一下。

数据库的连接初始化完成后,在model目录下根据不同的需求分别编写SQL语句,处理增删改查等操作,以登陆接口为例:

// model/login.js
const db = require('../libs/database');

module.exports = async (data) => {
    const { name, password, phone } = data;
    const searchKey = name ? 'user_name' : 'phone';
    const searchValue = name ? name : phone;
    const searchSQL = `SELECT * FROM user_info WHERE ${searchKey} = "${searchValue}" AND password = "${password}"`;
    return await db.query(searchSQL);
};

最终返回数据库的处理结果,也可以在这里做一些处理再包一层promise返回,至此,Model层和View层都基本完成了。

3、Controller

Controller中,需要Model层和View层进行关联,将通过统一的modules目录来管理不同模块的逻辑,最后暴露一个controller对象以便View层调用。

// index.js
const user = require('./modules/user');
const home = require('./modules/home');
const items = require('./modules/items');
const order = require('./modules/order');

module.exports = {
    user,
    home,
    items,
    order
};

这样分层的作用是,在处理不同模块逻辑时只需要调用对应模块下对应功能的控制器就能处理请求,不同模块间如果有关联也可以在对应模块中进行调用。

这里以登陆注册接口为例,定义在user对象下:

const resJson = require('../../utils/resJson');
const tokenUtils = require('../../utils/token');
const loginModel = require('../../model/login');
const registerModel = require('../../model/register');
const verifyModel = require('../../model/verifyRegister');
const md5 = require("md5");

const user = {
    // 登陆
    login: async (req, res) => {
        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
        const {name, password, phone} = req.body;
        await loginModel({name, password: md5(password), phone}).then(data => {
            if (data.length) {
                const tokenData = {
                    name: data[0].user_name,
                    password: data[0].password,
                    phone: data[0].phone,
                    user_uid: data[0].user_uid
                };
                res.end(resJson.returnSuccess({token: tokenUtils.getToken(tokenData)}));
            } else {
                res.end(resJson.returnError(500, '用户名或密码错误'));
            }
        }).catch(e => {
            res.end(resJson.returnError(500, '用户名或密码错误'));
        })
    },
    // 注册
    register: async (req, res) => {
        let flag = true;
        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
        const {name, password, phone} = req.body;
        // 验证用户名是否存在
        await verifyModel('user_name', name).then(data => {
            if (data.length) {
                res.end(resJson.returnError(500, '用户名已存在'));
                flag = false;
            }
        }).catch(e => {
            res.end(resJson.returnError(500, e));
            flag = false;
        });
        if (!flag) return;
        // 验证手机号是否存在
        await verifyModel('phone', phone).then(data => {
            if (data.length) {
                res.end(resJson.returnError(500, '手机号已存在'));
                flag = false;
            }
        }).catch(e => {
            res.end(resJson.returnError(500, e));
            flag = false;
        });
        if (!flag) return;

        await registerModel(req.body).catch(e => {
            res.end(resJson.returnError(500, e));
            flag = false;
        });
        if (!flag) return;

        await loginModel({name, password: md5(password), phone}).then(data => {
            if (data.length) {
                const tokenData = {
                    name: data[0].user_name,
                    password: data[0].password,
                    phone: data[0].phone,
                    user_uid: data[0].user_uid
                };
                res.end(resJson.returnSuccess({token: tokenUtils.getToken(tokenData)}));
            } else {
                res.end(resJson.returnError(500, '用户名或密码错误'));
            }
        }).catch(e => {
            res.end(resJson.returnError(500, '用户名或密码错误'));
        })
    }
};

module.exports = user;

最后在router.js中只需要调用user中的login方法就可以实现接口的功能。

总结

这种模式设计的项目整体结构比较清晰,基本思路如下:

  • 用户发起请求,express-router处理请求

  • 然后node server解析该请求,定位到对应的controller里面执行具体的操作。

  • 进入controller层,执行某项需要操作数据库的操作。

  • 进入model层,则调用model层对应的SQL语句。

  • 然后由controller层将数据返回给用户。

以上就是从零搭建该项目的整个过程,如有不足,还请多多指正!