一個nuxt(vue)+mongoose全棧項目聊聊我粗淺的項目架構

這是一篇求職文章 年齡21 座標成都 找一份vue.js移動端H5工做
一份沒有任何包裝純真實的簡歷 簡歷戳這

求職文章一共有兩篇 另一篇請點擊一個基於Vue+TypeScript的[移動端]Vue UI

項目簡介

名字javascript

JsonMakercss

做用html

添加api和屬性,用於製造JSON前端

地址vue

  githubjava

技術棧node

前端webpack

pug scss vue vue-router vuex axios nuxt element-ui
複製代碼

後端ios

node express mongoose mongodb jsonwebtoken
複製代碼

項目目錄

前端git

assets 資源文件和js邏輯存放處
components 組件目錄 (由於引用了element-ui 項目不大 沒單獨構造組件)
layouts 佈局目錄(此項目沒用上)
middleware 中間件目錄
pages 頁面目錄
plugins 插件目錄
static 靜態文件目錄
store vuex狀態數目錄

後端

actions js事件目錄
config 配置目錄
lib js模版目錄
middleware express中間件目錄
model mongoose.model 目錄
plugins 插件目錄
schmea mongoose.Schema 目錄
app.js 主app
router.js 路由

圖片

架構思路

前端

首先咱們大體瞭解一下咱們這個nuxt.config.js中的配置,以後會一個一個講解

nuxt.config.js

nuxt.config.js 配置

