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

web 语音通话 jssip

先把封装好的地址安上(非本人封装):webrtc-webphone: 基于JsSIP开发的webrtc软电话

jssip中文文档:jssip中文开发文档(完整版) - 简书

jssip使用文档:(我没有运行过,但是他写的很清楚,反正比我好)jssip+webrtc+freeswitch实现电话网页及遇到的488状态码问题_freeswitch 488_weixin_39715323的博客-CSDN博客

正常使用由于web限制应该在https上使用,但是http也不是不可以,我放到下一篇文章了,这就说怎么使用

我这用的是webrtc+jssip

webrtc-webphone已经实现了我的需求,所以我没有使用原生jssip

特主要实现的功能有:注册,拨叫,接听,保持,恢复

我的项目中需要静音(指我不能说话,通话人可以讲话)

所以我将hold(保持)和unhold(恢复)改成了mute和unmute

 下面是完整代码和方法说明:

1.init(注册citbar)config中需要使用ip地址、端口号、extNo拨号人、extPwd密码

2.handleAgentBarBtnClick(通话状态更改)

        makecall(拨号)

        hangup(挂断)

        hold(静音)

        unhold(取消静音)

3.onbeforeunload (通话中刷新对讲群组中没有退出,导致群组中有多个同一个人)

4.beforeDestroy(切换页面后没有退出群组,刷新不走这个方法)

注意:

1.不要重复拨号,状态卡住后就能同一个设备对话了bug

2.如果想切换页面还能通话就不要beforeDestroy,但是在其他页面在回来时会导致状态不一致,而且容易出现卡状态,所以我将通话操作放在index最顶层里面

