响应式编程实战:Spring WebFlux集成MongoDB和Swagger

news2025/1/9 17:01:37

1 缘起

新的项目,快速迭代,
技术选型:Spring WebFlux,
非Spring MVC,
之前没有接触过Spring WebFlux,项目中都是使用Spring MVC,
这次学到了新的知识Spring WebFlux,记录下。

2 SpringMVC & Spring WebFlux

Spring产品提供了两个并行的技术路线:

  • 基于Spring MVC和Spring Data构造的Servlet API路线
  • 基于Spring WebFlux和Spring Data的响应流路线
    响应式:https://spring.io/reactive
    响应式系统是低延迟、高吞吐工作负载的理想方案
    WebFlux一切皆流。
    下面看下Spring WebFlux和Spring MVC这两个技术路线的对比:
    在这里插入图片描述
    由上图可知,
    Spring WebFlux支持非关系型数据库,
    这一点与Spring MVC的差别还是很大的,
    如果需要使用关系型数据库,基于Spring MVC构建项目是最有选择,
    因此,基于Spring WebFlux构建项目,适用于非结构化存储。

本篇主要讲解应用,响应式编程的细节会在其他文章中分享。

3 项目依赖

版本:
SpringBoot2.4.5
Swagger:3.0.0

这里,使用spring-boot-starter-webflux作为启动容器(默认为Netty)。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.monkey</groupId>
	<artifactId>spring-boot-template</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-template</name>
	<description>Template project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<io.springfox.version>3.0.0</io.springfox.version>
		<io.swagger.version>1.5.22</io.swagger.version>
		<fastjson.version>1.2.60</fastjson.version>
		<logback.version>1.2.3</logback.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!--接口管理工具-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-boot-starter</artifactId>
			<version>${io.springfox.version}</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>${io.springfox.version}</version>
			<exclusions>
				<exclusion>
					<groupId>io.swagger</groupId>
					<artifactId>swagger-annotations</artifactId>
				</exclusion>
				<exclusion>
					<groupId>io.swagger</groupId>
					<artifactId>swagger-models</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>${io.springfox.version}</version>
		</dependency>
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-annotations</artifactId>
			<version>${io.swagger.version}</version>
		</dependency>
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-models</artifactId>
			<version>${io.swagger.version}</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${fastjson.version}</version>
		</dependency>
		<!--日志工具-->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
			<version>${logback.version}</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
		</dependency>
		<!--AOP-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

4 数据库

本文选择MongoDB作为持久层。
MongoDB的部署:https://blog.csdn.net/Xin_101/article/details/130902224

4.1 MongoDB配置

spring:
  devtools:
    restart:
      enabled: true
  data:
    mongodb:
      uri: mongodb://tutorial:admin123456@172.22.75.234:27017/tutorial?authSource=tutorial

4.2 文档配置

MongoDB使用Document作为存储数据的基本单位,对比而言,MySQL中的表,新建的数据需要明确指定存储的Document,
下面新建一个user文档,存储用户数据。
其中,@Id用户表示该数据为MongDB中的_id属性,由MongoDB生成。
而,使用MongoDB操作数据没有自动的数据映射,需要手动映射,因此,在每个需要映射的字段下新建了一个static final的属性名称。
_id,username,操作数据时使用。

package com.monkey.springboottemplate.modules.user.entity;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

/**
 * 用户信息.
 *
 * @author xindaqi
 * @since 2023-05-28 11:38
 */
@Document("user")
public class UserEntity {

    /**
     * 用户主键id,MongoDB自动生成
     */
    @Id
    private String id;
    public final static String _id = "_id";

    /**
     * 用户id
     */
    private String uid;

    /**
     * 用户姓名
     */
    private String username;
    public static final String _username = "username";

    /**
     * 用户性别
     */
    private String sex;
    public static final String _sex = "sex";

    /**
     * 创建时间
     */
    private Long createdTime;

    /**
     * 更新时间
     */
    private Long updatedTime;
    public static final String _updatedTime = "updatedTime";


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Long getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(Long createdTime) {
        this.createdTime = createdTime;
    }

    public Long getUpdatedTime() {
        return updatedTime;
    }

    public void setUpdatedTime(Long updatedTime) {
        this.updatedTime = updatedTime;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "id='" + id + '\'' +
                ", uid='" + uid + '\'' +
                ", username='" + username + '\'' +
                ", sex='" + sex + '\'' +
                ", createdTime='" + createdTime + '\'' +
                ", updatedTime='" + updatedTime + '\'' +
                '}';
    }
}

5 Service

