分享一个业务中采用 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 执行轮巡,能在页面不显示时不被调用。

相关推荐
前端大卫32 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘1 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare1 小时前
浅浅看一下设计模式
前端
Lee川1 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端