谋划已久的蓄力:我的官网第三次重构(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 舒服太多,更符合我的思维习惯。 ...

2026-05-17 · 2 min · Eagle

构建自定义验证码登录系统及研究语音验证码实战

当我使用小程序码登录网站时,我发现了一个问题,在手机端不方便登录。我们都知道手机号验证码可以登录网站,但是我没有资源去实现手机号验证码功能,我使用一个变通的方案,在手机端不使用手机号验证码也能登录。 小程序实现验证码登录 它的核心还是鉴权,我在小程序端制作了一个获取验证码界面,它可以生成模拟编号和验证码。当用户点击获取验证码时,会向后台请求返回编号和验证码,后台这个时候会记录为哪个用户openid生成了哪个验证码。那为什么不单单使用验证码呢?还要再添加一个编号,不麻烦吗?因为单单使用验证码会发生撞车的可能。要知道,验证码一般4位或者6位,很容易被暴力攻击的。 当用户在网站端输入编号和验证码时,后端会校验是否存在这对编号和验证码,如果校验正确,将取出openid并绑定到sessionID上,然后返回给前端,存入cookie中。可以看看前面的文章,扫描二维码登录,同样的道理。 除了在小程序生成验证码外,我们还可以在公众号中发送消息获取验证码。它其实也是使用了微信的账号体系。 公众号实现验证码登录 要知道公众号不仅是内容分发平台,更是一个强大的身份认证中间件。我们使用发送消息来获取验证码信息。 1. 握手与回调 (Webhook) 当你在公众号消息发送验证码三个字时,微信会推送事件到你的服务器。我使用go对接了微信公众号消息。 要在 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. 验证码生存逻辑 当用户发送“验证码”关键字时,我们的 Go 后端会生成一个 4-6 位的随机数和一个编号。并将消息中的 OpenID 与 编号和随机数 存入 Redis,并设置 TTL(如 5 分钟)。然后通过 XML 响应将验证码发回给用户。 4. 身份绑定,完成登录 我们在Web HTML页面提供了登录表单,提供编号和验证码输入框。用户输入编号和验证码后提交到后端,后端从 Redis 中根据编号和验证码反查 OpenID,若存在且有效,则代表身份验证成功,返回包含身份的TOKEN。 ...

2025-03-21 · 1 min · Eagle

基于 Go + 小程序实现网页端“扫码登录”实战

不想使用账号和密码登录,害怕被攻击,也不想做注册等功能;不想使用手机号和验证码登录,没有钱也没有资质去做这些。我想到了二维码,通过二维码进行登录。这里有一个核心的问题还是如何鉴权? 在了解小程序后,我就决定使用小程序扫描二维码登录,使用小程序自带的微信账户体系完成鉴权。 我们要知道,二维码(QR Code)是连接物理世界与数字世界的“虫洞”。在登录系统中,它承载着一个临时的身份信标。 在实现这个Web登录功能的过程中,我们需要: 1. 生成有效二维码 这个二维码需要显示在网页上,供用户扫码登录,登录二维码通常包含一个加密的 URL 或一个唯一的 UUID(通用唯一识别码)。 这个唯一识别码需要具备的特性: 唯一性: 每一对扫描动作都必须对应一个独一无二的 ID。 时效性: 二维码必须配合 Redis 设置过期时间(如 2 分钟),逾期自动失效。 在本项目中,我们将用户的微信 OpenID 作为 Key,生成的验证码作为 Value,使用Redis进行存储: // 存储验证码,并设置 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. 传输二维码 当在服务端生成二维码后,还需要传递给浏览器,让浏览器进行显示,用户才可以扫码。这里有两个方法:1,生成图片文件,然后浏览器下载后显示。2,将图片内容转为Base64字符串传递给浏览器。这里我们选择了后者,我们不希望在用户硬盘上产生大量的临时 .png 文件。 在Go中可以这样操作: // 转换为前端可直接识别的 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 · 2 min · Eagle