You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何识别Ionic应用中扫描的QR码来自图片还是打印件?

阻止Ionic中扫描屏幕QR码的最优方案

哥们,这个需求我之前在几个Ionic项目里都碰到过——就是要拦着用户用手机拍屏幕上的QR码,必须扫实体打印的那种对吧?我给你梳理几个从易到难、实用性拉满的方案,你可以根据自己的业务安全级别来选:

核心思路:区分实体与屏幕QR的关键

首先得搞清楚两者的差异:

  • 实体QR有打印纹理、噪点,手机移动时透视变形连续自然;
  • 屏幕QR是像素化的网格,亮度均匀,拍的时候画面要么静止不动,要么移动时边缘有明显的像素块卡顿。

抓住这些差异,我们就能针对性做检测。


方案1:动态帧分析(性价比最高,易实现)

这个方案不需要额外硬件权限,纯靠摄像头帧数据判断,适合大多数普通场景。

实现逻辑

  1. 不要一扫描到QR就直接处理,先收集连续3-5帧画面;
  2. 计算帧与帧之间的像素差异或特征点变化:如果连续几帧几乎没变化,说明是对着静态屏幕;
  3. 额外可以加亮度方差检测:屏幕的亮度方差远低于纸质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能自然响应这个动作,而屏幕照片很难模拟真实的物理晃动。

实现逻辑

  1. 在扫描界面提示用户“请轻微晃动手机完成扫描”;
  2. 用Ionic的加速度插件监听手机的晃动数据;
  3. 扫描到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。

实现逻辑

  1. 后端生成纸质QR码时,嵌入一个短期有效的动态参数;
  2. 用户扫描后,前端把参数传给后端验证;
  3. 如果参数过期或已被使用,直接拒绝,彻底杜绝屏幕照片的问题。

这个方案的优势是完全无解(除非用户实时拍摄刚生成的屏幕QR),但需要前后端配合开发。


注意事项

  • 没有100%完美的方案(比如用户用高清投影仪投射QR),但组合方案1+方案2就能覆盖99%的场景;
  • 测试时要在不同光线、不同屏幕亮度、不同纸质材质下调整阈值,避免误判;
  • 如果用Cordova的cordova-plugin-qrscanner,逻辑是一样的,只要在扫描回调里加入检测逻辑即可。

内容的提问来源于stack exchange,提问作者user2828442

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:46:09