flutter开发实战-dio文件下载实现

news2024/11/24 20:53:18

flutter开发实战-dio文件下载实现

在开发中,需要下载文件,这里使用的是dio
dio 是一个强大的 Dart HTTP 请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时以及自定义适配器等。

在这里插入图片描述

一、引入dio

在工程中pubspec.yaml引入dio

dio: ^5.1.1
  dio_cookie_manager: ^3.0.0

二、代码实现

我们对dio进行封装

// 定义枚举,请求方法枚举
enum HttpApiMethod {
  GET,
  POST,
  DELETE,
  PUT,
}

// 网络请求的成功与失败
// 上传
typedef OnUploaded = void Function(Map<String, dynamic> result);
// 下载进度
typedef OnDownloadProgress = void Function(int count, int total);
// 下载成功
typedef OnDownloaded = void Function();
// 请求成功
typedef OnSuccess = void Function(ResponseData responseData);
// 请求失败
typedef OnFailure = void Function(ApiHttpError error);

// 请求Api
class HttpApi {
  // 网络请求库dio
  Dio dio = Dio(BaseOptions(
    // connectTimeout: 60000, // 连接服务器超时时间,单位是毫秒.
    // receiveTimeout: 10000, // 响应流上前后两次接受到数据的间隔,单位为毫秒, 这并不是接收数据的总时限
    headers: {
      HttpHeaders.acceptHeader: "text/plain,"
          "text/plain,"
          "multipart/form-data,"
          "application/json,"
          "text/html,"
          "image/jpeg,"
          "image/png,"
          "application/octet-stream,"
          "text/json,"
          "text/javascript,"
          "text/html",
    },
  ));

  // 私有构造函数
  HttpApi._internal();

  //保存单例
  static HttpApi _singleton = HttpApi._internal();

  //工厂构造函数
  factory HttpApi() => _singleton;

  /// 配置请求头header
  ///   /// The request Content-Type. The default value is 'application/json; charset=utf-8'.
  //   /// If you want to encode request body with 'application/x-www-form-urlencoded',
  //   /// you can set [Headers.formUrlEncodedContentType], and [Dio]
  //   /// will automatically encode the request body.
  Future<void> configHeaders(
      String requestUrl, Map<String, dynamic>? params) async {

    dio.options.headers['Content-Type'] = Headers.jsonContentType;

    LoggerManager().info(
        "requestUrl:${requestUrl} dio.options.headers:${dio.options.headers}");
  }

  get(String url, ApiServiceDomain serviceDomain,
      {Map<String, dynamic>? params, OnSuccess? success, OnFailure? failure}) {
    doRequest(url, serviceDomain, HttpApiMethod.GET,
        params: params, success: success, failure: failure);
  }

  post(String url, ApiServiceDomain serviceDomain,
      {Map<String, dynamic>? params, OnSuccess? success, OnFailure? failure}) {
    doRequest(url, serviceDomain, HttpApiMethod.POST,
        params: params, success: success, failure: failure);
  }

