Mosquitto 安全加固从匿名访问到多用户 ACL 细粒度权限控制

我在豆子碎片项目中开始使用MQTT,比如文章上传后我使用了MQTT来通知审核的消息。这样就要求不能让张三接收到属于李四的消息。当然还有其它场景使用MQTT,比如ESP设备连接MQTT。比如用户上传文章后,我的ESP设备的LED就会闪烁,蜂鸣器也会响。我服务器上部署的MQTT Broker是mosquitto。在 Mosquitto 实例上配置身份验证非常重要,这样未经授权的客户端就无法连接。在 Mosquitto 2.0 及更高版本中,你必须明确选择身份验证选项,然后客户端才能连接。早期版本中,默认设置是允许客户端无需身份验证即可连接。 Mosquitto身份验证有三种选择:密码文件、身份验证插件和匿名访问。可以单独使用,也可以使用三个选项的组合。在 Mosquitto2.0 及更高版本中,可以通过配置文件中将 per_listener_settings 设置为 true,让不同的侦听器使用不同的身份验证方法。除了身份验证,还应该考虑访问权限控制,以确定哪些客户端可以访问哪些主题。我们来看下不同选项的配置。 密码文件设置 密码文件是一种将用户名和密码存储在单个文件中的简单机制。适合于有相对较少的静态用户。请注意,修改了密码文件后,需要重新加载 mosquitto。 创建密码文件,可以使用mosquitto_passwd工具,命令如下: mosquitto_passwd -c <password file> <username> 请注意,使用-c 标志将覆盖已存在的文件,如果向已存在文件添加,请去掉-c 标志。 要开始使用密码文件,需要配置 broker,在配置文件中,添加如下项: password_file <path to the configuration file> 密码文件必须能够被运行 mosquitto 的用户读取,也就是必须具备读取它的权限。 身份验证插件设置 如果需要更多的控制来认证用户,则需要使用认证插件。具体的特性依赖于使用的认证插件。 可使用的插件有: mosquitto-go-auth ,它提供了各种后端来存储用户数据,例如 mysql,postgresql,jwt 或 redis 等。 Dynamic security,动态安全插件,仅适用于 2.0 及更高版本,可提供原创管理的灵活的代理客户端、组和角色。 配置身份验证插件依赖你的 Mosquitto 的版本。版本 1.6.x 和以前的版本,使用auth_plugin选项。这个选项在版本 2.0 也被支持。 listener 1883 auth_plugin <path to plugin> 版本 2.0 及更高,使用plugin选项。 listener 1883 plugin <path to plugin> 我使用了这个选项,因为这个可以对接数据库,我可以方便的在数据库中维护权限,这样我不需要像密码文件那样,修改一个权限还需要重新启动Mosquitto。这对于对接其他系统或扩展业务非常方便。 匿名访问设置 配置匿名访问,请使用allow_anonymous选项。 ...

2025-01-01 · 1 min · Eagle

文章上传命令行客户端升级版本使用Cobra支持更多命令

对于添加更多的命令,使用 flag,就有点麻烦了,这次我们使用一个更高级的库 cobra。同时,我们使用 viper 替换 ini 库,这个库可以读取多种格式的配置文件,可以读取环境变量。 要实现的功能如下: 上传文章 文章删除 更新文章标题 更新文章关键字 更新文章内容 文章公开开关 文章加锁开关 根据标题查找文章 根据关键字查询文章 现在使用 cobra 实现上面的命令。 首先,我想要的效果如下: 上传文章 gart upload title keyword filename ispub islock 删除文章 gart remove uuid 更新文章标题 gart updatetitle uuid title 更新文章关键字 gart updatekeyword uuid keyword 更新文章内容 gart updatecontent uuid filename 更新文章公开或不公开 gart updatepub uuid ispub 更新文章加锁或不加锁 gart updatelock uuid islock 根据标题或关键字查找文章 gart search content 上面的 upload,remove,updatetitle,updatekeyword 等,在 cobra 中都是命令。title keyword 等都是参数。 我在读 cobra 文档后,最疑惑的地方就是标志和参数,这两者最大的区别就是标志需要在命令中添加–flag 然后是值,而参数是直接跟在命令后,直接就是内容的。标志可以更改程序的行为。 ...

