← 中文资源

简体中文版本

纯 JS 单调递增 UUIDv7 生成器

生成带 48 位毫秒时间戳与 12 位计数器的 UUIDv7,保证同一毫秒内仍按字典序递增。

阅读英文原文

功能说明

该示例生成 UUIDv7。UUIDv7 的前 48 位是毫秒时间戳,因此按字符串排序就近似按创建时间排序,适合作为数据库主键或分布式 ID。除了时间前缀,它还用 12 位计数器替换部分随机位,保证同一毫秒内连续生成的 ID 仍然单调递增,避免同一批次写入时顺序抖动。

关键代码

uuidv7 读取当前时间,如果时钟回拨则沿用上次时间戳。时间戳相同则递增计数器,计数器超过 0xfff 时把时间戳加一毫秒并清零,确保继续递增。随后用 crypto.getRandomValues 填充 16 字节,再覆盖时间戳六个字节、版本位 0111、变体位 10,并把计数器写入第 6 字节低四位和第 7 字节。最后按 8-4-4-4-12 的格式拼接十六进制。

使用注意

ID 包含时间信息,可能泄露创建时间,敏感场景应评估。同一毫秒超过 4096 个 ID 时会把时间戳推进到未来,之后真实时间追上之前会继续复用该时间戳,单调性仍然成立。时钟大幅回拨会沿用旧时间戳,可能导致时间前缀滞后。UUIDv7 不是秘密,不能当作随机令牌使用。依赖 crypto.getRandomValues,老旧环境需要 polyfill。

验证方式

用正则断言版本位为 7、变体位为 8 到 b。连续生成一千个 ID,断言严格递增且互不重复。解析前 48 位时间戳,断言与当前时间接近且不回退。模拟同一毫秒内多次调用,检查计数器递增;再验证缺失 crypto 时抛出明确错误。

代码示例

let lastTimestamp = -1;
let counter = 0;

export function uuidv7() {
  if (typeof globalThis.crypto?.getRandomValues !== "function") {
    throw new Error("crypto.getRandomValues is required to generate UUIDv7");
  }
  let timestamp = Date.now();
  if (timestamp < lastTimestamp) timestamp = lastTimestamp;
  if (timestamp === lastTimestamp) {
    counter += 1;
    if (counter > 0x0fff) {
      timestamp = lastTimestamp + 1;
      counter = 0;
    }
  } else {
    counter = 0;
  }
  lastTimestamp = timestamp;
  const bytes = new Uint8Array(16);
  globalThis.crypto.getRandomValues(bytes);
  bytes[0] = Math.floor(timestamp / 2 ** 40) & 0xff;
  bytes[1] = Math.floor(timestamp / 2 ** 32) & 0xff;
  bytes[2] = Math.floor(timestamp / 2 ** 24) & 0xff;
  bytes[3] = Math.floor(timestamp / 2 ** 16) & 0xff;
  bytes[4] = Math.floor(timestamp / 2 ** 8) & 0xff;
  bytes[5] = timestamp & 0xff;
  bytes[6] = 0x70 | ((counter >> 8) & 0x0f);
  bytes[7] = counter & 0xff;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
  const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
  return [
    hex.slice(0, 4).join(""),
    hex.slice(4, 6).join(""),
    hex.slice(6, 8).join(""),
    hex.slice(8, 10).join(""),
    hex.slice(10, 16).join(""),
  ].join("-");
}

const first = uuidv7();
const second = uuidv7();
console.log(first);
console.log(second);
console.log(first < second);