  // 请求服务器
  // params,参数
  // 请求成功
  // 请求失败
  Future<void> doRequest(
      String url, ApiServiceDomain serviceDomain, HttpApiMethod method,
      {Map<String, dynamic>? params,
      OnSuccess? success,
      OnFailure? failure}) async {
    String requestUrl = getRequestUrl(url, serviceDomain);

    try {
      /// 可以添加header
      await configHeaders(requestUrl, params);
      Response? response;
      switch (method) {
        case HttpApiMethod.GET:
          {
            // get请求
            if (params != null && params.isNotEmpty) {
              response = await dio.get(requestUrl,
                  queryParameters: params,
                  options: Options(contentType: Headers.jsonContentType));
              LoggerManager()
                  .debug("await dio.get response:$response,params:$params");
            } else {
              response = await dio.get(requestUrl,
                  options: Options(contentType: Headers.jsonContentType));
            }
            break;
          }
        case HttpApiMethod.POST:
          {
            // post请求
            String? contentType = Headers.formUrlEncodedContentType;
            if (params != null && params.isNotEmpty) {
              response = await dio.post(requestUrl,
                  data: params, options: Options(contentType: contentType));
              LoggerManager()
                  .debug("await dio.post response:$response,params:$params");
            } else {
              response = await dio.post(requestUrl,
                  options: Options(contentType: contentType));
            }
            break;
          }
        // case HttpApiMethod.PUT: {
        //   break;
        // }
        // case HttpApiMethod.DELETE: {
        //   break;
        // }
        default:
      }
      LoggerManager().debug('doRequest: $response, params:$params');

      if (response != null) {
        Map<String, dynamic> result = json.decode(response.toString());
        assert(() {
          // assert只会在debug模式下执行,release模式下不会执行
          // 打印信息
          LoggerManager().debug('''api: $requestUrl\nresult: $result''');
          return true;
        }());

        ResponseData responseData = ResponseData.fromJson(result);
        if (responseData.status == 0) {
          if (success != null) {
            //返回请求数据
            success(responseData);
          }
        } else {
          //返回失败信息
          ApiHttpError apiHttpError = getErrorRequestResponseData(responseData);

          LoggerManager().debug("apiHttpError:${apiHttpError.toString()}");

          LoggerManager().error('''api: $requestUrl\nresult: $result''');

          if (failure != null) {
            failure(apiHttpError);
          }
        }
      } else {
        // 没有获得response,failure
        ApiHttpError apiHttpError =
            ApiHttpError(ApiHttpErrorType.Default, "请求失败!");

        LoggerManager().debug("apiHttpError:${apiHttpError.toString()}");

        if (failure != null) {
          failure(apiHttpError);
        }
      }
    } on DioError catch (e, s) {
      // catch到异常,failure
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx and is also not 304.
      LoggerManager()
          .error("doRequest api: $requestUrl, dioError:${e.message}, s:$s");
      ApiHttpError apiHttpError = getRequestFailure(e.response, e.type);

      LoggerManager().debug("apiHttpError:${apiHttpError.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    } catch (e) {
      // 可以捕获任意异常
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, "${e.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    }
  }

  // 上传文件(图片)
  doUploadFile(String url, UploadFileInfo fileInfo,
      {Map<String, dynamic>? params,
      OnUploaded? uploaded,
      OnFailure? failure}) async {
    try {
      String timeStamp = DateTime.now().millisecondsSinceEpoch.toString();
      Map<String, dynamic> fromParams = Map();
      if (params != null && params.isNotEmpty) {
        fromParams.addAll(params);
      }

      fromParams["file"] = await MultipartFile.fromFile(fileInfo.file.path,
          filename: '${fileInfo.key}-${timeStamp}.jpg');

      FormData formData = FormData.fromMap(fromParams);
      Response? response = await dio.post(url, data: formData);
      assert(() {
        // assert只会在debug模式下执行,release模式下不会执行
        // 打印信息
        LoggerManager().error('''api: $url\nresult: $response''');
        return true;
      }());

      if (response != null) {
        Map<String, dynamic> result = json.decode(response.toString());
        assert(() {
          // assert只会在debug模式下执行,release模式下不会执行
          // 打印信息
          LoggerManager().debug('''api: $url\nresult: $result''');
          return true;
        }());

        if (response.statusCode == 200) {
          if (uploaded != null) {
            uploaded(result);
          }
        } else {
          //返回失败信息
          LoggerManager().error('''api: $url\nresult: $result''');

          ApiHttpError apiHttpError =
              ApiHttpError(ApiHttpErrorType.Default, "请求失败!");

          if (failure != null) {
            failure(apiHttpError);
          }
        }
      } else {
        //返回失败信息
        // 没有获得response,failure
        ApiHttpError apiHttpError =
            ApiHttpError(ApiHttpErrorType.Default, "请求失败!");

        if (failure != null) {
          failure(apiHttpError);
        }
      }
    } on DioError catch (e, s) {
      // catch到异常,failure
      LoggerManager().error("doUploadFile api: $url, dioError:$e, s:$s");
      ApiHttpError apiHttpError = getRequestFailure(e.response, e.type);

      if (failure != null) {
        failure(apiHttpError);
      }
    } catch (e) {
      // 可以捕获任意异常
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, "${e.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    }
  }

  // 下载文件
  void doDownload(String url, String savePath,
      {required CancelToken cancelToken,
      Map<String, dynamic>? params,
      dynamic? data,
      Options? options,
      OnDownloadProgress? progress,
      OnDownloaded? completion,
      OnFailure? failure}) async {
    try {
      dio.download(
        url,
        savePath,
        queryParameters: params,
        cancelToken: cancelToken,
        onReceiveProgress: (int count, int total) {
          if (total != -1) {
            if (!cancelToken.isCancelled) {
              double downloadRatio = (count / total);
              if (downloadRatio == 1) {
                if (completion != null) {
                  completion();
                }
              } else {
                if (progress != null) {
                  progress(count, total);
                }
              }
            }
          } else {
            ApiHttpError apiHttpError =
                ApiHttpError(ApiHttpErrorType.Default, "无法获取文件大小,下载失败!");

            if (failure != null) {
              failure(apiHttpError);
            }
          }
        },
      );
    } on DioError catch (e) {
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, e.toString());
      if (CancelToken.isCancel(e)) {
        apiHttpError = ApiHttpError(ApiHttpErrorType.Cancel, "下载已取消!");
      } else {
        if (e.response != null) {
          apiHttpError = getRequestFailure(e.response, e.type);
        } else {
          apiHttpError = ApiHttpError(ApiHttpErrorType.Default, e.message??"");
        }
      }

      if (failure != null) {
        failure(apiHttpError);
      }
    } on Exception catch (e) {
      // EasyLoading.showError(e.toString());
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, e.toString());

      if (failure != null) {
        failure(apiHttpError);
      }
    } catch (e) {
      // 可以捕获任意异常
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, "${e.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    }
  }

  // 根据服务器来拼接服务器具体地址
  String getRequestUrl(String url, ApiServiceDomain serviceDomain) {
    String requestUrl = url;
    return requestUrl;
  }

  ApiHttpError getErrorRequestResponseData(ResponseData responseData) {
    //返回失败信息
    ApiHttpError apiHttpError =
        ApiHttpError(ApiHttpErrorType.Default, responseData.errorMsg);

    if (kNeedAuthLoginErrorCode == responseData.errorCode) {
      apiHttpError = ApiHttpError(ApiHttpErrorType.Auth, responseData.errorMsg);
    }

    return apiHttpError;
  }

  ApiHttpError getRequestFailure(
      Response? response, DioErrorType dioErrorType) {
    LoggerManager()
        .error("getRequestFailure: $response, dioError:$dioErrorType");

    ApiHttpErrorType errorType = ApiHttpErrorType.Default;
    String errorMessage = "请求失败!";

    if (response != null) {
      if (dioErrorType == DioErrorType.connectionTimeout) {
        errorType = ApiHttpErrorType.NetWork;
        errorMessage = "网络链接异常!";
      } else if (dioErrorType == DioErrorType.sendTimeout) {
        errorType = ApiHttpErrorType.Timeout;
        errorMessage = "网络链接异常!";
      } else if (dioErrorType == DioErrorType.receiveTimeout) {
        errorType = ApiHttpErrorType.Timeout;
        errorMessage = "网络链接异常!";
      } else if (dioErrorType == DioErrorType.badResponse) {
        // When the server response, but with a incorrect status, such as 404, 503...
        if (response != null) {
          if (response.statusCode == 401) {
            errorType = ApiHttpErrorType.Auth;
            errorMessage = "认证失败!";
          } else if (response.statusCode == 400) {
            errorType = ApiHttpErrorType.BadRequest;
            errorMessage = "无效请求!";
          } else if (response.statusCode == 404) {
            errorType = ApiHttpErrorType.NotFound;
            errorMessage = "访问的资源丢失了!";
          } else if (response.statusCode == 405) {
            // 请求的方法错误
            errorType = ApiHttpErrorType.BadParamHeader;
            errorMessage = "参数出错!";
          } else if (response.statusCode! >= 500) {
            errorType = ApiHttpErrorType.BadRequest;
            errorMessage = "服务器居然累倒了!";
          }
        }
      } else if (dioErrorType == DioErrorType.cancel) {
        errorType = ApiHttpErrorType.Cancel;
        errorMessage = "请求已经取消";
      }
    } else {
      errorType = ApiHttpErrorType.NetWork;
      errorMessage = "网络链接异常!";
    }

    ApiHttpError apiHttpError = ApiHttpError(errorType, errorMessage);
    return apiHttpError;
  }
}

/// 上传的文件类
class UploadFileInfo {
  File file;
  String key;

  UploadFileInfo({required this.file, required this.key});
}

文件下载页面实现实例

class DownloadPage extends StatefulWidget {
  const DownloadPage({
    Key? key,
    this.messages,
    this.uniqueId,
    this.arguments,
  }) : super(key: key);

  final Object? arguments;
  final String? messages;
  final String? uniqueId;

  
  State<DownloadPage> createState() => _DownloadPageState();
}

class _DownloadPageState extends State<DownloadPage> {
  String _downloadPath =
      'https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg';
  double _downloadRatio = 0.0;
  String _downloadIndicator = '0.00%';
  late String _destPath;
  late CancelToken _token;
  bool _downloading = false;

  
  void initState() {
    getTemporaryDirectory()
        .then((tempDir) => {_destPath = tempDir.path + 'googlechrome.dmg'});

    super.initState();
  }

  void _downloadFile() {
    if (_downloading == true) {
      return;
    }
    _token = CancelToken();
    _downloading = true;
    HttpApi().doDownload(_downloadPath, _destPath, cancelToken: _token,
        progress: (int received, int total) {
      // 下载进度
      setState(() {
        _downloadRatio = (received / total);
        if (_downloadRatio == 1) {
          _downloading = false;
        }
        _downloadIndicator = (_downloadRatio * 100).toStringAsFixed(2) + '%';
      });
    }, completion: () {
      // 下载成功
      _downloading = false;
      FlutterLoadingHud.showToast(message: "\"下载完成\"");
    }, failure: (error) {
      // 下载出错
      _downloading = false;
      FlutterLoadingHud.showToast(message: error.message);
    });
  }

  void _cancelDownload() {
    if (_downloadRatio < 1.0) {
      _token.cancel();
      _downloading = false;
      setState(() {
        _downloadRatio = 0;
        _downloadIndicator = '0.00%';
      });
    }
  }

  void _deleteFile() {
    try {
      File downloadedFile = File(_destPath);
      if (downloadedFile.existsSync()) {
        downloadedFile.delete();
      } else {
        FlutterLoadingHud.showToast(message: "文件不存在");
      }
    } catch (e) {
      FlutterLoadingHud.showToast(
          message: "${e.toString()}");
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        leading: AppBarIconButton(
          icon: Icon(Icons.arrow_back_ios),
          onPressed: () => {NavigatorRoute.pop()},
        ),
        centerTitle: true,
        backgroundColor: ColorUtil.hexColor(0xffffff),
        foregroundColor: ColorUtil.hexColor(0x777777),
        elevation: 0,
        title: Text(
          "下载示例",
          textAlign: TextAlign.center,
          softWrap: true,
          style: TextStyle(
            fontSize: 17,
            color: ColorUtil.hexColor(0x333333),
            fontWeight: FontWeight.w600,
            fontStyle: FontStyle.normal,
            decoration: TextDecoration.none,
          ),
        ),
        shadowColor: ColorUtil.hexColor(0xffffff),
        toolbarHeight: 44.0,
        bottomOpacity: 0.0,
      ),
      body: Container(
        color: ColorUtil.hexColor(0xf7f7f7),
        alignment: Alignment.center,
        padding: EdgeInsets.all(10),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Row(
              children: [
                _buildDownloadButton(),
                TextButton(
                  child: Text('取消'),
                  onPressed: () {
                    _cancelDownload();
                  },
                ),
                TextButton(
                  child: Text(
                    '删除',
                    style: TextStyle(
                        color: !_downloading ? Colors.red : Colors.grey),
                  ),
                  onPressed: (!_downloading ? _deleteFile : null),
                  style: ButtonStyle(),
                ),
              ],
            ),
            SizedBox(
              height: 25,
            ),
            Row(children: [
              Expanded(
                child: LinearProgressIndicator(
                  backgroundColor: Colors.grey[600],
                  value: _downloadRatio,
                ),
              ),
              SizedBox(
                width: 5,
              ),
              Text(
                _downloadIndicator,
                style: TextStyle(color: Colors.black, fontSize: 12.0),
              ),
            ]),
          ],
        ),
      ),
    );
  }

  Widget _buildDownloadButton() {
    return ButtonWidget(
      onPressed: () {
        _downloadFile();
      },
      child: Text(
        "下载文件",
        textAlign: TextAlign.center,
        softWrap: true,
        style: TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.w400,
          fontStyle: FontStyle.normal,
          color: ColorUtil.hexColor(0xffffff),
          decoration: TextDecoration.none,
        ),
      ),
      height: 40,
      width: 100.0,
      highlightedColor: ColorUtil.hexColor(0xff462e),
      bgColor: ColorUtil.hexColor(0xff462e),
      bgHighlightedColor: ColorUtil.hexColor(0xff462e, alpha: 0.75),
      enabled: true,
      bgDisableColor: Colors.grey,
      borderRadius: 22.0,
    );
  }
}

