Some pits and knowledge collections of Vue and SprinBook development and maintenance

Keywords: node.js Vue axios SSL Nginx

1. Perfectly Solve the Cross-domain Problem of Vue2.0+Axios Development and Production Environment

Because bloggers mainly do back-end development and automated operation and maintenance, so the front-end is basically oriented to classmates and search engine programming.. This time, a simple and elegant cross-domain solution with Vue and Axios has been thoroughly worked out, which is suitable for the development environment and production environment!

(1) Configuring development environments across domains in config/index.js

proxyTable: {
    '/api': {
        target: 'https://211.64.32.228:8899/',
        secure: false,
        changeOrigin: true,
        pathRewrite: {
            '^/api': ''
        },
        headers: {
            Referer: 'https://211.64.32.228:8899'
        }
    }
}

(2) Configure automatic selection in main.js

import axios from 'axios'
import QS from 'qs'

Vue.prototype.$axios = axios
Vue.prototype.$qs = QS

Vue.prototype.baseUrl = process.env.NODE_ENV === "production" ? "https://211.64.32.228:8899" : "/api"

(3) Use Axios in Vue files

this.axios({
    method: 'post',
    url: this.baseUrl + '/helloworld',
    data: {},
    headers: {}
}).then((response) => {
    // do some
}).catch((error) => {
    // do some
});

(4) SpringBook configuration allows cross-domain

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
}

2. AES+Base64 Encryption and Decryption User Logon Credentials in SpringBook

This year, md5 can be reversed, no matter how old it is, it can't lose its teeth.

import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

// Usage method:
// PasswordUtil.Encrypt(String)
// PasswordUtil.Decrypt(String)
public class PasswordUtil {

    // openssl rand -hex 16
    private static String salt = "38350e78e96b83e894b59cc9953af122";

    public static String Encrypt(String password) throws Exception {
        byte[] raw = salt.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec sRawSpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, sRawSpec);
        byte[] encrypted = cipher.doFinal(password.getBytes(StandardCharsets.UTF_8));
        return new Base64().encodeToString(encrypted);
    }

    public static String Decrypt(String password) throws Exception{
        byte[] raw = salt.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec sRawSpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, sRawSpec);
        byte[] encrypted = new Base64().decode(password);
        byte[] original = cipher.doFinal(encrypted);
        return new String(original, StandardCharsets.UTF_8);
    }
}

3. Pure CSS Custom Super-concise File Upload Control input

Mainly to solve the problem of custom CSS style and Vue upload files.... Notes do not write, calm down to a little read to understand!

<template>
  <div class="upload">
    <div class="upload-demo-show">
      <input accept="image/png,image/gif,image/jpeg" type="file" class="upload-demo-button" @change="handleUploadDemoButtonChange($event)">
      <i class="el-icon-upload upload-btn-icon" :style="uploadTipsStyle"></i>
      <div class="upload-demo-text" :style="uploadTipsStyle">{{uploadTips}}</div>
    </div>
    <div class="upload-button">
      <el-button :loading="isProcessUpload" type="success" icon="el-icon-right" circle @click="handleProcessUpload"></el-button>
    </div>
  </div>
</template>

<script>
    export default {
        name: "Upload",
        data: function () {
            return {
                uploadImageObject: '',
                isProcessUpload: false,
                uploadTips: 'Click Upload',
                uploadTipsStyle: {
                    'color': 'gray'
                }
            }
        },
        mounted() {
            this.$store.dispatch('commitNormalStepNumber', 2)
        },
        methods: {
            handleUploadDemoButtonChange: function (e) {
                if ((e.target.files[0].size / 1024) >= 400) {
                    this.$message.error('Uploaded files exceed the specified size, Please re-select');
                } else {
                    this.uploadImageObject = e.target.files[0];
                    this.uploadTips = e.target.files[0].name;
                    this.uploadTipsStyle.color = '#409EFF';
                }

            },
            handleProcessUpload: function () {

                this.isProcessUpload = true;

                // Using FormData to Solve the Problem of Not Obtaining Parameters in POST Remote API
                let formData = new FormData();
                formData.append('uuid', this.$store.getters.getFormUsername);
                formData.append('file', this.uploadImageObject);

                this.$axios({
                    url: this.baseUrl + '/upload/image',
                    method: 'post',
                    headers: {
                        'Content-Type': 'multipart/form-data',
                        token: this.$store.getters.getToken
                    },
                    data: formData
                }).then((response) => {
                    if (response.data === "OK") {
                        this.isProcessUpload = false;
                        this.$router.push({
                            path: '/finish'
                        });
                    } else if (response.data === "UNAUTHORIZED"){
                        this.$message.error('Please login and try again');
                    } else if (response.data === "INTERNAL_SERVER_ERROR") {
                        this.$message.error('I'm sorry, We made some mistakes.');
                    } else if (response.data === "BAD_REQUEST") {
                        this.$message.error('Your request is incorrect., Documentation may be a bit problematic.');
                    } else {
                        this.$message.error('An unpredictable error occurred., Please re-login');
                        console.log(response.data)
                    }
                }).catch((err) => {
                    this.$message.error('Network request error');
                    console.log(err)
                });

                this.isProcessUpload = false;
            }
        }
    }
</script>

<style scoped>
  .upload {
    width: 50%;
    margin: 0 auto;
    padding-top: 35px;
  }

  .upload-button {
    padding-top: 25px;
    text-align: center;
  }

  .upload-demo-button {
    width: 349px;
    height: 149px;
    opacity: 0;
  }

  .upload-demo-button:hover {
    cursor: pointer;
  }

  .upload-demo-show {
    border-radius: 5px;
    border: lightgray 1px dashed;
    width: 350px;
    height: 150px;
    margin: 0 auto;
    position: relative;
  }

  .upload-btn-icon {
    position: absolute;
    top: 15%;
    left: 40%;
    font-size: 50pt;
    z-index: -1;
  }

  .upload-demo-text {
    z-index: -1;
    position: absolute;
    top: 58%;
    width: 250px;
    text-align: center;
    left: 50%;
    font-size: 10pt;
    margin-left: -125px;
  }
</style>

IV. Vuex Best Practices

(1) Defining store/index.js should actually be modular beforehand, but I'm too lazy.

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const state = {
  token: ''
};

const getters = {
  getToken(state) {
    return state.token
  }
};

const mutations = {
  setToken(state, token) {
    state.token = token
  }
};

const actions = {
  commitToken({commit}, token) {
    return commit('setToken', token)
  }
};

const store = new Vuex.Store(
  {
    state,
    getters,
    mutations,
    actions
  }
);

export default store;

(2) Reference in main.js

import store from './store'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: {App},
  template: '<App/>',
  store
})

