import dayjs from "dayjs"; /** * 门店编号(third_shop_id)自动生成 —— 汇安融门店创建表单用。 * * 形状:`SP` + `yyyyMMddHHmmss` + 16 位随机,共 32 字符,正好吃满 * `third_shop_id` 的 varchar(32)。改成自动生成是因为运营手填这个编号的代价不对称: * 撞号 ⇒ 上游按「重复提交」拒绝建店;填错一个字符 ⇒ 上游没有更新接口,事后补不上。 * * 两个刻意的选择: * - 字母表去掉 0/1/I/O(连同数字段共 32 个字符):编号要能在电话/微信里念得出来, * 去掉易混字符,一个歧义就少一类抄错。 * - 32 是 2 的幂 ⇒ 取随机字节的低 5 位做下标是无偏的(256 % 32 == 0),无需拒绝采样。 * 随机源必须是 crypto.getRandomValues:这个编号在上游是**幂等键**,撞号等于 * 整笔建店被拒,Math.random 的碰撞概率和可预测性都不能接受。 */ const ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; export function generateShopCode(now: Date = new Date()): string { const stamp = dayjs(now).format("YYYYMMDDHHmmss"); const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); let rand = ""; for (const b of bytes) { rand += ALPHABET[b & 31]; } return `SP${stamp}${rand}`; }