如何构建适用于 Android 和 iOS 的 React Native MRZ 护照扫描仪

手动验证旅行证件容易出错且效率低下。护照、身份证和其他符合国际民航组织 (ICAO) 标准的旅行证件上印有的机读区 (MRZ) 以结构化的机器可读格式编码所有关键身份信息——但在移动设备上提取这些信息需要精准的 OCR 引擎和实时摄像头数据处理流程。Dynamsoft MRZ Scanner SDK将这两项功能封装在一个 React Native 调用中,可在 Android 和 iOS 系统上运行。

你将构建:一个 React Native 应用(Android API 21+ / iOS 13+),该应用打开一个实时摄像头扫描仪,读取任何 TD1/TD2/TD3 旅行证件的 MRZ,并使用 v3.2.5002 显示解析后的字段(姓名、证件号码、国籍、日期)dynamsoft-mrz-scanner-bundle-react-native

演示视频:React Native MRZ 扫描器实战

要点总结

  • 本教程展示了如何通过一次 SDK 调用,无需原生模块样板代码,将实时 MRZ 扫描集成到 React Native 应用程序中。
  • dynamsoft-mrz-scanner-bundle-react-native公开了一个MRZScanner.launch()API,可以一次性处理相机权限、取景器 UI 和 OCR。
  • SDK 返回一个完全解析的结果对象( ) ,result.data其中包含命名字段—— 、、、、firstName等等——从而无需手动解析 MRZ 行。lastNamedocumentNumbernationality
  • 这种模式可直接应用于机场值机亭、酒店登记系统以及任何需要快速、无需人工干预的文件验证工作流程。

开发者常见问题

  • 如何在不使用原生 Android/iOS 代码的情况下,使用 React Native 扫描护照 MRZ?
  • 为什么MRZScanner.launch()在授予权限后,Android 系统仍然会出现相机权限错误?
  • Dynamsoft MRZ Scanner 可以离线使用吗?还是每次扫描都需要网络连接?

先决条件

开始之前,请确保您已准备好以下物品:

  • Node.js ≥ 18
  • React Native 0.79(采用新架构)
  • 最新版Android Studio,连接运行 Android 5.0 (API 21) 或更高版本的设备或模拟器。
  • 适用于 iOS 13.0 及更高版本的Xcode (最新版,仅限 macOS)
  • JDK 17
  • Dynamsoft MRZ Scanner 许可证密钥——该项目附带一个有时限的试用密钥,需要网络连接。

访问dynamsoft.com/customer/license/trialLicense获取 30 天免费试用许可证

步骤 1:安装和配置 SDK

使用 npm 安装项目依赖项。Dynamsoft 的两个软件包——MRZ 扫描仪软件包和底层 Capture Vision 引擎——已在文件中声明,package.json并将自动安装。

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code>npm <span style="color:#336666">install</span>
</code></span></span></span>

相关 SDK 依赖项位于package.json

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#9999ff">"dependencies"</span>: {
  <span style="color:#9999ff">"dynamsoft-capture-vision-react-native"</span>: <span style="color:#cc3300">"3.2.5002"</span>,
  <span style="color:#9999ff">"dynamsoft-mrz-scanner-bundle-react-native"</span>: <span style="color:#cc3300">"3.2.5002"</span>,
  <span style="color:#9999ff">"react"</span>: <span style="color:#cc3300">"19.0.0"</span>,
  <span style="color:#9999ff">"react-native"</span>: <span style="color:#cc3300">"0.79.0"</span>
}
</code></span></span></span>

对于 iOS,请在安装 CocoaPods 依赖项之后进行安装npm install

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#336666">cd </span>ios <span style="color:#555555">&&</span> pod <span style="color:#336666">install</span> <span style="color:#555555">&&</span> <span style="color:#336666">cd</span> ..
</code></span></span></span>

步骤 2:配置许可证并启动 MRZ 扫描器

从捆绑包中导入 `<module>` MRZScanner、 `<module> MRZScanConfig` 和 ` <module>`,然后使用您的许可证密钥调用 `<module>`。SDK 将接管摄像头,显示其内置的扫描用户界面,并且仅在用户完成扫描或取消扫描后返回。EnumResultStatusMRZScanner.launch(config)

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>import</strong></span> {
  EnumResultStatus,
  MRZScanConfig,
  MRZScanner,
} <span style="color:#006699"><strong>from</strong></span> '<span style="color:#cc3300">dynamsoft-mrz-scanner-bundle-react-native</span>';

