PaperIdGenerator.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <?php
  2. namespace App\Services;
  3. /**
  4. * 试卷ID生成器
  5. * 参考行业标准 Snowflake ID 思想
  6. */
  7. class PaperIdGenerator
  8. {
  9. // 基准时间:2020-01-01 00:00:00 (秒)
  10. private const EPOCH = 1577836800;
  11. // 12位数字的位分配(总计约2.8万亿个容量,足够使用200年)
  12. // 格式:时间戳(8位) + 序列号(2位) + 随机数(2位) = 12位数字
  13. private const TIMESTAMP_BITS = 35; // 时间戳(秒)- 可覆盖约34年
  14. private const SEQUENCE_BITS = 11; // 序列号 - 2048个值
  15. private const RANDOM_BITS = 11; // 随机数 - 2048个值
  16. /**
  17. * 生成12位数字ID
  18. * 格式:TTTTTsssssR(时间戳+序列号+随机数)
  19. *
  20. * @return string 12位数字字符串
  21. */
  22. public static function generate(): string
  23. {
  24. // 使用时间戳(分钟)而不是秒,以减少位数
  25. $timestamp = intdiv(time() - self::EPOCH, 60); // 从基准时间开始的分钟数
  26. $timestamp &= (1 << self::TIMESTAMP_BITS) - 1; // 截取指定位数
  27. // 同一分钟内使用序列号递增,避免并发重复
  28. static $lastTimestamp = 0;
  29. static $sequence = 0;
  30. if ($timestamp == $lastTimestamp) {
  31. $sequence = ($sequence + 1) & ((1 << self::SEQUENCE_BITS) - 1);
  32. // 序列号用完,等待下一分钟
  33. if ($sequence == 0) {
  34. do {
  35. $timestamp = intdiv(time() - self::EPOCH, 60);
  36. $timestamp &= (1 << self::TIMESTAMP_BITS) - 1;
  37. } while ($timestamp <= $lastTimestamp);
  38. }
  39. } else {
  40. $sequence = 0;
  41. }
  42. $lastTimestamp = $timestamp;
  43. // 添加随机数后缀避免猜测
  44. $random = random_int(0, 99); // 2位随机数:00-99
  45. // 组合:时间戳(8位) + 序列号(2位) + 随机数(2位) = 12位数字
  46. // 时间戳占8位(不够前面补0)
  47. $timestampStr = str_pad((string)$timestamp, 8, '0', STR_PAD_LEFT);
  48. // 序列号占2位
  49. $sequenceStr = str_pad((string)$sequence, 2, '0', STR_PAD_LEFT);
  50. // 随机数占2位
  51. $randomStr = str_pad((string)$random, 2, '0', STR_PAD_LEFT);
  52. $id = $timestampStr . $sequenceStr . $randomStr;
  53. // 确保第一位不为0
  54. if ($id[0] === '0') {
  55. $id[0] = '1';
  56. }
  57. return $id;
  58. }
  59. /**
  60. * 验证12位数字ID格式
  61. *
  62. * @param string $id
  63. * @return bool
  64. */
  65. public static function validate(string $id): bool
  66. {
  67. return preg_match('/^[1-9]\d{11}$/', $id) === 1;
  68. }
  69. /**
  70. * 从ID提取时间戳
  71. *
  72. * @param string $id
  73. * @return int|null
  74. */
  75. public static function extractTimestamp(string $id): ?int
  76. {
  77. if (!self::validate($id)) {
  78. return null;
  79. }
  80. // 提取前8位时间戳(分钟),转换为秒
  81. $timestampMinutes = (int)substr($id, 0, 8);
  82. return $timestampMinutes * 60 + self::EPOCH;
  83. }
  84. /**
  85. * 批量生成不重复的ID
  86. *
  87. * @param int $count
  88. * @return array
  89. */
  90. public static function generateBatch(int $count): array
  91. {
  92. $ids = [];
  93. $attempts = 0;
  94. $maxAttempts = $count * 10;
  95. while (count($ids) < $count && $attempts < $maxAttempts) {
  96. $id = self::generate();
  97. if (!in_array($id, $ids)) {
  98. $ids[] = $id;
  99. }
  100. $attempts++;
  101. }
  102. return $ids;
  103. }
  104. }