|
|
@@ -5,23 +5,26 @@ namespace App\Services;
|
|
|
/**
|
|
|
* 试卷ID生成器
|
|
|
* 参考行业标准 Snowflake ID 思想
|
|
|
+ *
|
|
|
+ * 【修复】增强多进程并发安全:
|
|
|
+ * - 添加进程ID区分不同 PHP-FPM Worker
|
|
|
+ * - 增加随机数位数降低碰撞概率
|
|
|
*/
|
|
|
class PaperIdGenerator
|
|
|
{
|
|
|
// 基准时间:2020-01-01 00:00:00 (秒)
|
|
|
private const EPOCH = 1577836800;
|
|
|
|
|
|
- // 12位数字的位分配(总计约2.8万亿个容量,足够使用200年)
|
|
|
- // 格式:时间戳(8位) + 序列号(2位) + 随机数(2位) = 12位数字
|
|
|
+ // 15位数字的位分配
|
|
|
+ // 格式:时间戳(8位) + 进程ID(2位) + 序列号(2位) + 随机数(3位) = 15位数字
|
|
|
private const TIMESTAMP_BITS = 35; // 时间戳(秒)- 可覆盖约34年
|
|
|
private const SEQUENCE_BITS = 11; // 序列号 - 2048个值
|
|
|
- private const RANDOM_BITS = 11; // 随机数 - 2048个值
|
|
|
|
|
|
/**
|
|
|
- * 生成12位数字ID
|
|
|
- * 格式:TTTTTsssssR(时间戳+序列号+随机数)
|
|
|
+ * 生成15位数字ID(增强版,支持多进程并发)
|
|
|
+ * 格式:TTTTTTTT + PP + SS + RRR(时间戳 + 进程ID + 序列号 + 随机数)
|
|
|
*
|
|
|
- * @return string 12位数字字符串
|
|
|
+ * @return string 15位数字字符串
|
|
|
*/
|
|
|
public static function generate(): string
|
|
|
{
|
|
|
@@ -29,6 +32,9 @@ class PaperIdGenerator
|
|
|
$timestamp = intdiv(time() - self::EPOCH, 60); // 从基准时间开始的分钟数
|
|
|
$timestamp &= (1 << self::TIMESTAMP_BITS) - 1; // 截取指定位数
|
|
|
|
|
|
+ // 获取进程ID(区分不同的 PHP-FPM Worker)
|
|
|
+ $processId = getmypid() % 100; // 2位进程ID:00-99
|
|
|
+
|
|
|
// 同一分钟内使用序列号递增,避免并发重复
|
|
|
static $lastTimestamp = 0;
|
|
|
static $sequence = 0;
|
|
|
@@ -48,18 +54,16 @@ class PaperIdGenerator
|
|
|
|
|
|
$lastTimestamp = $timestamp;
|
|
|
|
|
|
- // 添加随机数后缀避免猜测
|
|
|
- $random = random_int(0, 99); // 2位随机数:00-99
|
|
|
+ // 添加随机数后缀避免猜测(3位:000-999,碰撞概率 0.1%)
|
|
|
+ $random = random_int(0, 999);
|
|
|
|
|
|
- // 组合:时间戳(8位) + 序列号(2位) + 随机数(2位) = 12位数字
|
|
|
- // 时间戳占8位(不够前面补0)
|
|
|
+ // 组合:时间戳(8位) + 进程ID(2位) + 序列号(2位) + 随机数(3位) = 15位数字
|
|
|
$timestampStr = str_pad((string)$timestamp, 8, '0', STR_PAD_LEFT);
|
|
|
- // 序列号占2位
|
|
|
- $sequenceStr = str_pad((string)$sequence, 2, '0', STR_PAD_LEFT);
|
|
|
- // 随机数占2位
|
|
|
- $randomStr = str_pad((string)$random, 2, '0', STR_PAD_LEFT);
|
|
|
+ $processIdStr = str_pad((string)$processId, 2, '0', STR_PAD_LEFT);
|
|
|
+ $sequenceStr = str_pad((string)($sequence % 100), 2, '0', STR_PAD_LEFT); // 确保只取2位
|
|
|
+ $randomStr = str_pad((string)$random, 3, '0', STR_PAD_LEFT);
|
|
|
|
|
|
- $id = $timestampStr . $sequenceStr . $randomStr;
|
|
|
+ $id = $timestampStr . $processIdStr . $sequenceStr . $randomStr;
|
|
|
|
|
|
// 确保第一位不为0
|
|
|
if ($id[0] === '0') {
|
|
|
@@ -70,14 +74,15 @@ class PaperIdGenerator
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 验证12位数字ID格式
|
|
|
+ * 验证数字ID格式(支持12位旧格式和15位新格式)
|
|
|
*
|
|
|
* @param string $id
|
|
|
* @return bool
|
|
|
*/
|
|
|
public static function validate(string $id): bool
|
|
|
{
|
|
|
- return preg_match('/^[1-9]\d{11}$/', $id) === 1;
|
|
|
+ // 支持12位(旧)和15位(新)格式
|
|
|
+ return preg_match('/^[1-9]\d{11,14}$/', $id) === 1;
|
|
|
}
|
|
|
|
|
|
/**
|