如何制作安装包打包软件

news/2025/2/25 7:53:23

实现原理

本质就是将exe所需的所有资源制作为一个自解压文件(SFX)。

打包软件

本体

tauri+rust做配置界面

  • 打包文件夹
  • 界面方式(本地文件-单页面应用/网址)
  • 起始界面(资源路径)
  • pip(可新增)
  • install(进度回调)
  • complete(选项设置-快捷方式)

打包自解压

使用rust打包

[
dependencies
]
flate2 = { version = "1.0", features = ["zlib"] }
walkdir = "2.3"
tempfile = "3.3"
std = { version = "1.0", features = ["fs", "path", "process"] }
  • flate2:用于文件的压缩和解压缩操作,它提供了对 zlib 等压缩算法的支持。
  • walkdir:方便遍历目录及其子目录中的所有文件。
  • tempfile:用于创建临时目录和解压文件。
rust">use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::Path;
use tempfile::tempdir;
use walkdir::WalkDir;

// 压缩文件或目录
fn compress_files(source_paths: &[&str], output_path: &str) -> io::Result<()> {
    let output_file = File::create(output_path)?;
    let mut encoder = ZlibEncoder::new(BufWriter::new(output_file), Compression::default());

    for source_path in source_paths {
        for entry in WalkDir::new(source_path) {
            let entry = entry?;
            if entry.file_type().is_file() {
                let mut file = File::open(entry.path())?;
                let mut buffer = Vec::new();
                file.read_to_end(&mut buffer)?;

                // 写入文件名长度和文件名
                let file_name = entry.path().to_str().unwrap();
                encoder.write_all(&(file_name.len() as u32).to_be_bytes())?;
                encoder.write_all(file_name.as_bytes())?;

                // 写入文件内容长度和文件内容
                encoder.write_all(&(buffer.len() as u32).to_be_bytes())?;
                encoder.write_all(&buffer)?;
            }
        }
    }

    encoder.finish()?;
    Ok(())
}

// 解压文件
fn decompress_files(compressed_path: &str, destination_dir: &Path) -> io::Result<()> {
    let compressed_file = File::open(compressed_path)?;
    let mut decoder = flate2::read::ZlibDecoder::new(BufReader::new(compressed_file));

    loop {
        let mut name_length_bytes = [0; 4];
        if decoder.read_exact(&mut name_length_bytes).is_err() {
            break;
        }
        let name_length = u32::from_be_bytes(name_length_bytes) as usize;

        let mut name_bytes = vec![0; name_length];
        decoder.read_exact(&mut name_bytes)?;
        let file_name = String::from_utf8_lossy(&name_bytes);

        let mut content_length_bytes = [0; 4];
        decoder.read_exact(&mut content_length_bytes)?;
        let content_length = u32::from_be_bytes(content_length_bytes) as usize;

        let mut content_bytes = vec![0; content_length];
        decoder.read_exact(&mut content_bytes)?;

        let output_path = destination_dir.join(&file_name);
        fs::create_dir_all(output_path.parent().unwrap())?;
        let mut output_file = File::create(output_path)?;
        output_file.write_all(&content_bytes)?;
    }

    Ok(())
}

// 执行程序
fn execute_program(program_path: &Path) -> io::Result<()> {
    std::process::Command::new(program_path).spawn()?.wait()?;
    Ok(())
}

fn main() -> io::Result<()> {
    // 要压缩的文件或目录
    let source_paths = ["main.exe", "example.dll", "config.ini"];
    let compressed_file_path = "compressed_data.zlib";

    // 压缩文件
    compress_files(&source_paths, compressed_file_path)?;

    // 创建临时目录
    let temp_dir = tempdir()?;
    let temp_dir_path = temp_dir.path();

    // 解压文件
    decompress_files(compressed_file_path, temp_dir_path)?;

    // 执行主程序
    let main_program_path = temp_dir_path.join("main.exe");
    execute_program(&main_program_path)?;

    // 删除压缩文件
    fs::remove_file(compressed_file_path)?;

    Ok(())
}
  • compress_files 函数:
    • 遍历指定的文件和目录,使用 flate2 库的 ZlibEncoder 进行压缩。
    • 对于每个文件,先写入文件名的长度和文件名,再写入文件内容的长度和文件内容。
  • decompress_files 函数:
    • 读取压缩文件,使用 flate2 库的 ZlibDecoder 进行解压。
    • 根据之前写入的文件名长度和内容长度信息,将文件解压到指定的临时目录。
  • execute_program 函数:使用 std::process::Command 执行指定路径的程序。
  • main 函数:
    • 调用 compress_files 函数将文件压缩到 compressed_data.zlib
    • 创建临时目录,调用 decompress_files 函数将压缩文件解压到临时目录。
    • 执行主程序 main.exe
    • 最后删除压缩文件。

编译并运行,生成自解压文件(SFX)

cargo build --release
./target/release/your_project_name

本体打包

