SpringBoot响应式编程(2)WebFlux入门

news2025/1/12 15:56:46

一、概述

1.1简介

简单来说,Webflux 是响应式编程的框架,与其对等的概念是 SpringMVC。两者的不同之处在于 Webflux 框架是异步非阻塞的,其可以通过较少的线程处理高并发请求。

WebFlux:底层完全基于netty+reactor+springweb 完成一个全异步非阻塞的web响应式框架

底层:异步 + 消息队列(内存) + 事件回调机制 = 整套系统

优点:能使用少量资源处理大量请求;

以前: 浏览器 --> Controller --> Service --> Dao: 阻塞式编程

现在: Dao(数据源查询对象【数据发布者】) --> Service --> Controller --> 浏览器: 响应式

1.2什么是异步 Servlet

在 Servlet3.0 之前,Servlet 采用 Thread-Per-Request 的方式处理 Http 请求,即每一次请求都是由某一个线程从头到尾负责处理。

如果一个请求需要进行 IO 操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待 IO 操作完成, 而 IO 操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,如果并发量很大的话,那肯定会造性能问题。

有了异步 Servlet 之后,后台 Servlet 的线程会被及时释放,释放之后又可以去接收新的请求,进而提高应用的并发能力。

1.3SSE

SSE 全称是 Server-Sent Events,它的作用和 WebSocket 的作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息,不同的是,WebSocket 是一种全双工通信协议,而 SSE 则是一种单工通信协议,即使用 SSE 只能服务器向浏览器推送信息流,浏览器如果向服务器发送信息,就是一个普通的 HTTP 请求。

使用 SSE,当服务端给客户端响应的时候,他不是发送一个一次性数据包,而是会发送一个数据流,这个时候客户端的连接不会关闭,会一直等待服务端发送过来的数据流,我们常见的视频播放其实就是这样的例子。

SSE 和 WebSocket 主要有如下区别:

  • SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。

  • SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。

  • SSE 默认支持断线重连,WebSocket 需要自己实现。

  • SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。

  • SSE 支持自定义发送的消息类型。

二、快速入门

2.0创建工程

为了演示方便,松哥这里就直接采用 Spring Boot 工程了,首先我们创建一个 Spring Boot 工程,需要注意的是,以往创建 Spring Boot 时我们都是选择 Spring Web 依赖,但是这次我们选择 Spring Reactive Web 依赖,如下图:

添加上这一个依赖就 OK 了。

这个时候创建好的 Spring Boot 项目,底层容器是 Netty 而不是我们之前广泛使用的 Tomcat 了。

2.1添加依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

2.2HttpHandler、HttpServer

public class FluxMainApplication {

    public static void main(String[] args) throws IOException {
        //快速自己编写一个能处理请求的服务器

        //1、创建一个能处理Http请求的处理器。 参数:请求、响应; 返回值:Mono<Void>:代表处理完成的信号
        HttpHandler handler = (ServerHttpRequest request,
                                   ServerHttpResponse response)->{
            URI uri = request.getURI();
            System.out.println(Thread.currentThread()+"请求进来:"+uri);
            //编写请求处理的业务,给浏览器写一个内容 URL + "Hello~!"
//            response.getHeaders(); //获取响应头
//            response.getCookies(); //获取Cookie
//            response.getStatusCode(); //获取响应状态码;
//            response.bufferFactory(); //buffer工厂
//            response.writeWith() //把xxx写出去
//            response.setComplete(); //响应结束

            //数据的发布者:Mono<DataBuffer>、Flux<DataBuffer>

            //创建 响应数据的 DataBuffer
            DataBufferFactory factory = response.bufferFactory();

            //数据Buffer
            DataBuffer buffer = factory.wrap(new String(uri.toString() + " ==> Hello!").getBytes());


            // 需要一个 DataBuffer 的发布者
            return response.writeWith(Mono.just(buffer));
        };

        //2、启动一个服务器,监听8080端口,接受数据,拿到数据交给 HttpHandler 进行请求处理
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);


        //3、启动Netty服务器
        HttpServer.create()
                .host("localhost")
                .port(8080)
                .handle(adapter) //用指定的处理器处理请求
                .bindNow(); //现在就绑定

        System.out.println("服务器启动完成....监听8080,接受请求");
        System.in.read();
        System.out.println("服务器停止....");


    }
}

2.3DispatcherHandler

SpringMVC: DispatcherServlet;

SpringWebFlux: DispatcherHandler

