针对用友ic读卡器 web 脚本的重构

189 阅读6分钟

针对用友ic读卡器 web 脚本的重构


import { CallbackToPromiseTask } from './callbacToPromiseTask'
import asyncFormat from 'left-f-tools/lib/asyncFormat'
import Enum from 'left-f-tools/lib/Enum'


// API 名称及代码
const functionIDEnum = new Enum([

  [0,  'RequestTypeACardNo', { label: '查询A卡卡号' }],
  [1,  'RequestTypeBCardNo', { label: '查询B卡卡号' }],
  [2,  'Request15693CardUID', { label: '查询15693卡卡号' }],
  [3,  'RequestChinaIDCardNo', { label: '查询身份证卡号, 非证件号' }],
  [4,  'RequestCardNo', { label: '查询卡号' }],
  [5,  'M1ReadBlock', { label: 'M1读卡' }],
  [6,  'M1WriteBlock', { label: 'M1写卡' }],
  [7,  'M1ReadSector', { label: 'M1读取扇区' }],
  [8,  'M1WriteSector', { label: 'M1写入扇区' }],
  [9,  'M1IntialValue', { label: 'M1初始化钱包' }],
  [10, 'M1GetValue', { label: 'M1获取金额' }],
  [11, 'M1IncreaseValue', { label: 'M1充值' }],
  [12, 'M1DecreaseValue', { label: 'M1消费' }],
  [13, 'Beep', { label: '蜂鸣次数' }],
  [14, 'Ver', { label: '版本' }],
  [15, 'Tick', { label: '' }],
  [16, 'LED', { label: '闪烁次数' }],
  [17, 'typeacpureset', { label: '' }],
  // [18, '', { label: '' }],
  [19, 'CPUCos', { label: 'cpu 执行cos' }],
  [20, 'SAMReset', { label: 'SAM 重置' }],
  [21, 'SAMCos', { label: 'SAM 执行cos' }],
  [22, 'DES', { label: 'DES 加密' }],
  [23, 'G2_Inventory', { label: 'G2 存储' }],
  [24, 'G2_Read', { label: 'G2 读卡' }],
  [25, 'G2_Write', { label: 'G2 写卡' }],
  [26, 'G2_WriteEPC', { label: 'G2 写 EPC' }],
  [27, 'G2_KillTag', { label: 'G2 删除标签' }],
  [28, 'G2_SetProtected', { label: 'G2 上锁' }],
  [29, 'G2_Earse', { label: '' }],
  [30, 'G2_SetReadProtected', { label: 'G2 读取上锁' }],
  [31, 'G2_SetUnlockReadProtected', { label: 'G2 读取解锁' }],
  [32, 'G2_SetEASAlert', { label: '' }],
  [33, 'G2_LockUser', { label: 'G2 用户上锁' }],
  [34, 'SinoPecCard_GetInfo', { label: '加油卡 获取信息' }],
  [35, 'SinoPecCard_GetBalance', { label: '加油卡 获取余额' }],
  [36, 'SinoPecCard_GetRecord', { label: '加油卡 获取纪律' }],
  [37, 'ISO15693ReadBlock', { label: 'ISO15693 读块' }],
  [38, 'ISO15693WriteBlock', { label: 'ISO15693 写块' }],
  [39, 'ISO15693LockBlock', { label: 'ISO15693 锁块' }],
  [40, 'ISO15693WriteAFI', { label: 'ISO15693 设置领域标识' }],
  [41, 'ISO15693LockAFI', { label: 'ISO15693 锁定领域标识' }],
  [42, 'ISO15693WriteDSFID', { label: 'ISO15693 写入 DSFID' }],
  [43, 'ISO15693LockDSFID', { label: 'ISO15693 锁定 DSFID' }],
  [44, 'ISO15693GetInformation', { label: 'ISO15693 获取卡信息' }],
  [45, 'DownKey', { label: '下载密钥' }],
  [46, 'NTAG_Auth', { label: 'NTAG 获取权限' }],
  [47, 'NTAG_Read', { label: 'NTAG 读卡' }],
  [48, 'NTAG_Write', { label: 'NTAG 写卡' }],
  [49, 'NTAG_Counter', { label: '' }],
  [50, 'NDEF_AddRecord_Sign', { label: 'NDEF 添加记录' }],
  [51, 'NDEF_AddRecord', { label: 'NDEF 添加记录' }],
  [52, 'NDEF_ClearAllRecords', { label: 'NDEF 清除记录' }],
  [53, 'NDEF_Write', { label: 'NDEF 读卡' }],
  [54, 'NDEF_Read', { label: 'NDEF 写卡' }],
  [55, 'EY5K_RquestCardNo', { label: 'EY5K 读取卡号' }],
  [56, 'EY5K_GetPublicInfo', { label: 'EY5K 获取公开信息' }],
  [57, 'EY5K_SetPublicInfo', { label: 'EY5K 设置公开信息' }],
  [58, 'EY5K_GetHolderInfo', { label: 'EY5K 获取用户信息' }],
  [59, 'EY5K_SetHolderInfo', { label: 'EY5K 设置用户信息' }],
  [60, 'EY5K_ReadUserFile', { label: 'EY5K 读取用户文件' }],
  [61, 'EY5K_WriteUserFile', { label: 'EY5K 写入用户文件' }],
  [62, 'EY5K_GetValue', { label: '' }],
  [63, 'EY5K_IncreaseValue', { label: '' }],
  [64, 'EY5K_DecreaseValue', { label: '' }],
  [65, 'EY5K_GetRecords', { label: '' }],
  [66, 'EY5K_ChangeKey', { label: '' }],
  [67, 'GetTeminateCode', { label: '获取终止码' }],
  [68, 'ShowImage', { label: '设置展示图片' }],
  [69, 'ShowText', { label: '设置展示文本' }],
  
])

