深度实战:基于 Wails v3 与 Go 打造跨平台 FRP 桌面客户端 Mole-go 的技术架构与原理

一、 缘起:为什么需要 mole-go? 在开发微信公众号、调试支付接口、以及演示本地开发网站时,或由于服务器资源限制需要在本地部署服务时,frp 是不可或缺的内网穿透神器。然而,原生的 frpc 存在几个显著的痛点: 运行隐形性差:必须开启命令行窗口,一旦误关服务即中断。 配置门槛高:新手难以记忆复杂的 .toml 参数。 为了解决这些问题,我开发了 mole-go。它是一个轻量级、跨平台的桌面客户端,旨在实现 frp 的配置、启动与监控一体化。我选择 Wails v3 则是看中了其原生渲染、系统托盘支持、Go 强力后端以及极小的打包体积。 二、 核心架构:Go + Wails v3 的化学反应 mole-go 采用了经典的“UI-Backend-Service”三层架构: Wails UI:负责前端展示,通过事件驱动(Event-Driven)与后端交互。 Go Backend:核心大脑,负责业务逻辑、进程管理与系统级 API 调用。 frpc 二进制:底层服务,通过 Go 的 embed 特性内嵌到二进制文件中。 三、 关键实现细节:从命令行到图形化的进化 前端:从“面条代码”到模块化数据驱动 早期版本中,我直接采用 window.startFrp,window.stopFrp这样的写法,导致代码碎片化严重,以及管理app运行状态不方便。在 mole-go 的正式版中,我将其重构为数据驱动模式,类似Vue,由数据驱动界面: 模块化封装:定义全局 window.App 对象,将数据状态与行为(Methods)统一封装,使代码结构清晰。 动态 UI 组件:针对 HTTP、TCP、UDP 等不同代理模型,不再机械地堆砌 HTML 片段,而是通过逻辑判断实现“按需渲染”,大大精简了 DOM 结构。 后端:全局实例与事件机制 为了保证服务层(Service)能随时与 UI 通信,我设计了一个全局 App 实例,这样可以方便得调用和管理。 状态约定:前后端约定一套状态码,通过 Wails v3 的 Events 机制,后端可以主动向前端推送 frpc 的运行状态、日志等信息。 独立服务层:将 frp 相关逻辑抽离到专门的文件中,通过 Wails 的 Binding 暴露给前端,保持代码的解耦。 系统深度集成 系统托盘(System Tray):利用 Wails v3 原生的托盘支持,实现了“关闭即隐藏”逻辑。 外部链接调用:使用 wails3自带的 Browser.OpenURL 方法,确保点击文档链接时能正确唤起系统浏览器。 可参考项目源码。 ...

2026-01-10 · 1 min · Eagle

全栈实战:基于 Go + 小程序实现网页端“扫码登录”逻辑

扫码基石:构建视觉化的连接钥匙 二维码(QR Code)是连接物理世界与数字世界的“虫洞”。在登录系统中,它承载着一个临时的身份信标。 1. 生成有效二维码 登录二维码通常包含一个加密的 URL 或一个唯一的 UUID(通用唯一识别码)。 唯一性: 每一对扫描动作都必须对应一个独一无二的 ID。 时效性: 二维码必须配合 Redis 设置过期时间(如 2 分钟),逾期自动失效。 Redis 最核心的用法是 Key-Value (键值对)。 在本项目中,我们将用户的微信 OpenID 作为 Key,生成的验证码作为 Value: // 存储验证码,并设置 5 分钟过期 err := rdb.Set(ctx, "user:123:code", "8888", 5*time.Minute).Err() // 读取验证码 val, err := rdb.Get(ctx, "user:123:code").Result() 在 Go 生态中,我们使用 skip2/go-qrcode 等库来完成像素的绘制: // 生成二维码字节数组 var png []byte png, _ = qrcode.Encode("91demo.top"+sessionID, qrcode.Medium, 256) 为了防止用户伪造扫码请求,二维码里的内容通常是加密的或者是不可预测的长随机数(UUID)。只有真实存在的 ID 才能通过后端的 Redis 校验。 2. 传输二维码 我们不希望在用户硬盘上产生大量的临时 .png 文件。 最佳实践是将二进制图片转换为 Base64 字符串,通过结构体返回给前端: // 转换为前端可直接识别的 Data URL base64Img := "data:image/png;base64," + base64.StdEncoding.EncodeToString(png) 为了让前端不至于崩溃,后端必须返回统一的格式。 func HandleLogin(c *gin.Context) { // 逻辑处理... c.JSON(200, gin.H{ "code": 1, "content": base64Img, }) } 在前端,我们不再需要引入沉重的第三方库来做简单的请求。浏览器原生提供的 Fetch API 简洁且基于 Promise。 ...

2025-03-15 · 3 min · Eagle

Web 实战:构建高可靠验证码登录系统及后端防刷流控策略

我们都知道手机号验证码可以登录网站。这里我也做了类似的功能,方便在手机端不能使用扫码也能登录。 我在小程序端制作了一个获取验证码界面,它可以生成模拟编号和验证码。当用户点击获取验证码时,会返回编号和验证码,它同时绑定用户的小程序身份Openid。 当用户在网站端输入编号和验证码时,后端会校验是否存在这对编号和验证码,如果校验正确,将取出openid并绑定到sessionID上,然后返回给前端,存入cookie中。 除了在小程序生成验证码外,我还添加了在公众号发送消息获取验证码。 1. 公众号获取验证码 公众号不仅是内容分发平台,更是一个强大的身份认证中间件。 1. 握手与回调 (Webhook) 要在 Go 中接收微信消息,你必须先在微信公众平台配置一个 服务器地址 (URL)。 校验: 微信会向你的 URL 发送一个 GET 请求,包含签名、随机数等。你必须按照规定的算法计算并返回正确的 echostr,这被称为“服务器验证”。 消息推送: 验证通过后,每当用户发送消息,微信服务器就会以 POST 方式将消息体推送给你。 2. 解析 XML 数据 与现代 API 不同,微信公众号的推送采用的是 XML 格式。Go 的标准库 encoding/xml 提供了强大的解析能力: type WxMsg struct { ToUserName string `xml:"ToUserName"` FromUserName string `xml:"FromUserName"` // 这就是用户的 OpenID Content string `xml:"Content"` // 用户发来的文字 } 3. 验证码生存逻辑 (Redis 联动) 当用户发送“验证码”关键字时,我们的 Go 后端会: 生成一个 4-6 位的随机数。 将 OpenID 与 随机数 存入 Redis,并设置 TTL(如 5 分钟)。 通过 XML 响应将验证码发回给用户。 4. 身份对撞 用户在Web端输入这个验证码。后端从 Redis 中根据验证码反查 OpenID,若存在且有效,则代表身份验证成功。执行登录流程。 ...

2025-03-11 · 1 min · Eagle