uniapp人脸识别解决方案
APP端:
因为APP端无法使用uni的camera组件,最开始考虑使用内嵌webview的方式,通过原生dom调用video渲染画面然后通过canvas截图。但是此方案兼容性在ios几乎为0,如果app只考虑安卓端的话可以采用此方案。后面又想用live-pusher组件来实现,但是发现快照api好像需要真实流地址才能截取图像。因为种种原因,也是安卓ios双端兼容性不佳。最终决定采用5+api实现。经实测5+api兼容性还算可以,但是毕竟是调用原生能力,肯定是没有原生开发那么丝滑的,难免会出现一些不可预测的兼容性问题。所以建议app和手机硬件交互强的话还是不要用uni开发了。不然真的是翻文档能翻死人。社区也找不到靠谱的解决方案。
5+api 文档
https://www.html5plus.org/doc/zh_cn/video.html#plus.video.createLivePusher
就是使用这个api调用原生的camera完成。并且可以直接在预览模式下完成快照,也不需要真实的推流地址。
<template><view class="content"><view class="c-footer"><view class="msg-box"><view class="msg">1、请保证本人验证。</view><view class="msg">2、请使头像正面位于画框中。</view><view class="msg">3、请使头像尽量清晰。</view><view class="msg">4、请保证眼镜不反光,双眼可见。</view><view class="msg">5、请保证无墨镜,口罩,面膜等遮挡物。</view><view class="msg">6、请不要化浓妆,不要戴帽子。</view></view><view class="but" @click="snapshotPusher" v-if="!cilckSwitch">采集本人人脸</view></view></view>
</template><script>import permission from '../../util/permission.js'export default {data() {return {type: '', //是否是补签拉起的人脸识别imgData: '',pusher: null,scanWin: null,faceInitTimeout: null,snapshTimeout: null,uploadFileTask: null,cilckSwitch: false, //防止多次点击};},methods: {//人脸比对handleFaceContrast(param) {uni.hideLoading()this.$http({url: '/API_AUTH/AppIaiFace/faceContrast.api',data: {...param,userid: uni.getStorageSync('userInfo').id}}).then(res => {console.log(res)if (res.data.compareResult == 1) {let pages = getCurrentPages(); //获取所有页面栈实例列表let nowPage = pages[pages.length - 1]; //当前页页面实例let prevPage = pages[pages.length - 2]; //上一页页面实例if (this.type == 'signOut') {prevPage.$vm.signOutXh = param.xh;prevPage.$vm.signOutPhotoPath = param.path} else {prevPage.$vm.xh = param.xh;prevPage.$vm.photoPath = param.path}uni.navigateBack()} else {uni.showToast({title: '人脸比对不通过,请重试',icon: 'none'})this.cilckSwitch = false}}).catch(err => {uni.showToast({title: '人脸比对不通过,请重试',icon: 'none'})this.cilckSwitch = false})},//初始化faceInit() {uni.showLoading({title: '请稍后...'})this.faceInitTimeout = setTimeout(async () => {//创建livepusherif (uni.getSystemInfoSync().platform === 'android') {const data1 = await permission.requestAndroidPermission("android.permission.RECORD_AUDIO")const data2 = await permission.requestAndroidPermission("android.permission.CAMERA")console.log(data1,data2,1111)if (data1 == 1 && data2 == 1) {this.pusherInit();}} else {this.pusherInit();}//覆盖在视频之上的内容,根据实际情况编写// this.scanWin = plus.webview.create('/hybrid/html/faceTip.html', '', {// background: 'transparent'// });//新引入的webView显示// this.scanWin.show();//初始化上传本地文件的apithis.initUploader()}, 500);},//初始化播放器pusherInit() {const currentWebview = this.$mp.page.$getAppWebview();this.pusher = plus.video.createLivePusher('livepusher', {url: '',top: '0px',left: '0px',width: '100%',height: '50%',position: 'absolute',aspect: '9:16',muted: false,'z-index': 999999,'border-radius': '50%',});currentWebview.append(this.pusher);//反转摄像头this.pusher.switchCamera();//开始预览this.pusher.preview();uni.hideLoading()},//初始化读取本地文件initUploader() {let that = thisthis.uploadFileTask = plus.uploader.createUpload("完整的接口请求地址", {method: "POST",headers: {// 修改请求头Content-Type类型 此类型为文件上传"Content-Type": "multipart/form-data"}},// data:服务器返回的响应值 status: 网络请求状态码(data, status) => {// 请求上传文件成功if (status == 200) {console.log(data)// 获取data.responseText之后根据自己的业务逻辑做处理let result = JSON.parse(data.responseText);console.log(result.data.xh)that.handleFaceContrast({xh: result.data.xh,path: result.data.path})}// 请求上传文件失败else {uni.showToast({title: '上传图片失败',icon: 'none'})console.log("上传失败", status)that.cilckSwitch = falseuni.hideLoading()}});},//快照snapshotPusher() {if (this.cilckSwitch) {uni.showToast({title: '请勿频繁点击',icon: 'none'})return}this.cilckSwitch = trueuni.showLoading({title: '正在比对,请勿退出'})let that = thisthis.snapshTimeout = setTimeout(() => {this.pusher.snapshot(e => {// this.pusher.close();// this.scanWin.hide();//拿到本地文件路径var src = e.tempImagePath;this.uploadImg(src)//获取图片base64// this.getMinImage(src);},function(e) {plus.nativeUI.alert('snapshot error: ' + JSON.stringify(e));that.cilckSwitch = falseuni.hideLoading()});}, 500);},//调用原生能力读取本地文件并上传uploadImg(imgPath) {this.uploadFileTask.addFile('file://' + imgPath, {key: "file" // 填入图片文件对应的字段名});//添加其他表单字段(参数) 两个参数可能都只支持传字符串// uploadFileTask.addData("参数名", 参数值);this.uploadFileTask.start();},//获取图片base64getMinImage(imgPath) {plus.zip.compressImage({src: imgPath,dst: imgPath,overwrite: true,quality: 40},zipRes => {setTimeout(() => {var reader = new plus.io.FileReader();reader.onloadend = res => {var speech = res.target.result; //base64图片console.log(speech.length);console.log(speech)this.imgData = speech;};reader.readAsDataURL(plus.io.convertLocalFileSystemURL(zipRes.target));}, 1000);},function(error) {console.log('Compress error!', error);});},},onLoad(option) {//#ifdef APP-PLUSthis.type = option.typethis.faceInit();//#endif},onHide() {console.log('hide')this.faceInitTimeout && clearTimeout(this.faceInitTimeout);this.snapshTimeout && clearTimeout(this.snapshTimeout);// this.scanWin.hide();},onBackPress() {// let pages = getCurrentPages(); //获取所有页面栈实例列表// let nowPage = pages[pages.length - 1]; //当前页页面实例// let prevPage = pages[pages.length - 2]; //上一页页面实例// prevPage.$vm.xh = '11111';// prevPage.$vm.photoPath = '22222' this.faceInitTimeout && clearTimeout(this.faceInitTimeout);this.snapshTimeout && clearTimeout(this.snapshTimeout);// this.scanWin.hide();},onUnload() {this.faceInitTimeout && clearTimeout(this.faceInitTimeout);this.snapshTimeout && clearTimeout(this.snapshTimeout);// this.scanWin.hide();},};
</script><style lang="scss" scoped>.but {margin: 50rpx auto 0;border-radius: 10px;width: 80%;height: 100rpx;display: flex;align-items: center;justify-content: center;background-color: #008AFF;font-size: 16px;color: #FFFFFF;}.c-footer {width: 100%;position: fixed;top: 50%;left: 0;z-index: 10;padding: 30rpx 0;.msg-box {width: 80%;margin: 0 auto;text-align: left;.msg {margin-bottom: 15rpx;font-size: 13px;color: #666;}}}.img-data {width: 100%;height: auto;}.content {background-color: #fff;}
</style>
以上是完整的包含逻辑的代码。
关键代码
//初始化播放器pusherInit() {const currentWebview = this.$mp.page.$getAppWebview();this.pusher = plus.video.createLivePusher('livepusher', {url: '',top: '0px',left: '0px',width: '100%',height: '50%',position: 'absolute',aspect: '9:16',muted: false,'z-index': 999999,'border-radius': '50%',});currentWebview.append(this.pusher);//反转摄像头this.pusher.switchCamera();//开始预览this.pusher.preview();uni.hideLoading()},//快照snapshotPusher() {if (this.cilckSwitch) {uni.showToast({title: '请勿频繁点击',icon: 'none'})return}this.cilckSwitch = trueuni.showLoading({title: '正在比对,请勿退出'})let that = thisthis.snapshTimeout = setTimeout(() => {this.pusher.snapshot(e => { //拿到本地文件路径var src = e.tempImagePath;//这里因为接口参数需要加密,用base64的话加密出来的参数太大了,所以选择了直接读取本地文件上传文件流的方式。this.uploadImg(src)//获取图片base64// this.getMinImage(src);},function(e) {plus.nativeUI.alert('snapshot error: ' + JSON.stringify(e));that.cilckSwitch = falseuni.hideLoading()});}, 500);},//获取图片base64getMinImage(imgPath) {plus.zip.compressImage({src: imgPath,dst: imgPath,overwrite: true,quality: 40},zipRes => {setTimeout(() => {var reader = new plus.io.FileReader();reader.onloadend = res => {var speech = res.target.result; //base64图片console.log(speech.length);console.log(speech)this.imgData = speech;};reader.readAsDataURL(plus.io.convertLocalFileSystemURL(zipRes.target));}, 1000);},function(error) {console.log('Compress error!', error);});},//初始化读取本地文件initUploader() {let that = thisthis.uploadFileTask = plus.uploader.createUpload("完整的接口请求地址", {method: "POST",headers: {// 修改请求头Content-Type类型 此类型为文件上传"Content-Type": "multipart/form-data"}},// data:服务器返回的响应值 status: 网络请求状态码(data, status) => {// 请求上传文件成功if (status == 200) {console.log(data)// 获取data.responseText之后根据自己的业务逻辑做处理let result = JSON.parse(data.responseText);console.log(result.data.xh)that.handleFaceContrast({xh: result.data.xh,path: result.data.path})}// 请求上传文件失败else {uni.showToast({title: '上传图片失败',icon: 'none'})console.log("上传失败", status)that.cilckSwitch = falseuni.hideLoading()}});},//调用原生能力读取本地文件并上传uploadImg(imgPath) {this.uploadFileTask.addFile('file://' + imgPath, {key: "file" // 填入图片文件对应的字段名});//添加其他表单字段(参数) 两个参数可能都只支持传字符串// uploadFileTask.addData("参数名", 参数值);this.uploadFileTask.start();},
以上就是关键的代码。
接下来补充几个坑的地方。创建出来的livepusher层级很高,无法在同一页面被别的元素遮挡。所以想要在他上面写样式是行不通了。只能再创建一个webview。然后将这个webview覆盖在livepusher上,达到人脸识别页面的样式。
//覆盖在视频之上的内容,根据实际情况编写this.scanWin = plus.webview.create('/hybrid/html/faceTip.html', '', {background: 'transparent'});//新引入的webView显示this.scanWin.show();//新引入的webView影藏this.scanWin.hide();
这种方案在ios基本没问题。至少目前没遇到过。但是安卓就一言难尽了。首先这个组件默认调起的是后置摄像头,这显然不符合我们的需求。但是官方提供的文档里也没有明确支持可以配置优先调起哪个摄像头。好早提供了一个switchCamera的api可以翻转摄像头。
但是在安卓系统上,尤其是鸿蒙系统,调用这个api就会导致程序闪退,而且发生频率还特别高。这个问题至今不知道该怎么解决。除了闪退问题,安卓还存在一个麻烦事儿,那就是首次进入app,翻转摄像头的api没有用,拉起的还是后置摄像头。但是后续再进入app就无此问题了。后面折腾来折腾去,发现好像是首次进入拉起授权弹窗的时候才会出现这种问题。
然后写了个定时器做测试,五秒之后再拉起摄像头再去翻转摄像头。然后再五秒内赶紧把授权给同意了。结果发现翻转竟然生效了。
然后决定再渲染推流元素之前先让用户通过权限授权,然后再拉起摄像头。 也就是上文完整代码中的
//创建livepusherif (uni.getSystemInfoSync().platform === 'android') {const data1 = await permission.requestAndroidPermission("android.permission.RECORD_AUDIO")const data2 = await permission.requestAndroidPermission("android.permission.CAMERA")console.log(data1,data2,1111)if (data1 == 1 && data2 == 1) {this.pusherInit();}} else {this.pusherInit();}
具体的意思就不过多赘述了,自行看permission的文档。或者看他的代码。很简单
permission下载地址
https://ext.dcloud.net.cn/plugin?id=594
以上就是调用原生能力拉起摄像头实现快照功能的所有内容了。
下面也记录一下web端如果实现这种功能,毕竟当时搞出来也不容易,但是最终还是败在了兼容性上
方案的话大致有两种,一种是借助tracking js 有兴趣的可以了解一下,一个web端人脸识别库。他可以识别画面中是否出现人脸。以及一下更高级的功能我就没有去探索了。有需要的可以自行研究
<!doctype html>
<html><head><meta charset="utf-8"><title>人脸识别</title><script src="../html/js/tracking-min.js"></script><script src="../html/js/face-min.js"></script><style>video,canvas {position: absolute;}</style></head><body><div class="demo-container"><video id="video" width="320" height="240" preload autoplay loop muted></video><canvas id="canvas" width="320" height="240"></canvas></div><script>window.onload = function() {console.log(123123123)// 视频显示var video = document.getElementById('video');// 绘图var canvas = document.getElementById('canvas');var context = canvas.getContext('2d');var time = 10000;var tracker = new tracking.ObjectTracker('face');// 设置识别的放大比例tracker.setInitialScale(4);// 设置步长tracker.setStepSize(2);// 边缘密度tracker.setEdgesDensity(0.1);// 启动摄像头,并且识别视频内容var trackerTask = tracking.track('#video', tracker, {camera: true});var flag = true;tracker.on('track', function(event) {if (event.data.length === 0) {console.log('未检测到人脸')context.clearRect(0, 0, canvas.width, canvas.height);} else if (event.data.length > 1) { console.log('检测到多张人脸')} else {context.clearRect(0, 0, canvas.width, canvas.height);event.data.forEach(function(rect) {context.strokeStyle = '#ff0000';context.strokeRect(rect.x, rect.y, rect.width, rect.height);context.fillStyle = "#ff0000";//console.log(rect.x, rect.width, rect.y, rect.height);});if (flag) {console.log("拍照");context.drawImage(video, 0, 0, 320, 240);saveAsLocalImage();context.clearRect(0, 0, canvas.width, canvas.height);flag = false;setTimeout(function() {flag = true;}, time);} else {//console.log("冷却中");}}});};function saveAsLocalImage() {var myCanvas = document.getElementById("canvas");// here is the most important part because if you dont replace you will get a DOM 18 exception. // var image = myCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream;Content-Disposition: attachment;filename=foobar.png"); var image = myCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream");// window.location.href = image; // it will save locally // create temporary link returnvar tmpLink = document.createElement('a');tmpLink.download = 'image.png'; // set the name of the download file tmpLink.href = image;// temporarily add link to body and initiate the download document.body.appendChild(tmpLink);tmpLink.click();document.body.removeChild(tmpLink);}</script></body></html>
另外一种就是纯video+canvas截取一张视频中的画面。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>人脸采集</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" type="text/css" href="./css/index.css" /><script src="./js/jq.js" type="text/javascript" charset="utf-8"></script><!-- uni 的 SDK --><script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.0.0/vconsole.min.js" type="text/javascript"charset="utf-8"></script><script>// init vConsole,app中查看var vConsole = new VConsole();// console.log('Hello world');</script><style>.mui-content {margin: 0 auto;text-align: center;border: 0px solid red;}/*摄像头翻转180度*/video {transform: rotateY(180deg);-webkit-transform: rotateY(180deg);/* Safari 和 Chrome */-moz-transform: rotateY(180deg);}</style></head><body class='body'><div class="mui-content"><div style="margin: 40px auto;"><!-- <input type="file" id='image' accept="image/*" capture='user'> --><video id="video" style="margin: 0 auto; border-radius: 150px;"></video><canvas id='canvas' width="300" height="300"style="border: 0px solid red;margin: auto; display: none;"></canvas></div><div class="msg-box"><div class="msg">1、请保证本人验证。</div><div class="msg">2、请使头像正面位于画框中。</div><div class="msg">3、请使头像尽量清晰。</div><div class="msg">4、请保证眼镜不反光,双眼可见。</div><div class="msg">5、请保证无墨镜,口罩,面膜等遮挡物。</div><div class="msg">6、请不要化浓妆,不要戴帽子。</div></div><div style="width: 80%; position: absolute; bottom: 20px; left: 50%; transform: translate(-50%, -50%);"><div class="but" id="start">采集本人人脸</div></div></div></body><script type="text/javascript">var video, canvas, vendorUrl, interval, videoHeight, videoWidth, time = 0;// 获取webview页面数据// var data = JSON.parse(getUrlParam('data'))// var info = data.info;// var userInfo = data.userInfo;// const userId = data.userId; // 当前登录用户id$(function() {// 初始化initVideo()// uni.app事件document.addEventListener('UniAppJSBridgeReady', function() {uni.getEnv(function(res) {console.log('当前环境:' + JSON.stringify(res));});setInterval(() => {uni.postMessage({data: {action: 'postMessage'}});}, 1000)});})// 获取url携带的数据function getUrlParam(name) {var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");var r = window.location.search.substr(1).match(reg);if (r != null) return unescape(r[2]);return null;}// 摄像头初始化function initVideo() {console.log('摄像头初始化')video = document.getElementById("video");videoHeight = 300videoWidth = 300setTimeout(() => {console.log(navigator)navigator.mediaDevices.getUserMedia({video: {width: {ideal: videoWidth,max: videoWidth},height: {ideal: videoHeight,max: videoHeight},facingMode: 'user', //前置摄像头// facingMode: { exact: "environment" }, //后置摄像头frameRate: {ideal: 30,min: 10}}}).then(videoSuccess).catch(videoError);if (navigator.mediaDevices.getUserMedia ||navigator.getUserMedia ||navigator.webkitGetUserMedia ||navigator.mozGetUserMedia ||navigator.mediaCapabilities) {console.log('调用用户媒体设备, 访问摄像头')//调用用户媒体设备, 访问摄像头getUserMedia({video: {width: {ideal: videoWidth,max: videoWidth},height: {ideal: videoHeight,max: videoHeight},facingMode: 'user', //前置摄像头// facingMode: { exact: "environment" }, //后置摄像头frameRate: {ideal: 30,min: 10}}},videoSuccess,videoError);} else {}}, 300);}// 获取用户设备function getUserMedia(constraints, success, error) {if (navigator.mediaDevices.getUserMedia) {//最新的标准APInavigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);} else if (navigator.webkitGetUserMedia) {//webkit核心浏览器navigator.webkitGetUserMedia(constraints, success, error);} else if (navigator.mozGetUserMedia) {//firfox浏览器navigator.mozGetUserMedia(constraints, success, error);} else if (navigator.getUserMedia) {//旧版APInavigator.getUserMedia(constraints, success, error);}}// 开始有画面function videoSuccess(stream) {//this.mediaStreamTrack = stream;console.log("=====stream")video.srcObject = stream;video.play();//$("#max-bg").css('background-color', 'rgba(0,0,0,0)')// 这里处理我的的东西}function videoError(error) {alert('摄像头获取错误')console.log('摄像头获取错误')setTimeout(() => {initVideo()}, 6000)}// 单次拍照function getFaceImgBase64() {canvas = document.getElementById('canvas');//绘制canvas图形canvas.getContext('2d').drawImage(video, 0, 0, 300, 300);//把canvas图像转为img图片var bdata = canvas.toDataURL("image/jpeg");//img.src = canvas.toDataURL("image/png");return bdata.split(',')[1]; //照片压缩成base位数据}$('#start').on('click', function() {time = 0;console.log("开始人脸采集,请正对屏幕");faceGather();})// 人脸采集function faceGather() {const faceImgBase64 = getFaceImgBase64();console.log(faceImgBase64);}</script>
</html>
相关文章:

uniapp人脸识别解决方案
APP端: 因为APP端无法使用uni的camera组件,最开始考虑使用内嵌webview的方式,通过原生dom调用video渲染画面然后通过canvas截图。但是此方案兼容性在ios几乎为0,如果app只考虑安卓端的话可以采用此方案。后面又想用live-pusher组件…...

hashlib模块
欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 hashlib模块专栏:《python从入门到实战》 哈希算法,也叫摘要算法。 加密&…...

NC65合并报表如何取消上报并退回以及注意事项和相关问题总结
NC65合并报表如何取消上报并退回? 在【企业绩效管理】-【合并报表】-【合并】-【合并执行】节点中,点击〖数据中心〗按钮,在弹出的〖合并报表数据中心〗界面中,点击〖报送管理〗-〖合并方案请求退回〗,然后到【合并综…...

28岁,终于从字节退休了...
大厂一直是每个程序员都向往职业目标,大厂意味着薪资高、福利好、倍有面儿,而且发展空间也大。甚至有人调侃不想进大厂的程序员不是好程序员。 而在网上,也有各个网友分享自己在大厂的经历,在某平台还有一个近2600万浏览的话题&a…...

数据的表示和存储——
目录 浮点数的编码表示 浮点数类型 编辑 浮点数的表示 (1)浮点数(Float Point)的表示范围 (2)规格化数形式 (3)IEEE 754标准 其他形式的机器数表示 个人总结 浮点数的编码表…...

springboot零基础到项目实战
推荐教程: springboot零基础到项目实战 SpringBoot这门技术课程所包含的技术点其实并不是很多,但是围绕着SpringBoot的周边知识,也就是SpringBoot整合其他技术,这样的知识量很大,例如SpringBoot整合MyBatis等等。因此…...

自媒体都在用的5个素材网站,视频、音效、图片全部免费下载~
推荐几个自媒体必备的素材库,免费可商用,建议收藏! 1、菜鸟图库 视频素材下载_mp4视频大全 - 菜鸟图库 国内超大的素材库,在这里你可以找到设计、办公、图片、视频、音频等各种素材。视频素材就有上千个,全部都很高清…...

开放式耳机新巅峰!南卡OE Pro兼备澎湃音质、舒适佩戴、创新设计
众所周知,当初苹果带来TWS耳机新时代以后,后面有许多的蓝牙耳机相继跟随和模仿,但NANK南卡却独辟蹊径,将在近日重磅推出首款0压无感全开放无线耳机——南卡OE Pro,走向开放式TWS耳机的新时代。 31度黄金倾斜受力面&…...

1700页,卷S人的 Java《八股文》PDF手册,涨薪跳槽拿高薪就靠它了
大家好,最近有不少小伙伴在后台留言,又得准备面试了,不知道从何下手! 不论是跳槽涨薪,还是学习提升!先给自己定一个小目标,然后再朝着目标去努力就完事儿了! 为了帮大家节约时间&a…...

普通人是否能从ChatGPT中分一杯羹?
ChatGPT3.0刚刚推出,最开始的时候,人们只是将ChatGPT看作一个很会聊天的机器人,无论问题多么天马行空,它的答案看上去都有理有据。后来,像打开潘多拉魔盒一样,很多人开始拿它编大纲、撰写文案、编代码、创作…...

SpringBoot自动装配原理(附面试快速答法)
文章目录SpringBoot自动装配原理1. 从调用SpringApplication构造器方法开始2. 解析启动类4.按需装配4.1 分析dubbo自动装配5. 如果定义自己的starter6. 面试答法SpringBoot自动装配原理 之前面试被问到这个题目,只会答一些spi、AutoConfigration注解、Import之类的&…...

如何在大厂做好架构演进?
1 架构演进的定义 1.1 定义 通过设计新的系统架构(4R),来应对业务和技术的发展变化。 1.2 关键点 新架构新的复杂度 1.3 目的 应对业务和技术的发展变化后带来新的复杂度。 案例 淘宝去IOE,是因为业务发展大了后,IOE的成本和可控性难…...

减半技术实现求a的n次幂
目录 减半技术实现求a的n次幂 程序设计 程序分析 减半技术实现求a的n次幂 【问题描述】给定两个正整数a和n,采用减半技术求a的n次幂;其中a<100,b<20; 【输入形式】两个整数a,n(a与n中间用空格隔开); 【输出形式】一个整数 【样例输入1】2 3 【样例输出1】8 【样…...

MYSQL8窗口函数
MYSQL8窗口函数 MYSQL8窗口函数窗口函数分类序号函数--排行榜row_number()示例rank()示例dense_rank()示例partition by对每个分区内的行进行排名不加partition by全局排序 开窗聚合函数分布函数CUME_DIST()PERCENT_RANK() 前后函数LAG()的用法LEAD() 头尾函数其他函数NTH_VALU…...

全国大学生智能汽车竞赛——安装Ubuntu操作系统(双系统)
1.1 电脑分区 1.1.1 分区原因 由于我们想要在电脑上同时安装Windows和Ubuntu系统,所以就要在window使用的内存中划分出来一段用来给Ubuntu系统使用,相当于一个应用程序一样 1.1.2 分区步骤 1.右击此电脑,点击管理,然后双击左侧…...

[STM32F103C8T6]看门狗
看门狗: 在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造 成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会 造成整个…...

浪潮:2022年净利同比增长51.39%
一、4月头条 华为的紧急回应,让东方材料21亿收购要黄? 4月10日消息,东方材料昨日晚间公告拟定增募资不超20亿元,用于向诺基亚全资子公司NSN收购TD TECH 51%股权(交易对价21.22亿元)。TD TECH剩余49%股权由…...

大厂面试内幕:阿里内部整理出的5000页Java面试复盘指南,起飞!!!
互联网的技术岗一直是高薪的代名词,特别是大厂,应届生的年薪基本都20W起,比一般的公司高多了。 看下面这张网上热传的大厂应届生薪酬表就知道了,SP offer甚至能拿到30W以上。 技术社区也有晒出高薪offer的同学: 除了薪…...

数据结构——哈希表相关题目
数据结构——哈希表相关题目 242. 有效的字母异位词1.暴力解法2.排序后比较3.哈希表 383. 赎金信哈希解法 49. 字母异位词分组438. 找到字符串中所有字母异位词3. 无重复字符的最长子串76. 最小覆盖子串349. 两个数组的交集1.排序双指针2.哈希表 350. 两个数组的交集 II1.排序双…...

域名解析设置方法
域名解析设置都是实时生效的,一般只需几秒即可同步到各地 DNS 上,但各地 DNS 均有缓存机制,解析的最终生效取决于各运营商刷新时间! 一、A记录 ①.主机名必须填写; 常用主机名有:www//*,效果参见上图说明&…...

MySQL连接空闲时间超过8小时报错原因与延伸知识
1 错误原因 1.1 两个参数 MySQL服务端两个参数控制连接超时时间: wait_timeoutinteractive_timeout1.1.1 如何查看 show global variables like interactive_timeout show global variables like wait_timeout 复制代码 1.1.2 含义与区别 wait_timeout…...

Flutter渲染原理
一 Widget Element RenderObject 之间的关系 1 Widget 在Flutter 中,万物皆是Widget,无论是可见的还是功能型的。一切都是Widget. 官方文档中说的Widget 使用配置和状态来描述View 界面应该长什么样子。 它不仅可以表示UI元素,也可以表示一些功能性的…...

PathCore:IAD文献解读
论文链接:[Towards Total Recall in Industrial Anomaly Detection]Towards Total Recall in Industrial Anomaly Detection :数据集, :标签 : 在ImageNet上预训练后的网络 第 张图 网络中第 层 1. Locall…...

C语言判断一个日期是在该年的第几天案例讲解
今天是2023年4月11号,我们就用今天举例得出是2023年的第几天。 思路分析 1)我们想知道2023年4月11号是2023年的第几天,只需要把1到3月份的天数累加求和然后加上今天日期也就是11就可以算出2023年4月11号是2023年的第几天。 推广:…...

【超全总结】集成环信消息推送注意事项(华为、oppo、vivo等)
环信即时通讯 IM 支持集成第三方厂商的消息推送服务,为 Android 开发者提供低延时、高送达、高并发、不侵犯用户个人数据的离线消息推送服务。 当客户端应用进程被关闭等原因导致用户离线,环信即时通讯 IM 服务会通过第三方厂商的消息推送服务向该离线用…...

C++回调函数以及epoll中回调函数的使用
回调函数是一种常用的编程技术,它允许程序在运行时将一个函数作为参数传递给另一个函数,以实现更加灵活和可扩展的功能。在C中,回调函数通常被实现为函数指针或者函数对象。 函数指针是指向函数的指针变量,可以通过它来调用函数。…...

0基础学习软件测试有哪些建议
其实现在基础的资料和视频到处都是,就是看你有没有认真的去找学习资源了,去哪里学习都是要看你个人靠谱不靠谱,再好的教程和老师,你自己学习不进去也是白搭在正式选择之前,大可以在各种学习网站里面找找学习资源先自己…...

MySQL数据类型
文章目录一、数据类型分类二、数值类型1. tinyint 类型2. bit 类型3. int 类型4. float 类型5. decimal 类型三、字符串类型1. char 类型2. varchar 类型3. char 和 varchar 比较4.日期和时间类型5. enum 和 set一、数据类型分类 数据类型本质也是一种约束! 如果插入…...

【设计模式】从Mybatis源码中学习到的10种设计模式
文章目录 一、前言二、源码:学设计模式三、类型:创建型模式1. 工厂模式2. 单例模式3. 建造者模式 四、类型:结构型模式1. 适配器模式2. 代理模式3. 组合模式4. 装饰器模式 五、类型:行为型模式1. 模板模式2. 策略模式3. 迭代器模式…...

爬虫攻守道 - 猿人学第20题 - 殊途同归
写在开头 这题也是,自己搞顶多追踪到wasm代码,然后就走不下去了。找了2个参考方案,自己做的过程中还又遇到些新的问题,下面做个记录。解法1参考文章解法2参考文章 解法1:追根溯源 在 JS 代码中追踪到 Payload 赋值位…...