<template><div class="top"><div style="padding: 0 30px;"><div style="display: flex;padding-top: 3px;"><!-- 设备树插件 --><organizationTree ref="organizationTree"></organizationTree><div style="width:77vw;height:80vh; margin-top: 5px;"><div class="photo_date" style="position:relative"><div style="display:flex;min-width:30%"><div class="text_type photo_deviceName " v-show="groupName" style="text-align: center;">{{groupName}}</div><div class="text_type" :class="meetingStatus==2?' meetingName1':' meetingName'"style="width: 125px;text-align: center;">{{meetingStatus==2?meetingName:meetingName1}}</div></div><div style="position: absolute;right: 0;" v-show="createBy==userName"><div v-show="meetingStatus==2" class="btn_type photo_meeting_btn" @click="meetingAll"style="position:absolute; right:135px;width: 140px;height: 35px;line-height: 35px;">邀请全部成员</div><div class="bg_btn_type photo_meeting_btn" @click="meeting"style="position:absolute; right:0;width: 125px;height: 35px;line-height: 35px;">{{meetingStatus==1?'开始会议':'结束会议'}}</div></div></div><div class=""><div class="deviceList_title"><div class="deviceList_title_text" style="width:15%">名称</div><div class="deviceList_title_text" style="width:15%">imei</div><div class="deviceList_title_text" style="width:15%">类型</div><div class="deviceList_title_text" style="width:15%">状态</div><div class="deviceList_title_text" style="width:15%">会议状态</div><div class="deviceList_title_text" style="width:24%">操作</div><!-- <div class="deviceList_title_text">操作</div> --></div><div v-show="deviceList" class="deviceList"><div class="deviceList_list" :class="item.id==itemId?'deviceList_list1':''"v-for="(item,index) in deviceList" :key="item.id" @mouseover="mouseover(item.id)"@mouseleave="mouseout()"><!-- <el-tooltip :content="item.name" placement="bottom" effect="light"> --><div class="deviceList_title_text" style="width:15%">{{item.devName}}</div><!-- </el-tooltip> --><div class="deviceList_title_text" style="width:15%">{{item.imei}}</div><div class="deviceList_title_text" style="width:15%">{{item.devTypeName}}</div><div class="deviceList_title_text" style="width:15%">{{dictionary(item.devStatus,'dev_status',item.imei)}}</div><div class="deviceList_title_text" style="width:15%">{{dictionary(item.memberStatus,'meeting_member_status')}}</div><div style="width:24%;"><div v-if="meetingStatus==2"><div v-if="item.imei == createBy&&item.imei == userName"><div v-if="item.devStatus==3||item.devStatus==4||item.devStatus==5||item.devStatus==1"><div style="width:100%;justify-content: center;" class="deviceList_operate"><div class="deviceList_title_text1" @click="speak('request')"v-if="item.memberStatus!=3&&item.memberStatus!=1&&item.memberStatus!=4&&item.memberStatus!=5"><div>开始发言</div></div><div class="deviceList_title_text1" @click="speak('request')" v-if="item.memberStatus==3"><div>结束发言</div></div><div class="deviceList_title_text1" @click="outMeeting" v-if="item.memberStatus==2">离开会议</div><div class="deviceList_title_text1" @click="inMeeting" v-if="item.memberStatus==1">进入会议</div></div></div></div><div v-if="item.imei != createBy"><div v-if="item.devStatus==3||item.devStatus==4||item.devStatus==5||item.devStatus==1"><div style="width:100%;justify-content: center;" class="deviceList_operate"v-show="createBy==userName&&isInOrOutMeeting&&deviceList[0].memberStatus==2"><div class="deviceList_title_text1" @click="speak('call',item.imei,'start')"v-if="item.memberStatus!=3&&item.memberStatus!=1&&item.memberStatus!=4&&item.memberStatus!=5"><div>点名发言</div></div><div class="deviceList_title_text1" @click="speak('call',item.imei,'end')"v-if="item.memberStatus==3"><div>结束发言</div></div><div class="deviceList_title_text1" @click="inOrOut('out',item.imei)"v-if="item.memberStatus==2">请离会议</div><div class="deviceList_title_text1" @click="inOrOut('in',item.imei)"v-if="item.memberStatus==1">拉入会议</div></div></div></div></div></div></div></div></div></div></div></div></div></template>
<script>import Header from "../home/header/index";import Footer from "../home/footer/index";import webSocketClass from "@/utils/webSocket";import { postWarnStatus } from "@/api/AlarmRecord";import organizationTree from "./deviceTree/organizationTree"import { devicetree } from "@/api/system/deviceTree";import { timestampToTime } from "../../../utils/time.js"import { addGroup, delGroup, getGroup, listGroup, updateGroup, updateMeetingStatus, selectDeviceGroupDetailList, deviceRequestTalking, inOrOutMeeting } from "@/api/system/group";import { listData } from "@/api/system/dict/data";import Ctibar from './AgentBar/ctibar.js';var audio = document.getElementById('audio');var constraints = {audio: true,video: true,mandatory: {maxWidth: 640,maxHeight: 360}};URL = window.URL || window.webkitURL;var eventHandlers = {'progress': function (e) {console.log('call is in progress');},'failed': function (e) {console.log('call failed: ', e);},'ended': function (e) {console.log('call ended : ', e);},'confirmed': function (e) {console.log('call confirmed');}};export default {dicts: ["warn_type"],components: {Header,Footer,organizationTree},data() {return {// 以下群组isGroup: false,createBy: '',isInOrOutMeeting: false,isSpeak: false,itemId: '',meetingStatus: '',groupDetail: {},isMike: false,deviceList: null,meetingName1: "会议未开始",meetingName: "会议中",groupName: "",dataList: null,userId: JSON.parse(sessionStorage.getItem("userInfo")).userId,userName: JSON.parse(sessionStorage.getItem("userInfo")).userName,// 查询参数queryParams: {pageNum: 1,pageSize: 10,deptId: null,planName: null,status: null,},total: 0,deviceTreeList: [],title: "",num: 1,groupId: '',dictionaryList: [],websocket: null,//初始化SDK所需要的配置config: {host: '39.152.2.103',port: '5066',proto: false,extNo: '',extPwd: '20181231',autoRegister: true,debug: true,//stunServer: 'stun.1.google.com',   可自行修改使用stun服务器地址stateEventListener: this.stateEventListener},//坐席分机号agentNo: '',//客户号码customerNo: '',//拨号弹窗showDial: false,//转接弹窗showTransferDial: false,//转接号码transNum: '',numList: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#',],agentStatus: 'DISCONNECTED',statusMap: {CONNECTED: '已连接',DISCONNECTED: '网络断开',REGISTERED: '已注册',UNREGISTERED: '未注册',REGISTER_FAILED: '注册失败',IN_CALL: '通话中',INCOMING_CALL: '来电振铃',OUTGOING_CALL: '外呼振铃',HOLD: '保持中',CALL_END: '通话结束'},timer: null,timerString: '00:00:00',outNum: '',isHold: true};},computed: {classObject() {const bool1 = this.alarmArr.length > 1;const bool2 = this.alarmArr.length === 1;return {tanchuangbox: true,"tanchuangbox-height-multi": bool1,"tanchuangbox-height-single": bool2,};},},mounted() {this.init()},methods: {// 以下群组inMeeting() {this.loadingFun()var body = {devImei: this.userName,flag: 'in',groupId: this.groupId,}inOrOutMeeting(body).then(response => {if (response.data.success) {} else {this.$modal.msgError(response.data.message);}this.loading.close();},error => {this.loading.close();});},outMeeting() {this.loadingFun()var body = {devImei: this.userName,flag: 'out',groupId: this.groupId,}inOrOutMeeting(body).then(response => {if (response.data.message == '已离开!') {} else {this.$modal.msgError(response.data.message);}this.loading.close();},error => {this.loading.close();});},inOrOut(flag, devImei) {this.loadingFun()var body = {devImei: devImei,flag: flag,groupId: this.groupId,}inOrOutMeeting(body).then(response => {console.log("123123123", response)if (response.data.success) {this.$modal.msgSuccess(response.data.message);} else {this.$modal.msgError(response.data.message);}this.loading.close();},error => {this.loading.close();});},loadingFun() {this.loading = this.$loading({lock: true,text: 'Loading',spinner: 'el-icon-loading',background: 'rgba(0, 0, 0, 0.7)'});},speak(type, devImei, flag) {this.loadingFun()var flagg = ''if (type == 'request') {flagg = !this.isSpeak ? 'start' : 'end'devImei = this.userName} else {flagg = flag}var body = {devImei: devImei,flag: flagg,groupId: this.groupId,type: type,}deviceRequestTalking(body).then(response => {if (response.data.success) {if (type == 'request') {}this.$modal.msgSuccess(response.data.message);//恢复发言} else {this.$modal.msgError(response.data.message);this.hold()}this.loading.close();},error => {this.loading.close();});},// 1、进入元素mouseover(index) {this.itemId = index},// 4、离开元素mouseout() {this.itemId = ''},ws() {console.log('{"groupId":' + this.groupId + '}')this.websocket.webSocketSendMsg('{"groupId":' + this.groupId + '}')},devStatus(devStatus) {for (const i in this.deviceList) {if (this.deviceList[i].devStatus == devStatus) {this.deviceList[i].devStatus = devStatus}}},dictionary(e, type, imei) {for (const i in this.dictionaryList) {if (this.createBy == imei && e == 4) {return '在线'}if (this.dictionaryList[i].dictValue == e && this.dictionaryList[i].dictType == type) {return this.dictionaryList[i].dictLabel}}},handleAdd() {// this.reset();// this.open = true;this.title = "添加群组";},tableRowClassName({ row, rowIndex }) {return 'photo';},rowClass({ row, rowIndex }) {return 'text-align: center;background-color: #1A1D30;color: #fff'},meetingAll() {var meetingStatusAll = 2var params = {groupId: this.groupId,meetingStatus: meetingStatusAll}updateMeetingStatus(params).then(response => {if (response.code == 200) {this.$modal.msgSuccess(response.msg);} else {this.$modal.msgError(response.msg);}});},meeting() {this.loadingFun()if (this.deviceList != null) {console.log('meetingStatusmeetingStatus', this.meetingStatus)var params = {groupId: this.groupId,meetingStatus: this.meetingStatus == 1 ? 2 : 1}updateMeetingStatus(params).then(response => {setTimeout(() => {if (response.code == 200) {if (this.meetingStatus == 1) {if (sessionStorage.getItem('groupId') == this.groupId) {this.handleAgentBarBtnClick('hangup')sessionStorage.setItem('groupId', '')}this.isInOrOutMeeting = falsethis.isHold = truethis.loading.close()}}}, 5000);});}},//孙组件向父组件传递数据wsMeetingStatus(isInterface) {console.log('isMeeting状态:', isInterface)this.meetingStatus = isInterface},groupDeviceItemClick(item) {// this.handleAgentBarBtnClick('hangup')this.isSpeak = falsethis.deviceList = []this.groupId = item.idthis.meetingStatus = item.meetingStatusthis.groupName = item.groupNamethis.createBy = item.createBythis.item = itemvar params = { groupInfoId: item.id, pageNum: 0, pageSize: 0, }selectDeviceGroupDetailList(params).then(response => {this.deviceList = response.rowsthis.total = this.deviceList.lengththis.ws()if (this.deviceList[0].memberStatus == 3 &&this.deviceList[0].imei == this.userName) {this.isSpeak = true;}if (this.deviceList[0].memberStatus == 2 &&this.deviceList[0].imei == this.userName) {if (sessionStorage.getItem("groupId") == this.groupId) {this.isInOrOutMeeting = truereturn}if (sessionStorage.getItem("groupId")) {} else {this.call()}}});},onClickDialOutside(event) {console.log(event)this.showDial = false},onClickTransDialOutside(event) {this.showTransferDial = false;},handleAgentBarBtnClick(name) {console.log(name + '当前')if (name === 'login') {this.login();} else if (name === 'logout') {this.logout();} else if (name === 'answer') {this.answer();} else if (name === 'hangup') {this.hangup();} else if (name === 'makecall') {this.makeCall('9*' + this.groupId)} else if (name === 'hold') {this.hold();} else if (name === 'unhold') {this.unhold();} else if (name === 'transfer') {this.transfer(this.transNum)}},login() {this.init()Ctibar.register()},logout() {Ctibar.unregister()},makeCall(phone) {if (phone === "" || phone === undefined) {console.error('无效的号码,请重新输入!');return}Ctibar.makecall(phone);},hold() {Ctibar.hold();},unhold() {Ctibar.unhold();},answer() {Ctibar.answer();},hangup() {Ctibar.hangup();},transfer(phone) {console.info("触发转接", phone)Ctibar.transfer(phone);},//外呼拨号盘handleDialBtnClick(val) {this.outNum += val;this.$refs.outNumInput.focus();},//转接拨号盘handleTransDialBtnClick(val) {this.transNum += val;this.$refs.transNumInput.focus();},//参数为时间差秒数,返回这两个时间差并格式化computeTimeDiff(diff) {diff = Math.round(diff / 1000);let hour = Math.floor(diff / 3600).toString().padStart(2, '0');let min = Math.floor((diff - hour * 3600) / 60).toString().padStart(2, '0');let sec = (diff % 60).toString().padStart(2, '0');return hour + ':' + min + ':' + sec;},//重置时间restoreTime(origin) {clearInterval(this.timer);this.timerString = '00:00:00';this.timer = setInterval(() => {this.timerString = this.computeTimeDiff(new Date().getTime() - origin);}, 1000);},//状态变更回调stateEventListener(event, data) {//debug使用console.log('当前event为: ' + event + ', 当前data为: ' + JSON.stringify(data))this.agentStatus = eventlet origin = new Date().getTime();switch (event) {case "CONNECTED":this.agentNo = data.localAgentthis.restoreTime(origin);break;case "DISCONNECTED":this.restoreTime(origin);break;case "UNREGISTERED":this.restoreTime(origin);break;case "OUTGOING_CALL":this.customerNo = data.otherLegNumber;this.restoreTime(origin);break;case "INCOMING_CALL":this.customerNo = data.otherLegNumber;//播放来电振铃音this.playRingMedia();this.restoreTime(origin);break;case "IN_CALL":this.stopPlayRingMedia();this.restoreTime(origin);// this.timer = setInterval(() => {// }, 1000);console.log('当前是否hold', this.isHold)if (this.isHold) {setTimeout(() => {this.handleAgentBarBtnClick('hold')this.isHold = false// 方法区}, 500);}break;case "CALL_END":this.stopPlayRingMedia();//挂机铃声this.playHangupMedia()this.restoreTime(origin);break;default:}},//播放挂机铃声playHangupMedia() {const _this = this;var hangupAudio = document.getElementById("hangupMediaAudioId")if (!hangupAudio) {hangupAudio = document.createElement('audio');hangupAudio.id = 'hangupMediaAudioId';hangupAudio.hidden = true;hangupAudio.src = 'wav/hangup.wav'document.body.appendChild(hangupAudio);}hangupAudio.play();},//播放来电振铃playRingMedia() {const _this = this;_this.stopPlayRingMedia();var ringAudio = document.getElementById("ringMediaAudioId")if (!ringAudio) {ringAudio = document.createElement('audio');ringAudio.id = 'ringMediaAudioId';ringAudio.hidden = true;ringAudio.src = 'wav/ring.wav';ringAudio.loop = 'loop';document.body.appendChild(ringAudio);}ringAudio.play();},//停止播放来电振铃stopPlayRingMedia() {const _this = this;var ringAudio = document.getElementById("ringMediaAudioId");if (ringAudio) {document.body.removeChild(ringAudio);}},//初始化方法init() {this.config.extNo = this.userNameif (sessionStorage.getItem("freeSwitchWs") != null) {this.config.host = sessionStorage.getItem("freeSwitchWs").split(":")[0]this.config.port = sessionStorage.getItem("freeSwitchWs").split(":")[1]}Ctibar.initSDK(this.config)let url = `/ws/` + this.userId + '/group/device/push';this.websocket = new webSocketClass(url)this.websocket.getWebSocketMsg(evt => {// 客户端接收服务端返回的数据var data = JSON.parse(evt.data);console.log("websocket返回的数据123:", data);switch (data.flag) {case "group"://会议状态this.$refs.organizationTree.setGroupList(data, this.groupId)console.log("websocket返回的数据123:", data);breakcase "memberStatus"://成员状态for (const i in this.deviceList) {console.log('123123132', data.member);if (this.deviceList[i].imei == data.member) {this.deviceList[i].devStatus = data.memberStatus;}}breakcase "memberMeetingStatus"://成员会议状态for (const i in this.deviceList) {if (this.deviceList[i].imei == data.member) {this.deviceList[i].memberStatus = data.memberMeetingStatus;}}if (this.groupId == data.groupId && this.userName == data.member && this.createBy == data.member) {if (data.memberMeetingStatus == 3) {this.isSpeak = true;this.unhold()console.log('数据this.isInOrOutMeeting1111', this.isSpeak)} else {this.isSpeak = false;this.hold()}}if (data.memberMeetingStatus == 4) {this.loading.close()}if (!this.isInOrOutMeeting && this.groupId == data.groupId && this.userName == data.member && this.createBy == data.member && data.memberMeetingStatus == 2) {this.hold()if (sessionStorage.getItem("groupId") &&sessionStorage.getItem("groupId") !== this.groupId) {} else {console.log('this.isInOrOutMeeting1111', data.memberMeetingStatus)this.call()}}if (this.createBy == data.member && data.memberMeetingStatus == 1 && this.userName == data.member) {this.handleAgentBarBtnClick('hangup')sessionStorage.setItem("groupId", '')this.isInOrOutMeeting = falsethis.isSpeak = falsethis.isHold = true}break}})},setGroup(group) {this.isGroup = group},call() {this.handleAgentBarBtnClick('makecall')sessionStorage.setItem("groupId", this.groupId)this.isInOrOutMeeting = trueif (this.deviceList[0].imei != this.createBy) {// this.groupDeviceItemClick(this.item)}var that = thiswindow.onbeforeunload = (e) => {console.log('this.isInOrOutMeeting', that.isInOrOutMeeting)if (that.isInOrOutMeeting) {that.handleAgentBarBtnClick('hangup')sessionStorage.setItem("groupId", '')that.websocket.closeSocket()window.onbeforeunload = null}}this.loading.close()this.isInOrOutMeeting = truethis.isHold = true}},beforeDestroy() {  //进行监听销毁console.log('1231232131232132131,', this.isInOrOutMeeting)if (this.isInOrOutMeeting) {this.handleAgentBarBtnClick('hangup')sessionStorage.setItem("groupId", '')this.websocket.closeSocket()window.onbeforeunload = null}},};
</script>

相关文章:

web 语音通话 jssip

先把封装好的地址安上&#xff08;非本人封装&#xff09;&#xff1a;webrtc-webphone: 基于JsSIP开发的webrtc软电话 jssip中文文档&#xff1a;jssip中文开发文档&#xff08;完整版&#xff09; - 简书 jssip使用文档&#xff1a;&#xff08;我没有运行过&#xff0c;但…...

随风摇曳的她——美蕨(matlab实现)

目录 1 随风摇曳的她 2 摇曳带来的哲思 3 Matlab代码实现 1 随风摇曳的她 梦幻的场景、浪漫的气息&#xff0c;带上心爱的人&#xff0c;拥抱在这片花海之下&#xff0c;便有了电影男女主角的氛围感&#xff1b; 就算阅尽了世间风貌&#xff0c;也抵不上和她在一起时锦短情长&a…...

时序数据库的流计算支持

一、时序数据及其特点 时序数据&#xff08;Time Series Data&#xff09;是基于相对稳定频率持续产生的一系列指标监测数据&#xff0c;比如一年内的道琼斯指数、一天内不同时间点的测量气温等。时序数据有以下几个特点&#xff1a; 历史数据的不变性数据的有效性数据的时效…...

springboot启动流程 (3) 自动装配

在SpringBoot中&#xff0c;EnableAutoConfiguration注解用于开启自动装配功能。 本文将详细分析该注解的工作流程。 EnableAutoConfiguration注解 启用SpringBoot自动装配功能&#xff0c;尝试猜测和配置可能需要的组件Bean。 自动装配类通常是根据类路径和定义的Bean来应…...

ansible-roles模块

roles用于层次性&#xff0c;结构化地组织playbook&#xff0c;roles能够根据层次型结构自动装载变量文件&#xff0c;tasks以及handlers等。要使用只要载playbook中使用include指令引入即可。 &#xff08;roles就是通过分别将变量&#xff0c;文件&#xff0c;任务&#xff…...

聊聊我做 NeRF-3D重建性能优化经历

我们新推出大淘宝技术年度特刊《长期主义&#xff0c;往往从一些小事开始——工程师成长总结专题》&#xff0c;专题收录多位工程师真诚的心路历程与经验思考&#xff0c;覆盖终端、服务端、数据算法、技术质量等7大技术领域&#xff0c;欢迎一起沟通交流。 本文为此系列第四篇…...

未磁科技全球首台64通道无液氦心磁图仪及首个培训基地落户北京安贞医院

【全球首台64通道无液氦心磁图仪在北京安贞医院举行开机仪式】 近日&#xff0c;在北京安贞医院举行了未磁科技全球首台64通道无液氦心磁图仪开机仪式&#xff0c;中国医学装备协会赵自林理事长、北京安贞医院纪智礼书记、张宏家院长、宋现涛教授&#xff0c;以及未磁科技蔡宾…...

SpringBoot 如何使用 ApplicationEventPublisher 发布事件

SpringBoot 如何使用 ApplicationEventPublisher 发布事件 在 SpringBoot 应用程序中&#xff0c;我们可以使用 ApplicationEventPublisher 接口来发布事件。事件可以是任何对象&#xff0c;当该对象被发布时&#xff0c;所有监听该事件的监听器都会收到通知。 下面是一个简单…...

【深度学习】2-3 神经网络-输出层设计

前馈神经网络(Feedforward Neural Network)&#xff0c;之前介绍的单层感知机、多层感知机等都属于前馈神经网络&#xff0c;它之所以称为前馈(Feedforward)&#xff0c;或许与其信息往前流有关&#xff1a;数据从输入开始&#xff0c;流过中间计算过程&#xff0c;最后达到输出…...

Python网络爬虫开发:使用PyQt5和WebKit构建可定制的爬虫

部分数据来源:ChatGPT 引言 在网络爬虫开发中,使用Web浏览器模拟用户行为是非常重要的。而在这个过程中,基于 WebKit 的框架可以提供比其他技术更紧密的浏览器集成,以及更高效、更多样化的页面交互方式。 在本文中,我们将通过一个使用基于 WebKit 的爬虫示例,并与类似…...

Laya3.0游戏框架搭建流程(随时更新)

近两年AI绘图技术有了长足发展&#xff0c;准备把以前玩过的游戏类型重制下&#xff0c;也算是圆了一个情怀梦。 鉴于unity商用水印和启动时间的原因&#xff0c;我决定使用Laya来开发。目前laya已经更新到了3.0以上版本&#xff0c;就用目前比较新的版本。 之后关于开发中遇到…...

.net 软件开发模式——三层架构

三层架构是一种常用的软件开发架构模式&#xff0c;它将应用程序分为三个层次&#xff1a;表示层、业务逻辑层和数据访问层。每一层都有明确的职责和功能&#xff0c;分别负责用户交互、业务处理和数据存储等任务。这种架构模式的优点包括易于维护和扩展、更好的组织结构和代码…...

SpringBoot如何优雅的实现重试功能

文章目录 使用背景spring-retry介绍快速使用加入依赖开启Retry使用参数 使用背景 在有些特定场景&#xff0c;如和第三方对接。 我们调用接口时需要支持重试功能&#xff0c;第一次调用没成功&#xff0c;我们需要等待x秒后再次调用。 通常会设置重试次数&#xff0c;避免业务…...

【CEEMDAN-VMD-GRU】完备集合经验模态分解-变分模态分解-门控循环单元预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

OpenText Exceed TurboX(ETX)—— 适用于 UNIX、Linux 和 Windows 的远程桌面解决方案

由于新技术的采用&#xff0c;以及商业全球化和全球协作的现实&#xff0c;几乎所有企业&#xff08;无论其规模和所处行业&#xff09;的员工的工作方式、时间和地点都发生了重大变化。业务领导者正在推动其 IT 部门提出解决方案&#xff0c;以帮助其远程员工提高工作效率&…...

【人工智能】— 逻辑回归分类、对数几率、决策边界、似然估计、梯度下降

【人工智能】— 逻辑回归分类、对数几率、决策边界、似然估计、梯度下降 逻辑回归分类Logistic Regression ClassificationLogistic Regression: Log OddsLogistic Regression: Decision BoundaryLikelihood under the Logistic ModelTraining the Logistic ModelGradient Desc…...

k8s pod “cpu和内存“ 资源限制

转载用于收藏学习&#xff1a;原文 文章目录 Pod资源限制requests&#xff1a;limits&#xff1a;docker run命令和 CPU 限制相关的所有选项如下&#xff1a; Pod资源限制 为了保证充分利用集群资源&#xff0c;且确保重要容器在运行周期内能够分配到足够的资源稳定运行&#x…...

datagrip 连接 phoenix

jar替换完后尽量重启datagrip. 然后重新连接即可. 不重启貌似报错... 效果:...

黑客入侵的常法

1.无论什么站&#xff0c;无论什么语言&#xff0c;我要渗透&#xff0c;第一件事就是扫目录&#xff0c;最好一下扫出个上传点&#xff0c;直接上传 shell &#xff0c;诸位不要笑&#xff0c;有时候你花很久搞一个站&#xff0c;最后发现有个现成的上传点&#xff0c;而且很容…...

VB报警管理系统设计(源代码+系统)

可定时显示报警系统是一个能够定时并及时报警,提醒人们安全有效地按计划完成任务的系统。本论文从软件工程的角度,对可定时显示报警系统做了全面的需求分析,简要说明了该系统的构思、特点及开发环境;阐述了系统的主要功能,论述了它的设计与实现,并且叙述了系统的测试与评…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...

React核心概念:State是什么?如何用useState管理组件自己的数据?

系列回顾&#xff1a; 在上一篇《React入门第一步》中&#xff0c;我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目&#xff0c;并修改了App.jsx组件&#xff0c;让页面显示出我们想要的文字。但是&#xff0c;那个页面是“死”的&#xff0c;它只是静态…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录 &#x1f50d; 若用递归计算每一项&#xff0c;会发生什么&#xff1f; Horners Rule&#xff08;霍纳法则&#xff09; 第一步&#xff1a;我们从最原始的泰勒公式出发 第二步&#xff1a;从形式上重新观察展开式 &#x1f31f; 第三步&#xff1a;引出霍纳法则&…...