名字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 配置
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個
Vue.use()
引入插件,直接經過vue環境下的this
調用document
這個屬性的,因此無法獲取經過這種方式獲取cookiereq
獲取token的函數,我寫在了assets/lib/utils
下cookie
是從req.headers.cookie
中讀取的解析
nuxt.config.js
中的middleware
middleware
目中就一個文件,這個文件包含了驗證用戶登錄和自動登錄的功能
這個位置也有一個坑,與非nuxt
項目不一樣,咱們日常的vue
項目這個操做
是在router.beforeEach
全局鉤子裏進行驗證,並且在nuxt
中你不光要驗證客戶端也要驗證服務器端
大致思路就幾點
meta: { auth: true }
,不須要的頁面設置meta: { notAuth: true }
token
直接退出,沒有則分兩部獲取token
,一個客戶端,一個服務器端,最後若是token
存在vuex
,若是不存在則返回登錄界面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'
複製代碼
nuxt.config.js
聊完了,咱們來聊聊先後端分離的一個大點,就是請求,個人習慣的一層一層從底部往上抽離
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
複製代碼
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)
})
})
}
}
複製代碼
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)
})
})
}
複製代碼
actions
裏面封裝好的事件了,但這個項目還多了一層,是用vuex
再次封了一層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: '用戶不存在,請從新登錄'
}
})
})
}
複製代碼
vue
中引入actions
就能夠用了,接下來咱們聊聊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
幹了幾件事
mongoose
並鏈接mongodb
全局參數
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
}
複製代碼
數據存儲架構思路
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'
}
}
}
複製代碼
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
複製代碼
這個存儲實際上是爲了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)
}
})
})
}
複製代碼
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)
})
}
複製代碼
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
爲何我會這麼作呢,由於剛開始寫的時候想的是若是要從一個數據庫去搜索一個信息,這個信息是屬於用戶的,有兩個方法
model
而後存儲,存儲中帶一個userId
指向當前這個信息所屬的用戶userModel
用戶model裏,查找的時候先查找當前用於而後再讀取這個信息最後我選擇了第二個....由於我想的是若是數據10w條,用戶只有100個,去找100個總比找10w個好,我這麼選擇帶來的幾個問題
mongoose
儲存的時候若是對象裏面嵌套過多你想儲存是沒有api
接口提供的.我看了幾遍文檔,只能經過$set
$push
去存儲對象的最多第二屬性 好比下面的對象,是沒有直接的api
提供去修改collections的值的,須要用其餘的方法繞一圈[
{
userName: 'Qymh',
id: 'xxxxx',
api: [
{
id: 'xxxx',
apiName: 'test',
collections:[
{
id: 'xxxx',
age: 21,
sex: man
}
]
}
]
}
]
複製代碼
因此我感受本身在這一步上出錯了
1 最後項目的掛載是經過pm2掛載的
2 項目的node後端和前端都引用了ssl證書
如今項目已經掛到線上了但個人服務器太差,以前阿里雲買的9.9元的學生機如今續費了只能拿來測試玩玩
這個項目斷斷續續寫了20來天,不少功能沒有完善,以後我會作的
schema添加
,好比mongoose的幾個類型string
boolean
schema.types.mixed
等xml
,引入echarts
加入數據可視化之類的