2024-09-22 · 5 min · Eagle

使用Go自制豆子碎片文章上传命令行客户端

这是 Golang 实现的上传文章以及管理文章的一个命令行工具。 项目地址 https://gitee.com/littletow/upart-go 实现一个上传文章的命令行工具 这是一个最初的版本,使用 flag 和 ini 来实现文章上传功能。使用 flag 来解析命令行参数,使用 Ini 配置文件,记录识别码,以及 token。记录 token 的原因是因为每次启动命令行,都需要重新获取 token,为了减少 token 获取次数,在获取到 token 后,同时存储到 Ini 配置文件中。每次命令行启动,优先查看配置文件中的 token。 开发这个工具主要有以下几个技术要点: 从命令行获取参数 从配置文件中读取参数 读取文件内容 请求后端接口 我们通过分析后,需要定义以下几个参数: title 标题 keyword 关键字 filename MD 文件名 ispub 是否公开 islock 是否加锁 使用 Go 代码定义参数和结构体 var ( title string keyword string filename string ispub int // 1 pub islock int // 1 lock ) func init() { flag.StringVar(&title, "b", "", "文章题目") flag.StringVar(&keyword, "k", "", "文章关键字") flag.StringVar(&filename, "f", "", "MD文件") flag.IntVar(&islock, "l", 0, "是否加锁") flag.IntVar(&ispub, "p", 0, "是否开放") } func main(){ flag.Parse() } init() 在 main 函数前调用。需要在 main 函数中调用 flag.Parse(),这一步非常关键。之后就可以使用变量了。 ...

2024-09-21 · 3 min · Eagle

记一次因直接使用unwrap导致Rust上传工具崩溃问题

最近,更新了上传文章工具 rust 版本,这个版本使用的库为 nwg,即 native-windows-gui 库,该库支持老的 Windows GUI。 昨天,当我准备在另一台电脑上录制视频时,发现运行不起来,GUI 界面闪现一下,就退出了。以为版本太老,我先升级了 Rust 版本到最新版本(😭,不应该的原因,固定版本反而更可靠稳定一些)。 rustup update 升级完成后,cargo clean ,再 cargo run 重新运行。 郁闷,还是报错。 将 main.rs 文件中该行注释,#![windows_subsystem = "windows"],可以在命令行中查看报错信息,提示是找不到文件。我想了下,只有图片是文件,然后我将图片的路径修改,发现修改之后,编辑器还提示错误,找不到文件。重新改回去后,编辑器提示错误消失。说明不是这里的错误。 我真的晕了。从网上查找问题原因,没有找到此类问题的解决方法,郁闷。在查找时,发现了另一个 Windows 官方支持的 rust 库,就叫 windows,打算有时间了用这个库重写一下。 今天,我又查看了下另一台电脑上的这个项目,发现可以正常运行,将 rust 升级到最新版本后,还是可以运行。在看到配置文件后,我才恍然大悟,原来是查找的是这个配置文件。我赶紧扒拉代码确认,发现确实是这个问题。 let conf = Ini::load_from_file("conf.ini").unwrap(); 上面是使用文件的地方,找不到文件 panic。我这里优化了一下,让错误提醒的更明显一些。这样我就瞬间能知道问题原因了。 优化后的代码如下: let conf = Ini::load_from_file("conf.ini").expect("please config conf.ini file"); 有些问题其实很简单,但由于时间长了,还是会一头懵,如果报错信息提示完善一点,会很快定位到问题。

2024-01-01 · 1 min · Eagle

使用Rust基于 Native-Windows-GUI 构建的轻量小程序码生成工具