三、小结

flutter开发实战-dio文件下载实现,封装dio下载功能,实现文件下载

学习记录,每天不停进步。

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

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

相关文章

apache下载

Apache VS17 binaries and modules download php下载地址 PHP For Windows: Home windows.php.net - /downloads/releases/archives/ 历史版本下载 php下载 https://windows.php.net/downloads/releases/archives/php-5.6.37-Win32-VC11-x64.zip https://www.apachehaus.com/…

代码随想录day14

这里推荐这三道题先熟悉二叉树的三种遍历方式 144. 二叉树的前序遍历&#xff08;中->左->右) 根左右。前序遍历首先访问根结点然后遍历左子树&#xff0c;最后遍历右子树。在遍历左、右子树时&#xff0c;仍然先访问根节点&#xff0c;然后遍历左子树&#xff0c;最后…

如何在云中实现安全与合规的规模化?亚马逊云科技给出了答案

在亚马逊云科技&#xff0c;为满足客户不断变化的需求&#xff0c;亚马逊云科技持续创新与迭代&#xff0c;设计的服务能帮助客户满足最严格的安全和合规性要求。针对安全相关工作&#xff0c;亚马逊云科技服务团队与Amazon Security Guardians云守护者项目密切配合&#xff0c…

【计算机视觉】80 TB!58.5 亿!世界第一大规模公开图文数据集 LAION-5B 解读

