有这样一个需求,要求能自动读取用户的身份证信息。如果是一代身份证,这个功能恐怕只能通过图像识别的办法来解决了。不过现在二代身份证已经很普及。客户要求能读二代身份证就可以了。
现在二代身份证阅读器很常见,本文中使用的是新中新的身份证阅读器。下图是其外观,如果有朋友也是用的这个型号,则此处提供的代码不加修改就可以运行了。
厂家提供了sdk。由于要在windows上使用,就下载了最新的dll。如此一来,就变成了一个用python调用dll的问题。由于要传结构体给dll,所以在调用时还是有些点要注意到。
身份证阅读器所需的dll不只一个,都放在一个目录下,如下图所示:
其中python需要调用的入口dll是SynIDCardAPI.dll。而这个dll又调用了其它的dll。这样就带来了一个问题,由于多个dll之间有依赖关系,不能简单地通过绝对路径加载入口dll。现在的解决办法,在python中临时切换当前工作目录。dll加载完成后再切换回所需的目录。相关代码如下所示:
#当前工作目录
oldCwd = os.getcwd()
#不要将本程序放置于中文目录下,可能会导致os.getcwd()异常
#print('oldCwd:',oldCwd)
#将工作目录切换到当前文件所在的目录
os.chdir(os.path.dirname(os.path.abspath(__file__)))
newCwd = os.getcwd()
#print('newCwd:',newCwd)
#为了加载dll,临时切换到dll所在目录,
#由于多个dll之间有依赖关系,不能简单地通过路径加载其中一个文件
dllPath = newCwd +'\\dll'
os.chdir(dllPath)
dll = windll.LoadLibrary("SynIDCardAPI.dll")
#dll加载完成后将工作目录切换回原来的位置
os.chdir(oldCwd)
这其中的一个技巧就是,事先是知道正在执行的python文件与dll的相对路径关系的。通过os.path.dirname(os.path.abspath(file))可以获取正在执行的python文件所在的目录。然后可以调用os.chdir改变当前工作目录。
要加载dll其实很好办,网上很多文献都说到了,就是用dll = windll.LoadLibrary(“SynIDCardAPI.dll”)。
这里比较麻烦的一点是如何将结构体数据传给dll中的方法。现在采用的办法是用python中的class来模拟struct的功能,相关代码如下:
class IDCardData(Structure):
_fields_ = [
("Name", c_char * 32),
("Sex", c_char * 6),
("Nation", c_char * 20),
("Born", c_char * 18),
("Address", c_char * 72),
("IDCardNo", c_char * 38),
("GrantDept", c_char * 32),
("UserLifeBegin", c_char * 18),
("UserLifeEnd", c_char * 18),
("reserved", c_char * 38),
("PhotoFileName", c_char * 255)]
要做好这一步,需要仔细分析阅读器的开发文档与示例代码,如果是不同厂家的阅读器,这里可能要做相应的调整。
完整的代码如下,补充说明一下,为了与其它程序配合,代码中还设计了mock功能,也就是说在不实际连接身份证阅读器时,可以产生模拟数据。实际证明,这个办法在测试及与其它功能模块联调时是个好主意。
#coding=utf-8
#读取身份证信息
import time
import random
import ConfigParser
import os
from ctypes import *
import sys
sys.path.append('../')
#当前工作目录
oldCwd = os.getcwd()
#不要将本程序放置于中文目录下,可能会导致os.getcwd()异常
#print('oldCwd:',oldCwd)
#将工作目录切换到当前文件所在的目录
os.chdir(os.path.dirname(os.path.abspath(__file__)))
newCwd = os.getcwd()
#print('newCwd:',newCwd)
#为了加载dll,临时切换到dll所在目录,
#由于多个dll之间有依赖关系,不能简单地通过路径加载其中一个文件
dllPath = newCwd +'\\dll'
os.chdir(dllPath)
dll = windll.LoadLibrary("SynIDCardAPI.dll")
#dll加载完成后将工作目录切换回原来的位置
os.chdir(oldCwd)
#idCard存放数据的结构体定义,python中用类来实现
class IDCardData(Structure):
_fields_ = [
("Name", c_char * 32),
("Sex", c_char * 6),
("Nation", c_char * 20),
("Born", c_char * 18),
("Address", c_char * 72),
("IDCardNo", c_char * 38),
("GrantDept", c_char * 32),
("UserLifeBegin", c_char * 18),
("UserLifeEnd", c_char * 18),
("reserved", c_char * 38),
("PhotoFileName", c_char * 255)]
def read(idCardMockdata):
if idCardMockdata == 'yes':
return readMockData()
else:
return readRealData()
#从身份证读卡器读取数据
def readRealData():
cardData = IDCardData()
nPort= dll.Syn_FindReader()
dll.Syn_SetPhotoType(4)
dll.Syn_SetSexType(1)
pucIIN = create_string_buffer('/0'*4)
pucSN = create_string_buffer('/0'*8)
if dll.Syn_OpenPort(nPort) == 0:
if dll.Syn_SetMaxRFByte(nPort,80,0) == 0:
nRet = dll.Syn_StartFindIDCard(nPort, byref(pucIIN), 0)
nRet = dll.Syn_SelectIDCard(nPort, byref(pucSN), 0)
nRet = dll.Syn_ReadMsg(nPort, 0,byref(cardData))
if nRet == 0:
return cardData.IDCardNo
return ''
#读取模拟数据,返回400以内的一个随机整数
def readMockData():
cardData = IDCardData()
n =random.random()
if(n>0.5):
n =int(120*n)
print('idCard random index:',n)
print('customer',customer)
if customer:
cardData.IDCardNo = customer['idCard']
else:
time.sleep(1)
cardData.IDCardNo =''
else:
time.sleep(1)
cardData.IDCardNo =''
return cardData.IDCardNo