秒杀订单需要提前生成订单号时,可以使用雪花id来作为订单号;
如果对订单号有要求,可以使用alpha_id,自定义数据的长度;然后自己进行拼接其他的数字长度【自定义拼接】
至于uuid:目前还没有需求- 一般用于软件注册码之类的
基于thinkphp的容器方式返回数据:里面的app('api_result')是接口的统一处理返回值类app('api_log')是日志类?app('alarm') 是邮件通知类 ,所以请参考方式即可
<?php
/*
* @Description: 生成唯一ID
* 雪花算法是由 1 + 41 + 5 + 5 + 12 = 64 位二进制组成的,最终将这些二进制转化为 10 进制得到雪花 ID
* 第一个 bit 为未使用的符号位。
* 第二部分由 41 位的时间戳(毫秒)构成,他的取值是当前时间相对于某一时间的偏移量。
* 第三部分和第四部分的 5 个 bit 位表示数据中心和机器ID,其能表示的最大值为 2^5 -1 = 31。
* 最后部分由 12 个 bit 组成,其表示每个工作节点每毫秒生成的序列号 ID,同一毫秒内最多可生成 2^12 -1 即 4095 个 ID。
* @ComposerRequire: 雪花算法:composer require godruoyi/php-snowflake
* UUID:composer require ramsey/uuid 3.9.3
* UUID依赖包:composer require paragonie/random_compat 2.0.9
* UUID依赖包:composer require symfony/polyfill-ctype 1.19.0
* @ComposerRemove: 雪花算法:composer remove godruoyi/php-snowflake
* UUID:composer remove ramsey/uuid >> composer remove paragonie/random_compat >> composer remove symfony/polyfill-ctype
* @Author: 阿海 <764882431@qq.com>
* @Date: 2021-02-01 17:10:06
* @LastEditTime: 2021-02-22 11:50:03
* @LastEditors: 阿海
*/
namespace app\common\library;
use Godruoyi\Snowflake\Snowflake;
use Ramsey\Uuid\Uuid;
// use think\Exception;
class Uniqid {
/**
* 雪花算法 取值0:则随机(1,31)
* 数据中心 取值范围1-31 最大值为:2^5-1=31
*/
private $datacenterId = 1;
/**
* 雪花算法 取值0:则随机(1,31)
* 机器id 取值范围1-31 最大值为:2^5-1=31
*/
private $workerId = 1;
/**
* 雪花算法 === 41 个 1 组成的二进制转为 10 进制得到时间差的毫秒数,转化为 年 为 69年
* 41 位的二进制长度最多能表示 2^41 -1 毫秒即69 年,所以雪花算法最多能正常使用 69年,为了能最大限度的使用该算法,应该为其指定一个开始时间
*/
private $startTime = 1614049994*1000; //2021-01-30的时间戳*1000毫秒
public function __construct($config=[])
{
$this->datacenterId = isset($config['datacenterId'])?(int)$config['datacenterId']:$this->datacenterId;
$this->workerId = isset($config['workerId'])?(int)$config['workerId']:$this->workerId;
$this->startTime = isset($config['startTime'])?(int)$config['startTime']:$this->startTime;
}
/**
* @param int $number 新创建雪花id的个数
* @param array $data 已经存在的id数组
* 雪花算法
* 时钟错乱:基于redis
* ==》 验证当前系统(eg:生成一个lock文件) 是否注册过机器id
* ==》(没有则注册机器id)校验系统当前时间是否大于上次上报的时间(失败时启动失败报警)
* ==》 如果是分布式系统,则校验各个系统时间,取平均值校验(失败时启动失败报警)
* ==》 周期性上报系统时间【redis记录】
* ==》 开始批量创建 雪花id
* 生成id -- 前端js可能会丢失精度,需要转为string
*/
public function id ( $number = 1, $data=[])
{
try{
//注册机器id
$res = $this->checkIdRegister();
if($res['code'] !== 0){
return app('api_result')->returnArray($res['code']);
}
//验证系统时间
$res = $this->checkSystemTime();
if($res['code'] !== 0){
return app('api_result')->returnArray($res['code'],['msg'=>$res['msg']]);
}
//开始批量创建雪花id
$snowflake = new Snowflake($this->datacenterId, $this->workerId);
$snowflake->setStartTimeStamp($this->startTime);
//叠加雪花ID
for($i=0; $i<$number; $i++){
$data[] = (string) $snowflake->id();
}
//去重过滤
$data = array_unique($data);
//数据不够,继续写入
if(count($data)<$number){
return $this->id($number-count(array_unique($data)),$data);
}
$data['msg'] = '已创建:'.count($data)."个雪花id;机器id:".$this->workerId;
app('api_log',['config'=>['type'=>'file']])->tag("snowflake")->write($data['msg'],'info','file');
return app('api_result')->returnArray(0,$data);
}catch(\Exception $e){
$msg = "文件:" . $e->getFile() .";代码不可执行,错误行:".$e->getLine().";错误信息:".$e->getMessage();
app('api_log',['config'=>['type'=>'file']])->tag("uniqid")->write($msg,'error','file');
app('alarm')->alarm($msg);
return app('api_result')->echoJson(3,['msg'=>$msg]);
}
}
/**
* 雪花算法:验证 机器id
* 1.是否已注册 机器id, 返回机器id
* 2.是否以注册满了==>(满了,启动失败报警)未满,返回新的机器id
*/
public function checkIdRegister()
{
try{
//由于目前没有redis,使用session来做测试!
$workerIds = !empty(session('workerIds','','register'))?session('workerIds','','register'):[];
//已经注册了机器码=返回机器id
if(isset($workerIds[md5(request()->ip())])){
$this->workerId = $workerIds[md5(request()->ip())]['workerId'];
return app("api_result")->returnArray(0,['msg'=>'机器码已注册绑定,重新赋予当前机器码']);
}
//判断是否已经满了,未满则分配机器id,否则触发失败报警
if(count($workerIds)<31){
$workerIds[md5(request()->ip())] = ['workerId'=>count($workerIds)+1];
session('workerIds',$workerIds,'register');
$this->workerId = $workerIds[md5(request()->ip())]['workerId'];
return app("api_result")->returnArray(0,['msg'=>'机器码未注册,正在注册新的机器码']);
}
$msg = "雪花机器id已满:".count($workerIds)."个;当前ip的md5:".md5(request()->ip()).";当前的机器码有:".json_encode($workerIds);
app('api_log',['config'=>['type'=>'file']])->tag("snowflake")->write($msg,'error','file');
app('alarm')->alarm($msg);
return app("api_result")->returnArray('3',['msg'=>'机器id已被注册满了','err'=>$msg]);
}catch(\Exception $e){
$msg = "文件:" . $e->getFile() .";代码不可执行,错误行:".$e->getLine().";错误信息:".$e->getMessage();
app('api_log',['config'=>['type'=>'file']])->tag("uniqid")->write($msg,'error','file');
app('alarm')->alarm($msg);
return app('api_result')->echoJson(3,['msg'=>$msg]);
}
}
/**
* 周期性上报系统时间,验证当前时间大于上次上报的系统时间==》验证通过,重新上报系统时间 ,验证不通过,启动失败报警
*/
public function checkSystemTime()
{
try{
$systemTime = session('system_time','',md5(request()->ip()));
// $systemTime = time()+1;//测试时间异常报警
if(time()>$systemTime){
session('system_time',time(),md5(request()->ip()));
return app("api_result")->returnArray(0,['msg'=>'系统时间正常']);
}else{
$msg = "【系统时间错误】系统上次上报的时间是:".date("Y-m-d H:i:s",$systemTime).";当前的系统时间:".date("Y-m-d H:i:s");
app('api_log',['config'=>['type'=>'file']])->tag("uniqid")->write($msg,'warning','file');
app('alarm')->alarm($msg);
return app("api_result")->returnArray(3,['msg'=>'系统时间错误','time'=>date("Y-m-d H:i:s")]);
}
}catch(\Exception $e){
$msg = "文件:" . $e->getFile() .";代码不可执行,错误行:".$e->getLine().";错误信息:".$e->getMessage();
app('api_log',['config'=>['type'=>'file']])->tag("uniqid")->write($msg,'error','file');
app('alarm')->alarm($msg);
return app('api_result')->echoJson(3,['msg'=>$msg]);
}
}
/**
* UUID
* 生成uuid
*/
public function uuid()
{
//uuid=>time based,uuid3=>name based & MD5,uuid4=>random,uuid5=>name based & SHA1
$data = [
'uuid1'=> (Uuid::uuid1())->toString(),
'uuid3'=> (Uuid::uuid3(Uuid::NAMESPACE_DNS, 'key'))->toString(),
'uuid4'=> (Uuid::uuid4())->toString(),
'uuid5'=> (Uuid::uuid5(Uuid::NAMESPACE_DNS, 'key'))->toString()
];
return app('api_result')->echoJson(0,$data);
}
/**
* @param int $number 个数
* @param int $length 数字的长度
* @param array $data 已有的数字id, 需注意:其中以数组中最后一个数字id作为最小的数字起始值
* 批量生成 alpha_id 数字id
*/
public function alphaId( $number = 1, $length = 8, $data=[])
{
try{
$min = 1;
//系统最大正整数
$limitNum = PHP_INT_MAX;
if($length>=strlen($limitNum)){
$max = $limitNum;
for($i=0; $i < strlen($limitNum)-1 ; $i++){
(int)($min *= 10);
}
$msg = "数据长度过大,已自动限制;最小值:".$min."(".strlen($min).");最大值:".$max."(".strlen($max).");数字长度参数:".$length.";个数:".$number.";alpha_id:".json_encode($data);
app('api_log',['config'=>['type'=>'file']])->tag("alpha_id")->write($msg,'warning','file');
}else{
$max = 9;
for($i=0; $i < $length-1 ; $i++){
(int)($min *= 10);
(int)($max .= 9);
}
}
//需先计算长度 -- 由于$data一直在变化,所以需先赋值 $total
$total = ($number-count($data));
//批量生成数据
for($i = 0; $i < $total; $i++){
if(count($data) == 0){
//计算分割段之间的差值
$rand = ($max-$min) / ($number) ;
$randNum = mt_rand( $rand/2, $rand );
//取分割段差值中随机数 + 最小值 = 第一个值
$data = [ $min + $randNum ];
}else{
//如果数组已存在,则读取最后一个数字id作为最小值
// $lastValue = array_pop($data);
$lastValue = $data[count($data)-1];
//计算分割段之间的差值
$rand = ($max - $lastValue) / ($number - count($data)) ;
//数据长度小于对应的个数
if($rand<1){
$msg = "差值:".$rand.";最小值:".$min."(".strlen($min).");最大值:".$max."(".strlen($max).");最后的数字:".$lastValue.";数字长度参数:".$length.";个数:".$number.";alpha_id的个数:".count($data);
app('api_log',['config'=>['type'=>'file']])->tag("alpha_id")->write($msg,'warning','file');
// app('alarm')->alarm($msg);
return app('api_result')->returnArray(3,['msg'=>$msg]);
}
$randNum = mt_rand( $rand/2, $rand );
$value = $lastValue + (($randNum>0) ? $randNum : 1);
array_push( $data , (int) $value );
}
}
//去重,继续计算长度,足够则不再进行计算,否则继续追加数据
$data = array_unique( $data );
//验证数组的长度是否足够长
if( count($data) >= $number ){
$msg = "成功创建alpha_id:".count($data).";最后一个数字id:".$data[count($data)-1];
app('api_log',['config'=>['type'=>'file']])->tag("alpha_id")->write($msg,'info','file');
return app('api_result')->returnArray(0,$data);
}
//出现重复数字id 删除重复id,继续追加数字id
return $this->alphaId($number,$length,$data);
}catch(\Exception $e){
$msg = "文件:" . $e->getFile() .";代码不可执行,错误行:".$e->getLine().";错误信息:".$e->getMessage();
app('api_log',['config'=>['type'=>'file']])->tag("uniqid")->write($msg,'error','file');
app('alarm')->alarm($msg);
return app('api_result')->echoJson(3,['msg'=>$msg]);
}
}
/**
* 销毁容器
*/
public function __destruct(){
// app('api_log',['config'=>['type'=>'file']])->tag("uniqid")->write('__destruct','info','file');
}
}
|