文章目录 一、导读二、数据集背景信息2.1 图文对数据集2.2 图像数据集 三、LAION-5B有什么3.1 子集3.2 开源模型3.3 KNN index/web界面 四、LAION可以做什么任务4.1 图文匹配及多模态预训练4.2 生成任务4.3 分类任务4.4 其他任务 五、总结 一、导读 继去年 LAION-400M 这个史上…

Android Jetpack Compose多平台用于Android和IOS

Android Jetpack Compose多平台用于Android和IOS JetBrains和外部开源贡献者已经努力工作了几年时间来开发Compose Multiplatform&#xff0c;并最近发布了适用于iOS的Alpha版本。自然地&#xff0c;我们对其功能进行了测试&#xff0c;并决定通过使用该框架在iOS上运行我们的…

leetcode47. 全排列 II 回溯剪枝的细节问题

题目描述&#xff1a; 1、思路 作为回溯算法的经典问题&#xff0c;常用的方法是&#xff0c;每次dfs前先判断是否达到临界条件&#xff0c;满足条件则加入结果集并return。通过循环和dfs来构建树&#xff0c;查找出全部满足条件的集合。 例如本题&#xff0c;如1&…

dp动态规划详解下

dp动态规划详解上&#xff1a;https://blog.csdn.net/weixin_73936404/article/details/131527247?spm1001.2014.3001.5501 目录 dp动态规划详解上&#xff1a;https://blog.csdn.net/weixin_73936404/article/details/131527247?spm1001.2014.3001.5501 【案例1】 【题目描…

