因爲博主主要是作後端開發和自動化運維的,所以,前端基本面向同窗和搜索引擎編程...此次完全搞出了一個簡潔優雅的Vue和Axios配合的跨域方案,適合開發環境和生產環境!javascript
(1)在config/index.js中配置開發環境跨域css
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中配置自動選擇html
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)在Vue文件中使用Axios前端
this.axios({ method: 'post', url: this.baseUrl + '/helloworld', data: {}, headers: {} }).then((response) => { // do some }).catch((error) => { // do some });
(4)SpringBoot配置容許跨域vue
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); } }
這年頭,md5是能反解的,再老也不能掉牙呀..java
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樣式問題和Vue上傳文件的問題...註釋就不寫了,靜下心來稍微一看就懂!ios
<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>
(1)定義store/index.js,事實上應該事先模塊化,可是我太懶了。nginx
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中引用web
import store from './store' /* eslint-disable no-new */ new Vue({ el: '#app', router, components: {App}, template: '<App/>', store })
(3)在Vue組件中引用spring
this.$store.dispatch('commitToken', value); // 向Store中存儲數據 this.$store.getters.getToken; // 讀取Store中的數據
然而,官方文檔是寫的很明確的,可是我懶得翻官方文檔...
this.$router.push({ path: '/normal' });
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; } } }
就是這個問題,我一直都記不住怎麼作,可是我一直都能百度到,連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; }
最近本身用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 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>
讓這個組件的高度老是等於瀏覽器窗口高度!
(1)組件綁定CSS樣式
:style="sidebarStyle"
(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); } }
持續更新中...