腾讯IM h5版本,在安卓原生和IOS原生支持情况的调查以及踩坑、解决办法

news2024/9/29 17:21:55

介绍

公司准备基于腾讯IM进行开发即时通讯功能,想用H5来开发,这样方便以后移植,在原生app里直接加载,通过三天时间的调查,以及与腾讯客服,技术来回沟通,已经有一定的成果,现将调查成果,以及调查过程中踩过的坑以及解决方法记录一下,希望可以使有相同需求的人少走弯路。

调查h5 demo在浏览器中的支持情况

h5 demo使用的官方提供的体验 Demo,对应的url地址是(https://web.sdk.qcloud.com/im/demo/latest/index.html#/login),我是安卓设备,使用浏览器扫描即时通信IM体验Demo中的H5,在浏览器打开,点击语音通话,视频通话,均提示“权限获取失败:用户禁止使用设备”,但是我浏览器权限是打开的,我还重新下载了一个新的浏览器,也出现同样的问题,iOS会提示获取权限的对话框,授权以后是可以进行音视频通话的。
官方对这一问题的回复是:

您好,关于您的问题,您可以在Android上检测下能力测试,看下浏览器上正常嘛
https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html

我用浏览器加载这个网站,进行测试,结果是
在这里插入图片描述
按理说是不会有问题的,音视频设备都支持,但是确实不行,官方建议让使用chrome,我下载手机版chrome,结果音视频通话都可以,结果在浏览器与应用环境信息找到了答案,
在这里插入图片描述
官方也回应,存在兼容性问题。

调查h5在Android Webview和腾讯TBS浏览器里的情况

既然支持移动版chrome浏览器,我想当然的觉得,安卓原生的Webview应该也是支持的,结果在android程序中加载url,在android Webview中,与文件选取有关的按钮点击没效果,音视频聊天不能使用,在IOS里,视频聊天的摄像头打不开。因为Webview存在很多坑吧,所以我换了TBS浏览器,依然有这个问题,我刚开始怀疑是权限问题,我用代码授权,手动设置,把权限都勾选上,还是有这个问题。同样,用webview加载那个检测地址(https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html),有一个问题,就是摄像头那个,切换0或者1,都看不到摄像头,有可能是这个问题,后边会说。
在这里插入图片描述
在这里插入图片描述

官方给的回应是:

您好,这个demo只能是作为展示,如果是需要内嵌到webview里面的话,需要您业务侧做二次开发哈。不能直接使用这个

好嘛,那就二次开发吧,就进入了编码阶段,跟着文档一步一步来,创建vue项目,集成UI,折腾半天,本地运行起来,用Webview加载本地地址,能访问到网页,但是与文件选取有关的按钮点击没效果,音视频聊天弹出权限获取失败的提示。

官方技术说这个音视频聊天,必须放到https环境里,好嘛,继续折腾。开始踩坑之旅。
坑1 提示检测到您暂未集成TUICallKit
我已经按照文档集成了TUICallKit,也开通了7天免费体验,结果还是报这个错,这个解决的办法是:
到这个页面CDN&音视频通信产品免费试用中心,开启免费试用套餐

在这里插入图片描述
这样才能使用。(那个7天体验期过了,还可以再续7天,我也是不经意发现的)
坑2(大坑) 发布后报Uncaught syntaxError: Unexpected token .
首先,我不是专业做前端的,第二,我们公司现有的项目都是vue 2,官方使用的是vue 3,对vue 3开发的经验不足,我本来以为都一样吧,很自信的发布到服务器,就是很简单的配置一下nginx嘛,结果直接报错,

Uncaught syntaxError: Unexpected token .

注意这个".“号。就寻找出错代码呗,发现出在”?."语法,就找到那行出错的代码,是在“< template>”部分,问公司前端,他说在js里支持这样写,在< template>应该不支持,去掉试试,然后我就一个一个改,把问号去掉,自信满满去试试,结果还是报错,不过这次找不到报错的地方了。然后查百度,各种尝试,我以为是发布问题,可能node.js版本太高,我就删除原来的高版本node.js,然后下载node v14.20.1,结果发布了还是不行。那是不是发布的路径问题,我试了vue.config.js里边publicPath 设置’./‘或者’/'都解决不了问题,进入了死胡同,不知道怎么做了,就尝试找官方技术解决,官方的小哥还挺热情,我就按照他的指导一步一步来,具体如下:

  1. 提供配置配合官方调查

在这里插入图片描述
官方回复:

publicPath release 下设置为 “” 试试

我这边的技术也这样说的,我觉得问题不大,改了试试,结果还是一样,这样基本排除了发布的问题

  1. Uncaught syntaxError: Unexpected token .

继续报这个错,不过这次找到了出错的地方,

在这里插入图片描述
是这段代码。这个是不支持 "?."这样的语法。应该是es6语法不支持,开始配置babel。
vue —— 项目启动时无法识别es6的扩展语法
1.切换淘宝镜像

npm install -g cnpm --registry=http://registry.npm.taobao.org
cnpm install --legacy-peer-deps --save-dev babel-preset-stage-3

cnpm install --legacy-peer-deps --save-dev babel-plugin-transform-object-rest-spread

4.根目录下新增文件 .babelrc

{
    "presets": [
        ["es2015", { "modules": false }]
    ],
    "plugins": ["transform-object-rest-spread"] //不能解析es6语法关键解决
}

5.根目录下新增文件postcss.config.js

module.exports = { 
    plugins: { 
        'autoprefixer': {browsers: 'last 5 version'} 
    } 
}

按照这个配置配置完,编译,会报

Error: Cannot find module ‘babel-preset-es2015‘

解决办法:

npm install babel-plugin-component -D

修改babel.config.js的内容,将es2015改为@babel/preset-env。编译,可能会报这个错误

Cannot read property ‘bindings‘ of null

解决办法
.babelrc (或webpack.config.js)中

presets: ["env"]

修改成

presets: ["@babel/preset-env"]

我这边还安装了一个plugin-proposal-optional-chaining,用来解决let str = a?.b?.c;可选链操作符语法时会出现编译报错的情况,安装babel依赖@babel/plugin-proposal-optional-chaining,并添加babel.config.js中。

module.exports = {
  presets: ['@babel/preset-env'],
  plugins: ["@babel/plugin-proposal-optional-chaining"]
}

发布,之前的错误没有了,又换了一个

在这里插入图片描述
这个是 ?? 语法没有编译,按照这个进行配置
在这里插入图片描述
又出现这个
在这里插入图片描述
然后再引用这个@babel/preset-typescript,@babel/preset-typescript,结果还是报错,按理说不应该的,官方问了一句浏览器版本,我才发现服务器上浏览器版本太低,换成版本高的服务器就好了。
现在我把最终的配置发一下

.babelrc

{
    "presets": [
        "@babel/preset-env","@babel/preset-typescript"
    ],
    "plugins": ["transform-object-rest-spread"] //不能解析es6语法关键解决
}

babel.config.js

module.exports = {
  // presets: [
  //   '@vue/cli-plugin-babel/preset'
  // ]

  presets: [
    "@babel/preset-env","@babel/preset-typescript"
  ],
  plugins: ["@babel/plugin-proposal-optional-chaining"]
}

package.json

{
  "name": "chat-example",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@tencentcloud/call-uikit-vue": "^1.4.2",
    "@tencentcloud/chat-uikit-vue": "^1.3.4",
    "babel-polyfill": "^6.26.0",
    "core-js": "^3.8.3",
    "es6-promise": "^4.2.8",
    "vue": "^3.2.13",
    "vue-class-component": "^8.0.0-0"
  },
  "devDependencies": {
    "@babel/plugin-proposal-optional-chaining": "^7.21.0",
    "@babel/preset-typescript": "^7.21.0",
    "@typescript-eslint/eslint-plugin": "^5.4.0",
    "@typescript-eslint/parser": "^5.4.0",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-plugin-typescript": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "@vue/eslint-config-typescript": "^9.1.0",
    "babel-plugin-component": "^1.1.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-3": "^6.24.1",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "sass": "^1.32.7",
    "sass-loader": "^12.0.0",
    "typescript": "~4.5.5",
    "vconsole": "^3.15.0",
    "vite-plugin-vconsole": "^1.3.1"
  }
}

