UUID生成策略/算法(JavaScript / Typescript)

这篇博客介绍了如何在不依赖外部库的情况下,使用JavaScript/TypeScript编写一个简单的UUID生成器。通过结合时间戳、随机数和计数器确保在本地环境下的唯一性。虽然在多用户环境下重复概率极低,但作者提到了可能的重复情况,并提供了一个进阶版,加入了服务端前缀和MD5散列以进一步减少冲突。
摘要由CSDN通过智能技术生成

UUID生成策略/算法(JavaScript / Typescript)

  • 我只是想简单的生成一些UUID,不想引入一堆库,所以最后设计了一个简单的UUID生成策略。

  • 下面的UUID生成方法是依据时间戳随机数count计数器三个输入因子生成的,能保证本地的UUID是不会重复的,但是在多用户端环境下有非常非常低的概率出现重复。

  • 多用户端场景UUID有可能重复的极端场景,需要满足以下两个前提条件:

    • 两个用户端在同一毫秒内完成了此代码的初始化(获得了相同的时间戳)。
    • 两个用户生成的随机数相同(理论上相同的概率是一百亿分之一)。

实现代码(JavaScript / Typescript):

// UUID中允许出现的基本字符;
const baseChar: string[] = (() => {
  const array: string[] = [];
  // 将数字0-9加入到 baseChar 中
  for (let i = 0; i < 10; i++) {
    array.push(i.toString(10));
  }
  // 将小写字母a-z加入到 baseChar 中
  for (let i = 0; i < 26; i++) {
    array.push(String.fromCharCode('a'.charCodeAt(0) + i));
  }
  // 将大写字母A-Z加入到 baseChar 中
  for (let i = 0; i < 26; i++) {
    array.push(String.fromCharCode('A'.charCodeAt(0) + i));
  }
  return array;
})();

// 进制转换/数字转字符
function scaleTransition(value: number, base = baseChar): string {
  if (value < 0) {
    throw new Error('scaleTransition Error, value < 0');
  }
  if (value === 0) {
    return base[0];
  }
  const radix = base.length;
  let result = '';
  let resValue = value;
  while (resValue > 0) {
    result = base[resValue % radix] + result;
    resValue = Math.floor(resValue / radix);
  }
  return result;
}

// UUID参数集合, 使用 count 能保证在本地环境中UUID绝对不会重复.
const UuidObject: Record<string, { prefix: string; count: number }> = {};
// 生成随机的UUID
export default function createUUID(prefix = '') {
  let uuidObject = UuidObject[prefix];
  if (uuidObject === undefined) {
    const str = `${scaleTransition(Date.now())}-${scaleTransition(
      Math.floor(Math.random() * 10000000000),
    )}-`;
    if (prefix) {
      uuidObject = {
        prefix: `${prefix}-${str}`,
        count: 0,
      };
    } else {
      uuidObject = {
        prefix: str,
        count: 0,
      };
    }
    UuidObject[prefix] = uuidObject;
  }
  return uuidObject.prefix + scaleTransition(uuidObject.count++);
}

  • 这里的baseChar中包含的是UUID中允许出现的字符。
  • scaleTransition会将传入的value进行“进制转换”。
  • 总得来说,baseChar其中包含的字符越多,最后生成的UUID长度越短。

进阶版,加入服务端前缀与MD5散列

import MD5 from 'md5';

// UUID中的分隔符
const splitChar = '.';
// UUID中允许出现的基本字符;
const baseChar: string[] = (() => {
  const array: string[] = [];
  // 将数字0-9加入到 baseChar 中
  for (let i = 0; i < 10; i++) {
    array.push(i.toString(10));
  }
  // 将小写字母a-z加入到 baseChar 中
  for (let i = 0; i < 26; i++) {
    array.push(String.fromCharCode('a'.charCodeAt(0) + i));
  }
  // 将大写字母A-Z加入到 baseChar 中
  for (let i = 0; i < 26; i++) {
    array.push(String.fromCharCode('A'.charCodeAt(0) + i));
  }
  // 加入一些其它的ASCII中的打印字符(尽量对URL或者JSON友好)
  array.push('*', '+', '-', '<', '=', '>');
  array.push('(', ')', '[', ']', '{', '}', '|');
  array.push('!', '$', '%', ':', ';', '@', '^', '_', '~');
  return array;
})();

// 进制转换/数字转字符
function scaleTransition(value: number | bigint, base = baseChar): string {
  if (value < 0) {
    throw new Error('scaleTransition Error, value < 0');
  }
  if (value === 0) {
    return base[0];
  }
  if (typeof value === 'bigint') {
    const radix = BigInt(base.length);
    let result = '';
    let resValue = value;
    while (resValue > 0) {
      result = base[Number(resValue % radix)] + result;
      resValue /= radix;
    }
    return result;
  } else {
    const radix = base.length;
    let result = '';
    let resValue = value;
    while (resValue > 0) {
      result = base[resValue % radix] + result;
      resValue = Math.floor(resValue / radix);
    }
    return result;
  }
}

function getPrefix(): string {
  // TODO 从服务端UUID前缀, 来避免多用户端生成的uuid出现重复, (如果获取失败,则使用时间戳来兜底)
  return String(Date.now());
}

// UUID参数集合, 使用 count 能保证在本地环境中UUID绝对不会重复.
const UuidObject: Record<string, { prefix: string; count: number }> = {};
function getUuidInfo(prefix: string): { prefix: string; count: number } {
  let uuidObject = UuidObject[prefix];
  if (uuidObject === undefined) {
    // 先使用MD5将 getPrefix 散列成为一个32位长度的16进制字符串, 再将其转换为bigInt数值, 最后使用scaleTransition转换来压缩长度
    const str1 = scaleTransition(BigInt(`0x${MD5(getPrefix())}`));
    // MD5散列理论上存在碰撞的可能性, 可以在加入一个一亿以内的随机数降低在散列发生碰撞时生成UUID重复的可能性
    const str2 = scaleTransition(Math.floor(Math.random() * 100000000));
    if (prefix) {
      uuidObject = {
        prefix: prefix + splitChar + str1 + splitChar + str2 + splitChar,
        count: 0,
      };
    } else {
      uuidObject = {
        prefix: str1 + splitChar + str2 + splitChar,
        count: 0,
      };
    }
    UuidObject[prefix] = uuidObject;
  }
  return uuidObject;
}

// 生成随机的UUID
export default function createUUID(prefix = '') {
  const uuidObject = getUuidInfo(prefix);
  return uuidObject.prefix + scaleTransition(uuidObject.count++);
}

// for (let i = 0; i < 10; i++) {
//   console.log(createUUID());
// }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值