Spring中WebSocket的使用

news2024/11/11 10:51:08

文章目录

  • 前言
  • 什么是 WebSocket
  • WebSocket 协议和 HTTP 协议的区别
  • WebSocket 原理解析
    • WebSocket 报文格式
  • Spring 中 WebSocket 的使用
    • 前后端发送的数据的数据类型是对象该如何做
    • 使用websocket协议如何获取到HTTP协议中的HttpSession
  • WebSocket使用的完整代码

前言

我们在使用 Spring 做网页开发的时候,我们后端服务器的主要作用就是处理前端、客户端发送来的请求,也就是说我们的服务器是被动接受请求的一方,而在某些时候,需要我们的服务器主动向客户端发送网络数据包,例如购物软件的降价通知,聊天软件别人发送消息时候的通知,这些都需要服务器主动向客户端发送网络数据包。

浏览器像服务器发送网络请求,应用层使用的协议往往是 HTTP 或者 HTTPS 协议,而 HTTP 协议在发展的时候却没有涉及到服务器主动向客户端发送网络数据包的功能,因为 HTTP 的发展初衷就是为了人们通过网络能够看报纸、新闻的,也就没想到服务器能够通过 HTTP 协议主动向客户端发送网络数据包,所以通过 HTTP 协议是无法实现服务器主动向客户端发送网络数据包的功能的。

那么如何实现服务器主动向客户端发送请求的功能呢?这就需要使用到另外一种应用层协议——WebSocket 了。

什么是 WebSocket

来看看百度的解释:

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许服务端主动向客户端推送数据,同时也支持客户端向服务端发送数据,实现了真正的双向通信。WebSocket协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。WebSocket API也被W3C定为标准,使得浏览器和服务器之间的数据交换变得更加简单和高效。

传统的应用层使用其他协议的 web 程序,都是属于”一问一答“形式,客户端给服务器发送 HTTP 请求之后,服务器给客户端返回一个 HTTP 响应,在这种情况下,服务器是属于被动的一方,如果客户端不主动发起请求,服务器无法主动给客户端响应。而 WebSocket 则是更接近于 TCP 这种级别的通信方式,一旦建立连接,客户端和服务端都可以主动向对方发送数据。

WebSocket 协议和 HTTP 协议的区别

  1. 协议性质
  • WebSocket:是一种在单个TCP连接上进行全双工通信的协议。它允许服务端主动向客户端推送数据,使得客户端和服务器之间的数据交换变得更加简单和高效。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。(来源:百度百科)
  • HTTP:全称超文本传输协议(HyperText Transfer Protocol),是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP是一个基于TCP/IP通信协议来传递数据的协议,客户端发送请求,服务器返回响应。HTTP是无连接的,即每次连接只处理一个请求,服务器处理完客户请求,并收到客户的应答后,就断开连接。(来源:知乎专栏)
  1. 通信方式
  • WebSocket:支持双向通信,即客户端和服务器可以同时向对方发送数据。这种全双工的通信方式使得WebSocket特别适用于需要实时数据交换的场景,如在线聊天、实时数据更新等。
  • HTTP:是单向的、请求-响应模式的协议。客户端发起请求,服务器返回响应,然后连接断开。如果需要再次交换数据,必须重新建立连接。
  1. 连接状态
  • WebSocket:是有状态的协议。一旦建立了WebSocket连接,客户端和服务器之间的通信就可以持续进行,直到连接被关闭。这种持久连接减少了因频繁建立连接而产生的开销。
  • HTTP:是无状态的协议。HTTP协议对事务处理没有记忆能力,即服务器不会记住任何关于客户端请求的信息。如果需要处理多个请求之间的关联,必须在每个请求中携带必要的状态信息。
  1. 数据传输效率
  • WebSocket:由于使用了长连接和较少的控制开销(如头部信息较小),WebSocket在数据传输效率上优于HTTP。特别是在需要频繁交换小量数据的场景中,WebSocket能够显著减少网络带宽的消耗。
  • HTTP:每次请求都需要携带完整的头部信息,这可能导致在传输小量数据时头部信息的开销占比较大。此外,HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,从而浪费了网络带宽。

WebSocket 原理解析

WebSocket 本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加的头信息,通过这些附加的头信息来完成握手过程。

那么这些附加的头信息是哪些呢?

对于浏览器发送的请求数据包的头信息中,附加的信息有:

  • Connection: upgrade。表示我需要升级协议
  • Sec-WebSocket-Accept: xxxxxx。服务端与该客户端通讯的钥匙
  • Sec-WebSocket-Version: 13。升级的协议的版本
  • Upgrade: websocket。升级的协议格式

