從零搭建一個全棧項目(四)—— express重構server端

1、內容簡介

前面幾章已經實現了項目的基礎功能(用戶登陸註冊,查看博客列表),後面要作的就是功能的豐富和代碼優化,因爲目前代碼量還比較少,因此考慮先進行一些代碼的優化整合,方便後續開發。html

2、優化內容

相比以前代碼進行了以下優化:前端

  1. 以前需啓動兩個端口3000, 8080 ——> 先後端運行在同一端口(8080);
  2. 以前server端由node原生開發,代碼較多不易維護 ——> 使用express進行重構,代碼邏輯更簡介;
  3. 以前登陸註冊使用seeion ——> 登陸註冊改成jwt。

下面就這三點分別介紹一下個人解決方案。node

3、運行項目的優化

以前前端運行是依賴webpack的devServer:webpack

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用來實現頁面熱更新,當咱們修改代碼後會幫咱們更新前端頁面:ios

/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 後就能夠打開對應的登陸頁面。git

4、server端代碼重構

因爲以前使用原生node代碼較多,好比:
須要寫相應的匹配規則來判斷請求是否爲接口: github

在使用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 這樣的代碼,代碼的邏輯會更清晰。web

5、使用express + express-jwt 實現用戶登陸

1.實現思路:

在咱們登陸成功後,在後端生成token返回給前端,前端獲取到token後保存起來,在每次進行請求的時候將token設置在headers中,後端在請求中獲取token ——>校驗token是否有效 ———> 經過token獲取用戶信息。這裏我前端請求接口使用axios,使用axios的添加攔截器來設置token。sql

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)
    }
  })
}
複製代碼

6、總結

本章介紹了

  1. 使用webpack-dev-middleware +webpack-hot-middleware 對項目啓動進行優化
  2. 使用express對服務端進行重構,主要介紹了用戶登陸的相關邏輯。

下一章主要內容:

  1. 優化前端界面;
  2. 添加webpack按需加載(用到相關邏輯時加載響應js)

7、最後

GitHub地址:戳這裏

本項目僅爲學習交流使用,若是有小夥伴有更好的建議歡迎提出來你們一塊兒討論,另外感興趣的小夥伴就點個star吧! ^_^

相關文章
相關標籤/搜索