// 错误代码
const errCodeEnum = new Enum([

  [ -1, "没有找到IC卡读卡器,支持型号:YW-605HA, YW-607,YW-627" ],
  [ -3, "寻卡失败" ],
  [ -4, "寻卡失败" ],
  [ -5, "卡休眠失败" ],
  [ -6, "密钥认证失败" ],
  [ -7, "读取失败" ],
  [ -8, "写入失败" ],
  [ -9, "钱包初始化失败" ],
  [ -10, "钱包读余额失败" ],
  [ -11, "钱包充值失败" ],
  [ -12, "钱包减值失败" ],
  [ -13, "复位错误" ],
  [ -14, "COS执行错误" ],
  [ -101, "参数错误" ],
  [ -102, "DES校验错误" ],
  [ -103, "读卡器不支持" ],
  [ -600, "没找到YW-602系列UHF读卡器" ],
  [ -601, "寻G2标签失败" ],
  [ -602, "读G2标签失败" ],
  [ -603, "写G2标签失败" ],
  [ -603, "执行失败" ],
  
])

// 错误类型
const errTypeEnum = new Enum([
  [ 'socket', 'socket错误' ],
  [ 'server', '服务错误' ],
])




/**
 * 对用友 M1卡读卡器js的重构
 * 提供两种监听模式,callback | promise
 * 
 * 
 * -------------------------------------
 * @callback_模式
 * 
 * - 监听设置函数与websorct API 对应关系
 * 
 *   setonOpen --> websorct.onopen
 *   setonClose --> websorct.onclose
 *   setonError --> websorct.onerror
 *   setonMessage --> websorct.onmessage
 * 
 * - 例子
 *   const c = new CardReader
 *   c.setonOpen(() => console.log('连接成功'))
 *   c.setonError(() => console.log('报错提示'))
 *   c.connect()
 * 
 * 
 * -------------------------------------
 * @promise_模式
 * 
 * - 例子
 *   通过追加 then 或 catch 可获取连接是否成功
 *   const c = new CardReader
 *   c.connect().then(() => console.log('连接成功')).catch(err => console.log('连接失败'))
 * 
 * 
 * -------------------------------------
 * @method
 * 
 * - setProps  设置参数
 * - connect 连接插件服务
 * - disconnect 关闭连接
 * - send 发送消息
 * 
 * 
 * -------------------------------------
 * @example
 * 
 * (
      async function(){
          const c = new CardReaderAPI()

          let [ res, err ] = await asyncFormat(c.connect())

          if(err){ console.log('fail', data); return }
          console.log(res)
          
          // 获取A卡号
          const [ resNo1, errNo1 ] = await asyncFormat(c.send(0,1,0))
          console.log('>>>>', resNo1, errNo1)

      }()
    )

 * 
 */
export class CardReader{

