当前位置: 首页 > news >正文

TypeScript实战:零依赖实现4种自定义UUID生成方案

1. 为什么需要自定义UUID生成方案

在小程序开发中,我们经常会遇到一个头疼的问题:很多常用的npm库在小程序环境下无法使用。最近我在开发uni-app项目时就遇到了这个坑,标准UUID库在小程序里直接报错。经过一番折腾,我发现不依赖第三方库实现UUID生成其实并不复杂,而且还能根据业务需求定制各种格式。

UUID(通用唯一标识符)通常表现为32位16进制数字,用连字符分成5组,格式为8-4-4-4-12。但在实际业务中,我们可能需要更短的ID,或者需要兼容特定字符集的ID。比如在小程序里传参时,太长的ID会影响性能;在某些数据库里,特殊字符可能引发问题。这时候自定义UUID方案就派上用场了。

2. 基础UUID生成原理

先来看最基础的UUIDv4生成方法。虽然JavaScript没有内置UUID生成器,但我们可以用随机数模拟:

const generateUUID = (): string => { const hexDigits = '0123456789abcdef' const s: string[] = Array(36).fill('') // 填充随机16进制字符 for (let i = 0; i < 36; i++) { s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10)) } // 设置版本标识位(第13位设为4) s[14] = '4' // 设置变体标识位(第17位高位设为01) s[19] = hexDigits.charAt((parseInt(s[19], 16) & 0x3) | 0x8) // 设置分隔符 s[8] = s[13] = s[18] = s[23] = '-' return s.join('') }

这段代码有几个关键点:

  1. 使用16进制随机填充大部分位
  2. 第13位固定为'4'表示这是UUIDv4
  3. 第17位高位设为'8','9','a'或'b',符合RFC标准
  4. 按规定位置插入分隔符

实测下来,这种生成方式虽然简单,但完全能满足大部分场景的唯一性需求。我在一个日活10万+的小程序中使用,从未出现重复ID。

3. 三种压缩算法实现

3.1 CookieBase90压缩方案

原始UUID有36个字符,在某些场景下还是太长。我们可以用Base90编码压缩:

const constants = { CookieBase90: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+-./:<=>?@[]^_`{|}~" } const hexToCustomBase = (hex: string, alphabet: string): string => { const base = alphabet.length let num = BigInt(`0x${hex}`) let encoded = '' while (num > 0) { encoded = alphabet[Number(num % BigInt(base))] + encoded num = num / BigInt(base) } return encoded }

这个方案的特点是:

  • 使用所有可打印ASCII字符(共90个)
  • 能将UUID压缩到20个字符左右
  • 特别适合需要URL传参的场景

我在实际使用中发现,虽然压缩率不是最高的,但兼容性最好,所有浏览器和小程序都能正确处理这些字符。

3.2 FlickrBase58压缩方案

如果需要更友好的ID格式,可以借鉴Flickr的Base58方案:

const constants = { FlickrBase58: '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' }

这个方案:

  • 去除了容易混淆的字符(如0/O,1/l等)
  • 生成22字符左右的ID
  • 大小写字母+数字,适合需要人工识别的场景

实测在小程序中,这种格式的ID用户最容易接受,客服人员处理工单时出错率最低。

3.3 Base36压缩方案

如果环境限制只能使用小写字母和数字,可以用Base36:

const constants = { UUID25Base36: '0123456789abcdefghijklmnopqrstuvwxyz' }

特点包括:

  • 纯小写+数字,兼容性最强
  • 生成25字符左右的ID
  • 适合需要全小写的数据库主键

我在MongoDB中使用这种格式作为_id,查询性能比原始UUID提升约15%。

4. 完整实现与使用示例

把以上方案整合起来,我们可以实现一个灵活的UUID生成器:

enum UUIDFormat { CookieBase90, FlickrBase58, UUID25Base36 } interface PaddingParams { shortIdLength: number consistentLength: boolean paddingChar: string } const shortenUUID = (longId: string, alphabet: string, paddingParams?: PaddingParams): string => { const hex = longId.replace(/-/g, '') const translated = hexToCustomBase(hex, alphabet) if (!paddingParams?.consistentLength) return translated return translated.padStart(paddingParams.shortIdLength, paddingParams.paddingChar) } const uuid = (format?: UUIDFormat): string => { const standardUUID = generateUUID() if (format === undefined) { return standardUUID } const useAlphabet = constants[format] const shortIdLength = Math.ceil(Math.log2(2**128) / Math.log2(useAlphabet.length)) const paddingParams: PaddingParams = { shortIdLength, consistentLength: true, paddingChar: useAlphabet[0] } return shortenUUID(standardUUID, useAlphabet, paddingParams) }

使用示例:

// 标准UUID console.log(uuid()) // 例如: fe6eee2b-f3bb-4afb-b6d1-427829aa2720 // CookieBase90压缩版 console.log(uuid(UUIDFormat.CookieBase90)) // 例如: otDc4[?2Wo5JYd*F=AME // FlickrBase58压缩版 console.log(uuid(UUIDFormat.FlickrBase58)) // 例如: fV8nBAawjXG9dfQJvnmx6w // Base36压缩版 console.log(uuid(UUIDFormat.UUID25Base36)) // 例如: d4vid4vf6usa9bga6awv0jjja

在实际项目中,我通常会根据场景选择不同格式:

  • 前后端通信用标准UUID
  • URL参数用CookieBase90
  • 用户可见ID用FlickrBase58
  • 数据库主键用Base36

5. 性能优化与注意事项

经过多次测试,我发现几点性能优化经验:

  1. BigInt性能:现代JavaScript引擎对BigInt的优化很好,但在老版本iOS上可能会有性能问题。如果遇到性能瓶颈,可以考虑用分段计算替代。

  2. 字符集选择:Base58的字符集去除了容易混淆的字符,但计算量会稍大。在对用户体验要求不高的内部系统,可以用Base62获得更好的压缩率。

  3. 填充策略:固定长度ID有利于数据库索引,但会增加计算开销。在我的测试中,对100万条ID进行查询,固定长度比变长快约8%。

  4. 随机数质量Math.random()在大多数场景下足够用,如果需要更安全的随机数,可以考虑crypto.getRandomValues()

一个常见的坑是字符集定义。曾经我把Base58的字符集少写了一个字符,导致生成的ID在特定情况下会重复。现在我会在单元测试中加入字符集完整性检查:

describe('UUID生成器', () => { it('字符集不应有重复', () => { for (const format in constants) { const chars = constants[format] expect(new Set(chars.split('')).size).toBe(chars.length) } }) })

6. 扩展应用场景

除了基本用法,这套方案还可以扩展:

多租户系统:可以在ID前加上租户前缀

function generateTenantId(tenantCode: string) { return `${tenantCode}_${uuid(UUIDFormat.FlickrBase58)}` }

有序ID:结合时间戳可以生成大致有序的ID

function generateOrderedId() { const timePart = Date.now().toString(36) const randomPart = uuid(UUIDFormat.UUID25Base36) return `${timePart}-${randomPart}` }

短链生成:用Base90可以生成很短的唯一字符串

function generateShortCode() { return uuid(UUIDFormat.CookieBase90).slice(0, 8) }

在小程序开发中,我还遇到过微信云开发环境的一些特殊限制。比如云函数间传递数据时,太长的ID会导致序列化问题。这时候用Base58压缩后的ID就完美解决了问题。

7. 与第三方库的对比

虽然不依赖第三方库很爽,但也要客观看待优缺点:

优势

  • 零依赖,小程序兼容性好
  • 可定制各种格式满足业务需求
  • 代码透明,便于调试和优化

不足

  • 缺少UUID v1/v3/v5等版本支持
  • 随机数质量依赖运行环境
  • 需要自行维护测试用例

如果项目允许使用npm包,像uuid这样的成熟库仍然是首选。但在受限环境或需要特殊格式时,这套方案就显示出它的价值了。

我在实际项目中会把这两种方式结合使用:大部分场景用标准库,特殊场景用自定义方案。这样既保证了开发效率,又能满足各种边界需求。

http://www.jsqmd.com/news/649342/

相关文章:

  • 12. C++17新特性-std::optional
  • 纯前端实现视频封面生成:Canvas与Video API的实战应用
  • 3分钟解锁Unity游戏无限可能:MelonLoader终极安装秘籍
  • Conda环境创建报错:深入剖析ERROR conda.core.link:_execute(502)的根源与解决
  • 如何使用RobotJS实现响应式桌面自动化:从基础到实战指南
  • 群晖音乐播放器歌词插件终极指南:免费打造家庭卡拉OK系统
  • 手把手教你:Win10/Win11桌面路径改错D盘后,如何用注册表+批处理一键恢复(附自动生效脚本)
  • OBS Multi RTMP插件:一键实现多平台直播的免费开源解决方案
  • OpenAppFilter网络协议分析:如何实现高效的应用识别与拦截
  • 3步完成视频智能剪辑:FunClip免费开源工具快速上手终极指南
  • Bresenham直线插补算法在激光振镜控制系统中的优化应用
  • 2835基于51单片机的简易秒表时钟系统设计
  • 从推荐系统到以图搜图:Faiss + Sentence-Transformers 构建你的第一个AI应用
  • 因公出差平台怎么选?差旅预订/报销/费控/SaaS系统深度对比 - 匠言榜单
  • Java的java.util.Optional流式方法与flatMap在嵌套可选值中的展开操作
  • 生日祝福不会说?语际点歌台:用歌声传心意,体面又有仪式感
  • GPT-5.4-Cyber:AI 网络安全军备竞赛的分水岭,防御方终于拿到了对等武器
  • 如何通过Chrome扩展一键捕获完整网页内容?
  • 2836基于51单片机的简易秒表系统设计
  • 微信小程序自动化签到避坑指南:从抓包到服务器部署的全流程解析
  • 公司福利沃尔玛卡回收合法吗? - 京顺回收
  • 5分钟永久备份你的QQ空间记忆:GetQzonehistory终极指南
  • AI建站工具从0到1全攻略:不懂代码也能快速上线公司官网
  • BetterGI:如何用开源自动化技术实现原神全流程智能操作?
  • 性价比高的游乐坦克设备厂推荐,为你揭秘价格背后的真相 - 工业品牌热点
  • 如何构建ApexCharts.js图表错误处理与监控告警机制:完整指南
  • phpfastcache监控与调试:确保缓存系统稳定运行的完整方案
  • Cloudbox社区与生态系统:如何参与贡献和获取支持
  • 如何使用Johnny-Five实现Prometheus硬件指标采集:物联网监控终极指南
  • 2824基于51单片机的简易四位密码锁设计