WebFlux中使用Mono或者Flux作为返回对象,
因此,在Service层直接使用Mono或Flux作为最终的结果类型,
而不是在Controller层中封装最终结果,
WebFlux:一切皆流数据,因此,编码很优雅,可以做到瀑布式编程,数据一层一层地流下去,最终输出结果。

package com.monkey.springboottemplate.modules.user.service.impl;

import com.monkey.springboottemplate.common.response.Response;
import com.monkey.springboottemplate.modules.user.convert.UserConvert;
import com.monkey.springboottemplate.modules.user.dao.UserDAO;
import com.monkey.springboottemplate.modules.user.entity.UserEntity;
import com.monkey.springboottemplate.modules.user.service.IUserService;
import com.monkey.springboottemplate.modules.user.vo.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户服务实现类.
 *
 * @author xindaqi
 * @date 2021-05-08 16:01
 */
@Service
public class UserServiceImpl implements IUserService {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private ReactiveMongoTemplate mongoTemplate;

    @Override
    public Mono<Response<String>> addUser(UserAddInputVO params) {
        UserEntity userEntity = new UserEntity();
        userEntity.setUid(params.getUid());
        userEntity.setUsername(params.getUsername());
        userEntity.setSex(params.getSex());
        userEntity.setCreatedTime(System.currentTimeMillis());
        return mongoTemplate.insert(userEntity).flatMap(user -> Mono.just(Response.success(user.getId())));
    }

    @Override
    public Mono<Response<String>> deleteUser(String id) {
        Query query = Query.query(Criteria.where(UserEntity._id).is(id));
        return mongoTemplate.findAndRemove(query, UserEntity.class).flatMap(user ->
                Mono.just(Response.success(id))
        );
    }

    @Override
    public Mono<Response<String>> editUser(UserEditInputVO params) {
        Query query = Query.query(Criteria.where(UserEntity._id).is(params.getId()));
        Update update = new Update()
                .set(UserEntity._sex, params.getSex())
                .set(UserEntity._username, params.getUsername())
                .set(UserEntity._updatedTime, System.currentTimeMillis());
        return mongoTemplate.updateFirst(query, update, UserEntity.class).flatMap(user -> Mono.just(Response.success(params.getId())));
    }

    @Override
    public Mono<Response<UserInfoVO>> queryUserById(String id) {
        Query query = Query.query(Criteria.where(UserEntity._id).is(id));
        return mongoTemplate.findOne(query, UserEntity.class).flatMap(user -> {
            UserInfoVO userInfo = new UserInfoVO();
            userInfo.setUid(user.getUid());
            userInfo.setId(user.getId());
            userInfo.setSex(user.getSex());
            userInfo.setUsername(user.getUsername());
            return Mono.just(Response.success(userInfo));
        });
    }

    @Override
    public Mono<Response<List<UserInfoVO>>> queryUserByPage(UserPageInputVO params) {
        Response<List<UserInfoVO>> resp = Response.success();
        Query query = new Query();
        return mongoTemplate.count(query, UserEntity.class).flatMap(count -> {
            if (Objects.isNull(count)) {
                resp.setTotal(0);
            }
            resp.setTotal(count);
            query.skip((long) (params.getPageStart() - 1) * params.getPageSize()).limit(params.getPageSize());
            return mongoTemplate.find(query, UserEntity.class).collectList();
        }).map(userLi -> {
            List<UserInfoVO> li = userLi.stream().map(UserConvert::convert).collect(Collectors.toList());
            resp.setData(li);
            return resp;
        });
    }
}

6 Controller

接口层,直接使用Mono封装结果。

package com.monkey.springboottemplate.api;

import com.monkey.springboottemplate.common.response.Response;
import com.monkey.springboottemplate.modules.user.service.IUserService;
import com.monkey.springboottemplate.modules.user.vo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

import java.util.List;

import static com.monkey.springboottemplate.common.constant.DigitalConstant.ONE;

/**
 * 用户增删改查接口.
 *
 * @author xindaqi
 * @date 2021-05-08 16:09
 */
@RestController
@RequestMapping("/api/v1/user")
@Api(tags = "人员配置")
public class UserApi {

    @Resource
    IUserService userService;

    @PostMapping("/add")
    @ApiOperation("添加用户")
    public Mono<Response<String>> addUser(@RequestBody UserAddInputVO params) {
        return userService.addUser(params);
    }

    @GetMapping("/delete")
    @ApiOperation("删除用户")
    public Mono<Response<String>> deleteUser(@Param("id") String id) {
        return userService.deleteUser(id);
    }

    @PostMapping("/edit")
    @ApiOperation("编辑/修改用户")
    public Mono<Response<String>> editUser(@RequestBody UserEditInputVO params) {
        return userService.editUser(params);
    }