  constructor(){

    this.taskState = CallbackToPromiseTask.create()
    this.taskState.clear()

    this._onMessage = () => {},
    this._onError = () => {}
    this._onOpen = () =>{}
    this._onClose = () => {}
    
    this.state = {
      
      // 插件服务地址
      serverURL: "ws://127.0.0.1:8009/YOWORFIDReader",
      // 版本
      version: '',
      // 读卡器ID
      readerID: 1,
      // 自定义标识
      UID: 0,
      // 寻卡模式  0: 所有卡  1: 已激活
      requestActive: 1,
      // 密钥类型   0: A秘钥  1: B秘钥
      keyMode: 0,
      // 密钥字符串
      keyString: 'FFFFFFFFFFFF',
      // 密钥字符串的格式  0: 16进制  1: 字符串  2: 使用下载的密钥
      keyStringMode: 0,
      // 重复执行标志  0: 只执行一次   1: 重复执行 
      repeat: 0,
      // 调用成功 蜂鸣器Beep次数
      beepOnSuccess: 1,
      // 调用失败 蜂鸣器Beep次数
      beepOnFail: 0,
      // 否将卡休眠  0: 不休眠  1: 休眠
      haltAfterSuccess: 0,
      // 加解密原数据处理方式  0: 原数据  1: 对原数据取反  2: 原数据+原数据取反
      desMode: 0,
      // 数据加密方向  0: 不加密  1: 加密  2: 解密
      desDir: 0,
      // DES密钥
      desKey: '',
      // DesKey密钥的格式  0: 16进制字符串  1: 普通字符串
      desKeyMode: 0,

      // websocet 对象
      _wx: null,
      // 是否已连接
      _socketOpen: false,
      // 参数拼接符
      _splitChar: String.fromCharCode(65530),
      
    }
    
  }
  
  setonMessage(callback){
    if( typeof callback !== 'function'){ throw '监听方法非可执行函数' }
    this._onMessage = callback
  }

  setonError(callback){
    if( typeof callback !== 'function'){ throw '监听方法非可执行函数' }
    this._onError = callback
  }

  setonOpen(callback){
    if( typeof callback !== 'function'){ throw '监听方法非可执行函数' }
    this._onOpen = callback
  }

  setonClose(callback){
    if( typeof callback !== 'function'){ throw '监听方法非可执行函数' }
    this._onClose = callback
  }


  setProps(data={}){
    this.state = Object.assign({}, this.state, data)
  }
  
  

  _WSonOpen(){
    this._onOpen()
    this.setProps({ _socketOpen:true })
    const task = this.taskState.shift()
    task.resolve({ message: '连接成功' })
  }


  _WSonError(err){
    const taks = this.taskState.shift()
    const errorMsg = { message: err, type: errTypeEnum.get('socket') }
    taks.reject(errorMsg)
    this._onError(errorMsg)
  }


  _WSonClose(err){
    this._onClose()
    this.setProps({ _socketOpen:false })
    const taks = this.taskState.shift()
    taks.reject({message: '关闭服务'})
  }
  

  _WSonMessage(e){

    const [ functionID, result,  UID, readerID, cardNo,  strData, valData ] = e.data.split(this.state._splitChar)
    
    const resultData = {

      functionID: parseInt(functionID),
      result: parseInt(result),
      UID: parseInt(UID),
      readerID: parseInt(readerID),
      cardNo,
      strData,
      valData: parseInt(valData)
      
    }
    
    // 设置版本
    if(functionID === functionIDEnum.get('Ver')){
      _this.setProps({ version: strData })
    }
  
    
    // promise task 分发
    if(this.taskState.hasByKey(resultData.UID)){
      
      const task = this.taskState.shift(resultData.UID)
      if(resultData.result < 0){
        const errorMsg = { message: errCodeEnum.get(resultData.result), type: errTypeEnum.get('server') }
        task.reject(errorMsg)
      }else{
        task.resolve(errorMsg)
      }

    }

    
    // 回调 task
    if(resultData.result < 0){
      const errorMsg = { message: errCodeEnum.get(resultData.result), type: errTypeEnum.get('server') }
      this._onError(errorMsg)
    }else{
      this._onMessage(resultData)
    }

  }

  
  _joinBySplitChar(strList){
      return strList.join(this.state._splitChar)
  }
 
