这是一篇求职文章 年龄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
加入数据可视化之类的