This is a job hunting article age 21 coordinates chengdu looking for a vue.js mobile TERMINAL H5 job

A resume that is pure truth without any packagingResume poke it

There are two job search articles and click on the other oneA mobile Vue UI based on Vue+TypeScript

Project introduction

The name

JsonMaker

role

Add apis and properties to make JSON

address

  github

Technology stack

The front end

pug scss vue vue-router vuex axios nuxt element-ui
Copy the code

The back-end

node express mongoose mongodb jsonwebtoken
Copy the code

Project directory

The front end

Assets and JS logic components component directory (because it references the Elementary-UI project and doesn’t have a separate component) Middleware directory Pages directory Plugins directory static directory of static files Store Directory of vuex states

The back-end

Actions JS event directory Config Configuration directory Lib JS template directory Middleware Express Middleware directory Model Mongoose. Model directory Plugins directory schmea Mongoose.Schema directory app.js Main app router.js route

The picture

Architectural ideas

The front end

First let’s take a look at our configuration in nuxt.config.js, and we’ll go through it one by one

nuxt.config.js

Nuxt. Config. Js configuration

module.exports = {
  // html
  head: {
    title: 'JsonMaker a JSON maker '.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 User Made JSON, a full stack project with a front end based on Nuxt Vuex Pug Scss Axios Element - UI back end based on Node Express Mongoose mongodb JsonWebToken '}].link: [{rel: 'icon'.type: 'image/x-icon'.href: 'https://nav.qymh.org.cn/static/images/q.ico'}},/ / global CSS
  css: [
    // reset css
    '~/assets/style/normalize.css'.// common css
    '~/assets/style/common.css'.// element-ui css
    'element-ui/lib/theme-chalk/index.css'].// Load the color
  loading: { color: '#409EFF' },
  / / the plugin
  plugins: [
    // element-ui
    { src: '~/plugins/element-ui' },
    // widget
    { src: '~/plugins/widget' },
    // Baidu statistics
    { src: '~/plugins/baiduStatistics'.ssr: false },
    // Webmaster platform
    { src: '~/plugins/baiduStation'.ssr: false}]./ / webpack configuration
  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 configuration
    postcss: [require('autoprefixer') (the)],/ / public library
    vendor: ['axios'.'element-ui']},router: {
    // Authenticate middleware
    middleware: 'authenticate'}}Copy the code

Resolve the plug-in in nuxt.config.js

I refer to four of them in the plugin

  • 1 element – UI plug-in
  • The widget contains the cookie operation method

    throughVue.use()Introduce plug-ins directly through the VUE environmentthiscall

    This location has a pit, the server side is notdocumentOf this property, so you can’t get cookies that way

    So we also need to construct a fromreqThe function to get the token, I wrote it downassets/lib/utilsUnder the

    cookiefromreq.headers.cookieReads the
  • 3. Introduce Baidu statistics
  • 4. Introduce baidu webmaster platform

Parse middleware in nuxt.config.js

The middleware section is a file that contains functions to verify user login and automatic login, and it has a pit in it. Unlike non-NUxT projects, our normal VUE projects do this This is done in router.beforeeach global hooks, and in Nuxt you need to validate not only the client but also the server

  • Procedure 1 Set the login pagemeta: { auth: true }, unnecessary page Settingsmeta: { notAuth: true }
  • 2 If there is any page that needs to be logged intokenExit directly, or get in two parts if you don’ttokenOne client, one server, and finally iftokenThere are

    An API call to the global system parameter is performed and then writtenvuexIf no, return to the login page
  • 3 in somenotAuth authWhen none exists, check the storageuserNameIf the attribute exists, it will jump to the user home page. If it does not exist, it will jump to the login interface

Global Parameter Configuration

Everyone understands the global configuration, see the habit, some people like to put a lot of configuration to the global, such as the vue – the router configuration, I don’t think it’s necessary I usually put some configuration in global configuration is not so complicated, such as project name and all kinds of plug-in configuration, this project is not big, so the global configuration also not too much assets/lib/appconfig.js