在这里插入图片描述

WebSocket 报文格式

WebSocket 协议的相关信息大家可以去官方文档中查看:https://www.rfc-editor.org/rfc/rfc6455

在这里插入图片描述

  • FIN 表示是否要关闭 WebSocket,为 1 表示断开 WebSocket 连接,这里的 FIN 和 TCP 报文中的 FIN 不是一个概念。
  • RSV1/RSV2/RSV3:保留位,现在先不用,但是不保证后面可能会用到,值一般为 0。
  • opcode:操作代码,决定了如何理解后面的数据载荷。
    • %x0:表示这是一个延续帧。当 opcode 为0,表示本次数据传输采用了数据分片,当前收到的帧为其中一个分片
    • %x1:表示这是文本帧,也就是载荷中的数据是文本类型
    • %x2:表示这是二进制帧,也就是载荷中的数据是二进制类型
    • %x3-7:保留,暂未使用
    • %x8:表示连接断开
    • %x9:表示 ping 帧
    • %xA:表示 pong 帧
    • %xB-F:保留,暂未使用
  • mask:表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作
  • Payload length:数据载荷的长度,单位是字节,能表示的范围是0-127
  • Extended Payload length:扩展的载荷长度,127字节的大小肯定是不够用的,所以就出现了扩展载荷,当Payload length的值0-125的时候表示扩展载荷的长度为0,Payload length的值为126时,表示扩展载荷的长度为16位,值为127时,表示扩展载荷的长度为64位
  • Masking-key:0或者4字节(32位)所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,mask 为 1,且携带了4字节的Masking-key。如果 mask 为0.则没有 Masking-key
  • Payload Data:报文携带的载荷数据

websocket 载荷的长度可以是 6比特位,单位是字节能表示的大小,也可以是 16比特位、或者64比特位能表示的范围大小。

使用掩码算法的目的主要是从安全角度考虑,避免一些缓冲区溢出攻击。

我们可以直接使用 tomcat 提供的 WebSocket 的原生 API,也可以使用 Spring 内置的 WebSocket,其实这两者区别不大。

Spring 中 WebSocket 的使用

首先我们在创建 Spring 项目的时候需要添加进去 WebSocket 依赖。

在这里插入图片描述
在这里插入图片描述
也可以自己手动添加 websockt 依赖。

添加完依赖之后,我们创建一个类,继承TextWebSocketHandler 类,如果你的 websocket 中的载荷的数据类型是二进制类型的话,就继承 BinaryWebSocketHandler 类:

package com.example.websocket.component;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class TestWebSocketComponent extends TextWebSocketHandler {
}

TextWebSocketHandler 父类中的方法有很多:

在这里插入图片描述

我们需要重写的方法主要就是:afterConnectionEstablishedhandleTextMessagehandleTransportErrorafterConnectionClosed方法。

  • afterConnectionEstablished: 方法表示客户端和服务端建立 websocket 连接之后执行的方法
  • handleTextMessage:方法表示对方传来 websocket 数据帧的时候执行的方法
  • handleTransportError:方法表示 websocket 连接出现异常的时候执行的方法
  • afterConnectionClosed: 方法表示关闭 websocket 连接后执行的方法

重写完成 TextWebSocketHandler 类之后,就需要将这个实例给注册到 spring 中,进行路由的配置:

创建一个类,实现 WebSocketConfigurer 接口,并且实现接口中的 registerWebSocketHandlers 方法:

package com.example.websocket.config;


import com.example.websocket.component.TestWebSocketComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    private TestWebSocketComponent testWebSocketComponent;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(testWebSocketComponent,"/test");
    }
}

@EnableWebSocket 注解用于开启Spring应用程序对WebSocket协议的支持。

registry.addHandler() 方法中的参数分别上我们上面继承并重写了 TextWebSocketHandler 类中的方法的实例,而后面的字符串则是路由配置,当客户端发送的请求的路由定位到这个字符串的时候,就会执行 TextWebSocketHandler 中的对应方法。

当配置完成服务端的代码之后,我们来实现一个简单的页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>websocket测试</title>
</head>
<body>
    <input type="text" id="message">
    <button id="sendBtn">发送</button>
</body>
</html>

在这里插入图片描述
然后编写 js 这边的 websocket 代码:

