分享一个业务中采用 ukey 登录的代码片段

前言:项目中需要使用到 ukey 更新,业务代码都写好了,后面告知不需要太复杂,要删除轮巡监听 ukey 通信启动程序 的步骤,token 刷新机制

这是控制 SoftUkey 程序创建的类

  • 内部调用 SoftKey3W 的方法获取ukey的设备信息
  • 使用 websocket 与厂家提供的软件程序通信(独立程序.exe)
  • websocket 断连重试
  • 每隔10分钟自动调用一次刷新机制
ukey调用方法 调用类
FindPort SoftKey3W
GetID_1 SoftKey3W
GetID_2 SoftKey3W
YReadEx SoftKey3W
YReadString SoftKey3W
GetLastError SoftKey3W
EncString SoftKey3W
StrDec SoftKey

调用类的方法按自己需求去模拟。也可以不去调用注释即可

舍不得删除代码,分析此片段代码。

javascript 复制代码
import Emitter from 'tiny-emitter';
import { setToken, removeToken } from '@/utils/auth';
import { SoftKey, SoftKey3W, sprint } from '@/lib/ukey';
import { Message } from 'element-ui';
import dayjs from 'dayjs';

/*
    export function sprint(func, t = 1000, maxStep = 100, leading = false) {
      const stamp = Date.now();
      let raf;
      let counter = 0;
      let old_counter = 0;

      leading && func && func instanceof Function && func(counter);

      (function loop() {
        if (counter !== old_counter) {
          func && func instanceof Function && func(counter);
          old_counter = counter;
        }
        counter = Math.floor((Date.now() - stamp) / t);
        if (counter >= maxStep) return;
        raf = window.requestAnimationFrame(loop);
      })();

      function cancalAnimation() {
        cancelAnimationFrame(raf);
      }
      return cancalAnimation;
    }
*/

function GetRandomNum(Min, Max) {
  var Range = Max - Min;
  var Rand = Math.random();
  return (Min + Math.round(Rand * Range)).toString();
}

const MAX_COUNT = 60;
const RETRY_DELAY = 2000;
const RETRY = true;
const REFRESH_TOKEN = 1000 * 60 * 10;
class MySocket extends Emitter {
  ws = null;

  #retry_count = MAX_COUNT;
  #connected_state = null;
  #timer = null;
  #once_disconnected = false;
  #protocol = document.URL.startsWith('https') ? 'wss://' : 'ws://';

  constructor() {
    super();
    this.#self_connected();
  }

  #self_connected() {
    this.#self_disconnected();

    const url = `${this.#protocol}127.0.0.1:4006/xxx`;
    const WindowWebSocket = typeof MozWebSocket !== 'undefined' ? MozWebSocket : WebSocket;

    const ws = (this.ws = new WindowWebSocket(url, 'usbkey-protocol'));

    ws.onopen = this.#onopen.bind(this);
    ws.onclose = this.#onclose.bind(this);
    ws.onerror = this.#onerror.bind(this);
    ws.onmessage = this.#onmessage.bind(this);
  }

  #self_disconnected() {
    if (!this.ws) return;

    this.ws.close();
    this.ws.onopen = null;
    this.ws.onmessage = null;
    this.ws.onerror = null;
    this.ws.onclose = null;
    this.ws = null;
  }

  #onopen(_event) {
    clearTimeout(this.#timer);
    this.#connected_state = 'connected';
    this.#retry_count = MAX_COUNT;
    this.emit('connected');
  }

  #onclose(_event) {
    const retry = () => {
      if (this.#retry_count <= 0 || this.#connected_state === 'connected') {
        clearTimeout(this.#timer);
        return;
      }

      this.#retry_count--;
      this.#self_connected();
    };

    this.#connected_state = 'disconnected';

    if (RETRY) {
      this.#timer = setTimeout(retry, RETRY_DELAY);
    }
  }

  #onerror(_event) {
    if (!this.#once_disconnected) {
      // alert('未能连接服务程序,请确定服务程序是否安装。');
      this.#once_disconnected = true;
    }

    this.emit('error');
  }

  #onmessage(_event) {
    const data = MySocket.parseJsonString(_event.data);

    if (data.type === 'PnpEvent') {
      if (data.IsIn) {
        MySocket.toast({
          type: 'warning',
          duration: 3500,
          message: `UKEY已插入,被插入的锁的路径是:${data.DevicePath}`,
        });
        this.emit('deviceIn');
      } else {
        MySocket.toast({
          type: 'error',
          duration: 6000,
          message: `UKEY已被拨出,被拨出的锁的路径是:${data.DevicePath}`,
        });
        this.emit('deviceOut');
      }
    }
  }

  static parseJsonString(str) {
    try {
      return JSON.parse(str);
    } catch (e) {
      return {};
    }
  }

  static toast(...args) {
    Message(...args);
  }
}