强度(极限强度、屈服强度)、韧性材料与脆性材料(韧性材料与脆性材料的强度、为什么脆性材料在压缩时比在拉伸时更坚固?)、材料的延展性、韧性、弹性

1.强度 强度&#xff08;Strength&#xff09;是材料可以承受的应力的量度&#xff0c;通常使用极限强度&#xff08;Ultimate strength&#xff09;和屈服强度&#xff08;Yield strength&#xff09;来定义材料的强度-极限。 材料的极限抗拉强度定义为在拉伸试验过程中达到的…

windows 编译libyuv

一、libyuv下载 git clone https://chromium.googlesource.com/external/libyuv 二、libjpeg-turbo下 git clone https://github.com/libjpeg-turbo/libjpeg-turbo.git 三、编译可以参考 BUILDING.md 需要环境&#xff1a; VS2019 CMake YASM 启动vs工具 编译&#xff1…

js模块化开发

◼ 到底什么是模块化、模块化开发呢&#xff1f;  事实上模块化开发最终的目的是将程序划分成一个个小的结构&#xff1b;  这个结构中编写属于自己的逻辑代码&#xff0c;有自己的作用域&#xff0c;定义变量名词时不会影响到其他的结构&#xff1b;  这个结构可以将自己…

SAP S4 Hana 下面ACDOCA 凭证行字段增强创建过程

