整体思路是前端的takephoto方法拍照调用全局ajax请求函数,像后端发送post,postman测过后端没问题,怀疑takephoto方法有问题
后端已写跨域解决代码,不知为什么无效
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH")
.maxAge(3600);
}
}
<!-- 这里是html模板 -->
<template>
<div class="page">
<!-- //el-row 组件设置了 type="flex",表示使用弹性布局,子元素会按比例自适应屏幕大小;
justify="center" 表示子元素在横向上居中对齐;align="middle" 表示子元素在竖向上居中对齐 -->
<el-row type="flex" justify="center" align="middle" class="container">
<!-- :lg="14" 和 :xl="10" 属性表示在不同的屏幕尺寸下,该元素会占据不同的列数。
同时,使用 class="hidden-md-and-down" 属性可以隐藏该元素在中等及以下屏幕尺寸下的显示,
从而实现响应式布局 -->
<el-col :lg="14" :xl="10" class="hidden-md-and-down">
<el-row class="panel">
<!-- span 属性表示该列占据的网格数,可以是 1 到 24 之间的整数。例如,:span="12" 表示该列占据了当前行的一半宽度。 -->
<el-row :span="12">
<div class="left">
<img src="../assets/login/logo.png" class="logo" />
<img src="../assets/login/big-1.png" class="big" />
</div>
</el-row>
<el-row :span="12">
<div class="right">
<div class="title-container">
<h2>基于人脸识别的在线办公平台</h2>
</div>
<div v-if="modeCode==1">
<div class="row" >
<el-input v-model="username" placeholder="用户名" prefix-icon="el-icon-user" clearable></el-input>
</div>
<div class="row">
<el-input type="password" v-model="password" placeholder="密码" prefix-icon="el-icon-lock" clearable></el-input>
</div>
<div class="row">
<el-button type="primary" class="btn" @click="login">登录</el-button>
</div>
<div class="row">
<el-button type="primary" class="btn" @click="bx2">人脸识别</el-button>
</div>
<div class="row">
<el-button type="primary" class="btn" @click="changeMode">扫码登录</el-button>
</div>
</div>
<div v-if="modeCode==2">
<div class="qrCode-container">
<img :src="qrCode" height="153" class="qrCode" />
<img src="../assets/login/phone.png" height="148" />
</div>
<div class="row">
<el-button type="primary" class="btn" @click="bx1">其他登陆</el-button>
</div>
</div>
<div v-if="modeCode==3">
<div class="video-container">
<video id="videoCamera" class="camera-video" :width="videoWidth" :height="videoHeight" autoplay></video>
<canvas id="canvasCamera" style="display:none;" :width="videoWidth" :height="videoHeight"></canvas>
<img v-if="imageUrl" :src="imageUrl" class="photo-preview">
</div>
<div class="controls">
<el-button type="primary" @click="openCamera">打开摄像头</el-button>
<el-button type="primary" @click="takePhotoHandle" :disabled="isPlaying">拍照登录</el-button>
<el-button type="primary" @click="bx1">返回</el-button>
</div>
</div>
</div>
</el-row>
</el-row>
</el-col>
</el-row>
</div>
</template>
<!-- 这里是js代码 -->
<script>
// 如果要用外部的需要在这里导入,和以前js一样
import 'element-plus/lib/theme-chalk/display.css';
import {
isUsername,
isPassword
} from '../utils/validate.js';
import router from '../router/index.js';
// 这里是初始绑定数据
export default { //导出默认对象、函数或值会在模块加载时被加载到内存中,以供其他模块使用
data: function() { //值,值会自动渲染,当组件被渲染到页面中时,Vue.js 会自动将 data 中定义的数据绑定到相应的 DOM 元素上,以便在需要时获取和修改数据
return {
loading: false, // 上传照片的loading
videoWidth: 350,
videoHeight: 188,
username: null,
password: null,
modeCode: 1,
imgSrc: "", // 照片地址
photoVideo: null, // 拍照框
photoContext: null, // canvas绘图环境
photoCancas: null, // 预览框
downloadButton: false,
qrCode: '',
uuid: null,
qrCodeTimer: null,
loginTimer: null
};
},
methods: { //函数
login: function() {
let that = this;
if (!isUsername(that.username)) {
// Element UI 中的消息提示组件 $message,用于在页面上显示一条消息提示。
// 具体来说,$message 是一个全局的方法,
// 可以通过 this.$message 或者 that.$message 的方式在组件中使用。
that.$message({
message: '用户名格式不正确',
type: 'error',
duration: 1200
});
} else if (!isPassword(that.password)) {
that.$message({
message: '密码格式不正确',
type: 'error',
duration: 1200
});
} else {
//准备数据发送ajax请求
// 这样做的目的是将用户输入的数据封装到一个对象中,方便后续的处理和传递。在发送登录请求时,
// 使用该对象作为请求参数,可以直接将用户名和密码发送到后端进行验证。
// 另外,使用 let 关键字定义的变量具有块级作用域,只在当前代码块中有效。
// 这样可以避免变量污染和命名冲突的问题,提高代码的健壮性和可维护性。
let data = {
username: that.username,
password: that.password
};
//发送登陆请求:参数分别是路径,请求方式,同步异步,回调函数
that.$http('user/login', 'POST', data, true, function(resp) {
if (resp.result) { //根据后端的rusult判断
//在浏览器的storage中存储用户权限列表,这样其他页面也可使用storage中的数据,实现共享
let permissions = resp.permissions;
//取出Token令牌,保存到storage中,因为新版本浏览器不支持crendential,我们手动存储
let token = resp.token;
localStorage.setItem('permissions', permissions); //存储到浏览器的 localStorage 中
localStorage.setItem('token', token);
//让路由跳转页面,这里的Home是home.vue页面的名字
router.push({
name: 'Home'
});
} else {
that.$message({
message: '登陆失败',
type: 'error',
duration: 1200
});
}
});
}
},
async openCamera() {
this.photoVideo = document.getElementById('videoCamera')
this.photoCancas = document.getElementById('canvasCamera')
this.photoContext = this.photoCancas.getContext('2d')
try {
const constraints = {
audio: false,
video: {
width: this.videoWidth,
height: this.videoHeight
}
}
// 这段代码使用 WebRTC 的 navigator.mediaDevices.getUserMedia() 方法获取用户的媒体设备(例如摄像头和麦克风)的媒体流。
const stream = await navigator.mediaDevices.getUserMedia(constraints)
this.photoVideo.srcObject = stream //把媒体流给video的来源
this.photoVideo.play()
} catch (error) {
this.$message({
title: '警告',
message: '请确认摄像头能正常工作,必须使用谷歌浏览器或者360浏览器的极速模式,否则拍照不能正常使用',
type: 'warning',
duration: 8000
});
}
},
takePhotoHandle() {
this.photoContext.drawImage(this.photoVideo, 0, 0, this.videoWidth, this.videoHeight)
this.photoCancas.toBlob((blob) => {
const file = new File([blob], 'face.jpg', {
type: 'image/jpeg',
lastModified: Date.now()
})
const formData = new FormData()
formData.append('file', file)
let that = this;
that.$sftp("face/login", "POST", formData, true, function(resp) {
that.$message({
message: '操作成功',
type: 'success',
duration: 1200,
});
router.push({
name: 'Home'
});
})
})
},
bx2: function() {
let that = this;
that.modeCode = 3;
},
bx1: function() {
let that = this;
that.modeCode = 1;
stopNavigator();
},
stopNavigator: function(){
that.photoVideo.srcObject.getTracks()[0].stop()
},
changeMode: function() {
let that = this;
that.modeCode = 2;
//加载二维码图片
if (that.modeCode == 2) {
that.loadQRCode();
//创建刷新二维码定时器
that.qrCodeTimer = setInterval(function() {
that.loadQRCode();
}, 5 * 60 * 1000);
that.loginTimer = setInterval(function() {
that.$http('user/wechatLogin', 'POST', {
uuid: that.uuid
}, true, function(resp) {
if (resp.result) {
clearInterval(that.qrCodeTimer);
clearInterval(that.loginTimer);
let permissions = resp.permissions;
localStorage.setItem('permissions', permissions);
router.push({
name: 'Home'
});
}
});
}, 5000);
} else {
//销毁刷新二维码定时器
clearInterval(that.qrCodeTimer);
clearInterval(that.loginTimer);
}
},
//加载二维码图片的封装方法
loadQRCode: function() {
this.$http('user/createQrCode', 'GET', null, true, resp => {
this.qrCode = resp.pic;
this.uuid = resp.uuid;
});
}
}
};
</script>
<!-- 这里是css区域,当然可以不用写到这里,可以用引入的方法 -->
<style lang="less" scoped="scoped">
@import url('login.less');
</style>
app.config.globalProperties.$sftp = function(url, method, mydata, async, fun) {
$.ajax({
url: baseUrl + url,
dataType: 'json',
type: method,
contentType: "multipart/form-data",
xhrFields: {
withCredentials: true
},
headers: {
"token": localStorage.getItem("token"),
},
async: async,
data: mydata,
success: function(resp) {
if (resp.code == 200) {
fun(resp)
} else {
ElMessage.error({
message: resp.msg,
duration: 1200
});
}
},
error: function(e) {
if (e.status == undefined) {
ElMessage.error({
message: "前端页面错误",
duration: 1200
});
} else {
let status = e.status
if (status == 401) {
router.push({
name: 'Login'
})
} else {
ElMessage.error({
message: e.responseText,
duration: 1200
});
}
}
}
})
}
后端代码
@RestController
@RequestMapping("/face")
@Slf4j
@Tag(name = "FaceController", description = "人脸识别接口")//在swagger文档的描述
public class FaceController {
@Autowired
private FaceService faceService;
@PostMapping("/login")
@Operation(summary = "人脸登录")
public R detectFace(@RequestParam("file") MultipartFile face) throws Exception {
byte[] buff = face.getBytes();
JSONObject jsonObject = JSONUtil.parseObj(faceService.detectFace(buff));
if (jsonObject.containsKey("faces")) {
JSONObject faceToken = jsonObject.getJSONArray("faces").getJSONObject(0);
return R.ok().put("face_token", faceToken.getStr("face_token"));
}
return R.error().put("result","失败了");
}