    @GetMapping("/query")
    @ApiOperation("根据ID查询用户")
    public Mono<Response<UserInfoVO>> queryUserById(@Param("id") String id) {
        return userService.queryUserById(id);
    }

    @PostMapping("/query/page")
    @ApiOperation("分页查询用户")
    public Mono<Response<List<UserInfoVO>>> queryUserByPage(@RequestBody UserPageInputVO params) {
        return userService.queryUserByPage(params);
    }
}

7 配置Swagger

Swagger使用3.0.0版本。

package com.monkey.springboottemplate.common.config;


import springfox.documentation.service.Contact;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import org.springframework.core.env.Profiles;
import org.springframework.core.env.Environment;
import springfox.documentation.oas.annotations.EnableOpenApi;

/**
 * Swagger配置.
 *
 * @author xindaqi
 * @since 2023-05-27 22:20
 */
@Configuration
@EnableSwagger2
@EnableOpenApi
public class SwaggerConfig {

    @Bean
    public Docket createRestApi(Environment environment){
        // 配置Swagger显示,仅dev和test环境显示
        Profiles profiles = Profiles.of("dev");
        boolean b = environment.acceptsProfiles(profiles);
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(b)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("接口文档")
                .contact(new Contact("xindaqi", "xxx@qq.com", "xxx@qq.com"))
                .version("1.0")
                .description("个人信息")
                .termsOfServiceUrl("http://localhost:9121/api/v1")
                .build();
    }
}

访问:http://localhost:9121/swagger-ui/index.htm

在这里插入图片描述

8 小结

技术选型依实际情况而定,不是为了引入技术而引入技术,是为了解决问题,引入技术。
(1)Spring WebFlux使用Mono或Flux作为返回类型;
(2)Spring WebFlux一切皆流,通过流将数据一层一层地传递下去,编程方式非常优雅:瀑布式;
(3)Spring WebFlux使用Swagger3.0.0,测试时,使用了Swagger2.9.2无法生成文档;
(4)Spring WebFlux是响应式编程,低延迟、高吞吐系统的首选,但是,数据层的选择受限,如仅支持有限的非关系型数据库:MongoDB、Redis。

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

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

相关文章

C++实现哈希表

文章目录 前言1.哈希表的相关介绍2.哈希表的实现1.开放定址法实现哈希表1.插入2.查找3.删除 2.链地址法(开链法)实现哈希表1.插入节点2.查找3.删除4.相关的一些补充 3.封装unordered_map与unordered_set1.封装前的改造2.迭代器的实现3.unordered_map和unordered_set复用 前言 …

60题学会动态规划系列:动态规划算法第一讲

坚持就是胜利 - - 文章目录 1.第N个泰波那切数 2.三步问题 3.使用最小花费爬楼梯 4.解码方法 1.第N个泰波那切数 力扣链接&#xff1a;力扣 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c…

多线程 -- 线程安全问题(3)

本篇重点: 总结线程安全问题的原因以及解决办法 目录 synchronized 加锁关键字join 和 synchronized 的区别volatile 关键字 在上一篇中我们介绍了Thread类的基本使用方法, 本篇将会介绍有关于线程的安全问题 线程不安全的原因: 抢占式执行(罪魁祸首, 万恶之源) 多个线程修改同…

搜索推荐系统[10]项目实战系列Z5:汽车说明书跨模态智能问答系统,针对汽车说明书(可自定义文档)进行自动问答,采用了OCR、RocketQA等技术

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

小程序之页面通信派发通知

文章目录 1. 介绍小程序页面通信的概念解释小程序页面通信的意义和必要性介绍小程序页面通信的方法 2. 小程序页面通信的实现示例通过事件传递数据实现页面之间通信通过全局变量实现页面之间通信 3. 实现小程序页面之间的消息通知介绍小程序发布订阅模式的概念使用事件订阅-发布…

网络通信IO模型-BIO

承接上文网络通信IO模型上 BIO的Java代码 服务端创建一个ServerSocket&#xff0c;绑定了端口号8090&#xff0c;目的是让客户端和服务端建立连接后进行通信&#xff0c;然后进入死循环&#xff0c;死循环里面会调用server.accept得到一个socket客户端&#xff0c;打印客户端的…

【PyQt5】指示灯显示

【PyQt5】指示灯显示 1、背景2、代码示例3、QtDesigner绘制 1、背景 利用Qt5写工业控制软件交互界面的时候&#xff0c;经常需要在界面上有指示灯功能。 例如下面的明暗表示串行端口的连接和断开。 我们本质是用Qt5的label文本标签来实现的&#xff0c;即通过设置标签的样式表…

