一个nuxt(vue)+mongoose全栈项目聊聊我粗浅的项目架构
這是一篇求職文章 年齡21 坐標成都 找一份vue.js移動端H5工作
一份沒有任何包裝純真實的簡歷 簡歷戳這
求職文章一共有兩篇 另外一篇請點擊一個基于Vue+TypeScript的[移動端]Vue UI
項目簡介
名字
JsonMaker
作用
添加api和屬性,用于制造JSON
地址
??github
技術(shù)棧
前端
pug scss vue vue-router vuex axios nuxt element-ui 復制代碼后端
node express mongoose mongodb jsonwebtoken 復制代碼項目目錄
前端
assets 資源文件和js邏輯存放處
components 組件目錄 (因為引用了element-ui 項目不大 沒單獨構(gòu)造組件)
layouts 布局目錄(此項目沒用上)
middleware 中間件目錄
pages 頁面目錄
plugins 插件目錄
static 靜態(tài)文件目錄
store vuex狀態(tài)數(shù)目錄
后端
actions js事件目錄
config 配置目錄
lib js模版目錄
middleware express中間件目錄
model mongoose.model 目錄
plugins 插件目錄
schmea mongoose.Schema 目錄
app.js 主app
router.js 路由
圖片
架構(gòu)思路
前端
首先我們大致了解一下我們這個nuxt.config.js中的配置,之后會一個一個講解
nuxt.config.js
nuxt.config.js 配置
module.exports = {// htmlhead: {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'}]},// 全局csscss: [// 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' },// 百度統(tǒng)計{ src: '~/plugins/baiduStatistics', ssr: false },// 百度站長平臺{ src: '~/plugins/baiduStation', ssr: false }],// webpack配置build: {extend(config, { isDev, isClient }) {// eslintif (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環(huán)境下的this調(diào)用
這個位置有一個坑,服務器端是沒有document這個屬性的,所以沒法獲取通過這種方式獲取cookie
所以我們還需要構(gòu)造一個從req獲取token的函數(shù),我寫在了assets/lib/utils下
cookie是從req.headers.cookie中讀取的 - 3 引入百度統(tǒng)計
- 4 引入百度站長平臺
解析 nuxt.config.js 中的 middleware
middleware目中就一個文件,這個文件包含了驗證用戶登陸和自動登陸的功能
這個位置也有一個坑,與非nuxt項目不同,我們平常的vue項目這個操作
是在router.beforeEach全局鉤子里進行驗證,而且在nuxt中你不光要驗證客戶端也要驗證服務器端
大體思路就幾點
- 1 在需要登陸的頁面設(shè)置meta: { auth: true },不需要的頁面設(shè)置meta: { notAuth: true }
- 2 當處于需要登陸的頁面如果有token直接退出,沒有則分兩部獲取token,一個客戶端,一個服務器端,最后如果token存在
則執(zhí)行全局系統(tǒng)參數(shù)的api調(diào)用然后寫入vuex,如果不存在則返回登陸界面 - 3 在某些notAuth auth 都不存在時,檢查存放的userName屬性存在不,存在就跳到用戶首頁,不存在則跳到登陸界面
全局參數(shù)配置
每個人對這個全局配置理解不一樣,看習慣,有人喜歡把很多配置都往全局放,比如vue-router的配置,我覺得沒必要
我一般在全局配置中放一些配置沒那么復雜的,諸如項目名字啊還有各類插件的配置,這個項目不大,所以全局配置也不太多 assets/lib/appconfig.js
全局還有一個配置就是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函數(shù)請求架構(gòu)
nuxt.config.js聊完了,我們來聊聊前后端分離的一個大點,就是請求,我的習慣的一層一層從底部往上抽離
- 1 第一步,封裝攔截器
攔截器就幾個部分,一個axios基礎(chǔ)參數(shù)配置,一個請求request攔截,一個響應response攔截
一般在請求攔截就是構(gòu)造參數(shù),比如參數(shù)加密 請求頭的發(fā)送 之類的,這個項目暫時還沒做前端參數(shù)加密嗎,同時我也會在請求輸出log日志
響應攔截也是一樣的,輸出接收到的參數(shù)日志并處理出錯的情況,我們來看看代碼
assets/lib/axios.js
- 2 第二部構(gòu)造http請求底層
底層分裝了4個方法,get post put delete, 增刪改查,用promise實現(xiàn),一層一層往上套,我們來看看代碼
assets/lib/http.js
import ax from './axios' import Vue from 'vue'export default {/*** ajax公用函數(shù)* @param {String} api api接口* @param {Object} data 數(shù)據(jù)* @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 } = resif (data.error_code) {isLoading && loading.close()reject(data)} else {isLoading && loading.close()resolve(data)}})})},/*** post函數(shù)* @param {String} api api接口* @param {Object} data 數(shù)據(jù)* @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函數(shù)* @param {String} api api接口* @param {Object} data 數(shù)據(jù)* @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函數(shù)* @param {String} api api接口* @param {Object} data 數(shù)據(jù)* @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實現(xiàn),一步一步往上套,通過調(diào)用底層封裝的4個方法,調(diào)用封裝的全局api參數(shù),這里舉一個關(guān)于api首頁獲取的操作事件的列子
assets/actions/api.js
- 4 其實一般到第三步,直接在vue中就可以引用 actions里面封裝好的事件了,但這個項目還多了一層,是用vuex再次封了一層
這里仍然舉獲取api并操作vuex的列子,省略掉了非事件的代碼
- 5 下面就是在vue中引入actions就可以用了,接下來我們聊聊vuex的規(guī)范性
vuex的架構(gòu)
-
1 接口暴漏
vuex中有四個屬性,state getters mutations actions
按我的架構(gòu)思路,我永遠暴漏在vue中可以使用的僅有兩個,一個getters,一個actions
為什么呢?因為state改變后值不會在dom中刷新,mutations無法異步 -
2 命名
按官方建議要有一個mutations-type專門用于存放突變事件名字,我覺得沒必要,太麻煩了
按第一點所說的,未暴漏的命名我會直接在前面加一個下劃線,就像我上面的代碼顯示的那樣 -
3 事件和值的改變
從名字上來講,actions表事件,mutations表突變,換句話來說,我執(zhí)行事件邏輯,比如接口請求,我會在actions里面執(zhí)行, 而改變vuex狀態(tài)樹的值,我會在mutations里面執(zhí)行 -
4 命名空間限定
一定要在每個模塊上加入namespaced: true,一個是思路更清晰,第二個避免重復命名
后端
這個項目是我第二次用express寫后端,架構(gòu)思路感覺自己還不太成熟,寫完之后發(fā)現(xiàn)有很多地方?jīng)]對.忙著找工作,時間也來不及了,之后改改
先來看看app.js
app.js
app.js干了幾件事
- 1 引入mongoose并連接mongodb
- 2 設(shè)置跨域CORS
- 3 引入中間件和路由
全局參數(shù)
node后端也有全局參數(shù),主要包含了錯誤代碼的集合還有一些常用的配置
config/nodeconfig.js
// token設(shè)置 exports.token = {secret: 'Qymh',expires: '7 days' }// 錯誤code exports.code = {// 用戶不存在noUser: 10001,// 密碼錯誤wrongPassword: 10002,// token過期outDateToken: 10003,// 檢驗不符合規(guī)則notValidate: 10004,// 已存在的數(shù)據(jù)existData: 10005,// 未知錯誤unknown: 100099,// 未知錯誤文字unknownText: '未知錯誤,請重新登陸試試' }// session exports.session = {secret: 'Qymh',maxAge: 10000 }復制代碼數(shù)據(jù)存儲架構(gòu)思路
- 1 第一步 構(gòu)建Schema
Schema也是mongoose需要第一個構(gòu)建的,項目中引用了很多官方提供的驗證接口,我將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').USERSCHEMACONFIGconst 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, '帳號已經(jīng)存在'],unique: [true, '帳號已經(jīng)存在'],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, '用戶名已經(jīng)存在'],unique: [true, '用戶名已經(jīng)存在'],required: [true, '用戶名不能為空'],minlength: [2, '姓名長度需要大于等于2'],maxlength: [8, '姓名長度需要小于等于8'],trim: true},// tokentoken: {type: String},// schema配置options: {versionKey: 'v1.0',timestamps: {createdAt: 'createdAt',updatedAt: 'updatedAt'}} }復制代碼- 2 第二步構(gòu)建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 第三步構(gòu)建數(shù)據(jù)存儲lib
這個存儲其實是為了actions文件服務的,actions接受路由事件,而lib則負責儲存,包含了注冊和登陸功能,然后在這個lib操作里面,我將對最后獲得數(shù)據(jù)的處理進行封裝,封裝到了plugins目錄,里面就包括了,對用戶的token處理,對用于注冊失敗成功和登陸失敗成功的回調(diào)參數(shù)處理,我們來看看用戶的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 第四步 構(gòu)建路由actions
actions目錄用于處理路由的接收,然后引入lib進行數(shù)據(jù)的存儲,我們來看看用戶的actions
actions/user.js
const user = require('../lib/user')// 注冊 exports.register = async (req, res) => {const data = req.bodyconst { account, password, userName } = dataawait user.register(account, password, userName).then(doc => {res.json(doc)}).catch(err => {res.json(err)}) }// 登陸 exports.login = async (req, res) => {const data = req.bodyconst { account, password } = dataawait user.login(account, password).then(doc => {res.json(doc)}).catch(err => {res.json(err)}) }復制代碼- 5 構(gòu)建路由
router.js就是所有api的掛載處,最后在app.js里面引用即可掛載,這個項目不大,一共提供了16個api
數(shù)據(jù)儲存這5步就基本結(jié)束了,下面我們聊聊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.authenticateres.locals.token = tokenif (token) {const code = userPlugin.verifyToken(token)if (code === nodeconfig.code.outDateToken) {const err = {error_code: code,error_message: 'token過期'}res.json(err)}}next() }復制代碼我的出錯
后端的架構(gòu)就上面這些了,在這次的后端架構(gòu)中我出了一個錯誤,你可以看見我上面的userSchema是把apiSchema放在里面了,然后 apiSchema里面我有包含了兩個schema,一個propertSchema,一個collectionsSchema
為什么我會這么做呢,因為剛開始寫的時候想的是如果要從一個數(shù)據(jù)庫去搜索一個信息,這個信息是屬于用戶的,有兩個方法
- 1 直接構(gòu)造這個數(shù)據(jù)庫的model然后存儲,存儲中帶一個userId指向當前這個信息所屬的用戶
- 2 將這個數(shù)據(jù)放在userModel用戶model里,查找的時候先查找當前用于然后再讀取這個信息
最后我選擇了第二個....因為我想的是如果數(shù)據(jù)10w條,用戶只有100個,去找100個總比找10w個好,我這么選擇帶來的幾個問題
- 1 mongoose儲存的時候如果對象里面嵌套過多你想儲存是沒有api接口提供的.我看了幾遍文檔,只能通過$set $push 去存儲對象的最多第二屬性 比如下面的對象,是沒有直接的api提供去修改collections的值的,需要用其他的方法繞一圈
- 2 查找的時候挺麻煩的,比如我要查找到collections,我需要提供兩個參數(shù),一個用戶的id先找到用戶,再一個就是api的id再找到api最后再去提取collections,如果選擇第一種只需要用戶id就行了
所以我感覺自己在這一步上出錯了
項目的掛載
-
1 最后項目的掛載是通過pm2掛載的
-
2 項目的node后端和前端都引用了ssl證書
現(xiàn)在項目已經(jīng)掛到線上了但我的服務器太差,之前阿里云買的9.9元的學生機現(xiàn)在續(xù)費了只能拿來測試玩玩
之后要做的
這個項目斷斷續(xù)續(xù)寫了20來天,很多功能沒有完善,之后我會做的
- 1 前端傳入?yún)?shù)加密
- 2 api屬性加入類型判斷前端傳入后端,后端schema添加,比如mongoose的幾個類型string boolean schema.types.mixed 等
- 3 后端密碼加鹽
- 4 更過的功能點,比如不止制造json,制造xml,引入echarts加入數(shù)據(jù)可視化之類的
總結(jié)
以上是生活随笔為你收集整理的一个nuxt(vue)+mongoose全栈项目聊聊我粗浅的项目架构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker发布应用程序指南
- 下一篇: Mvp快速搭建商城购物车模块