Ver código fonte

创建身份证读取服务端项目

csk 5 anos atrás
commit
d40d07c8e0

+ 118 - 0
.gitignore

@@ -0,0 +1,118 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
+# pycharm
+.idea/
+
+# 数据文件
+sample/
+model/
+labels.json
+test.csv
+loss_test.csv
+loss_train.csv
+
+
+logs/

+ 15 - 0
Pipfile

@@ -0,0 +1,15 @@
+[[source]]
+name = "pypi"
+url = "https://mirrors.aliyun.com/pypi/simple/"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+django = "*"
+requests = "*"
+flask = "*"
+django-cors-headers = "*"
+
+[requires]
+python_version = "3.6"

+ 168 - 0
Pipfile.lock

@@ -0,0 +1,168 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "8b18b053a212417281aa3a61977f0c3a863b8b3ef81d80b7e1493cf6a23541aa"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.6"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://mirrors.aliyun.com/pypi/simple/",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "asgiref": {
+            "hashes": [
+                "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
+                "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
+            ],
+            "version": "==3.2.7"
+        },
+        "certifi": {
+            "hashes": [
+                "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
+                "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
+            ],
+            "version": "==2020.4.5.1"
+        },
+        "chardet": {
+            "hashes": [
+                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
+                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+            ],
+            "version": "==3.0.4"
+        },
+        "click": {
+            "hashes": [
+                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
+                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
+            ],
+            "version": "==7.1.2"
+        },
+        "django": {
+            "hashes": [
+                "sha256:051ba55d42daa3eeda3944a8e4df2bc96d4c62f94316dea217248a22563c3621",
+                "sha256:9aaa6a09678e1b8f0d98a948c56482eac3e3dd2ddbfb8de70a868135ef3b5e01"
+            ],
+            "index": "pypi",
+            "version": "==3.0.6"
+        },
+        "django-cors-headers": {
+            "hashes": [
+                "sha256:48d267c10d11d8e17805bf896071c0a3e8efb6f79f6634a90e6faac4c2f8a1a0",
+                "sha256:73d654950b5f5e7e4f67c05183d2169d4f7518ceb87734eb0d68f9e43be59f1c"
+            ],
+            "index": "pypi",
+            "version": "==3.3.0"
+        },
+        "flask": {
+            "hashes": [
+                "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
+                "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
+            ],
+            "index": "pypi",
+            "version": "==1.1.2"
+        },
+        "idna": {
+            "hashes": [
+                "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
+                "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
+            ],
+            "version": "==2.9"
+        },
+        "itsdangerous": {
+            "hashes": [
+                "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
+                "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
+            ],
+            "version": "==1.1.0"
+        },
+        "jinja2": {
+            "hashes": [
+                "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
+                "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
+            ],
+            "version": "==2.11.2"
+        },
+        "markupsafe": {
+            "hashes": [
+                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
+                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
+                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
+                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
+                "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
+                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
+                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
+                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
+                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
+                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
+                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
+                "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
+                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
+                "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
+                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
+                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
+                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
+                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
+                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
+                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
+                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
+                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
+                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
+                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
+                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
+                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
+                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
+                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
+                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
+                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
+                "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
+                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
+                "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
+            ],
+            "version": "==1.1.1"
+        },
+        "pytz": {
+            "hashes": [
+                "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
+                "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
+            ],
+            "version": "==2020.1"
+        },
+        "requests": {
+            "hashes": [
+                "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
+                "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
+            ],
+            "index": "pypi",
+            "version": "==2.23.0"
+        },
+        "sqlparse": {
+            "hashes": [
+                "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
+                "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
+            ],
+            "version": "==0.3.1"
+        },
+        "urllib3": {
+            "hashes": [
+                "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
+                "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
+            ],
+            "version": "==1.25.9"
+        },
+        "werkzeug": {
+            "hashes": [
+                "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
+                "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
+            ],
+            "version": "==1.0.1"
+        }
+    },
+    "develop": {}
+}

