能直接抄走的 UUID 正则
标准形态是八个、四个、四个、四个、十二个十六进制数字,用连字符分隔:
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
这是最稳妥的通用检查:它接受大小写十六进制,强制五段各自的长度,并要求连字符恰好落在四个正确位置。加上 i 标志你可以省掉 A-F 部分,但显式写出来能让模式在忽略标志的引擎间也能移植。
每一段代表什么
[0-9a-fA-F]{8}—— 头 32 位,通常按版本不同是时间戳的低或高部分。{4}—— 接下来 16 位,历史上放与版本相关的时间字段。{4}—— 版本位就坐在这段的第一个字符(见下)。{4}—— 变体位就坐在这段的第一个字符。{12}—— 最后 48 位,通常是节点(MAC 或随机值)。
连字符不是装饰。它们是 RFC 4122 / 9562 文本表示的一部分,位置本身 encodes 了每个字段的边界落在哪儿。
版本位与变体位,正则可以钉死的部分
UUID 不只是三十二个十六进制字符;有两个特定位置承载着含义:
- 版本号是第三段(第三组)的第一个十六进制字符(从 0 数第 14 位)。目前合法值是 1 到 8:v1 基于时间、v2 DCE、v3 和 v5 基于名字(MD5/SHA-1)、v4 随机、v6/v7 时间有序、v8 自定义。
- 变体位是第四段(第四组)的第一个十六进制字符(第 19 位)。在 RFC 4122 布局下它必须是 8、9、a 或 b——也就是最高两位是
10。
如果你只收 v4,就把这两个位置钉死:
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
第二个连字符后的 4 强制版本为 4;第三个连字符后的 [89abAB] 强制合法的 RFC 变体。
为什么正则只能查形状
这是本页核心论点。正则只能确认一个字符串看起来像 UUID。它无法确认版本语义被遵守了。一个字符串版本位写了 4,可能只是碰巧,背后零位随机性——对正则"完全合法",密码学上却一文不值。反过来,未来的版本 9 会被今天钉死版本的正则拒掉,即便它本是个正当 UUID。正则看的是数字,看不懂含义。
常见错误写法
- 丢了大小写:只写
[0-9a-f]会拒掉大写,于是550E8400-...失败。要么补上A-F,要么先归一化大小写。 - 段数写错:漏掉一段、或在某块写成
{16},会得到匹配错长度、悄悄放过垃圾的模式。 - 用了
\w:\w匹配数字、字母,还匹配下划线,于是____-____-____-____-________也能过。永远用显式十六进制类。 - 丢了锚点:没有
^和$,1234-...嵌在更长的串里也会被当子串匹配,骗过校验器。
真例子:放行与拒绝
| 输入 | 结果 | 原因 |
|---|---|---|
| 550e8400-e29b-41d4-a716-446655440000 | 放行 | 正确的 8-4-4-4-12 形态,合法 v4 |
| 550E8400-E29B-41D4-A716-446655440000 | 放行 | 大写同样是合法十六进制 |
| 123e4567-e89b-12d3-a456-426614174000 | 放行 | 合法的 v1 形态 |
| not-a-uuid | 拒绝 | 不是十六进制,没有结构 |
| 12345678-1234-1234-1234-1234567890 | 拒绝 | 最后一段只有 10 位,需要 12 位 |
花括号、URN 前缀与 nil UUID
真实输入比标准形态更脏:
- 微软工具爱把 UUID 包进花括号:
{550e8400-e29b-41d4-a716-446655440000}。测试前先去掉花括号,或者扩展模式放行。 - URN 形态带前缀:
urn:uuid:550e8400-e29b-41d4-a716-446655440000。同样,先去掉或放行前缀。 - nil UUID
00000000-0000-0000-0000-000000000000在语法上合法,意思是"没有 UUID"。把它当哨兵值,别当真标识符用。
该用什么替代
要做真正的校验,去解析而不是去匹配。多数语言自带 UUID 类型,会拒绝畸形输入,甚至检查版本一致性:
import uuid
try:
u = uuid.UUID("550e8400-e29b-41d4-a716-446655440000")
print(u.version) # 4
except ValueError:
print("不是 uuid")
JavaScript 里 crypto.randomUUID() 产出 v4 字符串,new UUID(...)(或一个小库)负责解析。用解析器:它对 nil、版本、变体的理解远胜你写的任何正则。
生成与校验是两件事
把这两件事分开值得:生成 UUID 应当来自密码学安全的随机源(大多数标准库已经这样做了),而校验 UUID 才是本页的主题。正则是可能最弱的校验器,解析器是最强的。
千万别自己手搓随机十六进制、再指望版本位碰巧写对——调用库的生成器,版本位和变体位会替你设好。如果你只是想在数据库里放一个主键,直接拿库生成的字符串,存之前用解析器过一遍,比任何正则都省心。记住:能被正则放行的字符串千千万,其中只有真正由生成器产出的才该被信任;其余碰巧符合形态的,不过是长得像而已。把信任建立在解析器而不是形状上,是写对这块代码最便宜的一课。