代码
<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Websocket/Web API 使用 Demo</title>
<style>
/* ==================
布局
==================== */
/* -- flex弹性布局 -- */
.flex {
display: flex;
}
.basis-xs {
flex-basis: 20%;
}
.basis-sm {
flex-basis: 40%;
}
.basis-df {
flex-basis: 50%;
}
.basis-lg {
flex-basis: 60%;
}
.basis-xl {
flex-basis: 80%;
}
.flex-sub {
flex: 1;
}
.flex-twice {
flex: 2;
}
.flex-treble {
flex: 3;
}
.flex-direction {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.align-start {
align-items: flex-start;
}
.align-end {
align-items: flex-end;
}
.align-center {
align-items: center;
}
.align-stretch {
align-items: stretch;
}
.self-start {
align-self: flex-start;
}
.self-center {
align-self: flex-center;
}
.self-end {
align-self: flex-end;
}
.self-stretch {
align-self: stretch;
}
.align-stretch {
align-items: stretch;
}
.justify-start {
justify-content: flex-start;
}
.justify-end {
justify-content: flex-end;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.justify-around {
justify-content: space-around;
}
</style>
</head>
<body>
<div class="container">
<div class="head flex justify-start align-center" style="margin-bottom: 20px;">
<h2>未来之窗-鱼住未来身份识别服务使用 Demo</h2>
</div>
<div class="content flex justify-start">
<!-- 功能使用 -->
<div class="flex-sub" style="border-right: 1px solid #eee;">
<h3 class="block-title">功能使用</h3>
<div class="flex justify-start align-center" style="color: #5c6063;">
<p>填写连接地址:</p>
<input id="connect-address" value="127.0.0.1:30004"></input>
</div>
<div class="flex flex-direction align-center" style="height: 535px;">
<div class="func-card websocket flex-sub">
<p class="title">Websocket 使用 Demo</p>
<p class="sub-title">( 点击按钮读取相应信息;支持被动接收和主动请求两种方式。 )</p>
<div class="btn-area">
<div class="flex">
<button id="on-websocket" class="flex-sub">连接到webSocket</button>
<button id="off-websocket" class="flex-sub">断开webSocket</button>
</div>
<div class="flex flex-wrap">
<button id="websocket-ID" class="flex-sub">身份证</button>
<button id="websocket-ID-sn" class="flex-sub">身份证SN</button>
<button id="websocket-A-sn" class="flex-sub">A卡SN</button>
<button id="websocket-device-No" class="flex-sub">设备唯一号</button>
</div>
</div>
</div>
<div class="func-card webapi flex-sub">
<p class="title">Web API 使用 Demo</p>
<p class="sub-title">( 点击按钮读取相应信息;支持主动请求方式。 )</p>
<div class="btn-area">
<div class="flex flex-wrap">
<button id="http-ID" class="flex-sub">身份证</button>
<button id="http-ID-sn" class="flex-sub">身份证SN</button>
</div>
<div class="flex flex-wrap">
<button id="http-A-sn" class="flex-sub">A卡SN</button>
<button id="http-device-No" class="flex-sub">设备唯一号</button>
</div>
</div>
</div>
</div>
</div>
<!-- 读取过程 -->
<div class="flex-sub" style="border-right: 1px solid #eee;">
<h3 class="block-title">读取过程</h3>
<textarea name="process" id="process-content" readonly="readonly"></textarea>
</div>
<!-- 读取结果 -->
<div class="result-container flex-sub">
<h3 class="block-title">读取结果</h3>
<div class="flex justify-start align-center" style="padding-left: 25px;color: #5c6063;">
<p>SN 号:</p>
<p id="SN-content"></p>
</div>
<div id="card-front" class="ID-card card-front">
<img class="image" src="" alt="证件照">
<p class="name"></p>
<p class="enName"></p>
<p class="sex"></p>
<p class="nation"></p>
<p class="year"></p>
<p class="month"></p>
<p class="date"></p>
<p class="address"></p>
<p class="number"></p>
</div>
<div id="card-back" class="ID-card card-back">
<p class="department"></p>
<p class="expiry"></p>
<p class="pass-number"></p>
</div>
<div class="flex justify-center">
<button id="clean-process">清空</button>
</div>
</div>
</div>
</div>
<div class="copyright">
<span class="copyright-item" id="copyright-text">
<script type="text/javascript">
document.getElementById("copyright-text").innerText = "成都鱼住未来科技有限公司版权所有 ©2019 - "+ new Date().getFullYear()
</script>
</span>
</div>
</body>
</html>
<script src="/o2o/tpl/Merchant/static/js/jquery.min.js"> </script>
<script>
var btnOnWS = $('#on-websocket')
var btnOffWS = $('#off-websocket')
var btnWS_ID = $('#websocket-ID')
var btnWS_IDSN = $('#websocket-ID-sn')
var btnWS_ASN = $('#websocket-A-sn')
var btnWS_DeviceNo = $('#websocket-device-No')
var btnHttp_ID = $('#http-ID')
var btnHttp_IDSN = $('#http-ID-sn')
var btnHttp_ASN = $('#http-A-sn')
var btnHttp_DeviceNo = $('#http-device-No')
var connectAddress = $('#connect-address')
var processContent = $('#process-content')
var btnCleanProcess = $('#clean-process')
var cardFront = $('#card-front')
var cardBack = $('#card-back')
var SNContent = $('#SN-content')
var image = $('#image')
var name = $('#name')
var sex = $('#sex')
var nation = $('#nation')
var year = $('#year')
var month = $('#month')
var date = $('#date')
var address = $('#address')
var number = $('#number')
var department = $('#department')
var expiry = $('#expiry')
</script>
<script>
let ws = null
function hex2a(hex) {
let str_list = ''
for (let i = 0; i < hex.length && hex.substr(i, 2) !== '00'; i += 2) {
const a = hex.charCodeAt(i)
const b = hex.charCodeAt(i + 1)
const c = b * 256 + a
str_list += String.fromCharCode(c)
}
return str_list.toString()
}
function setDocumentInfo(szparam)
{
if (szparam.CardType == 74)
{
// 切换背景图片 83是台湾
cardFront.removeClass()
cardBack.removeClass()
cardFront.addClass('GAT-card')
cardFront.addClass('card-hongkong-macao-taiwan-front')
cardBack.addClass('GAT-card')
let no = hex2a(window.atob(szparam.CardInfo.No))
if (no && no.startsWith('83')){
cardBack.addClass('card-taiwan-back')
}else{
cardBack.addClass('card-hongkong-macao-back')
}
strLog = '读取 港澳台居民居住证 成功\r\n';
strLog += 'SN:' + szparam.CardInfo.SN + '\r\n';
strLog += '中文名:' + hex2a(window.atob(szparam.CardInfo.Name)) + '\r\n';
strLog += '证件号码:' + no + '\r\n';
strLog += '性别:' + hex2a(window.atob(szparam.CardInfo.Sex)) + '\r\n';
strLog += '出生日期:' + hex2a(window.atob(szparam.CardInfo.Birthday)) + '\r\n';
strLog += '民族:' + hex2a(window.atob(szparam.CardInfo.Nation)) + '\r\n';
strLog += '地址:' + hex2a(window.atob(szparam.CardInfo.Address)) + '\r\n';
strLog += '签发机关:' + hex2a(window.atob(szparam.CardInfo.SignedDepartment)) + '\r\n';
strLog += '开始日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin)) + '\r\n';
strLog += '结束日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)) + '\r\n\r\n';
strLog += '通行证号码:' + hex2a(window.atob(szparam.CardInfo.OtherNO)) + '\r\n';
strLog += '签发次数:' + hex2a(window.atob(szparam.CardInfo.SignNum)) + '\r\n';
processContent.text(strLog)
// 港澳台通行证号码
console.log(szparam.CardInfo)
cardFront.find('.name').text(hex2a(window.atob(szparam.CardInfo.Name)))
cardFront.find('.sex').text(hex2a(window.atob(szparam.CardInfo.Sex)) ==='1'? '男':'女')
const Birthday = hex2a(window.atob(szparam.CardInfo.Birthday))
const birthArr = parseDateString(Birthday , ".", true).split(".")
cardFront.find('.year').text(birthArr[0])
cardFront.find('.month').text(birthArr[1])
cardFront.find('.date').text(birthArr[2])
cardFront.find('.address').text(hex2a(window.atob(szparam.CardInfo.Address)))
cardFront.find('.number').text(hex2a(window.atob(szparam.CardInfo.No)))
cardBack.find('.department').text(hex2a(window.atob(szparam.CardInfo.SignedDepartment)))
const ValidityPeriodBegin = hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin))
const ValidityPeriodEnd = hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)).trim()
const expiryBegin = parseDateString(ValidityPeriodBegin, '.')
const expiryEnd = ValidityPeriodEnd !== '长期' ? parseDateString(ValidityPeriodEnd, '.') : ValidityPeriodEnd
cardBack.find('.expiry').text( expiryBegin + '-' + expiryEnd)
cardBack.find('.pass-number').text( hex2a(window.atob(szparam.CardInfo.OtherNO)))
}
else if (szparam.CardType == 73)
{
// 切换背景图片 83是台湾
cardFront.removeClass()
cardBack.removeClass()
cardFront.addClass('WGR-card-1')
cardFront.addClass('card-old-foreigner-front')
cardBack.addClass('WGR-card-1')
cardBack.addClass('card-old-foreigner-back')
strLog = '读取 外国人永久居留身份证(旧版) 成功\r\n';
strLog += 'SN:' + szparam.CardInfo.SN + '\r\n';
strLog += '中文名:' + hex2a(window.atob(szparam.CardInfo.Name)) + '\r\n';
strLog += '英文名:' + hex2a(window.atob(szparam.CardInfo.EnName)) + '\r\n';
strLog += '证件号码:' + hex2a(window.atob(szparam.CardInfo.No)) + '\r\n';
strLog += '性别:' + hex2a(window.atob(szparam.CardInfo.Sex)) + '\r\n';
strLog += '出生日期:' + hex2a(window.atob(szparam.CardInfo.Birthday)) + '\r\n';
strLog += '国籍:' + hex2a(window.atob(szparam.CardInfo.Country)) + '\r\n';
strLog += '签发机关:中华人民共和国移民管理局\r\n';
strLog += '开始日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin)) + '\r\n';
strLog += '结束日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)) + '\r\n\r\n';
strLog += '版本号:' + hex2a(window.atob(szparam.CardInfo.Version)) + '\r\n';
processContent.text(strLog)
let name = hex2a(window.atob(szparam.CardInfo.Name))
let enName = hex2a(window.atob(szparam.CardInfo.EnName))
let nameText = enName + (name.trim()? ' / '+ name : '')
cardFront.find('.name').text(nameText)
cardFront.find('.sex').text(hex2a(window.atob(szparam.CardInfo.Sex)) === '1'? '男': '女')
const Birthday = hex2a(window.atob(szparam.CardInfo.Birthday))
const birthArr = parseDateString(Birthday , ".", true).split(".")
cardFront.find('.year').text(birthArr.join('-')) //出生年月
cardFront.find('.month').text(hex2a(window.atob(szparam.CardInfo.Country)))//国籍
const ValidityPeriodBegin = hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin))
const ValidityPeriodEnd = hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)).trim()
const expiryBegin = parseDateString(ValidityPeriodBegin, '.')
const expiryEnd = ValidityPeriodEnd !== '长期' ? parseDateString(ValidityPeriodEnd, '.') : ValidityPeriodEnd
cardFront.find('.date').text(expiryBegin + '-' + expiryEnd)
cardFront.find('.address').text('中华人民共和国移民管理局') //
cardFront.find('.number').text(hex2a(window.atob(szparam.CardInfo.No)))
}
else if (szparam.CardType == 89)
{
// 切换背景图片 83是台湾
cardFront.removeClass()
cardBack.removeClass()
cardFront.addClass('WGR-card')
cardFront.addClass('card-new-foreigner-front')
cardBack.addClass('WGR-card')
cardBack.addClass('card-new-foreigner-back')
strLog = '读取 外国人永久居留身份证(新版) 成功\r\n';
strLog += 'SN:' + szparam.CardInfo.SN + '\r\n';
strLog += '中文名:' + hex2a(window.atob(szparam.CardInfo.Name)) + '\r\n';
strLog += '英文名:' + hex2a(window.atob(szparam.CardInfo.EnName)) + '\r\n';
strLog += '证件号码:' + hex2a(window.atob(szparam.CardInfo.No)) + '\r\n';
strLog += '性别:' + hex2a(window.atob(szparam.CardInfo.Sex)) + '\r\n';
strLog += '出生日期:' + hex2a(window.atob(szparam.CardInfo.Birthday)) + '\r\n';
strLog += '国籍:' + hex2a(window.atob(szparam.CardInfo.Country)) + '\r\n';
strLog += '签发机关:中华人民共和国移民管理局\r\n';
strLog += '开始日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin)) + '\r\n';
strLog += '结束日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)) + '\r\n\r\n';
strLog += '通行证号码:' + hex2a(window.atob(szparam.CardInfo.OtherNO)) + '\r\n';
strLog += '签发次数:' + hex2a(window.atob(szparam.CardInfo.SignNum)) + '\r\n';
processContent.text(strLog)
let name = hex2a(window.atob(szparam.CardInfo.Name))
let enName = hex2a(window.atob(szparam.CardInfo.EnName))
cardFront.find('.name').text(name)
cardFront.find('.enName').text(enName)
cardFront.find('.sex').text(hex2a(window.atob(szparam.CardInfo.Sex)) === '1'? '男': '女')
const Birthday = hex2a(window.atob(szparam.CardInfo.Birthday))
const birthArr = parseDateString(Birthday , ".", true).split(".")
cardFront.find('.year').text(birthArr.join('-')) //出生年月
cardFront.find('.month').text(hex2a(window.atob(szparam.CardInfo.Country)))//国籍
const ValidityPeriodBegin = hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin))
const ValidityPeriodEnd = hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)).trim()
const expiryBegin = parseDateString(ValidityPeriodBegin, '.')
const expiryEnd = ValidityPeriodEnd !== '长期' ? parseDateString(ValidityPeriodEnd, '.') : ValidityPeriodEnd
cardFront.find('.date').text(expiryBegin + '-' + expiryEnd)
cardFront.find('.number').text(hex2a(window.atob(szparam.CardInfo.No)))
}
else
{
cardFront.removeClass()
cardBack.removeClass()
cardFront.addClass('ID-card')
cardFront.addClass('card-front')
cardBack.addClass('ID-card')
cardBack.addClass('card-back')
strLog = '读取 身份证 成功\r\n';
strLog += 'SN:' + szparam.CardInfo.SN + '\r\n';
strLog += '中文名:' + hex2a(window.atob(szparam.CardInfo.Name)) + '\r\n';
strLog += '证件号码:' + hex2a(window.atob(szparam.CardInfo.No)) + '\r\n';
strLog += '性别:' + hex2a(window.atob(szparam.CardInfo.Sex)) + '\r\n';
strLog += '出生日期:' + hex2a(window.atob(szparam.CardInfo.Birthday)) + '\r\n';
strLog += '民族:' + hex2a(window.atob(szparam.CardInfo.Nation)) + '\r\n';
strLog += '地址:' + hex2a(window.atob(szparam.CardInfo.Address)) + '\r\n';
strLog += '签发机关:' + hex2a(window.atob(szparam.CardInfo.SignedDepartment)) + '\r\n';
strLog += '开始日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin)) + '\r\n';
strLog += '结束日期:' + hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)) + '\r\n\r\n';
strLog += '通行证号码:' + hex2a(window.atob(szparam.CardInfo.OtherNO)) + '\r\n';
strLog += '签发次数:' + hex2a(window.atob(szparam.CardInfo.SignNum)) + '\r\n';
processContent.text(strLog)
// 内容填充
cardFront.find('.name').text(hex2a(window.atob(szparam.CardInfo.Name)))
cardFront.find('.sex').text(hex2a(window.atob(szparam.CardInfo.Sex)))
cardFront.find('.nation').text(hex2a(window.atob(szparam.CardInfo.Nation)))
const Birthday = hex2a(window.atob(szparam.CardInfo.Birthday))
const birthArr = parseDateString(Birthday , ".", true).split(".")
cardFront.find('.year').text(birthArr[0])
cardFront.find('.month').text(birthArr[1])
cardFront.find('.date').text(birthArr[2])
cardFront.find('.address').text(hex2a(window.atob(szparam.CardInfo.Address)))
cardFront.find('.number').text(hex2a(window.atob(szparam.CardInfo.No)))
cardBack.find('.department').text(hex2a(window.atob(szparam.CardInfo.SignedDepartment)))
const ValidityPeriodBegin = hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin))
const ValidityPeriodEnd = hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)).trim()
const expiryBegin = parseDateString(ValidityPeriodBegin, '.')
const expiryEnd = ValidityPeriodEnd !== '长期' ? parseDateString(ValidityPeriodEnd, '.') : ValidityPeriodEnd
cardBack.find('.expiry').text( expiryBegin + '-' + expiryEnd)
}
SNContent.text(szparam.CardInfo.SN)
cardFront.find('.image').attr('src','data:image/jpg;base64,' + szparam.BmpInfo)
// if (szparam.CardInfo.Name){
// cardFront.find('.name').text(hex2a(window.atob(szparam.CardInfo.Name)))
// }
// if (szparam.CardInfo.Sex){
// cardFront.find('.sex').text(hex2a(window.atob(szparam.CardInfo.Sex)))
// }
// if (szparam.CardInfo.Nation){
// cardFront.find('.nation').text(hex2a(window.atob(szparam.CardInfo.Nation)))
// }
//
// const Birthday = hex2a(window.atob(szparam.CardInfo.Birthday))
// const birthArr = parseDateString(Birthday , ".", true).split(".")
// cardFront.find('.year').text(birthArr[0])
// cardFront.find('.month').text(birthArr[1])
// cardFront.find('.date').text(birthArr[2])
//
// if (szparam.CardInfo.Address){
// cardFront.find('.address').text(hex2a(window.atob(szparam.CardInfo.Address)))
// }
//
// if (szparam.CardInfo.No){
// cardFront.find('.number').text(hex2a(window.atob(szparam.CardInfo.No)))
// }
//
// if (szparam.CardInfo.SignedDepartment){
// cardBack.find('.department').text(hex2a(window.atob(szparam.CardInfo.SignedDepartment)))
// }
//
// const ValidityPeriodBegin = hex2a(window.atob(szparam.CardInfo.ValidityPeriodBegin))
// const ValidityPeriodEnd = hex2a(window.atob(szparam.CardInfo.ValidityPeriodEnd)).trim()
// const expiryBegin = parseDateString(ValidityPeriodBegin, '.')
// const expiryEnd = ValidityPeriodEnd !== '长期' ? parseDateString(ValidityPeriodEnd, '.') : ValidityPeriodEnd
// cardBack.find('.expiry').text( expiryBegin + '-' + expiryEnd)
}
function conWS() {
const webUrl = 'ws://' + connectAddress.val() + '/ws'
ws = new WebSocket(webUrl)
ws.onopen = function (evt) {
let szhelp = 'websocket连接成功,url[' + webUrl + '],读卡器上放置身份证后websocket会自动接收身份证数据,如需手动操作请调用WS_ReadInfo()函数\r\n\r\n'
szhelp += '支持被动接收和主动请求两种方式\r\n'
szhelp += '被动接收:当读卡器刷卡成功后会推送身份证信息到websocket,websocket直接显示即可\r\n'
szhelp += '主动请求:支持网页端主动向服务器请求对应的消息。可查看<WS_ReadInfo><WS_GetASN><WS_GetBCardNo>这三个接口'
processContent.text(szhelp)
}
ws.onclose = function (evt) {
processContent.text('websocket已断开')
}
ws.onmessage = function (messageEvent) {
const jsonobject = JSON.parse(messageEvent.data)
if (jsonobject.Ret == 0) {
if (jsonobject.Cmd == 10001) {
cleanMsg()
const szparam = JSON.parse(window.atob(jsonobject.UserParam))
setDocumentInfo(szparam);
} else if (jsonobject.Cmd == 30401) {
const szparam = JSON.parse(window.atob(jsonobject.UserParam))
processContent.text('websocket 协议 读取A卡SN成功:' + szparam.SN)
} else if (jsonobject.Cmd == 20401) {
const szparam = JSON.parse(window.atob(jsonobject.UserParam))
processContent.text('websocket 协议 读取身份证卡片SN成功:' + szparam.SN)
} else if (jsonobject.Cmd == 20511) {
const szparam = JSON.parse(window.atob(jsonobject.UserParam))
processContent.text('websocket 协议 读卡器唯一号:' + szparam.SN)
}else if (jsonobject.Cmd == 1000) {
szparam = JSON.parse(window.atob(jsonobject.UserParam));
if (szparam.State == 0)
{
processContent.text('读卡器已被拔出')
}
else processContent.text('读卡器已插入')
}
} else {
processContent.text('websocket 协议调用失败,原因:' + jsonobject.ErrInfo)
}
}
}
function disconWS() {
if (ws) {
ws.close()
processContent.text('websocket已断开')
}
}
function cleanMsg() {
processContent.text('')
SNContent.text('')
cardFront.find('.name').text('')
cardFront.find('.enName').text('')
cardFront.find('.sex').text('')
cardFront.find('.nation').text('')
cardFront.find('.year').text('')
cardFront.find('.month').text('')
cardFront.find('.date').text('')
cardFront.find('.address').text('')
cardFront.find('.number').text('')
cardBack.find('.department').text('')
cardBack.find('.expiry').text('')
cardBack.find('.pass-number').text('')
cardFront.find('.image').attr('src', '')
}
function WS_GetASN() {
const szJson = '{"Cmd":30400,"Head":"YZWL","IPFlag":"YWYyNWMxOWQ1ZTY4ZmJhOQ==","UserParam":"","Version":"V1.0.0"}\n'
ws.send(szJson)
}
function WS_GetBCardNo() {
const szJson = '{"Cmd":20400,"Head":"YZWL","IPFlag":"YWYyNWMxOWQ1ZTY4ZmJhOQ==","UserParam":"","Version":"V1.0.0"}\n'
ws.send(szJson)
}
function WS_GetDeviceNo() {
const szJson = '{"Cmd":20510,"Head":"YZWL","IPFlag":"YWYyNWMxOWQ1ZTY4ZmJhOQ==","UserParam":"","Version":"V1.0.0"}\n'
ws.send(szJson)
}
function WS_ReadInfo() {
cleanMsg()
const szJson = '{"Cmd":10000,"Head":"YZWL","IPFlag":"MGEyZmU1NmY5ODZlZGMyNg==","UserParam":"eyJBcHBLZXkiOiI5OWZmYjJmOThhMjkwNzExMDdjN2EwOWFkMmM2ZDA5NiIsIkRlY29kZVBob3RvIjp0cnVlLCJGYWNlQ29tcGFyZSI6ZmFsc2UsIlBob3RvRm9ybWF0IjoxLCJTZXJ2ZXJJUCI6ImlkLnl6ZnV0dXJlLmNuIiwiU2VydmVyUG9ydCI6ODg0OH0NCg==","Version":"V1.0.0"}\n'
ws.send(szJson)
}
function Http_ReadInfo() {
cleanMsg()
const webUrl = 'http://' + connectAddress.val() + '/api/info'
$.ajax({
url: webUrl,
type: 'GET',
dataType: 'json',
success: function (result) {
processContent.text('web api接口:' + webUrl + ' 读取身份证信息成功')
const szparam = result
setDocumentInfo(szparam);
},
error: function (jqXHR, textStatus, errorThrown) {
processContent.text('web api接口:' + webUrl + ' 读取身份证失败,原因:' + hex2a(window.atob(errorThrown)))
}
})
}
function Http_GetASN() {
const webUrl = 'http://' + connectAddress.val() + '/api/asn'
$.ajax({
url: webUrl,
type: 'GET',
dataType: 'json',
success: function (result) {
const szparam = result
processContent.text('web api接口:' + webUrl + ' 读取成功。 A卡SN:' + szparam.SN)
},
error: function (jqXHR, textStatus, errorThrown) {
processContent.text('web api接口:' + webUrl + ' 读取A卡SN失败,原因:' + hex2a(window.atob(errorThrown)))
}
})
}
function Http_GetBCardNo() {
const webUrl = 'http://' + connectAddress.val() + '/api/bsn'
$.ajax({
url: webUrl,
type: 'GET',
dataType: 'json',
success: function (result) {
const szparam = result
processContent.text('web api接口:' + webUrl + ' 读取成功。 身份证卡片SN:' + szparam.SN)
},
error: function (jqXHR, textStatus, errorThrown) {
processContent.text('web api接口:' + webUrl + ' 读取身份证卡片SN失败,原因:' + hex2a(window.atob(errorThrown)))
}
})
}
function Http_GetDeviceNo() {
const webUrl = 'http://' + connectAddress.val() + '/api/devsn'
$.ajax({
url: webUrl,
type: 'GET',
dataType: 'json',
success: function (result) {
const szparam = result
processContent.text('web api接口:' + webUrl + ' 读取成功。 读卡器芯唯一号:' + szparam.SN)
},
error: function (jqXHR, textStatus, errorThrown) {
processContent.text('web api接口:' + webUrl + ' 读取读卡器芯唯一号失败,原因:' + hex2a(window.atob(errorThrown)))
}
})
}
function parseDateString(str, deco, zero) {
let year = str.substr(0,4)
let month = str.substr(4,2)
let date = str.substr(6)
if(zero) {
month = month.substr(0,1) === "0" ? month.substr(1) : month
date = date.substr(0,1) === "0" ? date.substr(1) : date
}
return `${year}${deco}${month}${deco}${date}`
}
</script>
<script>
function cyberwin_仙盟创梦_初始化本体(){
var btnOnWS = $('#on-websocket')
var btnOffWS = $('#off-websocket')
var btnWS_ID = $('#websocket-ID')
var btnWS_IDSN = $('#websocket-ID-sn')
var btnWS_ASN = $('#websocket-A-sn')
var btnWS_DeviceNo = $('#websocket-device-No')
var btnHttp_ID = $('#http-ID')
var btnHttp_IDSN = $('#http-ID-sn')
var btnHttp_ASN = $('#http-A-sn')
var btnHttp_DeviceNo = $('#http-device-No')
var connectAddress = $('#connect-address')
var processContent = $('#process-content')
var btnCleanProcess = $('#clean-process')
var cardFront = $('#card-front')
var cardBack = $('#card-back')
var SNContent = $('#SN-content')
var image = $('#image')
var name = $('#name')
var sex = $('#sex')
var nation = $('#nation')
var year = $('#year')
var month = $('#month')
var date = $('#date')
var address = $('#address')
var number = $('#number')
var department = $('#department')
var expiry = $('#expiry')
btnOnWS.on('click', conWS)
btnOffWS.on('click', disconWS)
btnWS_ID.on('click', WS_ReadInfo)
btnWS_IDSN.on('click', WS_GetBCardNo)
btnWS_ASN.on('click', WS_GetASN)
btnWS_DeviceNo.on('click', WS_GetDeviceNo)
btnHttp_ID.on('click', Http_ReadInfo)
btnHttp_IDSN.on('click', Http_GetBCardNo)
btnHttp_ASN.on('click', Http_GetASN)
btnHttp_DeviceNo.on('click', Http_GetDeviceNo)
btnCleanProcess.on('click', cleanMsg)
processContent.text('本demo支持websocket和webapi两种网页调用方式')
}
</script>
未来之窗 - 鱼住未来身份识别服务 Demo 技术解析与应用场景
一、Demo 核心功能概述
该 Demo 展示了鱼住未来身份识别服务的两种核心调用方式,通过 Websocket 和 Web API 实现对身份证、A 卡等证件信息的读取与解析,主要功能包括:
- 双协议支持:同时兼容 WebSocket 长连接与 HTTP 短连接两种通信协议
- 多证件类型识别:支持身份证、港澳台居民居住证、新旧版外国人永久居留证等多种证件
- 信息可视化展示:将读取的证件信息(姓名、性别、地址等)结构化展示在页面中
- 主动 / 被动交互模式:WebSocket 支持读卡器刷卡自动推送数据,也可主动发送请求
二、技术架构与实现细节
1. 页面布局与样式设计
采用 Flex 弹性布局实现响应式界面,主要分为三大功能区块:
- 功能操作区:提供连接配置、协议选择(WebSocket/HTTP)、功能按钮
- 过程日志区:实时显示通信过程与错误信息
- 结果展示区:以卡片形式可视化证件信息,支持正反面切换
核心 CSS 布局类说明:
css
.flex { display: flex; } /* 基础弹性布局 */
.flex-direction { flex-direction: column; } /* 垂直排列 */
.justify-between { justify-content: space-between; } /* 两端对齐 */
.align-center { align-items: center; } /* 垂直居中 */
.basis-df { flex-basis: 50%; } /* 占据50%宽度 */
2. WebSocket 通信实现
通过new WebSocket()
创建长连接,核心事件处理:
- onopen:连接成功后显示操作指引
- onmessage:解析二进制数据并转换为 JSON 格式
- onclose:断开连接时提示状态
javascript
// WebSocket连接函数
function conWS() {
const webUrl = 'ws://' + connectAddress.val() + '/ws';
ws = new WebSocket(webUrl);
ws.onmessage = function(messageEvent) {
const jsonobject = JSON.parse(messageEvent.data);
if (jsonobject.Ret == 0) {
// 成功处理逻辑,调用证件解析函数
const szparam = JSON.parse(window.atob(jsonobject.UserParam));
setDocumentInfo(szparam);
}
}
}
3. Web API 接口调用
基于 jQuery.ajax 实现 HTTP 请求,支持 GET 方式获取数据:
javascript
// Web API读取身份证信息
function Http_ReadInfo() {
const webUrl = 'http://' + connectAddress.val() + '/api/info';
$.ajax({
url: webUrl,
success: function(result) {
const szparam = result;
setDocumentInfo(szparam); // 统一使用证件解析函数
}
});
}
4. 证件信息解析处理
通过hex2a
函数解码二进制数据,根据CardType
字段区分证件类型:
- 74:港澳台居民居住证
- 73:旧版外国人永久居留证
- 89:新版外国人永久居留证
- 其他:中国大陆身份证
javascript
// 核心解析函数
function setDocumentInfo(szparam) {
if (szparam.CardType == 74) {
// 港澳台证件样式处理
cardFront.addClass('card-hongkong-macao-taiwan-front');
// 填充姓名、性别等信息
cardFront.find('.name').text(hex2a(window.atob(szparam.CardInfo.Name)));
}
// 其他证件类型处理逻辑...
}
三、核心技术亮点
双协议兼容性设计:
- WebSocket 适合需要实时推送的场景(如读卡器刷卡自动通知)
- Web API 适合短连接请求(如单次信息查询)
多证件类型适配:
- 通过
CardType
字段动态切换界面样式 - 统一解析函数处理不同证件数据结构
- 通过
数据可视化展示:
- 模拟真实证件布局,区分正反面显示
- 照片以 Base64 格式直接渲染
四、应用场景与扩展方向
典型应用场景:
- 酒店入住登记系统:通过读卡器快速读取身份证信息
- 机场安检身份核验:实时获取证件信息与数据库比对
- 政务服务自助终端:支持多类型证件办理业务
- 企业访客管理系统:非接触式读取访客证件
技术扩展方向:
- 增加人脸识别功能,实现人证比对
- 集成 OCR 技术,支持纸质证件扫描识别
- 加入加密传输模块,提升数据安全性
- 开发移动端 SDK,支持 Android/iOS 设备
五、部署与使用说明
环境要求:
- 浏览器支持 WebSocket(现代浏览器均支持)
- 后端服务地址配置(默认
127.0.0.1:30004
) - 读卡器硬件连接正常
操作流程:
- 配置服务地址
- 选择协议(WebSocket 需先连接)
- 点击对应按钮读取证件信息
- 查看右侧结果展示区
该 Demo 通过简洁的界面与清晰的代码结构,展示了身份识别服务在 Web 端的完整实现方案,为集成各类证件读取功能提供了可复用的技术框架。