从零搭建一个全栈项目(四)—— express重构server端

715 阅读4分钟

一、内容简介

前面几章已经实现了项目的基础功能(用户登录注册,查看博客列表),后面要做的就是功能的丰富和代码优化,由于目前代码量还比较少,所以考虑先进行一些代码的优化整合,方便后续开发。

二、优化内容

相比之前代码进行了如下优化:

  1. 之前需启动两个端口3000, 8080 ——> 前后端运行在同一端口(8080);
  2. 之前server端由node原生开发,代码较多不易维护 ——> 使用express进行重构,代码逻辑更简介;
  3. 之前登录注册使用seeion ——> 登录注册改为jwt。

下面就这三点分别介绍一下我的解决方案。

三、运行项目的优化

之前前端运行是依赖webpack的devServer:

  devServer: {
    contentBase: path.resolve(__dirname, '../dist'),
    historyApiFallback: true,
    port: 8080,
    inline: true,
    hot: true,
    host: 'localhost',
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: { '^/': '' }
      }
    }
  }

前端代码运行在8080端口,设置代理3000端口用来请求后台接口。
现在使用webpack-dev-middleware +webpack-hot-middleware (页面热更新),进行启动,这样可以在express中通过webpack.config.js获取到webpack文件,随后将其打包到内存中从而启动前端项目,类似devServer的功能;webpack-hot-middleware用来实现页面热更新,当我们修改代码后会帮我们更新前端页面:

/client/app.js

<!-- express中 引入所需依赖 -->
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const WebpackConfig = require('../build/webpack.dev.js')
const compiler = webpack(WebpackConfig)

<!-- 当运行express服务时使用  webpackDevMiddleware,webpackHotMiddleware  -->
app.use(
  webpackDevMiddleware(compiler, {
    publicPath: '/dist/'
  })
)
app.use(webpackHotMiddleware(compiler))

这样我们在启动express后,会同时帮我们打包前端的代码,由于我们打包后的目录是dist,输入http://localhost:8080/dist/login.html 后就可以打开对应的登录页面。

四、server端代码重构

由于之前使用原生node代码较多,比如:
需要写相应的匹配规则来判断请求是否为接口:

在使用seesion实现登录注册时也需要写一些方法取到相应cookie,设置session等:

使用express后,可以使用中间件 body-parser来帮助我们获取请求中的参数。

/client/app.js

// 解析 application/json
app.use(bodyParser.json())
// 解析 application/x-www-form-urlencoded
app.use(bodyParser.urlencoded())

同时使用exprss中间件可以使我们的代码不需要加很多if else 这样的代码,代码的逻辑会更清晰。

五、使用express + express-jwt 实现用户登录

1.实现思路:

在我们登录成功后,在后端生成token返回给前端,前端获取到token后保存起来,在每次进行请求的时候将token设置在headers中,后端在请求中获取token ——>校验token是否有效 ———> 通过token获取用户信息。这里我前端请求接口使用axios,使用axios的添加拦截器来设置token。

2.代码实现:

前端:

因为前端逻辑简单,先贴前端代码:
登录接口请求成功后,获取token保存在localStorage中:

const account = document.getElementById('account').value
const password = document.getElementById('password').value
const user = await getAxiosData('login', 'post', { account, password })
// 存储token
setStorage('blog-token', user.token)

在axios中添加拦截器,请求拦截器:在请求接口时判断本地是否有token,如果有则设置在headers中 这里设置headers 的属性一定要 authorization,内容一定要 'Bearer ' + token。 config.headers.token = token 这种是不可以的,后端校验会报错(=_= 别问怎么知道的)

// 请求拦截器
axios.interceptors.request.use(
  function(config) {
    const token = getStorage('blog-token')
    if (token) {
      config.headers.authorization = 'Bearer ' + token
    }
    return config
  },
  function(error) {
    return Promise.reject(error)
  }
)

响应拦截器:判断了接口code为401的情况(token校验失败),这里考虑后期可能会在未登录做些什么,所以加了响应拦截器

// 响应拦截器
axios.interceptors.response.use(function(response) {
  const data = response.data
  if (data.code === 401) {
    return {
      code: 401,
      message: '请登录'
    }
  }
  return data
})

服务端:

在app.js中添加校验token的中间件,同时设置不需要校验的接口:

app.use(
  jwt({
    secret: secretKey
  }).unless({
    path: ['/api/user/login', '/api/user/register', '/api/blog/list', /\.ico$/]
  })
)

// 校验token
app.use(verifyToken)

//  verifyToken:
// 校验token
const verifyToken = function(error, req, res, next) {
  const token = getToken(req)
  if (token) {
    jwt.verify(token, secretKey, (err, decoded) => {
      if (err) {
        return res.send({
          code: 401,
          message: err.message
        })
      } else {
        return next()
      }
    })
  }
  return res.send({
    code: error.status,
    message: error.message
  })
}

当用户登录接口时,获取用户信息并生成token返回给前端:
getUser: 返回数据库中的用户信息;
getTokenByData:将用户信息转成token。

// 用户登录
router.post('/api/user/login', async (req, res) => {
  try {
    const user = await getUser(req.body)
    const token = getTokenByData(user)
    user.token = token
    res.send(new SuccessModel(user))
  } catch (err) {
    res.send(new ErrorModel(err))
  }
})

// getTokenByData
const jwt = require('jsonwebtoken')

// 根据数据返回生成token
const getTokenByData = function(data) {
  return jwt.sign({ data }, secretKey, {
    expiresIn: 60 * 60 * 24 // 授权时效24小时
  })
}

当用户登录后请求其他接口时,通过token获取用户信息,再查找数据库中相关数据:
getToken: 根据req返回token getUserByToken: 根据token返回用户信息

// 获取用户信息
router.get('/api/user/getUser', (req, res) => {
  const token = getToken(req)
  const user = getUserByToken(token)
  if (user.id) {
    res.send(new SuccessModel(user))
  } else {
    res.send({
      code: 401,
      message: user.message
    })
  }
})

// getToken: 获取 请求 token
const getToken = (req) => {
  let token = ''
  if (req.headers.authorization) {
    token = req.headers.authorization.split(' ')[1]
  }
  return token
}

// getUserByToken: 通过token返回用户信息
const getUserByToken = function (token) {
  let data
  jwt.verify(token, secretKey, (err, decoded) => {
    if (!err) {
      data = decoded.data
    } else {
      data = err
    }
  })
  return data
}

最后,就是执行sql语句查询数据库后对数据进行一个整合返回给前端:
例如获取用户信息:

const getUser = (data, username) => {
  const s = '`password`'
  let sql
  if (username) {
    sql = `select id, username, realname from users where username='${username}';`
  } else {
    sql = `select id, username, realname from users where username='${data.account}' and ${s}='${data.password}';`
  }
  return new Promise(async (resolve, reject) => {
    const user = await exec(sql)
    if (user.length === 0) {
      reject('账号或密码错误')
    } else if (user.length === 1) {
      resolve(user[0])
    } else {
      resolve(user)
    }
  })
}

六、总结

本章介绍了

  1. 使用webpack-dev-middleware +webpack-hot-middleware 对项目启动进行优化
  2. 使用express对服务端进行重构,主要介绍了用户登录的相关逻辑。

下一章主要内容:

  1. 优化前端界面;
  2. 添加webpack按需加载(用到相关逻辑时加载响应js)

七、最后

GitHub地址:戳这里

本项目仅为学习交流使用,如果有小伙伴有更好的建议欢迎提出来大家一起讨论,另外感兴趣的小伙伴就点个star吧! ^_^