是不是努力Coding 就可以买大House
Qzone 发布的动态
Qzone
### 从乱码到中文:前端二维码解码的一次实操记录
#### 背景
客户那边有一套用了很多年的 ERP 系统,导出的二维码用手机扫一扫,呈现出来的是一串看不懂的乱码。用 Java 或者 Python 去读
...全文### 从乱码到中文:前端二维码解码的一次实操记录
#### 背景
客户那边有一套用了很多年的 ERP 系统,导出的二维码用手机扫一扫,呈现出来的是一串看不懂的乱码。用 Java 或者 Python 去读图片底层的字节数据,再做一次编码转换,是可以还原成正常中文的。但我们的场景是纯前端——用户在浏览器里上传图片或者打开摄像头,就要直接得到正确的结果,不能依赖后端服务。
前端这边折腾了挺久,最后用 `zxing-wasm` 配合一套自定义的解码逻辑,总算把这个问题解决了。
#### 解码引擎的选择
`zxing-wasm` 是 ZXing C++ 库的 WebAssembly 版本,编译后可以直接在浏览器里运行。调用时把图像数据传进去,通过配置参数来启用各种内置的预处理能力:
```ts const results = await readBarcodes(imageData, { formats: ['QRCode'], tryRotate: true, // 自动旋转 0/90/180/270 度 tryInvert: true, // 尝试反色(黑底白字) tryHarder: true, // 启用更精细的搜索 tryDownscale: true, // 大图自动缩小以加速 tryDenoise: true, // 形态学降噪 maxNumberOfSymbols: 4, }); ```
灰度化、二值化、旋转修正、反色判断、降噪这些步骤,全部在 C++ 层内部完成,前端不需要额外处理,代码更简洁,识别率也有保障。
#### 乱码问题的根源
引擎虽然能识别出二维码的内容,但遇到不带 ECI 字符集声明的中文二维码时,会默认按 UTF-8 解码。GBK 编码的中文在这种情况下就会显示成 `汉å—` 这样的乱码——这是字符集探测本身的局限,不是引擎的 Bug。
`readBarcodes` 返回的结果里,除了 `text`(引擎自动解码后的文本),还有一个 `bytes` 字段,存放的是原始字节数组。问题就转化成了:拿到原始字节后,如何判断它到底应该用什么编码来解读。
#### 暴力探测 + 评分择优
思路很简单:把常见的几种中文编码都试一遍,然后挑一个最像“正常文本”的结果。
```ts function decodeBytesSmart(bytes: Uint8Array) { const encodings = ['gbk', 'gb18030', 'gb2312', 'utf-8', 'big5']; const attempts = encodings.map(enc => { try { const text = new TextDecoder(enc).decode(bytes); return { encoding: enc, text, score: scoreText(text) }; } catch { return null; } }).filter(Boolean); attempts.sort((a, b) => b.score - a.score); return attempts[0]; } ```
关键是这个评分函数——怎么判断一段文本是“正常的”还是“乱码”?
```ts function scoreText(t: string): number { if (!t) return -1e9; let s = 0; // 中文字符越多,得分越高 const cn = t.match(/[\u4e00-\u9fff]/g); if (cn) s += cn.length * 5; // 以 { 或 [ 开头,可能是 JSON,加分 if (t.trim().startsWith('{') || t.trim().startsWith('[')) s += 20; // 包含典型的 UTF-8 误读乱码字符,减分 if (/Ã.|Â./.test(t)) s -= 30; // 高字节西欧符号,减分 const garbage = (t.match(/[€‚ƒ…†‡ˆ‰Š‹ŒŽ'""•–—˜™š›œžŸ]/g) || []).length; s -= garbage * 10; return s; } ```
中文字符多、有 JSON 结构,说明这段文本很大概率是正常的;出现 `Ã`、`Â` 这类 Latin-1 误读字符,或者大量高字节符号,基本就是乱码。用这个规则给每个编码尝试结果打分,分数最高的那个就是最可能的正确文本。
最后一步,把引擎自带的解码结果和暴力探测的最高分结果放在一起比较,谁分高就用谁:
```ts const smart = decodeBytesSmart(target.bytes); let text = target.text; if (!text || scoreText(smart.text) > scoreText(text)) { text = smart.text; } ```
#### 结果
这套方案上线之后,无论是标准的 UTF-8 二维码,还是老 ERP 导出的 GBK 编码二维码,都能在浏览器里直接还原成正确的中文内容。整个过程不依赖后端,用户上传图片或打开摄像头扫码,得到的就是可读的文本。
算是给这个“老旧 ERP + 现代前端”的组合,补齐了最后一块拼图。
Qzone
网站恢复上线了。
微信又收到了打赏通知。
22年开始做的这个工具网站,最开始就寝室里用。
再后来开了源,上了在线版,修bug、优化性能,小白一个,跌跌撞撞弄了几年。
用户过了万,挺意外的。
今年五月,接口被攻击
...全文网站恢复上线了。
微信又收到了打赏通知。
22年开始做的这个工具网站,最开始就寝室里用。
再后来开了源,上了在线版,修bug、优化性能,小白一个,跌跌撞撞弄了几年。
用户过了万,挺意外的。
今年五月,接口被攻击,上游把服务拉黑了。
只能暂时停掉,仓库也藏起来了,怕被人顺着逻辑继续搞。
原来攒的star也全没了。
那段时间在群里教大家怎么本地部署,能用的接着用。
最近工作没那么忙,把代码重构了,重新开源。
之前用的朋友也陆续回来了。
重新上线之后,开始有人打赏了。
感谢每一位。
Qzone
周末休息,跟女朋友去中医医院看脸上的痘痘。
反反复复长了好一阵,心情也不太好。
深圳还在下雨,打不到车,在路边拦了辆摩托车,小雨,让师傅慢点开。
预约时间快到了,但安全要紧。
医院有好几栋楼,约的是一位甘肃名
...全文周末休息,跟女朋友去中医医院看脸上的痘痘。
反反复复长了好一阵,心情也不太好。
深圳还在下雨,打不到车,在路边拦了辆摩托车,小雨,让师傅慢点开。
预约时间快到了,但安全要紧。
医院有好几栋楼,约的是一位甘肃名医,独立的大办公室,挺气派。
把了脉,问了诊,开了药方,还有祛痘面膜和痛经的药。
下楼去药房办了快递,中药要熬,直接寄回去方便。
又上七楼,医生做了火针,一颗颗挑破,又用紫光灯照了十来分钟。
灯光太刺眼,没敢一直看。
灯停了,问医生可以走了。女朋友脸上大大小小的红点,看着有点心疼。
希望这次能好,别再受这番困扰了。
出来还在下雨,还是打不到车。
沿着坡往下走了一段,到了大公路才叫到车。
等车的时候,雨伞遮不住雨点,我们俩蹲在门庭下躲雨。
司机到了,车里空调开得足,一上车打了个寒颤。
没一会儿开始晕车,红灯多,走走停停,胃里翻得难受。
女朋友也累,靠着眯了一会儿。
总算到了,下车吸了口新鲜空气,缓过来了。