在项目中,需要生成一个全局唯一的 uuid,来标记每行数据。我就在想,只是全局不唯一就行了,也懒得加载第三方 npm 包了,自己小小封装一下。
结果没想到踩了一个坑,测试环境是好好地,到了正式环境就报错了。我起初还以为是 webpack 打包差异造成的,最后排查下来,是我封装的 uuid()
有问题。
我最最开始,是用时间戳+自增数
来生成唯一 id 的。后来觉得这种实现方式不太优雅,而且浏览器也支持了 crypto.randomUUID() 方法,我就想着给改写一下。同时,这个方法只在 localhost 或者 https 环境下才可用,然后我也做了下兜底兼容。
export const uuid = () => {
if (typeof window.crypto?.randomUUID === "function") {
return window.crypto.randomUUID();
}
if (typeof window.crypto.getRandomValues === "function" && typeof window.Uint8Array === "function") {
const arr = new Uint8Array(16);
crypto.getRandomValues(arr);
let str = "";
arr.forEach((item) => {
str += item.toString(16).padStart(2, "0");
});
return str;
}
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
上面封装的代码,其实有一个问题:这三种方式生成的唯一 id 的格式是不一样的。
我们本地的开发环境和测试环境,都是 http 协议的,以浏览器的兼容性,都会都到第 2 步里,也没发现有啥问题。然后上线后,出问题了,以生成的 uuid 来找对应的数据,一直找不到。
最后发现是两个原因造成的:
- 线上正式环境是 https 协议,所以会走第 1 步,生成一个 uuid;
- 这三种方式生成的唯一 id 的格式是不一样的(我刚开始想的是只要全局唯一即可,没必要非得是标准的 8-4-4-4-12 的这种格式);
- 业务逻辑中是用
[name]-[uuid]
拼接成的 key,然后我再用-
来分割出 name 和 uuid;
分割的代码如下:
const [name, uuid] = key.split("-");
本地开发和测试环境中都是在第 2 步中生成的唯一 id,这里面没有中横线-
的分隔符,一直没问题。但在正式环境中,是用第 1 步的方法生成的唯一 id,所以这里面就会有中横线-
的分隔符,就会导致分割的时候出错。取到的 uuid 是不完整的。
这里也给自己一个警示:
若采用了多种策略来封装一个功能,一定要注意输出数据的一致性。
知道原因后,就知道该怎么改了,要么就去掉 uuid 中所有的中横线-
的分隔符,要么就在业务中拼接 key 时,使用其他的字符来代替中横线。