const isDev = process.env.NODE_ENV === 'development'

// app
export const APPCONFIG = {
  isDebug: true
}

/ / cookie Settings
export const COOKIECONFIG = {
  expiresDay: 7
}

/ / server Settings
export const SERVERCONFIG = {
  domain: isDev ? 'http://127.0.0.1:5766' : 'https://api.qymh.org.cn'.timeout: 10000
}

Copy the code

The other configuration globally is the configuration of the API, I like to put the API in a file, and then import, it’s not a big project, there are 15 interfaces assets/lib/ API

// Get global properties
export const system = '/api/system'

/ / register
export const register = '/api/register'
/ / login
export const login = '/api/login'

/ / add API
export const addApi = '/api/addApi'
/ / get the API
export const getApi = '/api/getApi'
/ / delete the API
export const deleteApi = '/api/deleteApi'
/ / modify the API
export const putApi = '/api/putApi'

// Add attributes
export const addProperty = '/api/addProperty'
// Get attributes
export const getProperties = '/api/getProperties'
// Delete attributes
export const deleteProperty = '/api/deleteProperty'
// Modify attributes
export const putProperty = '/api/putProperty'

// Add collection
export const addCollections = '/api/addCollections'
// Get the collection
export const getCollections = '/api/getCollections'
// Delete the collection
export const deleteCollections = '/api/deleteCollections'
// Modify the collection
export const putCollections = '/api/putCollections'

Copy the code

Ajax functions request architecture

Nuxt.config.js, let’s talk about the front and back end of the separation of a big point, is the request, my habit of layer by layer from the bottom up

  • Step 1 encapsulates the interceptor

    The interceptor has several parts, oneaxiosBasic parameter configuration, one requestrequestIntercept, a responseresponseintercept

    Typically in request interception is a construct parameter, such asParameters of the encryption Sending of the request headerThis project has not done front-end parameter encryption for the time being, and I will also request the output of log

    The same is true for response interception, which prints the received parameter log and handles the error case. Let’s look at the code

    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
})

// Request interception
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 The ${JSON.stringify(data)}`)}}return config
})

// Response interception
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 ? 'Abnormal network connection' : 'Network exception, error code:${status}`.duration: 1500})}}}return {
    data: response.data
  }
})

export default ax

Copy the code
  • The second part constructs the HTTP request layer

    The bottom layer is divided into four methods,get post put delete, add, delete, change, check, usepromiseImplementation, layer by layer, let’s look at the code

assets/lib/http.js

import ax from './axios'
import Vue from 'vue'

export default {
  /** * Ajax public functions * @param {String} API API interface * @param {Object} data data * @param {Boolean} isLoading whether to load */
  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 function * @param {String} API API * @param {Object} data Data * @param {Boolean} isLoading whether to load */
  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 function * @param {String} API API * @param {Object} data Data * @param {Boolean} isLoading whether to load */
  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 function * @param {String} API API * @param {Object} data Data * @param {Boolean} isLoading whether to load */
  put(api, data, isLoading = false) {
    return new Promise((resolve, reject) = > {
      this.ajax('PUT', api, data, isLoading)
        .then(data= > {
          resolve(data)
        })
        .catch(err= > {
          reject(err)
        })
    })
  }
}

Copy the code
  • The third part is the logic code of the event, which I put inassets/actionsInside, same thingpromiseImplementation, step by step, by calling the bottom encapsulated four methods, call encapsulated global API parameters, here is an example of the API home page to get the operation event

    assets/actions/api.js
import http from '.. /lib/http'
import * as api from '.. /lib/api'

