feat: 添加 JWT 支持与开发环境配置,更新 API 路由与中间件
This commit is contained in:
@@ -73,6 +73,14 @@ VITE_APP_NAME="${APP_NAME}"
|
|||||||
# 默认结算币种(PRD:NPR)
|
# 默认结算币种(PRD:NPR)
|
||||||
LOTTERY_DEFAULT_CURRENCY=NPR
|
LOTTERY_DEFAULT_CURRENCY=NPR
|
||||||
|
|
||||||
|
# 本地开发:Bearer dev:{数据库 players.id}(仅 APP_ENV=local 且为 true 时生效)
|
||||||
|
LOTTERY_PLAYER_AUTH_DEV_BYPASS=false
|
||||||
|
|
||||||
|
# JWT 内站点/玩家字段名(与主站签发约定一致)
|
||||||
|
# LOTTERY_JWT_ALGORITHM=HS256
|
||||||
|
# LOTTERY_JWT_CLAIM_SITE_CODE=site_code
|
||||||
|
# LOTTERY_JWT_CLAIM_SITE_PLAYER_ID=site_player_id
|
||||||
|
|
||||||
# 主站 SSO / 钱包(名称可按实际接口调整)
|
# 主站 SSO / 钱包(名称可按实际接口调整)
|
||||||
# MAIN_SITE_BASE_URL=
|
# MAIN_SITE_BASE_URL=
|
||||||
# MAIN_SITE_SSO_JWT_SECRET=
|
# MAIN_SITE_SSO_JWT_SECRET=
|
||||||
|
|||||||
22
app/Exceptions/PlayerAuthenticationException.php
Normal file
22
app/Exceptions/PlayerAuthenticationException.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 玩家端 Bearer 鉴权失败时抛出,由 EnsurePlayerApi 捕获并转为 JSON。
|
||||||
|
*
|
||||||
|
* @property-read int $lotteryCode 业务错误码(对齐 docs/04-领域字典 §10,SSO 段 8000–8999)
|
||||||
|
* @property-read int $httpStatus HTTP 状态码(401 未授权、503 服务未配置等)
|
||||||
|
*/
|
||||||
|
final class PlayerAuthenticationException extends RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
string $message,
|
||||||
|
public readonly int $lotteryCode,
|
||||||
|
public readonly int $httpStatus = 401,
|
||||||
|
) {
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,10 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Support\ApiResponse;
|
use App\Support\ApiResponse;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
/** 探路由用,上线前可删除或改为需登录 */
|
/**
|
||||||
|
* 无需登录(当前 admin 中间件为直通):确认 `/api/v1/admin` 前缀可达。
|
||||||
|
* 路由:GET /api/v1/admin/ping
|
||||||
|
*/
|
||||||
class PingController extends Controller
|
class PingController extends Controller
|
||||||
{
|
{
|
||||||
public function __invoke(): JsonResponse
|
public function __invoke(): JsonResponse
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ class HealthController extends Controller
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 健康检查(Next / 网关探活)。路径:GET /api/v1/health
|
* 健康检查(Next / 网关探活)。路径:GET /api/v1/health
|
||||||
|
*
|
||||||
|
* 非调试环境不返回框架版本号,避免信息泄露。
|
||||||
*/
|
*/
|
||||||
public function __invoke(): JsonResponse
|
public function __invoke(): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -19,7 +21,7 @@ class HealthController extends Controller
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (config('app.debug')) {
|
if (config('app.debug')) {
|
||||||
$payload['laravel'] = app()->version();
|
$payload['laravel'] = app()->version(); // 仅本地/调试
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResponse::success($payload);
|
return ApiResponse::success($payload);
|
||||||
|
|||||||
33
app/Http/Controllers/Api/V1/Player/MeController.php
Normal file
33
app/Http/Controllers/Api/V1/Player/MeController.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\Player;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Support\ApiResponse;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鉴权自检:返回当前 Token 对应的玩家公开字段(不含密码)。
|
||||||
|
*
|
||||||
|
* 路由:GET /api/v1/player/me ,需 middleware lottery.player。
|
||||||
|
*/
|
||||||
|
class MeController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$player = $request->lotteryPlayer();
|
||||||
|
// 理论上不会为 null(路由已套 EnsurePlayerApi);保留断言便于排查配置错误
|
||||||
|
abort_if($player === null, 500, 'lottery_player missing');
|
||||||
|
|
||||||
|
return ApiResponse::success([
|
||||||
|
'id' => $player->id,
|
||||||
|
'site_code' => $player->site_code,
|
||||||
|
'site_player_id' => $player->site_player_id,
|
||||||
|
'username' => $player->username,
|
||||||
|
'nickname' => $player->nickname,
|
||||||
|
'default_currency' => $player->default_currency,
|
||||||
|
'status' => $player->status,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,10 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Support\ApiResponse;
|
use App\Support\ApiResponse;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
/** 探路由用,上线前可删除或改为需登录 */
|
/**
|
||||||
|
* 无需登录:仅供网关/前端确认「玩家 API 前缀」可达。
|
||||||
|
* 路由:GET /api/v1/player/ping
|
||||||
|
*/
|
||||||
class PingController extends Controller
|
class PingController extends Controller
|
||||||
{
|
{
|
||||||
public function __invoke(): JsonResponse
|
public function __invoke(): JsonResponse
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ use Illuminate\Http\Request;
|
|||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后台 API:后续在此校验管理员登录(如 Sanctum)与 RBAC。
|
* 后台 API 守卫:后续在此注入 Sanctum(admin_users)与权限校验。
|
||||||
|
*
|
||||||
|
* 当前为占位直通,勿在生产暴露敏感 admin 路由前长期保持空实现。
|
||||||
*/
|
*/
|
||||||
class EnsureAdminApi
|
class EnsureAdminApi
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,17 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Exceptions\PlayerAuthenticationException;
|
||||||
|
use App\Services\PlayerTokenResolver;
|
||||||
|
use App\Support\ApiResponse;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 玩家端 API:后续在此校验 SSO / Bearer Token,并解析当前 players.id。
|
* 玩家端受保护路由前置:解析 Authorization,失败时直接返回 { code, msg, data },不进入控制器。
|
||||||
|
*
|
||||||
|
* 成功后在 request 上挂 `lottery_player`,控制器内使用 `$request->lotteryPlayer()`
|
||||||
|
*(由 AppServiceProvider 注册的宏,返回 ?Player)。
|
||||||
*/
|
*/
|
||||||
class EnsurePlayerApi
|
class EnsurePlayerApi
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$player = app(PlayerTokenResolver::class)->resolve($request);
|
||||||
|
} catch (PlayerAuthenticationException $e) {
|
||||||
|
return ApiResponse::error($e->getMessage(), $e->lotteryCode, null, $e->httpStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 attributes,避免与 Laravel 内置 input 混淆
|
||||||
|
$request->attributes->set('lottery_player', $player);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
app/Models/Player.php
Normal file
34
app/Models/Player.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主站玩家在本地映射账号(表 players),与 SSO JWT 中 site_code + site_player_id 对应。
|
||||||
|
*/
|
||||||
|
class Player extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'site_code',
|
||||||
|
'site_player_id',
|
||||||
|
'username',
|
||||||
|
'nickname',
|
||||||
|
'default_currency',
|
||||||
|
'status',
|
||||||
|
'last_login_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'last_login_at' => 'datetime',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function wallets(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(PlayerWallet::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
app/Models/PlayerWallet.php
Normal file
36
app/Models/PlayerWallet.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 彩票侧余额(表 player_wallets),按 player + wallet_type + currency 唯一。
|
||||||
|
*/
|
||||||
|
class PlayerWallet extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'player_id',
|
||||||
|
'wallet_type',
|
||||||
|
'currency_code',
|
||||||
|
'balance',
|
||||||
|
'frozen_balance',
|
||||||
|
'status',
|
||||||
|
'version',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'balance' => 'integer',
|
||||||
|
'frozen_balance' => 'integer',
|
||||||
|
'version' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function player(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Player::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Player;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -19,6 +21,10 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
// 仅在通过 EnsurePlayerApi 后可用;未走中间件时为 null
|
||||||
|
Request::macro('lotteryPlayer', function (): ?Player {
|
||||||
|
/** @var Request $this */
|
||||||
|
return $this->attributes->get('lottery_player');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
app/Services/PlayerTokenResolver.php
Normal file
110
app/Services/PlayerTokenResolver.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Exceptions\PlayerAuthenticationException;
|
||||||
|
use App\Models\Player;
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
use Firebase\JWT\Key;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求头解析玩家身份,返回已落库的 {@see Player}。
|
||||||
|
*
|
||||||
|
* 两种模式(互斥优先级:先判断 dev,再走 JWT):
|
||||||
|
* 1) 开发绕过:仅当 `APP_ENV=local` 且 `LOTTERY_PLAYER_AUTH_DEV_BYPASS=true`,
|
||||||
|
* 接受 `Authorization: Bearer dev:{players.id}`,直连主键查库(勿上生产)。
|
||||||
|
* 2) 生产:使用 `MAIN_SITE_SSO_JWT_SECRET` 验签 JWT(默认 HS256),
|
||||||
|
* 从 payload 读取 `site_code`、`site_player_id`(字段名可 env 覆盖)再查 players 表。
|
||||||
|
*
|
||||||
|
* 错误码约定(见中间件返回 JSON 的 code):
|
||||||
|
* - 8001:无 Bearer / Token 空 / Header 格式不对
|
||||||
|
* - 8002:JWT 无效、过期或缺少站点/玩家字段
|
||||||
|
* - 8003:数据库无对应玩家(未建档)
|
||||||
|
* - 8004:未配置 MAIN_SITE_SSO_JWT_SECRET(HTTP 503)
|
||||||
|
*/
|
||||||
|
final class PlayerTokenResolver
|
||||||
|
{
|
||||||
|
public function resolve(Request $request): Player
|
||||||
|
{
|
||||||
|
$header = $request->header('Authorization', '');
|
||||||
|
if (! is_string($header) || ! str_starts_with($header, 'Bearer ')) {
|
||||||
|
throw new PlayerAuthenticationException('缺少或非法 Authorization', 8001);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标准:`Authorization: Bearer <token>`,此处去掉前缀 7 字节 "Bearer "
|
||||||
|
$token = trim(substr($header, 7));
|
||||||
|
if ($token === '') {
|
||||||
|
throw new PlayerAuthenticationException('Token 为空', 8001);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地 dev: 优先于 JWT,避免未配密钥时仍能测需登录接口
|
||||||
|
if ($this->devBypassAllowed() && str_starts_with($token, 'dev:')) {
|
||||||
|
return $this->resolveDevToken($token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 .env 中 MAIN_SITE_SSO_JWT_SECRET 一致,用于 firebase/php-jwt 验签
|
||||||
|
$secret = config('lottery.main_site.sso_jwt_secret');
|
||||||
|
if (! is_string($secret) || $secret === '') {
|
||||||
|
throw new PlayerAuthenticationException('SSO 未配置(MAIN_SITE_SSO_JWT_SECRET)', 8004, 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resolveJwt($token, $secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function devBypassAllowed(): bool
|
||||||
|
{
|
||||||
|
return (bool) config('lottery.player_auth.dev_bypass')
|
||||||
|
&& app()->environment('local');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveDevToken(string $token): Player
|
||||||
|
{
|
||||||
|
if (! preg_match('/^dev:(\d+)$/', $token, $m)) {
|
||||||
|
throw new PlayerAuthenticationException('开发 Token 格式应为 dev:{玩家ID}', 8002);
|
||||||
|
}
|
||||||
|
|
||||||
|
$player = Player::query()->find((int) $m[1]);
|
||||||
|
if ($player === null) {
|
||||||
|
throw new PlayerAuthenticationException('玩家不存在', 8003);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $player;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveJwt(string $jwt, string $secret): Player
|
||||||
|
{
|
||||||
|
$alg = (string) config('lottery.player_auth.jwt.algorithm', 'HS256');
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var object $claims */
|
||||||
|
$claims = JWT::decode($jwt, new Key($secret, $alg));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// 签名错误、exp 过期、格式损坏等均归 8002
|
||||||
|
throw new PlayerAuthenticationException('Token 无效或已过期', 8002);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与主站约定 JWT 里字段名;若主站用 sub/iss 等可改 env LOTTERY_JWT_CLAIM_*
|
||||||
|
$siteKey = (string) config('lottery.player_auth.jwt.claim_site_code', 'site_code');
|
||||||
|
$pidKey = (string) config('lottery.player_auth.jwt.claim_site_player_id', 'site_player_id');
|
||||||
|
|
||||||
|
$siteCode = data_get($claims, $siteKey);
|
||||||
|
$sitePlayerId = data_get($claims, $pidKey);
|
||||||
|
|
||||||
|
if (! is_string($siteCode) || $siteCode === '' || ! is_string($sitePlayerId) || $sitePlayerId === '') {
|
||||||
|
throw new PlayerAuthenticationException('JWT 缺少站点或玩家标识', 8002);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首期:库中必须先有该行;若需「首次进入自动建档」可在此处 firstOrCreate
|
||||||
|
$player = Player::query()
|
||||||
|
->where('site_code', $siteCode)
|
||||||
|
->where('site_player_id', $sitePlayerId)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($player === null) {
|
||||||
|
throw new PlayerAuthenticationException('玩家未建档', 8003);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $player;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,10 @@ namespace App\Support;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 与 PRD / docs/04-领域字典与编码规范.md 对齐:{ code, msg, data }。
|
* 对外 API 统一 JSON 结构:{ code, msg, data }。
|
||||||
|
*
|
||||||
|
* - code=0 表示成功;非 0 为业务码(见 docs/04-领域字典与编码规范.md)。
|
||||||
|
* - error() 的 HTTP 状态可与 code 独立(如鉴权失败 401 + code 8001)。
|
||||||
*/
|
*/
|
||||||
final class ApiResponse
|
final class ApiResponse
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,13 +7,16 @@ use Illuminate\Foundation\Configuration\Middleware;
|
|||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
// 自动加前缀 `api` + middleware `api`,见 routes/api.php
|
||||||
api: __DIR__.'/../routes/api.php',
|
api: __DIR__.'/../routes/api.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
|
// 玩家端需登录路由使用;解析 Bearer → Player
|
||||||
'lottery.player' => \App\Http\Middleware\EnsurePlayerApi::class,
|
'lottery.player' => \App\Http\Middleware\EnsurePlayerApi::class,
|
||||||
|
// 后台 API 预留:Sanctum / RBAC
|
||||||
'lottery.admin' => \App\Http\Middleware\EnsureAdminApi::class,
|
'lottery.admin' => \App\Http\Middleware\EnsureAdminApi::class,
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
|
"firebase/php-jwt": "^6.11",
|
||||||
"laravel/framework": "^13.7",
|
"laravel/framework": "^13.7",
|
||||||
"laravel/tinker": "^3.0"
|
"laravel/tinker": "^3.0"
|
||||||
},
|
},
|
||||||
@@ -87,4 +88,4 @@
|
|||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
|||||||
65
composer.lock
generated
65
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "a9f356437f9d4d09ea0629df7da9db01",
|
"content-hash": "709df8b7c1a41b9d01918fea32398dfb",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -406,6 +406,69 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-03-06T22:45:56+00:00"
|
"time": "2025-03-06T22:45:56+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "firebase/php-jwt",
|
||||||
|
"version": "v6.11.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/googleapis/php-jwt.git",
|
||||||
|
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
||||||
|
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/guzzle": "^7.4",
|
||||||
|
"phpspec/prophecy-phpunit": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"psr/cache": "^2.0||^3.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||||
|
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Firebase\\JWT\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Neuman Vong",
|
||||||
|
"email": "neuman+pear@twilio.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anant Narayanan",
|
||||||
|
"email": "anant@php.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||||
|
"homepage": "https://github.com/firebase/php-jwt",
|
||||||
|
"keywords": [
|
||||||
|
"jwt",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/googleapis/php-jwt/issues",
|
||||||
|
"source": "https://github.com/googleapis/php-jwt/tree/v6.11.1"
|
||||||
|
},
|
||||||
|
"time": "2025-04-09T20:32:01+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fruitcake/php-cors",
|
"name": "fruitcake/php-cors",
|
||||||
"version": "v1.4.0",
|
"version": "v1.4.0",
|
||||||
|
|||||||
@@ -12,4 +12,20 @@ return [
|
|||||||
'wallet_timeout' => (int) env('MAIN_SITE_WALLET_TIMEOUT', 10),
|
'wallet_timeout' => (int) env('MAIN_SITE_WALLET_TIMEOUT', 10),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
| player_auth:配合 app/Services/PlayerTokenResolver.php
|
||||||
|
|
|
||||||
|
| dev_bypass:仅当 APP_ENV=local 且 LOTTERY_PLAYER_AUTH_DEV_BYPASS=true 时,
|
||||||
|
| 允许 Authorization: Bearer dev:{players.id}
|
||||||
|
| jwt.* :主站签发的 JWT 内取站点、玩家字段的路径名(与主站约定一致)
|
||||||
|
*/
|
||||||
|
'player_auth' => [
|
||||||
|
'dev_bypass' => env('LOTTERY_PLAYER_AUTH_DEV_BYPASS', false),
|
||||||
|
'jwt' => [
|
||||||
|
'algorithm' => env('LOTTERY_JWT_ALGORITHM', 'HS256'),
|
||||||
|
'claim_site_code' => env('LOTTERY_JWT_CLAIM_SITE_CODE', 'site_code'),
|
||||||
|
'claim_site_player_id' => env('LOTTERY_JWT_CLAIM_SITE_PLAYER_ID', 'site_player_id'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,25 +2,34 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Api\V1\Admin\PingController as AdminPingController;
|
use App\Http\Controllers\Api\V1\Admin\PingController as AdminPingController;
|
||||||
use App\Http\Controllers\Api\V1\HealthController;
|
use App\Http\Controllers\Api\V1\HealthController;
|
||||||
|
use App\Http\Controllers\Api\V1\Player\MeController;
|
||||||
use App\Http\Controllers\Api\V1\Player\PingController as PlayerPingController;
|
use App\Http\Controllers\Api\V1\Player\PingController as PlayerPingController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
| 全局前缀已由 bootstrap 注册为 /api,本文件内为相对路径。
|
| Laravel 默认在 bootstrap 中为 api 文件加前缀 `api`,故实际 URL:
|
||||||
| 玩家端:/api/v1/player/...
|
| /api/v1/health
|
||||||
| 后台: /api/v1/admin/...
|
| /api/v1/player/...
|
||||||
|
| /api/v1/admin/...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::prefix('v1')->group(function (): void {
|
Route::prefix('v1')->group(function (): void {
|
||||||
|
// 探活:无鉴权
|
||||||
Route::get('health', HealthController::class)->name('api.v1.health');
|
Route::get('health', HealthController::class)->name('api.v1.health');
|
||||||
|
|
||||||
Route::middleware('lottery.player')
|
Route::prefix('player')
|
||||||
->prefix('player')
|
|
||||||
->name('api.v1.player.')
|
->name('api.v1.player.')
|
||||||
->group(function (): void {
|
->group(function (): void {
|
||||||
|
// 不需 Bearer
|
||||||
Route::get('ping', PlayerPingController::class)->name('ping');
|
Route::get('ping', PlayerPingController::class)->name('ping');
|
||||||
|
|
||||||
|
// 需 Bearer:PlayerTokenResolver + EnsurePlayerApi
|
||||||
|
Route::middleware('lottery.player')->group(function (): void {
|
||||||
|
Route::get('me', MeController::class)->name('me');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 后台 API 前缀;中间件 lottery.admin 内预留 Sanctum / RBAC
|
||||||
Route::middleware('lottery.admin')
|
Route::middleware('lottery.admin')
|
||||||
->prefix('admin')
|
->prefix('admin')
|
||||||
->name('api.v1.admin.')
|
->name('api.v1.admin.')
|
||||||
|
|||||||
Reference in New Issue
Block a user