vue.config.js

const path = require("path");
// const CompressionWebpackPlugin = require("compression-webpack-plugin");
// // 定义压缩文件类型
// const productionGzipExtensions = ["js", "css"];
// let timeStamp = new Date().getTime();

function resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
publicPath: "",// publicPath: process.env.BASE_URL,
 outputDir: `dist`,
 assetsDir: 'static',
  lintOnSave: process.env.NODE_ENV === 'development',
  productionSourceMap: true,
  runtimeCompiler: true,

  devServer: {
    // 设置主机地址
    // 设置默认端口
    port: 8080,
   
  },
  // use the full build with in-browser compiler?
  // https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
  // compiler: false,

  // webpack配置
  // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md   webpack链接API,用于生成和修改webapck配置
  //部署打包html带引号
  chainWebpack: (config) => {
    config.resolve.alias.set("@", resolve("src"));
    config.plugin("html").tap((args) => {
      args[0].minify = false;
      return args;
    });
  },
  //压缩打包文件大小
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === "Production") {
      // config.output.filename = `assets/js/[name].${timeStamp}.js`;
      // config.output.chunkFilename = `assets/js/[name].${timeStamp}.js`;
      config.plugins.push(
        new CompressionWebpackPlugin({
          algorithm: "gzip",
          test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
          threshold: 10240,
          minRatio: 0.8,
        })
      );
    }
    config.externals = {
      //   'vue': 'Vue',
      //   'vuex': 'Vuex',
      //   'vue-router': 'VueRouter',
      //   'element-ui': 'ELEMENT',
      //   'Axios': 'axios',
      //   'jquery': '$',
      //   'moment': 'moment',
      //   'js-cookie': 'Cookies',
      //   'echarts': 'echarts',
      //   'tinymce/tinymce': 'tinymce'
    };
    // }
  },

  // 生产环境是否生成 sourceMap 文件
  productionSourceMap: false,
  
  // 第三方插件配置
  pluginOptions: {
    // ...
  },
};

