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//*,效果参见上图说明&…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...
RabbitMQ 各类交换机
为什么要用交换机? 交换机用来路由消息。如果直发队列,这个消息就被处理消失了,那别的队列也需要这个消息怎么办?那就要用到交换机 交换机类型 1,fanout:广播 特点 广播所有消息:将消息…...
