记录如何通过 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通知,监控面板更精细等。

下载本地客户端豆子域名证书,请访问地址:https://91demo.top/tools/

总结

很多时候,自动化并不是为了追求多么高大上的技术,而是为了把人从那种机械、高风险的记忆工作中解放出来。

这个小工具的逻辑虽然简单,但它是我内容生态中非常重要的一环。它验证了:只要抓住了痛点,简单的技术组合也能产生巨大的生产力。