<?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>GO on 豆子技术站</title><link>https://blog.91demo.top/tags/go/</link><description>Recent content in GO 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/tags/go/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>记一次 Windows DLL 动态链接库并供第三方多语言调用</title><link>https://blog.91demo.top/go-dll/</link><pubDate>Thu, 29 Jan 2026 18:00:00 +0800</pubDate><guid>https://blog.91demo.top/go-dll/</guid><description>&lt;p&gt;我使用go写了一个http转WebSocket服务，这是一个命令行程序，双击可以直接运行，今天，有个新的需求，需要将这个命令行程序转为dll，然后供第三方使用。&lt;/p&gt;
&lt;h2 id="1改造程序"&gt;1，改造程序&lt;/h2&gt;
&lt;p&gt;这个命令行程序很小，核心逻辑就是启动了一个HTTP服务，然后接收连接，然后通过WebSocket将内容转发出去。所以，在调整为dll时，仅仅需要导出两个函数即可。&lt;/p&gt;
&lt;p&gt;1，StartServer，启动http服务，监听端口地址。因为不能阻塞，所以需要在监听服务时使用go方法启动一个携程。&lt;br&gt;
2，StopServer，关闭http服务，需要提供给第三方，当它关闭时，需要调用它，释放监听地址等资源。&lt;/p&gt;
&lt;p&gt;下面开始改造代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;package main
import &amp;#34;C&amp;#34; // 必须导入 C 包
import (
// ... 原有导入 ...
)
// 保持原有的全局变量和函数逻辑 (例如transDataGet, connWs 等)
// 需要注意下面的//export这中间不能有空格，否则无法导出头文件
//export StartServer
func StartServer() {
// 将原本 main 函数里的逻辑放在这里
gin.SetMode(gin.ReleaseMode)
r := gin.New()
// ... 注册路由 ...
r.Run(&amp;#34;127.0.0.1:9988&amp;#34;)
}
// 必须保留一个空的 main 函数
func main() {}
// 其它参考StartServer，如果需要导出，一定要添加//export
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2编译命令"&gt;2，编译命令&lt;/h2&gt;
&lt;p&gt;我是在windows环境，需要安装cgo环境，我安装了Mingw-w64支持C编译环境，执行以下命令：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;go build -buildmode=c-shared -o trans.dll main.go
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行后会生成trans.h和trans.dll两个文件。&lt;/p&gt;
&lt;h2 id="3验证dll"&gt;3，验证dll&lt;/h2&gt;
&lt;p&gt;除了一些可以查看dll的工具外，还可以使用第三方语言进行测试。例如我这里使用Python。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import ctypes
import time
lib = ctypes.CDLL(&amp;#34;./trans.dll&amp;#34;)
lib.StartServer()
print(&amp;#34;服务已在后台启动...&amp;#34;)
time.sleep(10) # 模拟第三方程序运行
lib.StopServer()
print(&amp;#34;服务已关闭&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="4再次优化代码"&gt;4，再次优化代码&lt;/h2&gt;
&lt;p&gt;当实际测试的时候，我发现它会阻塞调用者，为了解决这个问题，我们需要再次改造程序。将启动服务改为非阻塞模式。将Gin的启动逻辑放入协程，并利用http.Server提供的Shutdown方法来实现优雅退出。&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><item><title>基于 Dufs + Mole-go (FRP) 快速搭建高效的内网穿透开发演示环境</title><link>https://blog.91demo.top/mole-devenv/</link><pubDate>Thu, 15 Jan 2026 18:00:00 +0800</pubDate><guid>https://blog.91demo.top/mole-devenv/</guid><description>&lt;p&gt;最近开发完 Mole-go，想给它做个网站用来展示和下载。但我这个后端糙汉子，样式真搞不定，求助 AI 调了半天还是差点意思。最头疼的是，手机端调试得一遍遍输 IP，给朋友演示也得发一串 IP 端口，太不专业了！于是我一顿折腾，搞出了这套方案……&lt;/p&gt;
&lt;p&gt;为了解决这些痛点，我摸索出了一套“黄金组合”：Dufs + Mole-go + FRP + Caddy。这套方案打通了从本地到公网域名的全链路，实现了自动 HTTPS、域名访问以及极致的访问体验。&lt;/p&gt;
&lt;h2 id="第一步构建本地内容基石dufs"&gt;第一步：构建本地内容基石（Dufs）&lt;/h2&gt;
&lt;p&gt;一切的起点是本地文件服务。我选择使用 Dufs 作为静态文件服务器。它极其轻量，支持上传、搜索、打包下载甚至 WebDAV，是我演示 Web 应用或分发安装包的首选。&lt;/p&gt;
&lt;p&gt;通过下面简单的命令，就能在本地 5000 端口启动了静态文件及Web服务。虽然此时它还被“困”在局域网内，但它为后续的展示提供了稳固的基础。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dufs.exe --render-index
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们这个时候将HTML文件放在启动命令时所在文件夹下就可以了。&lt;/p&gt;
&lt;h2 id="第二步突破局域网束缚frp-与-mole-go"&gt;第二步：突破局域网束缚（FRP 与 Mole-go）&lt;/h2&gt;
&lt;p&gt;为了让公网流量能顺利精准触达内网，我采用了经典的 FRP 方案，但在客户端层面，我使用了自己开发的 Mole-go。它需要以下几部分配合，注意，像FRP Server部署一次就可以一直使用了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;服务端 (FRP Server)：部署在具备公网 IP 的云服务器上，充当流量中转站。这个服务器配置可以很低，因为网站服务在本地电脑，即使本地有数据库，也是消耗的本地主机资源，如果仅作为演示，带宽1MB就可以，这样在公网就可以访问了，非常方便远程调试。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户端 (Mole-go)：这是我为 FRP 打造的桌面管理客户端。它封装了 frpc 核心，不仅提供了直观的 UI，还通过系统托盘设计彻底解决了“关闭窗口即断连”的痛点。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 Mole-go，我可以将本地 5000 端口通过加密隧道安全地映射到云端。它出色的资源管理和连接稳定性，确保了演示过程中即便网络波动，链接依然稳固如初。它的下载地址：&lt;a href="https://91demo.top/tools/"&gt;https://91demo.top/tools/&lt;/a&gt;，中文名称是豆子内网管家。&lt;/p&gt;
&lt;h2 id="第三步优雅的网关入口caddy"&gt;第三步：优雅的网关入口（Caddy）&lt;/h2&gt;
&lt;p&gt;即便流量已到达公网，我也不希望朋友们通过 http://IP:端口 这种生硬的方式访问。我追求的是“域名+HTTPS”的专业感，这不仅是为了美观，更是为了开发环境需求，比如公众号开发，小程序后端服务等必须 HTTPS 环境。仅需配置一次就可以一直使用了。&lt;/p&gt;
&lt;p&gt;我选择了 Caddy 担任“守门人”。Caddy 的魅力在于其近乎零配置的 自动 HTTPS 功能。我看中了它的简单方便，它非常符合我的场景。在 Caddyfile 中，我只需写下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;example.com {   
reverse_proxy localhost:7000  # 指向 FRP 映射出的本地端口
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;仅需这一行配置，Caddy 就会自动搞定 SSL 证书的申请与续签。当访问者输入域名时，映入眼帘的是受信任的绿色小锁头，所有的复杂端口逻辑都被完美隐藏。当然，如果你需要高性能，或者服务器已经有了Nginx，你也可以直接使用它。&lt;/p&gt;</description></item><item><title>深度实战：基于 Wails v3 与 Go 打造跨平台 FRP 桌面客户端 Mole-go 的技术架构与原理</title><link>https://blog.91demo.top/mole-develop/</link><pubDate>Sat, 10 Jan 2026 06:00:00 +0800</pubDate><guid>https://blog.91demo.top/mole-develop/</guid><description>&lt;h2 id="一-缘起为什么需要-mole-go"&gt;一、 缘起：为什么需要 mole-go？&lt;/h2&gt;
&lt;p&gt;在开发微信公众号、调试支付接口、以及演示本地开发网站时，或由于服务器资源限制需要在本地部署服务时，frp 是不可或缺的内网穿透神器。然而，原生的 frpc 存在几个显著的痛点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行隐形性差：必须开启命令行窗口，一旦误关服务即中断。&lt;/li&gt;
&lt;li&gt;配置门槛高：新手难以记忆复杂的 .toml 参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了解决这些问题，我开发了 mole-go。它是一个轻量级、跨平台的桌面客户端，旨在实现 frp 的配置、启动与监控一体化。&lt;/p&gt;
&lt;p&gt;在选择使用哪个语言，哪个库开发时，我尝试了多个，分别为go fyne，rust tauri，rust iced，go wails3。&lt;/p&gt;
&lt;p&gt;放弃Go fyne是因为frp日志列表显示达不到我的要求，放弃rust tauri是因为frp二进制启动和关闭我无法实现，放弃rust iced是因为只开发了部分，界面和进度无法我无法掌控，并且此时我了解到了wails v3版本。我最终选择 Wails v3 则是看中了其原生渲染、系统托盘支持、Go 强力后端以及极小的打包体积。最主要的原因是我熟悉Go，可以用它实现功能。&lt;/p&gt;
&lt;h2 id="二-核心架构go--wails-v3-的化学反应"&gt;二、 核心架构：Go + Wails v3 的化学反应&lt;/h2&gt;
&lt;p&gt;mole-go 采用了经典的“UI-Backend-Service”三层架构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wails UI：负责前端展示，通过事件驱动（Event-Driven）与后端交互。&lt;/li&gt;
&lt;li&gt;Go Backend：核心大脑，负责业务逻辑、进程管理与系统级 API 调用。&lt;/li&gt;
&lt;li&gt;frpc 二进制：底层服务，通过 Go 的 embed 特性内嵌到二进制文件中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="三-关键实现细节从命令行到图形化的进化"&gt;三、 关键实现细节：从命令行到图形化的进化&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;前端：从“面条代码”到模块化数据驱动&lt;br&gt;
早期版本中，我直接采用 window.startFrp，window.stopFrp这样的写法，导致代码碎片化严重，以及管理app运行状态不方便。在 mole-go 的正式版中，我将其重构为数据驱动模式，类似Vue，由数据驱动界面：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;模块化封装：定义全局 window.App 对象，将数据状态与行为（Methods）统一封装，使代码结构清晰。&lt;/li&gt;
&lt;li&gt;动态 UI 组件：针对 HTTP、TCP、UDP 等不同代理模型，不再机械地堆砌 HTML 片段，而是通过逻辑判断实现“按需渲染”，大大精简了 DOM 结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;后端：全局实例与事件机制&lt;br&gt;
为了保证服务层（Service）能随时与 UI 通信，我设计了一个全局 App 实例，这样可以方便得调用和管理。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;状态约定：前后端约定一套状态码，通过 Wails v3 的 Events 机制，后端可以主动向前端推送 frpc 的运行状态、日志等信息。&lt;/li&gt;
&lt;li&gt;独立服务层：将 frp 相关逻辑抽离到专门的文件中，通过 Wails 的 Binding 暴露给前端，保持代码的解耦。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;系统深度集成&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;系统托盘（System Tray）：利用 Wails v3 原生的托盘支持，实现了“关闭即隐藏”逻辑。&lt;/li&gt;
&lt;li&gt;外部链接调用：使用 wails3自带的 Browser.OpenURL 方法，确保点击文档链接时能正确唤起系统浏览器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可参考项目源码。&lt;/p&gt;</description></item><item><title>FRP 图形化管理新方案：基于 Wails 3 的 Mole-go 桌面客户端部署指南</title><link>https://blog.91demo.top/mole-help/</link><pubDate>Wed, 07 Jan 2026 06:00:00 +0800</pubDate><guid>https://blog.91demo.top/mole-help/</guid><description>&lt;p&gt;记录自研 FRP 管理工具 Mole-go 的公版部署与使用过程，探讨如何通过 Go + Wails 3 构建极致简单的内网穿透图形化管理体验。&lt;/p&gt;
&lt;p&gt;在介绍部署高性能的内网穿透开发演示环境时，虽然也提到了部署配置，但是不太详细。这次本文将带你详细的快速完成基于 Mole 的内网穿透服务部署，包含服务端 (frps) 与桌面客户端 (Mole/frpc) 的配置与排查要点。&lt;/p&gt;
&lt;h2 id="-核心概念"&gt;📖 核心概念&lt;/h2&gt;
&lt;p&gt;首先，我们了解几个关键概念，只有了解之后我们才能进行扩展，应用到更多的场景中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;什么是 FRP？&lt;br&gt;
FRP (Fast Reverse Proxy) 是一款高性能的反向代理/内网穿透工具，它通过在公网服务器和内网客户端之间建立隧道，使外网可以访问内网服务（如 NAS、树莓派、开发环境等）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;什么是 Mole？&lt;br&gt;
Mole 是一款基于 wails3 开发的 FRP 桌面客户端，提供图形化管理界面，简化 frpc 的配置与使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;告别繁琐命令行&lt;/li&gt;
&lt;li&gt;支持系统托盘常驻，防止误关窗口导致中断&lt;/li&gt;
&lt;li&gt;支持 HTTP/HTTPS、TCP、UDP 等多种协议&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="-部署前准备"&gt;🛠️ 部署前准备&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;必须有一台拥有公网 IP 的低配云服务器（例如1核1G内存的&lt;a href="https://www.aliyun.com/minisite/goods?userCode=zrqh6alb"&gt;阿里云&lt;/a&gt;、&lt;a href="https://curl.qcloud.com/XX6que5w"&gt;腾讯云&lt;/a&gt;等），如果你需要提供视频等服务，那么带宽要高，服务器配置视情况进行调高。&lt;/li&gt;
&lt;li&gt;FRP 服务端（frps）建议使用 FRP Releases 的 v0.65.0 或更高版本，可以从&lt;a href="https://91demo.top/tools/"&gt;https://91demo.top/tools/&lt;/a&gt;下载我已经打包好各平台的服务端。&lt;/li&gt;
&lt;li&gt;Mole 桌面客户端安装包，可以从&lt;a href="https://91demo.top/tools/"&gt;https://91demo.top/tools/&lt;/a&gt;直接下载，或者从Github下载源码&lt;a href="https://github.com/littletow/mole-go/releases"&gt;littletow/mole-go&lt;/a&gt;编译。&lt;/li&gt;
&lt;li&gt;需要具备基本网络与防火墙管理知识。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="1-服务端配置frps"&gt;1. 服务端配置（frps）&lt;/h2&gt;
&lt;p&gt;在你拥有公网IP的云服务器上部署frps服务端：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;下载并解压 FRP 服务端（以 Linux 为例）&lt;br&gt;
访问 FRP 的 Releases 页面下载对应版本并解压（示例为 v0.65.0 或更高）。&lt;/p&gt;</description></item><item><title>Wails 3 初体验及在FRP管理客户端中的应用</title><link>https://blog.91demo.top/wails3-demo/</link><pubDate>Sat, 01 Nov 2025 07:34:14 +0000</pubDate><guid>https://blog.91demo.top/wails3-demo/</guid><description>&lt;h2 id="为什么选择了wails-3-"&gt;为什么选择了Wails 3 ？&lt;/h2&gt;
&lt;p&gt;Wails 3 最大的改变在于它不再强绑定于某个特定的前端框架，且引入了&lt;strong&gt;多窗口支持&lt;/strong&gt;和&lt;strong&gt;更轻量级的 Runtime&lt;/strong&gt;。它允许你在不启动主窗口的情况下运行后端服务，这正是我们实现“系统托盘”和“后台演示服务”的基础。&lt;/p&gt;
&lt;p&gt;在 v2 中，我们习惯于自动生成的 &lt;code&gt;wailsjs&lt;/code&gt; 文件夹。但在 v3 中，这一逻辑被进一步标准化。&lt;/p&gt;
&lt;p&gt;当你运行开发指令时，Wails 会扫描你的 Go 结构体方法，并将其映射为前端可以调用的 JavaScript 函数。这个过程在 v3 中被称为 &lt;strong&gt;Generate&lt;/strong&gt; 过程。&lt;/p&gt;
&lt;h3 id="wails-3-通信灵魂"&gt;Wails 3 通信灵魂&lt;/h3&gt;
&lt;p&gt;我们在前端调用在后端定义的 HandleConnect 返回自定义结构体，这在 Wails 3 中是前后端通信的灵魂。&lt;/p&gt;
&lt;p&gt;在 Wails 3 开发中，最核心的动作就是：&lt;strong&gt;后端做功，前端表现&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当你调用 &lt;code&gt;MoleService.HandleConnect()&lt;/code&gt; 时，Go 后端会产生一个结果。在本项目中，我们需要同时返回一个 &lt;code&gt;Code&lt;/code&gt;（状态码）和一个 &lt;code&gt;Content&lt;/code&gt;（数据内容）。&lt;/p&gt;
&lt;p&gt;为了实现这一点，我们定义了一个结构体：&lt;br&gt;
&lt;code&gt;type Response struct { Code int; Content string }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;虽然 Go 内部使用的是结构体，但前端 JavaScript 只能读懂 JSON 对象。Wails 3 内部会自动帮你完成这个“翻译”过程。&lt;/p&gt;
&lt;p&gt;但是，如果你想让前端看到的字段名是小写的（例如 &lt;code&gt;res.code&lt;/code&gt; 而不是 &lt;code&gt;res.Code&lt;/code&gt;），你必须在 Go 结构体定义时加上“注解”。&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-go" data-lang="go"&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;Response&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;Code&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;code&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;Content&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;content&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;记住，所有通过 bindings 调用的 Go 方法，在前端返回的都是一个 Promise 对象。这意味着你必须使用 await 或者 .then() 来接收数据，否则你拿到的将是一个永不开启的“盲盒”。&lt;/p&gt;</description></item><item><title>文章上传命令行客户端升级版本使用Cobra支持更多命令</title><link>https://blog.91demo.top/v-upart-cobra/</link><pubDate>Sun, 22 Sep 2024 12:40:16 +0800</pubDate><guid>https://blog.91demo.top/v-upart-cobra/</guid><description>&lt;p&gt;对于添加更多的命令，使用 flag，就有点麻烦了，这次我们使用一个更高级的库 cobra。同时，我们使用 viper 替换 ini 库，这个库可以读取多种格式的配置文件，可以读取环境变量。&lt;/p&gt;
&lt;p&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;li&gt;更新文章关键字&lt;/li&gt;
&lt;li&gt;更新文章内容&lt;/li&gt;
&lt;li&gt;文章公开开关&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;p&gt;现在使用 cobra 实现上面的命令。&lt;/p&gt;
&lt;p&gt;首先，我想要的效果如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上传文章 gart upload title keyword filename ispub islock&lt;/li&gt;
&lt;li&gt;删除文章 gart remove uuid&lt;/li&gt;
&lt;li&gt;更新文章标题 gart updatetitle uuid title&lt;/li&gt;
&lt;li&gt;更新文章关键字 gart updatekeyword uuid keyword&lt;/li&gt;
&lt;li&gt;更新文章内容 gart updatecontent uuid filename&lt;/li&gt;
&lt;li&gt;更新文章公开或不公开 gart updatepub uuid ispub&lt;/li&gt;
&lt;li&gt;更新文章加锁或不加锁 gart updatelock uuid islock&lt;/li&gt;
&lt;li&gt;根据标题或关键字查找文章 gart search content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面的 upload,remove,updatetitle,updatekeyword 等，在 cobra 中都是命令。title keyword 等都是参数。&lt;/p&gt;
&lt;p&gt;我在读 cobra 文档后，最疑惑的地方就是标志和参数，这两者最大的区别就是标志需要在命令中添加&amp;ndash;flag 然后是值，而参数是直接跟在命令后，直接就是内容的。标志可以更改程序的行为。&lt;/p&gt;</description></item><item><title>使用Go自制豆子碎片文章上传命令行客户端</title><link>https://blog.91demo.top/v-upart-go/</link><pubDate>Sat, 21 Sep 2024 12:40:16 +0800</pubDate><guid>https://blog.91demo.top/v-upart-go/</guid><description>&lt;p&gt;这是 Golang 实现的上传文章以及管理文章的一个命令行工具。&lt;/p&gt;
&lt;h2 id="项目地址"&gt;项目地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://gitee.com/littletow/upart-go"&gt;https://gitee.com/littletow/upart-go&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="实现一个上传文章的命令行工具"&gt;实现一个上传文章的命令行工具&lt;/h2&gt;
&lt;p&gt;这是一个最初的版本，使用 flag 和 ini 来实现文章上传功能。使用 flag 来解析命令行参数，使用 Ini 配置文件，记录识别码，以及 token。记录 token 的原因是因为每次启动命令行，都需要重新获取 token，为了减少 token 获取次数，在获取到 token 后，同时存储到 Ini 配置文件中。每次命令行启动，优先查看配置文件中的 token。&lt;/p&gt;
&lt;p&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;li&gt;请求后端接口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们通过分析后，需要定义以下几个参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;title 标题&lt;/li&gt;
&lt;li&gt;keyword 关键字&lt;/li&gt;
&lt;li&gt;filename MD 文件名&lt;/li&gt;
&lt;li&gt;ispub 是否公开&lt;/li&gt;
&lt;li&gt;islock 是否加锁&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 Go 代码定义参数和结构体&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;var (
title string
keyword string
filename string
ispub int // 1 pub
islock int // 1 lock
)
func init() {
flag.StringVar(&amp;amp;title, &amp;#34;b&amp;#34;, &amp;#34;&amp;#34;, &amp;#34;文章题目&amp;#34;)
flag.StringVar(&amp;amp;keyword, &amp;#34;k&amp;#34;, &amp;#34;&amp;#34;, &amp;#34;文章关键字&amp;#34;)
flag.StringVar(&amp;amp;filename, &amp;#34;f&amp;#34;, &amp;#34;&amp;#34;, &amp;#34;MD文件&amp;#34;)
flag.IntVar(&amp;amp;islock, &amp;#34;l&amp;#34;, 0, &amp;#34;是否加锁&amp;#34;)
flag.IntVar(&amp;amp;ispub, &amp;#34;p&amp;#34;, 0, &amp;#34;是否开放&amp;#34;)
}
func main(){
flag.Parse()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;init() 在 main 函数前调用。需要在 main 函数中调用 flag.Parse()，这一步非常关键。之后就可以使用变量了。&lt;/p&gt;</description></item></channel></rss>