结论:需要确认下最终产品的目标浏览器版本范围。如果要覆盖低版本(不支持 es6的语法),还是需要配置 babel 进行兼容。否则可以忽略
坑3 安卓Webview不支持选取文件操作
把开发好的vue项目发布到服务器上以后,用Webview加载,还是不能选取文件,官方给的建议是参照Android-WebView-解决对选择文件 input type=“file“无响应 来操作,我试了一下,很容易报错,而且代码很多,也有点老,就在网上找了找,发现一个简单的配置,分享一下

private ValueCallback<Uri> mUploadMessage;
public ValueCallback<Uri[]> uploadMessage;
public static final int REQUEST_SELECT_FILE = 100;
private final static int FILECHOOSER_RESULTCODE = 2;
webview.setWebChromeClient(new WebChromeClient(){
        // For 3.0+ Devices (Start)
        // onActivityResult attached before constructor
        protected void openFileChooser(ValueCallback uploadMsg, String acceptType)
        {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
        }
        // For Lollipop 5.0+ Devices
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
        {
            if (uploadMessage != null) {
                uploadMessage.onReceiveValue(null);
                uploadMessage = null;
            }
            uploadMessage = filePathCallback;
            Intent intent = fileChooserParams.createIntent();
            try
            {
                startActivityForResult(intent, REQUEST_SELECT_FILE);
            } catch (ActivityNotFoundException e)
            {
                uploadMessage = null;
                Toast.makeText(getBaseContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show();
                return false;
            }
            return true;
        }
        //For Android 4.1 only
        protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
        {
            mUploadMessage = uploadMsg;
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("image/*");
            startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
        }
        protected void openFileChooser(ValueCallback<Uri> uploadMsg)
        {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }
    });
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
    {
        if (requestCode == REQUEST_SELECT_FILE)
        {
            if (uploadMessage == null)
                return;
            uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
            uploadMessage = null;
        }
    }
    else if (requestCode == FILECHOOSER_RESULTCODE)
    {
        if (null == mUploadMessage)
            return;
        // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment
        // Use RESULT_OK only if you're implementing WebView inside an Activity
        Uri result = intent == null || resultCode != MainActivity.RESULT_OK ? null : intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    }
    else
        Toast.makeText(getBaseContext(), "Failed to Upload Image", Toast.LENGTH_LONG).show();
}

配置好了就可以选取文件,相册了
坑4 还是不能调起摄像头进行音视频聊天
现在已经按照文档开发集成,并且发布到了外网,但是使用webview加载,音视频聊天还是弹出权限获取失败的提示,我这边检查过,权限都是有的。官方给的回应是

尊敬的用户: 您好,web端SDK只能使用js调用获取摄像头和麦克风的,底层使用的getUserMedia。如果获取不到需要检查app
webview的配置是否正确。上述能力检查页面是否能听到麦克风声音呢。可以用下面原生的HTML Demo测试一下是否能拿到

https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia

这个得修改安卓原生代码了,我这边只是测试一下,所以在首页的时候,已经把摄像头和语音的权限全部授权了

 mWebView.setWebChromeClient(new WebChromeClient() {
        @Override
        public void onPermissionRequest(PermissionRequest request) {
            request.grant(request.getResources());
        }
    });

