<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Rust on 豆子技术站</title><link>https://blog.91demo.top/tags/rust/</link><description>Recent content in Rust on 豆子技术站</description><generator>Hugo -- 0.155.1</generator><language>zh-cn</language><lastBuildDate>Tue, 12 May 2026 12:40:16 +0800</lastBuildDate><atom:link href="https://blog.91demo.top/tags/rust/index.xml" rel="self" type="application/rss+xml"/><item><title>记一次使用egui 构建的轻量小程序码生成工具</title><link>https://blog.91demo.top/mpcode-egui/</link><pubDate>Tue, 12 May 2026 12:40:16 +0800</pubDate><guid>https://blog.91demo.top/mpcode-egui/</guid><description>&lt;p&gt;最近要使用小程序码生成工具，捡起了以前开发的使用 Rust + native-windows-gui 打造极致轻量的小程序工具。&lt;/p&gt;
&lt;p&gt;我发现这个工具最大的痛点就是每次打开都需要重复输入appid和appsecret。我决定修复它，我没有重新开始该项目，是因为我准备使用最近很流行的egui库。使用这个库的另一个原因是我为开发ESP网关客户端做技术储备。&lt;/p&gt;
&lt;p&gt;在开发的中间，我还想使用iced，但是开发了一段时间放弃了，不是iced不好，是因为我没有精力。它适合长期的大型的项目。我现在想快速开发完成，所以egui的界面我忍受了。&lt;/p&gt;
&lt;p&gt;开发完成的初版界面如下：&lt;br&gt;
&lt;img alt="豆子太阳码管家" loading="lazy" src="https://blog.91demo.top/images/suncode-page.png"&gt;&lt;/p&gt;
&lt;p&gt;它极致轻量，只有一个小巧的运行文件，无需安装臃肿的运行库。响应迅速毫秒级启动，操作如丝般顺滑，没有多余的动效和加载等待。&lt;/p&gt;
&lt;p&gt;下载工具后，放入你要生成小程序码的文件夹中，打开工具，填入小程序参数（AppID、路径等），一键获取高清太阳码，小程序码图片就会输出到本文件夹中。 最为暖心的是，应用重新启动后，不用重新输入appid和appsecret，并且会自动获取token。&lt;/p&gt;
&lt;p&gt;下面看看核心代码片段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// --- 主应用状态 ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BeanApp&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; store: &lt;span style="color:#a6e22e"&gt;AppConfig&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 保存信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; appid_raw: String, &lt;span style="color:#75715e"&gt;// appid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; secret_raw: String, &lt;span style="color:#75715e"&gt;// appsecret
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; access_token: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 存储 Token
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expires_in: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 存储过期时间戳 (秒)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output_dir: String, &lt;span style="color:#75715e"&gt;// 小程序码输出目录
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_pro: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 是否开启了高级功能
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; active_tab: &lt;span style="color:#66d9ef"&gt;usize&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 0: 单张模式, 1: 批量模式
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; page_path: String, &lt;span style="color:#75715e"&gt;// 页面路径
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scene_value: String, &lt;span style="color:#75715e"&gt;// 场景值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qr_size: &lt;span style="color:#66d9ef"&gt;u32&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 导出尺寸
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; show_crop_line: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 是否显示裁剪线
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; preview_texture: Option&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TextureHandle&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 预览图句柄
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status_msg: String, &lt;span style="color:#75715e"&gt;// 状态信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; show_password: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 是否显示密码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tx: &lt;span style="color:#a6e22e"&gt;Sender&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AppMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 发送端
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rx: &lt;span style="color:#a6e22e"&gt;Receiver&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;AppMessage&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 接收端
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ctx: &lt;span style="color:#a6e22e"&gt;egui&lt;/span&gt;::Context, &lt;span style="color:#75715e"&gt;// 保存上下文，用于异步刷新
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_loading_token: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 防止重复请求
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这是最核心的内容，egui就靠这个结构体，让UI和业务逻辑进行交互。本质就是UI交互区修改这个结构体中的一些内容，然后调用业务逻辑去处理内容，然后再返回信息到这个结构体，然后UI读取这些值的内容再显示出来。&lt;/p&gt;</description></item><item><title>记一次因直接使用unwrap导致Rust上传工具崩溃问题</title><link>https://blog.91demo.top/upart-rs/</link><pubDate>Mon, 01 Jan 2024 18:00:00 +0800</pubDate><guid>https://blog.91demo.top/upart-rs/</guid><description>&lt;p&gt;最近，更新了上传文章工具 rust 版本，这个版本使用的库为 nwg，即 native-windows-gui 库，该库支持老的 Windows GUI。&lt;/p&gt;
&lt;p&gt;昨天，当我准备在另一台电脑上录制视频时，发现运行不起来，GUI 界面闪现一下，就退出了。以为版本太老，我先升级了 Rust 版本到最新版本（😭，不应该的原因，固定版本反而更可靠稳定一些）。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rustup update
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;升级完成后，cargo clean ,再 cargo run 重新运行。&lt;/p&gt;
&lt;p&gt;郁闷，还是报错。&lt;/p&gt;
&lt;p&gt;将 main.rs 文件中该行注释，&lt;code&gt;#![windows_subsystem = &amp;quot;windows&amp;quot;]&lt;/code&gt;，可以在命令行中查看报错信息，提示是找不到文件。我想了下，只有图片是文件，然后我将图片的路径修改，发现修改之后，编辑器还提示错误，找不到文件。重新改回去后，编辑器提示错误消失。说明不是这里的错误。&lt;/p&gt;
&lt;p&gt;我真的晕了。从网上查找问题原因，没有找到此类问题的解决方法，郁闷。在查找时，发现了另一个 Windows 官方支持的 rust 库，就叫 windows，打算有时间了用这个库重写一下。&lt;/p&gt;
&lt;p&gt;今天，我又查看了下另一台电脑上的这个项目，发现可以正常运行，将 rust 升级到最新版本后，还是可以运行。在看到配置文件后，我才恍然大悟，原来是查找的是这个配置文件。我赶紧扒拉代码确认，发现确实是这个问题。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let conf = Ini::load_from_file(&amp;#34;conf.ini&amp;#34;).unwrap();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面是使用文件的地方，找不到文件 panic。我这里优化了一下，让错误提醒的更明显一些。这样我就瞬间能知道问题原因了。&lt;/p&gt;
&lt;p&gt;优化后的代码如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let conf = Ini::load_from_file(&amp;#34;conf.ini&amp;#34;).expect(&amp;#34;please config conf.ini file&amp;#34;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有些问题其实很简单，但由于时间长了，还是会一头懵，如果报错信息提示完善一点，会很快定位到问题。&lt;/p&gt;</description></item><item><title>使用Rust基于 Native-Windows-GUI 构建的轻量小程序码生成工具</title><link>https://blog.91demo.top/mpcode-nwg/</link><pubDate>Thu, 02 Nov 2023 12:40:16 +0800</pubDate><guid>https://blog.91demo.top/mpcode-nwg/</guid><description>&lt;p&gt;早于官方后台功能的桌面实践：探索使用 Rust + native-windows-gui 打造极致轻量的小程序工具。&lt;/p&gt;
&lt;p&gt;在沉浸于 Wails 3 开发 Mole 客户端的过程中，我不禁想起了我在 2023 年 11 月完成的一个小项目——&lt;strong&gt;mp-qrcode-gen&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;虽然现在微信小程序后台已经集成了生成小程序码的功能，但在 2023 年，开发者想要快速、批量或者自定义参数生成小程序码，往往需要自己写脚本。于是，我用 Rust 编写了这款极致轻量的桌面工具。&lt;/p&gt;
&lt;p&gt;&lt;img alt="生成小程序码流程图" loading="lazy" src="https://blog.91demo.top/images/mpcode-rs/mpcode-gen.webp"&gt;&lt;/p&gt;
&lt;h2 id="为什么在-2023-年做这个工具"&gt;为什么在 2023 年做这个工具？&lt;/h2&gt;
&lt;p&gt;当时，自己需要根据页面去生成一些小程序码，线下张贴使用。每次使用服务端修改代码感觉非常不便利。于是做了一个这样的生成小程序码的工具。&lt;/p&gt;
&lt;p&gt;当完成后，我还发了视频号进行了推广，发现很多人还下载使用了。其实运营人员和开发者在准备线下物料（如海报）时，通常会面临一个痛点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;接口调用门槛&lt;/strong&gt;：微信官方提供的是 API 接口，非技术人员无法直接使用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数复杂&lt;/strong&gt;：小程序码分为“受限”和“不受限”两种，参数限制各异，手动拼凑 URL 极易出错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批量需求&lt;/strong&gt;：线下场景往往需要针对不同页面、不同场景值生成大量图片，网页端操作效率低下。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="技术选型rust--native-windows-gui"&gt;技术选型：Rust + native-windows-gui&lt;/h2&gt;
&lt;p&gt;在那个时期，我并没有选择 Electron 这种庞然大物，而是选择了&lt;strong&gt;Rust&lt;/strong&gt;，保证了极高的运行效率和内存安全。和&lt;strong&gt;native-windows-gui (NWG)&lt;/strong&gt;，这是一个非常纯粹的 Windows 原生 GUI 库。它不包含浏览器内核，直接调用 Windows API，生成的 &lt;code&gt;.exe&lt;/code&gt; 文件体积非常小。选择Rust还有一个原因就是我在学习它，需要一个项目练手。&lt;/p&gt;
&lt;h2 id="核心功能拆解"&gt;核心功能拆解&lt;/h2&gt;
&lt;p&gt;做的工具界面非常简单直观，配置完 &lt;code&gt;AppID&lt;/code&gt; 和 &lt;code&gt;AppSecret&lt;/code&gt; 后，就可以使用它生成小程序码了。点击生成按钮后，工具会直接调用微信接口并处理返回的二进制流，将其保存为本地图片文件。无需打开浏览器，所见即所得。&lt;/p&gt;
&lt;p&gt;根据微信官方的接口，我实现了两种模式，分别为受限模式和不受限模式。&lt;/p&gt;
&lt;h3 id="1-数量受限模式-api-ab"&gt;1. 数量受限模式 (API A/B)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：支持输入完整的、带有超长 URL 参数的页面路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：适用于对码量要求不高（总计 10 万个以内），但需要精准携带复杂参数的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-数量不受限模式-api-c"&gt;2. 数量不受限模式 (API C)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：页面路径较短，但支持独立的 &lt;code&gt;Scene&lt;/code&gt;（场景值）字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：这是线下物料最常用的模式，可以无限次生成，通过场景值来区分不同的线下投放点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="开源和思考"&gt;开源和思考&lt;/h2&gt;
&lt;p&gt;虽然几个月后，微信官方后台也逐步完善了类似的功能，但 &lt;strong&gt;mp-qrcode-gen&lt;/strong&gt; 的意义在于：&lt;strong&gt;在需求未被完全满足的真空期，个人开发者可以用技术手段快速提供解决方案。&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>构建高可靠 APK 上传工具，彻底终结网络超时与手误引发的生产事故</title><link>https://blog.91demo.top/apkup-rs/</link><pubDate>Mon, 02 Jan 2023 12:40:16 +0800</pubDate><guid>https://blog.91demo.top/apkup-rs/</guid><description>&lt;p&gt;记录一个未公开的 Rust 实战小工具，分享如何通过技术手段解决网络超时与手误操作带来的生产事故。&lt;/p&gt;
&lt;p&gt;在自学 Rust 的那段日子里，我除了开发开源项目，还为日常工作量身定制了一些不公开的内部工具。其中最令我印象深刻的，是一个看似简单的 &lt;strong&gt;APK 文件上传小工具&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;虽然它只是一个 Rust + &lt;code&gt;native-windows-gui&lt;/code&gt; (NWG) 编写的单一界面应用，但它解决的两个核心生产问题，至今仍对我有着深远的影响。&lt;/p&gt;
&lt;h2 id="工具画像极简与高效"&gt;工具画像：极简与高效&lt;/h2&gt;
&lt;p&gt;这个工具的界面极其克制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;交互区&lt;/strong&gt;：一个醒目的文件拖拽控件，文案提示“请把 APK 文件拖拽到这里”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表单区&lt;/strong&gt;：版本号、Version Code、更新描述、是否强制升级（开关）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行区&lt;/strong&gt;：一个大大的“上传”按钮。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的核心逻辑非常纯粹：校验必填项 -&amp;gt; 调用内部上传 API -&amp;gt; 上传文件 -&amp;gt; 返回成功或失败的对话框。&lt;/p&gt;
&lt;h2 id="故事一消失的-60-秒与隐藏的超时限制"&gt;故事一：消失的 60 秒与隐藏的超时限制&lt;/h2&gt;
&lt;p&gt;工具上线初期，一切运行平稳。由于办公室带宽充足，APK 的上传时间通常维持在 40 秒左右。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事故发生&lt;/strong&gt;：&lt;br&gt;
APK 在集成广告后，经常会发生失败的情况，客户端弹出“超时”的报错。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;排查过程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我首先检查了后端服务，发现后端接口完全没有超时限制，服务器日志显示连接是被客户端主动断开的。&lt;/li&gt;
&lt;li&gt;多次重复上传文件使用有线和无线对比，经排查是文件变大，由于公司无线网络波动，上传速度时好时坏，就会偶尔出现失败的情况。对比排查，发现失败的情况上传时间都超过了 60 秒。&lt;/li&gt;
&lt;li&gt;回到 Rust 代码中，我发现当时为了追求简洁，直接使用了 HTTP 客户端的默认配置。而这个默认配置的 &lt;strong&gt;Timeout 为 60 秒&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;br&gt;
在那个瞬间我意识到，工具不能只考虑“理想状态”。我手动将本地超时时间放宽至 5 分钟，并增加了状态提示。上传时不会再出现超时的情况了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;教训&lt;/strong&gt;：&lt;strong&gt;永远不要依赖默认的超时设置&lt;/strong&gt;，尤其是在处理文件上传这类长耗时操作时，本地的容错范围必须根据业务场景精细化配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="故事二包名校验把人为失误挡在门外"&gt;故事二：包名校验——把人为失误挡在门外&lt;/h2&gt;
&lt;p&gt;这是这个工具最有价值的一次功能进化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事故发生&lt;/strong&gt;：&lt;br&gt;
有一次，同事不小心将应用 A 的打包产物当作应用 B 上传到了官网，导致用户下载后发现软件张冠李戴。这属于严重的生产事故。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;排查过程&lt;/strong&gt;：&lt;br&gt;
这个客户端软件调用内部API后，会自动将上传的文件名修改为固定的软件下载文件名，显示在网站上。经测试，如果将应用A的打包产物使用应用B上传，在官网就会出现相同的情况。解决这个方法也非常简单，就是上传的时候小心细致一点，应用A使用A客户端软件，应用B使用B客户端软件。但是人总会犯错，尤其是在面对多个长相相似的 &lt;code&gt;.apk&lt;/code&gt; 文件时。通过肉眼观察文件名来区分应用，是极其不可靠的。&lt;/p&gt;</description></item></channel></rss>