1. 维护代码健壮

2. 更新项目结构文档
This commit is contained in:
技术老胡
2026-04-27 16:20:41 +08:00
parent 9a16a88640
commit 0e5de50337
198 changed files with 21038 additions and 702 deletions

View File

@@ -6,6 +6,8 @@ use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use app\common\constant\AuthConstant;
use app\exception\AuthConfigException;
use support\Redis;
use Throwable;
@@ -29,6 +31,7 @@ class JwtTokenManager
* @param array<string, mixed> $sessionData 会话数据
* @param int|null $ttlSeconds 过期秒数
* @return array{token:string,expires_in:int,jti:string,claims:array<string, mixed>,session:array<string, mixed>} 签发结果
* @throws AuthConfigException
*/
public function issue(string $guard, array $claims, array $sessionData, ?int $ttlSeconds = null): array
{
@@ -47,7 +50,7 @@ class JwtTokenManager
'guard' => $guard,
], $claims);
$token = JWT::encode($payload, (string) $guardConfig['secret'], 'HS256');
$token = JWT::encode($payload, (string) $guardConfig['secret'], AuthConstant::JWT_ALGORITHM_HS256);
$session = array_merge($sessionData, [
'guard' => $guard,
@@ -80,6 +83,7 @@ class JwtTokenManager
* @param string $ip 最近访问 IP
* @param string $userAgent 用户Agent
* @return array{claims:array<string, mixed>,session:array<string, mixed>}|null 验证结果
* @throws AuthConfigException
*/
public function verify(string $guard, string $token, string $ip = '', string $userAgent = ''): ?array
{
@@ -126,6 +130,7 @@ class JwtTokenManager
* @param string $guard 登录域
* @param string $token JWT 字符串
* @return bool 是否已撤销
* @throws AuthConfigException
*/
public function revoke(string $guard, string $token): bool
{
@@ -150,6 +155,7 @@ class JwtTokenManager
* @param string $guard 登录域
* @param string $jti 会话标识
* @return bool 是否已撤销
* @throws AuthConfigException
*/
public function revokeByJti(string $guard, string $jti): bool
{
@@ -168,6 +174,7 @@ class JwtTokenManager
* @param string $guard 登录域
* @param string $jti 会话标识
* @return array<string, mixed>|null 会话数据
* @throws AuthConfigException
*/
public function session(string $guard, string $jti): ?array
{
@@ -188,6 +195,7 @@ class JwtTokenManager
* @param string $guard 登录域
* @param string $token JWT 字符串
* @return array<string, mixed>|null JWT 载荷
* @throws AuthConfigException
*/
protected function decode(string $guard, string $token): ?array
{
@@ -196,7 +204,7 @@ class JwtTokenManager
try {
JWT::$leeway = (int) config('auth.leeway', 30);
$payload = JWT::decode($token, new Key((string) $guardConfig['secret'], 'HS256'));
$payload = JWT::decode($token, new Key((string) $guardConfig['secret'], AuthConstant::JWT_ALGORITHM_HS256));
} catch (ExpiredException|SignatureInvalidException|Throwable) {
return null;
}
@@ -221,6 +229,7 @@ class JwtTokenManager
* @param array<string, mixed> $session 会话数据
* @param int $ttlSeconds 过期秒数
* @return void
* @throws AuthConfigException
*/
protected function storeSession(string $guard, string $jti, array $session, int $ttlSeconds): void
{
@@ -239,6 +248,7 @@ class JwtTokenManager
* @param string $guard 登录域
* @param string $jti 会话标识
* @return string Redis 会话键
* @throws AuthConfigException
*/
protected function sessionKey(string $guard, string $jti): string
{
@@ -250,13 +260,15 @@ class JwtTokenManager
*
* @param string $guard 登录域
* @return array<string, mixed> 认证配置
* @throws \InvalidArgumentException
* @throws AuthConfigException
*/
protected function guardConfig(string $guard): array
{
$guards = (array) config('auth.guards', []);
if (!isset($guards[$guard])) {
throw new \InvalidArgumentException("Unknown auth guard: {$guard}");
throw new AuthConfigException("未知认证域:{$guard}", [
'guard' => $guard,
]);
}
return $guards[$guard];
@@ -268,7 +280,7 @@ class JwtTokenManager
* @param string $guard 登录域
* @param string $secret 密钥
* @return void
* @throws RuntimeException
* @throws AuthConfigException
*/
protected function assertHmacSecretLength(string $guard, string $secret): void
{
@@ -277,17 +289,19 @@ class JwtTokenManager
}
$envNames = match ($guard) {
'admin' => 'AUTH_ADMIN_JWT_SECRET or AUTH_JWT_SECRET',
'merchant' => 'AUTH_MERCHANT_JWT_SECRET or AUTH_JWT_SECRET',
default => 'the configured JWT secret',
AuthConstant::GUARD_ADMIN => 'AUTH_ADMIN_JWT_SECRET AUTH_JWT_SECRET',
AuthConstant::GUARD_MERCHANT => 'AUTH_MERCHANT_JWT_SECRET AUTH_JWT_SECRET',
default => '当前配置的 JWT 密钥',
};
throw new \RuntimeException(sprintf(
'JWT secret for guard "%s" is too short for HS256. Please set %s to at least 32 ASCII characters.',
throw new AuthConfigException(sprintf(
'认证域 "%s" 的 JWT 密钥长度不足HS256 至少需要 32 ASCII 字符,请将 %s 配置为至少 32 个字符。',
$guard,
$envNames
));
), [
'guard' => $guard,
'env_names' => $envNames,
'secret_length' => strlen($secret),
]);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace app\common\util;
use RuntimeException;
/**
* RSA 密钥对生成器。
*
* 统一用于后台自动生成商户 RSA 公私钥对,避免各处重复实现。
*/
final class RsaKeyPairGenerator
{
/**
* 生成 RSA 密钥对。
*
* @param int $bits 密钥长度
* @return array{private_key: string, public_key: string}
*/
public static function generate(int $bits = 2048): array
{
while (openssl_error_string()) {
}
$configPath = self::resolveOpenSslConfigPath();
if ($configPath === '') {
throw new RuntimeException('生成 RSA 密钥对失败,未找到可用的 openssl.cnf 配置文件');
}
$resource = openssl_pkey_new([
'private_key_bits' => max(1024, $bits),
'private_key_type' => OPENSSL_KEYTYPE_RSA,
'config' => $configPath,
]);
if ($resource === false) {
throw new RuntimeException('生成 RSA 密钥对失败:' . self::collectOpenSslErrors());
}
$privateKey = '';
if (!openssl_pkey_export($resource, $privateKey, null, ['config' => $configPath]) || trim($privateKey) === '') {
throw new RuntimeException('导出 RSA 私钥失败:' . self::collectOpenSslErrors());
}
$details = openssl_pkey_get_details($resource);
$publicKey = trim((string) ($details['key'] ?? ''));
if ($publicKey === '') {
throw new RuntimeException('导出 RSA 公钥失败');
}
return [
'private_key' => trim($privateKey),
'public_key' => $publicKey,
];
}
/**
* 查找可用的 OpenSSL 配置文件。
*
* @return string 配置文件路径
*/
private static function resolveOpenSslConfigPath(): string
{
$candidates = [];
$envConfig = trim((string) getenv('OPENSSL_CONF'));
if ($envConfig !== '') {
$candidates[] = $envConfig;
}
$baseDir = dirname(PHP_BINARY);
$candidates[] = $baseDir . DIRECTORY_SEPARATOR . 'extras' . DIRECTORY_SEPARATOR . 'ssl' . DIRECTORY_SEPARATOR . 'openssl.cnf';
$candidates[] = $baseDir . DIRECTORY_SEPARATOR . 'openssl.cnf';
$candidates[] = dirname($baseDir) . DIRECTORY_SEPARATOR . 'Apache2.4.39' . DIRECTORY_SEPARATOR . 'conf' . DIRECTORY_SEPARATOR . 'openssl.cnf';
$candidates[] = 'C:\\Program Files\\Git\\mingw64\\etc\\ssl\\openssl.cnf';
$candidates[] = 'C:\\Program Files\\Git\\usr\\ssl\\openssl.cnf';
foreach ($candidates as $candidate) {
$candidate = trim((string) $candidate);
if ($candidate !== '' && is_file($candidate)) {
return $candidate;
}
}
return '';
}
/**
* 收集当前 OpenSSL 错误栈。
*
* @return string 错误信息
*/
private static function collectOpenSslErrors(): string
{
$messages = [];
while ($message = openssl_error_string()) {
$messages[] = $message;
}
return $messages ? implode(' | ', $messages) : 'unknown error';
}
}