梦开始的地方

一个开发者的成长地图:记录Go、Rust、Web、小程序与云原生探索之旅。

巧用Nginx重定向解决云存储文件地址变更的痛点

在移动应用分发、文件共享等场景中,我们常常会遇到这样的困境:云存储平台(如阿里云OSS、腾讯云COS、蓝奏云等)上传的文件地址一旦生成就无法修改,但客户端版本频繁迭代时,每次更新都需要重新生成下载链接和二维码。用户端需要不断更新访问入口,体验极差。我就碰到了此类问题,在上一篇文章,我介绍了自己的客户端,并分享了下载地址。但我发现当我需要重新升级版本时,已经生成的地址将无法变更。在我的文章中,我直接使用了文件下载地址。这就让我急需解决如何实现"一次分发,永久可用"的优雅方案?Nginx反向代理配合301重定向,正是解决这一痛点的利器。 一、问题根源:云存储的地址锁定机制 大多数云存储服务采用"对象存储"模式,文件上传后生成固定URL,该地址包含存储桶名称、地域、文件路径等不可变信息。这种设计虽然保证了数据一致性,却带来了分发难题:每次版本更新,开发者必须重新上传文件、生成新地址、制作新二维码,用户需要重新扫描或获取新链接。对于频繁迭代的应用,这种重复劳动不仅效率低下,更可能因用户未及时更新而影响使用体验。 二、解决方案:Nginx重定向的架构设计 核心思路是地址解耦——将"物理存储地址"与"用户访问入口"分离。通过Nginx服务器作为中间层,用户始终访问固定域名,Nginx根据配置将请求重定向到实际的云存储地址。当文件更新时,只需修改Nginx配置中的目标地址,用户端的访问入口保持不变。 具体架构如下: 用户 → 固定域名/短链接 → Nginx服务器(301重定向) → 云存储实际地址 这种设计实现了三个关键目标: 入口稳定性:用户扫描的二维码或保存的链接永久有效 维护灵活性:后台地址变更只需修改Nginx配置,无需重新分发 成本可控性:Nginx只做重定向,流量消耗极低,普通云服务器即可承载 三、技术实现:从配置到部署 环境准备购买一台云服务器(1核1G配置即可),安装Nginx,将自有域名解析到服务器IP。推荐申请SSL证书启用HTTPS。 Nginx配置示例 server { listen 80; server_name your-domain.com; # 方案一:直接重定向 location /download/app.apk { return 301 https://bucket.oss-cn-region.aliyuncs.com/v2.0/app.apk; } # 方案二:通配符匹配(更灵活) location ~ ^/app/(.*)$ { rewrite ^/app/(.*)$ https://new-bucket.oss-cn-region.aliyuncs.com/$1 permanent; } } 重定向类型选择 301永久重定向:搜索引擎会更新索引,客户端会缓存重定向结果,适合生产环境 302临时重定向:每次请求都会重定向,适合测试阶段频繁变更的场景 二维码生成 将固定地址(如https://your-domain.com/app/v2.0.apk)生成二维码,用户扫描即可下载。后续版本更新时,只需修改Nginx配置中的目标地址,二维码无需重新制作。 四、方案优势与注意事项 核心优势 零用户感知:用户无需关心后台地址变化 维护成本低:修改配置即可,无需重新上传二维码 扩展性强:可支持多版本共存、灰度发布等复杂场景 兼容性好:支持各种客户端(浏览器、APP、微信等) 实施建议 测试先行:在测试环境验证重定向逻辑,确保目标地址正确 监控告警:配置Nginx日志监控,及时发现重定向失败问题 备份机制:保留旧版本配置一段时间,避免新配置问题影响用户 性能优化:虽然重定向开销小,但高并发时需确保服务器性能 五、总结 Nginx重定向方案将复杂的地址管理问题转化为简单的配置变更,实现了"一次分发,永久可用"的理想状态。这种解耦思维不仅适用于文件分发场景,在API网关、微服务路由等场景中同样具有借鉴意义。对于中小型项目而言,这是成本最低、效果最直接的解决方案,值得每一位开发者掌握。 最后推荐一下我的豆子域名管家客户端,这个客户端可以解决域名证书到期忘记更换证书的问题。我是为了解决这个问题而想出来的这个方案。这是我的新的下载地址:https://lab.91demo.top/b011 这样当我修改下载地址时,我不用再变更这个地址,比如我最近刚刚优化了域名扫描逻辑,上传了新的版本,它生成了一个新的下载地址。 当然这个方案还是有一点瑕疵,它需要每次更新都登录服务器修改nginx配置文件,这对于非技术人员非常不方便。后期我还有再次进行优化,我将使用小程序完成新地址的更换。大致思路如下:开发一个轻量的Go服务,提供接口给小程序提交新的地址,然后保存编号和新地址到数据库,例如上面的b011是编号,它对应新的下载URL地址。然后使用Nginx代理这个服务,当用户访问b011时查询它的URL地址,然后返回301和新的地址。这样就不用每次都登录服务器修改Nginx配置,在手机上也方便操作,提供了极大的便利性,也可以交给非技术人员进行维护。

2026-01-29 · 1 min · Eagle

使用go生成DLL文件供第三方使用

我使用go写了一个http转WebSocket服务,这是一个命令行程序,双击可以直接运行,今天,有个新的需求,需要将这个命令行程序转为dll,然后供第三方使用。 1,改造程序 这个命令行程序很小,核心逻辑就是启动了一个HTTP服务,然后接收连接,然后通过WebSocket将内容转发出去。所以,在调整为dll时,仅仅需要导出两个函数即可。 1,StartServer,启动http服务,监听端口地址。因为不能阻塞,所以需要在监听服务时使用go方法启动一个携程。 2,StopServer,关闭http服务,需要提供给第三方,当它关闭时,需要调用它,释放监听地址等资源。 下面开始改造代码: package main import "C" // 必须导入 C 包 import ( // ... 原有导入 ... ) // 保持原有的全局变量和函数逻辑 (例如transDataGet, connWs 等) // 需要注意下面的//export这中间不能有空格,否则无法导出头文件 //export StartServer func StartServer() { // 将原本 main 函数里的逻辑放在这里 gin.SetMode(gin.ReleaseMode) r := gin.New() // ... 注册路由 ... r.Run("127.0.0.1:9988") } // 必须保留一个空的 main 函数 func main() {} // 其它参考StartServer,如果需要导出,一定要添加//export 2,编译命令 我是在windows环境,需要安装cgo环境,我安装了Mingw-w64支持C编译环境,执行以下命令: go build -buildmode=c-shared -o trans.dll main.go 执行后会生成trans.h和trans.dll两个文件。 3,验证dll 除了一些可以查看dll的工具外,还可以使用第三方语言进行测试。例如我这里使用Python。 import ctypes import time lib = ctypes.CDLL("./trans.dll") lib.StartServer() print("服务已在后台启动...") time.sleep(10) # 模拟第三方程序运行 lib.StopServer() print("服务已关闭") 4,再次优化代码 当实际测试的时候,我发现它会阻塞调用者,为了解决这个问题,我们需要再次改造程序。将启动服务改为非阻塞模式。将Gin的启动逻辑放入协程,并利用http.Server提供的Shutdown方法来实现优雅退出。 ...

2026-01-29 · 5 min · Eagle

告别域名过期焦虑,豆子域名管家支持批量检测加企微和钉钉通知

域名过期导致业务中断、流量缺失、品牌受损的案例比比皆是。手动记录域名的到期时间?是否会忘记,漏看? 我以前曾介绍过,我在微信小程序实现了域名证书监控功能。但是担心隐私,功能限制。这次我带来了豆子域名管家,本地化运行。 经过数月的规划和开发测试,豆子域名管家终于可以使用了。这是一款完全本地运行、支持批量导入管理以及可以企微和钉钉通知的域名证书检测工具。旨在解决域名过期监控难题。 特性 传统方式 豆子域名管家 运行方式 依赖云端服务 纯本地运行,数据不出本地 批量处理 手动逐个查询 一键导入,批量删除 通知渠道 单一(邮件/短信) 钉钉+企微双通道,支持Markdown格式 自定义提醒 固定时间提醒 可配置通知时间,提前预警天数 系统集成 无 系统托盘常驻,后台静默运行 隐私安全 数据上传第三方 域名数据和配置数据存本地 技术架构亮点: 工具基于Wails3框架构建,采用Vue3+NaiveUI前端技术栈,确保界面简洁美观,交互流畅,支持暗黑/浅色两种主题。 本地证书检测引擎,直接调用系统网络库进行TLS检测,无需依赖外部API。 多线程并发处理,使用go的特性批量进行域名检测。 跨平台兼容:支持Windows、macOS、Linux系统。 配置持久化,所有配置本地存储,重启后自动恢复。 支持域名证书检测,域名到期时间检测。 软件运行界面预览: 控制面板 配置页面 基本操作指南: 1,导入域名我们需要把要监控的域名按行录入到txt文档中,可以在软件配置界面下载示例模板,当准备完成后,选择刚才的文件,然后验证。没有问题后,点击确认导入即可。 2,配置机器人目前工具支持钉钉机器人和企业微信机器人,支持Markdown格式消息,可以查点击推送预览效果按钮查看推送效果。当输入机器人配置后,可以验证测试,当收到消息后说明配置成功,点击保存即可。可以同时配置企微和钉钉。这样会同时推送两份通知。 3,配置推送通知策略目前工具扫描调度间隔固定24小时,可以配置通知时间,和告警天数间隔。在通知时间,系统会将当天扫描的结果报表推送到机器人。如果用户更新某些域名证书后,可以在监控面板进行手动刷新。想查看效果,可以把通知时间设置为当前时间加几分钟,当到达时间后,将推送报表。确认无误后,可以调整为真正的推送时间。 4,系统托盘运行当完成域名导入和机器人以及通知策略配置后,可以关闭窗口,工具将自动缩放到系统托盘。如果需要退出,需要右键系统托盘退出。注意,如果退出工具,将不能监控域名,因为这是一个本地工具。所以需要工具长期运行。 5,监控面板监控面板的仪表板显示你的域名配置项统计信息,表格显示监视的域名列表。域名可以搜索,刷新以及删除。域名按照过期、告警、正常顺序排序。请查看最前面域名并及时处理。 工具按照自己的真实需求开发,如果你需要尝试,可以通过下方下载链接。目前仅提供了Windows版本。 https://lab.91demo.top/b011 如果您有任何问题和建议,欢迎反馈和交流。

2026-01-28 · 1 min · Eagle

拒绝频繁上传服务器!我用 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

把项目做成像 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

音频格式转换新增支持 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