早于官方后台功能的桌面实践:探索使用 Rust + native-windows-gui 打造极致轻量的小程序工具。 在沉浸于 Wails 3 开发 Mole 客户端的过程中,我不禁想起了我在 2023 年 11 月完成的一个小项目——mp-qrcode-gen。 虽然现在微信小程序后台已经集成了生成小程序码的功能,但在 2023 年,开发者想要快速、批量或者自定义参数生成小程序码,往往需要自己写脚本。于是,我用 Rust 编写了这款极致轻量的桌面工具。 为什么在 2023 年做这个工具? 当时,自己需要根据页面去生成一些小程序码,线下张贴使用。每次使用服务端修改代码感觉非常不便利。于是做了一个这样的生成小程序码的工具。 当完成后,我还发了视频号进行了推广,发现很多人还下载使用了。其实运营人员和开发者在准备线下物料(如海报)时,通常会面临一个痛点: 接口调用门槛:微信官方提供的是 API 接口,非技术人员无法直接使用。 参数复杂:小程序码分为“受限”和“不受限”两种,参数限制各异,手动拼凑 URL 极易出错。 批量需求:线下场景往往需要针对不同页面、不同场景值生成大量图片,网页端操作效率低下。 技术选型:Rust + native-windows-gui 在那个时期,我并没有选择 Electron 这种庞然大物,而是选择了Rust,保证了极高的运行效率和内存安全。和native-windows-gui (NWG),这是一个非常纯粹的 Windows 原生 GUI 库。它不包含浏览器内核,直接调用 Windows API,生成的 .exe 文件体积非常小。选择Rust还有一个原因就是我在学习它,需要一个项目练手。 核心功能拆解 做的工具界面非常简单直观,配置完 AppID 和 AppSecret 后,就可以使用它生成小程序码了。点击生成按钮后,工具会直接调用微信接口并处理返回的二进制流,将其保存为本地图片文件。无需打开浏览器,所见即所得。 根据微信官方的接口,我实现了两种模式,分别为受限模式和不受限模式。 1. 数量受限模式 (API A/B) 特点:支持输入完整的、带有超长 URL 参数的页面路径。 场景:适用于对码量要求不高(总计 10 万个以内),但需要精准携带复杂参数的场景。 2. 数量不受限模式 (API C) 特点:页面路径较短,但支持独立的 Scene(场景值)字段。 场景:这是线下物料最常用的模式,可以无限次生成,通过场景值来区分不同的线下投放点。 开源和思考 虽然几个月后,微信官方后台也逐步完善了类似的功能,但 mp-qrcode-gen 的意义在于:在需求未被完全满足的真空期,个人开发者可以用技术手段快速提供解决方案。 ...

2023-11-02 · 1 min · Eagle

构建高可靠 APK 上传工具,彻底终结网络超时与手误引发的生产事故

