您好,登錄后才能下訂單哦!
由于博主主要是做后端開發和自動化運維的,因此,前端基本面向同學和搜索引擎編程...這次徹底搞出了一個簡潔優雅的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 = 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配置允許跨域
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是能反解的,再老也不能掉牙呀..
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上傳文件的問題...注釋就不寫了,靜下心來稍微一看就懂!
<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>
(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中的數據
然而,官方文檔是寫的很明確的,但是我懶得翻官方文檔...
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 ></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>
讓這個組件的高度總是等于瀏覽器窗口高度!
(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);
}
}
持續更新中...
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。