package com.yanyu.webflux.controller;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;

/**
 * @author lfy
 * @Description
 * @create 2023-12-01 20:52
 */
@ResponseBody
    @Controller
public class HelloController {


    //WebFlux: 向下兼容原来SpringMVC的大多数注解和API;
    @GetMapping("/hello")
    public String hello(@RequestParam(value = "key",required = false,defaultValue = "哈哈") String key,
                        ServerWebExchange exchange,
                        WebSession webSession,
                        HttpMethod method,
                        HttpEntity<String> entity,
                        @RequestBody String s,
                        FilePart file){

//        file.transferTo() //零拷贝技术;
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String name = method.name();



        Object aaa = webSession.getAttribute("aaa");
        webSession.getAttributes().put("aa","nn");

        return "Hello World!!! key="+key;
    }



    // Rendering:一种视图对象。
    @GetMapping("/bai")
    public Rendering render(){
//        Rendering.redirectTo("/aaa"); //重定向到当前项目根路径下的 aaa
       return   Rendering.redirectTo("http://www.baidu.com").build();
    }

    //现在推荐的方式
    //1、返回单个数据Mono: Mono<Order>、User、String、Map
    //2、返回多个数据Flux: Flux<Order>
    //3、配合Flux,完成SSE: Server Send Event; 服务端事件推送

    @GetMapping("/haha")
    public Mono<String> haha(){

//        ResponseEntity.status(305)
//                .header("aaa","bbb")
//                .contentType(MediaType.APPLICATION_CBOR)
//                .body("aaaa")
//                .

        return Mono.just(0)
                .map(i-> 10/i)
                .map(i->"哈哈-"+i);
    }

    @GetMapping("/hehe")
    public Flux<String> hehe(){
        return Flux.just("hehe1","hehe2");
    }


    //text/event-stream
    //SSE测试; chatgpt都在用; 服务端推送
    @GetMapping(value = "/sse",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> sse(){
        return Flux.range(1,10)
                .map(i-> {

                    //构建一个SSE对象
                   return    ServerSentEvent.builder("ha-" + i)
                            .id(i + "")
                            .comment("hei-" + i)
                            .event("haha")
                            .build();
                })
                .delayElements(Duration.ofMillis(500));
    }

    //SpringMVC 以前怎么用,基本可以无缝切换。
    // 底层:需要自己开始编写响应式代码

}

2.4错误处理

 @GetMapping("/haha")
    public Mono<String> haha(){

//        ResponseEntity.status(305)
//                .header("aaa","bbb")
//                .contentType(MediaType.APPLICATION_CBOR)
//                .body("aaaa")
//                .

        return Mono.just(0)
                .map(i-> 10/i)
                .map(i->"哈哈-"+i);
    }
    @ExceptionHandler(ArithmeticException.class)
    public String error(ArithmeticException exception){
        System.out.println("发生了数学运算异常"+exception);

        //返回这些进行错误处理;
//        ProblemDetail:  建造者:声明式编程、链式调用
//        ErrorResponse : 

        return "炸了,哈哈...";
    }

2.6常用注解

1、目标方法传参

Method Arguments :: Spring Framework

Controller method argument

Description

ServerWebExchange

封装了请求和响应对象的对象; 自定义获取数据、自定义响应

ServerHttpRequest, ServerHttpResponse

请求、响应

WebSession

访问Session对象

java.security.Principal

org.springframework.http.HttpMethod

请求方式

java.util.Locale

国际化

java.util.TimeZone + java.time.ZoneId

时区

@PathVariable

路径变量

@MatrixVariable

矩阵变量

@RequestParam

请求参数

@RequestHeader

请求头;

@CookieValue

获取Cookie

@RequestBody

获取请求体,Post、文件上传

HttpEntity<B>

封装后的请求对象

@RequestPart

获取文件上传的数据 multipart/form-data.

java.util.Map, org.springframework.ui.Model, and org.springframework.ui.ModelMap.

Map、Model、ModelMap

@ModelAttribute

Errors, BindingResult

数据校验,封装错误

SessionStatus + class-level @SessionAttributes

UriComponentsBuilder

For preparing a URL relative to the current request’s host, port, scheme, and context path. See URI Links.

@SessionAttribute

@RequestAttribute

转发请求的请求域数据

Any other argument

所有对象都能作为参数:

1、基本类型 ,等于标注@RequestParam

2、对象类型,等于标注 @ModelAttribute

2、返回值写法

sse和websocket区别:

