62 lines
1.9 KiB
PHP
62 lines
1.9 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
namespace App\Service\business;
|
|||
|
|
|
|||
|
|
use Illuminate\Support\Facades\DB;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 业务单号生成(清单 QD_ / 订单 DD_)
|
|||
|
|
*
|
|||
|
|
* 随机串剔除 0/O/1/I/L 这些肉眼分不清的字符——单号会被打印在纸质报价单上人工抄录。
|
|||
|
|
* 唯一索引兜底,撞号就重试;重试仍失败宁可报错,也不要为了成功而降级成可能重复的号。
|
|||
|
|
*/
|
|||
|
|
class SerialNoService
|
|||
|
|
{
|
|||
|
|
private static mixed $_instance;
|
|||
|
|
|
|||
|
|
private const ALPHABET = '23456789ABCDEFGHJKMNPQRSTUVWXYZ';
|
|||
|
|
|
|||
|
|
public const PREFIX_LIST = 'QD_';
|
|||
|
|
public const PREFIX_ORDER = 'DD_';
|
|||
|
|
|
|||
|
|
public static function getInstance(): null|static
|
|||
|
|
{
|
|||
|
|
$name = get_called_class();
|
|||
|
|
if (!isset(self::$_instance[$name])) {
|
|||
|
|
self::$_instance[$name] = new static();
|
|||
|
|
}
|
|||
|
|
return self::$_instance[$name];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成一个库内不存在的单号
|
|||
|
|
*
|
|||
|
|
* @param string $prefix self::PREFIX_*
|
|||
|
|
* @param string $table business 连接下的表名(不带前缀)
|
|||
|
|
* @param string $column 单号字段
|
|||
|
|
*/
|
|||
|
|
public function generate(string $prefix, string $table, string $column, int $tries = 10): string
|
|||
|
|
{
|
|||
|
|
$day = date('Ymd');
|
|||
|
|
for ($i = 0; $i < $tries; $i++) {
|
|||
|
|
$no = $prefix . $day . $this->randomPart(6);
|
|||
|
|
$exists = DB::connection('business')->table($table)->where($column, $no)->exists();
|
|||
|
|
if (!$exists) {
|
|||
|
|
return $no;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 连续撞 10 次说明随机源或并发量出了问题,静默返回可能重复的号比抛错危险得多
|
|||
|
|
throw new \RuntimeException('单号生成失败,请重试');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private function randomPart(int $length): string
|
|||
|
|
{
|
|||
|
|
$max = strlen(self::ALPHABET) - 1;
|
|||
|
|
$out = '';
|
|||
|
|
for ($i = 0; $i < $length; $i++) {
|
|||
|
|
$out .= self::ALPHABET[random_int(0, $max)];
|
|||
|
|
}
|
|||
|
|
return $out;
|
|||
|
|
}
|
|||
|
|
}
|