网上找到这个类下面是可以新增增强的 现在需要在如下位置建立四代增强点 保存以后会出现下面的增强项 保存激活后&#xff0c;完成在源程序中增加了一个4代显式增强点.上面步骤只是在程序中建立了一个增强点&#xff0c;并没有执行什么动作&#xff0c;就相当于建立一个容器。如…

Python将Excel数字对应列的字母写成字典(json)

在日常的办公中&#xff0c;我们经常需要利用python去读写Excel类型的文件&#xff0c;有时候我们需要将每个数字代表的列的字母表现出来&#xff0c;那么我们可以利用Python实现&#xff0c;而且今天的代码可以根据自己的需求去任意的改变你想规定的长度 如,或者更长 a {1:…

1分钟搭建VPN服务器

1分钟搭建一个VPN服务器 VPN技术在保障网络通信安全和隐私上发挥着重要作用。IPsec VPN是其中一种常用的VPN模式。本文将介绍如何通过使用Docker来快速搭建IPsec VPN Server。 什么是IPsec VPN&#xff1f; IPsec即Internet Protocol Security&#xff0c;是一种用于保护互联…

「2024」预备研究生mem- 形式逻辑强化:逻辑的特殊文字表述方式(重点记忆)

一、形式逻辑强化&#xff1a;逻辑的特殊文字表述方式 二、课后题

关于torch.load报出找不到模型的错误,但路径明明正确

后来发现是因为使用 torch.save(model,save.pt) 会保存整个文件时会默认保存训练py文件的父目录&#xff0c;用torch.load导入文件时搜索路径必须有此父路径&#xff0c;否则将会提示no model named model这样的错误 解决办法是使用sys.path.apend把该父目录加入搜索路径中 …

Java开发 - 探寻Spring的秘密

前言 Spring是企业级J2EE一站式解决方案&#xff0c;提供了整个项目的表现层、业务层、持久层&#xff0c;而且&#xff0c;它的生态特别完善&#xff0c;可以和其他框架无缝对接&#xff0c;现在做Java的哪个项目里没有Spring的说出不去都不信。但往往我们开发者只重视项目是…

重定向:电商行业打败对手的杀手锏

重定向是一种在线营销策略&#xff0c;针对对产品或服务表示兴趣的潜在客户。可以追踪那些访问过您的网站、但未进行过消费的用户&#xff0c;再次向他们展示相关产品&#xff0c;激起消费欲。再营销则是可以追踪那些将商品加入网页购物车&#xff0c;但最后没有购买的用户&…

物流RFID设备一般在哪些场景应用?

随着现代物流行业的快速发展&#xff0c;传统条码技术信息量少&#xff0c;易脏污损毁&#xff0c;耐用性不高等问题很难满足物流企业多样化的需求&#xff0c;物流RFID设备的应用也越来越广泛。下面我们就跟大家一起来分析一下&#xff0c;物流RFID设备可以在哪些场景中应用。…

计算机体系结构基础知识介绍之动态调度(三)

首先回顾一下tomasulo算法&#xff0c; Tomasulo算法的第一个优点是分布式的冒险检测逻辑&#xff0c;这是通过使用预留站和公共数据总线实现的。预留站是一种存储指令和操作数的缓冲区&#xff0c;每个功能单元都有自己的预留站。公共数据总线是一种广播结果的方式&#xff0…

SpringBoot07:Thymeleaf模板引擎

目录 一、Thymeleaf 1、模板引擎 2、引入Thymeleaf 3、Thymeleaf分析 二、测试 1、编写一个TestController 2、编写一个测试页面welcome.html放在templates目录下 3、启动项目请求测试 三、Thymeleaf语法学习 1、修改测试请求&#xff0c;增加数据传输 2、要使用thy…