+ 25 - 0
README-DEPLOY.md

@@ -0,0 +1,25 @@
+# Django 启动
+
+1. 无前端
+2. 启动django
+    + 进入交互式命令模式
+    ```
+    $pipenv shell
+    ```
+    + 创建虚拟环境,安装依赖
+    ```
+    $pipenv install
+    ```
+    + 退出交互式命令模式
+    ```
+    $exit
+    ```
+    + 启动(默认IP和端口)
+    ```
+    $python manage.py runserver
+    ```
+    + 启动(指定IP和端口)
+    ```
+    $python manage.py runserver 192.168.1.50:8080
+    ```
+

+ 19 - 0
README.md

@@ -0,0 +1,19 @@
+# 身份证读取-后端服务
+
+
+## 技术
+* python + django
+* python3.6.8-32位
+* djingo2.2
+
+## 调用
+1. 外部请求django接口,**由于是外部系统发起接口请求,会涉及到跨域的问题**;
+2. django接口调用业务逻辑层封装的读取身份证读卡器的dll,以获取到身份证信息,返回结果;
+
+## 身份证读取-公用
+* 基于公安部第一研究所公开的dll库开发
+* 读取文件代码,可以单独抽取出来使用
+```
+apps/limit/biz/id_card_reader.py
+```
+

+ 0 - 0
apps/idcard/__init__.py


+ 3 - 0
apps/idcard/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 5 - 0
apps/idcard/apps.py

@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class IdcardConfig(AppConfig):
+    name = 'idcard'

+ 8 - 0
apps/idcard/biz/README.md

@@ -0,0 +1,8 @@
+# 文件说明
+
+## id_car_info
+* 读卡器读取身份证信息
+* 使用公安部第一研究所通用SDK 2.1版
+* lib/dll内为相关库,其中termb.dll为入口调用库
+
+

+ 183 - 0
apps/idcard/biz/id_card_reader.py