class UkeyDevice extends MySocket {
  static digitArray = '0123456789abcdef'.split('');

  mSoftKey3A = null;
  mSoftKey = null;

  chipId = null;
  ukeyID = null;
  password = null;
  username = null;
  devicePath = null;

  constructor() {
    super();
    this.mSoftKey = new SoftKey();
    this.mSoftKey3A = new SoftKey3W();

    this.on('connected', this.initialize.bind(this, 'connected'));
  }

  initialize() {
    this.#transmit();
    this.#ready();
  }

  #transmit() {
    this.mSoftKey3A.Socket_UK = this.ws;
  }

  async #ready() {
    this.devicePath = await this.#findPort();
    this.ukeyID = await this.#getUkeyId();
    this.username = await this.#getUsername();
    this.password = await this.#getPassword();
    this.emit('ready');
  }

  async #findPort() {
    const { mSoftKey3A } = this;

    let devicePath = await mSoftKey3A.FindPort(1);

    if (mSoftKey3A.GetLastError() === 0) {
      this.#throwError('系统上发现有2把及以上的加密锁,请只插入要进行的加密锁。');
      return;
    }

    devicePath = await mSoftKey3A.FindPort(0);

    if (mSoftKey3A.GetLastError() !== 0) {
      this.#throwError('未发现加密锁,请插入加密锁');
      return;
    }

    return devicePath;
  }

  async #getUkeyId() {
    const { mSoftKey3A, devicePath } = this;

    let ukeyID = (this.chipId = await mSoftKey3A.GetChipID(devicePath));

    if (mSoftKey3A.GetLastError() !== 0) {
      const aId = await mSoftKey3A.GetID_1(devicePath);
      const bId = await mSoftKey3A.GetID_2(devicePath);
      ukeyID = UkeyDevice.toHex(aId) + UkeyDevice.toHex(bId);

      if (mSoftKey3A.GetLastError() !== 0) {
        this.#throwError(`获取ID错误,错误码是 ${mSoftKey3A.GetLastError().toString()}`);
        return;
      }
    }

    return ukeyID;
  }

  async #getUsername() {
    const { mSoftKey3A, devicePath } = this;

    const addr = 0;
    const outLenBuf = await mSoftKey3A.YReadEx(addr, 1, 'ffffffff', 'ffffffff', devicePath);
    const nlen = outLenBuf[0];

    if (mSoftKey3A.GetLastError() !== 0) {
      this.#throwError(`读取用户名长度时错误。错误码:${mSoftKey3A.GetLastError().toString()}`);
      return;
    }

    const username = await mSoftKey3A.YReadString(addr + 1, nlen, 'ffffffff', 'ffffffff', devicePath);

    if (mSoftKey3A.GetLastError() !== 0) {
      this.#throwError(`读取用户名时错误。错误码:${mSoftKey3A.GetLastError().toString()}`);
      return;
    }

    return username;
  }

  async #getPassword() {
    const { mSoftKey3A, devicePath } = this;

    const addr = 20;
    const outLenBuf = await mSoftKey3A.YReadEx(addr, 1, 'ffffffff', 'ffffffff', devicePath);
    const nlen = outLenBuf[0];

    if (mSoftKey3A.GetLastError() !== 0) {
      const ErrorCode = `读取用户密码长度时错误。错误码:${mSoftKey3A.GetLastError().toString()}`;
      window.alert(ErrorCode);
      throw new Error(ErrorCode);
    }

    const password = await mSoftKey3A.YReadString(addr + 1, nlen, 'ffffffff', 'ffffffff', devicePath);

    if (mSoftKey3A.GetLastError() !== 0) {
      const ErrorCode = `读取用户密码时错误。错误码:${mSoftKey3A.GetLastError().toString()}`;
      window.alert(ErrorCode);
      throw new Error(ErrorCode);
    }

    return password;
  }

  async encode(code) {
    const { mSoftKey3A, devicePath } = this;

    const encCode = await mSoftKey3A.EncString(code, devicePath);

    if (mSoftKey3A.GetLastError() !== 0) {
      this.#throwError(`进行加密运行算时错误,错误码为:${mSoftKey3A.GetLastError().toString()}`);
      return;
    }

    return encCode;
  }

  decode(code) {
    const { mSoftKey } = this;
    const decCode = mSoftKey.StrDec(code, this.key);

    if (!decCode) {
      this.#throwError(`进行解密运行算时错误,错误码为:"错误码为 null"`);
      return;
    }

    return new Promise(resolve => resolve(decCode));
  }

  reset() {
    Object.assign(this, {
      ukeyID: undefined,
      chipId: undefined,
      password: undefined,
      username: undefined,
      devicePath: undefined,
    });
  }

  #throwError(errorMessage) {
    window.alert(errorMessage);
    console.warn(errorMessage);
    // throw new Error(errorMessage);
  }

  static toHex(n) {
    let result = '';
    let start = true;

    for (var i = 32; i > 0; ) {
      i -= 4;
      let digit = (n >> i) & 0xf;

      if (!start || digit != 0) {
        start = false;
        result += UkeyDevice.digitArray[digit];
      }
    }

    return result === '' ? '0' : result;
  }
}

