This commit is contained in:
Yangxt
2026-09-30 11:11:54 +08:00
parent cb9b7a4bd4
commit 2fb57ec490
160 changed files with 14764 additions and 53 deletions
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace App\Support;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
final class ApiExceptionResponse
{
public static function normalize(Response $response, Request $request): Response
{
if (! $request->is('api/*')) {
return $response;
}
$status = $response->getStatusCode();
$payload = self::payload($response);
if (($payload['success'] ?? null) === false && array_key_exists('message', $payload)) {
return $response;
}
$errors = [];
if ($status === 422 && isset($payload['errors']) && is_array($payload['errors'])) {
$errors = $payload['errors'];
}
$fallback = is_string($payload['message'] ?? null) ? $payload['message'] : '';
return ApiResponse::failure(self::messageForStatus($status, $fallback), $errors, $status);
}
public static function messageForStatus(int $status, string $fallback): string
{
return match ($status) {
401 => '未认证',
403 => '无权访问',
404 => '资源不存在',
405 => '方法不允许',
419 => '页面已过期',
422 => '参数错误',
429 => '请求过于频繁',
default => $status >= 500 ? '操作失败' : ($fallback !== '' ? $fallback : '操作失败'),
};
}
/**
* @return array<string, mixed>
*/
private static function payload(Response $response): array
{
if (! $response instanceof JsonResponse) {
return [];
}
$data = $response->getData(true);
return is_array($data) ? $data : [];
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Support;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
use stdClass;
final class ApiResponse
{
public const DEFAULT_PER_PAGE = 20;
public const MAX_PER_PAGE = 100;
public static function success(mixed $data = null, string $message = 'OK', int $status = 200): JsonResponse
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $status);
}
/**
* @param array<string, mixed> $errors
*/
public static function failure(string $message, array $errors = [], int $status = 400): JsonResponse
{
return response()->json([
'success' => false,
'message' => $message,
'errors' => $errors === [] ? new stdClass : $errors,
], $status);
}
/**
* @param LengthAwarePaginator<int, mixed> $paginator
*/
public static function paginated(LengthAwarePaginator $paginator, string $message = 'OK'): JsonResponse
{
return response()->json([
'success' => true,
'message' => $message,
'data' => array_values($paginator->items()),
'meta' => [
'current_page' => $paginator->currentPage(),
'per_page' => $paginator->perPage(),
'total' => $paginator->total(),
'last_page' => $paginator->lastPage(),
],
]);
}
public static function perPage(mixed $requested): int
{
if (! is_numeric($requested)) {
return self::DEFAULT_PER_PAGE;
}
$pageSize = (int) $requested;
if ($pageSize < 1) {
return self::DEFAULT_PER_PAGE;
}
return min($pageSize, self::MAX_PER_PAGE);
}
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Support;
use RuntimeException;
final class MissingTenantContextException extends RuntimeException
{
public function __construct()
{
parent::__construct('当前没有租户上下文。');
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Support;
final class TenantContext
{
private ?string $tenantCode = null;
public function set(string $tenantCode): void
{
if ($tenantCode === '') {
throw new \InvalidArgumentException('tenant_code 不能为空。');
}
$this->tenantCode = $tenantCode;
}
public function forget(): void
{
$this->tenantCode = null;
}
public function has(): bool
{
return $this->tenantCode !== null;
}
public function get(): ?string
{
return $this->tenantCode;
}
public function code(): string
{
if ($this->tenantCode === null) {
throw new MissingTenantContextException;
}
return $this->tenantCode;
}
public function cacheKey(string $key): string
{
return 'tenant:'.$this->code().':'.$key;
}
public function lockKey(string $key): string
{
return 'lock:tenant:'.$this->code().':'.$key;
}
/**
* @template T
*
* @param callable(): T $callback
* @return T
*/
public function run(string $tenantCode, callable $callback): mixed
{
$previous = $this->tenantCode;
$this->set($tenantCode);
try {
return $callback();
} finally {
$this->tenantCode = $previous;
}
}
}