  • SSE:单工;请求过去以后,等待服务端源源不断的数据
  • websocket:双工: 连接建立后,可以任何交互;

Controller method return value

Description

@ResponseBody

把响应数据写出去,如果是对象,可以自动转为json

HttpEntity<B>, ResponseEntity<B>

ResponseEntity:支持快捷自定义响应内容

HttpHeaders

没有响应内容,只有响应头

ErrorResponse

快速构建错误响应

ProblemDetail

SpringBoot3;

String

就是和以前的使用规则一样;

forward: 转发到一个地址

redirect: 重定向到一个地址

配合模板引擎

View

直接返回视图对象

java.util.Map, org.springframework.ui.Model

以前一样

@ModelAttribute

以前一样

Rendering

新版的页面跳转API; 不能标注 @ResponseBody 注解

void

仅代表响应完成信号

Flux<ServerSentEvent>, Observable<ServerSentEvent>, or other reactive type

使用 text/event-stream 完成SSE效果

Other return values

未在上述列表的其他返回值,都会当成给页面的数据;

2.7文件上传

@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, 
		@RequestPart("file-data") FilePart file) { 
	// ...
}

2.8自定义Flux配置

WebFluxConfigurer

容器中注入这个类型的组件,重写底层逻辑

@Configuration
public class MyWebConfiguration {

    //配置底层
    @Bean
    public WebFluxConfigurer webFluxConfigurer(){

        return new WebFluxConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedHeaders("*")
                        .allowedMethods("*")
                        .allowedOrigins("localhost");
            }
        };
    }
}

2.9 Filter

@Component
public class MyWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        System.out.println("请求处理放行到目标方法之前...");
        Mono<Void> filter = chain.filter(exchange); //放行


        //流一旦经过某个操作就会变成新流

        Mono<Void> voidMono = filter.doOnError(err -> {
                    System.out.println("目标方法异常以后...");
                }) // 目标方法发生异常后做事
                .doFinally(signalType -> {
                    System.out.println("目标方法执行以后...");
                });// 目标方法执行之后

        //上面执行不花时间。
        return voidMono; //看清楚返回的是谁!!!
    }
}

三、WebFlux  CURD 实战(mongodb)

3.1概述

WebFlux 最为人所诟病的是数据库的支持问题,毕竟数据是一个应用的生命,我们接触的大部分应用程序都是有数据库的,而 WebFlux 在这一方面的支持行一直比较弱,这也是大家总是吐槽它的原因。

不过从 Spring5 开始,这一问题得到了一定程度的缓解。

Spring 官方在 Spring5 发布了响应式 Web 框架 Spring WebFlux 之后急需能够满足异步响应的数据库交互 API,不过由于缺乏标准和驱动,Pivotal 团队开始自己研究响应式关系型数据库连接 Reactive Relational Database Connectivity,并提出了 R2DBC 规范 API 用来评估可行性并讨论数据库厂商是否有兴趣支持响应式的异步非阻塞驱动程序。最早只有 PostgreSQL 、H2、MSSQL 三家数据库厂商,不过现在 MySQL 也加入进来了,这是一个极大的利好。目前 R2DBC 的最新版本是 0.9.0.RELEASE。

3.2相关概念

基于spring-data-mongodb-reactive响应式框架实现与mongodb的互交。

ReactiveMongoRepository<T, ID>支持响应式编程并提供基础功能的接口,类似JpaRepository/BaseMapper接口。

基于Reactive编程时,查询返回多条记录封装在`Flux<T>`而非`Flux<List<T>>`。 再通过collectList()方法将元素聚合为单一`Mono<List<T>>`类型

聚合语句声明在Repository接口查询方法的@Aggregation注解中。java11不支持文本块,可读性太差了。

mongodb日期时间以UTC计算,并自动转换本地时间存储。但在获取时,spring-data-mongodb会转换回本地时间。  
因此,涉及日期时间的必须通过业务逻辑操作。

3.3环境配置

maven依赖

yml依赖

spring:
  application:
    name: mongodb-examples
  data:
    mongodb:
      host: 47.96.254.46
      port: 27017
      database: test
      username: mongo
      password: '1213' # 密码按char[]处理。纯数字要加单引号,最好都加单引号
      authentication-database: admin
#       uri: mongodb://mongo:1213@192.168.1.8/test?authSource=admin # 等效连接
      auto-index-creation: true # 仅声明索引注解无效,必须显式声明。

