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

豆子域名管家是一个使用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

实战笔记:为了不再漏掉任何一个域名到期提醒,我做了个自动化检测工具

记录如何通过 Go 语言实现多域名 SSL 证书到期自动巡检,并通过钉钉/企业微信机器人实现精准预警。 在运维工作中,有一类事故极其低级却又杀伤力巨大:SSL 证书过期。 痛点:被动挨打的“救火”模式 由于业务需要,我手里管理着大量客户的域名。每个客户购买证书的渠道各异(阿里云、腾讯云或其他厂商),证书下来后通过微信或邮件发给运维,再手动配置到服务器上。 这套流程在客户少的时候还算正常。但随着客户增多,问题出现了:证书到期时间不一,全靠人工记忆。 总会有那么一两次,因为忙碌或者交接疏忽,某个域名证书悄悄过期了。直到客户反馈 APP 无法访问、浏览器弹出红色的安全告警,我们才急忙去“救火”。这种被动的局面不仅影响专业度,也给客户带来了实际损失。 方案:主动出击的自动化巡检 为了彻底根治这个“心病”,我决定做一个自动化的域名证书检测工具。 我的设想很简单:变“人找信息”为“信息找人”。 1. 实现原理 配置简单化:将所有需要监测的域名汇总成一个 txt 文件,每行一个域名,管理起来极其方便。 核心引擎(Go):使用 Go 语言开发后端服务,利用 cron 库开启定时任务,设定每天固定时间(如凌晨 4 点)执行一次巡检。 检测逻辑:程序自动循环读取域名列表,通过 TLS 握手获取证书的有效载荷,计算当前的剩余天数。 精准预警:我设定了一个“7天阈值”。一旦发现有域名将在 7 天内过期,程序会立即将这些域名汇总,并通过企微或者钉钉送达。 2. 消息触达:为什么选择机器人? 在小程序中,邮箱属于敏感隐私资料,审核往往比较严格。为了避开这个麻烦,同时也为了让通知更具实时性,我选择了钉钉机器人和企业微信机器人。 管理员或运营人员只需将机器人的 Webhook 地址配置好,每天早上一上班,就能在手机上收到一份清晰的到期清单。 价值:买到了最宝贵的“时间” 这个功能上线后,我们最直接的收获就是**“沟通时间”**: 提前预判:有了 7 天的缓冲期,运营人员可以气定神闲地与客户沟通续费。 提前操作:运维人员有了充裕的时间更换新证书,彻底杜绝了“半夜修证书”的尴尬。 技术实现 整个域名检测分为两部分:小程序端和服务端,小程序端负责提交域名信息以及Webhook(钉钉、企微)信息。以及查看导入的域名。服务端需要存储域名以及Webhook。并在每天的固定时刻进行域名巡检,当域名证书有效期小于7天时,将发送通知给管理员。 小程序端主要是常规的信息提交,我们来看下后端的Go代码: // 域名证书检测计划任务,每天固定6点执行 func CronCertCheck() { logger.Info("启动域名证书检测计划任务") // 获取用户webhook,有webhook则进行域名检测 webhooks, err := dao.GetAllWebhook() if err != nil { logger.Error("计划任务域名证书检测加载Webhooks错误,", err) return } // 循环扫描 for _, v := range webhooks { uid := v.Uid url := v.Url ht := v.HookType // 获取单个用户的域名 dbDomains, err := dao.GetWDomainByUid(uid) if err != nil { logger.Error(uid, "计划任务域名证书检测获取域名列表错误,", err) continue } if len(dbDomains) == 0 { logger.Error(uid, "计划任务域名证书检测用户域名数量为0") continue } // 解密URL地址为WEBHOOK password := fmt.Sprintf("domain:%d:%d", uid, ht) webhook, err := cryptoutil.AesDecryptWithPassword(url, password) if err != nil { logger.Error(uid, "计划任务域名证书检测webhook解密失败,", err) continue } // 获取域名列表,需要去重,用户是可以上传重复域名的。 domains := make([]string, 0) domainMap := make(map[string]bool, 0) for _, n := range dbDomains { name := n.Name isExist := domainMap[name] if isExist { continue } domains = append(domains, name) domainMap[name] = true } // 开始批次检测,并将检测结果返回 chkResult := CheckDomainsCert(domains, nil) // 如果结果不为空,则发送webhook通知 if len(chkResult) > 0 { notifyCfg := NotifyConfig{ Type: ht, URL: webhook, RetryTimes: 3, RetryDelay: 5, } result := NotifyContent{ Title: "以下域名的SSL证书将在7天内过期:", Domains: chkResult, } go SendWebhookNotificationWithRetry(notifyCfg, result) } // 休息1秒 time.Sleep(1 * time.Second) } logger.Info("域名证书检测处理:", len(webhooks)) } 在小程序上线后,用户使用极少,我不知道具体原因,可能是不信任功能,Webhook不想暴露,域名不想暴露?我又开发了客户端工具,纯本地运行,使用Wails3开发,是图形化桌面客户端,域名导入和通知都是在本地运行。并且增加了很多功能。例如支持HTTP/3,支持系统托盘,支持开启启动,支持导入更多域名,支持更多Webhook通知,监控面板更精细等。 ...

2018-01-10 · 2 min · Eagle