记录一个未公开的 Rust 实战小工具,分享如何通过技术手段解决网络超时与手误操作带来的生产事故。 在自学 Rust 的那段日子里,我除了开发开源项目,还为日常工作量身定制了一些不公开的内部工具。其中最令我印象深刻的,是一个看似简单的 APK 文件上传小工具。 虽然它只是一个 Rust + native-windows-gui (NWG) 编写的单一界面应用,但它解决的两个核心生产问题,至今仍对我有着深远的影响。 工具画像:极简与高效 这个工具的界面极其克制: 交互区:一个醒目的文件拖拽控件,文案提示“请把 APK 文件拖拽到这里”。 表单区:版本号、Version Code、更新描述、是否强制升级(开关)。 执行区:一个大大的“上传”按钮。 它的核心逻辑非常纯粹:校验必填项 -> 调用内部上传 API -> 上传文件 -> 返回成功或失败的对话框。 故事一:消失的 60 秒与隐藏的超时限制 工具上线初期,一切运行平稳。由于办公室带宽充足,APK 的上传时间通常维持在 40 秒左右。 事故发生: APK 在集成广告后,经常会发生失败的情况,客户端弹出“超时”的报错。 排查过程: 我首先检查了后端服务,发现后端接口完全没有超时限制,服务器日志显示连接是被客户端主动断开的。 多次重复上传文件使用有线和无线对比,经排查是文件变大,由于公司无线网络波动,上传速度时好时坏,就会偶尔出现失败的情况。对比排查,发现失败的情况上传时间都超过了 60 秒。 回到 Rust 代码中,我发现当时为了追求简洁,直接使用了 HTTP 客户端的默认配置。而这个默认配置的 Timeout 为 60 秒。 解决方案: 在那个瞬间我意识到,工具不能只考虑“理想状态”。我手动将本地超时时间放宽至 5 分钟,并增加了状态提示。上传时不会再出现超时的情况了。 教训:永远不要依赖默认的超时设置,尤其是在处理文件上传这类长耗时操作时,本地的容错范围必须根据业务场景精细化配置。 故事二:包名校验——把人为失误挡在门外 这是这个工具最有价值的一次功能进化。 事故发生: 有一次,同事不小心将应用 A 的打包产物当作应用 B 上传到了官网,导致用户下载后发现软件张冠李戴。这属于严重的生产事故。 排查过程: 这个客户端软件调用内部API后,会自动将上传的文件名修改为固定的软件下载文件名,显示在网站上。经测试,如果将应用A的打包产物使用应用B上传,在官网就会出现相同的情况。解决这个方法也非常简单,就是上传的时候小心细致一点,应用A使用A客户端软件,应用B使用B客户端软件。但是人总会犯错,尤其是在面对多个长相相似的 .apk 文件时。通过肉眼观察文件名来区分应用,是极其不可靠的。 ...

2023-01-02 · 1 min · Eagle

实战笔记:实现一个语音验证码自助拨打版本

自助语音验证码是通过拨打VOIP电话自动获取验证码,验证码可用来豆子笔记网站认证。这个纯粹是自己的兴趣驱动的。掌握了此项技术,你也可以应用于其它地方。 我是使用Asterisk实现的,Asterisk 是一个流行的开源通信平台,它提供了构建各种通信应用的灵活而强大的解决方案。它被广泛应用于企业内部电话系统、呼叫中心、语音邮件等场景。Asterisk 支持多种通信协议,包括 SIP、H.323、MGCP 和 SCCP,并且能够与传统的 PSTN 线路深度兼容。此外,Asterisk 还支持通过 Inter-Asterisk eXchange (IAX)协议进行语音 IP 传输,允许数据和语音同时在网络上传输。 自助语音验证码使用说明 你需要准备: 软电话,或者支持 VOIP 的电话机 AST 账户,AST 账户从豆子工具小程序中获取,功能菜单为获取 AST 账户。 注意:该功能目前已经下架,这里仅作为备忘记录。 首先,使用获取到的账户配置你的电话。然后拨打 8000,即可自动获取验证码。如果您有什么问题,欢迎关注公众号【技术源泉】私信我。 技术实现 它的工作原理是:用户通过 VOIP 电话连接到 Asterisk,当拨打固定的数字 8000时,Asterisk 将调用 AGI 接口获取服务端的API生成验证码,然后将获取到的验证码拆分通过语音文件播报给用户,播报完毕后自动挂断。 我们根据自助语音验证码原理,将自助验证码实现分解为以下几个技术要点,只要我们解决以下技术要点,就可以实现自助语音验证码。 技术要点: 1,如何播放音频文件?音频文件从哪里来?如何播放动态数据? 2,如何存储用户信息?如何使用数据库存储用户信息? 3,如何获取验证码? 下面内容是我对各个技术点的对应解决方案。 1,Asterisk 自带 Playback 应用,可以通过它播放音频文件。音频文件需要我们提前录制好,并且转换为对应的音频格式,最简单的方法就是使用手机上的录音机。当在拨号计划中多次执行 Playback 应用,Asterisk 会将音频流自动连接起来。所以我们可以使用循环多次执行 Playback 应用即可。 2,Asterisk 使用配置文件写入 SIP 用户信息,但当写入新的 SIP 用户信息后,需要重新加载配置文件。为了方便和第三方对接,我们推荐使用数据库。Asterisk 支持数据库,并且可以实时获取用户信息。 3,Asterisk 支持 AGI 接口,我们可以使用 AGI 获取第三方应用的验证码,获取后和提前录制好的文件结合起来进行播放。 当掌握了这些技术点后,我们就可以灵活应用到其它解决方案。 下面我们来看具体实现: 1,音频文件处理。我们使用手机上的录音机来录制音频文件。Android 录音机录制的音频文件格式为 mp3,如果是 amr 格式,请使用豆子工具音频格式转换功能,转成 mp3 格式文件。IOS 录音机录制的音频文件格式为 m4a,请使用豆子工具音频格式转换功能,转成 mp3 格式文件。我们还需要使用 ffmpeg 将 mp3 文件转成 g711a 格式文件。这个 mp3 转 g711a 功能也可以在豆子工具中使用。 ...

