Spring Boot集成websocket实现webrtc功能
1.什么是webrtc?
WebRTC 是 Web 实时通信(Real-Time Communication)的缩写,它既是 API 也是协议。WebRTC 协议是两个 WebRTC Agent 协商双向安全实时通信的一组规则。开发人员可以通过 WebRTC API 使用 WebRTC 协议。目前 WebRTC API 仅有 JavaScript 版本。 可以用 HTTP 和 Fetch API 之间的关系作为类比。WebRTC 协议就是 HTTP,而 WebRTC API 就是 Fetch API。 除了 JavaScript 语言,WebRTC 协议也可以在其他 API 和语言中使用。你还可以找到 WebRTC 的服务器和特定领域的工具。所有这些实现都使用 WebRTC 协议,以便它们可以彼此交互。 WebRTC 协议由 IETF 工作组在rtcweb中维护。WebRTC API 的 W3C 文档在webrtc。
WebSocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
webrtc架构
2.代码工程
实验目标
实现视频通话功能
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>WebRTC</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies>
</project>
controller
package com.et.webrtc.controller;import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;import java.util.HashMap;
import java.util.Map;@RestController
public class HelloWorldController {@RequestMapping("/hello")public Map<String, Object> showHelloWorld(){Map<String, Object> map = new HashMap<>();map.put("msg", "HelloWorld");return map;}/*** WebRTC + WebSocket*/@RequestMapping("webrtc/{username}.html")public ModelAndView socketChartPage(@PathVariable String username) {ModelAndView modelAndView = new ModelAndView();modelAndView.setViewName("webrtc.html");modelAndView.addObject("username",username);return modelAndView;}
}
config
package com.et.webrtc.config;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** WebRTC + WebSocket*/
@Slf4j
@Component
@ServerEndpoint(value = "/webrtc/{username}")
public class WebRtcWSServer {/*** 连接集合*/private static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("username") String username, @PathParam("publicKey") String publicKey) {sessionMap.put(username, session);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session) {for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {if (entry.getValue() == session) {sessionMap.remove(entry.getKey());break;}}}/*** 发生错误时调用*/@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic void onMessage(String message, Session session) {try{//jacksonObjectMapper mapper = new ObjectMapper();mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);//JSON字符串转 HashMapHashMap hashMap = mapper.readValue(message, HashMap.class);//消息类型String type = (String) hashMap.get("type");//to userString toUser = (String) hashMap.get("toUser");Session toUserSession = sessionMap.get(toUser);String fromUser = (String) hashMap.get("fromUser");//msgString msg = (String) hashMap.get("msg");//sdpString sdp = (String) hashMap.get("sdp");//iceMap iceCandidate = (Map) hashMap.get("iceCandidate");HashMap<String, Object> map = new HashMap<>();map.put("type",type);//呼叫的用户不在线if(toUserSession == null){toUserSession = session;map.put("type","call_back");map.put("fromUser","系统消息");map.put("msg","Sorry,呼叫的用户不在线!");send(toUserSession,mapper.writeValueAsString(map));return;}//对方挂断if ("hangup".equals(type)) {map.put("fromUser",fromUser);map.put("msg","对方挂断!");}//视频通话请求if ("call_start".equals(type)) {map.put("fromUser",fromUser);map.put("msg","1");}//视频通话请求回应if ("call_back".equals(type)) {map.put("fromUser",toUser);map.put("msg",msg);}//offerif ("offer".equals(type)) {map.put("fromUser",toUser);map.put("sdp",sdp);}//answerif ("answer".equals(type)) {map.put("fromUser",toUser);map.put("sdp",sdp);}//iceif ("_ice".equals(type)) {map.put("fromUser",toUser);map.put("iceCandidate",iceCandidate);}send(toUserSession,mapper.writeValueAsString(map));}catch(Exception e){e.printStackTrace();}}/*** 封装一个send方法,发送消息到前端*/private void send(Session session, String message) {try {System.out.println(message);session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}
}
package com.et.webrtc.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
@EnableWebSocket
public class WebSocketConfiguration {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
前端页面
<!DOCTYPE>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>WebRTC + WebSocket</title><meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"><style>html,body{margin: 0;padding: 0;}#main{position: absolute;width: 370px;height: 550px;}#localVideo{position: absolute;background: #757474;top: 10px;right: 10px;width: 100px;height: 150px;z-index: 2;}#remoteVideo{position: absolute;top: 0px;left: 0px;width: 100%;height: 100%;background: #222;}#buttons{z-index: 3;bottom: 20px;left: 90px;position: absolute;}#toUser{border: 1px solid #ccc;padding: 7px 0px;border-radius: 5px;padding-left: 5px;margin-bottom: 5px;}#toUser:focus{border-color: #66afe9;outline: 0;-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}#call{width: 70px;height: 35px;background-color: #00BB00;border: none;margin-right: 25px;color: white;border-radius: 5px;}#hangup{width:70px;height:35px;background-color:#FF5151;border:none;color:white;border-radius: 5px;}</style>
</head>
<body>
<div id="main"><video id="remoteVideo" playsinline autoplay></video><video id="localVideo" playsinline autoplay muted></video><div id="buttons"><input id="toUser" placeholder="输入在线好友账号"/><br/><button id="call">视频通话</button><button id="hangup">挂断</button></div>
</div>
</body>
<!-- 可引可不引 -->
<!--<script th:src="@{/js/adapter-2021.js}"></script>-->
<script type="text/javascript" th:inline="javascript">let username = /*[[${username}]]*/'';let localVideo = document.getElementById('localVideo');let remoteVideo = document.getElementById('remoteVideo');let websocket = null;let peer = null;WebSocketInit();ButtonFunInit();/* WebSocket */function WebSocketInit(){//判断当前浏览器是否支持WebSocketif ('WebSocket' in window) {websocket = new WebSocket("wss://192.168.0.104/webrtc/"+username);} else {alert("当前浏览器不支持WebSocket!");}//连接发生错误的回调方法websocket.onerror = function (e) {alert("WebSocket连接发生错误!");};//连接关闭的回调方法websocket.onclose = function () {console.error("WebSocket连接关闭");};//连接成功建立的回调方法websocket.onopen = function () {console.log("WebSocket连接成功");};//接收到消息的回调方法websocket.onmessage = async function (event) {let { type, fromUser, msg, sdp, iceCandidate } = JSON.parse(event.data.replace(/\n/g,"\\n").replace(/\r/g,"\\r"));console.log(type);if (type === 'hangup') {console.log(msg);document.getElementById('hangup').click();return;}if (type === 'call_start') {let msg = "0"if(confirm(fromUser + "发起视频通话,确定接听吗")==true){document.getElementById('toUser').value = fromUser;WebRTCInit();msg = "1"}websocket.send(JSON.stringify({type:"call_back",toUser:fromUser,fromUser:username,msg:msg}));return;}if (type === 'call_back') {if(msg === "1"){console.log(document.getElementById('toUser').value + "同意视频通话");//创建本地视频并发送offerlet stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })localVideo.srcObject = stream;stream.getTracks().forEach(track => {peer.addTrack(track, stream);});let offer = await peer.createOffer();await peer.setLocalDescription(offer);let newOffer = offer.toJSON();newOffer["fromUser"] = username;newOffer["toUser"] = document.getElementById('toUser').value;websocket.send(JSON.stringify(newOffer));}else if(msg === "0"){alert(document.getElementById('toUser').value + "拒绝视频通话");document.getElementById('hangup').click();}else{alert(msg);document.getElementById('hangup').click();}return;}if (type === 'offer') {let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });localVideo.srcObject = stream;stream.getTracks().forEach(track => {peer.addTrack(track, stream);});await peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));let answer = await peer.createAnswer();let newAnswer = answer.toJSON();newAnswer["fromUser"] = username;newAnswer["toUser"] = document.getElementById('toUser').value;websocket.send(JSON.stringify(newAnswer));await peer.setLocalDescription(answer);return;}if (type === 'answer') {peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));return;}if (type === '_ice') {peer.addIceCandidate(iceCandidate);return;}}}/* WebRTC */function WebRTCInit(){peer = new RTCPeerConnection();//icepeer.onicecandidate = function (e) {if (e.candidate) {websocket.send(JSON.stringify({type: '_ice',toUser:document.getElementById('toUser').value,fromUser:username,iceCandidate: e.candidate}));}};//trackpeer.ontrack = function (e) {if (e && e.streams) {remoteVideo.srcObject = e.streams[0];}};}/* 按钮事件 */function ButtonFunInit(){//视频通话document.getElementById('call').onclick = function (e){document.getElementById('toUser').style.visibility = 'hidden';let toUser = document.getElementById('toUser').value;if(!toUser){alert("请先指定好友账号,再发起视频通话!");return;}if(peer == null){WebRTCInit();}websocket.send(JSON.stringify({type:"call_start",fromUser:username,toUser:toUser,}));}//挂断document.getElementById('hangup').onclick = function (e){document.getElementById('toUser').style.visibility = 'unset';if(localVideo.srcObject){const videoTracks = localVideo.srcObject.getVideoTracks();videoTracks.forEach(videoTrack => {videoTrack.stop();localVideo.srcObject.removeTrack(videoTrack);});}if(remoteVideo.srcObject){const videoTracks = remoteVideo.srcObject.getVideoTracks();videoTracks.forEach(videoTrack => {videoTrack.stop();remoteVideo.srcObject.removeTrack(videoTrack);});//挂断同时,通知对方websocket.send(JSON.stringify({type:"hangup",fromUser:username,toUser:document.getElementById('toUser').value,}));}if(peer){peer.ontrack = null;peer.onremovetrack = null;peer.onremovestream = null;peer.onicecandidate = null;peer.oniceconnectionstatechange = null;peer.onsignalingstatechange = null;peer.onicegatheringstatechange = null;peer.onnegotiationneeded = null;peer.close();peer = null;}localVideo.srcObject = null;remoteVideo.srcObject = null;}}
</script>
</html>
DemoAppliciation.java
package com.et.webrtc;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
- GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.
3.测试
启动Spring Boot应用
测试视频通话
前置条件:必须是https协议,不然无法打开视频和语音权限
- 笔记本:https://192.168.0.104/webrtc/2.html
- 手机:https://192.168.0.104/webrtc/1.html
输入对方id,进行视屏通话
4.引用
- 是什么,为什么,如何使用 | 给好奇者的WebRTC
- https://www.cnblogs.com/huanzi-qch/p/15716286.html
- Spring Boot集成websocket实现webrtc功能 | Harries Blog™
相关文章:

Spring Boot集成websocket实现webrtc功能
1.什么是webrtc? WebRTC 是 Web 实时通信(Real-Time Communication)的缩写,它既是 API 也是协议。WebRTC 协议是两个 WebRTC Agent 协商双向安全实时通信的一组规则。开发人员可以通过 WebRTC API 使用 WebRTC 协议。目前 WebRTC…...

StableSwarmUI 安装教程(详细)
文章目录 背景特点安装 背景 StableSwarmUI是StabilityAI官方开源的一个文生图工作流UI,目前处于beta阶段,但主流程是可以跑通的。该UI支持接入ComfyUI、Stable Diffusion-WebUI。其工作原理就是使用ComfyUI、Stable Diffusion-WebUI或者StabilityAI官方…...
利用Unity XR交互工具包实现简易VR菜单控制——6.18山大软院项目实训
初始设置 在Unity项目中,首先需要确保安装了XR插件和XR交互工具包。这些工具包提供了对VR硬件的支持,以及一系列用于快速开发VR交互的组件和预设。 脚本概览 本示例中的menuController脚本附加在一个Unity GameObject上,这个脚本负责监听用…...

区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测
区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CNN-ABKDE卷积神经网络自适应…...

【机器学习】第6章 支持向量机(SVM)
一、概念 1.支持向量机(support vector machine,SVM): (1)基于统计学理论的监督学习方法,但不属于生成式模型,而是判别式模型。 (2)支持向量机在各个领域内的…...
hive笔记
文章目录 1. 如何增加列2. 如何查看表的具体列的数据类型3. 如何drop一个表 1. 如何增加列 alter table your_table_name add columns (your_column_name varchar(255));2. 如何查看表的具体列的数据类型 DESCRIBE your_table_name3. 如何drop一个表 drop table your_table_…...
kali - 配置静态网络地址 + ssh 远程连接
文章目录 观前提示:本环境在 root 用户下kali 配置静态网络地址打开网络配置文件 kali 配置 ssh 远程连接 观前提示:本环境在 root 用户下 kali 配置静态网络地址 打开网络配置文件 vim /etc/network/interfaces出现一下内容 # This file describes …...

Redis常见数据类型及其常用命令详解
文章目录 一、Redis概述二、Redis常用命令1.通用命令1.1 KEYS:查看符合模板的所有 key1.2 DEL:删除一个指定的 key1.3 EXISTS:判断 key 是否存在1.4 EXPIRE:给一个 key 设置有效期,有效期到期时该 key 会被自动删除1.5…...

JMU 数科 数据库与数据仓库期末总结(4)实验设计题
E-R图 实体-关系图 E-R图的组成要素主要包括: 实体(Entity):实体代表现实世界中可相互区别的对象或事物,如顾客、订单、产品等。在图中,实体通常用矩形表示,并在矩形内标注实体的名称。 属性…...
Go版RuoYi
RuoYi-Go(DDD) 1. 关于我(在找远程工作,给机会的老板可以联系) 个人介绍 2. 后端 后端是用Go写的RuoYi权限管理系统 (功能正在持续实现) 用DDD领域驱动设计(六边形架构)做实践 后端 GitHub地址 后端 Gitee地址 3. 前端 本项目没有自研前端,前端代…...

八股系列 Flink
Flink 和 SparkStreaming的区别 设计理念方面 SparkStreaming:使用微批次来模拟流计算,数据已时间为单位分为一个个批次,通过RDD进行分布式计算 Flink:基于事件驱动,是面向流的处理框架,是真正的流式计算…...

HTTP/2 协议学习
HTTP/2 协议介绍 HTTP/2 (原名HTTP/2.0)即超文本传输协议 2.0,是下一代HTTP协议。是由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis (httpbis)工作小组进行开发。是自1999年http1.1发布后的首个更新。…...
“先票后款”条款的效力认定
当事人明确约定一方未开具发票,另一方有权拒绝支付工程款的,该约定对当事人具有约束力。收款方请求付款方支付工程款时,付款方可以行使先履行抗辩权,但为减少当事人诉累,收款方在诉讼中明确表示愿意开具发票࿰…...

CSDN 自动上传图片并优化Markdown的图片显示
文章目录 完整代码一、上传资源二、替换 MD 中的引用文件为在线链接参考 完整代码 完整代码由两个文件组成,upload.py 和 main.py,放在同一目录下运行 main.py 就好! # upload.py import requests class UploadPic: def __init__(self, c…...

常见日志库NLog、log4net、Serilog和Microsoft.Extensions.Logging介绍和区别
在C#中,日志库的选择主要取决于项目的具体需求,包括性能、易用性、可扩展性等因素。以下是关于NLog、log4net、Serilog和Microsoft.Extensions.Logging的基本介绍和使用示例。 包含如何配置输出日志到当前目录下的log.txt文件及控制台的示例,…...

【PX4-AutoPilot教程-TIPS】离线安装Flight Review PX4日志分析工具
离线安装Flight Review PX4日志分析工具 安装方法 安装方法 使用Flight Review在线分析日志,有时会因为网络原因无法使用。 使用离线安装的方式使用Flight Review,可以在无需网络的情况下使用Flight Review网页。 安装环境依赖。 sudo apt-get insta…...
探究Spring Boot自动配置的底层原理
在当今的软件开发领域,Spring Boot已经成为了构建Java应用程序的首选框架之一。它以其简单易用的特性和强大的功能而闻名,其中最引人注目的特性之一就是自动配置(Auto-Configuration)。Spring Boot的自动配置能够极大地简化开发人…...

Fedora40的#!bash #!/bin/bash #!/bin/env bash #!/usr/bin/bash #!/usr/bin/env bash
bash脚本开头可写成 #!/bin/bash , #!/bin/env bash , #!/usr/bin/bash , #!/usr/bin/env bash #!/bin/bash , #!/usr/bin/bash#!/bin/env bash , #!/usr/bin/env bash Fedora40Workstation版的 /bin 是 /usr/bin 的软链接, /sbin 是 /usr/sbin 的软链接, rootfedora:~# ll …...

重生之 SpringBoot3 入门保姆级学习(19、场景整合 CentOS7 Docker 的安装)
重生之 SpringBoot3 入门保姆级学习(19、场景整合 CentOS7 Docker 的安装) 6、场景整合6.1 Docker 6、场景整合 6.1 Docker 官网 https://docs.docker.com/查看自己的 CentOS配置 cat /etc/os-releaseStep 1: 安装必要的一些系统工具 sudo yum insta…...

cve_2014_3120-Elasticsearch-rce-vulfocus靶场
1.背景 来源:ElasticSearch(CVE-2014-3120)命令执行漏洞复现_mvel 漏洞-CSDN博客 参考:https://www.cnblogs.com/huangxiaosan/p/14398307.html 老版本ElasticSearch支持传入动态脚本(MVEL)来执行一些复…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...