如何识别Ionic应用中扫描的QR码来自图片还是打印件?
阻止Ionic中扫描屏幕QR码的最优方案
哥们,这个需求我之前在几个Ionic项目里都碰到过——就是要拦着用户用手机拍屏幕上的QR码,必须扫实体打印的那种对吧?我给你梳理几个从易到难、实用性拉满的方案,你可以根据自己的业务安全级别来选:
核心思路:区分实体与屏幕QR的关键
首先得搞清楚两者的差异:
- 实体QR有打印纹理、噪点,手机移动时透视变形连续自然;
- 屏幕QR是像素化的网格,亮度均匀,拍的时候画面要么静止不动,要么移动时边缘有明显的像素块卡顿。
抓住这些差异,我们就能针对性做检测。
方案1:动态帧分析(性价比最高,易实现)
这个方案不需要额外硬件权限,纯靠摄像头帧数据判断,适合大多数普通场景。
实现逻辑
- 不要一扫描到QR就直接处理,先收集连续3-5帧画面;
- 计算帧与帧之间的像素差异或特征点变化:如果连续几帧几乎没变化,说明是对着静态屏幕;
- 额外可以加亮度方差检测:屏幕的亮度方差远低于纸质QR,方差太小就判定为屏幕照片。
代码示例(基于@zxing/library)
import { BrowserMultiFormatReader } from '@zxing/library'; const codeReader = new BrowserMultiFormatReader(); let previousFrame: ImageData | null = null; let stableFrameCount = 0; // 帧差异阈值,可根据实际测试调整 const DIFF_THRESHOLD = 1200; // 启动扫描 codeReader.decodeFromVideoDevice(null, 'video', (result, err) => { if (result) { const currentFrame = getVideoFrame('video'); if (previousFrame) { const pixelDiff = calculateFrameDiff(previousFrame, currentFrame); // 连续3帧差异过小,判定为屏幕照片 if (pixelDiff < DIFF_THRESHOLD) { stableFrameCount++; if (stableFrameCount >= 3) { alert('请扫描实体打印的QR码,禁止拍摄屏幕'); stableFrameCount = 0; return; } } else { stableFrameCount = 0; } } previousFrame = currentFrame; // 这里处理合法的实体QR扫描结果 console.log('扫描成功:', result.text); } }); // 辅助函数:获取视频当前帧的ImageData function getVideoFrame(videoId: string): ImageData { const video = document.getElementById(videoId) as HTMLVideoElement; const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); ctx?.drawImage(video, 0, 0); return ctx?.getImageData(0, 0, canvas.width, canvas.height) as ImageData; } // 辅助函数:计算两帧像素差异总和 function calculateFrameDiff(img1: ImageData, img2: ImageData): number { let diff = 0; const data1 = img1.data; const data2 = img2.data; // 按RGB通道计算差异 for (let i = 0; i < data1.length; i += 4) { diff += Math.abs(data1[i] - data2[i]) + Math.abs(data1[i+1] - data2[i+1]) + Math.abs(data1[i+2] - data2[i+2]); } return diff; }
方案2:物理交互验证(高安全场景必备)
这个方案结合手机的加速度传感器,要求用户扫描时轻微晃动手机,实体QR能自然响应这个动作,而屏幕照片很难模拟真实的物理晃动。
实现逻辑
- 在扫描界面提示用户“请轻微晃动手机完成扫描”;
- 用Ionic的加速度插件监听手机的晃动数据;
- 扫描到QR时,检查最近1秒内是否有有效的加速度变化,没有就拒绝。
代码示例(基于@ionic-native/accelerometer)
import { Accelerometer, AccelerationData } from '@ionic-native/accelerometer/ngx'; private hasValidMotion = false; constructor(private accelerometer: Accelerometer) { // 监听加速度,频率100ms一次 this.accelerometer.watchAcceleration({ frequency: 100 }) .subscribe((acc: AccelerationData) => { // 检测是否有足够的晃动(X/Y轴加速度超过0.5g) if (Math.abs(acc.x) > 0.5 || Math.abs(acc.y) > 0.5) { this.hasValidMotion = true; // 1秒后重置状态,防止用户先晃再对着屏幕 setTimeout(() => { this.hasValidMotion = false; }, 1000); } }); } // 扫描成功后的回调 handleScanSuccess(result: any) { if (!this.hasValidMotion) { alert('请晃动手机扫描实体QR码'); return; } // 处理合法扫描结果 }
方案3:动态内容校验(终极解决方案,需后端配合)
如果你的业务安全级别极高,比如涉及支付、权限验证,那最好让后端生成带动态标识的QR码——比如每30秒更新一次的时间戳或一次性token。
实现逻辑
- 后端生成纸质QR码时,嵌入一个短期有效的动态参数;
- 用户扫描后,前端把参数传给后端验证;
- 如果参数过期或已被使用,直接拒绝,彻底杜绝屏幕照片的问题。
这个方案的优势是完全无解(除非用户实时拍摄刚生成的屏幕QR),但需要前后端配合开发。
注意事项
- 没有100%完美的方案(比如用户用高清投影仪投射QR),但组合方案1+方案2就能覆盖99%的场景;
- 测试时要在不同光线、不同屏幕亮度、不同纸质材质下调整阈值,避免误判;
- 如果用Cordova的
cordova-plugin-qrscanner,逻辑是一样的,只要在扫描回调里加入检测逻辑即可。
内容的提问来源于stack exchange,提问作者user2828442
相关产品推荐
相关产品推荐