@@ -0,0 +1,183 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+#
+# 读卡器读取身份证信息
+# 调用Dll
+# author: Scott Chen
+# date: 2019-12-13
+
+import ctypes
+import codecs
+from log_ware import LogWare
+logger = LogWare().get_logger()
+
+# 自定义异常
+class ReaderIdCardError:
+    pass
+
+
+class IdCardReader:
+    def __init__(self, dll_lib="apps/idcard/biz/lib/dll/termb.dll", id_card_path="wz.txt"):
+        # 要调用的动态链接库位置
+        self.__dll_lib_local = dll_lib
+        # 身份证信息保存文件路径,当前项目目录下
+        self.__id_card_info_path = id_card_path
+        # 调用公安部统一动态识别库
+        self.__dll = ctypes.windll.LoadLibrary(self.__dll_lib_local)
+
+        self.__id_car_info = self.__id_car_info_build()
+        self.__sex_dict = self.__sex_dict_build()
+        self.__nation_dict = self.__nation_dict_build()
+
+    def recognize(self):
+        t1 = self.__init_machine()
+        if t1 is False:
+            self.__id_car_info['error'] = '[10001]打开设备失败'
+            return self.__id_car_info
+
+        t2 = self.__authc()
+        if t2 is False:
+            self.__id_car_info['error'] = '[10002]未放卡或卡片放置不正确,身份证认证失败'
+            return self.__id_car_info
+
+        t3 = self.__read()
+        if t3 is False:
+            self.__id_car_info['error'] = '[10003]读卡发生错误'
+            return self.__id_car_info
+        return self.__id_car_info
+
+    def __init_machine(self):
+        # 初始化读卡器
+        connPort = self.__dll.InitCommExt()
+        if connPort == 0:
+            logger.debug("查找并打开设备端口失败")
+            self.__close()
+            return False
+        else:
+            logger.debug("设备连接在%s", connPort)
+            return True
+
+    def __authc(self):
+        # 读卡器身份证认证
+        is_authc = self.__dll.Authenticate()
+        if is_authc == 1:
+            logger.debug("设备认证%s", is_authc)
+            return True
+        else:
+            self.__error_print("未放卡或卡片放置不正确,身份证认证失败")
+            self.__close()
+            return False
+
+    def __read(self):
+        # 1 读基本信息,形成文字信息文件wz.txt、相片文件xp.wlt、zp.bmp,如果有指纹信息形成指纹信息文件fp.dat
+        # 2 只读文字信息,形成文字信息文件wz.txt和相片文件xp.wlt
+        # 3 读最新住址信息,形成最新住址文件newadd.txt
+        read_result = self.__dll.Read_Content(2)
+        if read_result != 1:
+            self.__error_print("读卡发生错误")
+            return False
+        else:
+            self.__read_id_card()
+            logger.debug("身份证信息:%s", str(self.__id_car_info))
+        self.__close()
+        return True
+
+
+    ####################################################################################################
+
+    def __id_car_info_build(self):
+        return {
+        'name': '',
+        'sex': '',
+        'nation': '',
+        'born_date': '',
+        'address': '',
+        'id_no': '',
+        'sign_gov': '',
+        'start_date': '',
+        'end_date': ''
+    }
+
+
+    def __sex_dict_build(self):
+        return {
+            '1': '男',
+            '2': '女',
+        }
+
+    def __nation_dict_build(self):
+        return {
+        '01': '汉', '02': '汉', '03': '回', '04': '藏', '05': '维吾尔', '06': '苗',
+        '07': '彝', '08': '壮', '09': '布依', '10': '朝鲜', '11': '满', '12': '侗',
+        '13': '瑶', '14': '白', '15': '土家', '16': '哈尼', '17': '哈萨克', '18': '傣',
+        '19': '黎', '20': '傈僳', '21': '佤', '22': '畲', '23': '高山', '24': '拉祜',
+        '25': '水', '26': '东乡', '27': '纳西', '28': '景颇', '29': '柯尔克孜', '30': '土',
+        '31': '达斡尔', '32': '仫佬', '33': '羌', '34': '布朗', '35': '撒拉', '36': '毛南',
+        '37': '仡佬', '38': '锡伯', '39': '阿昌', '40': '普米', '41': '塔吉克', '42': '怒',
+        '43': '乌孜别克', '44': '俄罗斯', '45': '鄂温克', '46': '德昂', '47': '保安', '48': '裕固',
+        '49': '京', '50': '塔塔尔', '51': '独龙', '52': '鄂伦春', '53': '赫哲', '54': '门巴',
+        '55': '珞巴', '56': '珞巴'
+    }
+
+    # 解码读取文件内容后的信息,共128个字符
+    # "总钻风            10119800918广东省深圳市南山区前海路1080号海之景A座102          440305198009183074深圳市公安局南山分局     2014120520341205               "
+
+    # 位置关系
+    # 1-15 姓名
+    # 16 性别
+    # 17-18 民族
+    # 19-26 出生
+    # 27-61 住址
+    # 62-79 身份证号
+    # 80-94 签发机关
+    # 95-102 有效期起始日期
+    # 103-128 有效期截止日期
+    # 读取文件信息
+    def __read_id_card(self):
+        try:
+            with codecs.open(self.__id_card_info_path, 'r', 'utf-16-le') as f:
+                content = f.read()
+                info_map = self.__format(content)
+                if (info_map is None):
+                    logger.debug("格式化身份信息文件内容出异常")
+                logger.debug("身体份证信息读取成功")
+        except Exception as e:
+            info = "Error when read id card info from txt file."
+            logger.debug("%s, error: %s", info, e)
+            raise ReaderIdCardError(info)
+
+    def __format(self, content):
+        if (content is None):
+            logger.debug("不能获取身份证信息,文件内容为空")
+            return None
+        if (len(content) != 128):
+            logger.debug("文件内容解码错误,文本长度异常")
+            return None
+
+        self.__id_car_info['name'] = content[0:14].strip()
+        self.__id_car_info['sex'] = self.__sex_dict[content[15]]
+        # 数字要往后多取一位
+        self.__id_car_info['nation'] = self.__nation_dict[content[16:18]]
+        self.__id_car_info['born_date'] = content[18:26]
+        self.__id_car_info['address'] = content[26:61].strip()
+        self.__id_car_info['id_no'] = content[61:79]
+        self.__id_car_info['sign_gov'] = content[79:93].strip()
+        self.__id_car_info['start_date'] = content[93:102]
+        self.__id_car_info['end_date'] = content[102:128].strip()
+        return self.__id_car_info
+
+    def __error_print(self, txt):
+        logger.debug("%s,发生错误,请重新放置卡片", txt)
+
+    def __close(self):
+        if (self.__dll.CloseComm() == 1):
+            logger.debug("读卡器关闭")
+        else:
+            logger.debug("读卡器关闭失败")
+
+
+# 单元测试
+if __name__ == '__main__':
+    id_card_reader = IdCardReader()
+    id_card_reader.recognize()
+