logging:
  level:
    root: warn
    com:
      example: debug
  pattern:
    console: '%-5level %C.%M[%line] - %msg%n'

3.4CRUD

实体类

@Document
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class user {
    @Id
    private String id;
    private String username;
    private String address;
}

Repository


@EnableMongoRepositories
public interface UserDao extends ReactiveMongoRepository<User,String> {
}

Controller

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserDao userDao;

    @PostMapping("/add")
    public Mono<user> addUser(@RequestBody user user) {
        return userDao.save(user);
    }
    @GetMapping("/getall")
    public Flux<user> getAll() {
        return userDao.findAll();
    }
    @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<user> streamGetAll() {
        return userDao.findAll();
    }
    @DeleteMapping("/delete/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable String id) {
        return userDao.findById(id)
                .flatMap(user -> userDao.delete(user).then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))))
                .defaultIfEmpty(new ResponseEntity(HttpStatus.NOT_FOUND));
    }
    @PutMapping("/update")
    public Mono<ResponseEntity<user>> updateUser(@RequestBody user user) {
        return userDao.findById(user.getId())
                .flatMap(u -> userDao.save(user))
                .map(u->new ResponseEntity<user>(u,HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity(HttpStatus.NOT_FOUND));
    }
}

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

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

相关文章

深入解析 vue 3 获取响应式数据

Vue 3 引入了一个全新的响应式系统&#xff0c;其中最核心的就是 reactive 和 ref。它们是实现响应式数据的基础&#xff0c;用于创建可以自动跟踪变化并更新视图的对象和变量。 1. reactive&#xff1a;响应式对象 1、概念 reactive 是用于创建响应式对象的 API。它接收一个…

HighConcurrencyCommFramework c++通讯服务器框架 :Epoll:事件驱动技术

在单独的进程或者线程中运行&#xff0c;收集处理事件&#xff0c;没有上下文切换的消耗&#xff0c;高校&#xff1b; 写小demo很简单&#xff0c;正经让epoll技术融合到商业环境中&#xff0c;那么难度很大&#xff1b; 达到的效果&#xff1a; 1.理解工作原理&#xff1b…

Splay学习笔记

Splay的两个关键函数&#xff0c;rotate和splay rotate就是正常的旋转。 splay(x,target)表示把x旋转为target的子节点 这里需要分讨&#xff0c;对于x的父亲y和祖父z 若 z target&#xff0c; 则直接转x若 x 与 y 方向相同&#xff0c;先转y&#xff0c;后转x若 x 与 y 方…

html+css网页制作 电商华为商城首页 ui还原度100%

htmlcss网页制作 电商华为商城首页 ui还原度100% 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码…

Docker日志管理

一、知识点介绍 1.ELK(Elasticserach、Logstash、Kibana) 前面笔记有 2.什么是 Filebeat Filebeat 是 ELK 组件的新成员&#xff0c; 也是 Beat 成员之一。基于 Go 语言开发&#xff0c;无任何依赖并且比 Logstash 更加轻量&#xff0c;不会带来过高的资源占用&#xff0c; …

django常用的组合搜索组件

文章目录 django常用的组合搜索组件快速使用配置信息1. 视图函数2. 前端模板3. css样式 代码实现 django常用的组合搜索组件 在项目开发中&#xff0c;如果有大量数据就要用到组合搜索&#xff0c;通过组合搜索对大块内容进行分类筛选。 快速使用 三步走&#xff1a;&#xf…

刷题记录第110天-分割等和数组

解题思路&#xff1a; 问题可转化为&#xff0c;用给定数组能否装满一个容量为数组总和一半的背包(targetsum/2)&#xff0c;即一个0-1背包问题。 0-1背包问题的关键在于数组的定义和状态转移方程以及价值的定义。dp[i][j]表示在[0…i]个物品内&#xff0c;背包容量为j能装的最…

再升级!MoneyPrinterPlus集成GPT_SoVITS

最近有很多优秀的语音合成TTS工具&#xff0c;目前MoneyPrinterPlus已经集成了ChatTTS和fasterWhisper。应朋友们的要求&#xff0c;最近MoneyPrinterPlus也集成了GPT_SoVITS这个优秀的语音合成工具。 今天给大家详细讲解一下&#xff0c;如何在MoneyPrinterPlus中使用GPT_SoV…

机器学习速成第三集——无监督学习之降维(理论部分)!