(3) Reference in Vue Components

this.$store.dispatch('commitToken', value);  // Store data to Store
this.$store.getters.getToken;                // Read the data in Store

V. Vue-router jump

However, the official documents are clearly written, but I am not bothered to look through the official documents.

this.$router.push({
    path: '/normal'
});

6. Nginx cooperates with SpringBook to realize HTTPS strengthening and API gateway load balancing

user  nginx;
worker_processes  16;

error_log  logs/error.log;

pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    gzip  on;

    // Setting up reverse proxy
    upstream apiserver {
        server 127.0.0.1:8090 weight=1;
        server 127.0.0.1:8091 weight=1;
        server 127.0.0.1:8092 weight=1;
        server 127.0.0.1:8093 weight=1;
    }

    server {
        listen       80;
        server_name  upload-image;

        // Setting up HTTPS forcefulness
        rewrite ^(.*)$ https://$host$1 permanent;
    }

    // API interface uses HTTPS
    server {
        listen       8899 ssl;
        server_name  upload-image-api;

        // Configuring HTTPS
        ssl_certificate      ../ssl/server.crt;
        ssl_certificate_key  ../ssl/server.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers ALL:!DH:!EXPORT:!RC4:+HIGH:+MEDIUM:-LOW:!aNULL:!eNULL;
        ssl_prefer_server_ciphers  on;

        // Adding Supported HTTPS Protocol
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

        location / {
            proxy_pass http://apiserver;
        }
    }

    server {
        // Jump the front-end static distribution settings to the interface
        listen       443 ssl;
        server_name  upload-image-ssl;

        ssl_certificate      ../ssl/server.crt;
        ssl_certificate_key  ../ssl/server.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers ALL:!DH:!EXPORT:!RC4:+HIGH:+MEDIUM:-LOW:!aNULL:!eNULL;
        ssl_prefer_server_ciphers  on;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

        location / {
            root   html;
            index  index.html index.htm;    
        }

        error_page  404              /404.html;
        error_page   500 502 503 504  /50x.html;

        location = /50x.html {
            root   html;
        }
    }

}

VII. The Vue components are in the middle horizontally and vertically.

That's the problem. I can't remember how to do it, but I can always Baidu, even Google doesn't need to do it.

(1) index.html, set on the style tag

html, body {
    margin: 0;
    padding: 0;
}

(2) Component style

.box {
    top: 50%;
    left: 50%;
    position: absolute;
    transform: translate(-50%, -50%);
    min-width: 450px;
    max-width: 550px;
    min-height: 500px;
    max-height: 550px;
}

8. Interaction between Vue and PC Camera

Recently, I trained a neural network for face recognition with Caffe, and we can log in face in the future.~
So, let's get the PC's webcam first... because the photos taken by the computer are not very pleasant, so the mirror is flipped.
But, you're so ugly... It's my CSS that makes you look better, haha ha.~

<template>
  <div class="login-with-facedetection-main box">
    <div class="login-with-facedetection-main-head">
      <img src="../../assets/qimo2-blue.svg" alt="" width="65" height="65">
    </div>
    <div class="login-with-title">Green Mang Cloud(Qimo Cloud)Console</div>
    <div class="login-with-subtitle">Face detection login, click on the picture to start detection</div>
    <div style="width: 100%; height: 10px"></div>
    <div class="login-with-form" @click="handleFaceDetection" v-loading="hasLoginFormLoading">
      <video class="video-box" src="" autoplay="autoplay" v-if="hasCameraOpen"></video>
      <img class="photo-box" :src="faceImage" alt="" v-if="hasTakePhoto">
      <canvas id="canvas" width="270" height="270" style="display: none;"></canvas>
    </div>
    <LoginType/>
  </div>
