Express+MySQL+Vue+Socket.io搭建一个聊

2019-01-24  本文已影响121人  HolidayPeng

写这个系列的文章的初衷是想利用socket.io自己开发一个聊天室。聊天室要取到用户的头像和昵称,以及拉取历史聊天记录,就需要开发注册、登录、保存、获取聊天记录的功能。于是需要搭建一个后台服务,提供接口供前端调用,技术栈要用到Mysql和Express。

本系列文章非手把手类型,而是从设计者的角度,阐述项目中遇到的问题和选择,以及如何解决这些问题,为什么这么选:

一、后台接口项目是不是可以和Vue-Cli生成的前端项目合并成一个项目?如果可以,是把后端代码放到前端项目里,还是把前端代码放在后端项目里?

我们先来看第一种:

当我们用指令npm run dev把项目跑起来的时候,webpack会启动一个服务,并在本地的8080端口(默认)进行监听,这个时候,本地的静态资源(html、js、css、img等)会被打到这个地址,以便我们访问。

这个功能是在Vue-Cli项目的build/webpack.dev.conf.js文件中进行配置的。具体是在devWebpackConfig对象里的devServer选项:

devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }
      ]
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll
    }
  }

既然webpack已经帮我们搭建了一个服务,我们也可以在这个服务里加上一个中间件,来给前端页面提供数据接口:


来自webpack官方文档

在devServer的before对象里,我们可以像上图webpack官方文档所描述的那样去moke数据,也可以用下面的方法连接数据库,从数据库中获取真实数据:

// webpack.dev.config.js
const express = require('express')
const mysql = require('mysql')
const app = express()
const bodyParser = require('body-parser')
const conn = mysql.createConnection({
  host: '127.0.0.1',
  user: 'root',
  password: 'password',
  database: 'test',
  multipleStatements: true
})
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
……
const devWebpackConfig = merge(baseWebpackConfig, {
  ……
  devServer: {
    before (app) {
      app.all('*', (req, res, next) => {
        res.header('X-Powered-By', '3.2.1')
        res.header('Cache-Contrl', 'no-store')
        next()
      })
      app.disable('etag')
      app.get(`/api/getUser`, (req, res) => {
        const sqlStr = 'select * from user'
        conn.query(sqlStr, (err, results) => {
          if (err) {
            return res.json({
              err_code: 1,
              message: '资料不存在',
              affextedRows: 0
            })
          }
          res.json({
            err_code: 200,
            message: results,
            affextedRows: results.affextedRows
          })
        })
      })
    },
  ……
  }
……
})
……

这样写的好处是解决了跨域的问题,但是后端的逻辑和webpack的配置混在一起,耦合紧密,不利于后期管理维护。

那么把前端代码放在后端的项目里又会是什么情况呢?

随便在网上搜了一下,确实有这样的项目模板,比如https://github.com/southerncross/vue-express-dev-boilerplate,他的目录结构如下:

.
├── LICENSE
├── README.md
├── index.js
├── nodemon.json
├── package.json
├── src
│   ├── client
│   │   ├── App.vue
│   │   ├── components
│   │   │   └── Hello.vue
│   │   └── index.js
│   └── server
│       ├── index.js
│       ├── public
│       │   └── favicon.ico
│       ├── router.js
│       └── views
│           ├── error.jade
│           └── index.jade
└── webpack.config.js

在webpack.config.js中,他将vue项目中的入口文件index.js打包生成的build.js放在了服务模块public静态文件夹的javascripts中:

module.exports = {
  entry: path.join(__dirname, 'src/client/index.js'),
  output: {
    path: path.join(__dirname, 'src/server/public/javascripts/'),
    publicPath: '/javascripts/',
    filename: 'build.js'
  }
……
 }

再在src/server/views/index.jade中,引入build.js文件:

doctype html
html
  head
    title= title
  body
    #app
    script(src='/javascripts/build.js')

这样做的好处是,前后端代码在同一个服务上,但逻辑分开写在两个文件夹里,不仅解决了请求接口的跨域问题,也便于后台的中间件对页面请求做一些统一的处理。

接下来我们再躯体考虑后台项目部分:

二、Express项目的结构怎么设计?

如下图所示,当前项目主要分为四层:db层存放数据库层面的逻辑,包括连接和mysql语句;model层存放操作数据库的方法;controller层处理接口的业务逻辑;routes层封装接口。


项目结构图

项目结构搭好了,我们来看具体的业务逻辑:

三、注册流程是怎样的?需要几个接口?

见下图:


注册流程
接口逻辑

接口层面的问题解决了,我们再来看数据库层面:

四、注册需要几张表?要保存哪些字段?

最开始设计的是两张表,user表和email表。email表用来保存用户注册时的昵称、密码、邮件地址、验证码、过期时间等,当他完成注册以后成为正式用户,再把这其中的昵称、密码和邮箱地址保存在user表里。也就是说,user表里保存的是正式用户,email表里保存发送过邮件的用户:


user表
email表

但是这样的设计并不是最合理的,首先两张表有重合的字段,会造成数据冗余;其次当这些重合的字段有变化的时候,两张表都需要更新。于是调整表结构为一张表,即用户表,注册成功与否用一个字段status来表示:


合并后的user表

然鹅,当数据库小白的我满怀信心带着表结构跑去请教大牛时,被告知这样的结构还是不合理,因为verification_code、verification_code_expired_time这两个字段保存在用户表里,对用户来说没有意义,应该分离开来。

根据大牛的建议,最后的表结构如下:


新user表
新email表

以上从项目设计、项目结构、业务逻辑、接口和数据库的设计,由整体到细节作了阐释,下一篇将从继续细节入手,对项目的登录部分进行分析,尽请期待……

上一篇下一篇

猜你喜欢

热点阅读