  _paramsToStr(FunctionID, paramsStr=[]){

      const {
         
        readerID,
        UID,
        requestActive,
        keyMode,
        keyString,
        keyStringMode,
        repeat,
        beepOnSuccess,
        beepOnFail,
        haltAfterSuccess,
        desDir,
        desMode,
        desKey,
        desKeyMode,

      } = this.state

      const dataStr = this._joinBySplitChar([

        readerID,
        UID,
        requestActive,
        keyMode,
        keyString,
        keyStringMode,
        repeat,
        beepOnSuccess,
        beepOnFail,
        haltAfterSuccess,
        desDir,
        desMode,
        desKey,
        desKeyMode,
        FunctionID,
        ...paramsStr

      ])
    
      const funcName = functionIDEnum.get(FunctionID) 
      return `${funcName}:${dataStr}`
  }

  async connect(){

    const [ promiseWait ] = this.taskState.push('CONNECT')

    try {

      let socket
      
      if("WebSocket" in window){
        socket = WebSocket
      }
      
      if("MozWebSocket" in window){
        socket = MozWebSocket
      }

      if(!socket){
        throw 'not supert websocket !!'
      }

      this._wx = new WebSocket(this.state.serverURL)

      // _wx.callback this 指向 _wx
      this._wx.onopen = this._WSonOpen.bind(this) 
      this._wx.onmessage = this._WSonMessage.bind(this) 
      this._wx.onclose = this._WSonClose.bind(this) 
      this._wx.onerror = this._WSonError.bind(this)

      return promiseWait

      
    } catch (error) {
      return Promise.reject(error)
    }


  }

  send(functionID, ...args){

      const [ promiseWait, _key ] = this.taskState.push()
      this.setProps({ UID: _key })
      this._wx.send(this._paramsToStr(functionID, [...args]))
      
      return promiseWait
  }

  
  disconnect(){
    wx&&wx.close()
  }
  
  
}







/**
 * 针对用友通信方法的封装
 * 方法的具体参数参看 http://www.youwokeji.com.cn/CloudReader
 * -------------------------------------
 * @example
 * 
 * 
    (
      async function(){
          const c = new CardReaderAPI()

          let [ res, err ] = await asyncFormat(c.connect())

          if(err){ console.log('fail', data); return }
          console.log(res)
          
          const [ resNo1, errNo1 ] = await asyncFormat(c.M1ReadBlock(24,1))
          console.log('>>>>', resNo1, errNo1)

          const [ resNo2, errNo2 ] = await asyncFormat(c.M1ReadBlock(24,1))
          console.log('>>>>', resNo2, errNo2)


      }()
    )
 * 
 * 
 */
export class CardReaderAPI extends CardReader {

    constructor(...args){
      super(...args)
    }

    
    RequestTypeACardNo(FormatID, OrderID){  return this.send(0,FormatID, OrderID) }
    
    RequestTypeBCardNo(){  return this.send(1) }
    
    Request15693CardUID(){  return this.send(2) }
    
    RequestChinaIDCardNo(){  return this.send(3) }

    RequestCardNo(){  return this.send(4) }
    
    M1ReadBlock(blockIndex, FormatID){  return this.send(5, blockIndex, FormatID) }

    M1WriteBlock(blockindex, blockdata, FormatID){  return this.send(6,blockindex, blockdata, FormatID) }

    M1ReadSector(sectorindex, FormatID){  return this.send(7, sectorindex, FormatID) }

    M1WriteSector(blockindex, blockdata, FormatID){  return this.send(8, blockindex, blockdata, FormatID) }

    M1IntialValue(blockIndex, value){  return this.send(9, blockIndex, value) }

    M1GetValue(blockIndex){  return this.send(10, blockIndex) }

    M1IncreaseValue(blockIndex, value){  return this.send(11, blockIndex, value) }

    M1DecreaseValue(blockIndex, value){  return this.send(12, blockIndex, value) }

    Beep(blockIndex, value){  return this.send(13, blockIndex, value) }

    Ver(){  return this.send(14) }

    Tick(){  return this.send(15) }

    LED(LedIndx, TimeOn, TimeOff, Times, LEDOnIndex){  return this.send(16, LedIndx, TimeOn, TimeOff, Times, LEDOnIndex) }

    ACPUReset(){  return this.send(17) }

    // (){  return this.send(18) }

    CPUCOS(value){  return this.send(19, value) }

    SAMReset(Index){  return this.send(20, Index) }

    SAMCOS(Index, value){  return this.send(21, Index, value) }

    Des(Data, DataForamt){  return this.send(22, Data, DataForamt) }

    G2_Inventory(isEPC){  return this.send(23, isEPC) }

    G2_Read(memType, StartPos, ReadLength, FormatID){  return this.send(24, memType, StartPos, ReadLength, FormatID) }