<script>
    // 创建一个 websocket 实例
    // ws 是 websocket 的缩写,然后后面就是我们的服务器的ip地址以及前面服务端配置的路由地址
    let websocket = new WebSocket("ws://127.0.0.1/test");

    // 为 websocket 注册一些回调函数
    websocket.onopen = function() {
        // websocket 连接建立完成之后,自动执行到
        console.log('websocket 连接成功');
    }

    websocket.onclose = function() {
        // websocket 连接断开后,自动执行到
        console.log('websocket 连接断开');
    }

    websocket.onerror = function() {
        // websocket 连接异常时,自动执行到
        console.log('websocket 连接异常');
    }

    websocket.onmessage = function(e) {
        // 收到对端消息时,自动执行到
        console.log('websocket 收到消息' + e.data);
    }
</script>

这是 websocket 相关的 js 代码完成了,让后我们为在这个 button 创建一个事件:

let sendBtn = document.querySelector('#sendBtn');
sendBtn.onclick = function() {
    let input = document.querySelector('#message');
    websocket.send(input.value)
}

js 通过 WenSocket 的实例中的 send 方法向对端发送消息,而 spring 则通过类 WebSocketSession 实例中的 sendMessage 方法来向对端发送消息。

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    log.info("websocket 接收到消息:" + message.toString());
    //sendMessage方法中的参数是类WebSocketMessage及其子类
    session.sendMessage(message);
}

前后端发送的数据的数据类型是对象该如何做

在网络传输中,不存在什么对象这样的概念,如果传输的数据类型是对象的话,首先需要将对象转换为 json 字符串,然后再传输。

前端发送的消息的数据类型是对象的话,就需要将对象转换为 JSON 字符串,而我们前面使用 Ajax 的时候是因为 Ajax 帮助我们完成了 JSON 的转换,所以才不需要手动转换:

let req = {
    type: 'message',
    data: input.value
}
websocket.send(JSON.stringify(req));

然后后端通过 User user = objectMapper.readValue(message.asBytes(),User.class); 来将 json 字符串转换为 Java 对象。

如果后端发送的消息的数据类型是 Java 对象的话,就需要将 Java 对象转换为 json 字符串然后再发送:

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    log.info("websocket 接收到消息:" + message.toString());
    //向对端发送消息
    User user = new User();
    user.setUserId(1);
    user.setUserName("zhangsan");
    String respJson = objectMapper.writeValueAsString(user);
    session.sendMessage(new TextMessage(respJson));
}

然后前端通过 e = JSON.parse(e) 来将 json 字符串转换为对象。

在这里插入图片描述
在这里插入图片描述

使用websocket协议如何获取到HTTP协议中的HttpSession

在很多时候,当我们登录的时候,如果登录成功,往往会将用户信息存放在 session 会话中,而后面升级了 websocket 协议之后,如果我们需要使用到 session 中的信息该怎么办呢?这里 websocket 的开发者也想到了这里。当我们在注册 TextWebSocketHandler 的时候,再注册一个 HttpSession 拦截器就可以了,这样就可以把用户给 HttpSession 中添加的 Attributes 键值对往我们的 WebSocketSession 中也添加一份。

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(testWebSocketComponent,"/test")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
}

在 spring 中,通过 WebSocketSession 的实例中的 getAttributes() 方法得到一个类似哈希表的结构,里面存放 HttpSession 中的设置的所有属性的键值对,然后再从这个哈希表结构中通过 get(key) 方法获取指定 key 的value。

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    log.info("websocket 接收到消息:" + message.toString());
    session.sendMessage(message);
    session.getAttributes().get("user");
}

WebSocket使用的完整代码

WebSocketComponent.java 中的代码

package com.example.websocket.component;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Slf4j
@Component
public class TestWebSocketComponent extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("websocket 连接成功");
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("websocket 接收到消息:" + message.toString());
        //向对端发送消息
        session.sendMessage(message);
        //获取HttpSession中的属性
        session.getAttributes().get("user");
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.info("websocket 连接异常:" + exception.toString());
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("websocket 断开连接");
    }
}

WebSocketConfig.java 中的代码:

package com.example.websocket.config;

import com.example.websocket.component.TestWebSocketComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    private TestWebSocketComponent testWebSocketComponent;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(testWebSocketComponent,"/test")
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
}