结论

腾讯im 的h5版本,在原生支持情况,可以通过设置原生代码来适配,而且对浏览器的兼容性要求比较高,最大的问题是即时性问题,因为没有离线推送,不能随时把控进度,不能离开页面。比如用户需要排队,等待与医生聊天,就可能会长时间停留在网页,不能退出,退出不能及时收到消息。
所以是否选择h5 im,需要根据公司成本和需求来吧。原生app对接Im sdk是最好的,但是需要安装app,剩下的是小程序,我现在把调研的小程序和h5的区别分享一下
在这里插入图片描述
在这里插入图片描述

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

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

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

相关文章

【Redis】Redis持久化(一)

目录 1.Redis持久化 1.1.RDB持久化 1.1.1.执行时机 1.1.2.RDB原理 1.1.3.小结 1.2.AOF持久化 1.2.1.AOF原理 1.2.2.AOF配置 1.2.3.AOF文件重写 1.3.RDB与AOF对比 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化 AOF持久化 1.1.RDB持久化 RDB全称Red…

SpringBoot - 什么是跨域?如何解决跨域?

什么是跨域&#xff1f; 在浏览器上当前访问的网站&#xff0c;向另一个网站发送请求&#xff0c;用于获取数据的过程就是跨域请求。 跨域&#xff0c;是浏览器的同源策略决定的&#xff0c;是一个重要的浏览器安全策略&#xff0c;用于限制一个 origin 的文档或者它加载的脚本…

Doris集成其他系统——ODBC外部表

Doris集成其他系统——ODBC外部表 文章目录Doris集成其他系统——ODBC外部表0. 写在前面1. 正文1.1 ODBC外部表介绍1.2 使用方式2. 使用 ODBC 的 MySQL 外表2.1 前置芝士2.2 安装 unixODBC2.3 安装MySQL 对应版本的 ODBC2.3.1 安装方式2.3.2 检查安装结果2.3.3 其他节点的安装及…

prometheus 配置服务器监控、服务监控、容器中服务监控与告警

最近公司有几个服务遇到了瓶颈&#xff0c;也就是数据量增加了&#xff0c;没有人发现&#xff0c;这不是缺少一个监控服务和告警的系统吗&#xff1f; 主要需求是监控每个服务&#xff0c;顺带监控一下服务器和一些中间件&#xff0c;这里采集的2种&#xff0c;zabbix和prom…

Kafka 消费者组

Kafka 消费者组Consumer数位移重平衡消费者组 (Consumer Group) : 可扩展且容错性的消费者机制 一个组内可能有多个消费者 (Consumer Instance) : 共享一个公共 ID (Group ID)组内的所有消费者协调消费订阅主题 (Subscribed Topics) 的所有分区 (Partition)每个分区只能由同个…

【1】熟悉刷题平台操作

TestBench使用 与quartus中testbench的写法有些许。或者说这是平台特有的特性&#xff01;&#xff01; 1 平台使用谨记 &#xff08;1&#xff09;必须删除&#xff1a;若设计为组合逻辑&#xff0c;需将自动生成的clk删除 若不删除&#xff0c;会提示运行超时错误。 &#…

git推送指定的提交到远程分支详细方法

默认情况下&#xff0c;git push会推送暂存区所有提交&#xff08;也即HEAD及其之前的提交&#xff09;到远程库&#xff0c;实际开发中可能因为功能没有开发完成&#xff0c;但是又必须提交部分修改好的代码就需要用到推送指定commit到远程分支。第一种方式&#xff1a;即符合…

CSS流动布局-页面自适应

项目中经常会碰到页面自适应的问题&#xff0c;例如&#xff1a;商城的列表展示、分类列表展示等页面&#xff0c;如下&#xff1a; 该页面会随着页面的放大缩小而随之发生变化&#xff0c;这种自适应的页面布局在大屏幕、小屏幕、不同的浏览器设备上都应该呈现出与设计匹配的…

【STM32MP157应用编程】4.串口接收、发送数据

目录 串口文件 指令操作串口 程序操作串口 程序说明 程序代码 4_ChuanKou_2.c 启动交叉编译工具 编译 拷贝到开发板 测试 串口文件 在/dev目录下&#xff0c;存放了串口的文件。 文件名对应的串口ttySTM0CH340ttySTM1com2&#xff08;公头&#xff09;ttySTM2com1&a…

