名字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
加入数据可视化之类的