谋划已久的蓄力:我的官网第三次重构(Nuxt3 + Naive UI + Nginx
最近,我使用 Nuxt3 全面重构了我的官网。大功告成后经实测发现,首屏加载速度其实变慢了。但奇妙的是,这次重构我却一点都不后悔。 上一个版本的官网,我是用最纯粹的 HTML+CSS+JS 开发的。说实话,刚写完的时候自己非常满意,纯原生代码,没有任何框架负担。但最近在规划我拥有的数字资产后续的功能时,我的想法发生了一些根本性的转变。我开始意识到,之前把很多工具一股脑塞进微信小程序里,其实并不合理。对于开发者或者需要高频使用不需要账号体系的工具的用户来说,在电脑大屏上操作,远比在手机上点来点去效率高得多,这也是我在小程序上实现没有多少人访问的原因吧。更别说每次改动功能就被小程序那让人头疼的、动不动就被卡住的审核机制了。于是我冒出一个强烈的念头:我要把小程序里适合在电脑上用的工具,全部搬到 Web 网页上来,面向全球用户开放;同时去精简小程序,让它只留下适合移动端且符合小程序场景的即开即用的功能。当我带着这个目标重新去审视我那套 HTML+CSS+JS 的旧代码时,我发现现在这样的写法后期维护起来极其麻烦。最让我抓狂的就是组件复用问题。在旧网站里,像导航栏和页脚这种每个页面都有的东西,要么靠人工全量复制粘贴,要么就直接缺失。每次我想改动一个导航菜单,就要把所有 HTML 文件全部手动改一遍。这种“原始人”一样的维护方式,对于我想规范化后期维护的人来说,不仅工作量巨大,而且写起来让人极度讨厌和烦躁。另一个更深层的坎是:我不希望这个官网仅仅是一个死板的展示页面。在我接下来的蓝图里,这个 Web 平台应该是一个“核心底座”——它能作为数据和逻辑的中心,向上能和小程序联通,向下能跟我的客户端工具做交互,而不是各自为战的孤岛。回看过去,这个官网前后重构了不下三次,但过去的改动大多只是 UI 层面的修修补补。而这一次,也许正是意识的苏醒,让我决定不再无谓地徘徊。为了彻底解决组件复用、多端联通以及降低未来维护成本的痛点,我最终敲定了 Nuxt3 + Naive UI + Nginx 这套架构,并决定长期坚持走下去。 跨端响应式的 UI 抉择,官网的 UI 必须完美兼容移动端、PC 端和平板端。以前尝试过很多 UI 框架,例如WeUI,Bootstrap等,都不尽如人意,我也不想再投入过多精力去试错。虽然我有丰富的后端管理平台 UI 开发经验,但总觉得那些框架不太适合做门户官网。我需要一款既简单易学、又能满足多端响应式需求的 UI 框架。刚好之前做客户端项目时用过 Naive UI,体验极佳,于是这次便果断选择了它。 确定使用 Naive UI 后,前端生态自然锁定了 Vue3。虽然原生 JavaScript 写简单页面很方便,但在实现复杂功能时,远不如 Vue3 高效。Vue3 内置了诸多便利功能,尤其是对有 Vue2 基础的我来说,上手极快。我非常享受在同一个 Vue 文件中把逻辑、页面、样式“一把刷”搞定的开发体验。 选定 UI 和前端底座后,我需要一个强大的开发框架来落地想法。这次重构,我计划编写一系列在线工具来替代微信小程序的部分功能,并计划停用小程序这些功能。经过改造后,我的这些工具将彻底面向全球用户开放,而不仅仅是小程序用户。在开发规范上,我更倾向于有组织、有约束的结构。经过一番调研,我发现当下流行的 Nuxt3 正好完美契合我的诉求。 为什么选择Nuxt3? 比如摆脱复制粘贴:以前使用原生开发网页,HTML 代码难以复用,缺乏组件化概念,每个页面都要复制粘贴相同的导航栏和底部,在没有框架的情况下,这些布局都需要每个页面去赋值。在 Nuxt3 中,我可以直接利用 Vue3 的 Layout(布局)机制完美解决。 状态管理与声明式渲染:纯 JS 操作 DOM 过于繁琐。对于习惯了后端逻辑的我来说,Vue3 的声明式渲染和响应式数据,写起来比纯单页面或原生 DOM 舒服太多,更符合我的思维习惯。 ...
记一次使用egui 构建的轻量小程序码生成工具
最近要使用小程序码生成工具,捡起了以前开发的使用 Rust + native-windows-gui 打造极致轻量的小程序工具。 我发现这个工具最大的痛点就是每次打开都需要重复输入appid和appsecret。我决定修复它,我没有重新开始该项目,是因为我准备使用最近很流行的egui库。使用这个库的另一个原因是我为开发ESP网关客户端做技术储备。 在开发的中间,我还想使用iced,但是开发了一段时间放弃了,不是iced不好,是因为我没有精力。它适合长期的大型的项目。我现在想快速开发完成,所以egui的界面我忍受了。 开发完成的初版界面如下: 它极致轻量,只有一个小巧的运行文件,无需安装臃肿的运行库。响应迅速毫秒级启动,操作如丝般顺滑,没有多余的动效和加载等待。 下载工具后,放入你要生成小程序码的文件夹中,打开工具,填入小程序参数(AppID、路径等),一键获取高清太阳码,小程序码图片就会输出到本文件夹中。 最为暖心的是,应用重新启动后,不用重新输入appid和appsecret,并且会自动获取token。 下面看看核心代码片段: // --- 主应用状态 --- pub struct BeanApp { store: AppConfig, // 保存信息 appid_raw: String, // appid secret_raw: String, // appsecret access_token: Option<String>, // 存储 Token expires_in: u64, // 存储过期时间戳 (秒) output_dir: String, // 小程序码输出目录 is_pro: bool, // 是否开启了高级功能 active_tab: usize, // 0: 单张模式, 1: 批量模式 page_path: String, // 页面路径 scene_value: String, // 场景值 qr_size: u32, // 导出尺寸 show_crop_line: bool, // 是否显示裁剪线 preview_texture: Option<TextureHandle>, // 预览图句柄 status_msg: String, // 状态信息 show_password: bool, // 是否显示密码 tx: Sender<AppMessage>, // 发送端 rx: Receiver<AppMessage>, // 接收端 ctx: egui::Context, // 保存上下文,用于异步刷新 is_loading_token: bool, // 防止重复请求 } 这是最核心的内容,egui就靠这个结构体,让UI和业务逻辑进行交互。本质就是UI交互区修改这个结构体中的一些内容,然后调用业务逻辑去处理内容,然后再返回信息到这个结构体,然后UI读取这些值的内容再显示出来。 ...
一次从“全量遍历”到“指针索引+异步刷盘”的性能优化历程
豆子域名管家是一个使用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,它和文件结构也一一对应。如下: ...
从单一 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证书检测需求的迫切性。 ...
一步一步将ESP8266 WIFI 配网搞定并丝滑体验
准备入手ESP网关项目了,手头有一块esp8266的开发小板子,就先从esp8266开始入手。白天搞定了ESP8266 的UDP配网编码和小程序端的控制编码。就一个功能模块给ESP8266配置WIFI网络。这是必须的入门操作,因为WIFI 的SSID和密码不能真的写入到代码中。 网络配置有很多种方式,我选择了SoftAP配网,虽然配置有点繁琐,但这是一种很可靠的配网方案,兼容性极高,不依赖手机硬件的特殊协议。以前做过HTTP配网,这是2025年时做的,到现在已经忘的差不多了。现在捡起来再回忆一下。 准确来说,这不应该算是一个项目,但因为它具有通用性和实用性。我决定把它记录下来,可以方便地应用到我的其它项目中。 Soft配网原理 初始化模式:ESP8266 启动后进入 WIFI_AP_STA 模式。它会开启一个无密码(或已知密码)的热点(AP),并运行一个轻量级的 Web 服务器(HTTP Server)。 通道建立:手机通过小程序或系统设置连接到该热点。此时手机与 ESP8266 处于同一个局域网内。 数据交互:小程序通过 HTTP Post 请求将目标 WiFi 的 SSID 和 Password 发送给 ESP8266 的固定接口(如 /config)。 校验与切换:ESP8266 收到参数后,尝试作为客户端(STA)连接路由器。如果连接成功,则关闭 AP 热点,保存参数到 Flash;如果失败,则返回并重置到AP状态。 ESP8266端核心代码(Arduino IDE)和小程序端代码 在Arduino IDE打开项目后,需要安装ESP8266 WebServer库。这个库提供了Web服务,可以接收其它HTTP客户端的连接。 它的核心是实现了一个非常简单的Web服务,它监听/config接口并能够接收WIFI信息,以及接收之后可以本地存储,实现动态配置的功能。 下面是Arduino端的代码片段: #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> // 定义AP热点的名称 const char* ap_ssid = "ESP8266_Config_Device"; ESP8266WebServer server(80); void handleConfig() { // 处理SSID和密码逻辑 } void setup() { // 设置为AP+STA模式 WiFi.mode(WIFI_AP_STA); WiFi.softAP(ap_ssid); // 注册接口 server.on("/config", HTTP_POST, handleConfig); server.begin(); } void loop() { server.handleClient(); if (WiFi.status() == WL_CONNECTED) { // 如果连接成功,可以根据需要在这里处理业务逻辑 } } 完整代码可以从https://91demo.top/tools/下载。小程序端则充当了HTTP客户端,它用来作为输入WIFI信息的终端。这个界面非常简单,一个WiFi信息提交表单,包含了SSID信息和密码信息,以及一个提交按钮。 ...
对于微信小程序的一些回忆和思考
有些事,不做笔记真的意识不到已经过去了这么久。 最近在整理博客内容时,我翻到了小程序的最早版本记录。那是 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。
小程序激励广告防刷演进史:从前端校验到“挑战模式”
在开发“模块集市”时,用户通过观看激励广告获取源码下载地址。虽然对接广告并不难,但如何防刷成了我最头疼的问题。由于小程序激励广告缺乏服务器回调(Server-to-Server),奖励的发放完全依赖前端触发,这给了一些“羊毛党”可乘之机。 在接口防护的过程中,我经历了四个阶段: 第一阶段:身份鉴权(解决匿名刷取) 我的获取奖励接口是getAdReward。调用它就可以获取奖励,后台进行发放。最初,由于经验不足,我没有对它做任何防护。这样任何人任何客户端都可以进行访问。了解小程序微信账号体系后,我添加了鉴权,只有携带Token的用户可以访问。这样可以保证只有能够调用wx.login的用户才可以访问。 这解决了一部分问题,但是又有了新的问题,它防不住“真实用户”,只要用户登录后拦截并解析出接口地址和 Token,就可以跳过广告手动调用了getAdReward接口,没有观看广告就获取了奖励。 第二阶段:共享密钥加密(解决接口暴露) 为了防止直接调用接口,我引入了对称加密。前端与后端约定一个硬编码的共享密钥,在小程序端和服务端同时使用这个共享密钥加密随机数和时间戳进行校验,匹配则发放奖励。这样可以防止fidder等工具直接拦截调用接口。 它正常运行了一段时间,我发现又有了新的情况。用户可能通过反编译或者其它方式获取到了共享密钥。这在一段时间内对我造成困扰,除了定期更新共享密钥,我没有好的办法,但更新共享密钥需要升级发布小程序。直到当我了解到小程序提供加密网络通道时,我发现这是一个好的解决方案。 第三阶段:微信加密网络通道(解决密钥安全) 首先,我们来了解一下加密网络通道,它是微信为小程序提供的一套加密KEY机制,它可以同时在微信小程序端和服务器端获取相同的密钥。由于密钥由微信官方通道生成且动态更新,反编译源码也拿不到密钥。除非攻击者能拦截微信的网络通道,否则无法伪造解密过程。同时,配合时间戳校验,有效防御了初级的重放攻击。这对于我来说,是非常好的利好消息,我不需要在源码中硬编码共享密钥了。 我们可以参考获取短信验证码,短信验证码之所以安全,是因为应用和短信验证码是两个不相同的通道。同样的这个加密KEY,在小程序获取和服务端获取都是通过微信的网络通道,和自己的应用通道也不是一个。对于我的小程序稍加改造就可以了。在获取奖励的接口中,首先使用wx.getUserEncryptKey获取微信的加密KEY,然后使用自定义的AES加密方法,将要传递的时间戳和Nonce等参数加密后,将加密数据提交给服务器就可以了。当数据到达服务器,服务器同样调用微信的接口获取微信的加密KEY,然后进行AES解密。这样就可以防止获取共享密钥的弊端。 好了,我们来看看具体如何使用?为了避免小程序与开发者后台通信时数据被截取和篡改,微信侧维护了一个用户维度的可靠key,用于小程序和后台通信时进行加密和签名。开发者可以分别通过小程序前端和微信后台提供的接口,获取用户的加密 key。 1,小程序端获取: const somedata = 'xxxxx' const userCryptoManager = wx.getUserCryptoManager() userCryptoManager.getLatestUserKey({ success({encryptKey, iv, version, expireTime}) { const encryptedData = someAESEncryptMethod(encryptKey, iv, somedata) wx.request({ data: encryptedData, success(res) { const decryptedData = someAESDEcryptMethod(encryptKey, iv, res.data) console.log(decryptedData) } }) } }) 其中,someAESEncryptMethod 和 someAESDEcryptMethod 分别为加解密函数,由开发者自行引入加解密库来实现,基础库暂时不提供加解密能力。 2,服务端获取: 在开发者服务端,可以调用getUserEncryptKey后台接口获取用户最近三次的key。在获取key的同时,接口会携带version信息,开发者可以比较version版本来选择使用对应的key对数据进行加解密。 curl -X POST "https://api.weixin.qq.com/wxa/business/getuserencryptkey?access_token=ACCESS_TOKEN&openid=OPENID&signature=SIGNATURE&sig_method=hmac_sha256" 其中,openid是用户的openid,signature用sessionkey对空字符串签名得到的结果。即 signature = hmac_sha256(session_key, “"),sig_method为签名方法,固定为hmac_sha256。 这个是核心,获取激励广告奖励基于它再添加一些参数进行加解密。 第四阶段:后端“挑战模式”(解决重放与逻辑伪造) 我在参数中添加的Nonce,它是一个噪音参数,它是小程序端本地生成的,还有时间戳也是本地生成的,增加它们是为了增加解密难度。在实际运行中,我发现硬核玩家会通过修改本地时间戳和 Nonce(随机数)来绕过检测,或者使用相同的参数进行重放攻击,或者不够15s(广告正常播放最短时间)调用多次接口。 为了解决这个问题,我将逻辑升级为“挑战模式”:首先,小程序端在广告开始前进行一次预请求,前端必须先调用 getNonce 接口获取Nonce然后使用这个Nonce进行加密,getNonce是一个新增接口,用来生成Nonce,并记录生成时间和关联openid,服务端存入缓存(如 Redis),完成服务端打标。当前端再次调用getAdReward接口时,如果差值小于广告常规时长(如 15s),则判定为非正常观看,直接拒绝。 ...
我是如何设计和实现模块源码集市的?
当我开发完51单片机温度采集并转换Modbus RTU后,我一度想分享出去,但是我又不想草率的去分享。我一直在思考,如何能让这些代码发挥更大的价值,而不仅仅是躺在个人仓库里。作为一名摸爬滚打多年的开发者,我手头积累了大量来自实践的代码、模块和解决方案。它们散落在硬盘的各个角落,像一颗颗未经打磨的珍珠。 于是,我萌生了做一个“模块源码集市”小程序的想法——一个可以直接浏览、查阅、并一键下载优质源码的平台。 经过最近一段时间的集中开发,这个小程序终于和大家见面了。今天,我想和大家分享其背后的设计与思考,希望能为有相似想法的朋友提供一些参考。这个小程序的核心功能简洁而实用: 首页全景浏览:所有上架的代码模块都在首页清晰展示,你可以快速了解其名称、简介和分类,对全站资源一目了然。 沉浸式详情阅读:点击任一模块,即可进入详情页。这里我使用了Markdown渲染引擎,来展示模块的详细介绍、使用说明、技术要点等。Markdown的优雅排版能让技术文档的阅读体验大幅提升。 一键获取源码:在详情页,点击获取模块源码链接按钮,即可轻松获取模块的完整源码压缩包。 为了让这个简单的流程稳定、安全、可扩展,我在几个关键环节做了一些设计和取舍: 动态资源与安全通道源码文件并非直接打包在小程序内,而是存储在后台的MinIO对象存储中。当用户点击下载时,服务端会动态生成一个具有时效性的访问链接返回给小程序。这样做的好处是资源可以随时更新。同时,为了维持后端服务器持续运行,我在列表及详情中接入了少量广告。为了保护下载接口不被恶意刷取,我集成了激励式视频广告。用户观看一则简短的广告,即可解锁下载权限。这不仅是简单的广告接入,更重要的是,我为此构建了一套防刷机制,我使用了微信的加密网络通道,对关键数据进行加密传输和验证,极大地增加了自动化盗刷的成本和难度,保护了服务器资源。 灵活的沟通与通知我深知,一个“活”的产品需要与用户保持沟通。因此,小程序内设置了系统消息中心,我可以在这里向所有用户下发重要的系统公告、模块上新以及更新日志。更重要的是,我为每个模块的上新功能,接入了微信服务通知。当有新的、优秀的代码模块入库时,订阅了的用户会在早上8点后收到一条微信消息提醒,能让你随时跟上“仓库”的更新节奏。 持续生长的内容生态目前上架的或正在上架的模块,覆盖了我擅长的多个领域:小程序、Go语言后端工具、Rust系统编程实践、Web前端片段、嵌入式软硬件代码,包含音视频、网络、加密等技术要点,形式包括客户端、命令行工具、固件、小程序、网页等。这只是一个开始。我的计划是将其作为一个长期项目,持续将我实践中验证过的、有价值的代码沉淀、整理并开源出来。未来,我还会在项目详情页中,增加关联的公众号技术文章跳转,或是有功能关联的其他小程序跳转,形成一个立体化的技术知识网络。 坦率地说,这个小程序的开发过程远超我最初的预期。从架构设计到具体实现,尤其是几个关键的技术卡点上,耗费了我大量的精力。 与广告盗刷的攻防:我的服务器每天都有人在扫描,所以在实现激励广告防刷机制时,我花了大量时间加强防刷机制,其实这个在微信小程序社区中也可以看到很多类似的问题。我这里研究了一种解决方案。就是使用加密网络通道。在对接的过程中,我与各种加密错误、签名错误(如常见的40079错误)作斗争。例如,处理会话密钥时,发现不需要进行Base64编码,而是直接以空字符串的字节形式处理;在URL拼接的细节上,POST与GET方法的误用也会导致失败;而加解密环节更是陷阱重重——加密密钥需要从Base64解码,IV却是一个直接的16位字符串,加密数据则需要从十六进制格式解码……这些细枝末节,任何一个出错都会导致整个流程崩掉,往往需要清空Token重新登录来排查。这个过程让我对网络数据安全有了更深刻的认识。这些错误和经验本想专开一篇文章介绍,但是细想一下,可能一段话就能总结,但是其中的辛酸和感悟无法表达在纸面上。索性后期整理出来开源这个模块,减少其他开发者的一些坎坷。 存储方案的抉择:在对象存储的选择上,也有一段小插曲。我最熟悉的是Mino,但是在我了解到MinIO版本已归档,便尝试了rustfs等其他方案进行探索。但经过综合对比在可靠性、API友好度以及与现有技术栈的契合度后,最终还是选择了功能完备、文档清晰的MinIO作为存储后端,事实证明这个选择是稳定高效的。 所幸,在这个艰难的过程中,AI编程助手给予了我巨大的帮助(但是也让我吃了很多苦头,因为它上面的资料都比较旧)。许多代码片段的生成、调试过程中错误信息的解读、API文档的快速查询,都因AI的介入而效率倍增。如果没有它,单靠我自己,恐怕几个月也难见成果,绝无可能在十几天内推动项目成型上线。 做这个小程序,并非为了追逐热点,这个小程序刚刚上线,还需要时间去细细打磨。开发其初心很简单:整理自己的知识资产,并以一种对开发者最友好、最便捷的方式分享出去。我希望它不仅仅是一个下载站点,更可以成为一个高质量的、经过实战检验的“代码片段集市”和“工具箱”。路漫漫其修远兮。在代码世界的探索中,每个人都是学生,也是老师。我在这里抛砖引玉,期待这些代码模块能真正帮助到在具体问题上寻找解决方案的你。也欢迎你常来看看,这些项目模块,会和我一样,在技术的道路上不断生长。 扫码体验:
在51单片机实现 Modbus RTU 协议过程中的踩坑和思考
在上篇原理文章介绍后,我开始实现代码,硬件使用现成的51开发板。原本以为只是简单的串口收发,结果却在 Modbus Poll 软件中疯狂循环 Checksum Error 和 Timeout。 下面是我在普中科技 51 开发板上,使用STC89C52芯片接入工业标准的 Modbus RTU 协议,从最基础的串口打印到实现动态温度采集,并成功通过 Modbus 协议回传的完整过程。 环境准备 硬件: 普中 51-A2 开发板 (STC89C52RC)、DS18B20 温度传感器、USB 转串口线。 软件: Keil uVision5、STC-ISP、Modbus Poll (主机仿真器)、SSCOM 串口调试助手。 核心挑战:为什么 Modbus Poll 总是报 Checksum Error? 这是我耗时最长的地方,也是我头疼的地方。如果你也遇到数据看着对但校验不过,请检查以下三点: 查表法的陷阱 因为51单片机的性能问题,我使用了查表法,CRC表中的数据是采摘网上教程的。在解决了之后,发现是自己设置的表中的数值不对。要知道Modbus 的 CRC16 校验非常严苛。为了方便以后的开发者,我把正确的CRC表和获取函数都贴出来。 // 高位表 unsigned char code aucCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 }; // 低位表 unsigned char code aucCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 }; // 获取CRC unsigned int GetCRC16(unsigned char *pData, unsigned char len) { unsigned char uIndex; unsigned char crch = 0xFF; unsigned char crcl = 0xFF; while (len--) { uIndex = crcl ^ *pData++; crcl = crch ^ aucCRCHi[uIndex]; crch = aucCRCLo[uIndex]; } return (unsigned int)(crch << 8 | crcl); } 字节序与 Quantity 的对齐 这里也是一个坑,自己概念不清,在设置quantity时设置错误,错把他们1比1进行换算。还有Modbus 规定 低字节在前,高字节在后。 ...
为豆子域名管家实现版本检测功能的技术实践
豆子域名管家是一款基于 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) 当本地版本低于云端时,左下角会亮起精美的红点徽标。点击后的交互逻辑非常人性化: 信息全透明:对比当前版本与新版本,展示完整的更新列表和使用指南。 尊重用户选择:弹窗配备了清晰的“取消/稍后再说”按钮,绝不强制升级。 快捷操作:提供“复制下载链接”按钮,点击后自动写入剪切板。用户只需打开浏览器粘贴,即可从蓝奏云等高速渠道下载,覆盖即升级。免去了前期的复杂步骤。 界面功能效果预览 徽标提示 弹窗说明 工具如何开始使用? 无论您是拥有几十个域名的运维大拿,还是只有几个小站的个人玩家,豆子域名管家都是您的不二之选。 ...