Vue, SprinBoot development operation and maintenance of some of the pits and Knowledge Collection

A perfect solution Vue2.0 + Axios development and production of environmental issues across domains

As the blogger is doing backend development and automation operation and maintenance, and therefore, the basic front-end search engine for students and the complete program ... to come up with a simple and elegant cross-domain solutions and the Axios Vue fit, suitable for the development environment and production environments!

(1) arranged in a cross-domain development environment config / index.js in

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

(2) disposed in the automatic selection 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) the use of Axios in Vue file

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

(4) SpringBoot 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);
    }
}

Two, SpringBoot in AES + Base64 encryption and decryption of user logon credentials

This year, md5 is able inverse solution, and then the old nor tooth loss ah ..

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

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

// 使用方法:
// 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);
    }
}

Third, pure CSS custom super simple file upload control input

The main problem is to solve the problem and custom CSS styles Vue upload files ... do not write comments, calm down a little one can 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: '点击上传',
                uploadTipsStyle: {
                    'color': 'gray'
                }
            }
        },
        mounted() {
            this.$store.dispatch('commitNormalStepNumber', 2)
        },
        methods: {
            handleUploadDemoButtonChange: function (e) {
                if ((e.target.files[0].size / 1024) >= 400) {
                    this.$message.error('上传的文件超过指定的大小, 请重新选择');
                } else {
                    this.uploadImageObject = e.target.files[0];
                    this.uploadTips = e.target.files[0].name;
                    this.uploadTipsStyle.color = '#409EFF';
                }

            },
            handleProcessUpload: function () {

                this.isProcessUpload = true;

                // 使用FormData解决POST远程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('请登录后重试');
                    } else if (response.data === "INTERNAL_SERVER_ERROR") {
                        this.$message.error('很抱歉, 我们发生了一些错误');
                    } else if (response.data === "BAD_REQUEST") {
                        this.$message.error('你的请求有误, 文件可能有点儿问题');
                    } else {
                        this.$message.error('产生了无法预知的错误, 请重新登陆');
                        console.log(response.data)
                    }
                }).catch((err) => {
                    this.$message.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>

Four, Vuex best practices

(1) define the store / index.js, should in fact be modular in advance, 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) incorporated in main.js

import store from './store'

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

(3) assembly incorporated Vue

this.$store.dispatch('commitToken', value);  // 向Store中存储数据
this.$store.getters.getToken;                // 读取Store中的数据

Five, Vue-router Jump

However, official documents are written in a very clear, but I'm too lazy to turn the official documents ...

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

Six, Nginx with SpringBoot achieve strong turn and HTTPS 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;

    // 设置反向代理
    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;

        // 设置HTTPS强转
        rewrite ^(.*)$ https://$host$1 permanent;
    }

    // API接口使用HTTPS
    server {
        listen       8899 ssl;
        server_name  upload-image-api;

        // 配置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;

        // 添加支持的HTTPS协议
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

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

    server {
        // 将前端静态分发设置跳转到该接口
        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;
        }
    }

}

Seven, Vue horizontal and vertical components are centered

That is the question, I always remember how to do it, but I have been able to Baidu, Google do not even ...

(1) index.html, disposed style tag

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

(2) component styles

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

Eight, Vue and PC camera interaction

Recently themselves with a face recognition Caffe trained neural network, we can face the future logged ~
(So), first get the camera-side Web PC's to say ... because the pictures are not very computer is pleasing to the eye, so be a mirror flip,
but you're so ugly ... my CSS so that you become good-looking, ha ha 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">青芒云(Qimo Cloud)控制台</div>
    <div class="login-with-subtitle">人脸检测登录,点击图片开始检测</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:显示弹窗,重复的提交
            },
            connectToCamera: function () {
                let self = this;
                navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
                if (navigator.getUserMedia) {
                    // 调用用户媒体设备, 访问摄像头
                    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: 显示错误弹窗,不支持的媒体类型
                    })
                } else {
                    // TODO:显示错误弹窗,无法访问摄像头
                }
            },
            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:发送图片进行识别

                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 和 Chrome */
    -moz-transform: rotateY(180deg);
  }

  .video-box {
    transform: rotateY(180deg);
    -webkit-transform: rotateY(180deg); /* Safari 和 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>

Nine, Vue monovalent group in highly adaptive

Let the height of the component is always equal to the height of the browser window!

(1) component binding CSS style

:style="sidebarStyle"

(2) JavaScript dynamic binding 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);
    }
}

Continuously updated in ...

Guess you like

Origin blog.51cto.com/xvjunjie/2440933