Files
nl-mall-api/app/Service/common/UtilsService.php

347 lines
9.2 KiB
PHP
Raw Normal View History

2025-05-12 00:19:35 +08:00
<?php
namespace App\Service\common;
use App\Enum\ErrorEnum;
use DateTime;
use Exception;
use Illuminate\Support\Facades\Route;
use Random\RandomException;
use ReflectionClass;
class UtilsService
{
private static mixed $_instance;
/**
* 获取实例
* @return null|static
*/
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 $msg
* @param ErrorEnum $code
* @return mixed
* @throws Exception
*/
public function errorThrow(string $msg = '请求出了问题!', ErrorEnum $code = ErrorEnum::FAIL): mixed
{
throw new Exception($msg, $code->value);
}
/**
* 抛出未找到异常 2000
*
* @param $msg
* @return mixed
* @throws Exception
*/
public function notFound($msg): mixed
{
return self::errorThrow($msg, ErrorEnum::NOT_FOUND);
}
/**
* 未授权 401
*
* @param $msg
* @return mixed
* @throws Exception
*/
public function notAuth($msg): mixed
{
return self::errorThrow($msg, ErrorEnum::NOT_AUTH);
}
/**
* 生成密码
* @param $password
* @return string
*/
public function genPassword($password): string
{
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* 密码验证
*
* @param $password
* @param $passwordAse
* @return bool
*/
public function checkPassword($password, $passwordAse): bool
{
// 盐
return password_verify($password, $passwordAse);
}
/**
* 生成OpenId
* @return string
* @throws RandomException
*/
public function genOpenId(): string
{
2025-05-12 13:12:57 +08:00
// 获取微秒级时间戳(提高时间精度)
$microtime = microtime(true);
$timestamp = dechex(floor($microtime)); // 秒部分
$microsec = dechex(($microtime - floor($microtime)) * 1000000); // 微秒部分
2025-05-12 00:19:35 +08:00
2025-05-12 13:12:57 +08:00
// 生成更多随机字节20字节=160位熵
$randomBytes = random_bytes(20);
$randomHex = bin2hex($randomBytes);
2025-05-12 00:19:35 +08:00
2025-05-12 13:12:57 +08:00
// 获取进程ID和内存ID作为额外熵源
$pid = dechex(getmypid() % 65536); // 进程ID
$memoryId = dechex(crc32(memory_get_usage(true))); // 内存使用哈希
// 打乱组合顺序并截取28个字符
$components = [
$timestamp,
$microsec,
substr($randomHex, 0, 16),
substr($randomHex, 16, 8),
$pid,
$memoryId
];
shuffle($components); // 打乱顺序
$combined = implode('', $components);
// 最终截取(确保固定长度)
return 'nl_'. substr($combined, 0, 28);
2025-05-12 00:19:35 +08:00
}
/**
* 递归生成树形结构
* @param $data
* @param int $pid
* @return array
*/
public function tree($data, int $pid = 0): array
{
$tree = [];
foreach ($data as $v) {
if ($v['pid'] == $pid) {
$v['children'] = $this->tree($data, $v['id']);
if (empty($v['children'])) {
unset($v['children']);
}
$tree[] = $v;
}
}
return $tree;
}
/**
* 检查手机号格式
*
* @param string $phone
* @return bool
* @throws Exception
*/
public function checkPhone(string $phone): bool
{
$phone = trim($phone);
if (preg_match('/^1[3456789]\d{9}$/', $phone)) {
return true;
} else {
self::errorThrow('手机号格式不正确');
}
}
2025-05-19 16:54:25 +08:00
/**
* 检查身份证号格式
* @param string $idCard
* @return true|void
* @throws Exception
*/
public function checkIdCard(string $idCard)
{
$idCard = trim($idCard);
if (preg_match('/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/', $idCard)) {
return true;
} else {
self::errorThrow('身份证号格式不正确');
}
}
2025-05-12 00:19:35 +08:00
/**
* 生成订单号
* @param int $type
* @return string
*/
public function genOrderNo(int $type = 0): string
{
$prefix = ['NL', 'TX', 'TK'];
$datePart = date('Ymd');
// 确保len不超过PHP_INT_MAX避免整数溢出
$maxLen = PHP_INT_MAX;
// 使用bcmath扩展生成安全的大数随机数
if (function_exists('bcadd') && function_exists('bcmul')) {
// 生成一个10位的随机字符串作为基础
$randomBase = '';
for ($i = 0; $i < 10; $i++) {
$randomBase .= mt_rand(0, 9);
}
// 使用bcmath计算一个大数随机数
$randomPart = bcadd(
'1', // 确保最小值为1
bcmul(
$randomBase,
bcdiv((string)$maxLen, '10000000000', 0),
0
)
);
// 截取前11位数字
$randomStr = substr($randomPart, 0, 11);
} else {
// 备用方案使用mt_rand并处理溢出
$randomNum = mt_rand(1, $maxLen);
$randomStr = (string)$randomNum;
// 如果长度不足11位左侧补0
if (strlen($randomStr) < 11) {
$randomStr = str_pad($randomStr, 11, '0', STR_PAD_LEFT);
} else {
// 如果长度超过11位截取前11位
$randomStr = substr($randomStr, 0, 11);
}
2025-05-12 00:19:35 +08:00
}
// 确保随机部分长度为11
$paddedRandom = str_pad($randomStr, 11, '0', STR_PAD_LEFT);
$paddedRandom = substr($paddedRandom, -11); // 确保最终长度为11
// 返回格式化后的代码
return isset($prefix[$type]) ? $prefix[$type] . $datePart . $paddedRandom : '';
2025-05-12 00:19:35 +08:00
}
/**
* 是否是开发环境
* @return bool
*/
public static function isDev(): bool
{
return env('ECS') === 'localhost';
}
/**
* 根据身份证号计算年龄
* @param string $idno 身份证号15位或18位
* @return int|false 成功返回年龄失败返回false
*/
public function getAgeFromIdNo($idno = '')
{
// 校验身份证号长度
$len = strlen($idno);
if (!in_array($len, [15, 18])) {
return false;
}
// 提取出生日期部分
if ($len === 18) {
$datePart = substr($idno, 6, 8);
} else {
// 15位身份证补全年份为19XX
$datePart = '19' . substr($idno, 6, 6);
}
// 分解年月日
$year = substr($datePart, 0, 4);
$month = substr($datePart, 4, 2);
$day = substr($datePart, 6, 2);
// 校验年月日是否为数字
if (!ctype_digit($year) || !ctype_digit($month) || !ctype_digit($day)) {
return false;
}
// 尝试创建日期对象
try {
$birthDate = new DateTime("$year-$month-$day");
} catch (Exception $e) {
return false;
}
$currentDate = new DateTime();
// 检查出生日期是否在当前日期之前
if ($birthDate > $currentDate) {
return false;
}
// 计算年龄差
$ageInterval = $currentDate->diff($birthDate);
return $ageInterval->y;
}
public function autoRouteRegister($class): void
{
foreach ($class as $key => $value) {
// 获取控制器$value的所有方法
$methods = (new ReflectionClass($value))->getMethods();
// 注册路由
foreach ($methods as $method) {
// 获取方法注释 @Method
$docComment = $method->getDocComment();
if ($method->name === '__construct' || preg_match('/@Method\s+(NO)\b/', $docComment, $matches)) {
continue;
}
$httpMethod = 'any';
// 查询 @Method GET 或者 @Method POST
if (preg_match('/@Method\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|ANY)\b/', $docComment, $matches)) {
$httpMethod = $matches[1];
}
Route::$httpMethod($key . '/' . cc_camel_case_to_dash($method->getName()), [$value, conjunction_symbol_processing($method->name)]);
}
}
}
2025-05-12 13:12:57 +08:00
// 获取项目根目录
public static function getRootPath(): string
{
return dirname(__DIR__, 3);
}
// 若文件不存在则创建文件
public static function createFileHolder($filePath): bool
{
if (!file_exists($filePath)) {
return mkdir($filePath, 777, true);
}
return true;
}
// 若文件不存在则创建文件
public static function createFile($filePath): bool
{
if (!file_exists($filePath)) {
$file = fopen($filePath, 'w');
fclose($file);
}
return true;
}
2025-05-12 00:19:35 +08:00
}