一、完美解决Vue2.0+Axios开发生产环境跨域问题
由于博主主要是做后端开发和自动化运维的,因此,前端基本面向同学和搜索引擎编程...这次彻底搞出了一个简洁优雅的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);
}
}
二、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);
}
}
持续更新中...