js 中的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>websocket测试</title>
</head>
<body>
    <input type="text" id="message">
    <button id="sendBtn">发送</button>

    <script>
        // 创建一个 websocket 实例
        // ws 是 websocket 的缩写,然后后面就是我们的服务器的ip地址以及前面服务端配置的路由地址
        let websocket = new WebSocket("ws://127.0.0.1:8080/test");

        // 为 websocket 注册一些回调函数
        websocket.onopen = function() {
            // websocket 连接建立完成之后,自动执行到
            console.log('websocket 连接成功');
        }

        websocket.onclose = function() {
            // websocket 连接断开后,自动执行到
            console.log('websocket 连接断开');
        }

        websocket.onerror = function() {
            // websocket 连接异常时,自动执行到
            console.log('websocket 连接异常');
        }

        websocket.onmessage = function(e) {
            // 收到对端消息时,自动执行到
            console.log('websocket 收到消息' + e.data);
        }

        let sendBtn = document.querySelector('#sendBtn');
        sendBtn.onclick = function() {
            let input = document.querySelector('#message');
            websocket.send(input.value)
        }
    </script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2033051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker基本管理和应用

一、docker是什么&#xff1a; 1.docker是什么&#xff1a;一个开源的应用容器引擎&#xff0c;基于go语言开发的&#xff0c;docker运行在linux的容器化工具&#xff0c;可以理解为轻量级的一个虚拟机。可以在任何主机上轻松创建的一个轻量级、可移植的自给自足的容器&#x…

【Tor】使用Debian系统搭建obfs4 Bridge网桥

你好 我是无聊的木子。 目录 前言 写作の原因 网桥是个啥&#xff1f; 正文 - 到底咋搭建捏 搞台机子先 比较简便の方法 - 买台云服务器 首月五折 一元试用 远程连接服务器 更加复杂の办法 - 自己拿物理机做网桥 开始搭建网桥 先安装Tor 然后配置网桥 最后组合网桥…

【阿旭机器学习实战】【38】支持向量机SVM实现手写数字识别,模型训练、评估,以及参数调优全流程

《------往期经典推荐------》 一、【100个深度学习实战项目】【链接】&#xff0c;持续更新~~ 二、机器学习实战专栏【链接】&#xff0c;已更新31期&#xff0c;欢迎关注&#xff0c;持续更新中~~ 三、深度学习【Pytorch】专栏【链接】 四、【Stable Diffusion绘画系列】专…

React之简易笔记本

此文使用React实现简易笔记本&#xff0c;包括环境配置&#xff0c;前台界面和后台应用等内容。其中后台应用主要功能是数据库操作&#xff0c;前台应用的主要功能是显示&#xff0c;增加&#xff0c;删除&#xff0c;更新数据 &#xff0c;效果如下所示&#xff1a; 一、数据…

Android Framework之Pkms详解

PKMS是Android系统中负责安装包管理的服务&#xff0c;它的主要职责如下&#xff1a; 管理系统安装的所有应用程序&#xff0c;包括升级、安装、卸载 根据Intent匹配相应的Activity、Service、Provider和BroadcastReceiver等&#xff0c;并提供相关信息 解析应用权限&#xff…

深入探讨进程间通信的重要性:理解不同的通信机制(下)

前言 在上一篇文章中&#xff0c;我们探讨了进程间通信的三种常见机制&#xff1a;管道、消息队列和共享内存。我们了解到&#xff0c;这些机制各有其特点和适用场景&#xff0c;可以根据实际需求选择合适的机制进行进程间通信。然而&#xff0c;进程间通信并不仅限于这三种方…

Zookeeper学习、Tomcat

怎样使用Zookeeper实现服务发现&#xff1f; 典型回答 服务发现是ZK的重要用途之一&#xff0c;当我们想要基于zk实现服务发现时&#xff0c;一般可以参考以下步骤&#xff1a;1. 向Zookeeper注册服务 服务提供者需要在Zookeeper上创建一个临时节点来注册自己的服务。节点的名…

第五届IEEE先进电气和能源系统国际会议(AEES 2024)即将召开!

第五届先进电气和能源系统国际会议将于2024年11月29日至12月1日在中国兰州召开&#xff0c;欢迎参加&#xff01; 本届会议关注先进电气和能源系统的新理论及其应用&#xff0c;为相关领域的技术及相关研究领域的专家、学者交流最新研究成果、探讨学术发展方向提供一个广泛的交…

LVS原理——详细介绍

目录 介绍 lvs简介 LVS作用 LVS 的优势与不足 LVS概念与相关术语 LVS的3种工作模式 LVS调度算法 LVS-dr模式 LVS-tun模式 ipvsadm工具使用 实验 nat模式集群部署 实验环境 webserver1配置 webserver2配置 lvs配置 dr模式集群部署 实验环境 router 效果呈现…

漏洞复现-Cacti命令执行漏洞 (CVE-2022-46169)

