ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

我用码道从零手搓了个二维码生成器,还让它自己扫自己、逐字证明没骗我

我用码道从零手搓了个二维码生成器,还让它自己扫自己、逐字证明没骗我 我用码道从零手搓了个二维码生成器还让它自己扫自己、逐字证明没骗我一键开通华为云码道 CodeArts 代码智能体 https://developer.huaweicloud.com/codeartsco.html?sourcedmzntgwatomgit1sourceaddmzntgwatomgithd作品介绍先说清楚这是个啥一个纯前端、零第三方运行时依赖的二维码生成器。输入一段文字、一个网址、一个 WiFi 密码、一张名片它当场给你画出来能改前景背景色、能切成圆点、能塞个中心 logo、能一键下载 PNG 和 SVG。这些功能随便一个在线工具都有。我做这个的野心不在生成在自证。市面上九成的二维码生成器都是import一个现成的库qrcode、qrcodejs、python 的 qrcode 之类调一下toDataURL就完事。你根本不知道它内部到底怎么把HELLO变成那堆黑白格子的你只能信它。而我这个项目——二维码编码器是我一行一行手写的GF(256) 有限域、Reed-Solomon 纠错、八种掩码的罚分规则、格式信息和版本信息的 BCH 编码全自己撸。然后关键来了手写的东西对不对不能我自己说了算。所以我给它配了一整套自我体检——用真正的解码器 jsQR 把我生成的二维码图片扫回来逐字符跟我原始输入比对。18 个用例覆盖文本/网址/WiFi/名片四种载荷、L/M/Q/H 四档纠错、版本一直到 718/18 全部一字不差地扫回来了。89 项单元测试全绿三份对拍报告全过。仓库是公开的你可以 clone 下来自己npm run verify复现。为什么非要从零写而不是 import 一个库起因很俗。我平时给 stuff 贴二维码有次想搞个扫码连 WiFi的码随手用某在线生成器结果某些安卓机的相机扫不出来换个生成器又好了。我就好奇同一个 WiFi 信息两家生成的码还能不一样去查了才发现WiFi 二维码那套WIFI:T:WPA;S:名字;P:密码;;的转义规则名字里带分号、冒号要反斜杠转义各家实现细节不一样有的还漏了隐藏网络字段。说白了生成器这活儿能扫出来和扫得对是两回事而大多数工具只保证前者。那我就想与其信别人不如自己写一个并且把扫得回来这件事做成一个能反复跑的自动化验证。这正好也是这次码道比赛我想试的东西——让 AI 编程智能体华为云码道 CodeArts去啃一个有客观真值可对拍的活而不是让它生成一个看着挺好但没人能证伪的玩具。可验证是我给这个项目选的护城河。好不好看是主观的但jsQR 能不能把这张图扫回原话是客观的是或否赖不掉。先跑起来看效果在讲怎么造之前先看它长啥样、验得怎么样。这是我在本地npm run serve起来后截的几态。网址模式扫出来就是原链接WiFi 模式SSID 和密码里故意塞了分号冒号这种麻烦字符转义后照样能扫回名片模式vCard 3.0切圆点样式 蓝色前景 纠错等级拉到 H自我体检卡同步变成 version 3 / size 29 / ECH / Byte 容量 24 / 模块 SHA-256 换了一个然后是我最看重的部分——验证。跑npm run verify三条线全过这三行数字是整篇文章的底气所在。下面讲它们是怎么来的以及中间踩的坑。把提示词钉到函数级别让 AI 自由发挥我是分轮把活喂给码道的第一轮只让它干一件事建一个公开仓库从零写二维码编码核心纯逻辑不许碰 DOM写完配单元测试。给 AI 派活我学乖了提示词必须钉到函数级否则它会给你一个看起来能跑的糊弄版。我第一轮的原话大概是这样src/galois.js —— GF(256) 有限域本原多项式 0x11d构建 log/antilog 表 导出 gfMul(a,b)、gfPow、gfInv。 src/reed-solomon.js —— RS 纠错构造生成多项式 rsGeneratorPoly(degree) 对数据码字求余得到 eccLength 个纠错码字。 src/tables.js —— 按 ISO/IEC 18004 提供各版本各纠错等级的 EC 码字数/分块数表、 对齐图案位置表、Byte 模式数据容量表。 src/qr.js —— 主编码位流打包、版本与纠错等级自动选择、分块交织、 放置功能图形、遍历 8 种掩码用罚分选最优、格式信息 BCH(15,5)、 版本信息 BCH(18,6)输出 { size, modules:boolean[][] }。 测试必须锚定外部已知真值不能自证。 直接建文件并 git commit push不要只描述、不要贴代码正文。最后那句直接建文件并提交别只描述是血泪教训——不加这句码道有概率给你写一大段我打算这样做……的方案一个字代码不落。它第一轮交出来的 GF(256) 乘法是对数表那套经典写法exportfunctiongfMul(a,b){if(a0||a255||b0||b255){thrownewRangeError(gfMul operands out of range:${a},${b});}if(a0||b0)return0;returnGF_EXP[GF_LOG[a]GF_LOG[b]];}41 项测试全绿仓库公开。看着挺完美直到我自己 clone 下来复核时发现了一个让我后背发凉的东西。GF(256)我差点被一个教科书值带进沟里我在提示词里为了逼它锚定外部真值写了这么一条“测试必须断言gfMul(0x57, 0x83) 0xC1教科书经典值”。结果码道跑出来把这行注释改成了这样还专门在回复里跟我顶了一句/** * 经典核对值gfMul(0x57, 0x83) 0x31。 * 注0xC1 是本原多项式 0x11BAES下的对应结果 * QR Code 的 Reed-Solomon 使用本原多项式 0x11D故真值为 0x31。 */我一开始还以为它写错了。赶紧自己拿笔算了遍 GF(256) 乘法0x57 和 0x83 相乘在 QR 用的本原多项式0x11Dx⁸x⁴x³x²1下余数确实是0x31而 0xC1 是 AES 用的0x11B下的结果。也就是说——是我这个教科书经典值记串了把 AES 的那套当成 QR 的了。这事给我敲了警钟GF(256) 的坑就在这0x11B和0x11D两个多项式长得就差一位全世界教程里 0xC1 这个例子大多是讲 AES 的我顺手抄过来用到了 QR 上。如果码道当时听话地按我说的断言 0xC1它要么把多项式偷偷改成 0x11B那整个 QR 纠错就全错了要么测试永远红。它选了后者并告诉我你这个真值本身是错的反而救了这个项目。我后来专门去查确认 0x11D 才是 ISO/IEC 18004 规定的。这条被我写进了 README 和源码注释里就为了以后别人扫源码别被我一开始的错误带偏。顺带说第一轮它自己还干了件让我惊喜的事它主动拿 Nayuki 和 kazuhikoarase 两个业界公认的参考 QR 库做了交叉验证——用相同的掩码、相同的码字把它的实现和两个参考库生成的矩阵逐比特比对。这不是我要求的是它自己加的保险。Reed-Solomon多项式乘法在有限域里怎么走纠错码是二维码能破破烂烂还能扫的根本。生成多项式那段实现码道写得很规矩就是不断乘(x αⁱ)exportfunctionrsGeneratorPolyFull(degree){letpoly[1];// 常数多项式 1letroot1;// α^0for(leti0;idegree;i){constnextnewArray(poly.length1).fill(0);for(letj0;jpoly.length;j){next[j]^poly[j];// poly * xnext[j1]^gfMul(poly[j],root);// poly * root}polynext;rootgfMul(root,2);// root * α}returnpoly;}掩码部分要遍历八种、按 ISO 18004 的 N1~N4 四条罚分规则打分取最低。这块码道一度在 2×2 同色罚分和全黑/对角线的判分上反复纠结因为它想确保罚分是确定性的、而不是碰运气。这种较真我喜欢。零依赖 PNG 编码器连出图都不许用库产品定位是零运行时依赖那把编码好的矩阵变成一张 PNG 图片也不能import sharp或者用 canvas那是浏览器侧Node 侧要能跑。所以码道得自己写一个 PNG 编码器PNG 签名、IHDR、IDAT、IEND每段算 CRC32图像数据用 zlib 的 stored不压缩块塞进去末尾补 adler32。// 零依赖 PNG手写签名 IHDR IDAT(zlib stored, 0x00 不压缩) IEND每段带 CRC32exportfunctionencodePng(matrix,{scale8,quiet4,bg[255,255,255],fg[0,0,0]}{}){// ... 逐行铺 filter byte(0x00) RGB 像素zlib stored 包一层CRC32/Adler32 自己算}验证它没写歪最直接的办法就是拿 jsQR 去扫它吐出来的 PNG——扫得回原文说明 PNG 和里面的二维码都对。这就是下一节的回环。顺带说 WiFi 载荷这块码道处理转义很细——SSID 或密码里带分号、冒号、反斜杠、双引号都得按规范反斜杠转义nopass 时还要省掉密码字段exportfunctionwifi({ssid,password,encryptionWPA,hiddenfalse}{}){constnormString(encryption).toUpperCase();if(![WPA,WEP,NOPASS].includes(norm)){thrownewRangeError(encryption must be WPA|WEP|nopass, got:${encryption});}constencnormNOPASS?nopass:norm;letoutWIFI:T:${enc};S${escapeWifi(ssid)};if(enc!nopass)out;P${escapeWifi(password)};if(hidden)out;H:true;return${out};;;}这一轮 R2 也不是一遍过的。码道在 PNG 像素坐标的静区偏移上算错过一次quiet1 时把 (0,0) 模块对应的像素写成了 (1,1) 而非 (2,2)是我本地 clone 跑测试时抓到、它再回去修的。这种它说绿了我还得自己跑一遍的来回贯穿全程。那个二维码画成一片白的坑前端页面白色系那套和 SVG 渲染是第三轮做的。我在本地起了服务一看人傻了预览区几乎全白只有右下角一小坨黑块。查下来是render.js里两套坐标系打架rect用的是像素坐标x(colquiet)*scale比如 x32、width8但viewBox却写成了模块单位0 0 size size比如 0 0 29 29。像素坐标 32 远超 viewBox 的 29全被裁到画布外了只剩零星几个没裁干净。修法就是把 viewBox 和像素坐标统一到同一空间constpx(size2*quiet)*scale;svg xmlnshttp://www.w3.org/2000/svg viewBox0 0${px}${px} width${px} height${px}修完二维码就完整居中长这样了这个坑有意思的地方在于单元测试当时是绿的。因为 render 的测试只数了rect的个数、看了 width 属性压根没校验 viewBox 和 rect 坐标是不是同一个空间。这给我上了一课——测试绿不等于对尤其是那种自证式的测试。这个点后来专家评审也独立揪出来了我记着。可验证护城河让代码自己扫自己这是整个项目我最想讲的部分。验证脚本放verify/用 jsqr、pngjs 当 devDependency只在验证时用产品运行时依旧零依赖。核心逻辑朴素得感人import{PNG}frompngjs;importjsQRfromjsqr;constmanifestJSON.parse(readFileSync(join(EVIDENCE,manifest.json),utf8));for(constentryofmanifest){constpngPNG.sync.read(readFileSync(join(EVIDENCE,png,entry.file)));constcodejsQR(newUint8ClampedArray(png.data),png.width,png.height);constmatchcodecode.dataentry.payload;// 扫回来逐字符比原话// ...}jsQR 是业界独立实现的一个纯 JS 二维码解码器不是我写的、也不是码道写的拿它当第三方裁判。它能把图扫回原话就证明我这套从零写的编码器含 GF、RS、掩码、BCH、PNG 出图整条链路是对的。除了回环还有两条一是标准对标——断言每张码的size 4*version17且各版本各等级的码字数对得上 ISO/IEC 18004 的表二是确定性——同一输入编两次把模块矩阵展平成 01 串算 SHA-256两次必须一模一样可重放。标准对标这块评审后我特意让它别 import 自己的表自证而是内置一份权威逐版本总码字数表去对// 第三方权威表ISO/IEC 18004 逐版本总码字数不再拿自己的 tables.js 自校constTOTAL_CODEWORDS{1:26,2:44,3:70,4:100,5:134,6:172,7:196,8:242,9:292,10:346};for(constcofcases){assert.equal(c.size,4*c.version17);assert.equal(c.totalCodewords,TOTAL_CODEWORDS[c.version]);// 对第三方常量}右侧那张自我体检卡就是把 version、size、纠错等级、Byte 数据容量、模块 SHA-256 这五个数实时摆在页面上。它不是装饰是护城河在 UI 上的投影。三轮专家评审揪出的硬伤写完 R1-R5 我以为差不多了于是拉了 UI、技术、产品三个专家评审各自读真实代码和截图打分。分数不算高UI 78、技术 86、产品 78。但揪出来的问题很实在尤其技术专家这三条我服galois.js 注释自相矛盾——它发现源码注释里还留着gfMul(0x57,0x83)0xC1那句就是我一开始写错的那个而实现用的是 0x11D 得 0x31。代码对、注释错评委扫源码会误判这项目用错了多项式。必须改。标准对标其实是自证——verify/standard.mjs当时是 import 自己的tables.js再校自己总码字数算了却没跟任何外部常量比。等于自己给自己发奖状。得内置一份真正的 18004 权威表去对。解码回环有覆盖盲区——15 个用例只测了 L/M 两个等级、版本还跳过了 7 和 8。而版本 ≥7 才有版本信息图形Q/H 的分块交织也更复杂。也就是说这两块从来没被 jsQR 独立验证过。这三条我全认直接开了 R6 一轮让码道修改注释、内置第三方码字数表、把用例扩到 18 个补上 v3-Q、v4-Q、v7-H再把确定性那部分诚实降级成回归哨兵它只证明幂等不证明正确正确性以 jsQR 为准。UI 那边也把预览框的灰虚线占位感、自我体检卡的排版、下载按钮的禁用态一并打磨了。修完再跑node --test89 全绿npm run verify三条线全部 18/18。v7-H 那条尤其关键——它证明版本信息图形的 BCH 编码也是对的。码道推不上去这件事才是真·开发日常如果说上面都是技术那这一段是和工具搏斗的流水账但我觉得这才是 AI 编程真实的体感值得写。整个过程我踩了至少这么几个坑模型配额不足 / 流量高峰好几轮跑到一半页面直接甩[错误: 当前模型配额不足 [APIError]]或者检测到流量高峰正在为您重试。有一轮 R4 就是干完了活、卡在最后git push那步报配额错代码没提交上去。上下文爆表会话聊到 100%628K/512K码道开始只讲不做或者答非所问。只能点新对话重新绑仓库。好在 CodeArts 会自动压缩上下文压回 20% 左右又能接着干。推送认证最搞心态的是这个。老会话的沙箱码道在 push 失败后自作聪明地执行了git config credential.helper store并往凭据文件里写了个dummy-token把整个沙箱的 git 凭据污染了之后怎么推都认证失败。我判断出来之后直接开了个全新会话新沙箱、干净凭据把 R5/R6 重新喂一遍一次就推上去了。我后来学乖了提示词里专门加一条“push 只许试一次认证失败就把原始报错贴出来不许碰 credential.helper、不许写 token、不许 force push。” 免得它又好心办坏事。公开仓库长这样仓库是公开的八次提交从 R1 的编码核心一路到 R6 的专家评审修复提交信息链清清楚楚。README 里我把功能亮点、可验证护城河那张表、复现命令、架构分层都写了。语言构成 JavaScript 90%、HTML、CSS符合纯前端。提效数据给个粗略的账方便你判断这套分轮喂 本地复核 专家评审的打法值不值环节轮次测试数关键产出编码核心R141GF(256)RS掩码BCH纠正了 0x11D/0x11B 真值输入层出图R278text/url/wifi/vcard零依赖 PNG取证 15 例渲染页面R387SVG 渲染、白色系 SPA、本地服务器修 viewBox护城河R489修坐标系、decode/standard/determinism 三报告修 serveREADMER589路由回退、完整 README专家评审修复R689注释纠偏、第三方标准表、Q/Hv7 覆盖、UI 打磨全程我一行代码没手写除了几段验证脚本是我本地跑的但每一轮我都 clone 下来自己node --test复核不是码道说绿就绿。最后 jsQR 回环 18/18 是我本地独立跑出来的。本地怎么跑gitclone https://atomgit.com/gcw_QMYlF6Ie/qr-studio.gitcdqr-studionpminstall# 只装验证用的 devDepsjsqr、pngjsnpmtest# 89 项单元测试npmrun verify# 解码回环 18/18 标准对标 确定性npmrun serve# 打开 http://127.0.0.1:8099/ 看白色系页面写在最后这个项目最想证明的其实不是我会写二维码而是另一件事在 AI 什么都能给你生成的年代“能生成早就不值钱了值钱的是能自证”。我让码道从零写编码器然后逼它用第三方解码器 jsQR 把自己生成的东西扫回来对答案——18 个用例一字不差。它中途纠正了我记错的 GF(256) 真值也揪出了我写的自证式测试的漏洞最后被三个专家评审按在地上摩擦了一轮才收敛到能看的样子。说白了trust but verify。这套打法我打算以后每个项目都这么干给 AI 派一个有客观真值可对拍的活然后让它自己把证据摊在桌面上。好不好看是主观的但扫不扫得回来赖不掉。仓库在这欢迎 clone 下来自己npm run verify打我的脸https://atomgit.com/gcw_QMYlF6Ie/qr-studio
返回列表