This article is not about bringing a project to life, but how to automate it.

A requirements management and release system is developed.

With this system, you can create requirements, create release plans, create branches, deploy to test, deploy to production, go live, merge code, and so on.

1. Functional design

The 9.9 yuan Alibaba Cloud server is really slow, but it’s still enough to finish the project off. Use three directories to simulate different environments.

directory store
project Store all items, such as the system of the front and back end code.
pre-dir Pre-release environments, of course, are for testing.
pro-dir Production environment, testing is ok, deployment is online.

A picture is worth a thousand words.

Second, the system page

My task

When a new requirement is received, you can create a new requirement and create a development branch.

Release the queue

Once development is complete, you can go to the release queue and deploy to the pre-release environment for testing. The test can access the test code by specifying a Cookie. Finally, deploy online.

Project information

2. Technology stack

Front-end technology stack

Vue + elementUI, the specific code on Github, interested can see and click the star ha ~✨

Server side technology stack

Very common node.js (Koa2) + Mysql + Redis + Pm2. The specific code is available on Github, if you are interested, you can check it out and click “Star” ~✨

3. Configure Redis and Session

// utils/Store.js
const Redis = require("ioredis");
const { Store } = require("koa-session2");
 
class RedisStore extends Store {
    constructor() {
        super(a);this.redis = new Redis();
    }
 
    async get(sid, ctx) {
        let data = await this.redis.get(`SESSION:${sid}`);
        return JSON.parse(data);
    }
 
    async set(session, { sid =  this.getID(24), maxAge = 1000 * 60 * 60 } = {}, ctx) {
        try {
            console.log(`SESSION:${sid}`);
            // Use redis set EX to automatically drop expired sessions
            await this.redis.set(`SESSION:${sid}`.JSON.stringify(session), 'EX', maxAge / 1000);
        } catch (e) {}
        return sid;
    }
 
    async destroy(sid, ctx) {
        return await this.redis.del(`SESSION:${sid}`); }}module.exports = RedisStore;
Copy the code
// Import file
const session = require("koa-session2");
const Store = require("./utils/Store.js");
/ / the session configuration
app.use(session({
    store: new Store(),
    key: "SESSIONID",}));Copy the code

4. Configure Router

To make the Router look more elegant, also through middleware

// 1. Middleware configuration file
const routers = require('.. /routers');

module.exports = (app) = > {
    app.use(routers());
}

// 2, index.js entry file
const middleware = require('./middleware');
middleware(app);

// 3. Registration documents of routers
const Router = require('koa-router');
const router = new Router();
const koaCompose = require('koa-compose');

// Interface entry
const {insertDemand} = require('.. /controllers/demand/insertDemand');
const {deleteDemand} = require('.. /controllers/demand/deleteDemandByDid');
const {updateDemand} = require('.. /controllers/demand/updateDemandByDid');

/ / add prefix
router.prefix('/api');

module.exports = (a)= > {
    // New demand
    router.get('/insertDemand', insertDemand);
    // Delete the requirement
    router.get('/deleteDemand', deleteDemand);
    return koaCompose([router.routes(), router.allowedMethods()]);
}

Copy the code

5. Nginx configuration

The biggest headache is the nginx configuration, because I am not very familiar with it, it is always trial and error, stomping. But at last it worked!

Front-end projects provide services through Nignx, and Node services are forwarded through Nginx, primarily to validate various environments.

If you do not set cookies, the default access is to the online environment, setting cookies will go to the pre-release test environment, used for testing.

$proxy_node = $proxy_node = $proxy_node
map $cookie_TEST $proxy_node {
    default "";
    "1"     "1";
    "2"     "2";
    "3"     "3";
}

Release management system front-end setup
server {
    listen       80;
    server_name  test.xue.com;
    if ($proxy_node  = ' ') {set $dollar "/data/pro-dir/dandelion/dist/";
    }
    if ($proxy_node = "1") {
        set $dollar "/data/pre-dir/dandelion/dist/";
    }
    location  / {
        root $dollar;
        index index.html;
        try_files $uri $uri/ /index.html; }}Release management system backend Settings
Reverse proxy to node services
server {
    listen       80;
    server_name  m.xue.com;
    if ($proxy_node = ' ') {set $dollar "/data/pro-dir/study-demo/";
    }
    if ($proxy_node = "2") {
        set $dollar "/data/pre-dir/study-demo/";
    }
    location  / {
        root $dollar; index index.html; }}# Demo project front-end setup
