一次从“全量遍历”到“指针索引+异步刷盘”的性能优化历程

豆子域名管家是一个使用Wails3开发的域名和证书检测客户端工具。该工具存储域名的结构体经过几次优化,现在已经进化到如下形式: type DomainModel struct { Domain string `json:"domain"` // 域名信息 Port int `json:"port"` // 端口信息,默认443 UpdatedAt int64 `json:"updatedAt"` // 更新时间戳 NextCheckAt int64 `json:"nextCheckAt"` // 下次检测时间 Whois WhoisModel `json:"whois"` // 域名状态 SSL SSLModel `json:"ssl"` // 证书状态 } type WhoisModel struct { Expiry int64 `json:"expiry"` // 域名过期时间 RegisteredAt int64 `json:"registeredAt"` // 域名注册时间 LastCheckAt int64 `json:"lastCheckAt"` // 上次扫描时间 Status string `json:"status"` // active, expired, error LastError string `json:"lastError"` // 最近的错误 } type SSLModel struct { Expiry int64 `json:"expiry"` // 证书过期时间 LastCheckAt int64 `json:"lastCheckAt"` // 上次扫描时间 Status string `json:"status"` // valid, warning, expired, error LastError string `json:"lastError"` // 最近的错误 Issuer string `json:"issuer"` // 签发者信息,方便排查 } 这个结构体已经涵盖了前端显示的所有信息。为了管理这些域名记录,我又定义了一个结构体Store,它和文件结构也一一对应。如下: ...

2026-05-07 · 2 min · Eagle

从单一 TCP 握手到 TCP/UDP 全协议覆盖的实战演进

豆子域名管家是一个使用Wails3开发的域名和证书检测客户端工具。该工具最初围绕标准的 HTTPS(TCP 443 端口)构建。通过 tls.Dial 建立三次握手,获取 PeerCertificates,然后获取到期时间。 具体的代码片段如下: func checkByTCP(ctx context.Context, domain, addr string) (int64, error) { dialer := &net.Dialer{Timeout: 5 * time.Second} // 使用 DialWithDialer 但需配合 context 处理取消 conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{ ServerName: domain, InsecureSkipVerify: true, }) if err != nil { return 0, err } defer conn.Close() // 检查 Context 是否已取消 select { case <-ctx.Done(): return 0, ctx.Err() default: cert := conn.ConnectionState().PeerCertificates[0] return cert.NotAfter.Unix(), nil } } 在运行一段时间后,有用户反应无法检测它的域名证书。经过排查后发现,用户使用的webtransport协议,且仅提供了quic流服务。当使用TCP探测纯 QUIC 服务时会直接报 Connection Refused。所以需要优化探测功能,增加基于UDP的证书提取。随着QUIC(UDP)的兴起以及HTTP/3的普及,越来越多的服务开始提供基于UDP的QUIC协议。这增加了新开实现UDP证书检测需求的迫切性。 ...

2026-05-06 · 3 min · Eagle

为豆子域名管家实现版本检测功能的技术实践

豆子域名管家是一款基于 Wails 3 开发的超轻量级单文件客户端。它不仅拥有 Naive UI 打造的精致界面,更集成了强大的域名监控能力: 全方位监测:主面板表格直观展示域名列表、SSL 证书剩余天数、Whois 到期时间。 智能告警:支持自定义告警阈值,状态(正常/警告/错误)一目了然。 多渠道通知:深度集成钉钉与企业微信,支持配置调度时间与通知频次,确保告警不漏报、不打扰。 静默守护:支持随系统自动启动,后台默默守护您的域名安全。 为什么需要版本提醒,为什么不是自动更新? 在开发 豆子域名管家 时,Wails 3 就提供了自动集成的更新方案,但经过深思熟虑,我选择了“版本提醒 + 手动覆盖”的策略:主要考虑到个人服务器的带宽限制,避免高并发下载造成服务器宕机。单文件二进制直接覆盖即可完成升级,无需复杂的安装程序。 在之前的版本中,我主要通过公众号发布新版本并提供下载链接进行手动升级。它的问题是用户需要关注公众号并手动前往蓝奏云下载。为了进一步优化体验,我决定在新版本中引入版本对比提醒: 红点微标提醒:客户端启动后会自动对比本地与云端版本号。若有新版,左下角版本号将出现灵动的红点徽标,提醒而不打扰。 交互式遮罩弹窗:点击徽标即可弹出基于 Naive UI 开发的漂亮窗口,清晰展示: 当前版本 vs 最新版本 详细的更新日志 保姆级使用方法说明 极简升级路径:点击“复制下载链接”,直接在浏览器中下载最新的二进制文件,覆盖旧文件即可完成升级。 版本检测核心实现思路 这不仅仅是一个简单的弹窗,背后凝聚了对安全和灵活性的思考。为了确保版本检测既高效又安全,我们采用了以下技术路径: 1,我没有在前端 JS 中硬编码版本号。使用了Go 后端驱动,客户端通过 Wails 3 的原生桥接功能,调用后端 Go 方法获取本地编译时的版本标识。 2,防爬虫机制:在向云端请求最新版本信息时,配置了特定的 Custom Header 校验,有效拦截恶意脚本和不必要的扫描,保护服务器资源。 3,设计了灵活的云端数据结构。我们的版本接口不只是返回一个数字,而是返回一个包含三个核心维度的 JSON 对象: 最新版本号:用于精准比对。 下载地址:支持动态变更下载镜像,防止因链接失效导致的无法更新。 更新日志:让用户第一时间了解修复了哪些 Bug 或新增了哪些功能。 4,极致的 UI 交互(基于 Naive UI) 当本地版本低于云端时,左下角会亮起精美的红点徽标。点击后的交互逻辑非常人性化: 信息全透明:对比当前版本与新版本,展示完整的更新列表和使用指南。 尊重用户选择:弹窗配备了清晰的“取消/稍后再说”按钮,绝不强制升级。 快捷操作:提供“复制下载链接”按钮,点击后自动写入剪切板。用户只需打开浏览器粘贴,即可从蓝奏云等高速渠道下载,覆盖即升级。免去了前期的复杂步骤。 界面功能效果预览 徽标提示 弹窗说明 工具如何开始使用? 无论您是拥有几十个域名的运维大拿,还是只有几个小站的个人玩家,豆子域名管家都是您的不二之选。 ...

2026-03-17 · 1 min · Eagle

为豆子域名管家实现Windows开机自启动功能的技术实践

在PC客户端开发中,开机自启动是提升用户体验的重要功能之一。豆子域名管家作为一款Windows平台下的域名管理工具,近期添加了随系统启动功能。本文将详细介绍从技术选型到最终实现的全过程,重点阐述在跨平台库适配失败后,如何针对Windows系统特性实现简洁可靠的自启动方案。 一、技术选型与挑战 1.1 初始方案:跨平台库的尝试 项目初期采用了go-autostart这一流行的Go语言跨平台自启动库。该库设计优雅,理论上支持Windows、macOS、Linux三大主流操作系统。然而在实际集成到wails3项目中时,遇到了编译问题: 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) 经过排查,发现虽然能定位到源码,但由于系统配置或依赖管理问题,无法正确调用库方法。这种问题在Go模块化开发中并不少见,特别是涉及CGO或系统特定依赖时。 1.2 平台限制的现实考量 进一步分析发现,即使解决编译问题,跨平台方案仍面临以下限制: macOS的签名要求:自macOS Catalina以来,苹果加强了应用安全策略。无签名的应用在开机自启动时会被Gatekeeper拦截,除非用户手动进入系统设置>安全性与隐私>通用中点击"仍要打开"。 Linux的碎片化:不同桌面环境(GNOME、KDE、XFCE等)的自启动机制存在差异,需要适配多种配置方式。 维护成本:跨平台库在提供便利的同时,也引入了额外的依赖和潜在的兼容性问题。 考虑到豆子域名管家主要用户群体为Windows用户,且无macOS开发者证书,决定采用专注Windows的轻量化方案。 二、Windows自启动实现方案 2.1 技术原理 Windows开机自启动主要通过注册表实现。当前用户的自启动项位于: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run 系统级的自启动项(需要管理员权限)位于: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run 对于大多数桌面应用,使用用户级注册表即可满足需求,且无需提权操作。 2.2 核心实现代码 // auto_start_windows.go // +build windows package main import ( "errors" "os" "path/filepath" "strings" "golang.org/x/sys/windows/registry" ) // AutoStartManager Windows自启动管理器 type AutoStartManager struct { appName string exePath string regPath string } // NewAutoStartManager 创建自启动管理器实例 func NewAutoStartManager(appName string) (*AutoStartManager, error) { exePath, err := os.Executable() if err != nil { return nil, errors.New("获取可执行文件路径失败") } // 转换为绝对路径并处理空格 absPath, _ := filepath.Abs(exePath) if strings.Contains(absPath, " ") { absPath = `"` + absPath + `"` } return &AutoStartManager{ appName: appName, exePath: absPath, regPath: `Software\Microsoft\Windows\CurrentVersion\Run`, }, nil } // IsAutoStartEnabled 检查是否已启用开机自启动 func (m *AutoStartManager) IsAutoStartEnabled() (bool, error) { key, err := registry.OpenKey(registry.CURRENT_USER, m.regPath, registry.QUERY_VALUE) if err != nil { return false, err } defer key.Close() value, _, err := key.GetStringValue(m.appName) if err != nil { if err == registry.ErrNotExist { return false, nil } return false, err } // 对比路径,处理可能的引号差异 current := strings.Trim(m.exePath, `"`) stored := strings.Trim(value, `"`) return strings.EqualFold(filepath.Clean(current), filepath.Clean(stored)), nil } // SetAutoStart 设置或取消开机自启动 func (m *AutoStartManager) SetAutoStart(enable bool) error { key, err := registry.OpenKey(registry.CURRENT_USER, m.regPath, registry.QUERY_VALUE|registry.SET_VALUE) if err != nil { return err } defer key.Close() if enable { return key.SetStringValue(m.appName, m.exePath) } else { err = key.DeleteValue(m.appName) if err == registry.ErrNotExist { return nil // 键不存在不算错误 } return err } } 2.3 关键实现细节 路径处理:使用os.Executable()获取可执行文件绝对路径,确保在不同工作目录下都能正确运行。 ...

2026-03-01 · 3 min · Eagle

告别域名过期焦虑:基于 Go + Wails 3 开发“豆子域名管家”,实现批量监测与企微钉钉预警

我以前曾介绍过,我在微信小程序实现了域名证书监控功能。但是运行一段时间后,发现用的人极少,我在想是否是因为域名以及Webhook隐私的问题。还有就是域名数量太少? 我就想开发一个客户端工具,前端时间使用wails3开发了内网穿透客户端,正好技术可以复用。经过数月的规划和开发测试,豆子域名管家终于可以使用了。这是一款完全本地运行、支持批量导入管理以及可以企微和钉钉通知的域名证书检测工具。旨在解决域名过期监控难题。 特性 传统方式 豆子域名管家 运行方式 依赖云端服务 纯本地运行,数据不出本地 批量处理 手动逐个查询 一键导入,批量删除 通知渠道 单一(邮件/短信) 钉钉+企微双通道,支持Markdown格式 自定义提醒 固定时间提醒 可配置通知时间,提前预警天数 系统集成 无 系统托盘常驻,后台静默运行 隐私安全 数据上传第三方 域名数据和配置数据存本地 技术架构亮点: 工具基于Wails3框架构建,采用Vue3+NaiveUI前端技术栈,确保界面简洁美观,交互流畅,支持暗黑/浅色两种主题。 本地证书检测引擎,直接调用系统网络库进行TLS检测,无需依赖外部API。 多线程并发处理,使用go的特性批量进行域名检测。 跨平台兼容:支持Windows、macOS、Linux系统。 配置持久化,所有配置本地存储,重启后自动恢复。 支持域名证书检测,域名到期时间检测。 软件运行界面预览: 控制面板 配置页面 基本操作指南: 1,导入域名我们需要把要监控的域名按行录入到txt文档中,可以在软件配置界面下载示例模板,当准备完成后,选择刚才的文件,然后验证。没有问题后,点击确认导入即可。 2,配置机器人目前工具支持钉钉机器人和企业微信机器人,支持Markdown格式消息,可以查点击推送预览效果按钮查看推送效果。当输入机器人配置后,可以验证测试,当收到消息后说明配置成功,点击保存即可。可以同时配置企微和钉钉。这样会同时推送两份通知。 3,配置推送通知策略目前工具扫描调度间隔固定24小时,可以配置通知时间,和告警天数间隔。在通知时间,系统会将当天扫描的结果报表推送到机器人。如果用户更新某些域名证书后,可以在监控面板进行手动刷新。想查看效果,可以把通知时间设置为当前时间加几分钟,当到达时间后,将推送报表。确认无误后,可以调整为真正的推送时间。 4,系统托盘运行当完成域名导入和机器人以及通知策略配置后,可以关闭窗口,工具将自动缩放到系统托盘。如果需要退出,需要右键系统托盘退出。注意,如果退出工具,将不能监控域名,因为这是一个本地工具。所以需要工具长期运行。 5,监控面板监控面板的仪表板显示你的域名配置项统计信息,表格显示监视的域名列表。域名可以搜索,刷新以及删除。域名按照过期、告警、正常顺序排序。请查看最前面域名并及时处理。 工具按照自己的真实需求开发,在发布后,比小程序版本要热闹一些,很多人下载尝试。有的用户尝试之后还给了反馈。 如果你需要尝试,可以通过下方下载链接。 https://91demo.top/b011 如果您有任何问题和建议,欢迎反馈和交流。

2026-01-28 · 1 min · Eagle

基于 Dufs + Mole-go (FRP) 快速搭建高效的内网穿透开发演示环境

最近开发完 Mole-go,想给它做个网站用来展示和下载。但我这个后端糙汉子,样式真搞不定,求助 AI 调了半天还是差点意思。最头疼的是,手机端调试得一遍遍输 IP,给朋友演示也得发一串 IP 端口,太不专业了!于是我一顿折腾,搞出了这套方案…… 为了解决这些痛点,我摸索出了一套“黄金组合”:Dufs + Mole-go + FRP + Caddy。这套方案打通了从本地到公网域名的全链路,实现了自动 HTTPS、域名访问以及极致的访问体验。 第一步:构建本地内容基石(Dufs) 一切的起点是本地文件服务。我选择使用 Dufs 作为静态文件服务器。它极其轻量,支持上传、搜索、打包下载甚至 WebDAV,是我演示 Web 应用或分发安装包的首选。 通过下面简单的命令,就能在本地 5000 端口启动了静态文件及Web服务。虽然此时它还被“困”在局域网内,但它为后续的展示提供了稳固的基础。 dufs.exe --render-index 我们这个时候将HTML文件放在启动命令时所在文件夹下就可以了。 第二步:突破局域网束缚(FRP 与 Mole-go) 为了让公网流量能顺利精准触达内网,我采用了经典的 FRP 方案,但在客户端层面,我使用了自己开发的 Mole-go。它需要以下几部分配合,注意,像FRP Server部署一次就可以一直使用了。 服务端 (FRP Server):部署在具备公网 IP 的云服务器上,充当流量中转站。这个服务器配置可以很低,因为网站服务在本地电脑,即使本地有数据库,也是消耗的本地主机资源,如果仅作为演示,带宽1MB就可以,这样在公网就可以访问了,非常方便远程调试。 客户端 (Mole-go):这是我为 FRP 打造的桌面管理客户端。它封装了 frpc 核心,不仅提供了直观的 UI,还通过系统托盘设计彻底解决了“关闭窗口即断连”的痛点。 使用 Mole-go,我可以将本地 5000 端口通过加密隧道安全地映射到云端。它出色的资源管理和连接稳定性,确保了演示过程中即便网络波动,链接依然稳固如初。它的下载地址:https://91demo.top/tools/,中文名称是豆子内网管家。 第三步:优雅的网关入口(Caddy) 即便流量已到达公网,我也不希望朋友们通过 http://IP:端口 这种生硬的方式访问。我追求的是“域名+HTTPS”的专业感,这不仅是为了美观,更是为了开发环境需求,比如公众号开发,小程序后端服务等必须 HTTPS 环境。仅需配置一次就可以一直使用了。 我选择了 Caddy 担任“守门人”。Caddy 的魅力在于其近乎零配置的 自动 HTTPS 功能。我看中了它的简单方便,它非常符合我的场景。在 Caddyfile 中,我只需写下: example.com { reverse_proxy localhost:7000 # 指向 FRP 映射出的本地端口 } 仅需这一行配置,Caddy 就会自动搞定 SSL 证书的申请与续签。当访问者输入域名时,映入眼帘的是受信任的绿色小锁头,所有的复杂端口逻辑都被完美隐藏。当然,如果你需要高性能,或者服务器已经有了Nginx,你也可以直接使用它。 ...

2026-01-15 · 1 min · Eagle

深度实战:基于 Wails v3 与 Go 打造跨平台 FRP 桌面客户端 Mole-go 的技术架构与原理

一、 缘起:为什么需要 mole-go? 在开发微信公众号、调试支付接口、以及演示本地开发网站时,或由于服务器资源限制需要在本地部署服务时,frp 是不可或缺的内网穿透神器。然而,原生的 frpc 存在几个显著的痛点: 运行隐形性差:必须开启命令行窗口,一旦误关服务即中断。 配置门槛高:新手难以记忆复杂的 .toml 参数。 为了解决这些问题,我开发了 mole-go。它是一个轻量级、跨平台的桌面客户端,旨在实现 frp 的配置、启动与监控一体化。 在选择使用哪个语言,哪个库开发时,我尝试了多个,分别为go fyne,rust tauri,rust iced,go wails3。 放弃Go fyne是因为frp日志列表显示达不到我的要求,放弃rust tauri是因为frp二进制启动和关闭我无法实现,放弃rust iced是因为只开发了部分,界面和进度无法我无法掌控,并且此时我了解到了wails v3版本。我最终选择 Wails v3 则是看中了其原生渲染、系统托盘支持、Go 强力后端以及极小的打包体积。最主要的原因是我熟悉Go,可以用它实现功能。 二、 核心架构:Go + Wails v3 的化学反应 mole-go 采用了经典的“UI-Backend-Service”三层架构: Wails UI:负责前端展示,通过事件驱动(Event-Driven)与后端交互。 Go Backend:核心大脑,负责业务逻辑、进程管理与系统级 API 调用。 frpc 二进制:底层服务,通过 Go 的 embed 特性内嵌到二进制文件中。 三、 关键实现细节:从命令行到图形化的进化 前端:从“面条代码”到模块化数据驱动 早期版本中,我直接采用 window.startFrp,window.stopFrp这样的写法,导致代码碎片化严重,以及管理app运行状态不方便。在 mole-go 的正式版中,我将其重构为数据驱动模式,类似Vue,由数据驱动界面: 模块化封装:定义全局 window.App 对象,将数据状态与行为(Methods)统一封装,使代码结构清晰。 动态 UI 组件:针对 HTTP、TCP、UDP 等不同代理模型,不再机械地堆砌 HTML 片段,而是通过逻辑判断实现“按需渲染”,大大精简了 DOM 结构。 后端:全局实例与事件机制 为了保证服务层(Service)能随时与 UI 通信,我设计了一个全局 App 实例,这样可以方便得调用和管理。 状态约定:前后端约定一套状态码,通过 Wails v3 的 Events 机制,后端可以主动向前端推送 frpc 的运行状态、日志等信息。 独立服务层:将 frp 相关逻辑抽离到专门的文件中,通过 Wails 的 Binding 暴露给前端,保持代码的解耦。 系统深度集成 系统托盘(System Tray):利用 Wails v3 原生的托盘支持,实现了“关闭即隐藏”逻辑。 外部链接调用:使用 wails3自带的 Browser.OpenURL 方法,确保点击文档链接时能正确唤起系统浏览器。 可参考项目源码。 ...

2026-01-10 · 1 min · Eagle

FRP 图形化管理新方案:基于 Wails 3 的 Mole-go 桌面客户端部署指南

记录自研 FRP 管理工具 Mole-go 的公版部署与使用过程,探讨如何通过 Go + Wails 3 构建极致简单的内网穿透图形化管理体验。 在介绍部署高性能的内网穿透开发演示环境时,虽然也提到了部署配置,但是不太详细。这次本文将带你详细的快速完成基于 Mole 的内网穿透服务部署,包含服务端 (frps) 与桌面客户端 (Mole/frpc) 的配置与排查要点。 📖 核心概念 首先,我们了解几个关键概念,只有了解之后我们才能进行扩展,应用到更多的场景中。 什么是 FRP? FRP (Fast Reverse Proxy) 是一款高性能的反向代理/内网穿透工具,它通过在公网服务器和内网客户端之间建立隧道,使外网可以访问内网服务(如 NAS、树莓派、开发环境等)。 什么是 Mole? Mole 是一款基于 wails3 开发的 FRP 桌面客户端,提供图形化管理界面,简化 frpc 的配置与使用: 告别繁琐命令行 支持系统托盘常驻,防止误关窗口导致中断 支持 HTTP/HTTPS、TCP、UDP 等多种协议 🛠️ 部署前准备 必须有一台拥有公网 IP 的低配云服务器(例如1核1G内存的阿里云、腾讯云等),如果你需要提供视频等服务,那么带宽要高,服务器配置视情况进行调高。 FRP 服务端(frps)建议使用 FRP Releases 的 v0.65.0 或更高版本,可以从https://91demo.top/tools/下载我已经打包好各平台的服务端。 Mole 桌面客户端安装包,可以从https://91demo.top/tools/直接下载,或者从Github下载源码littletow/mole-go编译。 需要具备基本网络与防火墙管理知识。 1. 服务端配置(frps) 在你拥有公网IP的云服务器上部署frps服务端: 下载并解压 FRP 服务端(以 Linux 为例) 访问 FRP 的 Releases 页面下载对应版本并解压(示例为 v0.65.0 或更高)。 ...

2026-01-07 · 2 min · Eagle

Wails 3 初体验及在FRP管理客户端中的应用

为什么选择了Wails 3 ? Wails 3 最大的改变在于它不再强绑定于某个特定的前端框架,且引入了多窗口支持和更轻量级的 Runtime。它允许你在不启动主窗口的情况下运行后端服务,这正是我们实现“系统托盘”和“后台演示服务”的基础。 在 v2 中,我们习惯于自动生成的 wailsjs 文件夹。但在 v3 中,这一逻辑被进一步标准化。 当你运行开发指令时,Wails 会扫描你的 Go 结构体方法,并将其映射为前端可以调用的 JavaScript 函数。这个过程在 v3 中被称为 Generate 过程。 Wails 3 通信灵魂 我们在前端调用在后端定义的 HandleConnect 返回自定义结构体,这在 Wails 3 中是前后端通信的灵魂。 在 Wails 3 开发中,最核心的动作就是:后端做功,前端表现。 当你调用 MoleService.HandleConnect() 时,Go 后端会产生一个结果。在本项目中,我们需要同时返回一个 Code(状态码)和一个 Content(数据内容)。 为了实现这一点,我们定义了一个结构体: type Response struct { Code int; Content string } 虽然 Go 内部使用的是结构体,但前端 JavaScript 只能读懂 JSON 对象。Wails 3 内部会自动帮你完成这个“翻译”过程。 但是,如果你想让前端看到的字段名是小写的(例如 res.code 而不是 res.Code),你必须在 Go 结构体定义时加上“注解”。 type Response struct { Code int `json:"code"` Content string `json:"content"` } 记住,所有通过 bindings 调用的 Go 方法,在前端返回的都是一个 Promise 对象。这意味着你必须使用 await 或者 .then() 来接收数据,否则你拿到的将是一个永不开启的“盲盒”。 ...

2025-11-01 · 4 min · Eagle