BIN
apps/idcard/biz/lib/dll/IDCard.dll


BIN
apps/idcard/biz/lib/dll/sdtapi.dll


BIN
apps/idcard/biz/lib/dll/termb.dll


+ 26 - 0
apps/idcard/biz/main.py

@@ -0,0 +1,26 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+#
+# 身份证识别 Python 3.6.8
+# 入口
+# author: Scott Chen
+# date: 2019-12-17
+
+from apps.idcard.biz.id_card_reader import IdCardReader
+from log_ware import LogWare
+logger = LogWare().get_logger()
+
+
+if __name__ == '__main__':
+    # 身份证信息读取
+    reader = IdCardReader()
+    id_car_info = reader.recognize()
+    if 'error' in id_car_info:
+        logger.debug(id_car_info['error'])
+
+    name, id_no = id_car_info['name'], id_car_info['id_no']
+    logger.debug("name: %s, id_no: %s", name, id_no)
+
+
+
+

+ 71 - 0
apps/idcard/biz/signature.py

@@ -0,0 +1,71 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+#
+# 签名
+# 字典升序
+# 拼接规则:secret_key_value+key+value
+# author: Scott Chen
+# date: 2019-12-17
+
+import time
+from hashlib import md5
+from log_ware import LogWare
+logger = LogWare().get_logger()
+
+
+class Signature:
+
+    def __init__(self, in_params_map, secret_key):
+        self.__params = in_params_map
+        self.__secret_key = secret_key
+        self.__sort_params = {}
+        self.__joint_str = ''
+        self.__out_params = in_params_map
+
+    # 请求参数字典排序-升序
+    def __dict_sort_asc(self):
+        temp = sorted(self.__params.items())
+        for i in temp:
+            self.__sort_params[i[0]] = i[1]
+        logger.debug('升序排列后:%s', str(self.__sort_params))
+
+    # 参数拼接
+    def __params_joint(self):
+        self.__joint_str += self.__secret_key
+        for k, v in self.__sort_params.items():
+            self.__joint_str += (k + v)
+        logger.debug('参数拼接后:%s', str(self.__joint_str))
+
+    # md5签名
+    def sign_with_md5(self):
+        self.__dict_sort_asc()
+        self.__params_joint()
+        sign_str = md5(self.__joint_str.encode("utf-8")).hexdigest()
+        logger.debug('md5签名的参数值:%s', sign_str)
+
+        self.__out_params['sign'] = sign_str
+        logger.debug('请求参数签名结果:%s', str(self.__out_params))
+        return self.__out_params
+
+
+# 单元测试
+if __name__ == '__main__':
+    data = {
+        'merchId': '111',
+        'merchName': '商户',
+        'thirdPartyMerchCode': '222',
+        'thirdPartyMerchName': '三方商户',
+        'name': '张三',
+        'idCard': '440305198009183074',
+        'isCheckIdCard': '0'
+    }
+    params_map = {
+        'merchId': '111',
+        'data': str(data),
+        'timestamp': str(int(time.time()))
+    }
+    secret_key = 'abc789'
+
+    instance = Signature(params_map, secret_key)
+    ret = instance.sign_with_md5()
+    logger.debug(str(ret))

