前言
近年来,随着智能设备的普及和物联网(IoT)技术的迅猛发展,用户对于跨平台、无缝连接体验的需求日益增长。在这样的背景下,华为推出的鸿蒙操作系统(HarmonyOS)迅速崛起,成为全球关注的焦点。
首先,我们要了解什么事ArkTS,ArkTS是HarmonyOS应用开发语言。它在保持TypeScript(简称TS)基本语法风格的基础上,对TS的动态类型特性施加更严格的约束,引入静态类型。同时,提供了声明式UI、状态管理等相应的能力,让开发者可以以更简洁、更自然的方式开发高性能应用。
身份证阅读功能接入
使用HarmonyOS操作系统的设备上需要接入身份证阅读功能,读写器采用诺塔斯NTS-13系列非接触式CPU卡读写器。
首先,需要在项目中引入/ets/libLotusCardDriver4ArkTs这个文件夹里面的相关文件。以index.d.ts文件为例!
/**
* 打开设备
*
* @param strDeviceName
* 串口设备名称
* @param nVID
* USB设备VID
* @param nPID
* USB设备PID
* @param nUsbDeviceIndex
* USB设备索引
* @param unRecvTimeOut
* 接收超时
* @param bUseExendReadWrite
* 是否使用外部读写通道 如果没有设备写权限时,可以使用外部USB或串口进行通讯,
* 需要改造OnExtendReadWriteCallBack中相关代码完成读写工作 目前范例提供USB操作
* @return 设备句柄
*/
export const OpenDevice: (strDeviceName: string, nVID: number, nPID:number, nUsbDeviceIndex:number,
unRecvTimeOut:number, bUseExendReadWrite:boolean) => number;
/**
* 关闭设备
*
* @param nDeviceHandle
* 设备句柄
*/
export const CloseDevice: (nDeviceHandle: number) => void;
/**
* 寻卡
*
* @param nDeviceHandle
* 设备句柄
* @param nRequestType
* 请求类型
* @param tLotusCardParam
* 结果值 用里面的卡片类型
* @return true = 成功
*/
export const Request: ( nDeviceHandle: number, nRequestType: number, tLotusCardParam:object) => boolean;
/**
* 防冲突
*
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 结果值 用里面的卡号
* @return true = 成功
*/
export const Anticoll: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 选卡
*
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 参数(使用里面的卡号)与结果值(使用里面的卡容量大小)
* @return true = 成功
*/
export const Select: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 密钥验证
*
* @param nDeviceHandle
* 设备句柄
* @param nAuthMode
* 验证模式
* @param nSectionIndex
* 扇区索引
* @param tLotusCardParam
* 参数(使用里面的卡号)
* @return true = 成功
*/
export const Authentication: ( nDeviceHandle: number, nAuthMode: number,nSectionIndex:number, tLotusCardParam:object) => boolean;
/**
* 密钥验证使用参数里面的密钥
*
* @param nDeviceHandle
* 设备句柄
* @param nAuthMode
* 验证模式
* @param nSectionIndex
* 扇区索引
* @param tLotusCardParam
* 参数(使用里面的卡号)
* @return true = 成功
*/
export const AuthenticationWithPassword: ( nDeviceHandle: number, nAuthMode: number,nSectionIndex:number, tLotusCardParam:object) => boolean;
/**
* 卡片中止响应
*
* @param nDeviceHandle
* 设备句柄
* @return true = 成功
*/
export const Halt: ( nDeviceHandle: number) => boolean;
/**
* 读指定地址数据
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @param tLotusCardParam 结果值(读写缓冲)
* @return true = 成功
*/
export const Read: ( nDeviceHandle: number, nAddress:number, tLotusCardParam:object) => boolean;
/**
* 写指定地址数据
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @param tLotusCardParam 参数(读写缓冲)
* @return true = 成功
*/
export const Write: ( nDeviceHandle: number, nAddress:number, tLotusCardParam:object) => boolean;
/**
* 加值
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @param nValue 值
* @return true = 成功
*/
export const Increment: ( nDeviceHandle: number, nAddress:number, nValue:number) => boolean;
/**
* 减值
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @param nValue 值
* @return true = 成功
*/
export const Decrement: ( nDeviceHandle: number, nAddress:number, nValue:number) => boolean;
/**
* 卡数据块传入卡的内部寄存器
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @return true = 成功
*/
export const Restore: ( nDeviceHandle: number, nAddress:number) => boolean;
/**
* 内部寄存器传入卡的卡数据块
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @return true = 成功
*/
export const Transfer: ( nDeviceHandle: number, nAddress:number) => boolean;
/**
* 装载密钥
* @param nDeviceHandle
* 设备句柄
* @param nAuthMode 验证模式
* @param nSectionIndex 扇区索引
* @param tLotusCardParam 参数(密钥)
* @return true = 成功
*/
export const LoadKey: ( nDeviceHandle: number, nAuthMode: number,nSectionIndex:number, tLotusCardParam:object) => boolean;
/**
* 蜂鸣
* @param nDeviceHandle
* 设备句柄
* @param nDeviceHandle 设备句柄
* @param nBeepLen 蜂鸣长度 毫秒为单位
* @return true = 成功
*/
export const Beep: ( nDeviceHandle: number, nBeepLen: number) => boolean;
/**
* 清空缓冲
* @param nDeviceHandle 设备句柄
* @param tLotusCardParam 结果值(读写缓冲)
* @return true = 成功
*/
export const EmptyBuffer: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 发送指令 用于CPU卡
* @param nDeviceHandle
* 设备句柄
* @param nTimeOut 超时参数
* @param tLotusCardParam 参数(指令缓冲,返回结果)
* @return true = 成功
*/
export const SendCpuCommand: ( nDeviceHandle: number, nTimeOut: number, tLotusCardParam:object) => boolean;
/**
* 读指定地址文本
* @param nDeviceHandle 句柄
* @param nSectionIndex 扇区索引
* @return 文本
*/
export const ReadText: ( nDeviceHandle: number, nSectionIndex: number) => string;
/**
* 写指定地址文本
* @param nDeviceHandle 句柄
* @param nSectionIndex 扇区索引
* @param strText 结果值(读写缓冲)
* @return true = 成功
*/
export const WriteText: ( nDeviceHandle: number, nSectionIndex: number, strText:string) => boolean;
/**
* 获取卡号 上位机简化
* @param nDeviceHandle
* 设备句柄
* @param nRequestType
* 请求类型
* @param tLotusCardParam
* 结果值
* @return true = 成功
*/
export const GetCardNo: ( nDeviceHandle: number, nRequestType: number, tLotusCardParam:object) => boolean;
/**
* 获取卡号 MCU简化
* @param nDeviceHandle
* 设备句柄
* @param nRequestType
* 请求类型
* @param nBeepLen 蜂鸣长度 最长255毫秒
* @param nUseHalt 使用中止 1=调用中止操作 0=不动作
* @param nUseLoop 使用循环
* 1=读卡器内部循环获取卡号 获取到数据再返回 上位机接收超时后 应立即再次读取
* 0=读卡器内部只动作一次
* @param tLotusCardParam
* 结果值
* @return true = 成功
*/
export const GetCardNoEx: ( nDeviceHandle: number, nRequestType: number, nBeepLen:number,nUseHalt:number,nUseLoop:number,
tLotusCardParam:object) => boolean;
/**
* 初始值
* @param nDeviceHandle
* 设备句柄
* @param nAddress 块地址
* @param nValue 值
* @return true = 成功
*/
export const InitValue: ( nDeviceHandle: number, nAddress:number, nValue:number) => boolean;
/**
* 修改密码AB
* @param nDeviceHandle
* 设备句柄
* @param nSectionIndex 扇区索引
* @param strPasswordA 密码A
* @param strPasswordB 密码B
* @return true = 成功
*/
export const ChangePassword: ( nDeviceHandle: number, nSectionIndex: number, strPasswordA:string,
strPasswordB:string) => boolean;
/**
* 复位CPU卡
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 结果值
* @return true = 成功
*/
export const ResetCpuCard: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 复位CPU卡 此方法里面没有调用GetCardNo
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 结果值
* @return true = 成功
*/
export const ResetCpuCardNoGetCardNo: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 取消激活CPU卡 后续可以发送WUPA
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 结果值
* @return true = 成功
*/
export const DeselectCpuCard: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 发送指令 用于CPU卡 封装LotusCardSendCpuCommand
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam 参数(指令缓冲,返回结果)
* @return true = 成功
*/
export const SendCOSCommand: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 获取银行卡卡号
* @param nDeviceHandle
* @return 文本
*/
export const GetBankCardNo: ( nDeviceHandle: number) => string;
/**
* 7816通道获取银行卡
* @param nDeviceHandle
* @return 文本
*/
export const GetBankCardNoBy7816: ( nDeviceHandle: number) => string;
/**
* 7816通道获取社保卡号
* @param nDeviceHandle
* @param tSocialSecurityCard 社保卡信息结构体
* @return true = 成功
*/
export const GetSocialSecurityInfoBy7816: ( nDeviceHandle: number, tSocialSecurityCard:object) => boolean;
/**
* 获取错误编码
* @param nDeviceHandle
* 设备句柄
* @return 错误编码 详见枚举值定义
*/
export const GetErrorCode: ( nDeviceHandle: number) => number;
/**
* 获取二代证操作错误编码
* @param nDeviceHandle
* 设备句柄
* @return 错误编码 详见枚举值定义
*/
export const GetTwoIdErrorCode: ( nDeviceHandle: number) => number;
/**
* 获取错误信息
* @param nDeviceHandle
* 设备句柄
* @param nErrorCode 错误编码
* @return 文本
*/
export const GetErrorInfo: ( nDeviceHandle: number, nErrorCode:number) => string;
/**
* 获取二代证错误信息
* @param nDeviceHandle
* 设备句柄
* @param errCode 错误编码
* @return 文本
*/
export const GetTwoIdErrorInfo: ( nDeviceHandle: number, nErrorCode:number) => string;
/**
* 设置卡片类型
* @param nDeviceHandle
* 设备句柄
* @param strCardType 卡片类型 A='A'/'a' B='B'/'b' F='F'/'f' C='C'/'c'
* @return true = 成功
*/
export const SetCardType: ( nDeviceHandle: number, strCardType:string) => boolean;
/**
* Felica寻卡
* @param nDeviceHandle
* 设备句柄
* @param unTimerSlot timer slot
* @param tLotusCardParam 参数(读写缓冲)
* @return true = 成功
*/
export const FelicaPolling: ( nDeviceHandle: number, unTimerSlot:number, tLotusCardParam:object) => boolean;
/**
* 发送SAMV命令
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam 参数(读写缓冲)
* @return true = 成功
*/
export const SendSamvCommand: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 发送COS指令结果给SAMV
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam 参数(读写缓冲)
* @return true = 成功
*/
export const SendCosResult2Samv: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 获取安全模块串口数据
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam 参数(读写缓冲)
* @return true = 成功
*/
export const GetSamvUartData: ( nDeviceHandle: number, tLotusCardParam:object) => boolean;
/**
* 获取二代证信息
* @param nDeviceHandle
* 设备句柄
* @param tTwoIdInfo 参数 二代证信息结构体地址
* @return true = 成功
*/
export const GetTwoIdInfo: ( nDeviceHandle: number, tTwoIdInfo:object) => boolean;
/**
* 通过转发服务器获取二代证信息
* @param nDeviceHandle
* 设备句柄
* @param strSamvBridgeUrl 参数 转发服务器IP或域名
* @param unSamvBridgePort 参数 转发服务器端口
* @param unUserAccount 参数 用户账号
* @param strPassWord 参数 密码 仅调度服务器有用
* @param tTwoIdInfo 参数 二代证信息结构体地址
* @param unRecvTimeOut 参数 接收超时 默认10秒
* @param bRequestFingerPrint 请求指纹
* @param bIsBlueDevice 参数 是否使用蓝牙优化API 需要读卡器固件支持
* @param bIsLotusDevice 参数 是否LOTUS设备
* @return true = 成功
*/
export const GetTwoIdInfoBySamvBridge: ( nDeviceHandle: number,strSamvBridgeUrl:string,unSamvBridgePort:number,
unUserAccount:number,strPassWord:string,tTwoIdInfo:object,unRecvTimeOut:number,
bRequestFingerPrint:boolean,bIsBlueDevice:boolean,bIsLotusDevice:boolean) => boolean;
/**
* 寻卡
*
* @param nDeviceHandle
* 设备句柄
* @param nRequestType
* 请求类型
* @param tLotusCardParam
* 结果值 用里面的卡片类型
* @return true = 成功
*/
export const RequestB: ( nDeviceHandle: number, nRequestType:number,tLotusCardParam:object) => boolean;
/**
* 选卡
*
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 参数(使用里面的卡号)与结果值(使用里面的卡容量大小)
* @return true = 成功
*/
export const SelectB: ( nDeviceHandle: number,tLotusCardParam:object) => boolean;
/**
* 卡片中止响应
*
* @param nDeviceHandle
* 设备句柄
* @return true = 成功
*/
export const HaltB: ( nDeviceHandle: number) => boolean;
/**
* 获取设备号
*
* @param nDeviceHandle
* 设备句柄
* @return 文本
*/
export const GetDeviceNo: ( nDeviceHandle: number) => string;
/**
* 获取设备MAC地址
*
* @param nDeviceHandle
* 设备句柄
* @return 文本
*/
export const GetDeviceMacAddress: ( nDeviceHandle: number) => string;
/**
* 获取芯片序列号
*
* @param parrMcuSerialNo
* 参数 序列号缓冲地址
* @return 文本
*/
export const GetMcuSerailNo: ( nDeviceHandle: number) => string;
/**
* 获取二代身份证卡片ID
*
* @param nDeviceHandle
* 设备句柄
* @return 文本
*/
export const GetTwoGenerationIDCardNo: ( nDeviceHandle: number) => string;
/**
* 获取二代身份证卡片DN码
*
* @param nDeviceHandle
* 设备句柄
* @param pstrDnNo
* 参数 DN码缓冲地址
* @param unDnNoLength
* 参数 DN码缓冲长度
* @return true = 成功
*/
export const GetTwoGenerationIDDnNo: ( nDeviceHandle: number) => string;
/**
* 设置射频开关
* @param nDeviceHandle
* 设备句柄
* @param nRfOnOff
* 1=打开射频信号 0= 关闭射频信号
* @return true = 成功
*/
export const SetRfOnOff: ( nDeviceHandle: number) => boolean;
/**
* 设置LED状态
*
* @param nDeviceHandle
* 设备句柄
* @param nLedStatus
* 参数 0=关闭 1=绿灯亮 2=红灯亮 3=两个灯都亮
* @return true = 成功
*/
export const SetLedStatus: ( nDeviceHandle: number) => boolean;
/**
* 复位射频IC
*
* @param nDeviceHandle
* 设备句柄
* @return true = 成功
*/
export const ResetRfIc: ( nDeviceHandle: number) => boolean;
/**
* 获取ntag版本 21x支持
*
* @param nDeviceHandle
* 设备句柄
* @param arrNtagVersionBuffer
* 参数 版本信息缓冲地址
* @param unNtagVersionBuffeLegnth
* 参数 版本信息缓冲长度
* @return true = 成功
*/
export const GetNtagGetVersion: ( nDeviceHandle: number,arrNtagVersionBuffer:Uint8Array,unNtagVersionBuffeLegnth:number) => boolean;
/**
* ntag密码验证 21x支持
*
* @param nDeviceHandle
* 设备句柄
* @param arrtagPasswordBuffer
* 参数 密码缓冲地址
* @param unNtagPasswordBuffeLegnth
* 参数 密码缓冲长度 默认4字节
* @return true = 成功
*/
export const NtagPwdAuth: ( nDeviceHandle: number,arrtagPasswordBuffer:Uint8Array, unNtagPasswordBuffeLegnth:number) => boolean;
/**
* 设置7816插槽索引
*
* @param nDeviceHandle
* 设备句柄
* @param unIndex
* 参数 插槽索引0=大卡 1,2,3,4=小卡 5=扩展大卡座
* @return true = 成功
*/
export const SetSamSlotIndex: ( nDeviceHandle: number,unIndex:number) => boolean;
/**
* 设置7816上下电 仅对大卡座有效
*
* @param nDeviceHandle
* 设备句柄
* @param unPowerOnOff
* 参数 0=下电操作 1=上电操作
* @return true = 成功
*/
export const SetSamPowerOnOff: ( nDeviceHandle: number,unPowerOnOff:number) => boolean;
/**
* 设置7816 电压选择VSEL0
*
* @param nDeviceHandle
* 设备句柄
* @param unIndex
* 参数 插槽索引0=大卡 1,2,3=小卡
* @param unSetLevel
* 参数 设置电平 1=高 0=低
* @return true = 成功
*/
export const SetSamVSel0: ( nDeviceHandle: number,unIndex:number, unSetLevel:number) => boolean;
/**
* 设置7816 电压选择VSEL1
*
* @param nDeviceHandle
* 设备句柄
* @param unIndex
* 参数 插槽索引0=大卡 1,2,3=小卡
* @param unSetLevel
* 参数 设置电平 1=高 0=低
* @return true = 成功
*/
export const SetSamVSel1: ( nDeviceHandle: number,unIndex:number, unSetLevel:number) => boolean;
/**
* 复位7816
*
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 结果值 返回复位ATR信息
*/
export const ResetSam: ( nDeviceHandle: number,tLotusCardParam:object) => boolean;
/**
* 发送SAM APDU
*
* @param nDeviceHandle
* 设备句柄
* @param tLotusCardParam
* 结果值 返回APDU执行结果
*/
export const SendSamAPDU: ( nDeviceHandle: number,tLotusCardParam:object) => boolean;
/**
* 获取7816插卡状态
* @param nDeviceHandle
* 设备句柄
* @return uint32 插卡状态 第一个字节是大卡座 第二个字节是外置卡座
*/
export const Get7816CardInsertStatus: ( nDeviceHandle: number) => number;
/**
* 获取库编译时间
*
* @param nDeviceHandle
* 设备句柄
*
* @return 库编译时间
*/
export const GetLibBuildTime: ( nDeviceHandle: number) => string;
在index.ets文件夹中引入相关依赖文件。
import hilog from '@ohos.hilog';
import promptAction from '@ohos.promptAction'
import LotusCardDriver from 'libLotusCardDriver4ArkTs.so'
import util from '@ohos.util';
import image from '@ohos.multimedia.image';
定义二代证相关字段信息。
interface ITwoIdInfoParam {
/**
* 姓名 UNICODE
*/
arrTwoIdName:Uint8Array;
/**
* 性别 UNICODE
*/
arrTwoIdSex:Uint8Array;
/**
* 民族 UNICODE
*/
arrTwoIdNation:Uint8Array;
/**
* 出生日期 UNICODE YYYYMMDD
*/
arrTwoIdBirthday:Uint8Array;
/**
* 住址 UNICODE
*/
arrTwoIdAddress:Uint8Array;
/**
* 身份证号码 UNICODE
*/
arrTwoIdNo:Uint8Array;
/**
* 签发机关 UNICODE
*/
arrTwoIdSignedDepartment:Uint8Array;
/**
* 有效期起始日期 UNICODE YYYYMMDD
*/
arrTwoIdValidityPeriodBegin:Uint8Array;
/**
* 有效期截止日期 UNICODE YYYYMMDD 有效期为长期时存储“长期”
*/
arrTwoIdValidityPeriodEnd:Uint8Array;
/**
* 最新住址 UNICODE
*/
arrTwoIdNewAddress:Uint8Array;
/**
* 照片信息
*/
arrTwoIdPhoto:Uint8Array;
/**
* 指纹信息
*/
arrTwoIdFingerprint:Uint8Array;
/**
* 照片信息 JPEG 格式
*/
arrTwoIdPhotoJpeg:Uint8Array;
/**
* 照片信息长度 JPEG格式
*/
unTwoIdPhotoJpegLength:number;
}
二代证阅读实现
TestTwoIdCard()
{
let TIEC_GET_SAMV_IP_PORT = 31; // 获取SAMV地址端口错误
let TIEC_CONNECT_SERVER = 24; // 连接服务器失败
let arrBmp: Uint8Array = new Uint8Array();
let nDecodeRet = 0;
//let strDispatchBridgeUrl = "samcq.highwillow.cn";
let strDispatchBridgeUrl = "219.153.15.85";
let nAccount = 99999;
let strPassword = "789789";
this.OpenLotusDevice();
if (-1 == this.m_nLotusHandle) {
promptAction.showToast({
message: '句柄不能为-1',
duration: 1600,
bottom: 150
})
this.AddLog('句柄不能为-1');
return;
}
//SamvErrorInfoParam tSamvErrorInfo = new SamvErrorInfoParam();
let bResult = false;
let bResultTmp = false;
let nResult = 0;
let bWlDecodeResult = false;
//byte[] arrBmpAndWl = null;
let temp = "";
let nErrorCode = 0;
//LotusCardParam tLotusCardParam1 = new LotusCardParam();
bResult = LotusCardDriver.SetCardType(this.m_nLotusHandle, 'B');
if (!bResult) {
this.AddLog("Call SetCardType Error!");
return;
}
this.AddLog("Call SetCardType Ok!");
bResult = LotusCardDriver.RequestB(this.m_nLotusHandle, 0, this.m_sttLotusCardParam);
if (false == bResult) {
this.AddLog("Call RequestB Error!");
return;
}
bResult = LotusCardDriver.SelectB(this.m_nLotusHandle, this.m_sttLotusCardParam);
if (false == bResult) {
this.AddLog("Call SelectB Error!");
return;
}
this.AddLog("二代证UID:" + LotusCardDriver.GetTwoGenerationIDCardNo(this.m_nLotusHandle));
bResult = LotusCardDriver.GetTwoIdInfoBySamvBridge(
this.m_nLotusHandle, strDispatchBridgeUrl, 31213, nAccount, strPassword,
this.m_sttTwoIdInfoParam, 0, false, false, true);
if (!bResult) {
nErrorCode = LotusCardDriver.GetTwoIdErrorCode(this.m_nLotusHandle);
this.AddLog("Call GetTwoIdInfoBySamvBridge Error! ErrorCode:" + nErrorCode);
// this.AddLog("ErrorInfo:" + LotusCardDriver.GetIdErrorInfo(nErrorCode));
// if(TIEC_GET_SAMV_IP_PORT == nErrorCode)
// {
// AddLog("DispatchServerErrorInfo:" + driver.GetTwoIdDispatchServerErrorInfo(nHandle));
// }
return;
}
this.AddLog("Call GetTwoIdInfoBySamvBridge Ok!");
if (true == bResult) {
this.ShowTwoIdInfo(this.m_sttTwoIdInfoParam);
} else {
this.AddLog("GetTwoIdInfoBySamvBridge执行失败");
}
}
总结
以上内容是如何在HarmonyOS系统中接入身份证阅读器,不仅如此,还支持M1卡,NFC标签,非接触式CPU卡等功能。