一个nuxt(vue)+mongoose全栈项目聊聊我粗浅的项目架构

这是一篇求职文章 年龄21 坐标成都 找一份vue.js移动端H5工做
一份没有任何包装纯真实的简历 简历戳这

求职文章一共有两篇 另一篇请点击一个基于Vue+TypeScript的[移动端]Vue UI

项目简介

名字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

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加入数据可视化之类的
相关文章
相关标签/搜索