<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>豆子实验室 on 豆子技术站</title><link>https://blog.91demo.top/categories/%E8%B1%86%E5%AD%90%E5%AE%9E%E9%AA%8C%E5%AE%A4/</link><description>Recent content in 豆子实验室 on 豆子技术站</description><generator>Hugo -- 0.155.1</generator><language>zh-cn</language><lastBuildDate>Sun, 17 May 2026 07:22:34 +0000</lastBuildDate><atom:link href="https://blog.91demo.top/categories/%E8%B1%86%E5%AD%90%E5%AE%9E%E9%AA%8C%E5%AE%A4/index.xml" rel="self" type="application/rss+xml"/><item><title>谋划已久的蓄力：我的官网第三次重构（Nuxt3 + Naive UI + Nginx</title><link>https://blog.91demo.top/nuxt3-mysite/</link><pubDate>Sun, 17 May 2026 07:22:34 +0000</pubDate><guid>https://blog.91demo.top/nuxt3-mysite/</guid><description>&lt;p&gt;最近，我使用 Nuxt3 全面重构了我的官网。大功告成后经实测发现，首屏加载速度其实变慢了。但奇妙的是，这次重构我却一点都不后悔。&lt;/p&gt;
&lt;p&gt;上一个版本的官网，我是用最纯粹的 HTML+CSS+JS 开发的。说实话，刚写完的时候自己非常满意，纯原生代码，没有任何框架负担。但最近在规划我拥有的数字资产后续的功能时，我的想法发生了一些根本性的转变。我开始意识到，之前把很多工具一股脑塞进微信小程序里，其实并不合理。对于开发者或者需要高频使用不需要账号体系的工具的用户来说，在电脑大屏上操作，远比在手机上点来点去效率高得多，这也是我在小程序上实现没有多少人访问的原因吧。更别说每次改动功能就被小程序那让人头疼的、动不动就被卡住的审核机制了。于是我冒出一个强烈的念头：我要把小程序里适合在电脑上用的工具，全部搬到 Web 网页上来，面向全球用户开放；同时去精简小程序，让它只留下适合移动端且符合小程序场景的即开即用的功能。当我带着这个目标重新去审视我那套 HTML+CSS+JS 的旧代码时，我发现现在这样的写法后期维护起来极其麻烦。最让我抓狂的就是组件复用问题。在旧网站里，像导航栏和页脚这种每个页面都有的东西，要么靠人工全量复制粘贴，要么就直接缺失。每次我想改动一个导航菜单，就要把所有 HTML 文件全部手动改一遍。这种“原始人”一样的维护方式，对于我想规范化后期维护的人来说，不仅工作量巨大，而且写起来让人极度讨厌和烦躁。另一个更深层的坎是：我不希望这个官网仅仅是一个死板的展示页面。在我接下来的蓝图里，这个 Web 平台应该是一个“核心底座”——它能作为数据和逻辑的中心，向上能和小程序联通，向下能跟我的客户端工具做交互，而不是各自为战的孤岛。回看过去，这个官网前后重构了不下三次，但过去的改动大多只是 UI 层面的修修补补。而这一次，也许正是意识的苏醒，让我决定不再无谓地徘徊。为了彻底解决组件复用、多端联通以及降低未来维护成本的痛点，我最终敲定了 Nuxt3 + Naive UI + Nginx 这套架构，并决定长期坚持走下去。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;跨端响应式的 UI 抉择，官网的 UI 必须完美兼容移动端、PC 端和平板端。以前尝试过很多 UI 框架，例如WeUI，Bootstrap等，都不尽如人意，我也不想再投入过多精力去试错。虽然我有丰富的后端管理平台 UI 开发经验，但总觉得那些框架不太适合做门户官网。我需要一款既简单易学、又能满足多端响应式需求的 UI 框架。刚好之前做客户端项目时用过 Naive UI，体验极佳，于是这次便果断选择了它。&lt;/li&gt;
&lt;li&gt;确定使用 Naive UI 后，前端生态自然锁定了 Vue3。虽然原生 JavaScript 写简单页面很方便，但在实现复杂功能时，远不如 Vue3 高效。Vue3 内置了诸多便利功能，尤其是对有 Vue2 基础的我来说，上手极快。我非常享受在同一个 Vue 文件中把逻辑、页面、样式“一把刷”搞定的开发体验。&lt;/li&gt;
&lt;li&gt;选定 UI 和前端底座后，我需要一个强大的开发框架来落地想法。这次重构，我计划编写一系列在线工具来替代微信小程序的部分功能，并计划停用小程序这些功能。经过改造后，我的这些工具将彻底面向全球用户开放，而不仅仅是小程序用户。在开发规范上，我更倾向于有组织、有约束的结构。经过一番调研，我发现当下流行的 Nuxt3 正好完美契合我的诉求。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="为什么选择nuxt3"&gt;为什么选择Nuxt3？&lt;/h2&gt;
&lt;p&gt;比如摆脱复制粘贴：以前使用原生开发网页，HTML 代码难以复用，缺乏组件化概念，每个页面都要复制粘贴相同的导航栏和底部，在没有框架的情况下，这些布局都需要每个页面去赋值。在 Nuxt3 中，我可以直接利用 Vue3 的 Layout（布局）机制完美解决。&lt;/p&gt;
&lt;p&gt;状态管理与声明式渲染：纯 JS 操作 DOM 过于繁琐。对于习惯了后端逻辑的我来说，Vue3 的声明式渲染和响应式数据，写起来比纯单页面或原生 DOM 舒服太多，更符合我的思维习惯。&lt;/p&gt;</description></item><item><title>构建自定义验证码登录系统及研究语音验证码实战</title><link>https://blog.91demo.top/vcode-login/</link><pubDate>Fri, 21 Mar 2025 07:22:34 +0000</pubDate><guid>https://blog.91demo.top/vcode-login/</guid><description>&lt;p&gt;当我使用小程序码登录网站时，我发现了一个问题，在手机端不方便登录。我们都知道手机号验证码可以登录网站，但是我没有资源去实现手机号验证码功能，我使用一个变通的方案，在手机端不使用手机号验证码也能登录。&lt;/p&gt;
&lt;h3 id="小程序实现验证码登录"&gt;小程序实现验证码登录&lt;/h3&gt;
&lt;p&gt;它的核心还是鉴权，我在小程序端制作了一个获取验证码界面，它可以生成模拟编号和验证码。当用户点击获取验证码时，会向后台请求返回编号和验证码，后台这个时候会记录为哪个用户openid生成了哪个验证码。那为什么不单单使用验证码呢？还要再添加一个编号，不麻烦吗？因为单单使用验证码会发生撞车的可能。要知道，验证码一般4位或者6位，很容易被暴力攻击的。&lt;/p&gt;
&lt;p&gt;当用户在网站端输入编号和验证码时，后端会校验是否存在这对编号和验证码，如果校验正确，将取出openid并绑定到sessionID上，然后返回给前端，存入cookie中。可以看看前面的文章，扫描二维码登录，同样的道理。&lt;/p&gt;
&lt;p&gt;除了在小程序生成验证码外，我们还可以在公众号中发送消息获取验证码。它其实也是使用了微信的账号体系。&lt;/p&gt;
&lt;h3 id="公众号实现验证码登录"&gt;公众号实现验证码登录&lt;/h3&gt;
&lt;p&gt;要知道公众号不仅是内容分发平台，更是一个强大的&lt;strong&gt;身份认证中间件&lt;/strong&gt;。我们使用发送消息来获取验证码信息。&lt;/p&gt;
&lt;h4 id="1-握手与回调-webhook"&gt;1. 握手与回调 (Webhook)&lt;/h4&gt;
&lt;p&gt;当你在公众号消息发送验证码三个字时，微信会推送事件到你的服务器。我使用go对接了微信公众号消息。&lt;/p&gt;
&lt;p&gt;要在 Go 中接收微信消息，你必须先在微信公众平台配置一个 &lt;strong&gt;服务器地址 (URL)&lt;/strong&gt;。** 微信会向你的 URL 发送一个 GET 请求，包含签名、随机数等。你必须按照规定的算法计算并返回正确的 &lt;code&gt;echostr&lt;/code&gt;，这被称为“服务器验证”。 验证通过后，每当用户发送消息，微信服务器就会以 POST 方式将消息体推送给你。&lt;/p&gt;
&lt;h4 id="2-解析-xml-数据"&gt;2. 解析 XML 数据&lt;/h4&gt;
&lt;p&gt;与现代 API 不同，微信公众号的推送采用的是 &lt;strong&gt;XML&lt;/strong&gt; 格式。Go 的标准库 &lt;code&gt;encoding/xml&lt;/code&gt; 提供了强大的解析能力：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WxMsg&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ToUserName&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;ToUserName&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;FromUserName&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;FromUserName&amp;#34;`&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 这就是用户的 OpenID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Content&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;Content&amp;#34;`&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 用户发来的文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="3-验证码生存逻辑"&gt;3. 验证码生存逻辑&lt;/h4&gt;
&lt;p&gt;当用户发送“验证码”关键字时，我们的 Go 后端会生成一个 4-6 位的随机数和一个编号。并将消息中的 OpenID 与 编号和随机数 存入 Redis，并设置 TTL（如 5 分钟）。然后通过 XML 响应将验证码发回给用户。&lt;/p&gt;
&lt;h4 id="4-身份绑定完成登录"&gt;4. 身份绑定，完成登录&lt;/h4&gt;
&lt;p&gt;我们在Web HTML页面提供了登录表单，提供编号和验证码输入框。用户输入编号和验证码后提交到后端，后端从 Redis 中根据编号和验证码反查 OpenID，若存在且有效，则代表身份验证成功，返回包含身份的TOKEN。&lt;/p&gt;</description></item><item><title>基于 Go + 小程序实现网页端“扫码登录”实战</title><link>https://blog.91demo.top/qrcode-login/</link><pubDate>Sat, 15 Mar 2025 07:22:34 +0000</pubDate><guid>https://blog.91demo.top/qrcode-login/</guid><description>&lt;p&gt;不想使用账号和密码登录，害怕被攻击，也不想做注册等功能；不想使用手机号和验证码登录，没有钱也没有资质去做这些。我想到了二维码，通过二维码进行登录。这里有一个核心的问题还是如何鉴权？&lt;/p&gt;
&lt;p&gt;在了解小程序后，我就决定使用小程序扫描二维码登录，使用小程序自带的微信账户体系完成鉴权。&lt;/p&gt;
&lt;p&gt;我们要知道，二维码（QR Code）是连接物理世界与数字世界的“虫洞”。在登录系统中，它承载着一个临时的&lt;strong&gt;身份信标&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在实现这个Web登录功能的过程中，我们需要：&lt;/p&gt;
&lt;h3 id="1-生成有效二维码"&gt;1. 生成有效二维码&lt;/h3&gt;
&lt;p&gt;这个二维码需要显示在网页上，供用户扫码登录，登录二维码通常包含一个加密的 URL 或一个唯一的 &lt;strong&gt;UUID&lt;/strong&gt;（通用唯一识别码）。&lt;/p&gt;
&lt;p&gt;这个唯一识别码需要具备的特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;唯一性：&lt;/strong&gt; 每一对扫描动作都必须对应一个独一无二的 ID。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时效性：&lt;/strong&gt; 二维码必须配合 Redis 设置过期时间（如 2 分钟），逾期自动失效。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在本项目中，我们将用户的微信 OpenID 作为 Key，生成的验证码作为 Value，使用Redis进行存储：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 存储验证码，并设置 5 分钟过期&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Set&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;user:123:code&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;8888&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Minute&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Err&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 读取验证码&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;val&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rdb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;user:123:code&amp;#34;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Result&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当有了二维码内容后，我们需要工具来生成二维码，在 Go 生态中，我们使用 &lt;code&gt;skip2/go-qrcode&lt;/code&gt; 库来完成像素的绘制：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 生成二维码字节数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;png&lt;/span&gt; []&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;png&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;qrcode&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Encode&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;91demo.top&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;sessionID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;qrcode&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Medium&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;256&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为了防止用户伪造扫码请求，二维码里的内容通常是加密的或者是不可预测的长随机数（UUID）。只有真实存在的 ID 才能通过后端的 Redis 校验。&lt;/p&gt;
&lt;h3 id="2-传输二维码"&gt;2. 传输二维码&lt;/h3&gt;
&lt;p&gt;当在服务端生成二维码后，还需要传递给浏览器，让浏览器进行显示，用户才可以扫码。这里有两个方法：1，生成图片文件，然后浏览器下载后显示。2，将图片内容转为Base64字符串传递给浏览器。这里我们选择了后者，我们不希望在用户硬盘上产生大量的临时 .png 文件。&lt;/p&gt;
&lt;p&gt;在Go中可以这样操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 转换为前端可直接识别的 Data URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;base64Img&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data:image/png;base64,&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StdEncoding&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;EncodeToString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;png&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为了让前端不至于崩溃，后端必须返回统一的格式。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HandleLogin&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 逻辑处理...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;gin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;H&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;base64Img&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在前端，我们不再需要引入沉重的第三方库来做简单的请求。浏览器原生提供的 &lt;strong&gt;Fetch&lt;/strong&gt; API 简洁且基于 Promise。&lt;/p&gt;</description></item></channel></rss>