这是一篇求职文章 年龄21 坐标成都 找一份vue.js移动端H5工作
一份没有任何包装纯真实的简历 简历戳这
求职文章一共有两篇 另外一篇请点击一个基于Vue+TypeScript的[移动端]Vue UI
项目简介
名字
JsonMaker
作用
添加api和属性,用于制造JSON
地址
github
技术栈
前端
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
主approuter.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
加入数据可视化之类的