這是一篇求職文章 年齡21 座標成都 找一份vue.js移動端H5工作
一份沒有任何包裝純真實的簡歷 簡歷戳這
求職文章一共有兩篇 另外一篇請點選一個基於Vue+TypeScript的[移動端]Vue UI
專案簡介
名字
JsonMaker
作用
新增api和屬性,用於製造JSON
地址
技術棧
前端
pug scss vue vue-router vuex axios nuxt element-ui
複製程式碼
後端
node express mongoose mongodb jsonwebtoken
複製程式碼
專案目錄
前端
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
加入資料視覺化之類的