功能说明
该示例生成 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);