export default {

  /** * get API */
  getApi(userName) {
    return new Promise((resolve, reject) = > {
      http
        .post(api.getApi, { userName })
        .then(data= > {
          resolve(data)
        })
        .catch(err= > {
          reject(err)
        })
    })
  }

Copy the code
  • In fact, the third step can be directly referenced in vUEactionsIt encapsulates events, but this project has another layer, which is usedvuexSeal it up again

    This is still about getting the API and doing itvuex, omitting non-event code
import api from '~/assets/actions/api'
import Vue from 'vue'
const vm = new Vue()

const actions = {
  / / get the API
  async getApi({ commit }, { userName, redirect }) {
    await api
      .getApi(userName)
      .then(arr= > {
        commit('_getApi', arr)
      })
      .catch((a)= > {
        redirect({
          path: '/login'.query: {
            errorMessage: 'User does not exist, please log in again'}})})}Copy the code
  • 5 the following is invueThe introduction ofactionsNow let’s talk about the normative aspects of Vuex

Vuex architecture

  • 1 There are four attributes in VUex,state getters mutations Actions According to my architectural thinking, I will always reveal that only two can be used in VUE, one getters and one actions. Why? Mutations cannot be asynchronous because the value of the changed state will not be refreshed in the DOM

  • According to the official recommendation, there should be a this-type specially for storing the name of the mutation event. I think it is unnecessary, because it is too troublesome. According to the first point, I will directly add an underscore in front of the name that is not disclosed, just as my code above shows

  • In terms of names,actions table events,mutations table mutations, in other words, I perform event logic, such as interface requests, in Actions, and changes in the vuex state tree value, I perform in mutations

  • 4 Namespace qualification

    Be sure to add it on every modulenamespaced: trueOne is to think more clearly, and the second is to avoid repeated naming

The back-end

This project is the second time for me to write the back end with Express, but I feel that I am not mature enough in terms of architecture ideas. After writing it, I find that there are many mistakes. I’m too busy looking for a job and I’m running out of time. I’ll change it later

Let’s start with app.js

app.js

App.js does several things

  • 1 introductionmongooseAnd connectmongodb
  • 2 Set cross-domain CORS
  • 3 Import middleware and routes

Global parameters

The Node backend also has global parameters, which mainly contain a collection of error codes and some common configurations

config/nodeconfig.js


/ / token Settings
exports.token = {
  secret: 'Qymh'.expires: '7 days'
}

/ / error code
exports.code = {
  // The user does not exist
  noUser: 10001.// The password is incorrect
  wrongPassword: 10002./ / token expired
  outDateToken: 10003.// Check does not conform to the rule
  notValidate: 10004.// Existing data
  existData: 10005.// Unknown error
  unknown: 100099.// Unknown error text
  unknownText: 'Unknown error, please try logging in again'
}

// session
exports.session = {
  secret: 'Qymh'.maxAge: 10000
}

Copy the code

Data storage architecture

  • 1 Step 1 Construct a Schema

Schema was also the first one that Mongoose needed to build. The project referenced many authentication interfaces provided by the authorities. I put the configuration of Schema in config/ Schema, so let’s see what the user’s Schema was

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

Copy the code

config/schema/user.js

exports.USERSCHEMACONFIG = {
  / / account
  account: {
    type: String || Number.index: [true.'Account already exists'].unique: [true.'Account already exists'].required: [true.'Account cannot be empty'].minlength: [5.'Account length must be greater than or equal to 5'].maxlength: [18.'Account length must not exceed 18'].trim: true
  },
  / / password
  password: {
    type: String || Number.required: [true.'Password cannot be empty'].minlength: [8.'Password length must be at least 8'].maxlength: [18.'Password length must be 18 or less'].trim: true
  },
  / / name
  userName: {
    type: String || Number.index: [true.'Username already exists'].unique: [true.'Username already exists'].required: [true.'User name cannot be empty'].minlength: [2.'Name length must be 2 or greater'].maxlength: [8.'Name length must be less than or equal to 8'].trim: true
  },
  // token
  token: {
    type: String
  },
  / / schema configuration
  options: {
    versionKey: 'v1.0'.timestamps: {
      createdAt: 'createdAt'.updatedAt: 'updatedAt'}}}Copy the code
  • 2 The second step is to build the model

The model goes into the Model folder, receives the incoming Schema, and then sends out the Model. Let’s look at the user’s model

model/user.js

const mongoose = require('mongoose')
const UserSchema = require('.. /schema/user')

const UserModel = mongoose.model('UserModel', UserSchema)

module.exports = UserModel

Copy the code
  • 3 Step 3 Build the data store lib

This store is actually for actions files,actions accept routing events, and lib is responsible for storing them, including registration and login functions. And then in this lib operation, I encapsulate the processing of the data that I finally get, into the plugins directory, which includes the processing of user tokens. To handle the callback parameters for failed registration and failed login, let’s look at the user’s lib

lib/user.js

const UserModel = require('.. /model/user')
const UserPlugin = require('.. /plugins/user')

Registered / * * * * @ param {String | Number} account account * @ param {String | Number} "password," password * @ param {String | Number} The userName name * /
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)
    })
  })
}