    G2_Write(memType, StartPos, DataForamtID, Data){  return this.send(25, memType, StartPos, DataForamtID, Data) }

    G2_WriteEPC(EPCData){  return this.send(26,EPCData) }

    G2_KillTag(G2_KillTag){  return this.send(27, G2_KillTag) }

    G2_SetProtected(ProtectByte, ProtectMode){  return this.send(28, ProtectByte, ProtectMode) }
    
    G2_Earse(memType, StartPos, EarseLength){  return this.send(29, memType, StartPos, EarseLength) }

    G2_SetReadProtected(){  return this.send(30) }

    G2_SetUnlockReadProtected(){  return this.send(31) }
    
    G2_SetEASAlert(EAS){  return this.send(32, EAS) }

    G2_LockUser(UserAddr){  return this.send(33, UserAddr) }

    SinoPecCard_GetInfo(){  return this.send(34) }

    SinoPecCard_GetBalance(PIN){  return this.send(35, PIN) }

    SinoPecCard_GetRecord(PIN, RecordID){  return this.send(36, PIN, RecordID) }

    ISO15693ReadBlock(StartBlock, BlockNums, DataFormat){  return this.send(37, StartBlock, BlockNums, DataFormat) }

    ISO15693WriteBlock(Block, Data, DataFormat){  return this.send(38, Block, Data, DataFormat) }

    ISO15693LockBlock(Block){  return this.send(39, Block) }

    ISO15693WriteAFI(AFI){  return this.send(40, AFI) }

    ISO15693LockAFI(){  return this.send(41) }

    RequestChinaIDCardNo(DSFID){  return this.send(42, DSFID) }

    ISO15693LockDSFID(){  return this.send(43) }

    ISO15693GetInformation(){  return this.send(44) }

    DownKey(KeyIndex, KeyString){  return this.send(45, KeyIndex, KeyString) }

    NTAG_Auth(){  return this.send(46) }

    NTAG_Read(StartBlock, BlockNums, FormatID){  return this.send(47, StartBlock, BlockNums, FormatID) }

    NTAG_Write(StartBlock, BlockNums, Data, FormatID){  return this.send(48, StartBlock, BlockNums, Data, FormatID) }

    NTAG_Counter(){  return this.send(49) }

    NTAG_Sign(){  return this.send(50) }

    NDEF_AddRecord(Uri, Payload){  return this.send(51, Uri, Payload) }

    NDEF_ClearAllRecords(){  return this.send(52) }

    NDEF_Write(){  return this.send(53) }

    NDEF_Read(){  return this.send(54) }

    EY5K_RquestCardNo(){  return this.send(55) }

    EY5K_GetPublicInfo(){  return this.send(56) }

    EY5K_SetPublicInfo(SamIndex, PubStr){  return this.send(57, SamIndex, PubStr) }

    EY5K_GetHolderInfo(SamIndex){  return this.send(58, SamIndex) }

    EY5K_SetHolderInfo(SamIndex, HolderStr){  return this.send(59,SamIndex, HolderStr) }

    EY5K_ReadUserFile(SamIndex, Addr, Count, Format){  return this.send(60, SamIndex, Addr, Count, Format) }

    EY5K_WriteUserFile(SamIndex, Addr, DataStr, Format){  return this.send(61, SamIndex, Addr, DataStr, Format) }

    EY5K_GetValue(){  return this.send(62) }

    EY5K_IncreaseValue(SamIndex, Value){  return this.send(63,SamIndex, Value) }
    
    EY5K_DecreaseValue(SamIndex, Value){  return this.send(64,SamIndex, Value) }

    EY5K_GetRecords(){  return this.send(65) }

    EY5K_ChangeKey(OldKey, NewKey){  return this.send(66,OldKey, NewKey) }

    GetTeminateCode(SamIndex){  return this.send(67,SamIndex) }

    ShowImage(Script){  return this.send(68,Script) }

    ShowText(Txt){  return this.send(69, Txt) }
    
    
}


(
  async function(){
      const c = new CardReaderAPI()

      let [ res, err ] = await asyncFormat(c.connect())

      if(err){ console.log('fail', data); return }
      console.log(res)
      
      const [ resNo1, errNo1 ] = await asyncFormat(c.M1ReadBlock(24,1))
      console.log('>>>>', resNo1, errNo1)

      const [ resNo2, errNo2 ] = await asyncFormat(c.M1ReadBlock(24,1))
      console.log('>>>>', resNo2, errNo2)


  }()
)