从 wasm-bindgen 到 C-ABI 写微信小程序WASM:一次真实的 WASM 落地复盘

在写完 ESP8266 IoT 架构之后,我就在想一件事情:既然代码开源,IoT 网关岂不是谁都能连? 于是我试图在微信小程序端做文章。但开源的小程序不能硬编码密钥,也不能仅靠接口鉴权。 我想到了很早之前预研的 WebAssembly(WASM)。配合后端防护,可以大幅提升攻击者的成本。 一、背景与目标 实现的功能非常简单: 传入 小程序 APPID + 设备 ID + 时间戳,使用 Ed25519 签名,返回 Token。 以前在微信小程序调通过 WxWebAssembly(简称 wxwasm),但当时主要处理 Int 类型,这次需要返回字符串。 当我拿出以前的预研代码进行修改时,我才意识到: “能用 Rust 写 WASM” 和 “能在微信小程序里稳定运行”,完全是两回事。 二、第一阶段:理想很丰满 —— Rust + wasm-bindgen 最初的环境是: Rust 1.95 wasm-bindgen wasm-pack Cargo.toml [dependencies] wasm-bindgen = "0.2" ed25519-dalek = { version = "2.1", features = ["rand_core"] } hex = "0.4" lib.rs [wasm_bindgen] pub fn generate_token(appid: &str, device_id: &str, timestamp: &str) -> Result<String, JsValue> { // Ed25519 签名逻辑 } 编译很顺利(就是有点慢 😭),本地 test.html 测试一切正常。 ...

2026-05-24 · 3 min · Eagle

对于微信小程序的一些回忆和思考

有些事,不做笔记真的意识不到已经过去了这么久。 最近在整理博客内容时,我翻到了小程序的最早版本记录。那是 2018 年,最初的它简陋得甚至有点滑稽:整个页面只有一个按钮,点一下,生成一个随机数。 一、豆子工具小程序 这个小程序项目名称“Wander”,它的小程序名称换了很多,只有到微信开始要求备案和实名后,才开始真正的确定下来。 这几年里,我断断续续的维护着这个项目,是由于自己没事的时候喜欢做一些东西,当忙起来时又会忘记了它,当有新的想法时,会改变它,然后更换微信小程序名称。印象深刻的有几次:1,随机数,纯粹是为了小程序上线,为了学习小程序,做了最简单的示例,然后上线了。2,图片集,制作了一个图片列表,然后展示图片,图片存储在云服务上,那是还是免费,并且提供两个环境,为了学习云存储,云函数。3,找厕所,这是我最费心最耗时最吃力的一次改进,为了学习地图map,最后没有完成放弃了。4,自用工具,音频格式转换,图片格式转换,MD5哈希等小工具,从这之后应该算是入门了吧,毕竟以前是自学,现在因为有了工作经历,感觉自己进步了,思维和技巧不在停留在简单的示例教程上。5,工具+项目,以前自学的一些项目以及现在新做的模块都包含进了这个小程序,工具还是自己在用,项目呢,可以用来分享给有需要的人。 在这几年里,我在这款小程序上倾注了太多的时间和精力,看起来是总在变化,没有一个明确的目标,但是现在回忆总结一下,还有在一个圆内,只不过半径大了一些而已。它像是我个人开发者生涯的一个“活化石”,记录了我每一个阶段遇到的问题和想出的方案。 总结了最近几年开发过和上线了的一些内容。现在的“豆子工具【已备案】”已经从当年的随机数生成器,进化成了一个覆盖网络、多媒体、开发调试的全能工具箱: 算法:随机数生成,MD5,SHA256,RSA,AES,Base64,URL编码等。 网络:获取公网IP地址和归属地,获取 WIFI 公网地址、云厂商安全组管理、查服务端口、域名证书检测,局域网TCP、UDP、WebSocket调试,ESP网络配置等。 多媒体:音频格式转换(m4a 转 mp3)、图片格式转换(png 转 webp)、二维码识别与生成,图片拼接等。 系统功能:邮件通知、微信服务通知、微信聊天分享,打开文档,导出文档等。 小程序一直在迭代中,这中间的很多功能因为自身原因或者微信审核政策的原因,遗憾地消失在了版本更迭中。 二、豆子碎片小程序 这个小程序项目名称“Visit”,它的微信小程序名称也是变更了多次,只有在备案时才确定了下来。不过它的内容现在已经融入了豆子工具v11.0.0版本中。只是为了维护方便。 这个Visit小程序在Wander小程序一年后注册,刚开始做的功能就是浏览 Go Web 框架 Beego 的教程。当时,正在自学 Beego,而 Beego 的网站时常打不开,就想做一个小程序,可以浏览 Beego 内容。刚做出来的一段时间,因为新鲜度小火了一把。 后来又切换到唐诗主题,当时自己费劲的采集了小学的古诗词作为内容源。印象最深的是添加拼音,当时没有AI,从网上搜索和自己琢磨。最后还是放弃了,一是因为素材的原因,没有解析,没有音频,界面美观一般(回忆来看)。到备案的时候,卡住了,个人主体不允许出现诗词这些关键字。要改名字了,我觉得内容不符合,就换内容了,对于我来说,名字需要和内容匹配,心里才会舒服。当时为了选内容而发愁,因为不想放弃这个小程序。想了几天后,觉得把自己的开发经验做成笔记吧。因为碎片化,所以叫豆子碎片。添加豆子前缀,是因为当时备案时,需要区分小程序名称。觉得豆子比较简单上口。那段时间学了towxml,将内容使用Markdown来编写。 这之后,虽然也改过游戏关卡,作品集,模块集市。但都是通过Markdown来编写和展示内容。 三、思考现在与未来 今年已经是2026年,AI相当发达,我以前收录的代码片段和想法思考,AI已经分分钟替代,并且做的更好。现在的小程序在备案和实名后,自己的小程序更是无人问津。到了今天,已经没有精力再去想尝鲜了,为了一个新出的功能或者想法去搞好几天去研究了。总结了小程序最近几年的变更,虽然内容一直在变,但是自己在技术上并没有新的突破,依旧是列表详情布局,依旧是本地和后端联动。以前围绕着微信小程序为中心,当抬高视野时,发现局限性很大,小程序很多内容是不能做的,很多功能是做不了的。当前的豆子工具是功能小程序,已经可用的功能如音频格式转换,图片格式转换继续保留。豆子碎片当前的模块集市依旧保留,它是一个项目列表,可以查看详情并可获取到源码和实用客户端工具。所以可以将豆子碎片和豆子工具的内容合并到一起,减少小程序的维护,可以有更多的精力去做一些其他事情。2026年已经计划在博客中写文章了,已经不再使用小程序记笔记,因为小程序的缺陷很明显,特别对于长篇文章。那么未来就可以这样,首页网站可以做Web实验性项目,博客中写项目实现和想法,豆子工具中做小程序最合适的工具和上架模块源码,公众号主要用来引流和发布一些比较好的博客文章,或者发布一些工具更新,或者一些好的开源项目。以后的内容可以深入一些,实用一些,比如以前的模块,现在可以做一个产品。然后继续新的方向,桌面客户端工具和嵌入式开发尝试,继续深入Rust和Go。

2026-04-24 · 1 min · Eagle

基于 Go + 小程序构建“口袋指令中心”,实现远程发布控制

我日常涉及 Hugo 博客发布、客户端打包、Nginx 运维等多种重复性脚本。每次都要 SSH 连服务器并执行命令,操作链过长,也不方便,特别是在身边没有电脑的情况。 曾经想过使用HTML 远程调用,又担心安全,使用了微信公众号发消息,也特适合这个场景,但是因为微信更新,私信入口现在非常难进入。所以就想能否通过自己的小程序实现远程控制和鉴权。构建一个通用的执行引擎,通过小程序远程触发,且具备零前端修改(免于小程序上线审核)的扩展能力。 系统架构设计 为了实现“明天增加脚本,小程序不发版”的目标,采用了“配置驱动” 模式。即“配置在云端,指令在指尖”。通过将业务逻辑(脚本路径与名称)完全从前端小程序中解耦,实现一套代码支持无限扩展的运维能力。 核心流程 后端 (Go):维护一个脚本配置列表(数据库或配置文件)。 前端 (小程序):启动时请求后端接口,拉取可用脚本列表。 触发:使用时选择脚本名称,点击执行。 鉴权:后端校验小程序 OpenID,仅允许本人指令生效。 技术实现 系统分为三层,确保安全性与扩展性的统一: 配置层 (Go Config):在服务器端定义脚本的 ID、名称和实际路径。 鉴权层 (WeChat Auth):利用微信小程序 OpenID 建立强一致性的身份白名单。 展示层 (Mini Program UI):动态拉取后端配置,仅负责“展示列表”与“触发指令”。 技术实现方案 A. 后端:动态脚本引擎 (Go) 后端不再硬编码脚本路径,而是定义一个结构体: // 脚本任务定义 type ScriptTask struct { ID string `json:"id"` // 前端传递的任务标识 Name string `json:"name"` // 小程序界面显示的文字 Command string `json:"-"` // 实际执行的脚本路径 (对前端保密) } // 示例配置(可存放在 JSON 文件或数据库中) var tasks = []ScriptTask{ {ID: "hugo-post", Name: "发布 Hugo 文章", Command: "/scripts/deploy_hugo.sh"}, {ID: "build-client", Name: "构建客户端", Command: "/scripts/build_mole_go.sh"}, {ID: "nginx-restart", Name: "重启 Nginx 服务", Command: "systemctl restart nginx"}, } 对外接口定义: ...

2026-02-04 · 3 min · Eagle

实战笔记:实现一个语音验证码远程呼叫版本

在实现了主动使用VOIP客户端拨打8000号码播报语音验证码的功能后,我发现了一个最大的缺点,就是这需要用户主动去操作。这对于想使用API集成的第三方应用来说根本无法实现。 在思考之后,我决定实现一个可以调用API就呼叫VOIP客户端的版本,当呼叫用户并且用户接通后,自动播报语音验证码的功能。当实现这个功能后,它的好处是显而易见的。比如,可以集成到嵌入式,集成到第三方网站,直接调用API就可以呼叫出去。 那么该如何实现它呢? 一、 系统原理 传统的拨号方案(Dialplan)是静态的,而 ARI 允许我们动态控制。整个“API 触发呼叫并播报”的流程如下: 触发阶段:第三方系统通过 API 向 Go 服务发送呼叫请求(包含目标 ID 和验证码)。 呼叫发起(Originate):Go 服务调用 Asterisk ARI 的 /channels 接口。此时 Asterisk 会尝试向 PJSIP 终端(或通过中继向手机)发起呼叫。 接通监听(Stasis Start):一旦用户接起电话,该通道会被移交给一个名为 Stasis 的应用。此时 Go 服务会收到一个“通道已接通”的 WebSocket 事件。 语音合成与播放:Go 服务识别到接通后,调用播报指令(可以播放预录音文件,或对接 TTS 引擎生成的语音流)。 挂断处理:播报完毕后,服务发送挂断指令,释放资源。 二、系统架构 [第三方API] --> [Go 后端服务] --(REST API)--> [Asterisk ARI] | | (WebSocket) (PJSIP/IMS) | | [接通状态回调] <--- [用户终端接听] 三、 核心代码实现 (Golang) 这里使用了 GitHub 上的 go-ari 库。 1. 初始化 ARI 客户端 import ( "github.com/v5" "github.com/v5/client/native" ) // 连接到 Asterisk ARI cl, err := native.Connect(&native.Options{ Application: "voice-verify", // 必须与 asterisk.conf 配置一致 Username: "admin", Password: "password", URL: "http://localhost:8088/ari", }) 2. 实现呼叫并播报语音验证码 这是核心逻辑:接收参数 -> 发起呼叫 -> 监听接通 -> 播放语音。 ...

2026-01-13 · 3 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

实战笔记:为了不再发错下载链接,我给工具箱加了“扫码鉴定”

小工具可以解决大问题。在软件发布流程中,最尴尬的事情莫过于:宣传图已经发出去了,用户扫码后却发现链接打不开,或者下载了一个旧版本的安装包。 我就亲身经历过这么一次“翻车”事故。 事故现场:一个 URL 引起的麻烦 有一次,我在更新软件下载页面后,由于换了一个新的 URL 地址,在生成二维码阶段没有进行严格校验,就直接把二维码发布了出去。 结果当用户兴冲冲地扫码下载时,发现软件运行异常。我对比了半天才发现,生成二维码时填写的 URL 链接版本号写错了一个字符,导致用户下载到了一个有问题的版本,这是偶然中的极端。 虽然这只是一个低级错误,但它让我意识到:在二维码挂载后、正式发布前,必须有一个极简的“内容鉴定”环节。 痛点:如何快速、无痛地校验? 通常我们校验二维码,习惯性地直接拿手机扫一下。但如果二维码指向的是一个大容量的 APP 下载包或复杂的跳转链接,扫码后手机会自动触发下载或进入复杂的网页路径。 我其实并不想真的下载并安装测试(那是后续的 QA 环节),我只是想一眼看到二维码里到底写了什么字符,确定那个 URL 是不是我想要的那一个。 实现:借力小程序的原生能力 既然发现了痛点,解决起来就非常顺手了。得益于微信生态对扫码的深度支持,我在“豆子工具”里实现了一个“二维码识别”功能: 核心逻辑:直接调用小程序的 wx.scanCode 接口。 功能表现:不仅支持扫描实物二维码,还支持从手机相册里直接读取生成的二维码图片,还支持小程序码扫描,支持查看页面路径和scene值。 结果反馈:系统不会触发自动跳转,而是将二维码包含的原始文本、URL、一维码内容直接以纯文本的形式展示在屏幕上。 价值:多看一眼,少出一次错 现在,每当我生成一个新的二维码,无论是用于软件下载、文档分享还是活动跳转,我都会习惯性地用自己的工具“扫一下”: 确认 URL 参数是否完整。 确认是否有肉眼难以察觉的拼写错误。 确认二维码的类型(一维码还是二维码)是否符合预期。 技术实现 使用小程序纯原生实现,仅仅做一个页面展示即可。 scanQRCode: function () { const that = this; wx.showLoading({ title: '识别中...', mask: true, }) wx.scanCode({ onlyFromCamera: false, scanType: ['qrCode', 'barCode', 'datamatrix', 'pdf417', 'wxCode'], success: (res) => { wx.hideLoading() if(res.scanType=='WX_CODE'){ that.setData({ scanResult: res.path }); }else{ that.setData({ scanResult: res.result }); } }, fail: (err) => { wx.hideLoading() console.error(err); } }); }, 需要注意的是,小程序码和其它码的结果字段有点不同,所以需要区分,以便取到正确的值。 ...

2018-01-09 · 1 min · Eagle

实战笔记:从一个按钮到全能生成器,随机数功能的“进化论”

这个工具是我的图腾,还记得我之前提到的吗?2018 年,“豆子工具”的初版只有一个功能:点一下按钮,生成一个随机数。 那时候的代码逻辑极其简单,甚至算不上一个“工具”。但随着这几年自己在开发、运维过程中不断遇到“需要一个复杂密码”、“需要一个 16 位纯金钥”或者“需要一个全大写 ID”等实际场景,我发现简单的随机数其实并不简单。 痛点:单一随机数的局限 早期的随机数功能非常死板,生成的格式往往不是我想要的。每次生成后,我可能还需要手动去改长度、改大小写,甚至手动补上特殊字符。 作为一个开发者,如果一个工具不能让我“一键到位”,那就是不合格的。 进化:高度自定义的“融合模式” 于是,我把这些年所有关于“随机”的需求全部揉碎、重组,将它升级成了一个全能的生成器。 现在的随机数功能,不再是盲目地随机,而是基于规则的精准生成。我为其设计了一套组合逻辑: 长度自定义:不再局限于几位数字,用户可以根据需求自由输入长度。 字符集自由组合: 纯数字:适用于验证码类场景。 纯字母:适用于临时 ID 或变量命名。 字母+数字:兼顾强度与易读性。 含特殊字符:专门为高强度随机密码设计。 结果格式化: 支持结果一键转大写或转小写。这在配置某些特定系统的 API Key 时非常有用,省去了手动切换输入法的麻烦。 我经常使用它生成一些随机数作为密码,密钥。我不用去数长度,或者生成后,我简单调整一下,让它有一点意义,我再去使用。 它使用小程序本地实现,下面是一段核心代码,描述了如何去实现随机数: // 生成随机字符串 genRndStr(rtype) { const that = this; if (utils.isEmpty(that.data.input1)) { wx.showToast({ title: '内容为空', }) return } let result = ''; switch (rtype) { case 1: // 纯数字 result = utils.randNumber(that.data.input1); break; case 2: // 纯字母 result = utils.randLetter(that.data.input1); break; case 3: // 字母和数字 result = utils.randStr(that.data.input1); break; case 4: // 含特殊字符 result = utils.randSymbol(that.data.input1); break; } that.setData({ result1: result }) }, 思考:小功能里的“产品观” 虽然随机数在技术实现上只是简单的字符数组随机采样,但从产品角度看,它体现了一种**“减少用户操作”**的原则。 ...

2018-01-07 · 1 min · Eagle