電商訂單號、會員卡號、用戶邀請碼等業務常需「固定長度、純數字、唯一、無規律可循」的編碼。直接使用自增 ID 會洩露業務量,且長度不固定。本文介紹一個基於時間戳 + ID 混淆編碼的解決方案。
<?php
namespace Minyifei\Libs;
class SequenceNumber {
// 各長度對應的位元映射表(核心混淆邏輯)
private static function _getMapbit($width) {
$mapBits = [
4 => [10, 2, 11, 3, 0, 1, 9, 7, 12, 6, 4, 8, 5],
5 => [4, 3, 13, 15, 7, 8, 6, 2, 1, 10, 5, 12, 0, 11, 14, 9],
6 => [2, 7, 10, 9, 16, 3, 6, 8, 0, 4, 1, 12, 11, 13, 18, 5, 15, 17, 14],
7 => [18, 0, 2, 22, 8, 3, 1, 14, 17, 12, 4, 19, 11, 9, 13, 5, 6, 15, 10, 16, 20, 7, 21],
8 => [11, 8, 4, 0, 16, 14, 22, 7, 3, 5, 13, 18, 24, 25, 23, 10, 1, 12, 6, 21, 17, 2, 15, 9, 19, 20],
9 => [24, 23, 27, 3, 9, 16, 25, 13, 28, 12, 0, 4, 10, 18, 11, 2, 17, 1, 21, 26, 5, 15, 7, 20, 22, 14, 19, 6, 8],
10 => [32, 3, 1, 28, 21, 18, 30, 7, 12, 22, 20, 13, 16, 15, 6, 17, 9, 25, 11, 8, 4, 27, 14, 31, 5, 23, 24, 29, 0, 10, 19, 26, 2],
11 => [9, 13, 2, 29, 11, 32, 14, 33, 24, 8, 27, 4, 22, 20, 5, 0, 21, 25, 17, 28, 34, 6, 23, 26, 30, 3, 7, 19, 16, 15, 12, 31, 1, 35, 10, 18],
12 => [31, 4, 16, 33, 35, 29, 17, 37, 12, 28, 32, 22, 7, 10, 14, 26, 0, 9, 8, 3, 20, 2, 13, 5, 36, 27, 23, 15, 19, 34, 38, 11, 24, 25, 30, 21, 18, 6, 1],
];
return $mapBits[intval($width)] ?? [];
}
// 時間戳格式化(預設 YmdHis)
private static $_fmt = 'YmdHis';
private static function _fmtTS($ts = null) {
return date(self::$_fmt, $ts ?? time());
}
/**
* 核心編碼演算法:位元置換 + 偏移量
* @param int $id 來源 ID(通常為資料庫自增主鍵)
* @param int $width 輸出長度(不含前綴)
* @return string 固定長度數字字串
*/
public static function encode($id, $width) {
$maximum = (int)str_repeat('9', $width); // 該長度最大值(如 999999)
$superscript = (int)(log($maximum) / log(2)); // 所需位元數
$r = 0;
$sign = 1 << $superscript; // 最高位標記
$id |= $sign; // 確保 ID 不為 0
$mapbit = self::_getMapbit($width);
for ($x = 0; $x < $superscript; $x++) {
$bit = ($id >> $x) & 1; // 取出第 x 位
$r |= ($bit << $mapbit[$x]); // 映射到新位置
}
// 偏移量確保結果滿足固定長度
$r += $maximum - pow(2, $superscript) + 1;
return sprintf("%0{$width}s", $r); // 左補零
}
/**
* 公開介面:帶前綴的編碼
* @param int $id 業務 ID
* @param int $prefix 前綴(如 10 表訂單,20 表用戶)
* @param int $width 編碼長度
* @return string 完整編碼
*/
public static function generateNumber($id, $prefix = 10, $width = 8) {
return sprintf("%s%s", $prefix, self::encode($id, $width));
}
/**
* 含時間戳的唯一編碼(適合訂單號)
* @param int $id 業務 ID
* @param int $width 編碼長度
* @param int $ts 時間戳(預設當前)
* @return string 如 20240115123045 + 8位編碼
*/
public static function get($id, $width, $ts = null) {
return sprintf('%s%s', self::_fmtTS($ts), self::encode($id, $width));
}
}
// 訂單號:時間戳(14位) + 8位混淆碼 = 22位純數字 $orderNo = SequenceNumber::get($orderId, 8); // 輸出範例:2024011512304573928104 // 用戶邀請碼:前綴 20 + 6位混淆碼 = 8位 $inviteCode = SequenceNumber::generateNumber($userId, 20, 6); // 輸出範例:20483921 // 純編碼(無前綴、無時間) $code = SequenceNumber::encode(12345, 8); // 輸出範例:03928104
| 特性 | 說明 |
|---|---|
| 唯一性 | 同一 ID + 同一長度 + 同一時間戳 = 固定輸出;不同 ID 輸出極大概率不同 |
| 不可逆性 | 位元置換映射表單向,難以從編碼反推原始 ID |
| 固定長度 | sprintf 左補零保證輸出長度恆定 |
| 無規律性 | 映射表打亂位元順序,連續 ID 生成的編碼無明顯規律 |
| 效能 | 純位元運算,微秒級完成,支援高併發 |