115.删除有序数组中的重复项 removeDuplicatesFromSortedArray

文章目录 题目描述解题思路代码详解运行截图 题目描述 题目链接 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元…

C# | 凸包算法之Jarvis,寻找一组点的边界/轮廓

C#实现凸包算法之Jarvis 文章目录 C#实现凸包算法之Jarvis前言示例代码实现思路测试结果结束语 前言 这篇关于凸包算法的文章&#xff0c;本文使用C#和Jarvis算法来实现凸包算法。 首先消除两个最基本的问题&#xff1a; 什么是凸包呢&#xff1f; 凸包是一个包围一组点的凸多…

驱动LSM6DS3TR-C实现高效运动检测与数据采集(1)----获取ID

概述 本文将介绍如何驱动和利用LSM6DS3TR-C传感器&#xff0c;实现精确的运动感应功能。LSM6DS3TR-C是一款先进的6轴惯性测量单元&#xff08;IMU&#xff09;&#xff0c;集成了三轴加速度计和三轴陀螺仪&#xff0c;可用于测量和检测设备的加速度、姿态和运动。 本文将提供L…

车载软件架构 —— 闲聊几句AUTOSAR OS(二)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 在最艰难的时候,自己就别去幻想太远的将来,只要鼓励自己过好今天就行了! 这世间有太多的猝不及防,有些东西根本不配占有自己的情绪,人生就是一场体验,…

牛客HJ43迷宫问题 - 创建智能体通过策略自己找路

文章目录 问题描述思路代码C 问题描述 描述 定义一个二维数组 N*M &#xff0c;如 5 5 数组下所示&#xff1a; int maze[5][5] { 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, }; 它表示一个迷宫&#xff0c;其中的1表示墙壁&#xff0…

SPA首屏加载速度慢的怎么解决?

SPA首屏加载速度慢的怎么解决&#xff1f; 加载慢的原因 网络延时问题资源文件体积是否过大资源是否重复发送请求去加载了加载脚本的时候&#xff0c;渲染内容堵塞了 解决方案 1.减小入口文件体积 常用的手段是路由懒加载&#xff0c;把不同路由对应的组件分割成不同的代码…

如何在华为OD机试中获得满分?Java实现【水仙花数】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 所谓水仙花数,是指一个…

LeetCode高频算法刷题记录10

文章目录 1. 旋转图像【中等】1.1 题目描述1.2 解题思路1.3 代码实现 2. 组合总和【中等】2.1 题目描述2.2 解题思路2.3 代码实现 3. 回文链表【简单】3.1 题目描述3.2 解题思路3.3 代码实现 4. 字符串解码【中等】4.1 题目描述4.2 解题思路4.3 代码实现 5. 多数元素【简单】5.…

高压功率放大器ATA4014VS高压功率放大器HSA42014

高压功率放大器ATA4014VS高压功率放大器HSA42014 一、企业背景&#xff1a; Aigtek是一家来自中国的专业从事测量仪器研发、生产和销售的高科技企业。公司主要研发和生产功率放大器、功率放大器模块、功率信号源、计量校准源等产品。核心团队主要是来自西安交通大学及西北工业大…

ERP系统介绍

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ERP系统概述&#xff1f;1.什么是ERP2.主流ERP系统介绍3.用友ERP4.部署用友ERP畅捷通T6软件系统环境要求4.用友ERP畅捷通T6软件用户管理4.用友ERP畅捷通T6软…

简单的UDP网络程序

目录 准备工作 makefile udpServer.hpp udpServer.cc 细节1 服务端部署 创建套接字 接口认识1 socket 协议家族 绑定套接字 认识接口2 bind sockaddr_in结构体类型 细节2 bzero inet_addr 服务器启动(初启动) udpServer.hpp udpServer.cc 细节3 本地回环通…

跑通NeRF-SLAM代码记录

前言 Install 原文章github链接 下载代码 git clone https://github.com/ToniRV/NeRF-SLAM.git --recurse-submodules git submodule update --init --recursive因为有相关依赖&#xff0c;所以尽量使用命令下载代码。 2. 新建nerf-slam环境&#xff0c;github上也没提到p…

从sftp下载大文件到浏览器

从sftp下载大文件到浏览器 问题方案相关依赖包相关代码片段&#xff08;后端&#xff09;文件信息缓存工具类-FileChunkCache文件信息对象-FileDetailsftp传输进度监控-FileProgressMonitor切片工具类-ChunkService文件下载服务-AsyncDownloadService 问题 近期遇到直接使用sf…