<?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>豆子域名管家 on 豆子技术站</title><link>https://blog.91demo.top/categories/%E8%B1%86%E5%AD%90%E5%9F%9F%E5%90%8D%E7%AE%A1%E5%AE%B6/</link><description>Recent content in 豆子域名管家 on 豆子技术站</description><generator>Hugo -- 0.155.1</generator><language>zh-cn</language><lastBuildDate>Thu, 07 May 2026 07:22:34 +0000</lastBuildDate><atom:link href="https://blog.91demo.top/categories/%E8%B1%86%E5%AD%90%E5%9F%9F%E5%90%8D%E7%AE%A1%E5%AE%B6/index.xml" rel="self" type="application/rss+xml"/><item><title>一次从“全量遍历”到“指针索引+异步刷盘”的性能优化历程</title><link>https://blog.91demo.top/b01-arr-map/</link><pubDate>Thu, 07 May 2026 07:22:34 +0000</pubDate><guid>https://blog.91demo.top/b01-arr-map/</guid><description>&lt;p&gt;豆子域名管家是一个使用Wails3开发的域名和证书检测客户端工具。该工具存储域名的结构体经过几次优化，现在已经进化到如下形式：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;type DomainModel struct {
Domain string `json:&amp;#34;domain&amp;#34;` // 域名信息
Port int `json:&amp;#34;port&amp;#34;` // 端口信息，默认443
UpdatedAt int64 `json:&amp;#34;updatedAt&amp;#34;` // 更新时间戳
NextCheckAt int64 `json:&amp;#34;nextCheckAt&amp;#34;` // 下次检测时间
Whois WhoisModel `json:&amp;#34;whois&amp;#34;` // 域名状态
SSL SSLModel `json:&amp;#34;ssl&amp;#34;` // 证书状态
}
type WhoisModel struct {
Expiry int64 `json:&amp;#34;expiry&amp;#34;` // 域名过期时间
RegisteredAt int64 `json:&amp;#34;registeredAt&amp;#34;` // 域名注册时间
LastCheckAt int64 `json:&amp;#34;lastCheckAt&amp;#34;` // 上次扫描时间
Status string `json:&amp;#34;status&amp;#34;` // active, expired, error
LastError string `json:&amp;#34;lastError&amp;#34;` // 最近的错误
}
type SSLModel struct {
Expiry int64 `json:&amp;#34;expiry&amp;#34;` // 证书过期时间
LastCheckAt int64 `json:&amp;#34;lastCheckAt&amp;#34;` // 上次扫描时间
Status string `json:&amp;#34;status&amp;#34;` // valid, warning, expired, error
LastError string `json:&amp;#34;lastError&amp;#34;` // 最近的错误
Issuer string `json:&amp;#34;issuer&amp;#34;` // 签发者信息，方便排查
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个结构体已经涵盖了前端显示的所有信息。为了管理这些域名记录，我又定义了一个结构体Store，它和文件结构也一一对应。如下：&lt;/p&gt;</description></item><item><title>从单一 TCP 握手到 TCP/UDP 全协议覆盖的实战演进</title><link>https://blog.91demo.top/b01-ssl-check/</link><pubDate>Wed, 06 May 2026 07:22:34 +0000</pubDate><guid>https://blog.91demo.top/b01-ssl-check/</guid><description>&lt;p&gt;豆子域名管家是一个使用Wails3开发的域名和证书检测客户端工具。该工具最初围绕标准的 HTTPS（TCP 443 端口）构建。通过 tls.Dial 建立三次握手，获取 PeerCertificates，然后获取到期时间。&lt;/p&gt;
&lt;p&gt;具体的代码片段如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;func checkByTCP(ctx context.Context, domain, addr string) (int64, error) {
dialer := &amp;amp;net.Dialer{Timeout: 5 * time.Second}
// 使用 DialWithDialer 但需配合 context 处理取消
conn, err := tls.DialWithDialer(dialer, &amp;#34;tcp&amp;#34;, addr, &amp;amp;tls.Config{
ServerName: domain,
InsecureSkipVerify: true,
})
if err != nil {
return 0, err
}
defer conn.Close()
// 检查 Context 是否已取消
select {
case &amp;lt;-ctx.Done():
return 0, ctx.Err()
default:
cert := conn.ConnectionState().PeerCertificates[0]
return cert.NotAfter.Unix(), nil
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在运行一段时间后，有用户反应无法检测它的域名证书。经过排查后发现，用户使用的webtransport协议，且仅提供了quic流服务。当使用TCP探测纯 QUIC 服务时会直接报 Connection Refused。所以需要优化探测功能，增加基于UDP的证书提取。随着QUIC(UDP)的兴起以及HTTP/3的普及，越来越多的服务开始提供基于UDP的QUIC协议。这增加了新开实现UDP证书检测需求的迫切性。&lt;/p&gt;</description></item><item><title>为豆子域名管家实现版本检测功能的技术实践</title><link>https://blog.91demo.top/ssl-checker/</link><pubDate>Tue, 17 Mar 2026 09:00:00 +0800</pubDate><guid>https://blog.91demo.top/ssl-checker/</guid><description>&lt;p&gt;豆子域名管家是一款基于 Wails 3 开发的超轻量级单文件客户端。它不仅拥有 Naive UI 打造的精致界面，更集成了强大的域名监控能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全方位监测：主面板表格直观展示域名列表、SSL 证书剩余天数、Whois 到期时间。&lt;/li&gt;
&lt;li&gt;智能告警：支持自定义告警阈值，状态（正常/警告/错误）一目了然。&lt;/li&gt;
&lt;li&gt;多渠道通知：深度集成钉钉与企业微信，支持配置调度时间与通知频次，确保告警不漏报、不打扰。&lt;/li&gt;
&lt;li&gt;静默守护：支持随系统自动启动，后台默默守护您的域名安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="为什么需要版本提醒为什么不是自动更新"&gt;为什么需要版本提醒，为什么不是自动更新？&lt;/h2&gt;
&lt;p&gt;在开发 豆子域名管家 时，Wails 3 就提供了自动集成的更新方案，但经过深思熟虑，我选择了“版本提醒 + 手动覆盖”的策略：主要考虑到个人服务器的带宽限制，避免高并发下载造成服务器宕机。单文件二进制直接覆盖即可完成升级，无需复杂的安装程序。&lt;/p&gt;
&lt;p&gt;在之前的版本中，我主要通过公众号发布新版本并提供下载链接进行手动升级。它的问题是用户需要关注公众号并手动前往蓝奏云下载。为了进一步优化体验，我决定在新版本中引入版本对比提醒：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;红点微标提醒：客户端启动后会自动对比本地与云端版本号。若有新版，左下角版本号将出现灵动的红点徽标，提醒而不打扰。&lt;/li&gt;
&lt;li&gt;交互式遮罩弹窗：点击徽标即可弹出基于 Naive UI 开发的漂亮窗口，清晰展示：
&lt;ul&gt;
&lt;li&gt;当前版本 vs 最新版本&lt;/li&gt;
&lt;li&gt;详细的更新日志&lt;/li&gt;
&lt;li&gt;保姆级使用方法说明&lt;/li&gt;
&lt;li&gt;极简升级路径：点击“复制下载链接”，直接在浏览器中下载最新的二进制文件，覆盖旧文件即可完成升级。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="版本检测核心实现思路"&gt;版本检测核心实现思路&lt;/h2&gt;
&lt;p&gt;这不仅仅是一个简单的弹窗，背后凝聚了对安全和灵活性的思考。为了确保版本检测既高效又安全，我们采用了以下技术路径：&lt;/p&gt;
&lt;p&gt;1，我没有在前端 JS 中硬编码版本号。使用了Go 后端驱动，客户端通过 Wails 3 的原生桥接功能，调用后端 Go 方法获取本地编译时的版本标识。&lt;/p&gt;
&lt;p&gt;2，防爬虫机制：在向云端请求最新版本信息时，配置了特定的 Custom Header 校验，有效拦截恶意脚本和不必要的扫描，保护服务器资源。&lt;/p&gt;
&lt;p&gt;3，设计了灵活的云端数据结构。我们的版本接口不只是返回一个数字，而是返回一个包含三个核心维度的 JSON 对象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最新版本号：用于精准比对。&lt;/li&gt;
&lt;li&gt;下载地址：支持动态变更下载镜像，防止因链接失效导致的无法更新。&lt;/li&gt;
&lt;li&gt;更新日志：让用户第一时间了解修复了哪些 Bug 或新增了哪些功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4，极致的 UI 交互（基于 Naive UI）&lt;br&gt;
当本地版本低于云端时，左下角会亮起精美的红点徽标。点击后的交互逻辑非常人性化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;信息全透明：对比当前版本与新版本，展示完整的更新列表和使用指南。&lt;/li&gt;
&lt;li&gt;尊重用户选择：弹窗配备了清晰的“取消/稍后再说”按钮，绝不强制升级。&lt;/li&gt;
&lt;li&gt;快捷操作：提供“复制下载链接”按钮，点击后自动写入剪切板。用户只需打开浏览器粘贴，即可从蓝奏云等高速渠道下载，覆盖即升级。免去了前期的复杂步骤。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="界面功能效果预览"&gt;界面功能效果预览&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;徽标提示&lt;br&gt;
&lt;img alt="alt 徽标提示" loading="lazy" src="https://blog.91demo.top/images/go/vercheck-tips.png"&gt;&lt;/li&gt;
&lt;li&gt;弹窗说明&lt;br&gt;
&lt;img alt="alt 弹窗说明" loading="lazy" src="https://blog.91demo.top/images/go/vercheck-window.png"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="工具如何开始使用"&gt;工具如何开始使用？&lt;/h2&gt;
&lt;p&gt;无论您是拥有几十个域名的运维大拿，还是只有几个小站的个人玩家，豆子域名管家都是您的不二之选。&lt;/p&gt;</description></item><item><title>为豆子域名管家实现Windows开机自启动功能的技术实践</title><link>https://blog.91demo.top/domain-autostart/</link><pubDate>Sun, 01 Mar 2026 09:00:00 +0800</pubDate><guid>https://blog.91demo.top/domain-autostart/</guid><description>&lt;p&gt;在PC客户端开发中，开机自启动是提升用户体验的重要功能之一。豆子域名管家作为一款Windows平台下的域名管理工具，近期添加了随系统启动功能。本文将详细介绍从技术选型到最终实现的全过程，重点阐述在跨平台库适配失败后，如何针对Windows系统特性实现简洁可靠的自启动方案。&lt;/p&gt;
&lt;h2 id="一技术选型与挑战"&gt;一、技术选型与挑战&lt;/h2&gt;
&lt;h3 id="11-初始方案跨平台库的尝试"&gt;1.1 初始方案：跨平台库的尝试&lt;/h3&gt;
&lt;p&gt;项目初期采用了go-autostart这一流行的Go语言跨平台自启动库。该库设计优雅，理论上支持Windows、macOS、Linux三大主流操作系统。然而在实际集成到wails3项目中时，遇到了编译问题：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sslchecker
.\domainservice.go:1453:11: app.Enable undefined (type *autostart.App has no field or method Enable)
.\domainservice.go:1455:11: app.Disable undefined (type *autostart.App has no field or method Disable)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;经过排查，发现虽然能定位到源码，但由于系统配置或依赖管理问题，无法正确调用库方法。这种问题在Go模块化开发中并不少见，特别是涉及CGO或系统特定依赖时。&lt;/p&gt;
&lt;h3 id="12-平台限制的现实考量"&gt;1.2 平台限制的现实考量&lt;/h3&gt;
&lt;p&gt;进一步分析发现，即使解决编译问题，跨平台方案仍面临以下限制：&lt;/p&gt;
&lt;p&gt;macOS的签名要求：自macOS Catalina以来，苹果加强了应用安全策略。无签名的应用在开机自启动时会被Gatekeeper拦截，除非用户手动进入系统设置&amp;gt;安全性与隐私&amp;gt;通用中点击&amp;quot;仍要打开&amp;quot;。&lt;/p&gt;
&lt;p&gt;Linux的碎片化：不同桌面环境（GNOME、KDE、XFCE等）的自启动机制存在差异，需要适配多种配置方式。&lt;/p&gt;
&lt;p&gt;维护成本：跨平台库在提供便利的同时，也引入了额外的依赖和潜在的兼容性问题。&lt;/p&gt;
&lt;p&gt;考虑到豆子域名管家主要用户群体为Windows用户，且无macOS开发者证书，决定采用专注Windows的轻量化方案。&lt;/p&gt;
&lt;h2 id="二windows自启动实现方案"&gt;二、Windows自启动实现方案&lt;/h2&gt;
&lt;h3 id="21-技术原理"&gt;2.1 技术原理&lt;/h3&gt;
&lt;p&gt;Windows开机自启动主要通过注册表实现。当前用户的自启动项位于：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;系统级的自启动项（需要管理员权限）位于：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于大多数桌面应用，使用用户级注册表即可满足需求，且无需提权操作。&lt;/p&gt;
&lt;h3 id="22-核心实现代码"&gt;2.2 核心实现代码&lt;/h3&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-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// auto_start_windows.go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// +build windows&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;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&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;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;errors&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;path/filepath&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;strings&amp;#34;&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;golang.org/x/sys/windows/registry&amp;#34;&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// AutoStartManager Windows自启动管理器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AutoStartManager&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;exePath&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;regPath&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// NewAutoStartManager 创建自启动管理器实例&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewAutoStartManager&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) (&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;AutoStartManager&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;exePath&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Executable&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;获取可执行文件路径失败&amp;#34;&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&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:#a6e22e"&gt;absPath&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;filepath&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Abs&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;exePath&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Contains&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;absPath&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34; &amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;absPath&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;`&amp;#34;`&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;absPath&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&amp;#34;`&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;span style="display:flex;"&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;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;AutoStartManager&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;exePath&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;absPath&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;regPath&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;`Software\Microsoft\Windows\CurrentVersion\Run`&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#66d9ef"&gt;nil&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// IsAutoStartEnabled 检查是否已启用开机自启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;AutoStartManager&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;IsAutoStartEnabled&lt;/span&gt;() (&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;OpenKey&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CURRENT_USER&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;regPath&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;QUERY_VALUE&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GetStringValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrNotExist&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&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:#a6e22e"&gt;current&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Trim&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exePath&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;`&amp;#34;`&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;stored&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Trim&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;`&amp;#34;`&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;EqualFold&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;filepath&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Clean&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;current&lt;/span&gt;), &lt;span style="color:#a6e22e"&gt;filepath&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Clean&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;stored&lt;/span&gt;)), &lt;span style="color:#66d9ef"&gt;nil&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// SetAutoStart 设置或取消开机自启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;AutoStartManager&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;SetAutoStart&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;enable&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;OpenKey&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CURRENT_USER&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;regPath&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;QUERY_VALUE&lt;/span&gt;|&lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SET_VALUE&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;enable&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetStringValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exePath&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DeleteValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;appName&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrNotExist&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&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;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="23-关键实现细节"&gt;2.3 关键实现细节&lt;/h3&gt;
&lt;p&gt;路径处理：使用os.Executable()获取可执行文件绝对路径，确保在不同工作目录下都能正确运行。&lt;/p&gt;</description></item><item><title>告别域名过期焦虑：基于 Go + Wails 3 开发“豆子域名管家”，实现批量监测与企微钉钉预警</title><link>https://blog.91demo.top/ssl-checker/</link><pubDate>Wed, 28 Jan 2026 18:00:00 +0800</pubDate><guid>https://blog.91demo.top/ssl-checker/</guid><description>&lt;p&gt;我以前曾介绍过，我在微信小程序实现了域名证书监控功能。但是运行一段时间后，发现用的人极少，我在想是否是因为域名以及Webhook隐私的问题。还有就是域名数量太少？&lt;/p&gt;
&lt;p&gt;我就想开发一个客户端工具，前端时间使用wails3开发了内网穿透客户端，正好技术可以复用。经过数月的规划和开发测试，豆子域名管家终于可以使用了。这是一款完全本地运行、支持批量导入管理以及可以企微和钉钉通知的域名证书检测工具。旨在解决域名过期监控难题。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;传统方式&lt;/th&gt;
&lt;th&gt;豆子域名管家&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;运行方式&lt;/td&gt;
&lt;td&gt;依赖云端服务&lt;/td&gt;
&lt;td&gt;纯本地运行，数据不出本地&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;批量处理&lt;/td&gt;
&lt;td&gt;手动逐个查询&lt;/td&gt;
&lt;td&gt;一键导入，批量删除&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通知渠道&lt;/td&gt;
&lt;td&gt;单一（邮件/短信）&lt;/td&gt;
&lt;td&gt;钉钉+企微双通道，支持Markdown格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自定义提醒&lt;/td&gt;
&lt;td&gt;固定时间提醒&lt;/td&gt;
&lt;td&gt;可配置通知时间，提前预警天数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;系统集成&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;系统托盘常驻，后台静默运行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;隐私安全&lt;/td&gt;
&lt;td&gt;数据上传第三方&lt;/td&gt;
&lt;td&gt;域名数据和配置数据存本地&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;技术架构亮点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;工具基于Wails3框架构建，采用Vue3+NaiveUI前端技术栈，确保界面简洁美观，交互流畅，支持暗黑/浅色两种主题。&lt;/li&gt;
&lt;li&gt;本地证书检测引擎，直接调用系统网络库进行TLS检测，无需依赖外部API。&lt;/li&gt;
&lt;li&gt;多线程并发处理，使用go的特性批量进行域名检测。&lt;/li&gt;
&lt;li&gt;跨平台兼容：支持Windows、macOS、Linux系统。&lt;/li&gt;
&lt;li&gt;配置持久化，所有配置本地存储，重启后自动恢复。&lt;/li&gt;
&lt;li&gt;支持域名证书检测，域名到期时间检测。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;软件运行界面预览：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;控制面板&lt;br&gt;
&lt;img alt="控制面板" loading="lazy" src="https://blog.91demo.top/images/notes/b01panel.webp"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置页面&lt;br&gt;
&lt;img alt="配置页面" loading="lazy" src="https://blog.91demo.top/images/notes/b01config.webp"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基本操作指南：&lt;br&gt;
1，导入域名我们需要把要监控的域名按行录入到txt文档中，可以在软件配置界面下载示例模板，当准备完成后，选择刚才的文件，然后验证。没有问题后，点击确认导入即可。&lt;br&gt;
2，配置机器人目前工具支持钉钉机器人和企业微信机器人，支持Markdown格式消息，可以查点击推送预览效果按钮查看推送效果。当输入机器人配置后，可以验证测试，当收到消息后说明配置成功，点击保存即可。可以同时配置企微和钉钉。这样会同时推送两份通知。&lt;br&gt;
3，配置推送通知策略目前工具扫描调度间隔固定24小时，可以配置通知时间，和告警天数间隔。在通知时间，系统会将当天扫描的结果报表推送到机器人。如果用户更新某些域名证书后，可以在监控面板进行手动刷新。想查看效果，可以把通知时间设置为当前时间加几分钟，当到达时间后，将推送报表。确认无误后，可以调整为真正的推送时间。&lt;br&gt;
4，系统托盘运行当完成域名导入和机器人以及通知策略配置后，可以关闭窗口，工具将自动缩放到系统托盘。如果需要退出，需要右键系统托盘退出。注意，如果退出工具，将不能监控域名，因为这是一个本地工具。所以需要工具长期运行。&lt;br&gt;
5，监控面板监控面板的仪表板显示你的域名配置项统计信息，表格显示监视的域名列表。域名可以搜索，刷新以及删除。域名按照过期、告警、正常顺序排序。请查看最前面域名并及时处理。&lt;/p&gt;
&lt;p&gt;工具按照自己的真实需求开发，在发布后，比小程序版本要热闹一些，很多人下载尝试。有的用户尝试之后还给了反馈。&lt;/p&gt;
&lt;p&gt;如果你需要尝试，可以通过下方下载链接。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://91demo.top/b011"&gt;https://91demo.top/b011&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果您有任何问题和建议，欢迎反馈和交流。&lt;/p&gt;</description></item></channel></rss>