欢迎来到豆子技术站 👋

记录小程序、Go 语言、Rust 语言及运维部署的实战经验。

拒绝频繁上传服务器!我用 Dufs + Mole-go 搭建了丝滑的内网穿透演示环境

最近开发完 Mole-go,想给它做个网站用来展示和下载。但我这个后端糙汉子,样式真搞不定,求助 AI 调了半天还是差点意思。最头疼的是,手机端调试得一遍遍输 IP,给朋友演示也得发一串 IP 端口,太不专业了!于是我一顿折腾,搞出了这套方案…… 为了解决这些痛点,我摸索出了一套“黄金组合”:Dufs + Mole-go + FRP + Caddy。这套方案打通了从本地到公网域名的全链路,实现了自动 HTTPS、域名访问以及极致的访问体验。 第一步:构建本地内容基石(Dufs) 一切的起点是本地文件服务。我选择使用 Dufs 作为静态服务器。它极其轻量,支持上传、搜索、打包下载甚至 WebDAV,是我演示 Web 应用或分发安装包的首选。 通过简单的命令,我在本地 5000 端口启动了服务。虽然此时它还被“困”在局域网内,但它为后续的展示提供了稳固的基础。 第二步:突破局域网束缚(FRP 与 Mole-go) 为了让公网流量能精准触达内网,我采用了经典的 FRP 方案,但在客户端层面,我使用了自己开发的 Mole-go。 服务端 (FRP Server):部署在具备公网 IP 的云服务器上,充当流量中转站。这个服务器配置可以很低,网站服务都在本地电脑,如果本地有数据库,也非常方便调试。 客户端 (Mole-go):这是我为 FRP 打造的桌面管理客户端。它封装了 frpc 核心,不仅提供了直观的 UI,还通过系统托盘设计彻底解决了“关闭窗口即断连”的痛点。 使用 Mole-go,我可以将本地 5000 端口通过加密隧道安全地映射到云端。它出色的资源管理和连接稳定性,确保了演示过程中即便网络波动,链接依然稳固如初。 第三步:优雅的网关入口(Caddy) 即便流量已到达公网,我也不希望朋友们通过 http://IP:端口 这种生硬的方式访问。我追求的是“域名+HTTPS”的专业感,这不仅是为了美观,更是为了开发环境需求,下次开发公众号等必须 HTTPS 环境时可以拿来就直接使用。 我选择了 Caddy 担任“守门人”。Caddy 的魅力在于其近乎零配置的 自动 HTTPS 功能。看中了它的简单方便,非常符合我的场景。在 Caddyfile 中,我只需写下: example.com { reverse_proxy localhost:7000 # 指向 FRP 映射出的本地端口 } 仅需这一行配置,Caddy 就会自动搞定 SSL 证书的申请与续签。当访问者输入域名时,映入眼帘的是受信任的绿色小锁头,所有的复杂端口逻辑都被完美隐藏。 ...

2026-01-15 · 1 min · Eagle

语音验证码远程呼叫

