当前位置: 首页 > news >正文

uniapp使用live-pusher实现模拟人脸识别效果

需求:

1、前端实现模拟用户人脸识别,识别成功后抓取视频流或认证的一张静态图给服务端。

2、服务端调用第三方活体认证接口,验证前端传递的人脸是否存在,把认证结果反馈给前端。

3、前端根据服务端返回的状态,显示在页面上用于提示用户。

难点:

1、前端APP如果要实现人脸活体校验验证,需要对接大厂的SDK实现。

2、一开始我采用用使用在App内嵌套H5来单独部署一套人脸验证,把结果通过webview与APP进行数据交互,但是H5试了使用好用高效的effet.js 库人脸识别 目前仅支持H5(但是发现最后在手机上识别人像整个人被压缩似得,而且是反向镜头,跟作者已反馈,等待作者持续更新)。

3、抛弃了使用App嵌套H5方法,因此最终选择了使用原生live-pusher直播流来模拟实现人脸识别效果。本打算给大家写成组件方便大家直接调用来着,但是发现组件内获取实例仅支持在onReady页面生命周期使用。

实现思路

1、首先需要获取手机是否有录音以及相机权限,没有的话引导用户前去设置页主动设置。

2、其次创建live-pusher实例,根据业务需求实现自己的模拟人脸识别思路(目前我们这是在用户手动点击5s后进行自动抓拍的)。

3、前端拿到抓拍最后一帧图片调用接口给服务端传递,服务端调用第三方进行人脸活体检测,一般是需要付费的哈。

4、最后前端把服务端返回的识别状态展示在页面上,方便后续用户操作。

代码步骤(当前是vue3项目示例,最低sdk 21版本并且勾选livepusher)

1、获取当前手机是否允许开启相机和录音权限。(建议直接使用官方大佬写的js sdk)App权限判断和提示

引入js sdk插件(vue3版本需要转换为export function,vue2版本直接按照官方大佬的直接使用即可)

import {requestAndroidPermission,gotoAppPermissionSetting} from '@/js_sdk/wa-permission/permission.js'

2、创建live-pusher实例,根据业务需求写业务逻辑(注意:一定要用nvue页面哈,cover-image覆盖一个矢量图在直播流画面上)。

ilve-pusher详细参数说明具体看live-pusher官方文档

