This article according to the completed development process from the perspective of the example, step by step to build an out-of-the-box application egg.js, let’s start!

Full project address: gitee.com/koukaile/eg…

The contents of this series

  • Egg enterprise-level combat (a) – framework building
  • Egg Enterprise level combat (ii) – login module
  • Egg enterprise practice (iii) – database

01. Login module

1. Write the controller in Controller

The controller – > new user. Js

'use strict'; /** * @Controller **/ const Controller = require('egg').Controller; Class LoginController extends Controller {// Login async login() {/** * @summary login * @description Login interface * @Router Post /user/login * @request body login Configures the request to carry parameters * @request Header String Token eg: Write your token at here * @response 200 JsonResult const {CTX} = this; ctx.body = await this.service.login.login(); } } module.exports = LoginController;Copy the code

2. Write the service layer in service

'use strict';

const Service = require('egg').Service;

class UserService extends Service {
  //登录
  async login(){
    //your service
  }
}

module.exports = UserService;
Copy the code

3. Asymmetric encryption on the front-end and decryption on the back-end node.js

Front End (VUE)

When doing the login function in the project, the parameters are generally submitted to the server for verification through form form or Ajax. In this process, if the login password is encrypted at the front end, the security is definitely better than that of direct submission.

  • Installation:

npm install encryptjs --save-dev

  • Vue components:

Import {JSEncrypt} from ‘JSEncrypt’ Stomp Hole Note: Vue3 cannot run properly because errors are detected. Here you need to use version 3.0.0-rc1, the new version will report an error, other versions can also try, not all tests.

  • Use within method:
Let encryptor = new JSEncrypt() encryptor.setPublicKey(publicKey)//publicKey is a key given by the background let rsaPassWord = Encryptor. encrypt(password)//rsaPassWord is the resulting encrypted stringCopy the code

Generate key:

Use online generated web.chacuo.net/netrsakeypa directly in here…

Back end (Egg)

  • Install dependencies

npm install --save node-jsencrypt

  • configuration
//config.default.js config.private key config.private_key = 'your private key'Copy the code
  • use
'use strict'; const Service = require('egg').Service; Const JSEncrypt = require(' nod-jsencrypt ') class UserService extends Service {// login(){const {CTX} = this // setPrivateKey const prvKey = this.app.config.private_key let jsencrypt = new jsencrypt () jsencrypt.setPrivateKey(prvKey) // Decrypt data let paramsData = CTX. Request. Body. RsaParams let prvData = JSON. Parse (jsencrypt. Decrypt (paramsData)); const user_number = prvData.user_number const user_password = prvData.user_password console.log(user_number,user_password) } } module.exports = UserService;Copy the code

4. Detect users, update data, generate tokens, and store user information

// let user = await ctx.model.User.findOne({include:{as:'menu',model:ctx.model.UserLogin},where:{user_number:user_number}}) if(! User){return ctx.fail(' account or password does not exist ')} // Verify password let res = Bcrypt.com pareSync (user_password, user. The dataValues. The menu. The dataValues. User_password) if (res) {/ / update the login data await ctx.model.User.update({login_num:user.dataValues.login_num+1,last_login_time:moment()},{where:{uuid:user.dataValues.uuid }}); // generate token const token = app.jwt.sign({'uuid': User.datavalues. Uuid, // Token data needed to store}, app.config.jwt.secret, {expiresIn: '60m'}); // 60 minutes token expires // Store the session parameter ctx.session.uid = user.datavalues. ctx.session.user_number = user_number; Return ctx.success(" Login successful!" ,{ token }); }else{return ctx.fail(" Wrong username or password!" ); }Copy the code

5. Complete login module code

Async login(){const {CTX,app} = this // Set private key const prvKey = this.app.config.private_key let jsencrypt = new JSEncrypt () JSEncrypt. SetPrivateKey (prvKey) / / decrypt data let paramsData = CTX. Request. Body. RsaParams let prvData = JSON.parse(jsencrypt.decrypt(paramsData)); Const user_number = prvdata. user_number const user_password = prvdata. user_password let user = await ctx.model.User.findOne({include:{as:'menu',model:ctx.model.UserLogin},where:{user_number:user_number}}) if(! User){return ctx.fail(' account or password does not exist ')} // Verify password let res = Bcrypt.com pareSync (user_password, user. The dataValues. The menu. The dataValues. User_password) if (res) {/ / update the login data await ctx.model.User.update({login_num:user.dataValues.login_num+1,last_login_time:moment()},{where:{uuid:user.dataValues.uuid }}); // generate token const token = app.jwt.sign({'uuid': User.datavalues. Uuid, // Token data to store}, app.config.jwt.secret, {expiresIn: '5m'}); // 5 minutes token expires // Store the session parameter ctx.session.uid= user.datavalues. ctx.session.user_number=user_number; Return ctx.success(" Login successful!" ,{ token }); }else{return ctx.fail(" Wrong username or password!" ); }}Copy the code

6. The session storage cannot be obtained

When testing according to the official document, we can find that the session is set when logging in, but it cannot be obtained from other services. The solution is as follows:

1. If the front-end VUE is a request initiated by AXIos, it should be set as follows:

import axios from 'axios'
axios.defaults.withCredentials=true
Copy the code

The foreground local runtime uses the form 127.0.0.1 to match the background CORS

2. The back-end egg.js framework uses the egg-CORS plug-in to configure cross-domains

In the config.default.js configuration file, press the following Settings: config.cors = {origin: 'http://127.0.0.1:8080', / / must be domain name port credentials: true, / / credentials is set to true, consistent and front-end allowMethods: 'the GET and POST'}Copy the code

02. Registration module

1. Write the Controller in Controller

'use strict'; /** * @Controller **/ const Controller = require('egg').Controller; Class LoginController extends Controller {// Register async Register () {/** * @summary register * @description Register interface * @Router post /user/register * @request body user_register Configures the request to carry parameters * @request header String Token eg: Write your token at here * @response 200 JsonResult const {CTX} = this; ctx.body = await this.service.user.register(); } } module.exports = LoginController;Copy the code

2. Write business code in Service

1. The user password is encrypted with hash and salt

  • Install the bcryptjs plugin (bcryptjs is a third-party encryption library used to implement bcrypt encryption in Node environment).

npm install bcryptjs -S

  • The introduction of the plugin

let bcrypt = require('bcryptjs');

  • Usage examples
// Generate hash password const salt = bcrypt.gensaltsync (10); Const hash = bcrypt.hashsync (" password ", salt); // Store hashin your password DB. // Verify password bcrypt.compareSync(" password ", hash); // true bcrypt.compareSync(" password ", hash); // falseCopy the code

2. Complete code of registration module

// Register async register(){const {CTX,app} = this // Set private key const prvKey = this.app.config.private_key let jsencrypt = new JSEncrypt () JSEncrypt. SetPrivateKey (prvKey) / / decrypt data let paramsData = CTX. Request. Body. RsaParams let prvData = JSON.parse(jsencrypt.decrypt(paramsData)); Const is_user_numnber = await ctx.model.user. findOne({where:{user_number: prvdata.user_number}}) if(! PrvData. User_number | | prvData. User_number = = ' ') {return CTX. Warn (' account can't be empty)} else if (! PrvData. User_password | | prvData. User_password = = ' ') {return CTX. Warn (' password cannot be empty)} else if (is_user_numnber! =null){return ctx.warn(' account registered ')} else{// Hash the password,salt with salt const salt = bcrypt.gensaltsync (10) const user_password =  bcrypt.hashSync(prvData.user_password, salt); let res = {}; Const params = {uuid: ctx.helper.uuidset (),// this method is an extension method, at /extend/helper.js, Use to generate uuid user_name: ctx.helper.makename (),// This method is an extension method, at /extend/helper.js, User_number, user_password:user_password, user_type:0, salt:salt, create_time:new Date() } try { await ctx.model.UserLogin.create(params) res = await ctx.model.User.create(params); Ctx.logger. info(res) return ctx.success(' Registered successfully '); } catch (err) {ctx.logger.error(err); return ctx.fail(err) } } }Copy the code

03. Log out

1. Write the controller in Controller

'use strict'; /** * @Controller **/ const Controller = require('egg').Controller; Class LoginController extends Controller {// logout async logout() {/** * @summary logout * @description logout of the interface * @router Post /user/logout * @request header String Token eg: Write your token at here * @response 200 JsonResult const {CTX} = this; ctx.body = await this.service.user.logout(); } } module.exports = LoginController;Copy the code

2. Write services in service

'use strict'; const Service = require('egg').Service; Class UserService extends Service {// Exiting async Logout (){const {CTX} = this const token = ctx.request.header.token Let uuid = ctx.helper.uuidGet(token) // Custom extension used to obtain token and decrypt let user = await ctx.model.user. findOne({where:{uuid:uuid}}) Ctx.session. uuid = null ctx.session. user_Number = null Return ctx.success(" Logged out successfully! ); }else{return ctx.fail(" Logout failed!" ); } } } module.exports = UserService;Copy the code

04. JWT verifies user login

1. Install the JWT

npm install egg-cors egg-jwt --save

2. Configuration JWT

//config/plugin.js //jwt jwt: { enable: true, package: 'egg-jwt', }, //config/config.default.js config.jwt = { secret: '123456'};Copy the code

3. Write jwtErr middleware

//middleware/jwtErr.js module.exports = (options) => { return async function jwtErr(ctx, next) { const token = ctx.request.header.token; let decode = ''; If (token) {try {// decode token decode = ctx.app.jwt.verify(token, options.secret); await next(); } catch (error) { ctx.status = 401; Ctx. body = {massage: 'token has expired, please re-log in ', code: -1,}; return; } } else { ctx.status = 401; Ctx. body = {message: 'token does not exist ', code: -1,}; }}; }Copy the code

Use 4.

Add middleware interception at routes that require verification of user login to access

'use strict'; /** * @param {Egg.Application} app - egg application */ module.exports = app => { const { router, controller , middleware } = app; // Redirect to Swagger router. Redirect ('/', '/swagger-ui.html', 302); // Redirect to Swagger router. // middleware const jwtErr = middleware.jwtErr(app.config.jwt) // rotuer module router.post('/home/test',jwtErr,controller.home.test); /* user */ //register router.post('/user/register',controller.user.register); // login router.post('/user/login', controller.user.login); // logout router.post('/user/logout', controller.user.logout); };Copy the code

5. Vue reception desk processing

import axios from 'axios' import DOMAIN_NAME from './config.js' import router from '.. / * * / router 'request interceptor * / axios. Interceptors. Request. Use ((config) = > {/ / pretreatment request information console.log('RequestMessage:',config,sessionStorage.getItem('token')) return config }, Request (error) = > {/ / pretreatment exception thrown error return Promise. Reject (error)})/response interceptor * * * / axios. Interceptors. Response. Use ((res) = > { // Const status = parseInt(res.status) switch (status) {case 200 :return res; default:return Promise.reject({ code: status, msg: res.data.data.message }); }}, (error) => { const status = (error.toString().substring(error.toString().length-3,error.toString().length)) Switch (status){case '404': console.error(' Request address not found ~') //router.push({path:'/error',query:{status:404}}) break; Case '401': console.error(' Router. push ') router.push('/login') break; Case '500': console.error(' server internal error ~') //router.push({path:'/error',query:{status:500}}) break; } return promise.reject (error)}) /** * returns the axios method * @param URL (baseURL does not append to the URL if absolute addresses are passed) * @param method * @param timeout * @param data * @param headers * @param dataType * @returns {AxiosPromise} */ export default function (url, Post method = 'post', // timeout = 2000, // data = {}, // request headers = {' content-type ':DOMAIN_NAME. Request_header. application, 'authorization':sessionStorage.getItem('token'), 'token':sessionStorage.getItem('token') }, // dataType = 'json'}) {const config = {method: method, timeout: timeout, url: url, baseURL: DOMAIN_NAME.URL_SERVER_MICRO, data: data, headers: headers, dataType: dataType, withCredentials:true } return axios(config) }Copy the code

05. Global error handling

1. The configuration

Returns {Function} */ module.exports = () => {return async Function ErrorHandler (CTX) {/ / record error log CTX. Logger. Error (CTX) response) const status = CTX. Status | | 500; Const error = status === 500&& ctx.app.config.env === 'prod'? 'Server exception, please contact customer service. ': ctx.response.message; If (ctx.acceptJSON) {switch(status){case 404: ctx.status = 404 CTx. body = {code:'-1', message: 'Not Found' }; break; case 500: ctx.status = 500 ctx.body = {code:'-1', message: JSON.stringify(error) }; break; default: ctx.status = status ctx.body = {code:'-1', message: ctx.error }; break; } } else { await ctx.render('500',{msg:JSON.stringify(error)}); }}; };Copy the code

2. Use

Add global middleware in config.default.js

// add your middleware config here
config.middleware = ['errorHandler'];
Copy the code

6. Summary

If all of the above is completed, then the Node framework login module is successfully constructed. Go ahead and give it a thumbs up if you like it