java版云HIS系统源码 微服务架构支持VUE

云his系统源码 一个好的HIS系统&#xff0c;要具有开放性&#xff0c;便于扩展升级&#xff0c;增加新的功能模块&#xff0c;支撑好医院的业务的拓展&#xff0c;而且可以反过来给医院赋能&#xff0c;最终向更多的患者提供更好地服务。 私信了解更多&#xff01; 本套基于…

【C语言】结构体和共用体

目录一、结构体&#xff08;一&#xff09;结构体声明&#xff08;二&#xff09;结构体变量定义&#xff08;三&#xff09;结构体变量的初始化&#xff08;四&#xff09;结构体的引用&#xff08;五&#xff09;结构体数组二、共用体&#xff08;一&#xff09;共用体定义&a…

全球商城库存系统架构设计与实践

业务背景 商城原库存系统耦合在商品系统&#xff0c;考虑到相关业务逻辑复杂度越来越高&#xff0c;库存做了服务拆分&#xff0c;在可售库存管理的基础上新增了实物库存管理、秒杀库存、物流时效 、发货限制、分仓管理等功能&#xff0c;满足了商城库存相关业务需求。 系统架构…

GitLab 存储型XSS漏洞 (CVE-2023-0050)

漏洞描述 GitLab 是由GitLab公司开发的、基于Git的集成软件开发平台。kroki是一款集成在GitLab的基于文本的图表描述自动转为图片的开源工具&#xff0c;在GitLab 13.7引入。 由于Kroki中lib/banzai/filter/kroki_filter.rb对接收的image_src过滤不严&#xff0c;具有AsciiDo…

SpringBoot 整合 clickhouse和mysql 手把手教程全网最详细

最近做一个项目 需要 整合mysql clickhouse 多数据源 后台用的是ruoyi框架 1. 首先pom引入相关依赖 <!--JDBC-clickhouse数据库--><dependency><groupId>com.clickhouse</groupId><artifactId>clickhouse-jdbc</artifactId><version&…

Homekit智能家居产品---智能吸顶灯

买灯要看什么因素 好灯具的灯光可以说是家居的“魔术师”&#xff0c;除了实用的照明功能外&#xff0c;对细节的把控也非常到位。那么该如何选到一款各方面合适的灯呢&#xff1f; 照度 可以简单理解为清晰度&#xff0c;复杂点套公式来说照度光通量&#xff08;亮度&#x…

5款小巧好用的电脑软件,让你的工作生活更加高效!

不得不说良心好软件让大家好评连连&#xff0c;爱不释手&#xff0c;不像某些软件自带广告弹窗。这期就由我给大家安利几款电脑中的得力助手&#xff0c;看看你都用过几个&#xff1f; 1.桌面管理神器——Coodesker Coodesker是一款免费小巧、无广告&#xff0c;功能简单的桌…

【Redis】哨兵机制(三)

目录 3.Redis哨兵 3.1.哨兵原理 3.1.1.集群结构和作用 3.1.2.集群监控原理 3.1.3.集群故障恢复原理 3.1.4.小结 3.2.搭建哨兵集群 3.3.RedisTemplate 3.3.1.导入Demo工程 3.3.2.引入依赖 3.3.3.配置Redis地址 3.3.4.配置读写分离 3.Redis哨兵 Redis提供了哨兵&am…

Spring Cloud Gateway学习

文章大纲 为什么需要网关&#xff1f; 传统的单体架构只有一个服务开放给客户端调用&#xff0c;但是在微服务架构体系中是将一个系统拆分成多个微服务&#xff0c;那么作为客户端如何去调用这些微服务呢&#xff1f;如果没有网关的存在&#xff0c;就只能在本地记录每个微服务…

彻底关闭Windows10更新!!

以下四个步骤都需要执行。 一、禁用Windows Update服务 1、同时按下键盘 Win R&#xff0c;然后输入 services.msc &#xff0c;点击确定。 2、找到 Windows Update 这一项&#xff0c;并双击打开。 3、双击打开它&#xff0c;点击 停止&#xff0c;把启动类型选为 禁用&…

SpringBoot+@Async注解-异步调用

编程开发里&#xff0c;使用java异步执行方法可以让程序同时处理多个请求业务&#xff0c;提升吞吐量来缩短业务的执行时间&#xff0c;在springboot的程序应用中&#xff0c;提供了Async注解来实现异步执行方法。在业务开发中&#xff0c;有些时候是不需要立即返回业务的处理结…