export class CreateUkeyDevice extends UkeyDevice {
  #timer = null;
  #now = Date.now();
  #duration = REFRESH_TOKEN;

  constructor(...args) {
    super(args);

    this.on('ready', this.#setup.bind(this));
    this.on('error', this.#stopSprint.bind(this));
    this.on('deviceIn', this.#ukeyDeviceIn.bind(this));
    this.on('deviceOut', this.#ukeyDeviceOut.bind(this));
  }

  #setup() {
    this.#timer = sprint(this.#authorization.bind(this), this.#duration, Infinity, true);
  }

  #stopSprint() {
    this.#timer instanceof Function && this.#timer();
    this.reset();
    this.#removeToken();
  }

  #ukeyDeviceIn() {
    console.log('设备已插入:', this);
    this.initialize();
  }

  #ukeyDeviceOut() {
    console.log('设备被拔出:', this);
    this.#timer instanceof Function && this.#timer();
    this.reset();
    this.#removeToken();
  }

  async #authorization() {
    const diff = dayjs(Date.now()).diff(this.#now, 'milliseconds');
    console.log('定时器执行中,距离上次执行间隔:', diff);
    this.#now = Date.now();
    const code = await this.#getServerCode();
    const token = await this.#getServerToken(code);
    console.log(token);
    this.#setToken(token);
  }

  async #getServerCode() {
    const { username } = this;
    username;
    const code = await new Promise(resolve => setTimeout(resolve.bind(this, GetRandomNum(1, 65535) + GetRandomNum(1, 65535)), 1000));
    return code;
  }

  async #getServerToken(code) {
    return new Promise(resolve =>
      setTimeout(() => {
        resolve(this.encode(code));
      }, 1000),
    );
  }

  #setToken(token) {
    setToken(token);
  }

  #removeToken() {
    removeToken();
  }
}

内部使用的requestAnimateFrame 执行轮巡,能在页面不显示时不被调用。

相关推荐
前端郭德纲6 分钟前
深入浅出ES6 Promise
前端·javascript·es6
就爱敲代码12 分钟前
ES6 运算符的扩展
前端·ecmascript·es6
天天进步201527 分钟前
Lodash:现代 JavaScript 开发的瑞士军刀
开发语言·javascript·ecmascript
王哲晓32 分钟前
第六章 Vue计算属性之computed
前端·javascript·vue.js
假装我不帅36 分钟前
js实现类似与jquery的find方法
开发语言·javascript·jquery
究极无敌暴龙战神X39 分钟前
CSS复习2
前端·javascript·css
风清扬_jd1 小时前
Chromium HTML5 新的 Input 类型week对应c++
前端·c++·html5
Ellie陈1 小时前
Java已死,大模型才是未来?
java·开发语言·前端·后端·python
GISer_Jing2 小时前
React面试常见题目(基础-进阶)
javascript·react.js·前端框架
想做白天梦2 小时前
双向链表(数据结构与算法)
java·前端·算法