module.exports = {
  // html
  head: {
    title: 'JsonMaker一個JSON製造器',
    meta: [
      { charset: 'utf-8' },
      { name: 'author', content: 'Qymh' },
      { name: 'keywords', content: 'Json,JSON,JsonMaker' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content:
          'JsonMaker用戶製造JSON,一個全棧項目,前端基於Nuxt Vuex Pug Scss Axios element-ui 後端基於 Node Express mongoose mongodb jsonwebtoken'
      }
    ],
    link: [
      {
        rel: 'icon',
        type: 'image/x-icon',
        href: 'https://nav.qymh.org.cn/static/images/q.ico'
      }
    ]
  },
  // 全局css
  css: [
    // reset css
    '~/assets/style/normalize.css',
    // common css
    '~/assets/style/common.css',
    // element-ui css
    'element-ui/lib/theme-chalk/index.css'
  ],
  // 加載顏色
  loading: { color: '#409EFF' },
  // 插件
  plugins: [
    // element-ui
    { src: '~/plugins/element-ui' },
    // widget
    { src: '~/plugins/widget' },
    // 百度統計
    { src: '~/plugins/baiduStatistics', ssr: false },
    // 百度站長平臺
    { src: '~/plugins/baiduStation', ssr: false }
  ],
  // webpack配置
  build: {
    extend(config, { isDev, isClient }) {
      // eslint
      if (isDev && isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
      config.module.rules.push(
        // pug
        {
          test: /\.pug$/,
          loader: 'pug-plain-loader'
        },
        // scss
        {
          test: /\.scss$/,
          use: [
            'vue-style-loader',
            'css-loader',
            'sass-loader',
            'postcss-loader'
          ]
        }
      )
    },
    // postcss配置
    postcss: [require('autoprefixer')()],
    // 公用庫
    vendor: ['axios', 'element-ui']
  },
  router: {
    // 認證中間件
    middleware: 'authenticate'
  }
}
複製代碼

解析nuxt.config.js中的插件

插件中我引用了4個

  • 1 element-ui 插件
  • 2 widget 這裏麪包裝了cookie的操做方法
    經過Vue.use()引入插件,直接經過vue環境下的this調用
    這個位置有一個坑,服務器端是沒有document這個屬性的,因此無法獲取經過這種方式獲取cookie
    因此咱們還須要構造一個從req獲取token的函數,我寫在了assets/lib/utils
    cookie是從req.headers.cookie中讀取的
  • 3 引入百度統計
  • 4 引入百度站長平臺

解析 nuxt.config.js 中的 middleware

middleware目中就一個文件,這個文件包含了驗證用戶登錄和自動登錄的功能
這個位置也有一個坑,與非nuxt項目不一樣,咱們日常的vue項目這個操做
是在router.beforeEach全局鉤子裏進行驗證,並且在nuxt中你不光要驗證客戶端也要驗證服務器端
大致思路就幾點

  • 1 在須要登錄的頁面設置meta: { auth: true },不須要的頁面設置meta: { notAuth: true }
  • 2 當處於須要登錄的頁面若是有token直接退出,沒有則分兩部獲取token,一個客戶端,一個服務器端,最後若是token存在
    則執行全局系統參數的api調用而後寫入vuex,若是不存在則返回登錄界面
  • 3 在某些notAuth auth 都不存在時,檢查存放的userName屬性存在不,存在就跳到用戶首頁,不存在則跳到登錄界面

全局參數配置

每一個人對這個全局配置理解不同,看習慣,有人喜歡把不少配置都往全局放,好比vue-router的配置,我以爲不必
我通常在全局配置中放一些配置沒那麼複雜的,諸如項目名字啊還有各種插件的配置,這個項目不大,因此全局配置也不太多 assets/lib/appconfig.js

const isDev = process.env.NODE_ENV === 'development'

// app
export const APPCONFIG = {
  isDebug: true
}

// cookie 設置
export const COOKIECONFIG = {
  expiresDay: 7
}

// server 設置
export const SERVERCONFIG = {
  domain: isDev ? 'http://127.0.0.1:5766' : 'https://api.qymh.org.cn',
  timeout: 10000
}

複製代碼

全局還有一個配置就是api接口的配置,我喜歡把api接口放在一個文件裏面,而後引入,這個項目不大,一共15個接口 assets/lib/api

// 獲取全局屬性
export const system = '/api/system'

// 註冊
export const register = '/api/register'
// 登錄
export const login = '/api/login'

// 添加api
export const addApi = '/api/addApi'
// 獲取api
export const getApi = '/api/getApi'
// 刪除api
export const deleteApi = '/api/deleteApi'
// 修改api
export const putApi = '/api/putApi'

// 添加屬性
export const addProperty = '/api/addProperty'
// 獲取屬性
export const getProperties = '/api/getProperties'
// 刪除屬性
export const deleteProperty = '/api/deleteProperty'
// 修改屬性
export const putProperty = '/api/putProperty'

// 添加集合
export const addCollections = '/api/addCollections'
// 獲取集合
export const getCollections = '/api/getCollections'
// 刪除集合
export const deleteCollections = '/api/deleteCollections'
// 修改集合
export const putCollections = '/api/putCollections'

複製代碼

ajax函數請求架構

nuxt.config.js聊完了,咱們來聊聊先後端分離的一個大點,就是請求,個人習慣的一層一層從底部往上抽離

  • 1 第一步,封裝攔截器
    攔截器就幾個部分,一個axios基礎參數配置,一個請求request攔截,一個響應response攔截
    通常在請求攔截就是構造參數,好比參數加密 請求頭的發送 之類的,這個項目暫時還沒作前端參數加密嗎,同時我也會在請求輸出log日誌
    響應攔截也是同樣的,輸出接收到的參很多天志並處理出錯的狀況,咱們來看看代碼
    assets/lib/axios.js
import axios from 'axios'
import Vue from 'vue'
import { SERVERCONFIG, APPCONFIG } from './appconfig'

const isClient = process.client
const vm = new Vue()

const ax = axios.create({
  baseURL: SERVERCONFIG.domain,
  timeout: SERVERCONFIG.timeout
})

// 請求攔截
ax.interceptors.request.use(config => {
  const token = isClient ? vm.$cookie.get('token') : process.TOKEN
  if (token) {
    config.headers.common['authenticate'] = token
  }
  const { data } = config
  if (APPCONFIG.isDebug) {
    console.log(`serverApi:${config.baseURL}${config.url}`)
    if (Object.keys(data).length > 0) {
      console.log(`request data ${JSON.stringify(data)}`)
    }
  }
  return config
})

// 響應攔截
ax.interceptors.response.use(response => {
  const { status, data } = response
  if (APPCONFIG.isDebug) {
    if (status >= 200 && status <= 300) {
      console.log('---response data ---')
      console.log(data)
      if (data.error_code && isClient) {
        vm.$message({
          type: 'error',
          message: data.error_message,
          duration: 1500
        })
      }
    } else {
      console.log('--- error ---')
      console.log(data)
      if (isClient) {
        vm.$message({
          type: 'error',
          message:
            status === 0 ? '網絡連接異常' : `網絡異常,錯誤代碼:${status}`,
          duration: 1500
        })
      }
    }
  }
  return {
    data: response.data
  }
})

export default ax

複製代碼
  • 2 第二部構造http請求底層
    底層分裝了4個方法,get post put delete, 增刪改查,用promise實現,一層一層往上套,咱們來看看代碼

assets/lib/http.js

import ax from './axios'
import Vue from 'vue'

export default {
  /** * ajax公用函數 * @param {String} api api接口 * @param {Object} data 數據 * @param {Boolean} isLoading 是否須要加載 */
  ajax(method, api, data, isLoading = false) {
    return new Promise((resolve, reject) => {
      let vm = ''
      let loading = ''
      if (isLoading) {
        vm = new Vue()
        loading = vm.$loading()
      }
      ax({
        method,
        url: api,
        data
      }).then(res => {
        let { data } = res
        if (data.error_code) {
          isLoading && loading.close()
          reject(data)
        } else {
          isLoading && loading.close()
          resolve(data)
        }
      })
    })
  },

  /** * post函數 * @param {String} api api接口 * @param {Object} data 數據 * @param {Boolean} isLoading 是否須要加載 */
  post(api, data, isLoading = false) {
    return new Promise((resolve, reject) => {
      this.ajax('POST', api, data, isLoading)
        .then(data => {
          resolve(data)
        })
        .catch(err => {
          reject(err)
        })
    })
  },

  /** * delete函數 * @param {String} api api接口 * @param {Object} data 數據 * @param {Boolean} isLoading 是否須要加載 */
  delete(api, data, isLoading = false) {
    return new Promise((resolve, reject) => {
      this.ajax('DELETE', api, data, isLoading)
        .then(data => {
          resolve(data)
        })
        .catch(err => {
          reject(err)
        })
    })
  },

  /** * put函數 * @param {String} api api接口 * @param {Object} data 數據 * @param {Boolean} isLoading 是否須要加載 */
  put(api, data, isLoading = false) {
    return new Promise((resolve, reject) => {
      this.ajax('PUT', api, data, isLoading)
        .then(data => {
          resolve(data)
        })
        .catch(err => {
          reject(err)
        })
    })
  }
}

複製代碼
  • 3 第三部分就是事件的邏輯代碼,我放在了assets/actions裏面,一樣用promise實現,一步一步往上套,經過調用底層封裝的4個方法,調用封裝的全局api參數,這裏舉一個關於api首頁獲取的操做事件的列子
    assets/actions/api.js
import http from '../lib/http'
import * as api from '../lib/api'

export default {

  /** * 獲取api */
  getApi(userName) {
    return new Promise((resolve, reject) => {
      http
        .post(api.getApi, { userName })
        .then(data => {
          resolve(data)
        })
        .catch(err => {
          reject(err)
        })
    })
  }

複製代碼
  • 4 其實通常到第三步,直接在vue中就能夠引用 actions裏面封裝好的事件了,但這個項目還多了一層,是用vuex再次封了一層
    這裏仍然舉獲取api並操做vuex的列子,省略掉了非事件的代碼
import api from '~/assets/actions/api'
import Vue from 'vue'
const vm = new Vue()

const actions = {
  // 獲取api
  async getApi({ commit }, { userName, redirect }) {
    await api
      .getApi(userName)
      .then(arr => {
        commit('_getApi', arr)
      })
      .catch(() => {
        redirect({
          path: '/login',
          query: {
            errorMessage: '用戶不存在,請從新登錄'
          }
        })
      })
  }

複製代碼
  • 5 下面就是在vue中引入actions就能夠用了,接下來咱們聊聊vuex的規範性

vuex的架構

  • 1 接口暴漏
    vuex中有四個屬性,state getters mutations actions
    按個人架構思路,我永遠暴漏在vue中可使用的僅有兩個,一個getters,一個actions
    爲何呢?由於state改變後值不會在dom中刷新,mutations沒法異步

  • 2 命名
    按官方建議要有一個mutations-type專門用於存放突變事件名字,我以爲不必,太麻煩了
    按第一點所說的,未暴漏的命名我會直接在前面加一個下劃線,就像我上面的代碼顯示的那樣

  • 3 事件和值的改變
    從名字上來說,actions表事件,mutations表突變,換句話來講,我執行事件邏輯,好比接口請求,我會在actions裏面執行, 而改變vuex狀態樹的值,我會在mutations裏面執行

  • 4 命名空間限定

    必定要在每一個模塊上加入namespaced: true,一個是思路更清晰,第二個避免重複命名

後端

這個項目是我第二次用express寫後端,架構思路感受本身還不太成熟,寫完以後發現有不少地方沒對.忙着找工做,時間也來不及了,以後改改

先來看看app.js

app.js

app.js幹了幾件事

  • 1 引入mongoose並鏈接mongodb
  • 2 設置跨域CORS
  • 3 引入中間件和路由

全局參數

node後端也有全局參數,主要包含了錯誤代碼的集合還有一些經常使用的配置

config/nodeconfig.js

// token設置
exports.token = {
  secret: 'Qymh',
  expires: '7 days'
}

// 錯誤code
exports.code = {
  // 用戶不存在
  noUser: 10001,
  // 密碼錯誤
  wrongPassword: 10002,
  // token過時
  outDateToken: 10003,
  // 檢驗不符合規則
  notValidate: 10004,
  // 已存在的數據
  existData: 10005,
  // 未知錯誤
  unknown: 100099,
  // 未知錯誤文字
  unknownText: '未知錯誤,請從新登錄試試'
}

// session
exports.session = {
  secret: 'Qymh',
  maxAge: 10000
}

複製代碼

數據存儲架構思路

  • 1 第一步 構建Schema

Schema也是mongoose須要第一個構建的,項目中引用了不少官方提供的驗證接口,我將Schema的配置放在了config/schema中,咱們來看一下用戶的Schema是什麼樣的

schema/user.js

const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ApiSchema = require('./api')
const config = require('../config/schema/user').USERSCHEMACONFIG

const UserSchema = new Schema(
  {
    account: config.account,
    password: config.password,
    userName: config.userName,
    token: config.token,
    api: [ApiSchema]
  },
  config.options
)

module.exports = UserSchema

複製代碼

config/schema/user.js

exports.USERSCHEMACONFIG = {
  // 賬號
  account: {
    type: String || Number,
    index: [true, '賬號已經存在'],
    unique: [true, '賬號已經存在'],
    required: [true, '賬號不能爲空'],
    minlength: [5, '賬號長度須要大於等於5'],
    maxlength: [18, '賬號長度須要小於等於18'],
    trim: true
  },
  // 密碼
  password: {
    type: String || Number,
    required: [true, '密碼不能爲空'],
    minlength: [8, '密碼長度須要大於等於8'],
    maxlength: [18, '密碼長度須要小於等於18'],
    trim: true
  },
  // 名字
  userName: {
    type: String || Number,
    index: [true, '用戶名已經存在'],
    unique: [true, '用戶名已經存在'],
    required: [true, '用戶名不能爲空'],
    minlength: [2, '姓名長度須要大於等於2'],
    maxlength: [8, '姓名長度須要小於等於8'],
    trim: true
  },
  // token
  token: {
    type: String
  },
  // schema配置
  options: {
    versionKey: 'v1.0',
    timestamps: {
      createdAt: 'createdAt',
      updatedAt: 'updatedAt'
    }
  }
}
    
複製代碼
  • 2 第二步構建model

model放在model文件夾中,接收傳來的Schema,而後傳出Model,咱們來看看用戶的model

model/user.js

const mongoose = require('mongoose')
const UserSchema = require('../schema/user')

const UserModel = mongoose.model('UserModel', UserSchema)

module.exports = UserModel

複製代碼
  • 3 第三步構建數據存儲lib

這個存儲實際上是爲了actions文件服務的,actions接受路由事件,而lib則負責儲存,包含了註冊和登錄功能,而後在這個lib操做裏面,我將對最後得到數據的處理進行封裝,封裝到了plugins目錄,裏面就包括了,對用戶的token處理,對用於註冊失敗成功和登錄失敗成功的回調參數處理,咱們來看看用戶的lib

lib/user.js

const UserModel = require('../model/user')
const UserPlugin = require('../plugins/user')

/** * 註冊 * @param {String | Number} account 賬號 * @param {String | Number} password 密碼 * @param {String | Number} userName 名字 */
exports.register = (account, password, userName) => {
  return new Promise((resolve, reject) => {
    const User = new UserModel({
      account,
      password,
      userName
    })

    User.save((err, doc) => {
      if (err) {
        err = UserPlugin.dealRegisterError(err)
        reject(err)
      }
      resolve(doc)
    })
  })
}

/** * 登錄 * @param {String | Number} account 賬號 * @param {String | Number} password 密碼 */
exports.login = (account, password) => {
  return new Promise((resolve, reject) => {
    UserModel.findOne({ account }).exec((err, user) => {
      err = UserPlugin.dealLoginError(user, password)
      if (err.error_code) {
        reject(err)
      } else {
        user = UserPlugin.dealLogin(user)
        resolve(user)
      }
    })
  })
}

複製代碼
  • 4 第四步 構建路由actions

actions目錄用於處理路由的接收,而後引入lib進行數據的存儲,咱們來看看用戶的actions

actions/user.js

const user = require('../lib/user')

// 註冊
exports.register = async (req, res) => {
  const data = req.body
  const { account, password, userName } = data
  await user
    .register(account, password, userName)
    .then(doc => {
      res.json(doc)
    })
    .catch(err => {
      res.json(err)
    })
}

// 登錄
exports.login = async (req, res) => {
  const data = req.body
  const { account, password } = data
  await user
    .login(account, password)
    .then(doc => {
      res.json(doc)
    })
    .catch(err => {
      res.json(err)
    })
}

複製代碼
  • 5 構建路由

router.js就是全部api的掛載處,最後在app.js裏面引用便可掛載,這個項目不大,一共提供了16個api

數據儲存這5步就基本結束了,下面咱們聊聊express的中間件

middleware中間件

這裏的中間件主要就驗證token過時沒,過時了則直接返回,而後不進行任何操做

middleware/authenticate.js

const userPlugin = require('../plugins/user')
const nodeconfig = require('../config/nodeconfig')

// 驗證token是否過時
exports.authenticate = (req, res, next) => {
  const token = req.headers.authenticate
  res.locals.token = token
  if (token) {
    const code = userPlugin.verifyToken(token)
    if (code === nodeconfig.code.outDateToken) {
      const err = {
        error_code: code,
        error_message: 'token過時'
      }
      res.json(err)
    }
  }
  next()
}

複製代碼

個人出錯

後端的架構就上面這些了,在此次的後端架構中我出了一個錯誤,你能夠看見我上面的userSchema是把apiSchema放在裏面了,而後 apiSchema裏面我有包含了兩個schema,一個propertSchema,一個collectionsSchema
爲何我會這麼作呢,由於剛開始寫的時候想的是若是要從一個數據庫去搜索一個信息,這個信息是屬於用戶的,有兩個方法

  • 1 直接構造這個數據庫的model而後存儲,存儲中帶一個userId指向當前這個信息所屬的用戶
  • 2 將這個數據放在userModel用戶model裏,查找的時候先查找當前用於而後再讀取這個信息

最後我選擇了第二個....由於我想的是若是數據10w條,用戶只有100個,去找100個總比找10w個好,我這麼選擇帶來的幾個問題

  • 1 mongoose儲存的時候若是對象裏面嵌套過多你想儲存是沒有api接口提供的.我看了幾遍文檔,只能經過$set $push 去存儲對象的最多第二屬性 好比下面的對象,是沒有直接的api提供去修改collections的值的,須要用其餘的方法繞一圈
[
       {
           userName: 'Qymh',
           id: 'xxxxx',
           api: [
               {
                   id: 'xxxx',
                   apiName: 'test',
                   collections:[
                       {
                           id: 'xxxx',
                           age: 21,
                           sex: man
                       }
                   ]
               }
           ]
       }
   ]
複製代碼
  • 2 查找的時候挺麻煩的,好比我要查找到collections,我須要提供兩個參數,一個用戶的id先找到用戶,再一個就是api的id再找到api最後再去提取collections,若是選擇第一種只須要用戶id就好了

因此我感受本身在這一步上出錯了

項目的掛載

  • 1 最後項目的掛載是經過pm2掛載的

  • 2 項目的node後端和前端都引用了ssl證書

如今項目已經掛到線上了但個人服務器太差,以前阿里雲買的9.9元的學生機如今續費了只能拿來測試玩玩

以後要作的

這個項目斷斷續續寫了20來天,不少功能沒有完善,以後我會作的

  • 1 前端傳入參數加密
  • 2 api屬性加入類型判斷前端傳入後端,後端schema添加,好比mongoose的幾個類型string boolean schema.types.mixed
  • 3 後端密碼加鹽
  • 4 更過的功能點,好比不止製造json,製造xml,引入echarts加入數據可視化之類的
相關文章
相關標籤/搜索