<span style="color:#006699"><strong>const</strong></span> handleScan <span style="color:#555555">=</span> <span style="color:#006699"><strong>async </strong></span>() <span style="color:#555555">=></span> {
  <span style="color:#cc00ff">setScanState</span>({<span style="color:#330099">kind</span>: '<span style="color:#cc3300">scanning</span>'});
  <span style="color:#006699"><strong>try</strong></span> {
    <span style="color:#006699"><strong>const</strong></span> <span style="color:#330099">config</span>: MRZScanConfig <span style="color:#555555">=</span> {
      <span style="color:#0099ff"><em>// Trial license — network connection required.</em></span>
      <span style="color:#0099ff"><em>// Request an extension at:</em></span>
      <span style="color:#0099ff"><em>// https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform</em></span>
      <span style="color:#330099">license</span>:
        '<span style="color:#cc3300">LICENSE-KEY</span>',
    };
    <span style="color:#006699"><strong>const</strong></span> result <span style="color:#555555">=</span> <span style="color:#006699"><strong>await</strong></span> MRZScanner.<span style="color:#cc00ff">launch</span>(config);
    <span style="color:#0099ff"><em>// ... handle result</em></span>
  } <span style="color:#006699"><strong>catch </strong></span>(<span style="color:#330099">e</span>: unknown) {
    <span style="color:#006699"><strong>const</strong></span> message <span style="color:#555555">=</span> e <span style="color:#006699"><strong>instanceof</strong></span> <span style="color:#336666">Error</span> ? e.message : <span style="color:#00aa88"><strong>String</strong></span>(e);
    <span style="color:#cc00ff">setScanState</span>({<span style="color:#330099">kind</span>: '<span style="color:#cc3300">error</span>', <span style="color:#330099">code</span>: <span style="color:#555555">-</span><span style="color:#ff6600">1</span>, message});
  }
};
</code></span></span></span>

步骤 3:处理扫描结果状态

MRZScanner.launch()解析结果对象,该对象resultStatus映射到以下三个值之一EnumResultStatus。访问之前请检查状态result.data,以区分扫描成功、用户取消或 SDK 级错误。

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>if </strong></span>(result.resultStatus <span style="color:#555555">===</span> EnumResultStatus.RS_FINISHED) {
  <span style="color:#cc00ff">setScanState</span>({<span style="color:#330099">kind</span>: '<span style="color:#cc3300">result</span>', <span style="color:#330099">data</span>: result.data <span style="color:#006699"><strong>as </strong></span><span style="color:#336666">Record</span><span style="color:#555555"><</span><span style="color:#006699"><strong>string</strong></span>, <span style="color:#006699"><strong>string</strong></span><span style="color:#555555">></span>});
} <span style="color:#006699"><strong>else</strong></span> <span style="color:#006699"><strong>if </strong></span>(result.resultStatus <span style="color:#555555">===</span> EnumResultStatus.RS_CANCELED) {
  <span style="color:#cc00ff">setScanState</span>({<span style="color:#330099">kind</span>: '<span style="color:#cc3300">cancelled</span>'});
} <span style="color:#006699"><strong>else</strong></span> {
  <span style="color:#cc00ff">setScanState</span>({
    <span style="color:#330099">kind</span>: '<span style="color:#cc3300">error</span>',
    <span style="color:#330099">code</span>: result.errorCode <span style="color:#555555">??</span> <span style="color:#555555">-</span><span style="color:#ff6600">1</span>,
    <span style="color:#330099">message</span>: result.errorString <span style="color:#555555">??</span> '<span style="color:#cc3300">An unknown error occurred.</span>',
  });
}
</code></span></span></span>

步骤 4:在用户界面中显示已解析的 MRZ 字段

React Native MRZ 扫描器,由 Dynamsoft 提供技术支持

当状态为“是”时RS_FINISHEDresult.data它是一个包含已解析 MRZ 字段的扁平键值映射。示例应用程序会将高价值字段(例如 `<field1>` firstName、 ` <field2>` lastName、` <field3>`、`<field4>`)提升到“醒目”区域,并将其余字段列在可滚动卡片中。documentNumberdocumentType

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>const</strong></span> HERO_FIELDS <span style="color:#555555">=</span> ['<span style="color:#cc3300">firstName</span>', '<span style="color:#cc3300">lastName</span>', '<span style="color:#cc3300">documentNumber</span>', '<span style="color:#cc3300">documentType</span>'];