Landing / * * * * @ param {String | Number} account account * @ param {String | Number} "password," 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)
      }
    })
  })
}

Copy the code
  • 4 Step 4 Construct routesactions

The actions directory is used to handle the receipt of routes, and then it is imported into the lib to store the data. Let’s look at the user’s actions

actions/user.js


const user = require('.. /lib/user')

/ / register
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)
    })
}

/ / login
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)
    })
}

Copy the code
  • 5 Building a Route

Router.js is the mount place for all apis, and you can mount them by referencing them in app.js. This project is not big, and it provides a total of 16 apis

Now that we’re done with the data storage step, let’s talk about express middleware

Middleware middleware

The middleware here mainly verifies that the token has expired and then returns with no action

middleware/authenticate.js

const userPlugin = require('.. /plugins/user')
const nodeconfig = require('.. /config/nodeconfig')

// Verify that the token expires
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 expired'
      }
      res.json(err)
    }
  }
  next()
}

Copy the code

My error

Back-end architecture is above these, in this back-end architecture I made a mistake, you can see my above userSchema is the apiSchema scheme in it, and then ApiSchema inside I have including two schema, a propertSchema, a collectionsSchema why I do this, because at the beginning of writing thinking is if you want to search a information from a database, this information belongs to the user, there are two methods

  • 1 directly constructs the databasemodelAnd then you store it, and then you store it with oneuserIdPoints to the user to which the current message belongs
  • 2 Put this data inuserModelIn user Model, when you look up, you look up the information that’s currently used and then you read that information

Finally I chose the second…. Because I’m thinking if I have 10W data and I only have 100 users, it’s better to find 100 than to find 10W, and there are a couple of problems with that choice

  • 1 mongooseWhen you’re saving it, if there’s too many nested objects in it you don’t want to save itapiInterface provided. I read the document several times, can only pass$set $pushTo store the most secondary properties of an object, such as the object below, is not straightforwardapiThose offering to modify collections values need to go around in other ways
[{userName: 'Qymh'.id: 'xxxxx'.api: [{id: 'xxxx'.apiName: 'test'.collections:[
                       {
                           id: 'xxxx'.age: 21.sex: man
                       }
                   ]
               }
           ]
       }
   ]
Copy the code
  • For example, if I want to find collections, I need to provide two parameters: one is the id of the user, and the other is the ID of the API, and then I need to find the API, and finally I need to extract collections. If I choose the first one, I only need the user ID

So I feel like I made a mistake here

Mount the project

  • 1 The final project is mounted through PM2

  • The node backend and front-end of the 2 project reference SSL certificates

Now the project has been put online, but my server is so bad that I can only use the student computer of 9.9 yuan bought by Ali Cloud to test and play

What we’re going to do next

This project has been written intermittently for 20 days, and many functions have not been perfected. I will do it later

  • 1 Front-end incoming parameter encryption
  • 2 API attributes added to determine the type of front-end to back-end, back-endSchema to addFor example, several types of Mongoosestring boolean schema.types.mixed
  • 3 Add salt to the back-end password
  • 4 more function points, such as not only make JSON, makexml, the introduction ofechartsAdd data visualization and things like that