<template><view class="container"><live-pusher id='livePusher' ref="livePusher" class="livePusher" url="" mode="FHD" :muted="true":enable-camera="true" :auto-focus="true" :beauty="2" whiteness="2" aspect="9:16" local-mirror="disable"@statechange="statechange" @error="error" @netstatus="netstatus" :style="[{width:'400rpx',height: '400rpx',marginLeft: '175rpx',marginTop:'20rpx',}]"></live-pusher><cover-image style="width: 400rpx;height: 400rpx;transform: scale(1.01);position: absolute;left: 175rpx;top: 190.5rpx;" src="@/static/circleBg.png" /></view>
</template>
<script>
export default{onReady() {//需要在onReady页面生命周期函数来写this.livePusher = uni.createLivePusherContext("livePusher", this);},
}
</script>

3、5s后抓拍最后一帧图片给服务端,人脸识别成功即登录系统,识别失败跳转认证失败页面(这里跟服务端对接采用的formdata格式上传文件流,你也可以采取转成base64

临时路径转base64

			// 定时器 几秒后自动抓拍handleSetTime() {this.timeFlag = setInterval(async () => {if (this.timeOut > 0) {this.timeOut--this.titleTips = this.countDownTimerStartTipsthis.buttonTip = `${this.countDownTimerStartBtnTips} ${this.timeOut}秒`}if (this.timeOut == 1) {this.livePusher.snapshot({success: (res) => {this.snapshotInfo = res.message}})}// 进行快照人脸认证if (this.timeOut == 0) {clearInterval(this.timeFlag);this.titleTips = this.countDownTimerZeroTipsthis.buttonTip = this.countDownTimerZeroBtnTipsuni.showLoading({title: this.countDownTimerZeroBtnTips})uni.uploadFile({url: 'http://192.168.60.2:8080/bsCheckImage/checkImg',filePath: this.snapshotInfo.tempImagePath,name: "file",success: (uploadFileRes) => {const jxStrData = JSON.parse(uploadFileRes.data)console.log(jxStrData)const resResultCode = jxStrData.codeconst resResultData = jxStrData.dataif (resResultCode !== '00000') {uni.navigateTo({url: '/pages/liveb-result/liveb-result?failResultObj=' +this.passData(jxStrData)})this.handleStop()return}if (resResultCode == '00000' && resResultData.score >= 0.8) {uni.showToast({title: this.faceSucessTips})this.buttonTip = this.faceSucessTipsthis.handleStop()return}if (resResultCode == '00000' && resResultData.score < 0.8) {const paramsData = {success: false,code: 'A9901',message: '人脸校验失败,请将人脸正对取景框内重新认证',failCode: 'A9901',faceScore: resResultData.score}uni.navigateTo({url: '/pages/liveb-result/liveb-result?failResultObj=' +this.passData(paramsData)})this.handleStop()return}},fail: (error) => {uni.hideLoading()uni.navigateTo({url: '/pages/liveb-result/liveb-result',animationType: 'zoom-out',animationDuration: 1000})this.handleStop()},complete: () => {uni.hideLoading()clearInterval(this.timeFlag) // 清除定时器,防止再次执行}});}}, 1000)},

4、人脸认证失败服务端返回状态,前端跳转认证失败页面,返回时给上个页面传递监听参数。(liveb-result.vue页面)

<template><view class="container"><view class="result-area"><view class="result-icon"><image class="result-icon-pic" src="../../static/fece_result.png"></image></view><view class="result-tips">{{failInfos.message}}</view><view class="result-button" @click="handleRetryFace">重新认证</view></view></view>
</template><script setup>import {ref} from 'vue'import {onLoad,onBackPress} from '@dcloudio/uni-app'const faceStatus = ref('')const failInfos = ref({})const failResultMsg = (() => {const data = {code: '3698',msg: '人脸认证失败'}uni.$emit('failResult', data);})onLoad((options) => {if (options.failResultObj) {const resultObj = JSON.parse(decodeURIComponent(options.failResultObj));failInfos.value = resultObj}})const handleRetryFace = (() => {console.log('handleRetryFace')failResultMsg()uni.navigateBack()})onBackPress((e) => {failResultMsg()})
</script><style lang="scss" scoped>.container {width: 750rpx;.result-area {position: absolute;top: 44%;left: 50%;transform: translate(-50%, -50%);.result-icon {display: flex;align-items: center;justify-content: center;.result-icon-pic {width: 140rpx;height: 140rpx;}}.result-tips {font-weight: 600;text-align: center;font-size: 32rpx;color: #515151;margin-top: 20rpx;margin-bottom: 60rpx;}.result-button {padding: 20rpx 100rpx;background-color: rgba(12, 75, 158, 1);border-radius: 60rpx;color: #eeeeee;}}}
</style>

人脸识别页面所有代码(liveb.nvue)

<template><view class="container"><view class="header"><view class="header-title"><text class="header-title-tips">{{titleTips}}</text><view class="header-title-carmera"><image class="header-title-img" src="../../static/change_camera.png" @click="handleChangeCrame"></image></view></view></view><live-pusher id='livePusher' ref="livePusher" class="livePusher" url="" mode="FHD" :muted="true":enable-camera="true" :auto-focus="true" :beauty="2" whiteness="2" aspect="9:16" local-mirror="disable"@statechange="statechange" @error="error" @netstatus="netstatus" :style="[{width:'400rpx',height: '400rpx',marginLeft: '175rpx',marginTop:'20rpx',}]"></live-pusher><cover-image style="width: 400rpx;height: 400rpx;transform: scale(1.01);position: absolute;left: 175rpx;top: 190.5rpx;" src="@/static/circleBg.png" /><view class="footer"><view class="footer-tips"><text class="footer-tips-first">{{footerTipsFirst}}</text><text class="footer-tips-second">{{footerTipsSecond}}</text></view><view class="footer-required"><view class="footer-required-row"><view class="row-area" v-for="(item,index) in tipList" :key="index"><image class="row-area-img" :src="item.icon"></image><text class="row-area-tip">{{item.name}}</text></view></view></view></view><!-- 手动抓拍 --><view class="start-button" :style="{marginTop:footerBtnStyle.marginTop}"><view class="button-hover":style="{width:footerBtnStyle.width,padding:footerBtnStyle.padding,borderRadius:footerBtnStyle.borderRadius,backgroundColor:footerBtnStyle.btnBackground}"@click="startFace"><text class="button-tip" :style="{fontSize:footerBtnStyle.fontSize,color:footerBtnStyle.textColor}">{{buttonTip}}</text></view></view></view>
</template><script>import {requestAndroidPermission,gotoAppPermissionSetting} from '@/js_sdk/wa-permission/permission.js'export default {name: 'sevenq-faceLiver',props: {//是否默认开启抓拍isDeaultStartLive: {type: Boolean,default: false},//默认开启的话需要设置延迟时间(毫秒级)defaultStartDelayTime: {type: Number,default: 600},//是否需要监听结果页传递的事件needListenResultPage: {type: Boolean,default: true},//是否开启可以翻转摄像头isCanChangeCarame: {type: Boolean,default: true},//抓拍倒计时 (如果默认开启需要+1)snapCountdownTimer: {type: Number,default: 6},//如果不允许翻转摄像头 提示词notAllowChangeCarameMsg: {type: String,default: "刷脸认证仅支持前置摄像头"},//顶部提示词topTitleTips: {type: String,default: "请把人脸放在圆圈内拍摄脸部,开始人脸识别"},//提示词 1 footerTipsFirst: {type: String,default: "确认为您本人照片"},//提示词 2footerTipsSecond: {type: String,default: "保持正脸在取景框中系统将在5s后自动抓拍"},//提示展示列表tipList: {type: Object,default: [{icon: "../../static/img3.png",name: '正对手机'},{icon: "../../static/img2.png",name: '光线充足'},{icon: "../../static/img1.png",name: '脸无遮挡'},]},//抓拍倒计时开始时提示词countDownTimerStartTips: {type: String,default: "请保存人脸在实景框中,正在进行抓拍"},//抓拍倒计时为0时提示词countDownTimerZeroTips: {type: String,default: "正在人脸认证中,请稍等..."},//按钮默认文本buttonTips: {type: String,default: "开始人脸识别"},//抓拍倒计时开始时按钮显示提示词countDownTimerStartBtnTips: {type: String,default: "正在抓拍中"},//抓拍倒计时为0时按钮提示词countDownTimerZeroBtnTips: {type: String,default: "人脸认证中...."},//认证成功按钮提示词faceSucessTips: {type: String,default: "认证成功"},//权限提示开启提示词premissonTips: {type: String,default: "当前应用需要使用相机权限进行拍照,但相机权限暂未开启。是否前往应用设置打开相机权限?"},//底部按钮样式footerBtnStyle: {type: Object,default: {marginTop: '120rpx',width: '480rpx',padding: '24rpx',btnBackground: 'rgba(12, 75, 158, 1)',borderRadius: '300rpx',textColor: '#dfdfdf',fontSize: '32rpx'}}},data() {return {titleTips: this.topTitleTips,buttonTip: this.buttonTips,livePusher: '', // livePusher实例snapshotInfo: '', // 快照信息showCountDown: false, // 拍摄倒计时timeOut: this.snapCountdownTimer, // 签到倒计时timeFlag: null, // 定时器isPass: null, // 是否通过人脸认证phoneSysInfos: {}, //当前手机系统信息}},onReady() {this.livePusher = uni.createLivePusherContext("livePusher", this);},onShow() {//监听结果页面传递的失败事件if (this.needListenResultPage) {uni.$on('failResult', (resultData) => {if (resultData.code == '3698') {clearInterval(this.timeFlag)this.resertAll()this.livePusher.startPreview()}});}},async mounted() {const that_ = thisif (!that_.showCountDown) {setTimeout(function() {that_.getCarmeraPremisson()}, this.defaultStartDelayTime)}that_.getPhoneSys()},onUnload() {if (this.needListenResultPage) {uni.$off('failResult');}clearInterval(this.timeFlag)uni.hideLoading();},onHide() {console.log('页面隐藏')},methods: {//校验是否获取相机权限async getCarmeraPremisson() {const currentSystem = uni.getSystemInfoSync().platformif (currentSystem == 'android') {const result = await requestAndroidPermission("android.permission.CAMERA")if (result == 1) {if (this.isDeaultStartLive) { //如果打开页面就进行抓拍this.showCountDown = true}this.startPreview()} else {uni.showModal({title: '提示',content: this.premissonTips,confirmText: '去设置',cancelText: '取消',success: function(res) {if (res.confirm) {gotoAppPermissionSetting()} else if (res.cancel) {uni.showToast({icon: 'error',title: '暂无相机权限'})}}});}}},//重置初始化值 需要在认证失败时候再次调用resertAll() {this.titleTips = this.topTitleTipsthis.buttonTip = this.buttonTipsthis.snapshotInfo = '' // 快照信息this.showCountDown = false // 拍摄倒计时this.timeOut = this.snapCountdownTimer // 签到倒计时this.timeFlag = null // 定时器this.isPass = null // 是否通过人脸认证},//手动翻转摄像头handleChangeCrame() {if (!this.isCanChangeCarame) {uni.showToast({icon: 'none',title: this.notAllowChangeCarameMsg})return}this.livePusher.switchCamera({success: (a) => {uni.showToast({icon: 'none',title: '摄像头翻转成功'})}});},//手动开始人脸识别startFace() {const that_ = thisif (!that_.showCountDown) {that_.showCountDown = trueif (that_.showCountDown) {const {platform,osVersion} = that_.phoneSysInfosif (platform == 'android' && osVersion < 10) { //判断兼容安卓10以下效果that_.startPreview()return}that_.handleSetTime()}}},// 开始预览直播流startPreview() {const _that = thisthis.livePusher.startPreview({success: (res) => {if (_that.showCountDown) {_that.handleSetTime()}}})},// 定时器 几秒后自动抓拍handleSetTime() {this.timeFlag = setInterval(async () => {if (this.timeOut > 0) {this.timeOut--this.titleTips = this.countDownTimerStartTipsthis.buttonTip = `${this.countDownTimerStartBtnTips} ${this.timeOut}秒`}if (this.timeOut == 1) {this.livePusher.snapshot({success: (res) => {this.snapshotInfo = res.message}})}// 进行快照人脸认证if (this.timeOut == 0) {clearInterval(this.timeFlag);this.titleTips = this.countDownTimerZeroTipsthis.buttonTip = this.countDownTimerZeroBtnTipsuni.showLoading({title: this.countDownTimerZeroBtnTips})// this.$emit(handleStartFaceApi, {// 	code: '4364',// 	msg: '开始人脸与服务端进行人脸',// 	currentTempImagePath: this.snapshotInfo.tempImagePath// })uni.uploadFile({url: 'http://192.168.60.2:8080/bsCheckImage/checkImg',filePath: this.snapshotInfo.tempImagePath,name: "file",success: (uploadFileRes) => {const jxStrData = JSON.parse(uploadFileRes.data)console.log(jxStrData)const resResultCode = jxStrData.codeconst resResultData = jxStrData.dataif (resResultCode !== '00000') {uni.navigateTo({url: '/pages/liveb-result/liveb-result?failResultObj=' +this.passData(jxStrData)})this.handleStop()return}if (resResultCode == '00000' && resResultData.score >= 0.8) {uni.showToast({title: this.faceSucessTips})this.buttonTip = this.faceSucessTipsthis.handleStop()return}if (resResultCode == '00000' && resResultData.score < 0.8) {const paramsData = {success: false,code: 'A9901',message: '人脸校验失败,请将人脸正对取景框内重新认证',failCode: 'A9901',faceScore: resResultData.score}uni.navigateTo({url: '/pages/liveb-result/liveb-result?failResultObj=' +this.passData(paramsData)})this.handleStop()return}},fail: (error) => {uni.hideLoading()uni.navigateTo({url: '/pages/liveb-result/liveb-result',animationType: 'zoom-out',animationDuration: 1000})this.handleStop()},complete: () => {uni.hideLoading()clearInterval(this.timeFlag) // 清除定时器,防止再次执行}});}}, 1000)},//向下个页面传递参数passData(obj) {let passDataStr = JSON.stringify(obj)let newPassDataStr = passDataStr.replace(/%/g, '%25');return encodeURIComponent(newPassDataStr);},//抛出停止推流 在调用成功与失败都得调用handleStop() {this.livePusher.stop()},//监听直播流状态变化statechange(val) {console.log(val, '监听直播流变化')},//监听直播流警告error(err) {console.log(err, '监听直播流警告')},//监听网络状态netstatus(status) {console.log(status, '监听直播流网络状态')},//获取手机型号getPhoneSys() {const system = uni.getDeviceInfo()this.phoneSysInfos = system}}}
</script><style lang="scss" scoped>.container {width: 750rpx;}
</style>

效果图如下所示

注意:代码仅可自己使用,不可进行二次转载哈,有问题在请私信我哦

相关文章:

uniapp使用live-pusher实现模拟人脸识别效果

需求&#xff1a; 1、前端实现模拟用户人脸识别&#xff0c;识别成功后抓取视频流或认证的一张静态图给服务端。 2、服务端调用第三方活体认证接口&#xff0c;验证前端传递的人脸是否存在&#xff0c;把认证结果反馈给前端。 3、前端根据服务端返回的状态&#xff0c;显示在…...

【JavaSE】【网络原理】初识网络

目录 一、网络互联二、局域网与广域网三、网络通信基础3.1 IP地址3.2 端口号3.3 网络协议3.4 五元组 四、协议分层4.1 OSI七层网络模型4.2 TCP/IP五层(四层)网络模型4.3 网络设备 五、网络数据通信基本流程。5.1 封装和分用5.2 简述过程 一、网络互联 网络互联&#xff1a; 网…...

鸿蒙之路的坑

1、系统 Windows 10 家庭版不可用模拟器 对应的解决方案【坑】 升级系统版本 直接更改密钥可自动升级系统 密钥找对应系统的&#xff08;例&#xff1a;windows 10专业版&#xff09; 升级完之后要激活 坑1、升级完后事先创建好的模拟器还是无法启动 解决&#xff1a;删除模拟…...

Python生日祝福烟花

1. 实现效果 2. 素材加载 2个图片和3个音频 shoot_image pygame.image.load(shoot(已去底).jpg) # 加载拼接的发射图像 flower_image pygame.image.load(flower.jpg) # 加载拼接的烟花图 烟花不好去底 # 调整图像的像素为原图的1/2 因为图像相对于界面来说有些大 shoo…...

Ubuntu环境 nginx.conf详解(二)

1、nginx.conf 结构详解&#xff1a; http 块&#xff1a;用于配置 HTTP 服务器的相关设置&#xff0c;包括处理 HTTP 和 HTTPS。 stream 块&#xff1a;用于配置 TCP/UDP 代理服务器&#xff0c;适用于需要进行四层负载均衡的情况。 ... # 全局块 events {...} …...

shardingsphere分库分表项目实践4-sql解析sql改写

为什么要sql解析重写&#xff1f; 如果我们的系统数据库实现了分表&#xff0c;那么我们的sql中表名需要根据参数动态确定&#xff0c;那么代码怎么写&#xff1f; 方案1&#xff1a; 自己手动拼接&#xff0c; 比如 update t_user_${suffix} , ${suffix} 作为一个变量传递…...

mysql数据库中,一棵3层的B+树,假如数据节点大小是1k,那这棵B+可以存多少条记录(2100万的由来)

在MySQL中&#xff0c;3层的B树可以存储的数据量取决于多个因素&#xff0c;包括页大小、每行数据的大小以及索引项的大小。以下是一个详细的计算过程&#xff1a; 一、假设条件 页大小&#xff1a;在InnoDB存储引擎中&#xff0c;B树的每个节点&#xff08;页&#xff09;大…...

Git 操作全解:从基础命令到高级操作的实用指南

文章目录 1.基本命令1.初始化仓库2.克隆远程仓库3.查看当前仓库状态4.查看提交日志5.添加文件到暂存区6.提交更改7.查看仓库的配置信息 2.分支操作1.查看所有分支2.创建新分支3.切换名称4.创建并切换到新分支5.删除分支6.查看当前分支 3.合并分支1.合并分支2.解决合并冲突 4.远…...

华院计算参与项目再次被《新闻联播》报道

12月17日&#xff0c;央视《新闻联播》播出我国推进乡村振兴取得积极进展。其中&#xff0c;华院计算参与的江西省防止返贫监测帮扶大数据系统被报道&#xff0c;该系统实现了由原来的“人找人”向“数据找人”的转变&#xff0c;有效提升监测帮扶及时性和有效性&#xff0c;守…...

从一次线上故障聊聊接口自动化测试

1、背景 3月初&#xff0c;运营同事配置了个还未上线的页面到网站首页 banner&#xff0c;导致用户点了报错。尽管这次很明确是运营人为操作失误引起的故障&#xff0c;但过往此类核心页面的访问异常&#xff0c;我们已不是第一次遇见。 从平台整体利益触发&#xff0c;我们各…...

Element-ui的使用教程 基于HBuilder X

文章目录 1.Element-ui简介2.使用HBuilderX 创建一个基于Vue3的项目 &#xff08;由于是使用的基于Vue3的Element-ui&#xff09;3.安装element-ui4.在项目里完全引用element-ui5.引用组件6.运行项目 1.Element-ui简介 Element&#xff0c;一套为开发者、设计师和产品经理准备…...

Chapter 03 复合数据类型-1

1.列表 Python内置的一种有序、可变的序列数据类型&#xff1b; 列表的定义&#xff1a; [ ]括起来的逗号分隔的多个元素组成的序列 列表对象的创建&#xff1a; &#xff08;1&#xff09;直接赋值 >>> list1 []#创建一个空列表赋值给list1 >>> list…...

【Python知识】Python面向对象编程知识

Python面向对象编程知识 概述1. 类&#xff08;Class&#xff09;2. 对象&#xff08;Object&#xff09;3. 封装&#xff08;Encapsulation&#xff09;4. 继承&#xff08;Inheritance&#xff09;5. 多态&#xff08;Polymorphism&#xff09;6. 抽象&#xff08;Abstractio…...

CSharp: Oracle Stored Procedure query table

存储过程查询postgreSQL,Oracle 和sql server,Mysql 有区别。程序调用也是有区别。 oracle sql script: CREATE OR REPLACE PROCEDURE procSelectSchool(paramSchoolId IN char,p_cursor OUT SYS_REFCURSOR ) AS BEGINOPEN p_cursor FORSELECT *FROM SchoolWHERE SchoolId p…...

“协同过滤技术实战”:网上书城系统的设计与实现

2.1 JSP技术介绍 Java Server Pages这三个英文词汇的首字母的组合就是JSP。所以JSP是一个简写的名字&#xff0c;代表动态网页开发技术。JSP与Java的关系可以使用公式表示&#xff0c;即&#xff1a;JSP HTMLJava&#xff0c;HTML就是编写静态内容的标记语言。JSP则是可以编写网…...

Dhatim FastExcel 读写 Excel 文件

Dhatim FastExcel 读写 Excel 文件 一、说明1、主要特点2、应用场景 二、使用方法1、引入依赖2、Sheet 数据3、读取 Excel4、写入 Excel 一、说明 Github 地址&#xff1a;Dhatim FastExcel Dhatim FastExcel是一个高性能、轻量级的Java库&#xff0c;专门用于读取和写入Exce…...

YOLO11全解析:从原理到实战,全流程体验下一代目标检测

前言 一、模型介绍 二、网络结构 1.主干网络&#xff08;Backbone&#xff09; 2.颈部网络&#xff08;Neck&#xff09; 3.头部网络&#xff08;Head&#xff09; 三、算法改进 1.增强的特征提取 2.优化的效率和速度 3.更高的准确性与更少的参数 4.环境适应性强 5.…...

深度学习领域的主要神经网络架构综述

阅读本文前请先按照顺序阅读&#xff1a; Coursera吴恩达《神经网络与深度学习》课程笔记&#xff08;1&#xff09;-- 深度学习概述_吴恩达深度学习课程-CSDN博客 https://blog.csdn.net/red_stone1/article/details/77799014 完结篇 | 吴恩达deeplearning.ai专项课程精炼笔…...

【Nginx系列】---Nginx配置tcp转发

参考 Nginx 配置文件&#xff1a; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connections 1024; }stream {# 第一个服务转发upstream mysqltest {server 172.16.187.142:9000;}server {listen 9000;proxy_pass mysqltest;}…...

Java抽象工厂+单例模式

在前端时间开发过程中,有这样一个业务场景:A;B两家厂商设备进行设备信息的同步功能。 根据实际场景,做了抽象工厂+单例模式实现调用工厂时,生成不同的具体业务引用对象,实现方法的调用。 概念: 抽象工厂模式通过接口或抽象类来创建一系列相关或依赖对象。它定义了一组工…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

jdbc查询mysql数据库时,出现id顺序错误的情况

我在repository中的查询语句如下所示&#xff0c;即传入一个List<intager>的数据&#xff0c;返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致&#xff0c;会导致返回的id是从小到大排列的&#xff0c;但我不希望这样。 Query("SELECT NEW com…...

2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版

1.题目描述 2.思路 当前的元素可以重复使用。 &#xff08;1&#xff09;确定回溯算法函数的参数和返回值&#xff08;一般是void类型&#xff09; &#xff08;2&#xff09;因为是用递归实现的&#xff0c;所以我们要确定终止条件 &#xff08;3&#xff09;单层搜索逻辑 二…...

PydanticAI快速入门示例

参考链接&#xff1a;https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...

41道Django高频题整理(附答案背诵版)

解释一下 Django 和 Tornado 的关系&#xff1f; Django和Tornado都是Python的web框架&#xff0c;但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架&#xff0c;鼓励快速开发和干净、实用的设计。它遵循MVC设计&#xff0c;并强调代码复用。Django有…...