+ 0 - 0
apps/idcard/migrations/__init__.py


+ 3 - 0
apps/idcard/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
apps/idcard/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 11 - 0
apps/idcard/urls.py

@@ -0,0 +1,11 @@
+"""idcard_reader_web URL Configuration
+limit app urls
+
+"""
+
+from django.urls import re_path
+from apps.idcard.views.idcard_reader import read_idcard
+
+urlpatterns = [
+    re_path(r'read$', read_idcard),
+]

+ 47 - 0
apps/idcard/views/idcard_reader.py

@@ -0,0 +1,47 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+#
+#
+# author: Scott Chen
+# date: 2019-12-23
+
+from django.views.decorators.http import require_http_methods
+from django.http import JsonResponse
+
+from apps.idcard.biz.id_card_reader import IdCardReader
+
+
+@require_http_methods(["GET", "POST"])
+def read_idcard(request):
+    # response = {}
+    # data = {}
+    # data['name'] = '张三'
+    # data['id_no'] = '443xxxxxxxxxxx32x7'
+    #
+    # response['code'] = '0'
+    # response['msg'] = '成功'
+    # response['rows'] = [{'total': 1, 'data': data}]
+    # return JsonResponse(response)
+
+    response = {}
+    data = {}
+
+    # 身份证信息读取
+    reader = IdCardReader()
+    id_car_info = reader.recognize()
+    if 'error' in id_car_info:
+        response['code'] = '-1'
+        response['msg'] = id_car_info['error']
+        response['rows'] = [{'total': 0, 'data': []}]
+        return JsonResponse(response)
+
+    name, id_no = id_car_info['name'], id_car_info['id_no']
+
+    data['name'] = name
+    data['id_no'] = id_no
+
+    response['code'] = '0'
+    response['msg'] = '成功'
+    response['rows'] = [{'total': 1, 'data': data}]
+    return JsonResponse(response)
+

BIN
db.sqlite3


+ 0 - 0
idcard_reader_service/__init__.py


+ 16 - 0
idcard_reader_service/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for idcard_reader_service project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'idcard_reader_service.settings')
+
+application = get_asgi_application()

+ 156 - 0
idcard_reader_service/settings.py