2022-08-19 · 2 min · Eagle

实战笔记:当我想查端口却没装 Telnet 时,我决定自己写个工具

作为开发者,你一定遇到过这种“抓狂”的时刻: 某次我急需确认服务器上的一个端口是否正常开启,习惯性地打开 Windows 命令行准备敲下 telnet 命令。结果弹出的却是:“telnet 不是内部或外部命令”。 由于系统刚重装,这个组件还没勾选。当我从控制面板找到它、安装、甚至可能还要重启系统时,这套复杂的操作让我陷入了反思:为了检测一个端口,至于这么麻烦吗? 更进一步想,如果我手头没有电脑,只有一部手机,我该如何快速判断服务器防火墙是不是忘了开? 痛点:环境依赖与生态限制 在实现这个功能之前,我有过两个思考维度: 环境依赖:无论是 Windows 的 Telnet 还是 Linux 的 nc,都依赖当前操作系统的环境配置。 小程序限制:微信小程序虽然有网络请求能力,但它必须配置服务器域名白名单。如果你想直接用小程序前端去探测一个随机的 IP 或端口,微信的底层安全机制是不允许的。 方案:Go 后端代劳,小程序只做“遥控器” 为了绕过这些限制,我在“豆子工具”里实现了一个远程端口检测功能。它的逻辑非常直接且高效: 前端交互:用户只需在小程序里输入目标服务器的 IP/域名 和 端口号。 后端核心:数据提交到我用 Go 编写的后端服务。 模拟连接:后端服务代替用户执行 TCP 连接探测。Go 语言的 net.DialTimeout 在这里非常实用,可以精准控制探测时间,避免由于网络超时导致的页面死等。 结果反馈:后端将“连接成功”或“连接失败”的结果返回给小程序展示。 价值:随时随地的“运维眼” 自从这个功能上线后,我解决了很多尴尬的场景: 排查防火墙:在云厂商后台改了安全组策略后,掏出手机点一下,秒知是否生效。 现场交付:在客户现场没有电脑时,快速确认后端服务是否已经拉起。 零部署成本:再也不用担心当前电脑有没有装 Telnet 或 Netcat。 技术实现 这里的难点在于服务器端实现,小程序端仅仅提交FORM表单就可以了。下面我们看看服务端的Go的核心实现: // 检测端口是否被打开? func IsPortOpened(ip string, port int) bool { addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port)) conn, err := net.DialTimeout("tcp", addr, time.Second*3) if err != nil { return false } if conn != nil { conn.Close() return true } return false } 这里仅能探测TCP端口,UDP端口我还没有思路,等有思路了我再在这篇文章里补充。 ...

2018-02-05 · 1 min · Eagle

实战笔记:为了管好那堆记不住的密码,我给自己写了个全加密密码本