server {
    listen       80;
    server_name  api.xue.com;

    location  / {
        if ($proxy_node = "") {
            set $from 3001;
            proxy_pass http://47.107.188.55:3001;
        }
        if ($proxy_node = "3") {
            set $from3002; Proxy_pass http://47.107.188.55:3002; }}}Copy the code

Vi. Some middleware

Common HTTP Settings

Solve cross-domain, OPTIONS requests, carrying Cookie credentials and other issues.

module.exports = () => {
    return async (ctx, next) => {
        ctx.set('Access-Control-Allow-Origin'.'http://test.xue.com');
        ctx.set('Access-Control-Allow-Credentials'.true);
        ctx.set('Access-Control-Allow-Headers'.'content-type');
        ctx.set('Access-Control-Allow-Methods'.'OPTIONS, GET, HEAD, PUT, POST, DELETE, PATCH'); // The value of this response header is to set a relative time from the moment the non-simple request passes the validation on the server side. When the elapsed milliseconds are less than access-control-max-age, there is no need to precheck and the request can be sent directly once. ctx.set('Access-Control-Max-Age', 3600 * 24);
        if (ctx.method == 'OPTIONS') {
            ctx.body = 200; 
        } else{ await next(); }}}Copy the code

The login

This system belongs to the mandatory login, login unified processing.

const Store = require(".. /.. /utils/Store");
const redis = new Store();
module.exports = () => {
    returnAsync (CTX, next) => {// whitelistif (ctx.request.url === '/api/login') {
            return await next();
        } 
        const SESSIONID = ctx.cookies.get('SESSIONID');

        if(! SESSIONID) {return ctx.body = {
                mes: 'No SESSIONID~',
                data: ' ',
                err_code: 1,
                success: false}; } const redisData = await redis.get(SESSIONID);if(! redisData) {return ctx.body = {
                mes: 'SESSIONID has expired ~',
                data: ' ',
                err_code: 1,
                success: false}; }if(redisData && redisdata.uid) {console.log(' logged in, user UID is${redisData.uid}`); await next(); }}}Copy the code

Operate shell scripts

For example, create a project branch

let path = ' '; // Project path
// Create a branch
const branch_name = `branch_The ${new Date().getTime()}`;
cp.execSync(`/data/dandelion-server/shell/createBranch.sh ${path} ${branch_name}`);
Copy the code
#! /bin/bash
cd The $1
git pull origin master
git checkout -b $2
git push --set-upstream origin $2
Copy the code

Connect to the database

Config.js Configuration file

let dbConf = null;
const DEV = {
    database: 'dandelion'./ / database
    user: 'root'./ / user
    password: '123456'./ / password
    port: '3306'./ / port
    host: '127.0.0.1'     // Service IP address
}

const PRO = {
    database: 'dandelion'./ / database
    user: 'root'./ / user
    password: '123456'./ / password
    port: '3306'./ / port
    host: 'xx.xx.xx.xx'     // Service IP address
}
dbConf = PRO; // This can be judged to distinguish the development environment
module.exports = dbConf;
Copy the code

Database connection file

const mysql = require('mysql');
const dbConf = require('. /.. /config/dbConf');
const pool = mysql.createPool({
  host: dbConf.host,
  user: dbConf.user,
  password: dbConf.password,
  database: dbConf.database,
})

let query = function( sql, values ) {
    return new Promise(( resolve, reject ) => {
        pool.getConnection(function(err, connection) {
            if (err) {
                reject( err )
            } else {
                connection.query(sql, values, ( err, rows) => {
                    if ( err ) {
                        reject( err )
                    } else {
                        resolve( rows )
                    }
                    connection.release()
                })
            }
        })
    })
}
module.exports = {
    query,
}
Copy the code

~ can be called in the Model layer

const {query} = require('.. /common/mysql');

class UserModel {
    constructor() {} /** * @description: create a branch based on pid and ID * @param {PID} Project ID * @param {did} Requirement ID * @param {branch_name} branch name * @return*/ async insertBranchInfo(sqlParams) {const SQL ='insert branch_info (pid, bid, branch_name, pub_time) values(? ,? ,? ,?) ';
        console.log(sql)
        let data = await query(sql, sqlParams, (err, result) => {
            return result;
        });
        returndata; }}Copy the code

Nine, the domain name

Modify hosts locally without buying a domain name (you can use tools directly)

47.107.188.xx indicates the IP address of the server

47.107.188. Xx test.xue.com 47.107.188. Xx api.xue.com 47.107.188Copy the code

conclusion

It was my first time to build a complete project, from front end to back end.

Especially backend, as a front-end small white, from learning how to use the server, to the Linux/Vim/Shell/Nignx/Pm2 / Redis/Session/Mysql/Koa2. Instead of directly looking at other projects as before, I studied step by step. Although I barely scratched the surface, I felt that my knowledge system was much richer. I also learned a lot about continuous integration. Of course, the small project I did was relatively simple. If YOU like it, please give me a “like”.

Detailed use in the front-end project, back-end project, interested in can see and click the star ha ~✨