在开发环境中使用 cargo 把 Rust 项目编译成可执行文件,并且使用 --release 选项来进行优化,生成高性能的二进制文件。在项目根目录下执行如下命令:

cargo build --release

需要将以上可以生成自解压文件的项目打包,去除cargo流程

安装过程

首先移除 src-tauri/tauri.conf.json 中的 window 这个属性配置

其次修改 src-tauri/main.rs , 在初始化app时,使用其 setup 这个 api 进行动态化初始一个 window:

rust">tauri::Builder::default()
.setup(|app| {
    WindowBuilder::new(app, "main", WindowUrl::App("https://weread.qq.com/".into()))
    .title("")
    .build();
    Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");

tauri通过rust流式下载文件,并计算进度给前端

  1. 在 Rust 端,使用合适的网络库(如 reqwest)来发起文件下载请求。
  2. 在下载过程中,通过读取已接收的数据量来计算下载进度。
  3. 使用 Tauri 的消息传递机制(例如 invoke 方法)将进度信息传递给前端。
rust">use reqwest::blocking::Response;
use tauri::api::http::send_message;

fn download_file(url: &str) {
    let client = reqwest::blocking::Client::new();
    let mut response = client.get(url).unwrap();

    let total_length = response
      .content_length()
      .unwrap_or(0);
    let mut downloaded_bytes = 0;

    let mut buffer = Vec::new();
    while let Some(chunk) = response.chunk().unwrap() {
        buffer.extend_from_slice(&chunk);
        downloaded_bytes += chunk.len();

        let progress = (downloaded_bytes as f64 / total_length as f64) * 100.0;
        // 将进度发送给前端
        send_message("download_progress", progress).unwrap();
    }

    // 保存文件等后续操作
}

在这里插入图片描述

进度

目前已完成前端ui部分
在这里插入图片描述
源码在github


http://www.niftyadmin.cn/n/5865212.html

相关文章

数据驱动未来!天合光能与永洪科技携手开启数字化新篇章

在信息化时代的今天&#xff0c;企业间的竞争早就超越了传统产品与服务的范畴&#xff0c;新的核心竞争力即——数据处理能力和信息技术的应用。作为数据技术领域的领军者&#xff0c;永洪科技凭借其深厚的技术积累和丰富的行业经验&#xff0c;成功助力天合光能实现数字化升级…

LeetCode 贪心算法经典题目 (C++实现)

121. 买卖股票的最佳时机 题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返…

可狱可囚的爬虫系列课程 13:Requests使用代理IP

一、什么是代理 IP 代理 IP&#xff08;Proxy IP&#xff09;是一个充当“中间人”的服务器IP地址&#xff0c;用于代替用户设备&#xff08;如电脑、手机等&#xff09;直接与目标网站或服务通信。用户通过代理IP访问互联网时&#xff0c;目标网站看到的是代理服务器的IP地址&…

大厂数据仓库数仓建模面试题及参考答案

目录 什么是数据仓库,和数据库有什么区别? 数据仓库的基本原理是什么? 数据仓库架构是怎样的? 数据仓库分层(层级划分),每层做什么?分层的好处是什么?数据分层是根据什么?数仓分层的原则与思路是什么? 数仓建模常用模型有哪些?区别、优缺点是什么?星型模型和雪…

Spark(2.2)输出重定向,管道运算符与压缩解压

一.输出重定向 二.管道运算符 三.压缩和解压 一.输出重定向 1.覆盖原来的: 命令> echo aaa > a.txt。会把aaa这三个字符写入a.txt文件&#xff0c;并把之前的内容全部覆盖掉&#xff08;等价于先删除了a.txt的内容&#xff0c;再写入aaa。 2.输出重定向&#xff08;…

Ansible剧本-playbook

Ansible剧本-playbook 1 playbook基础1.1 简介1.2 playbook的组成结构Task 任务列表任务报错&#xff0c;如何继续执行响应事件Handler 1.3 常用选项执行playbookplaybook查询帮助信息校验playbook语法测试playbook能否正常运行 2 变量 的定义方式2.1 定义规则2.2 vars 变量2.3…

SEO长尾优化实战技巧

内容概要 在搜索引擎优化&#xff08;SEO&#xff09;实践中&#xff0c;长尾关键词的精细化运营已成为提升网站精准流量与搜索排名稳定性的关键策略。相较于竞争激烈的大词&#xff0c;长尾关键词凭借其搜索意图明确、转化率高且流量波动小的特性&#xff0c;能够有效覆盖用户…

(论文)使ConvNeXt模型适应语音数据集上的音频分类

《Adapting a ConvNeXt Model to Audio Classification on AudioSet》 摘要 在计算机视觉中&#xff0c;ConvNeXt 等卷积神经网络 &#xff08;CNN&#xff09; 已经能够超越最先进的转换器&#xff0c;部分归功于深度可分离卷积 &#xff08;DSC&#xff09;。DSC 作为常规卷…