登录注册功能

This commit is contained in:
2025-05-05 09:44:50 +08:00
parent db54446f8b
commit 147b2d3fd5
29 changed files with 552 additions and 60 deletions

112
app/Service/common/JWTService.php Executable file
View File

@@ -0,0 +1,112 @@
<?php
namespace App\Service\common;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Exception;
class JWTService
{
private static mixed $_instance;
private string $secretKey = 'cc_spa_jwt_secret_key'; // 请替换为你的密钥
private string $token;
/**
* 获取实例
* @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];
}
/**
* 获取 Token
* @return $this|null
* @throws Exception
*/
public function getToken(): null|static
{
$token = request()->bearerToken();
if (empty($token)) return UtilsService::getInstance()->notAuth('请先登录');
$this->token = $token;
return $this;
}
/**
* 生成 JWT Token
* @param array $data 要嵌入到 token 中的数据
* @return string
*/
public function generateToken(array $data): string
{
$issuedAt = time();
$expirationTime = $issuedAt + config('xk.redis.jwt_ttl');
$payload = [
'iat' => $issuedAt,
'exp' => $expirationTime,
'data' => $data
];
return JWT::encode($payload, $this->secretKey, 'HS256');
}
/**
* 解析 JWT Token
* @return object|null 返回解码后的 payload 或者 null 如果验证失败
* @throws Exception
*/
public function parseToken(): ?object
{
try {
if (empty($this->token)) return UtilsService::getInstance()->notAuth('请先登录');
return JWT::decode($this->token, new Key($this->secretKey, 'HS256'));
} catch (Exception $e) {
return UtilsService::getInstance()->notAuth('【1】Token解析失败请重新登录'. $e->getMessage());
}
}
/**
* 解析 JWT Token
* @return array|null 返回解码后的 payload 或者 null 如果验证失败
* @throws Exception
*/
public function getUserInfo(): ?array
{
try {
$jwt = $this->parseToken();
$user = RedisService::getInstance()->init(config('xk.redis.jwt'))->get($jwt->data->id);
if (empty($user)) return UtilsService::getInstance()->notAuth('登录状态过期');
return json_decode($user, true);
} catch (Exception $e) {
return UtilsService::getInstance()->notAuth('【2】Token解析失败请重新登录'. $e->getMessage());
}
}
/**
* 续签 JWT Token
* @return string|null 新的 JWT 字符串或者 null 如果原 token 已过期或无效
* @throws Exception
*/
public function refreshToken(): ?string
{
$decoded = $this->parseToken();
if ($decoded === null || !property_exists($decoded, 'data')) {
return null;
}
return $this->generateToken((array)$decoded->data);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Service\common;
use Illuminate\Support\Facades\Redis;
class RedisService
{
private static mixed $_instance;
private $prefix = 'default';
private $redis;
/**
* 获取实例
* @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 $prefix
* @return RedisService
*/
public function init(string $prefix = 'default'): static
{
$this->redis = Redis::class;
$this->prefix = $prefix;
return $this;
}
/**
* 设置缓存
* @param $key
* @param $value
* @param int $expire
* @return true
*/
public function set($key, $value, int $expire = 0): true
{
$this->redis::set($this->prefix . $key, $value);
if ($expire > 0) {
$this->redis::expire($this->prefix . $key, $expire);
}
return true;
}
/**
* 累加
* @param $key
* @param int $value
* @param int $expire
* @return true
*/
public function incr($key, int $value = 1, int $expire = 0): true
{
$this->redis::incr($this->prefix . $key, $value);
if ($expire > 0) {
$this->redis::expire($this->prefix . $key, $expire);
}
return true;
}
/**
* 获取缓存
*
* @param $key
* @return mixed
*/
public function get($key): mixed
{
return $this->redis::get($this->prefix . $key);
}
/**
* 删除缓存
* @param $key
* @return bool
*/
public function del($key): bool
{
return $this->redis::del($this->prefix . $key);
}
}

View File

@@ -0,0 +1,247 @@
<?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
{
exit(json_encode(['msg' => $msg, 'code' => ErrorEnum::NOT_AUTH->value]));
}
/**
* 生成密码
* @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
{
// 获取当前时间戳(精确到秒)
$timestamp = dechex(time());
// 生成16字节的随机数据并转换为32个十六进制字符组成的字符串
$randomString = bin2hex(random_bytes(16));
// 结合时间和随机字符串确保总长度为28个字符
return substr($timestamp . $randomString, 0, 28);
}
/**
* 递归生成树形结构
* @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('手机号格式不正确');
}
}
/**
* 生成订单号
* @param int $type
* @return string
*/
public function genOrderNo(int $type = 0): string
{
$len = 9999999999;
switch ($type) {
case 0:
return 'DK' . date('Ymd') . str_pad(mt_rand(1, $len), 11, '0', STR_PAD_LEFT);
case 1:
return 'DD' . date('Ymd') . str_pad(mt_rand(1, $len), 11, '0', STR_PAD_LEFT);
default:
return '';
}
}
/**
* 是否是开发环境
* @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)]);
}
}
}
}