虽然现在的 App 普遍支持手机号或微信一键登录,但对于开发者来说,GitHub、服务器、各类海外服务的登录依然离不开“邮箱+密码”的传统模式。 我曾经历过一段漫长的“密码管理进化史”: Excel 时代:最早用一个加密的 Excel 表格记录,在电脑端还凑合。 Flutter App 时代:为了手机查看方便,我曾写过一个简单的 Flutter App,支持指纹查看,但功能简陋,只有新增功能,连“修改”功能都没有。 直到有一次,GitHub 要求我重新登录,由于我频繁重置密码且新旧密码不能重复,导致我彻底记混了。在另一台电脑前尝试了无数次失败后,我意识到:我需要一个足够安全、随时可用、功能完整的个人密码管理工具。 于是,“豆子工具”里的小程序版密码本诞生了。 1. 设计核心:绝对的隐私与自由 在设计之初,我就定下了两个原则:不联网、重加密。 纯离线运行:为了打消用户(包括我自己)对隐私的顾虑,我砍掉了所有联网备份功能。所有的密码数据仅存储在手机本地,备份只能通过聊天文件导出。 三重加密体系:这是我花费心血最多的地方。主密码(Master Password)不存储在设备上。 第一步:用主密码解密 RSA 私钥。 第二步:用私钥解密 AES 密钥。 第三步:用 AES 密钥解密具体的 JSON 序列化记录。 这种混合加密机制确保了即使手机丢失,只要主密码不泄露,数据依然是安全的。 2. 攻克开发中的“大山” 这个功能的开发过程远比我想象中痛苦。尤其是在小程序环境下处理文件缓存、二进制流转换和加解密逻辑,经常一个 Bug 就要调试好几天。 中途我好几次想过放弃,觉得用 Excel 也可以凑合。但每次想到反复重置密码的痛苦,还是咬牙坚持了下来。为了稳定性,我将原本不稳定的二进制存储改为了 UTF-8 编码的 JSON 序列化,最终实现了丝滑的操作体验,这都是心血的教训。 3. 功能完善:不仅仅是“存一下” 相比之前的 Flutter 版本,这次我补全了所有短板: 增删改查:支持搜索功能,输入标题就能快速定位账号。 备份恢复:加入了完整的备份机制,更换手机时可以轻松迁移数据。 批量操作:支持导入和导出,方便从其他平台平替过来。 密码提示:为了防范“忘记主密码”这个终极灾难,我加入了密码提示功能(建议设一个只有自己懂的暗语)。 4. 字段灵活,满足多样需求 每一条记录都包含了:标题、URL、用户名、密码、备注。 无论是一个服务器的 SSH 密码,还是一个冷门网站的登录信息,都能井井有条地分类存放。 5. 核心代码展示 纯小程序本地原生实现,首先是密码本初始化,它会根据用户输入的主密码,生成密码本文件。 // 第一次使用设置方法 async setupFirstTime(masterPassword, passwordHint) { try { // 1. 检查是否已有密码本 if (!passwordManager.hasPasswordBook()) { console.log('1. 创建空密码本...'); const createResult = await passwordManager.createEmptyPasswordBook(masterPassword, passwordHint); if (createResult.success) { console.log('✓', createResult.message); } else { console.log('创建失败:', createResult.error); wx.showToast({ title: createResult.error, icon: 'none' }); return; } } } catch (err) { console.log('创建密码本错误,', err) } // 保存主密码安装和提示 wx.setStorageSync('pwbookInstall', new Date().toLocaleString()); wx.setStorageSync('passwordHint', passwordHint || ''); wx.setStorageSync('pwbookBakTime', getSecTs()); this.setData({ isFirstTime: false, isUnlocked: true, showPasswordDialog: false }); wx.showToast({ title: '初始化成功', icon: 'success' }); this.loadPasswordList(); }, 创建密码本文件核心代码: ...

2018-01-11 · 3 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