<?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/tags/%E4%BA%8C%E7%BB%B4%E7%A0%81/</link><description>Recent content in 二维码 on 豆子技术站</description><generator>Hugo -- 0.155.1</generator><language>zh-cn</language><lastBuildDate>Sat, 15 Mar 2025 07:22:34 +0000</lastBuildDate><atom:link href="https://blog.91demo.top/tags/%E4%BA%8C%E7%BB%B4%E7%A0%81/index.xml" rel="self" type="application/rss+xml"/><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><item><title>实战笔记：为了不再发错下载链接，我给工具箱加了“扫码鉴定”</title><link>https://blog.91demo.top/tool-scan/</link><pubDate>Tue, 09 Jan 2018 12:40:16 +0800</pubDate><guid>https://blog.91demo.top/tool-scan/</guid><description>&lt;p&gt;小工具可以解决大问题。在软件发布流程中，最尴尬的事情莫过于：宣传图已经发出去了，用户扫码后却发现链接打不开，或者下载了一个旧版本的安装包。&lt;/p&gt;
&lt;p&gt;我就亲身经历过这么一次“翻车”事故。&lt;/p&gt;
&lt;h3 id="事故现场一个-url-引起的麻烦"&gt;事故现场：一个 URL 引起的麻烦&lt;/h3&gt;
&lt;p&gt;有一次，我在更新软件下载页面后，由于换了一个新的 URL 地址，在生成二维码阶段没有进行严格校验，就直接把二维码发布了出去。&lt;/p&gt;
&lt;p&gt;结果当用户兴冲冲地扫码下载时，发现软件运行异常。我对比了半天才发现，生成二维码时填写的 URL 链接版本号写错了一个字符，导致用户下载到了一个有问题的版本，这是偶然中的极端。&lt;/p&gt;
&lt;p&gt;虽然这只是一个低级错误，但它让我意识到：&lt;strong&gt;在二维码挂载后、正式发布前，必须有一个极简的“内容鉴定”环节。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="痛点如何快速无痛地校验"&gt;痛点：如何快速、无痛地校验？&lt;/h3&gt;
&lt;p&gt;通常我们校验二维码，习惯性地直接拿手机扫一下。但如果二维码指向的是一个大容量的 APP 下载包或复杂的跳转链接，扫码后手机会自动触发下载或进入复杂的网页路径。&lt;/p&gt;
&lt;p&gt;我其实并不想真的下载并安装测试（那是后续的 QA 环节），我只是想&lt;strong&gt;一眼看到二维码里到底写了什么字符&lt;/strong&gt;，确定那个 URL 是不是我想要的那一个。&lt;/p&gt;
&lt;h3 id="实现借力小程序的原生能力"&gt;实现：借力小程序的原生能力&lt;/h3&gt;
&lt;p&gt;既然发现了痛点，解决起来就非常顺手了。得益于微信生态对扫码的深度支持，我在“豆子工具”里实现了一个“二维码识别”功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心逻辑&lt;/strong&gt;：直接调用小程序的 &lt;code&gt;wx.scanCode&lt;/code&gt; 接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能表现&lt;/strong&gt;：不仅支持扫描实物二维码，还支持从手机相册里直接读取生成的二维码图片，还支持小程序码扫描，支持查看页面路径和scene值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果反馈&lt;/strong&gt;：系统不会触发自动跳转，而是将二维码包含的原始文本、URL、一维码内容直接以&lt;strong&gt;纯文本&lt;/strong&gt;的形式展示在屏幕上。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="价值多看一眼少出一次错"&gt;价值：多看一眼，少出一次错&lt;/h3&gt;
&lt;p&gt;现在，每当我生成一个新的二维码，无论是用于软件下载、文档分享还是活动跳转，我都会习惯性地用自己的工具“扫一下”：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确认 URL 参数是否完整。&lt;/li&gt;
&lt;li&gt;确认是否有肉眼难以察觉的拼写错误。&lt;/li&gt;
&lt;li&gt;确认二维码的类型（一维码还是二维码）是否符合预期。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="识别二维码" loading="lazy" src="https://blog.91demo.top/images/wander/tool-scan.webp"&gt;&lt;/p&gt;
&lt;h3 id="技术实现"&gt;技术实现&lt;/h3&gt;
&lt;p&gt;使用小程序纯原生实现，仅仅做一个页面展示即可。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;scanQRCode: function () {
const that = this;
wx.showLoading({
title: &amp;#39;识别中...&amp;#39;,
mask: true,
})
wx.scanCode({
onlyFromCamera: false,
scanType: [&amp;#39;qrCode&amp;#39;, &amp;#39;barCode&amp;#39;, &amp;#39;datamatrix&amp;#39;, &amp;#39;pdf417&amp;#39;, &amp;#39;wxCode&amp;#39;],
success: (res) =&amp;gt; {
wx.hideLoading()
if(res.scanType==&amp;#39;WX_CODE&amp;#39;){
that.setData({
scanResult: res.path
});
}else{
that.setData({
scanResult: res.result
});
}
},
fail: (err) =&amp;gt; {
wx.hideLoading()
console.error(err);
}
});
},
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意的是，小程序码和其它码的结果字段有点不同，所以需要区分，以便取到正确的值。&lt;/p&gt;</description></item></channel></rss>