html读取身份证【成都鱼住未来身份证】:CyberWinApp-SAAS 本地化及未来之窗行业应用跨平台架构

发布于:2025-06-18 ⋅ 阅读:(18) ⋅ 点赞:(0)

 代码

<!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)));
  } 
  // 其他证件类型处理逻辑...
}
三、核心技术亮点
  1. 双协议兼容性设计

    • WebSocket 适合需要实时推送的场景(如读卡器刷卡自动通知)
    • Web API 适合短连接请求(如单次信息查询)
  2. 多证件类型适配

    • 通过CardType字段动态切换界面样式
    • 统一解析函数处理不同证件数据结构
  3. 数据可视化展示

    • 模拟真实证件布局,区分正反面显示
    • 照片以 Base64 格式直接渲染
四、应用场景与扩展方向
典型应用场景:
  • 酒店入住登记系统:通过读卡器快速读取身份证信息
  • 机场安检身份核验:实时获取证件信息与数据库比对
  • 政务服务自助终端:支持多类型证件办理业务
  • 企业访客管理系统:非接触式读取访客证件
技术扩展方向:
  1. 增加人脸识别功能,实现人证比对
  2. 集成 OCR 技术,支持纸质证件扫描识别
  3. 加入加密传输模块,提升数据安全性
  4. 开发移动端 SDK,支持 Android/iOS 设备
五、部署与使用说明
  1. 环境要求

    • 浏览器支持 WebSocket(现代浏览器均支持)
    • 后端服务地址配置(默认127.0.0.1:30004
    • 读卡器硬件连接正常
  2. 操作流程

    • 配置服务地址
    • 选择协议(WebSocket 需先连接)
    • 点击对应按钮读取证件信息
    • 查看右侧结果展示区

该 Demo 通过简洁的界面与清晰的代码结构,展示了身份识别服务在 Web 端的完整实现方案,为集成各类证件读取功能提供了可复用的技术框架。


网站公告

今日签到

点亮在社区的每一天
去签到