<span style="color:#006699"><strong>function</strong></span> <span style="color:#cc00ff">ResultCard</span>({data}: ResultCardProps) {
  <span style="color:#006699"><strong>const</strong></span> heroEntries <span style="color:#555555">=</span> HERO_FIELDS.<span style="color:#cc00ff">map</span>(k <span style="color:#555555">=></span> [k, data[k]] <span style="color:#006699"><strong>as </strong></span>[<span style="color:#006699"><strong>string</strong></span>, <span style="color:#006699"><strong>string</strong></span>]).<span style="color:#cc00ff">filter</span>(
    ([, v]) <span style="color:#555555">=></span> v <span style="color:#555555">!=</span> <span style="color:#006699"><strong>null</strong></span> <span style="color:#555555">&&</span> v <span style="color:#555555">!==</span> '',
  );
  <span style="color:#006699"><strong>const</strong></span> otherEntries <span style="color:#555555">=</span> <span style="color:#336666">Object</span>.<span style="color:#cc00ff">entries</span>(data).<span style="color:#cc00ff">filter</span>(
    ([k, v]) <span style="color:#555555">=></span> <span style="color:#555555">!</span>HERO_FIELDS.<span style="color:#cc00ff">includes</span>(k) <span style="color:#555555">&&</span> v <span style="color:#555555">!=</span> <span style="color:#006699"><strong>null</strong></span> <span style="color:#555555">&&</span> v <span style="color:#555555">!==</span> '',
  );

  <span style="color:#006699"><strong>return </strong></span>(
    <<span style="color:#00aa88"><strong>View</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.resultCard<span style="color:#aa0000">}</span>>
      <span style="color:#aa0000">{</span>heroEntries.length <span style="color:#555555">></span> <span style="color:#ff6600">0</span> <span style="color:#555555">&&</span> (
        <<span style="color:#00aa88"><strong>View</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.heroSection<span style="color:#aa0000">}</span>>
          <span style="color:#aa0000">{</span>heroEntries.<span style="color:#cc00ff">map</span>(([key, value]) <span style="color:#555555">=></span> (
            <<span style="color:#00aa88"><strong>View</strong></span> <span style="color:#330099">key</span>=<span style="color:#aa0000">{</span>key<span style="color:#aa0000">}</span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.heroRow<span style="color:#aa0000">}</span>>
              <<span style="color:#00aa88"><strong>Text</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.heroLabel<span style="color:#aa0000">}</span>><span style="color:#aa0000">{</span><span style="color:#cc00ff">formatLabel</span>(key)<span style="color:#aa0000">}</span></<span style="color:#00aa88"><strong>Text</strong></span>>
              <<span style="color:#00aa88"><strong>Text</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.heroValue<span style="color:#aa0000">}</span>><span style="color:#aa0000">{</span>value<span style="color:#aa0000">}</span></<span style="color:#00aa88"><strong>Text</strong></span>>
            </<span style="color:#00aa88"><strong>View</strong></span>>
          ))<span style="color:#aa0000">}</span>
        </<span style="color:#00aa88"><strong>View</strong></span>>
      )<span style="color:#aa0000">}</span>
      <span style="color:#aa0000">{</span><span style="color:#0099ff"><em>/* other fields omitted for brevity */</em></span><span style="color:#aa0000">}</span>
    </<span style="color:#00aa88"><strong>View</strong></span>>
  );
}
</code></span></span></span>

步骤 5:操作 Android 硬件返回按钮

在 Android 系统中,硬件返回键默认会退出 React Native 应用。示例应用会拦截此操作,使BackHandler用户返回到空闲状态,而不是在工作流程中途关闭应用。

<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#cc00ff">useEffect</span>(() <span style="color:#555555">=></span> {
  <span style="color:#006699"><strong>const</strong></span> subscription <span style="color:#555555">=</span> BackHandler.<span style="color:#cc00ff">addEventListener</span>('<span style="color:#cc3300">hardwareBackPress</span>', () <span style="color:#555555">=></span> {
    <span style="color:#006699"><strong>if </strong></span>(scanState.kind <span style="color:#555555">!==</span> '<span style="color:#cc3300">idle</span>') {
      <span style="color:#cc00ff">setScanState</span>({<span style="color:#330099">kind</span>: '<span style="color:#cc3300">idle</span>'});
      <span style="color:#006699"><strong>return</strong></span> <span style="color:#006699"><strong>true</strong></span>; <span style="color:#0099ff"><em>// event handled — prevent default (app exit)</em></span>
    }
    <span style="color:#006699"><strong>return</strong></span> <span style="color:#006699"><strong>false</strong></span>; <span style="color:#0099ff"><em>// let the system handle it (idle → exit as normal)</em></span>
  });
  <span style="color:#006699"><strong>return </strong></span>() <span style="color:#555555">=></span> subscription.<span style="color:#cc00ff">remove</span>();
}, [scanState.kind]);
</code></span></span></span>

常见问题和特殊情况

  • 试用许可证需要网络连接:每次启动时,捆绑的试用密钥都会与 Dynamsoft 的许可证服务器进行验证。如果设备离线,扫描器将返回错误状态。请将密钥替换为完整许可证以离线使用。
  • 在 Android 6 及更高版本上,相机权限被拒绝: React Native 不会CAMERA在启动时自动请求权限。如果您的应用目标 API 为 23 及更高版本,请确保AndroidManifest.xml在调用MRZScanner.launch()`react-app.getCamera(' ...
  • TD1卡上的空白结果字段: TD1(信用卡大小的身份证)文件的MRZ区域比TD3护照的MRZ区域窄。请确保光线充足,并将文件平放并完全置于取景器内;部分拍摄会导致字段值为空或格式错误。

结论

本教程演示了如何使用 Dynamsoft MRZ Scanner SDK v3.2 在 React Native 中构建跨平台 MRZ 扫描器。只需一次MRZScanner.launch()调用和简单的结果状态处理,即可在 Android 和 iOS 上实现实时 MRZ 读取,无需编写任何原生代码。后续步骤

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