目录 主成分分析&#xff08;PCA&#xff09; 独立成分分析&#xff08;ICA&#xff09; t分布随机邻近嵌入&#xff08;t-SNE&#xff09; 线性判别分析&#xff08;LDA&#xff09; 其他降维方法 应用场景 主成分分析&#xff08;PCA&#xff09;在处理大规模数据集时…

新能源汽车电机低频电磁场仿真应用

一、背景介绍 随着新能源汽车的普及&#xff0c;电机作为新能源汽车驱动系统的核心组成部分&#xff0c;其重要性不言而喻。电机使电能转化为机械能&#xff0c;通过传动系统将机械能传递到车轮&#xff0c;驱动汽车行驶。新能源汽车电机的发展经历了从初步探索到技术成熟的多…

Localization Translate API 的对接和使用

Localization Translate API 的对接和使用 Localization Translate API 的主要功能是通过输入需要翻译的文本来获取翻译后的文本&#xff0c;同时翻译后的语言可以自定义&#xff0c;并且翻译结果可以采用 json &#xff0c; markdown 俩种主流的方法来输出。 本文档将详细介…

【安卓】多线程编程

文章目录 线程的简单应用解析异步消息处理机制使用AsyncTask 线程的简单应用 新建一个AndroidThreadTest项目&#xff0c;然后修改activity_main.xml中的代码。 <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width…

CNN-GRU神经网络多输入单输出回归预测【MATLAB】

1. CNN&#xff08;卷积神经网络&#xff09;部分 作用&#xff1a; 特征提取&#xff1a;CNN擅长从输入数据中提取空间特征。在多输入情况下&#xff0c;它可以处理来自不同源的数据&#xff0c;提取有用的特征。 局部感受野&#xff1a;通过卷积操作&#xff0c;CNN能够识别…

【ACM出版,往届会后三个半月EI见刊/检索】第四届物联网与机器学习国际学术会议(IoTML 2024,8月23-25)

2024年第四届物联网与机器学习国际学术会议&#xff08;IoTML 2024&#xff09;将于2024年8月23-25日在中国南昌召开。 会议将围绕着物联网和机器学习开展&#xff0c;探讨本领域发展所面临的关键性挑战问题和研究方向&#xff0c;以期推动该领域理论、技术在高校和企业的发展和…

vector嵌套之空指针异常

文章目录 1. 题目链接2. 题目代码正确代码错误代码 1. 题目链接 118. 杨辉三角 2. 题目代码 正确代码 class Solution { public:vector<vector<int>> generate(int numRows) {vector<vector<int>> result(numRows);for(int i 0; i < numRows; i)…

STL中的栈(stack)和队列(queue)以及简单(复用)实现

适配器&#xff1a; 虽然 stack 和 queue 中也可以存放元素&#xff0c;但在 STL 中并没有将其划分在容器的行列&#xff0c;而是将其称为 容器适配器 &#xff0c;这是因为 stack 和队列只是对其他容器的接口进行了包装&#xff0c; STL 中 stack 和 queue 默认使用deque 换种…

【云备份】学习Json

文章目录 1.Json数据类型基础数据类型复合数据类型JSON数据类型的应用 2.学习jsoncpp库利用json实现序列化利用json实现反序列化 1.Json数据类型 json 是一种数据交换格式&#xff0c;采用完全独立于编程语言的文本格式来存储和表示数据。json数据交换格式是将多种数据对象组织…

CVE-2024-38077 Windows远程桌面授权服务漏洞介绍

CVE-2024-38077 是一个在Windows远程桌面授权服务&#xff08;Remote Desktop Licensing Service&#xff09;中存在的严重远程代码执行漏洞。以下是关于此漏洞的详细信息&#xff1a; 漏洞概述 漏洞编号&#xff1a;CVE-2024-38077漏洞类型&#xff1a;远程代码执行 (RCE)影…

基于单片机控制的多功能智能语音风扇

【摘要】 本文简述了一种基于单片机控制的智能多功能语音风扇的设计&#xff0c;该设计以STC11L08XE单片机为主控制器&#xff0c;通过YS-LDV7语音模块对语音信号进行采集识别&#xff0c;并将该信号上传给单片机进而控制风扇的转速和开关&#xff0c;以达到语音控制的效果。该…

Python 安装 PyTorch详细教程

本章教程,介绍如何安装PyTorch,介绍两种安装方式,一种是通过pip直接安装,一种是通过conda方式安装。 一、查看CUDA版本 二、安装PyTorch 1、pip安装方式 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu1162、conda安装方式 …