@@ -0,0 +1,156 @@
+"""
+Django settings for idcard_reader_service project.
+
+Generated by 'django-admin startproject' using Django 3.0.1.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.0/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = ')90tga^idkb$h*c$&6(*#=(q1^mq8o-u+f!%3(sb9b8-b*c29i'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = [
+    # CORS 处理跨域
+    '127.0.0.1'
+]
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    # CORS 处理跨域
+    'corsheaders',
+    # # 身份证读取app
+    'apps.idcard',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    # 'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    # CORS 处理跨域
+    'corsheaders.middleware.CorsMiddleware',
+    'django.middleware.common.CommonMiddleware',
+]
+
+ROOT_URLCONF = 'idcard_reader_service.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'idcard_reader_service.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.0/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/3.0/howto/static-files/
+
+STATIC_URL = '/static/'
+
+
+
+# ---------- for CORS 处理跨域 django-cors-headers ----------
+
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_CREDENTIALS = True
+CORS_ALLOW_METHODS = [
+    'DELETE',  'GET',  'OPTIONS',
+    'PATCH',  'POST',  'PUT',  'VIEW'
+]
+CORS_ALLOW_HEADERS = [
+    'XMLHttpRequest',
+    'X_FILENAME',
+    'accept-encoding',
+    'authorization',
+    'content-type',
+    'dnt',
+    'origin',
+    'user-agent',
+    'x-csrftoken',
+    'x-requested-with',
+    'Pragma'
+]
+
+

+ 11 - 0
idcard_reader_service/urls.py

@@ -0,0 +1,11 @@
+from django.contrib import admin
+from django.urls import re_path, include
+from django.views.generic import TemplateView
+
+import apps.idcard.urls
+
+urlpatterns = [
+    re_path(r'^admin/', admin.site.urls),
+    re_path(r'^api/idcard/', include(apps.idcard.urls)),
+    re_path(r'^$', TemplateView.as_view(template_name="index.html")),
+]

+ 16 - 0
idcard_reader_service/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for idcard_reader_service project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'idcard_reader_service.settings')
+
+application = get_wsgi_application()

+ 78 - 0
log_ware.py

@@ -0,0 +1,78 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+#
+#
+# author: Scott Chen
+# date: 2020-01-03
+
+import os
+import time
+import logging
+from logging.handlers import RotatingFileHandler
+
+
+class LogWare:
+
+    def __init__(self):
+        self.__log_size = 3 * 1024 * 1024
+        self.__backup_count = 100
+        self.__log_dir = 'd:/data/log/'
+        self.__project_name = 'idcard_reader_web'
+        self.__log_file_prefix = 'my-log'
+        self.__formatter = logging.Formatter('[%(asctime)s][%(threadName)s][%(levelname)s][%(filename)s][%(lineno)d] %(message)s', '%Y-%m-%d %H:%M:%S')
+
+        self.__log_dir = self.__log_dir + self.__project_name
+        self.__timestamp = time.strftime("%Y-%m-%d", time.localtime())
+        self.__log_file_name = '{}_{}.log'.format(self.__log_file_prefix, self.__timestamp)
+
+        self.__log_path = os.path.join(self.__log_dir, self.__log_file_name)
+
+
+        if os.path.exists(self.__log_dir) and os.path.isdir(self.__log_dir):
+            pass
+        else:
+            os.makedirs(self.__log_dir)
+
+        self.__logger = logging.getLogger("")
+        # 坑坑! 此处要整体设置 logger level = DEBUG,否则后面在不同的 handler 中设置 level 无效
+        self.__logger.setLevel(logging.DEBUG)
+
+    def get_logger(self):
+
+        # 控制台
+        # 不同 level, 颜色不同
+        console_handler = logging.StreamHandler()
+        console_handler.setLevel(logging.DEBUG)
+        console_handler.setFormatter(self.__formatter)
+
+        # 此处通过文件大小截断日志文件,如果想要通过时间截断,可以使用 TimedRotatingFileHandler 这个类
+        file_handler = logging.handlers.RotatingFileHandler(filename=self.__log_path, mode='a',
+                                                            maxBytes=self.__log_size,
+                                                            encoding='utf8', backupCount=self.__backup_count)
+        file_handler.setFormatter(self.__formatter)
+
+        # 日志文件输出
+        self.__logger.addHandler(file_handler)
+        self.__logger.addHandler(console_handler)
+        self.__logger.setLevel(logging.DEBUG)
+        return self.__logger
+
+
+
+# ########## 单元测试 ##########
+# 外部使用时,引入
+from log_ware import LogWare
+logger = LogWare().get_logger()
+
+if __name__ == '__main__':
+    pass
+    i = 0
+    while True:
+        logger.debug("level debug %s %s %d", 'aaaa', 'bbb', 10)
+        logger.info("level info")
+        logger.warning('level warning')
+        logger.error("level error")
+        logger.critical('level critical')
+
+        i += 1
+        if i == 10: break

+ 21 - 0
manage.py

@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'idcard_reader_service.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 9 - 0
static/README.md

@@ -0,0 +1,9 @@
+# static
+
+
+
+
+
+
+
+

+ 9 - 0
templates/README.md

@@ -0,0 +1,9 @@
+# template
+
+
+
+
+
+
+
+