update route service

This commit is contained in:
2026-04-02 14:41:47 +08:00
parent d28a014209
commit 6d7f892237
4 changed files with 271 additions and 79 deletions
+3 -79
View File
@@ -4,12 +4,9 @@ declare(strict_types=1);
namespace App\Command;
use App\Model\Route;
use App\Service\RouteSyncService;
use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\HttpServer\Router\DispatcherFactory;
use Hyperf\HttpServer\Router\Handler;
use Hyperf\HttpServer\Router\RouteCollector;
use Psr\Container\ContainerInterface;
#[Command]
@@ -28,80 +25,7 @@ class RouteSyncCommand extends HyperfCommand
public function handle(): void
{
$factory = $this->container->get(DispatcherFactory::class);
$router = $factory->getRouter('http');
$routes = $this->extractRoutes($router);
$synced = 0;
foreach ($routes as $route_info) {
Route::query()->updateOrCreate(
['method' => $route_info['method'], 'path' => $route_info['path']],
['name' => $route_info['name']]
);
$synced++;
}
$this->info("Synced {$synced} routes to database.");
}
/**
* 从路由收集器中提取所有 API 路由
*
* @return array<int, array{method: string, path: string, name: string|null}>
*/
protected function extractRoutes(RouteCollector $router): array
{
$routes = [];
[$staticRouters, $variableRouters] = $router->getData();
// 静态路由(无路径参数)
foreach ($staticRouters as $method => $items) {
foreach ($items as $handler) {
$this->collectRoute($routes, $method, $handler);
}
}
// 动态路由(含路径参数如 {id})
foreach ($variableRouters as $method => $items) {
foreach ($items as $item) {
if (is_array($item['routeMap'] ?? false)) {
foreach ($item['routeMap'] as $routeMap) {
$this->collectRoute($routes, $method, $routeMap[0]);
}
}
}
}
return $routes;
}
/**
* 收集单条路由信息,仅同步 /api/ 前缀的路由
*/
protected function collectRoute(array &$routes, string $method, Handler $handler): void
{
$path = $handler->route;
// 仅同步 API 路由
if (!str_starts_with($path, '/api/')) {
return;
}
// 解析 action 名称
$name = null;
if (is_array($handler->callback)) {
$name = $handler->callback[0] . '::' . $handler->callback[1];
} elseif (is_string($handler->callback)) {
$name = $handler->callback;
}
$key = $method . '|' . $path;
if (!isset($routes[$key])) {
$routes[$key] = [
'method' => $method,
'path' => $path,
'name' => $name,
];
}
$result = $this->container->get(RouteSyncService::class)->sync();
$this->info("Synced {$result['synced']} routes to database.");
}
}
@@ -9,6 +9,7 @@ use App\Middleware\AuthMiddleware;
use App\Middleware\PermissionMiddleware;
use App\Model\Route;
use App\Model\RouteGroup;
use App\Service\RouteSyncService;
use Hyperf\DbConnection\Db;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\Middleware;
@@ -641,4 +642,46 @@ class RouteGroupController extends AbstractController
'data' => $group,
];
}
/**
* 同步注解路由到数据库
*
* 将 Hyperf 注解定义的 API 路由同步到 routes 表,管理员专用
*/
#[OA\Post(
path: '/routes/sync',
summary: '同步注解路由到数据库',
description: '将 Hyperf 注解定义的 API 路由同步到 routes 表,支持重复调用(幂等)',
security: [['bearerAuth' => []]],
tags: ['Route Groups'],
responses: [
new OA\Response(
response: 200,
description: '同步成功',
content: new OA\JsonContent(properties: [
new OA\Property(property: 'code', type: 'integer', example: 0),
new OA\Property(property: 'message', type: 'string', example: '同步成功'),
new OA\Property(property: 'data', properties: [
new OA\Property(property: 'synced', type: 'integer', description: '同步的路由数量', example: 50),
new OA\Property(property: 'total', type: 'integer', description: '总路由数量', example: 50),
], type: 'object'),
])
),
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
new OA\Response(response: 403, description: '无权限', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
]
)]
#[RequestMapping(path: "/api/v1/routes/sync", methods: "POST")]
#[Middleware(AuthMiddleware::class)]
#[Middleware(PermissionMiddleware::class)]
public function sync(): array
{
$result = $this->container->get(RouteSyncService::class)->sync();
return [
'code' => 0,
'message' => '同步成功',
'data' => $result,
];
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Model\Route;
use Hyperf\HttpServer\Router\DispatcherFactory;
use Hyperf\HttpServer\Router\Handler;
use Hyperf\HttpServer\Router\RouteCollector;
use Psr\Container\ContainerInterface;
class RouteSyncService
{
public function __construct(
protected readonly ContainerInterface $container,
) {}
/**
* 同步 Hyperf 注解路由到 routes 表
*
* @return array{synced: int, total: int}
*/
public function sync(): array
{
$factory = $this->container->get(DispatcherFactory::class);
$router = $factory->getRouter('http');
$routes = $this->extractRoutes($router);
$synced = 0;
foreach ($routes as $route_info) {
Route::query()->updateOrCreate(
['method' => $route_info['method'], 'path' => $route_info['path']],
['name' => $route_info['name']]
);
$synced++;
}
return [
'synced' => $synced,
'total' => count($routes),
];
}
/**
* 从路由收集器中提取所有 API 路由
*
* @return array<int, array{method: string, path: string, name: string|null}>
*/
private function extractRoutes(RouteCollector $router): array
{
$routes = [];
[$staticRouters, $variableRouters] = $router->getData();
// 静态路由(无路径参数)
foreach ($staticRouters as $method => $items) {
foreach ($items as $handler) {
$this->collectRoute($routes, $method, $handler);
}
}
// 动态路由(含路径参数如 {id})
foreach ($variableRouters as $method => $items) {
foreach ($items as $item) {
if (is_array($item['routeMap'] ?? false)) {
foreach ($item['routeMap'] as $routeMap) {
$this->collectRoute($routes, $method, $routeMap[0]);
}
}
}
}
return array_values($routes);
}
/**
* 收集单条路由信息,仅同步 /api/ 前缀的路由
*/
private function collectRoute(array &$routes, string $method, Handler $handler): void
{
$path = $handler->route;
// 仅同步 API 路由
if (!str_starts_with($path, '/api/')) {
return;
}
// 解析 action 名称
$name = null;
if (is_array($handler->callback)) {
$name = $handler->callback[0] . '::' . $handler->callback[1];
} elseif (is_string($handler->callback)) {
$name = $handler->callback;
}
$key = $method . '|' . $path;
if (!isset($routes[$key])) {
$routes[$key] = [
'method' => $method,
'path' => $path,
'name' => $name,
];
}
}
}