在实现了使用VOIP客户端拨打8000号码后,播报语音验证码的功能后,我发现了一个最大的缺点,就是这需要用户主动去操作。这对于想使用API集成无法实现。 在思考之后,我决定使用一个可以调用API就呼叫VOIP客户端,当用户接通后,播报语音验证码的功能。当实现这个功能后,它的好处是显而易见的。比如,可以集成到嵌入式,集成到第三方网站。 那么该如何实现它呢? 一、 系统原理 传统的拨号方案(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

使用 Wails v3 打造 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

把项目做成像 frp 那样的多平台 Release

把项目做成像 frp 那样的多平台 Release Mole 项目已经开源,打算把仓库在 GitHub Release 页面中发布各个平台的安装包/二进制(像 frp 那样),已经完成脚本,可参考.github/workflows/release.yml,这个用来记录实现过程与遇到的坑,便于自己回顾。 大致流程 查阅 GitHub Actions、goreleaser、actions/create-release、actions/upload-release-asset 等资料。 在仓库中写一个 matrix workflow,在 Windows / macOS / Ubuntu runner 上分别构建并打包。 修复构建中出现的依赖与环境差异问题。 将构建产物上传为 Release asset(先查找/创建 release,再上传各平台包)。 调试并保证上传在 Release 页面能看到各平台包。 今天遇到并解决的主要问题(按流程顺序) Ubuntu 图形库版本不一致 问题:教程写的是 libwebkit2gtk 4.0,但在新版 Ubuntu runner 上只能安装 4.1。 处理:在 CI 的 apt 安装里兼容 4.0 和 4.1(尝试 4.1,或用 || 逻辑兼容两种包名)。 wails3 安装地址写错导致安装失败 问题:go install 用错了模块路径,安装一直失败。 处理:修正为官方地址(例如 github.com/wailsapp/wails/v3/cmd/wails3@latest),并确保 GOPATH/bin 在 PATH 中。 wails3 的 -platform / -o 参数在我的环境不可用 ...

2026-01-07 · 2 min · Eagle

FRP 管理桌面客户端公版Mole部署教程

FRP 桌面管理客户端 Mole 部署全攻略 本文将带你从零开始,快速完成基于 Mole 的内网穿透服务部署,包含服务端 (frps) 与桌面客户端 (Mole/frpc) 的配置与排查要点。 📖 核心概念 什么是 FRP? FRP (Fast Reverse Proxy) 是一款高性能的反向代理/内网穿透工具,它通过在公网服务器和内网客户端之间建立隧道,使外网可以访问内网服务(如 NAS、树莓派、开发环境等)。 什么是 Mole? Mole 是一款基于 wails3 开发的 FRP 桌面客户端,提供图形化管理界面,简化 frpc 的配置与使用: 告别繁琐命令行 支持系统托盘常驻,防止误关窗口导致中断 支持 HTTP/HTTPS、TCP、UDP 等多种协议 🛠️ 部署前准备 一台拥有公网 IP 的云服务器(例如阿里云、腾讯云等) FRP 服务端(frps)与客户端(frpc)二进制文件,建议使用 FRP Releases 的 v0.65.0 或更高版本 Mole 桌面客户端安装包(对应你操作系统的版本) 基本网络与防火墙管理权限 1. 服务端配置(frps) 下载并解压 FRP 服务端(以 Linux 为例) 访问 FRP 的 Releases 页面下载对应版本并解压(示例为 v0.65.0 或更高)。 编写 frps.toml 配置文件(示例) 将以下内容写入 frps.toml,并根据实际需求调整端口与 token: ...

2026-01-07 · 2 min · Eagle

音频格式转换新增支持 WAV

音频格式转换新增支持 WAV WAV 转 MP3 的 FFMPEG 命令: ffmpeg -i input.wav -codec:a libmp3lame -qscale:a 2 output.mp3 然后使用 go 封装一下。

2026-01-06 · 1 min · Eagle

Markdown 语法教程

Markdown 语法教程 我们使用 Markdown 作为我们内容的格式,所以学习 Markdown 就显得很必要了。 Markdown 是一种轻量级标记语言,后缀为.md。因我常用 Markdown 来写一些内容,记录下来常用语法格式。方便自己查找使用,也增加自己的一些流量。Markdown 可以用来撰写电子书,文章,博客等。介绍标题、段落的使用。 标题 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 段落 Markd 段落没有特殊的格式,直接编写文件就好,段落的换行使用两个以上空格加上回车。也可以使用一个空行来表示重新开始一个段落。 字体 使用如下格式分别表示斜体,粗体,粗斜体。 *斜体文本* _斜体文本_ **粗体文本** __粗体文本__ ***粗斜体文本*** ___粗斜体文本___ 分割线 你可以在一行中用三个以上的星号、减号、底线来建立一个分割线,行内不能有其它东西。你也可以在星号或减号中间插入空格。 *** * * * ***** - - - --------- 删除线 如果段落上的文字要添加删除线,只需要在文字的两端加上两个波浪线~~即可。 ~~删除线~~ 下划线 下划线可以通过 HTML 的标签来实现: <u>下划线文本</u> 脚注 脚注是对文本的补充说明。 [^要注明的文本] 列表 支持有序列表和无序列表。 无序列表使用星号、加号、减号作为列表标记,这些标记后面要添加一个空格,然后再填写内容。 * 第一项 * 第二项 * 第三项 + 第一项 + 第二项 + 第三项 - 第一项 - 第二项 - 第三项 有序列表使用数字并加上.号来表示。 ...

2026-01-05 · 2 min · Eagle

Visit 项目维护Protobuf文件

Visit 项目维护 Protobuf 文件 豆子碎片小程序在版本 5.0 开始使用 Protobuf 传输和存储数据。在使用 Protobuf 前,使用的是 JSON 数据,原始 JSON 数据文件大小为 10KB,在生成 PB 文件后,PB 文件只有 4KB。本文章主要记录如何维护 Protobuf 数据,和开发中碰到的问题记录。 现在由于实际需求和维护原因,又切回使用 JSON 文件。 目前是使用 JSON 文件维护原数据,后期可以考虑使用数据库来存储。将 JSON 文件转为 PB 文件的工具是使用 Python 写的脚本。 下面开始一步一步介绍,直到 PB 文件可以使用。 一、首先定义 data.proto 文件,这个文件 Python 和 JavaScript 都需要用到。 syntax = "proto3"; message DataItem { string aid = 1; string name = 2; string category = 3; string label = 4; int32 grade = 5; } message DataList { repeated DataItem items = 1; } 上面的内容就是描述了一个数组对象。 ...

2026-01-05 · 3 min · Eagle

笔记列表页面布局

笔记列表页面布局 这是现在小程序中实际用到的列表页面,请看真实的笔记列表页面需求: 页面包含三部分,广告、内容介绍、笔记列表。其中,广告占页面高度 260rpx,内容介绍高度 200rpx,笔记列表是一个卡片列表,卡片内容分为上下两部分,上部分是标题,下部分是标签,标签可以有多个,一行内显示,两个标签之间要留有间隙。当列表内容很多,一个屏幕无法显示出来时,要可以滑动查看列表内容。滑动时,上方的广告不滑动,内容介绍和笔记列表进行滑动,显示最新内容。 主题样式为 WEUI,但是标签为蓝色。 广告为 Banner 广告,高度为 120px,如果转换为 rpx 大概为 240rpx。所以广告上下可以留一些间隙。 内容介绍是卡片样式,分为上下两部分,上方为项目标题,介绍这个笔记列表归属哪个项目,下方是内容介绍,介绍这个项目是什么。 笔记列表的内容,为该项目从想法到实现的全部内容,每个文件都是一段内容,介绍想法、布局、实现、部署等等。以 Markdown 文件为载体,在列表页面只显示文件的标题,和人为绑定的标签,标签可以为多个,例如想法、开发、部署等,当为开发时,可以牵涉到使用什么语言,例如 Go、Js 等。 当我们了解了需求之后,可以发现这个通过 flex 布局可以很好的实现。 其中页面 WXML 如下: <view class="container"> <!-- 广告部分 - 固定不滚动 --> <view class="ad-container"> <ad unit-id="your-ad-unit-id" class="banner-ad"></ad> </view> <!-- 可滚动区域 --> <scroll-view class="scroll-area" scroll-y> <!-- 内容介绍部分 --> <view class="content-intro"> <view class="intro-title">{{projectTitle}}</view> <view class="intro-description">{{projectDescription}}</view> </view> <!-- 笔记列表 --> <view class="note-list"> <block wx:for="{{notes}}" wx:key="id"> <view class="note-card" bindtap="onNoteTap" data-id="{{item.id}}"> <view class="note-title">{{item.title}}</view> <view class="note-tags"> <block wx:for="{{item.tags}}" wx:key="*this"> <view class="tag">{{item}}</view> </block> </view> </view> </block> </view> </scroll-view> </view> 样式 WXSS 如下: ...

2026-01-05 · 2 min · Eagle

变现路径:广告接入与付费会员设计

变现路径:广告接入与付费会员设计 当开发完小程序后,如何收益成为一件重要的事情。 在微信小程序中,变现路径的选择对开发者的收益有着重要影响。本文将对比流量主广告、自定义付费墙等方案的收益模型,帮助开发者选择合适的变现途径。 目录 流量主广告 自定义付费墙 收益模型对比 选择适合的变现方案 1. 流量主广告 什么是流量主广告? 流量主广告是微信提供的一种变现方式,开发者可以在小程序中接入微信广告组件,通过展示广告获取收益。常见的广告形式包括激励视频广告、插屏广告、Banner 广告等。 流量主广告的优势 易于接入:微信提供了完善的广告组件和接口,开发者只需简单配置即可接入广告。 收益稳定:广告收益与用户的广告点击和观看次数相关,流量大的小程序可以获得稳定的广告收入。 不影响用户体验:合理布局广告位置,不会对用户体验造成过多干扰。 流量主广告的劣势 广告依赖性强:收益依赖于广告的点击率和展示量,受广告市场波动影响较大。 用户反感:广告数量过多或位置不合理,可能会引起用户反感,影响用户留存率。 示例代码 Page({ data: { adUnitId: "YOUR_AD_UNIT_ID", }, onLoad: function () { // 初始化广告组件 const videoAd = wx.createRewardedVideoAd({ adUnitId: this.data.adUnitId }); videoAd.onLoad(() => {}); videoAd.onError((err) => {}); videoAd.onClose((res) => { if (res.isEnded) { // 用户完整观看广告,可以给予奖励 } else { // 用户提前关闭广告,不给予奖励 } }); this.videoAd = videoAd; }, showAd: function () { this.videoAd.show().catch(() => { // 广告播放失败 }); }, }); 2. 自定义付费墙 什么是自定义付费墙? 自定义付费墙是一种内容付费模式,用户需要支付一定费用才能访问小程序中的特定内容或功能。开发者可以根据需求设计不同的付费策略,如单次付费、订阅付费等。 ...

2026-01-05 · 1 min · Eagle