JNA二次开发华视身份证阅读器

JNA二次开发华视身份证阅读器

前言

这两天了解了一下java调用dll动态库的方法,总的有三种:JNI、JNA、JNative,其中JNA调用DLL是最方便的。本次总结代码可以直接拿去用,不需要做太多的修改。

·JNI
JNI是 Java native interface的缩写,它提供了一定的API,实现了Java和其他语言之间的通信。从Java1.1之后JNI标准成为Java平台的一部分,它允许Java与其他语音进行通信,但是过程有点繁琐和复杂,不利于平时的敏捷开发。
·JNA
JNA是提供一些Java工具类用于在运行期间动态访问系统本地库(win dll),而不需要自己写JNative和JNI代码,只需要在一个接口实现目标native library,在运行之后 JNA会自己去映射相应的方法。
·JNative
JNative是一种能够使用Java语言调用DLL的技术,对JNI进行了封装。

Java使用 JNI来调用dll动态库的调用,工作量略大,一般情况下开发人员会选用JNA或JNative。
使用JNative调用DLL除了要引入jar包外还需要额外引入一个dll文件,而JNA只需要引入jar即可使用。

添加依赖

<dependency>
        <groupId>net.java.dev.jna</groupId>
        <artifactId>jna</artifactId>
        <version>3.1.0</version>
 </dependency>

SDK资料

二次开发sdk文件下载地址

编写代码

使用springBoot实现本次开发。
termb.dll API函数的动态联接库
sdtapi.dll 安全模块通讯函数
WltRs.dll 身份证相片解码库
加载dll,将dll文件放在resources文件夹下,网上有很多关于Dll放置位置的文章,本次采用的是放在项目中,方便读取DLL
在这里插入图片描述
关于获取DLL路径的方法,可以参考https://blog.csdn.net/Captain249/article/details/98172911

JNA实现library接口