</template>

<script>
    import LoginType from "../common/LoginType";

    export default {
        name: "LoginWithFaceDetection",
        components: {LoginType},
        data: function () {
            return {
                streamPicture: '',
                faceImage: '',
                hasCameraOpen: true,
                hasTakePhoto: false,
                faceImageFile: '',
                hasLoginFormLoading: false,
                clickTimes: 0
            }
        },
        methods: {
            handleFaceDetection: function () {
                if (this.clickTimes === 0) {
                    let video = document.querySelector('video');
                    this.takePhoto();
                    this.closeCamera();
                    this.postFaceDetection();
                    console.log("Face De");
                    this.clickTimes = 1;
                }
                // TODO: Show bullet windows, duplicate submissions
            },
            connectToCamera: function () {
                let self = this;
                navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
                if (navigator.getUserMedia) {
                    // Call User Media Device, Visit Camera
                    navigator.getUserMedia({
                        video: {
                            width: 270,
                            height: 270
                        }
                    }, function (stream) {
                        let video = document.querySelector('video');
                        video.srcObject = stream;
                        self.streamPicture = stream;
                        video.onloadedmetadata = function (e) {
                            video.play();
                        };
                    }, function (err) {
                        // TODO: Display error pop-ups, unsupported media types
                    })
                } else {
                    // TODO: Show the wrong pop-up window, unable to access the camera
                }
            },
            closeCamera: function () {
                this.streamPicture.getTracks()[0].stop();
            },
            takePhoto: function () {
                let video = document.querySelector('video');
                let canvas = document.getElementById('canvas');
                let context = canvas.getContext('2d');
                context.drawImage(video, 0, 0, 270, 270);
                let image = canvas.toDataURL('image/png');
                this.hasCameraOpen = false;
                this.hasTakePhoto = true;
                this.faceImage = image;
                this.faceImageFile = this.dataURLtoFile(image, 'face-detection.png')
            },
            dataURLtoFile: function (dataurl, filename) {
                let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
                    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
                while (n--) {
                    u8arr[n] = bstr.charCodeAt(n);
                }
                return new File([u8arr], filename, {type: mime});
            },
            postFaceDetection: function () {
                this.hasLoginFormLoading = true;

                // TODO: Send pictures for recognition

                setInterval(() => {
                    this.hasLoginFormLoading = false;
                    clearInterval();
                }, 5000);
            }
        },
        mounted() {
            this.connectToCamera();
        },
        destroyed() {
            this.closeCamera();
        }
    }
</script>

<style scoped>

  .photo-box {
    margin-top: 0;
    width: 270px;
    height: 270px;
    border-radius: 20px;
    transform: rotateY(180deg);
    -webkit-transform: rotateY(180deg); /* Safari And Chrome */
    -moz-transform: rotateY(180deg);
  }

  .video-box {
    transform: rotateY(180deg);
    -webkit-transform: rotateY(180deg); /* Safari And Chrome */
    -moz-transform: rotateY(180deg);
    margin-top: 0;
    width: 270px;
    height: 270px;
    object-fit: contain;
    border-radius: 20px;
  }

  .login-with-facedetection-main {
    width: 450px;
    height: 500px;
    box-shadow: 0 0 10px lightgrey;
  }

  .login-with-facedetection-main-head {
    width: 100%;
    height: 65px;
    padding-top: 35px;
    text-align: center;
  }

  .login-with-form {
    width: 270px;
    margin: 0 auto;
    height: 270px;
    text-align: center;
    background-color: #F1F3F4;
    border-radius: 20px;
  }

  .login-with-title {
    font-size: 15pt;
    text-align: center;
    width: 100%;
    padding-top: 20px;
  }

  .login-with-subtitle {
    font-size: 11pt;
    text-align: center;
    width: 100%;
    padding-top: 5px;
  }

  .box {
    top: 50%;
    left: 50%;
    position: absolute;
    transform: translate(-50%, -50%);
    min-width: 450px;
    max-width: 550px;
    min-height: 500px;
    max-height: 550px;
  }
</style>

9. Highly adaptive pricing in Vue

Let the height of this component always equal the height of the browser window!

(1) Component Binding CSS Styles

:style="sidebarStyle"

(2) Dynamic binding of JavaScript data

export default {
    name: "Admin",
    data: function () {
        return {
            isCollapse: true,
            sidebarStyle: {
                'height': ''
            }
        }
    },
    methods: {
        redressHeight: function () {
            this.sidebarStyle.height = window.innerHeight  + 'px';
        }
    },
    created() {
        window.addEventListener('resize', this.redressHeight);
        this.redressHeight();
    },
    destroyed() {
        window.removeEventListener('resize', this.redressHeight);
    }
}

Continuous updates are ongoing ___________.

Posted by buluk21 on Thu, 10 Oct 2019 05:17:27 -0700