Vue、SprinBoot开发运维的一些坑和知识集锦
由于博主主要是做后端开发和自动化运维的,因此,前端基本面向同学和搜索引擎编程...这次彻底搞出了一个简洁优雅的Vue和Axios配合的跨域方案,适合开发环境和生产环境!
(1)在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)在main.js中配置自动选择
import axios from 'axios'import QS from 'qs'Vue.prototype.$axios = axiosVue.prototype.$qs = QSVue.prototype.baseUrl = process.env.NODE_ENV === "production" ? "https://211.64.32.228:8899" : "/api"
(3)在Vue文件中使用Axios
this.axios({ method: 'post', url: this.baseUrl + '/helloworld', data: {}, headers: {}}).then((response) => { // do some}).catch((error) => { // do some});
(4)SpringBoot配置允许跨域
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;@Configurationpublic 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); }}
二、SpringBoot中AES+Base64加解密用户登录凭据
这年头,md5是能反解的,再老也不能掉牙呀..
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); }}
三、纯CSS自定义超简洁文件上传控件input
主要是解决自定义CSS样式问题和Vue上传文件的问题...注释就不写了,静下心来稍微一看就懂!
<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" :></i> <div class="upload-demo-text" :>{{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>
四、Vuex最佳实践
(1)定义store/index.js,事实上应该事先模块化,但是我太懒了。
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)在main.js中引用
import store from './store'/* eslint-disable no-new */new Vue({ el: '#app', router, components: {App}, template: '<App/>', store})
(3)在Vue组件中引用
this.$store.dispatch('commitToken', value); // 向Store中存储数据this.$store.getters.getToken; // 读取Store中的数据
五、Vue-router跳转
然而,官方文档是写的很明确的,但是我懒得翻官方文档...
this.$router.push({ path: '/normal'});
六、Nginx配合SpringBoot实现HTTPS强转和API网关负载均衡
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; } }}
七、Vue组件水平垂直都居中
就是这个问题,我一直都记不住怎么做,但是我一直都能百度到,连Google都不用...
(1) index.html,设置在style标签
html, body { margin: 0; padding: 0;}
(2) 组件样式
.box { top: 50%; left: 50%; position: absolute; transform: translate(-50%, -50%); min-width: 450px; max-width: 550px; min-height: 500px; max-height: 550px;}
八、Vue与PC端摄像头交互
最近自己用Caffe训练了一个人脸识别的神经网络,以后咱也可以人脸登录了~
So,先搞定PC的Web端的摄像头再说...因为电脑拍出来的照片是不太顺眼的,因此进行了镜像翻转,
但是,你就是那么丑...是我的CSS让你变好看了,哈哈哈~
<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 ></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" ></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>
九、Vue中组价高度自适应
让这个组件的高度总是等于浏览器窗口高度!
(1)组件绑定CSS样式
:
(2) JavaScript数据动态绑定
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); }}
持续更新中...
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。