```java

public interface TermbDLL extends Library {

    // termb.dll API函数的动态联接库
    // sdtapi.dll 安全模块通讯函数
    // WltRs.dll 身份证相片解码库
    TermbDLL termbDLL = (TermbDLL) Native.loadLibrary(TermbDLL.class.getResource("/Termb.dll").getPath().substring(1), TermbDLL.class);

    /**
     * 连接读卡器
     */
    int CVR_InitComm(int Port);

    /**
     * 卡认证
     */
    int CVR_Authenticate();

    /**
     * 读卡
     */
    int CVR_Read_Content(int active);

    /**
     * 读卡操作 含指纹
     */
    int CVR_Read_FPContent(int active);

    /**
     * 关闭连接
     */
    int CVR_CloseComm();

    /**
     * 找卡
     */
    int CVR_FindCard();

    /**
     * 选卡
     */
    int CVR_SelectCard();

    /**
     * 获取姓名
     */
    int GetPeopleName(byte[] name, IntByReference len);

    /**
     * 得到性别信息
     */
    int GetPeopleSex(byte[] sex, IntByReference len);

    /**
     * 得到民族信息
     */
    int GetPeopleNation(byte[] strTmp, IntByReference strLen);

    /**
     * 得到出生日期
     */
    int GetPeopleBirthday(byte[] strTmp, IntByReference strLen);

    /**
     * 得到地址信息
     */
    int GetPeopleAddress(byte[] strTmp, IntByReference strLen);

    /**
     * 得到身份证号信息
     *
     */
    int GetPeopleIDCode(byte[] strTmp, IntByReference strLen);

    /**
     * 得到发证机关信息
     */
    int GetDepartment(byte[] strTmp, IntByReference strLen);

    /**
     * 得到有效开始日期
     */
    int GetStartDate(byte[] strTmp, IntByReference strLen);

    /**
     * 得到有效截止日期
     */
    int GetEndDate(byte[] strTmp, IntByReference strLen);

    /**
     * 得到安全模块号
     */
    int CVR_GetSAMID(byte[] strTmp);

    /**
     * 得到指纹数据,不超过1024字节
     */
    int GetFPDate(byte[] strTmp, IntByReference strLen);

    /**
     * 得到头像照片bmp数据,不超过38862字节
     */
    int GetBMPData(byte[] strTmp, IntByReference strLen);

    /**
     * 得到头像照片base64编码数据,不超过38862*2字节
     */
    int Getbase64BMPData(byte[] strTmp, IntByReference strLen);

    /**
     * 得到头像照片jpg数据,不超过38862字节
     */
    int Getbase64JpgData(byte[] strTmp, IntByReference strLen);

}

``
具体调用的方法


@Service
public class CPReadIdCardInfoService {

    //成功状态码
    private static final int SUCCESS = 1;
    //失败状态码
    private static final int FAIL = 0;

    private static final String CHARACTER_ENCODING = "gb2312";

    public CPReadIdCardInfoModel readIdCardInfo() throws Exception {

        TermbDLL instance = buildInstance();

        try {

            connAndCheckDevice(instance);
            //读取身份证各信息
            IntByReference len = new IntByReference();
            //1、姓名
            String name = getPeopleName(instance, len);
            //2、性别
            String sex = getPeopleSex(instance,len);
          
           return readIdCardInfoModel;
        } finally {
            instance.CVR_CloseComm();
        }

    }

    private String getPeopleName(TermbDLL instance, IntByReference len) {
        try {
        	//长度更具华视文档设定
            byte [] nameStrTmp = new byte[30];
			//JNA调用DLL方法
            instance.GetPeopleName(nameStrTmp,len);

            return handleResult(nameStrTmp);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    private String getPeopleSex(TermbDLL instance, IntByReference len) {
        try {
            byte [] sexStrTmp = new byte[2];

            instance.GetPeopleSex(sexStrTmp,len);

            return handleResult(sexStrTmp);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    private String getPeopleInfo(TermbDLL instance, String methodName, IntByReference len) {
        try {

            byte [] strTmp = new byte[10];
            Method method = instance.getClass().getMethod(methodName, byte[].class, IntByReference.class);
            method.invoke(instance,strTmp,len);

            return handleResult(strTmp);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    private String handleResult(byte[] nameByte) throws UnsupportedEncodingException {
		//处理返回值防止乱码
        return new String(nameByte, CHARACTER_ENCODING).trim();
    }


    private void connAndCheckDevice(TermbDLL instance) throws Exception {

        //1、初始化连接
        cvrInitComm(instance);

        //2、读卡(同一个身份证,只要读取一次,下次读取优先从缓存中读取,并且CVR_Authenticate返回为2,卡认证失败,所以优先读卡一次)
        int readStatus = instance.CVR_Read_Content(1); //读取卡

        //2、卡认证,超出两分钟就是本次读卡失败
        int authenticate = getAuthenticateStatus(instance, readStatus);

        if (authenticate != SUCCESS)
            throw new Exception("读卡失败,请重新放置卡片");

        if (readStatus != SUCCESS)
            readStatus = instance.CVR_Read_Content(1); //读取卡

        if (readStatus != SUCCESS)
            throw new Exception("证件信息读取失败,请重新放置证件");

    }

    /**
     * 卡认证
     * @param instance     DLL
     * @param readStatus   同一个身份证,只要读取一次,下次读取优先从缓存中读取,并且CVR_Authenticate返回为2,卡认证失败,所以优先读卡一次
     */
    private int getAuthenticateStatus(TermbDLL instance, int readStatus) throws InterruptedException {

        if (readStatus == SUCCESS)
            return SUCCESS;

        final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(2L);
        int authenticate = FAIL;
        //循环读卡,失效时间为2分钟
        while (authenticate != SUCCESS){

            authenticate = instance.CVR_Authenticate();

            if (deadline ==SUCCESS)continue;

            if (deadline - System.nanoTime()<0L)break;
            Thread.sleep(100);//短暂休眠
        }

        return authenticate;
    }

    private void cvrInitComm(TermbDLL instance) throws Exception {

        int cvrInitComm = instance.CVR_InitComm(1001);

        if (cvrInitComm != SUCCESS){
            throw new Exception("读卡器连接失败,请检查设备状态");
        }
    }


    private TermbDLL buildInstance() throws Exception {

        URL dllResource = TermbDLL.class.getResource("/Termb.dll");
        if(dllResource == null) { throw new Exception("请检查设备状态"); }

        String dllPath = dllResource.getPath();

        if (StringUtils.isBlank(dllPath)) { throw new Exception("请检查设备状态"); }

        if (StringUtils.startsWithIgnoreCase(dllPath,"/")){
            dllPath = dllPath.substring(1);
        }

        try {
            return (TermbDLL) Native.loadLibrary(dllPath, TermbDLL.class);
        } catch (Exception e) {
            throw new Exception("请检查设备状态");
        }

    }


}

遇到的问题(坑)

问题一:华视的设备驱动,在windows电脑关闭之后,偶尔会出现需要重新安装;

  • 暂时还没有比较好的解决方案,华视客服也是没有给出具体的原因。

问题二:同时多次发起申请,会造成设备阻塞

  • 连接的时候优先使用缓存,看是否存在成功的连接
  • 同一个身份证多次读取,在设备没有关闭的情况下,会优先获取缓存,因为卡认证消耗资源,所以优先读卡,如果成功说明有缓存,不需要认证;
  • 卡认证需要一直循环认证,因为在请求接口的时候,设备上不一定放置了身份证,防止出现一只认证,设定一个超时时间;

问题三:DDL路径问题

  • 在idea 开发阶段,顺利读取到身份证信息,但是在打成jar之后,使用Java -jar的方式运行,无法获取到jar中的DDL,路径无法访问,出现各种!;
  • 将ddl读取到内存中,每次从内存中去读取DDL文件,但是需要每次都读取,浪费资源
  • 将DDL放在一个绝对路径中,直接读取,这个时候出现一个小麻烦,绝对路径之后,在连接设备的时候一直返回-2,并且出现DDL路径不存在的问题,原来termb.dll会去调用另外的ddl
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值