1.漏洞描述 Cacti是一套基于PHP&#xff0c;MySQL&#xff0c;SNMP及RRDTool开发的网络流量监测图形分析工具&#xff0c;可为用户提供强大且可扩展的操作监控和故障管理框架。 该漏洞存在于remote_agent.php文件中&#xff0c;未经身份验证的恶意攻击者可以通过设置HTTP_变量…

Vue2计算属性与Vue3的计算属性对比

Vue2的计算属性 在Vue2文档上存在这么一个例子&#xff1a;通过计算属性来获取全名 var vm new Vue({el: #demo,data: {firstName: Foo,lastName: Bar},computed: {fullName: function () {return this.firstName this.lastName}} }) 同时&#xff0c;如果我们更改了计算…

【学习笔记】Day 11

一、进度概述 1、《地震勘探原理》第四章 二、详情 4.1 影响地震波传播速度的因素分析 这里只做定性总结&#xff0c;定量参考书上公式&#xff08;p139-p143&#xff09;。这一章节是通过观测速度模型&#xff0c;确定岩层结构的基础知识&#xff08;虽然更像是地质解释那一…

Vue中的路由与多种守卫常见问题及解决方案

在Vue.js项目中&#xff0c;Vue Router是实现单页面应用&#xff08;SPA&#xff09;页面跳转的重要工具。路由守卫作为Vue Router的一个关键特性&#xff0c;用于在路由跳转前或跳转后进行逻辑判断&#xff0c;如权限验证、登录状态检查等。然而&#xff0c;在使用路由守卫时&…

零基础5分钟学会谷歌云GCP核心云架构技能 - 成本分析篇

简介&#xff1a; 欢迎来到小李哥谷歌云GCP云计算知识学习系列&#xff0c;适用于任何无云计算或者谷歌云技术背景的开发者&#xff0c;让大家零基础5分钟通过这篇文章就能完全学会谷歌云一个经典的服务开发架构方案。 我将每天介绍一个基于全球三大云计算平台&#xff08;AW…

基于单片机的智能风扇设计

摘 要: 传统风扇无法根据周围环境的温度变化进行风速的调整&#xff0c;必须人为地干预才能达到需求 。 本文基于单片机的智能风扇主要解决以往风扇存在的问题&#xff0c;其有两种工作模式: 手动操作模式和自动运行模式&#xff0c;人们可以根据需要进行模式选择。 在自动运行…

TIM定时器 溢出时间计算

在f1系列&#xff0c;所有定时器的时钟源频率都是72mhz&#xff0c;因为不管是挂在apb1还是apb2时钟总线上的定时器&#xff0c;经过倍频或者不倍频以后都将把定时器的频率设置成72mhz 时钟源频率除以psc1以后得到实际的频率&#xff0c;之所以psc要加1&#xff0c;是因为设置…

无人机测绘技术如何?

无人机测绘技术作为现代测绘技术的重要组成部分&#xff0c;正以其独特的优势在多个领域发挥着重要作用。以下是对无人机测绘技术的详细分析&#xff1a; 一、技术特点 1. 高精度&#xff1a;无人机测绘搭载高精度传感器和相机&#xff0c;能够快速、准确地获取地面信息&…

Datawhale X 魔搭 AI夏令营第四期-魔搭生图task1学习笔记

根据教程提供的链接&#xff0c;进入相应文章了解魔搭生图的主要工作是通过对大量图片的训练&#xff0c;生成自己的模型&#xff0c;然后使用不同的正向、反向提示词使模型输出对应的图片 1.官方跑baseline教程链接:Task 1 从零入门AI生图原理&实践 2.简单列举一下赛事的…

【Material-UI】Floating Action Button (FAB) 详解:基础用法

文章目录 一、Floating Action Button (FAB) 简介1. FAB 的定义2. FAB 的特点 二、Basic FAB 的基础用法1. 基础 FAB 按钮2. 次要颜色的 FAB 按钮3. 扩展变体的 FAB 按钮4. 禁用状态的 FAB 按钮 三、FAB 按钮的高级自定义1. 自定义按钮大小2. 调整按钮的悬浮位置 四、FAB 的无障…

研究报告系列二:供应链安全风险原因分析及相关新质生产力重要技术探讨

随着软件开发的复杂性和动态性不断增加&#xff0c;软件供应链的安全风险成为了一个亟需重视的重要议题&#xff0c;而在技术的飞速发展和广泛应用下&#xff0c;新质生产力相关领域同样存在着软件供应链安全方面的问题&#xff0c;作为《2023软件供应链安全研究报告》系列中的…