mirror of
https://gitee.com/ctexthuang/hyperf_rbac_framework_server_ctexthuang.git
synced 2025-12-25 18:17:49 +08:00
first commit
This commit is contained in:
14
app/Annotation/ResponseFormat.php
Normal file
14
app/Annotation/ResponseFormat.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Annotation;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||
class ResponseFormat
|
||||
{
|
||||
/**
|
||||
* @param string $format
|
||||
*/
|
||||
public function __construct(public string $format) {}
|
||||
}
|
||||
51
app/Aspect/ResponseFormatAspect.php
Normal file
51
app/Aspect/ResponseFormatAspect.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Aspect;
|
||||
|
||||
use App\Annotation\ResponseFormat;
|
||||
use Hyperf\Di\Annotation\Aspect;
|
||||
use Hyperf\Di\Aop\AbstractAspect;
|
||||
use Hyperf\Di\Exception\Exception;
|
||||
use Hyperf\HttpServer\Request;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||
|
||||
#[Aspect]
|
||||
class ResponseFormatAspect extends AbstractAspect
|
||||
{
|
||||
/**
|
||||
* @var array|class-string[]
|
||||
*/
|
||||
public array $annotations = [
|
||||
ResponseFormat::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(protected ContainerInterface $container) {}
|
||||
|
||||
/**
|
||||
* @param ProceedingJoinPoint $proceedingJoinPoint
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function process(ProceedingJoinPoint $proceedingJoinPoint): mixed
|
||||
{
|
||||
// 获取注解定义的格式
|
||||
$annotation = $proceedingJoinPoint->getAnnotationMetadata()->class[ResponseFormat::class]
|
||||
?? $proceedingJoinPoint->getAnnotationMetadata()->method[ResponseFormat::class] ?? null;
|
||||
|
||||
if ($annotation) {
|
||||
// 将注解格式存入请求属性(覆盖中间件的默认值)
|
||||
$request = $proceedingJoinPoint->arguments['request'] ?? null;
|
||||
if ($request instanceof Request) {
|
||||
$request->withAttribute('response_format', $annotation->format);
|
||||
}
|
||||
}
|
||||
|
||||
return $proceedingJoinPoint->process();
|
||||
}
|
||||
}
|
||||
51
app/Command/GenClass/RepositoryGenCommand.php
Normal file
51
app/Command/GenClass/RepositoryGenCommand.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command\GenClass;
|
||||
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Devtool\Generator\GeneratorCommand;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
#[Command]
|
||||
class RepositoryGenCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('gen:repository');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function configure(): void
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Create a new repository class');
|
||||
|
||||
$this->setHelp('php bin/hyperf.php gen:repository fileRepository');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 stubs
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub(): string
|
||||
{
|
||||
return __DIR__ . '/stubs/repository.stub';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认命名空间
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace(): string
|
||||
{
|
||||
return 'App\\Repository';
|
||||
}
|
||||
}
|
||||
50
app/Command/GenClass/ServiceGenCommand.php
Normal file
50
app/Command/GenClass/ServiceGenCommand.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command\GenClass;
|
||||
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Devtool\Generator\GeneratorCommand;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
#[Command]
|
||||
class ServiceGenCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('gen:service');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function configure(): void
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Create a new service class');
|
||||
|
||||
$this->setHelp('php bin/hyperf.php gen:service /folder/fileService');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 stubs
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub(): string
|
||||
{
|
||||
return __DIR__ . '/stubs/service.stub';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认命名空间
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace(): string
|
||||
{
|
||||
return 'App\\Service';
|
||||
}
|
||||
}
|
||||
16
app/Command/GenClass/stubs/repository.stub
Normal file
16
app/Command/GenClass/stubs/repository.stub
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* This service file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace %NAMESPACE%;
|
||||
|
||||
final class %CLASS% extends BaseRepository
|
||||
{
|
||||
public function __construct(protected readonly xxx $model) {}
|
||||
}
|
||||
19
app/Command/GenClass/stubs/service.stub
Normal file
19
app/Command/GenClass/stubs/service.stub
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* This service file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace %NAMESPACE%;
|
||||
|
||||
class %CLASS% extends
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
//todo Write logic
|
||||
}
|
||||
}
|
||||
18
app/Common/Macros/BlueprintMacros.php
Normal file
18
app/Common/Macros/BlueprintMacros.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Common\Macros;
|
||||
|
||||
use Hyperf\Database\Schema\Blueprint;
|
||||
|
||||
class BlueprintMacros
|
||||
{
|
||||
public static function register(): void
|
||||
{
|
||||
Blueprint::macro('authorFields', function () {
|
||||
/** @var Blueprint $this */
|
||||
$this->unsignedBigInteger('created_by')->nullable()->comment('创建人ID');
|
||||
$this->unsignedBigInteger('updated_by')->nullable()->comment('更新人ID');
|
||||
return $this;
|
||||
});
|
||||
}
|
||||
}
|
||||
19
app/Constants/AdminCode.php
Normal file
19
app/Constants/AdminCode.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
use Hyperf\Constants\Annotation\Message;
|
||||
use Hyperf\Constants\EnumConstantsTrait;
|
||||
|
||||
#[Constants]
|
||||
final class AdminCode extends ResultCode
|
||||
{
|
||||
#[Message("登录失败")]
|
||||
public const int LOGIN_ERROR = 10001;
|
||||
|
||||
#[Message("验证已过期")]
|
||||
public const int LOGIN_TOKEN_ERROR = 10002;
|
||||
}
|
||||
25
app/Constants/ErrorCode.php
Normal file
25
app/Constants/ErrorCode.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\AbstractConstants;
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class ErrorCode extends AbstractConstants
|
||||
{
|
||||
/**
|
||||
* @Message("Server Error!")
|
||||
*/
|
||||
public const SERVER_ERROR = 500;
|
||||
}
|
||||
32
app/Constants/Model/AdminUser/AdminMenuStatusCode.php
Normal file
32
app/Constants/Model/AdminUser/AdminMenuStatusCode.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Model\AdminUser;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
use Hyperf\Constants\Annotation\Message;
|
||||
use Hyperf\Constants\EnumConstantsTrait;
|
||||
|
||||
#[Constants]
|
||||
enum AdminMenuStatusCode: int
|
||||
{
|
||||
use EnumConstantsTrait;
|
||||
|
||||
|
||||
#[Message('正常')]
|
||||
case Normal = 1;
|
||||
|
||||
#[Message('停用')]
|
||||
case DISABLE = 2;
|
||||
|
||||
public function isNormal(): bool
|
||||
{
|
||||
return $this === self::Normal;
|
||||
}
|
||||
|
||||
public function isDisable(): bool
|
||||
{
|
||||
return $this === self::DISABLE;
|
||||
}
|
||||
}
|
||||
32
app/Constants/Model/AdminUser/AdminRoleStatusCode.php
Normal file
32
app/Constants/Model/AdminUser/AdminRoleStatusCode.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Model\AdminUser;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
use Hyperf\Constants\Annotation\Message;
|
||||
use Hyperf\Constants\EnumConstantsTrait;
|
||||
|
||||
#[Constants]
|
||||
enum AdminRoleStatusCode: int
|
||||
{
|
||||
use EnumConstantsTrait;
|
||||
|
||||
|
||||
#[Message('正常')]
|
||||
case Normal = 1;
|
||||
|
||||
#[Message('停用')]
|
||||
case DISABLE = 2;
|
||||
|
||||
public function isNormal(): bool
|
||||
{
|
||||
return $this === self::Normal;
|
||||
}
|
||||
|
||||
public function isDisable(): bool
|
||||
{
|
||||
return $this === self::DISABLE;
|
||||
}
|
||||
}
|
||||
31
app/Constants/Model/AdminUser/AdminUserStatusCode.php
Normal file
31
app/Constants/Model/AdminUser/AdminUserStatusCode.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Model\AdminUser;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
use Hyperf\Constants\Annotation\Message;
|
||||
use Hyperf\Constants\EnumConstantsTrait;
|
||||
|
||||
#[Constants]
|
||||
enum AdminUserStatusCode: int
|
||||
{
|
||||
use EnumConstantsTrait;
|
||||
|
||||
#[Message('正常')]
|
||||
case Normal = 1;
|
||||
|
||||
#[Message('停用')]
|
||||
case DISABLE = 2;
|
||||
|
||||
public function isNormal(): bool
|
||||
{
|
||||
return $this === self::Normal;
|
||||
}
|
||||
|
||||
public function isDisable(): bool
|
||||
{
|
||||
return $this === self::DISABLE;
|
||||
}
|
||||
}
|
||||
21
app/Constants/Model/AdminUser/AdminUserTypeCode.php
Normal file
21
app/Constants/Model/AdminUser/AdminUserTypeCode.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants\Model\AdminUser;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
use Hyperf\Constants\Annotation\Message;
|
||||
use Hyperf\Constants\EnumConstantsTrait;
|
||||
|
||||
#[Constants]
|
||||
enum AdminUserTypeCode: int
|
||||
{
|
||||
use EnumConstantsTrait;
|
||||
|
||||
#[Message('系统用户')]
|
||||
case SYSTEM = 100;
|
||||
|
||||
#[Message('普通用户')]
|
||||
case USER = 200;
|
||||
}
|
||||
22
app/Constants/ResultCode.php
Normal file
22
app/Constants/ResultCode.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\AbstractConstants;
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
use Hyperf\Constants\Annotation\Message;
|
||||
use Hyperf\Constants\EnumConstantsTrait;
|
||||
|
||||
#[Constants]
|
||||
class ResultCode extends AbstractConstants
|
||||
{
|
||||
use EnumConstantsTrait;
|
||||
|
||||
#[Message("success")]
|
||||
final public const int SUCCESS = 0;
|
||||
|
||||
#[Message("failed")]
|
||||
final public const int ERROR = 1;
|
||||
}
|
||||
30
app/Controller/AbstractController.php
Normal file
30
app/Controller/AbstractController.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected ContainerInterface $container;
|
||||
|
||||
#[Inject]
|
||||
protected RequestInterface $request;
|
||||
|
||||
#[Inject]
|
||||
protected ResponseInterface $response;
|
||||
}
|
||||
25
app/Controller/Admin/LoginController.php
Normal file
25
app/Controller/Admin/LoginController.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Annotation\ResponseFormat;
|
||||
use App\Controller\AbstractController;
|
||||
use App\Request\Admin\LoginRequest;
|
||||
use App\Service\Admin\Login\LoginService;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\Validation\Annotation\Scene;
|
||||
|
||||
#[Controller(prefix: "admin/login")]
|
||||
#[ResponseFormat('admin')]
|
||||
final class LoginController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path: "login", methods: "POST")]
|
||||
#[Scene(scene: "login")]
|
||||
public function login(LoginRequest $request)
|
||||
{
|
||||
return (new LoginService)->handle();
|
||||
}
|
||||
}
|
||||
27
app/Controller/IndexController.php
Normal file
27
app/Controller/IndexController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = $this->request->input('user', 'Hyperf');
|
||||
$method = $this->request->getMethod();
|
||||
|
||||
return [
|
||||
'method' => $method,
|
||||
'message' => "Hello {$user}.",
|
||||
];
|
||||
}
|
||||
}
|
||||
29
app/Exception/BusinessException.php
Normal file
29
app/Exception/BusinessException.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use App\Constants\ErrorCode;
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
use Throwable;
|
||||
|
||||
class BusinessException extends ServerException
|
||||
{
|
||||
public function __construct(int $code = 0, string $message = null, Throwable $previous = null)
|
||||
{
|
||||
if (is_null($message)) {
|
||||
$message = ErrorCode::getMessage($code);
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
14
app/Exception/ErrException.php
Normal file
14
app/Exception/ErrException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use App\Constants\ResultCode;
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
|
||||
class ErrException extends ServerException
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $code = ResultCode::ERROR;
|
||||
}
|
||||
38
app/Exception/Handler/AppExceptionHandler.php
Normal file
38
app/Exception/Handler/AppExceptionHandler.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
class AppExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
public function __construct(protected StdoutLoggerInterface $logger)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Throwable $throwable, ResponseInterface $response)
|
||||
{
|
||||
$this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
|
||||
$this->logger->error($throwable->getTraceAsString());
|
||||
return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
71
app/Exception/Handler/BaseErrExceptionHandler.php
Normal file
71
app/Exception/Handler/BaseErrExceptionHandler.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
|
||||
use App\Lib\Return\AdminReturn;
|
||||
use App\Lib\Return\ApiReturn;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\HttpServer\Request;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
abstract class BaseErrExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Request $request,
|
||||
private readonly ContainerInterface $container,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param Throwable $e
|
||||
* @param ResponseInterface $response
|
||||
* @return MessageInterface|ResponseInterface
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function handlerResponse(
|
||||
Throwable $e,
|
||||
ResponseInterface $response
|
||||
): MessageInterface|ResponseInterface
|
||||
{
|
||||
// 从注解获取响应格式(优先于路径解析)
|
||||
$format = $this->request->getAttribute('response_format') ?? $this->repairResponseFormatByPath();
|
||||
|
||||
// 动态选择策略
|
||||
$returnClass = match ($format) {
|
||||
'admin', 'common' => AdminReturn::class,
|
||||
'api' => ApiReturn::class,
|
||||
default => null,
|
||||
};
|
||||
if (!$returnClass) return $response;
|
||||
|
||||
/**
|
||||
* @var AdminReturn|ApiReturn $returnObj
|
||||
*/
|
||||
$returnObj = $this->container->get($returnClass);
|
||||
$result = $returnObj->error($e->getMessage(), $e->getCode());
|
||||
$this->stopPropagation();
|
||||
return $response->withHeader("Content-Type", "application/json")
|
||||
->withStatus(200)
|
||||
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function repairResponseFormatByPath(): string
|
||||
{
|
||||
// 兜底逻辑:根据路径前缀推断
|
||||
return match (explode('/', $this->request->path())[0] ?? '') {
|
||||
'admin', 'common' => 'admin',
|
||||
'api' => 'api',
|
||||
default => 'default',
|
||||
};
|
||||
}
|
||||
}
|
||||
35
app/Exception/Handler/ErrExceptionHandler.php
Normal file
35
app/Exception/Handler/ErrExceptionHandler.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use App\Exception\ErrException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
class ErrExceptionHandler extends BaseErrExceptionHandler
|
||||
{
|
||||
/**
|
||||
* @param Throwable $throwable
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
return $throwable instanceof ErrException
|
||||
? $this->handlerResponse($throwable, $response)
|
||||
: $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $throwable
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return $throwable instanceof ErrException;
|
||||
}
|
||||
}
|
||||
35
app/Exception/Handler/ValidationExceptionHandler.php
Normal file
35
app/Exception/Handler/ValidationExceptionHandler.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use Hyperf\Validation\ValidationException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
class ValidationExceptionHandler extends BaseErrExceptionHandler
|
||||
{
|
||||
/**
|
||||
* @param Throwable $throwable
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
return $throwable instanceof ValidationException
|
||||
? $this->handlerResponse($throwable, $response)
|
||||
: $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $throwable
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return $throwable instanceof ValidationException;
|
||||
}
|
||||
}
|
||||
24
app/Interface/JwtInterface.php
Normal file
24
app/Interface/JwtInterface.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interface;
|
||||
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
|
||||
interface JwtInterface
|
||||
{
|
||||
public function builderAccessToken(string $sub, ?\Closure $callable = null): UnencryptedToken;
|
||||
|
||||
public function builderRefreshToken(string $sub, ?\Closure $callable = null): UnencryptedToken;
|
||||
|
||||
public function parserAccessToken(string $accessToken): UnencryptedToken;
|
||||
|
||||
public function parserRefreshToken(string $refreshToken): UnencryptedToken;
|
||||
|
||||
public function addBlackList(UnencryptedToken $token): bool;
|
||||
|
||||
public function hasBlackList(UnencryptedToken $token): bool;
|
||||
|
||||
public function removeBlackList(UnencryptedToken $token): bool;
|
||||
|
||||
public function getConfig(string $key, mixed $default = null): mixed;
|
||||
}
|
||||
233
app/Lib/Jwt/AbstractJwt.php
Normal file
233
app/Lib/Jwt/AbstractJwt.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use App\Interface\JwtInterface;
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Cache\CacheManager;
|
||||
use Hyperf\Cache\Driver\DriverInterface;
|
||||
use Hyperf\Collection\Arr;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\JwtFacade;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Lcobucci\JWT\Validation\Constraint;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\Constraint\StrictValidAt;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
|
||||
abstract class AbstractJwt implements JwtInterface
|
||||
{
|
||||
/**
|
||||
* @param array $config
|
||||
* @param CacheManager $cacheManager
|
||||
* @param Clock $clock
|
||||
* @param AccessTokenConstraint $accessTokenConstraint
|
||||
* @param RefreshTokenConstraint $refreshTokenConstraint
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $config,
|
||||
private readonly CacheManager $cacheManager,
|
||||
private readonly Clock $clock,
|
||||
private readonly AccessTokenConstraint $accessTokenConstraint,
|
||||
private readonly RefreshTokenConstraint $refreshTokenConstraint
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string $sub
|
||||
* @param \Closure|null $callable
|
||||
* @return UnencryptedToken
|
||||
*/
|
||||
public function builderAccessToken(string $sub, ?\Closure $callable = null): UnencryptedToken
|
||||
{
|
||||
return $this->getJwtFacade()->issue(
|
||||
$this->getSigner(),
|
||||
$this->getSigningKey(),
|
||||
function (Builder $builder, \DateTimeImmutable $immutable) use ($sub, $callable) {
|
||||
$builder = $builder->identifiedBy($sub);
|
||||
if ($callable !== null) {
|
||||
$builder = $callable($builder);
|
||||
}
|
||||
return $builder->expiresAt($this->getExpireAt($immutable));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sub
|
||||
* @param \Closure|null $callable
|
||||
* @return UnencryptedToken
|
||||
*/
|
||||
public function builderRefreshToken(string $sub, ?\Closure $callable = null): UnencryptedToken
|
||||
{
|
||||
return $this->getJwtFacade()->issue(
|
||||
$this->getSigner(),
|
||||
$this->getSigningKey(),
|
||||
function (Builder $builder, \DateTimeImmutable $immutable) use ($sub, $callable) {
|
||||
$builder = $builder->identifiedBy($sub);
|
||||
$builder = $builder->expiresAt($this->getRefreshExpireAt($immutable));
|
||||
if ($callable !== null) {
|
||||
$builder = $callable($builder);
|
||||
}
|
||||
return $builder->relatedTo('refresh');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
* @return UnencryptedToken
|
||||
*/
|
||||
public function parserAccessToken(string $accessToken): UnencryptedToken
|
||||
{
|
||||
return $this->getJwtFacade()
|
||||
->parse(
|
||||
$accessToken,
|
||||
new SignedWith(
|
||||
$this->getSigner(),
|
||||
$this->getSigningKey()
|
||||
),
|
||||
new StrictValidAt(
|
||||
$this->clock,
|
||||
$this->clock->now()->diff($this->getExpireAt($this->clock->now()))
|
||||
),
|
||||
$this->getBlackListConstraint(),
|
||||
$this->refreshTokenConstraint
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
* @return UnencryptedToken
|
||||
*/
|
||||
public function parserRefreshToken(string $refreshToken): UnencryptedToken
|
||||
{
|
||||
return $this->getJwtFacade()
|
||||
->parse(
|
||||
$refreshToken,
|
||||
new SignedWith(
|
||||
$this->getSigner(),
|
||||
$this->getSigningKey()
|
||||
),
|
||||
new StrictValidAt(
|
||||
$this->clock,
|
||||
$this->clock->now()->diff($this->getRefreshExpireAt($this->clock->now()))
|
||||
),
|
||||
$this->getBlackListConstraint(),
|
||||
$this->accessTokenConstraint
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UnencryptedToken $token
|
||||
* @return bool
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function addBlackList(UnencryptedToken $token): bool
|
||||
{
|
||||
return $this->getCacheDriver()->set($token->toString(), 1, $this->getBlackConfig('ttl', 600));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UnencryptedToken $token
|
||||
* @return bool
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function hasBlackList(UnencryptedToken $token): bool
|
||||
{
|
||||
return $this->getCacheDriver()->has($token->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UnencryptedToken $token
|
||||
* @return bool
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function removeBlackList(UnencryptedToken $token): bool
|
||||
{
|
||||
return $this->getCacheDriver()->delete($token->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return Arr::get($this->config, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JwtFacade
|
||||
*/
|
||||
private function getJwtFacade(): JwtFacade
|
||||
{
|
||||
return new JwtFacade(clock: $this->clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Signer
|
||||
*/
|
||||
private function getSigner(): Signer
|
||||
{
|
||||
return Arr::get($this->config, 'alg');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Key
|
||||
*/
|
||||
private function getSigningKey(): Key
|
||||
{
|
||||
return Arr::get($this->config, 'key');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DriverInterface
|
||||
*/
|
||||
private function getCacheDriver(): DriverInterface
|
||||
{
|
||||
return $this->cacheManager->getDriver($this->getBlackConfig('connection'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
private function getBlackConfig(string $name, mixed $default = null): mixed
|
||||
{
|
||||
return Arr::get($this->config, 'blacklist.' . $name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Constraint
|
||||
*/
|
||||
private function getBlackListConstraint(): Constraint
|
||||
{
|
||||
return new BlackListConstraint((bool) $this->getBlackConfig('enable', false), $this->getCacheDriver());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeImmutable $immutable
|
||||
* @return \DateTimeImmutable
|
||||
*/
|
||||
private function getExpireAt(\DateTimeImmutable $immutable): \DateTimeImmutable
|
||||
{
|
||||
return Carbon::create($immutable)
|
||||
->addSeconds(Arr::get($this->config, 'ttl', 3600))
|
||||
->toDateTimeImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeImmutable $immutable
|
||||
* @return \DateTimeImmutable
|
||||
*/
|
||||
private function getRefreshExpireAt(\DateTimeImmutable $immutable): \DateTimeImmutable
|
||||
{
|
||||
return Carbon::create($immutable)
|
||||
->addSeconds(Arr::get($this->config, 'refresh_ttl', 7200))
|
||||
->toDateTimeImmutable();
|
||||
}
|
||||
}
|
||||
16
app/Lib/Jwt/AccessTokenConstraint.php
Normal file
16
app/Lib/Jwt/AccessTokenConstraint.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Validation\Constraint;
|
||||
use Lcobucci\JWT\Validation\ConstraintViolation;
|
||||
|
||||
class AccessTokenConstraint implements Constraint
|
||||
{
|
||||
|
||||
public function assert(Token $token): void
|
||||
{
|
||||
if (! $token->isRelatedTo('refresh')) throw ConstraintViolation::error('Token is not a refresh token', $this);
|
||||
}
|
||||
}
|
||||
33
app/Lib/Jwt/BlackListConstraint.php
Normal file
33
app/Lib/Jwt/BlackListConstraint.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use Hyperf\Cache\Driver\DriverInterface;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Validation\Constraint;
|
||||
use Lcobucci\JWT\Validation\ConstraintViolation;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
|
||||
class BlackListConstraint implements Constraint
|
||||
{
|
||||
/**
|
||||
* @param bool $enable
|
||||
* @param DriverInterface $cache
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly bool $enable,
|
||||
private readonly DriverInterface $cache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param Token $token
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function assert(Token $token): void
|
||||
{
|
||||
if ($this->enable !== true) return;
|
||||
|
||||
if ($this->cache->has($token->toString())) throw ConstraintViolation::error('Token is in blacklist', $this);
|
||||
}
|
||||
}
|
||||
15
app/Lib/Jwt/Clock.php
Normal file
15
app/Lib/Jwt/Clock.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTimeImmutable;
|
||||
use Psr\Clock\ClockInterface;
|
||||
|
||||
class Clock implements ClockInterface
|
||||
{
|
||||
public function now(): DateTimeImmutable
|
||||
{
|
||||
return Carbon::now()->toDateTimeImmutable();
|
||||
}
|
||||
}
|
||||
7
app/Lib/Jwt/Jwt.php
Normal file
7
app/Lib/Jwt/Jwt.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use App\Interface\JwtInterface;
|
||||
|
||||
final class Jwt extends AbstractJwt implements JwtInterface {}
|
||||
51
app/Lib/Jwt/JwtFactory.php
Normal file
51
app/Lib/Jwt/JwtFactory.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use App\Interface\JwtInterface;
|
||||
use Hyperf\Collection\Arr;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
final class JwtFactory
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConfigInterface $config,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return JwtInterface
|
||||
*/
|
||||
public function get(string $name = 'default'): JwtInterface
|
||||
{
|
||||
return make(Jwt::class, [
|
||||
'config' => $this->getConfig($name),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景配置
|
||||
* @param string $scene
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig(string $scene): array
|
||||
{
|
||||
if ($scene === 'default') {
|
||||
return $this->config->get($this->getConfigKey());
|
||||
}
|
||||
return Arr::merge(
|
||||
$this->config->get($this->getConfigKey()),
|
||||
$this->config->get($this->getConfigKey($scene), [])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
private function getConfigKey(string $name = 'default'): string
|
||||
{
|
||||
return 'jwt.' . $name;
|
||||
}
|
||||
}
|
||||
15
app/Lib/Jwt/RefreshTokenConstraint.php
Normal file
15
app/Lib/Jwt/RefreshTokenConstraint.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Jwt;
|
||||
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Validation\Constraint;
|
||||
use Lcobucci\JWT\Validation\ConstraintViolation;
|
||||
|
||||
class RefreshTokenConstraint implements Constraint
|
||||
{
|
||||
public function assert(Token $token): void
|
||||
{
|
||||
if ($token->isRelatedTo('refresh')) throw ConstraintViolation::error('Token is a refresh token', $this);
|
||||
}
|
||||
}
|
||||
16
app/Lib/Return/AdminReturn.php
Normal file
16
app/Lib/Return/AdminReturn.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Return;
|
||||
|
||||
class AdminReturn extends CommonReturn
|
||||
{
|
||||
/**
|
||||
* 通用返回
|
||||
* @param array $res
|
||||
* @return array
|
||||
*/
|
||||
final protected function afterSuccess(array $res): array
|
||||
{
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
16
app/Lib/Return/ApiReturn.php
Normal file
16
app/Lib/Return/ApiReturn.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Return;
|
||||
|
||||
class ApiReturn extends CommonReturn
|
||||
{
|
||||
/**
|
||||
* 通用返回
|
||||
* @param array $res
|
||||
* @return array
|
||||
*/
|
||||
final protected function afterSuccess(array $res): array
|
||||
{
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
53
app/Lib/Return/CommonReturn.php
Normal file
53
app/Lib/Return/CommonReturn.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Lib\Return;
|
||||
|
||||
use App\Constants\ResultCode;
|
||||
|
||||
abstract class CommonReturn
|
||||
{
|
||||
/**
|
||||
* 通用 success 返回
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @param int|ResultCode $code
|
||||
* @param array $debug
|
||||
* @return array
|
||||
*/
|
||||
final public function success(string $msg = 'success', array $data = [], ResultCode|int $code = ResultCode::SUCCESS, array $debug = []): array
|
||||
{
|
||||
$res = [
|
||||
'code' => $code,
|
||||
'message' => $msg,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
return $this->afterSuccess(array_merge($res, $debug));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 fail 返回
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @param int|ResultCode $code
|
||||
* @param array $debug
|
||||
* @return array
|
||||
*/
|
||||
final public function error(string $msg = 'failed', ResultCode|int $code = ResultCode::ERROR, array $data = [], array $debug = []): array
|
||||
{
|
||||
$res = [
|
||||
'code' => $code,
|
||||
'message' => $msg,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
return $this->afterSuccess(array_merge($res, $debug));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用类调子类返回方便切面类识别
|
||||
* @param array $res
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function afterSuccess(array $res): array;
|
||||
}
|
||||
66
app/Listener/DbQueryExecutedListener.php
Normal file
66
app/Listener/DbQueryExecutedListener.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Collection\Arr;
|
||||
use Hyperf\Database\Events\QueryExecuted;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[Listener]
|
||||
class DbQueryExecutedListener implements ListenerInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->logger = $container->get(LoggerFactory::class)->get('sql');
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
QueryExecuted::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryExecuted $event
|
||||
*/
|
||||
public function process(object $event): void
|
||||
{
|
||||
if ($event instanceof QueryExecuted) {
|
||||
$sql = $event->sql;
|
||||
if (! Arr::isAssoc($event->bindings)) {
|
||||
$position = 0;
|
||||
foreach ($event->bindings as $value) {
|
||||
$position = strpos($sql, '?', $position);
|
||||
if ($position === false) {
|
||||
break;
|
||||
}
|
||||
$value = "'{$value}'";
|
||||
$sql = substr_replace($sql, $value, $position, 1);
|
||||
$position += strlen($value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info(sprintf('[%s] %s', $event->time, $sql));
|
||||
}
|
||||
}
|
||||
}
|
||||
74
app/Listener/QueueHandleListener.php
Normal file
74
app/Listener/QueueHandleListener.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\AsyncQueue\AnnotationJob;
|
||||
use Hyperf\AsyncQueue\Event\AfterHandle;
|
||||
use Hyperf\AsyncQueue\Event\BeforeHandle;
|
||||
use Hyperf\AsyncQueue\Event\Event;
|
||||
use Hyperf\AsyncQueue\Event\FailedHandle;
|
||||
use Hyperf\AsyncQueue\Event\RetryHandle;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[Listener]
|
||||
class QueueHandleListener implements ListenerInterface
|
||||
{
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->logger = $container->get(LoggerFactory::class)->get('queue');
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
AfterHandle::class,
|
||||
BeforeHandle::class,
|
||||
FailedHandle::class,
|
||||
RetryHandle::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
if ($event instanceof Event && $event->getMessage()->job()) {
|
||||
$job = $event->getMessage()->job();
|
||||
$jobClass = get_class($job);
|
||||
if ($job instanceof AnnotationJob) {
|
||||
$jobClass = sprintf('Job[%s@%s]', $job->class, $job->method);
|
||||
}
|
||||
$date = date('Y-m-d H:i:s');
|
||||
|
||||
switch (true) {
|
||||
case $event instanceof BeforeHandle:
|
||||
$this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass));
|
||||
break;
|
||||
case $event instanceof AfterHandle:
|
||||
$this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass));
|
||||
break;
|
||||
case $event instanceof FailedHandle:
|
||||
$this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass));
|
||||
$this->logger->error((string) $event->getThrowable());
|
||||
break;
|
||||
case $event instanceof RetryHandle:
|
||||
$this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
app/Listener/ResumeExitCoordinatorListener.php
Normal file
35
app/Listener/ResumeExitCoordinatorListener.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Command\Event\AfterExecute;
|
||||
use Hyperf\Coordinator\Constants;
|
||||
use Hyperf\Coordinator\CoordinatorManager;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
|
||||
#[Listener]
|
||||
class ResumeExitCoordinatorListener implements ListenerInterface
|
||||
{
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
AfterExecute::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
|
||||
}
|
||||
}
|
||||
113
app/Model/AdminMenu.php
Normal file
113
app/Model/AdminMenu.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
use App\Constants\Model\AdminUser\AdminMenuStatusCode;
|
||||
use App\Model\Meta\AdminUserMeta;
|
||||
use App\Model\Meta\MetaCast;
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Database\Model\Events\Deleting;
|
||||
use Hyperf\Database\Model\Relations\BelongsToMany;
|
||||
use Hyperf\Database\Model\Relations\HasMany;
|
||||
use Hyperf\Database\Model\Collection;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $parent_id
|
||||
* @property string $name
|
||||
* @property AdminUserMeta $meta
|
||||
* @property string $path
|
||||
* @property string $component
|
||||
* @property string $redirect
|
||||
* @property int $status
|
||||
* @property int $sort
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property string $remark
|
||||
* @property Collection|AdminRole[] $roles
|
||||
* @property Collection|AdminMenu[] $children
|
||||
*/
|
||||
class AdminMenu extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_menu';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [
|
||||
'id',
|
||||
'parent_id',
|
||||
'name',
|
||||
'component',
|
||||
'redirect',
|
||||
'status',
|
||||
'sort',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'remark',
|
||||
'meta',
|
||||
'path',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = [
|
||||
'id' => 'integer',
|
||||
'parent_id' => 'integer',
|
||||
'status' => AdminMenuStatusCode::class,
|
||||
'sort' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'meta' => MetaCast::class,
|
||||
'path' => 'string',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 通过中间表获取角色.
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
AdminRole::class,
|
||||
'admin_role_belongs_menu',
|
||||
'menu_id',
|
||||
'role_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this
|
||||
->hasMany(self::class, 'parent_id', 'id')
|
||||
->where('status', AdminMenuStatusCode::Normal)
|
||||
->orderBy('sort')
|
||||
->with('children');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Deleting $event
|
||||
* @return void
|
||||
*/
|
||||
public function deleting(Deleting $event): void
|
||||
{
|
||||
$this->roles()->detach();
|
||||
}
|
||||
}
|
||||
96
app/Model/AdminRole.php
Normal file
96
app/Model/AdminRole.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
use Hyperf\Database\Model\Events\Deleting;
|
||||
use Hyperf\Database\Model\Relations\BelongsToMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $code
|
||||
* @property int $status
|
||||
* @property int $sort
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string $remark
|
||||
*/
|
||||
class AdminRole extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_role';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [
|
||||
'id',
|
||||
'name',
|
||||
'code',
|
||||
'status',
|
||||
'sort',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'remark'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = [
|
||||
'id' => 'integer',
|
||||
'status' => 'integer',
|
||||
'sort' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function adminMenus(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
AdminMenu::class,
|
||||
'role_belongs_menu',
|
||||
'role_id',
|
||||
'menu_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function adminUsers(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
AdminUser::class,
|
||||
'admin_user_belongs_role',
|
||||
'role_id',
|
||||
'user_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Deleting $event
|
||||
* @return void
|
||||
*/
|
||||
public function deleting(Deleting $event): void
|
||||
{
|
||||
$this->adminUsers()->detach();
|
||||
$this->adminMenus()->detach();
|
||||
}
|
||||
}
|
||||
32
app/Model/AdminRoleBelongsMenu.php
Normal file
32
app/Model/AdminRoleBelongsMenu.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $admin_role_id
|
||||
* @property int $admin_menu_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class AdminRoleBelongsMenu extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_role_belongs_menu';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'admin_role_id' => 'integer', 'admin_menu_id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
|
||||
}
|
||||
189
app/Model/AdminUser.php
Normal file
189
app/Model/AdminUser.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
use App\Constants\Model\AdminUser\AdminUserStatusCode;
|
||||
use App\Constants\Model\AdminUser\AdminUserTypeCode;
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Collection\Enumerable;
|
||||
use Hyperf\Database\Model\Collection;
|
||||
use Hyperf\Database\Model\Events\Creating;
|
||||
use Hyperf\Database\Model\Events\Deleted;
|
||||
use Hyperf\Database\Model\Relations\BelongsToMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $username
|
||||
* @property string $password
|
||||
* @property string $user_type
|
||||
* @property string $nickname
|
||||
* @property string $phone
|
||||
* @property string $email
|
||||
* @property string $avatar
|
||||
* @property string $signed
|
||||
* @property AdminUserStatusCode $status
|
||||
* @property string $login_ip
|
||||
* @property string $login_time
|
||||
* @property string $backend_setting
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property string $remark
|
||||
*/
|
||||
class AdminUser extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_user';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [
|
||||
'id',
|
||||
'username',
|
||||
'password',
|
||||
'user_type',
|
||||
'nickname',
|
||||
'phone',
|
||||
'email',
|
||||
'avatar',
|
||||
'signed',
|
||||
'status',
|
||||
'login_ip',
|
||||
'login_time',
|
||||
'backend_setting',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'remark'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = [
|
||||
'id' => 'integer',
|
||||
'status' => AdminUserStatusCode::class,
|
||||
'user_type' => AdminUserTypeCode::class,
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'backend_setting' => 'json',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 隐藏的字段列表.
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $hidden = ['password'];
|
||||
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
AdminRole::class,
|
||||
'admin_user_belongs_role',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Creating $event
|
||||
* @return void
|
||||
*/
|
||||
public function creating(Creating $event): void
|
||||
{
|
||||
if (!$this->isDirty('password')) $this->resetPassword();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Deleted $event
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Deleted $event): void
|
||||
{
|
||||
$this->roles()->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function resetPassword(): void
|
||||
{
|
||||
$this->password = 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password
|
||||
* @return void
|
||||
*/
|
||||
public function setPasswordAttribute(string $password): void
|
||||
{
|
||||
$this->attributes['password'] = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyPassword(string $password): bool
|
||||
{
|
||||
return password_verify($password, $this->password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSuperAdmin(): bool
|
||||
{
|
||||
return $this->roles()->where('code','SuperAdmin')->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRoles(array $fields): Collection
|
||||
{
|
||||
return $this->roles()
|
||||
->where('status',AdminUserStatusCode::Normal)
|
||||
->select($fields)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Hyperf\Collection\Collection|Enumerable|Collection<int, AdminMenu>
|
||||
*/
|
||||
public function getPermissions(): Collection|Enumerable|\Hyperf\Collection\Collection
|
||||
{
|
||||
return $this->roles()
|
||||
->with('adminMenus')
|
||||
->orderBy('sort')
|
||||
->get()
|
||||
->pluck('adminMenus')
|
||||
->flatten();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
return $this->roles()
|
||||
->whereRelation('adminMenus','name',$permission)
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
27
app/Model/AdminUserBelongsMenu.php
Normal file
27
app/Model/AdminUserBelongsMenu.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class AdminUserBelongsMenu extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_user_belongs_menu';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = [];
|
||||
}
|
||||
32
app/Model/AdminUserBelongsRole.php
Normal file
32
app/Model/AdminUserBelongsRole.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $admin_user_id
|
||||
* @property int $admin_role_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class AdminUserBelongsRole extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_user_belongs_role';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'admin_user_id' => 'integer', 'admin_role_id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
|
||||
}
|
||||
37
app/Model/AdminUserLoginLog.php
Normal file
37
app/Model/AdminUserLoginLog.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $admin_user_id
|
||||
* @property string $username
|
||||
* @property string $ip
|
||||
* @property string $os
|
||||
* @property string $browser
|
||||
* @property int $status
|
||||
* @property string $message
|
||||
* @property string $login_time
|
||||
* @property string $remark
|
||||
*/
|
||||
class AdminUserLoginLog extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_user_login_log';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'admin_user_id' => 'integer', 'status' => 'integer'];
|
||||
}
|
||||
37
app/Model/AdminUserOperationLog.php
Normal file
37
app/Model/AdminUserOperationLog.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $admin_user_id
|
||||
* @property string $username
|
||||
* @property string $method
|
||||
* @property string $router
|
||||
* @property string $service_name
|
||||
* @property string $ip
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string $remark
|
||||
*/
|
||||
class AdminUserOperationLog extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'admin_user_operation_log';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'admin_user_id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
|
||||
}
|
||||
78
app/Model/Meta/AdminUserMeta.php
Normal file
78
app/Model/Meta/AdminUserMeta.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model\Meta;
|
||||
|
||||
use App\Model\Model;
|
||||
|
||||
/**
|
||||
* @property string $title 标题
|
||||
* @property string $i18n 国际化
|
||||
* @property string $badge 徽章
|
||||
* @property string $icon 图标
|
||||
* @property bool $affix 是否固定
|
||||
* @property bool $hidden 是否隐藏
|
||||
* @property string $type 类型
|
||||
* @property bool $cache 是否缓存
|
||||
* @property bool $copyright 是否显示版权
|
||||
* @property string $link 链接
|
||||
* @property string $componentPath 视图文件类型
|
||||
* @property string $componentSuffix 视图前缀路径
|
||||
* @property string $breadcrumbEnable 是否显示面包屑
|
||||
* @property string $activeName 激活高亮的菜单标识
|
||||
* @property string $auth 前端权限判断,允许访问的权限码
|
||||
* @property string $role 前端权限判断,允许访问的角色码
|
||||
* @property string $user 前端权限判断,允许访问的用户名
|
||||
*/
|
||||
final class AdminUserMeta extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $incrementing = false;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected array $fillable = [
|
||||
'title',
|
||||
'i18n',
|
||||
'badge',
|
||||
'icon',
|
||||
'affix',
|
||||
'hidden',
|
||||
'type',
|
||||
'cache',
|
||||
'copyright',
|
||||
'breadcrumbEnable',
|
||||
'componentPath',
|
||||
'componentSuffix',
|
||||
'link',
|
||||
'activeName',
|
||||
'auth',
|
||||
'role',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected array $casts = [
|
||||
'affix' => 'boolean',
|
||||
'hidden' => 'boolean',
|
||||
'cache' => 'boolean',
|
||||
'copyright' => 'boolean',
|
||||
'breadcrumbEnable' => 'boolean',
|
||||
'title' => 'string',
|
||||
'componentPath' => 'string',
|
||||
'componentSuffix' => 'string',
|
||||
'i18n' => 'string',
|
||||
'badge' => 'string',
|
||||
'icon' => 'string',
|
||||
'type' => 'string',
|
||||
'link' => 'string',
|
||||
'activeName' => 'string',
|
||||
'auth' => 'array',
|
||||
'role' => 'array',
|
||||
'user' => 'array',
|
||||
];
|
||||
}
|
||||
33
app/Model/Meta/MetaCast.php
Normal file
33
app/Model/Meta/MetaCast.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model\Meta;
|
||||
|
||||
use Hyperf\Codec\Json;
|
||||
use Hyperf\Contract\CastsAttributes;
|
||||
|
||||
class MetaCast implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* @param $model
|
||||
* @param string $key
|
||||
* @param $value
|
||||
* @param array $attributes
|
||||
* @return AdminUserMeta
|
||||
*/
|
||||
public function get($model, string $key, $value, array $attributes): AdminUserMeta
|
||||
{
|
||||
return new AdminUserMeta(empty($value) ? [] : Json::decode($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $model
|
||||
* @param string $key
|
||||
* @param $value
|
||||
* @param array $attributes
|
||||
* @return array|string
|
||||
*/
|
||||
public function set($model, string $key, $value, array $attributes): array|string
|
||||
{
|
||||
return Json::encode($value);
|
||||
}
|
||||
}
|
||||
22
app/Model/Model.php
Normal file
22
app/Model/Model.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Hyperf\DbConnection\Model\Model as BaseModel;
|
||||
use Hyperf\ModelCache\Cacheable;
|
||||
use Hyperf\ModelCache\CacheableInterface;
|
||||
|
||||
abstract class Model extends BaseModel implements CacheableInterface
|
||||
{
|
||||
use Cacheable;
|
||||
}
|
||||
21
app/Process/AsyncQueueConsumer.php
Normal file
21
app/Process/AsyncQueueConsumer.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Process;
|
||||
|
||||
use Hyperf\AsyncQueue\Process\ConsumerProcess;
|
||||
use Hyperf\Process\Annotation\Process;
|
||||
|
||||
#[Process]
|
||||
class AsyncQueueConsumer extends ConsumerProcess
|
||||
{
|
||||
}
|
||||
34
app/Repository/AdminUserRepository.php
Normal file
34
app/Repository/AdminUserRepository.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* This service file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Model\AdminUser;
|
||||
|
||||
/**
|
||||
* Class AdminUserRepository
|
||||
* @extends BaseRepository<AdminUser>
|
||||
*/
|
||||
final class AdminUserRepository extends BaseRepository
|
||||
{
|
||||
public function __construct(protected readonly AdminUser $model) {}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @return AdminUser|null
|
||||
*/
|
||||
public function findByUserName(string $username): AdminUser|null
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->model->newQuery()
|
||||
->where('username', $username)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
151
app/Repository/BaseRepository.php
Normal file
151
app/Repository/BaseRepository.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Repository\Traits\BootTrait;
|
||||
use App\Repository\Traits\RepositoryOrderByTrait;
|
||||
use Hyperf\Collection\Collection;
|
||||
use Hyperf\Contract\LengthAwarePaginatorInterface;
|
||||
use Hyperf\Database\Model\Builder;
|
||||
use Hyperf\Database\Model\Model;
|
||||
use Hyperf\DbConnection\Traits\HasContainer;
|
||||
use Hyperf\Paginator\AbstractPaginator;
|
||||
|
||||
/**
|
||||
* @template T of Model
|
||||
* @property T $model
|
||||
*/
|
||||
abstract class BaseRepository
|
||||
{
|
||||
use BootTrait;
|
||||
use HasContainer;
|
||||
use RepositoryOrderByTrait;
|
||||
|
||||
public const string PER_PAGE_PARAM_NAME = 'per_page';
|
||||
public function handleSearch(Builder $query, array $params): Builder
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function handleItems(Collection $items): Collection
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function handlePage(LengthAwarePaginatorInterface $paginator): array
|
||||
{
|
||||
if ($paginator instanceof AbstractPaginator) {
|
||||
$items = $paginator->getCollection();
|
||||
} else {
|
||||
$items = Collection::make($paginator->items());
|
||||
}
|
||||
$items = $this->handleItems($items);
|
||||
return [
|
||||
'list' => $items->toArray(),
|
||||
'total' => $paginator->total(),
|
||||
];
|
||||
}
|
||||
|
||||
public function list(array $params = []): Collection
|
||||
{
|
||||
return $this->handleItems($this->perQuery($this->getQuery(), $params)->get());
|
||||
}
|
||||
|
||||
public function count(array $params = []): int
|
||||
{
|
||||
return $this->perQuery($this->getQuery(), $params)->count();
|
||||
}
|
||||
|
||||
public function page(array $params = [], ?int $page = null, ?int $pageSize = null): array
|
||||
{
|
||||
$result = $this->perQuery($this->getQuery(), $params)->paginate(
|
||||
perPage: $pageSize,
|
||||
pageName: static::PER_PAGE_PARAM_NAME,
|
||||
page: $page,
|
||||
);
|
||||
return $this->handlePage($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function create(array $data): mixed
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->getQuery()->create($data);
|
||||
}
|
||||
|
||||
public function updateById(mixed $id, array $data): bool
|
||||
{
|
||||
return (bool) $this->getQuery()->whereKey($id)->first()?->update($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|T
|
||||
*/
|
||||
public function saveById(mixed $id, array $data): mixed
|
||||
{
|
||||
$model = $this->getQuery()->whereKey($id)->first();
|
||||
if ($model) {
|
||||
$model->fill($data)->save();
|
||||
return $model;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function deleteById(mixed $id): int
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->model::destroy($id);
|
||||
}
|
||||
|
||||
public function forceDeleteById(mixed $id): bool
|
||||
{
|
||||
return (bool) $this->getQuery()->whereKey($id)->forceDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|T
|
||||
*/
|
||||
public function findById(mixed $id): mixed
|
||||
{
|
||||
return $this->getQuery()->whereKey($id)->first();
|
||||
}
|
||||
|
||||
public function findByField(mixed $id, string $field): mixed
|
||||
{
|
||||
return $this->getQuery()->whereKey($id)->value($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|T
|
||||
*/
|
||||
public function findByFilter(array $params): mixed
|
||||
{
|
||||
return $this->perQuery($this->getQuery(), $params)->first();
|
||||
}
|
||||
|
||||
public function perQuery(Builder $query, array $params): Builder
|
||||
{
|
||||
$this->startBoot($query, $params);
|
||||
return $this->handleSearch($query, $params);
|
||||
}
|
||||
|
||||
public function getQuery(): Builder
|
||||
{
|
||||
return $this->model->newQuery();
|
||||
}
|
||||
|
||||
public function existsById(mixed $id): bool
|
||||
{
|
||||
return (bool) $this->getQuery()->whereKey($id)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function getModel(): Model
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
}
|
||||
20
app/Repository/Traits/BootTrait.php
Normal file
20
app/Repository/Traits/BootTrait.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\Traits;
|
||||
|
||||
use function Hyperf\Support\class_basename;
|
||||
use function Hyperf\Support\class_uses_recursive;
|
||||
|
||||
trait BootTrait
|
||||
{
|
||||
protected function startBoot(...$params): void
|
||||
{
|
||||
$traits = class_uses_recursive(static::class);
|
||||
foreach ($traits as $trait) {
|
||||
$method = 'boot' . class_basename($trait);
|
||||
if (method_exists($this, $method)) {
|
||||
$this->{$method}(...$params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
app/Repository/Traits/RepositoryOrderByTrait.php
Normal file
38
app/Repository/Traits/RepositoryOrderByTrait.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\Traits;
|
||||
|
||||
use Hyperf\Database\Model\Builder;
|
||||
|
||||
trait RepositoryOrderByTrait
|
||||
{
|
||||
public function handleOrderBy(Builder $query, $params): Builder
|
||||
{
|
||||
if ($this->enablePageOrderBy()) {
|
||||
$orderByField = $params[$this->getOrderByParamName()] ?? $query->getModel()->getKeyName();
|
||||
$orderByDirection = $params[$this->getOrderByDirectionParamName()] ?? 'desc';
|
||||
$query->orderBy($orderByField, $orderByDirection);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function bootRepositoryOrderByTrait(Builder $query, array $params): void
|
||||
{
|
||||
$this->handleOrderBy($query, $params);
|
||||
}
|
||||
|
||||
protected function getOrderByParamName(): string
|
||||
{
|
||||
return 'order_by';
|
||||
}
|
||||
|
||||
protected function getOrderByDirectionParamName(): string
|
||||
{
|
||||
return 'order_by_direction';
|
||||
}
|
||||
|
||||
protected function enablePageOrderBy(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
28
app/Request/Admin/LoginRequest.php
Normal file
28
app/Request/Admin/LoginRequest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request\Admin;
|
||||
|
||||
use Hyperf\Validation\Request\FormRequest;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
52
app/Service/Admin/BaseAdminService.php
Normal file
52
app/Service/Admin/BaseAdminService.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* This service file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Admin;
|
||||
|
||||
use App\Lib\Return\AdminReturn;
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
|
||||
abstract class BaseAdminService
|
||||
{
|
||||
/**
|
||||
* 请求对象注入
|
||||
* @var RequestInterface
|
||||
*/
|
||||
#[Inject]
|
||||
protected RequestInterface $request;
|
||||
|
||||
/**
|
||||
* 返回对象注入
|
||||
* @var AdminReturn
|
||||
*/
|
||||
#[Inject]
|
||||
protected AdminReturn $adminReturn;
|
||||
|
||||
/**
|
||||
* 管理员 id
|
||||
* @var int
|
||||
*/
|
||||
protected int $adminId = 0;
|
||||
|
||||
/**
|
||||
* 主构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->adminId = Context::get('admin_id',0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数抽象类
|
||||
*/
|
||||
abstract public function handle();
|
||||
}
|
||||
72
app/Service/Admin/Login/LoginService.php
Normal file
72
app/Service/Admin/Login/LoginService.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* This service file is part of item.
|
||||
*
|
||||
* @author ctexthuang
|
||||
* @contact ctexthuang@qq.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Admin\Login;
|
||||
|
||||
use App\Constants\Model\AdminUser\AdminUserStatusCode;
|
||||
use App\Exception\ErrException;
|
||||
use App\Interface\JwtInterface;
|
||||
use App\Lib\Jwt\JwtFactory;
|
||||
use App\Repository\AdminUserRepository;
|
||||
use App\Service\Admin\BaseAdminService;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
|
||||
class LoginService extends BaseAdminService
|
||||
{
|
||||
/**
|
||||
* @var string jwt场景
|
||||
*/
|
||||
private string $jwt = 'admin';
|
||||
|
||||
/**
|
||||
* @var AdminUserRepository
|
||||
*/
|
||||
#[Inject]
|
||||
protected AdminUserRepository $userRepository;
|
||||
|
||||
/**
|
||||
* @var JwtFactory
|
||||
*/
|
||||
#[Inject]
|
||||
protected JwtFactory $jwtFactory;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function handle(): array
|
||||
{
|
||||
$adminInfo = $this->userRepository->findByUserName((string)$this->request->input('username'));
|
||||
|
||||
if (!$adminInfo) throw new ErrException('后台管理员不存在');
|
||||
|
||||
if (! $adminInfo->verifyPassword((string) $this->request->input('password'))) {
|
||||
// $this->dispatcher->dispatch(new UserLoginEvent($user, $ip, $os, $browser, false));
|
||||
throw new ErrException('密码错误');
|
||||
}
|
||||
|
||||
if ($adminInfo->status == AdminUserStatusCode::DISABLE) throw new ErrException('用户已禁用');
|
||||
|
||||
$jwtHandle = $this->getJwt();
|
||||
|
||||
return [
|
||||
'access_token' => $jwtHandle->builderAccessToken((string) $adminInfo->id)->toString(),
|
||||
'refresh_token' => $jwtHandle->builderRefreshToken((string) $adminInfo->id)->toString(),
|
||||
'expire_at' => (int) $jwtHandle->getConfig('ttl', 0),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JwtInterface
|
||||
*/
|
||||
private function getJwt(): JwtInterface
|
||||
{
|
||||
return $this->jwtFactory->get($this->jwt);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user