Files
datahub/backend/app/Controller/Api/V1/DashboardController.php
2026-03-18 09:13:53 +08:00

252 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Controller\Api\V1;
use App\Controller\AbstractController;
use App\Middleware\AuthMiddleware;
use App\Middleware\PermissionMiddleware;
use App\Service\DashboardStatsService;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\RequestMapping;
use OpenApi\Attributes as OA;
use Psr\Http\Message\ResponseInterface;
/**
* Dashboard 统计接口
*
* 提供 overview / trend / breakdown 三类数据同步统计端点
*/
#[OA\Tag(name: 'Dashboard', description: 'Dashboard 数据同步统计')]
#[Controller(prefix: "/api/v1/dashboard")]
#[Middleware(AuthMiddleware::class)]
#[Middleware(PermissionMiddleware::class)]
class DashboardController extends AbstractController
{
public function __construct(
protected readonly DashboardStatsService $statsService,
) {}
/**
* 概览统计
*/
#[OA\Get(
path: '/dashboard/overview',
summary: '获取概览统计',
description: '返回今日/本周/本月的成功和失败同步数,以及按数据类型的本月分组统计。',
security: [['bearerAuth' => []]],
tags: ['Dashboard'],
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', ref: '#/components/schemas/DashboardOverview'),
])
),
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: "overview", methods: "GET")]
public function overview(): ResponseInterface|array
{
$scope_type = $this->request->getAttribute('scope_type', 'all');
$scope_ids = $this->request->getAttribute('scope_ids', []);
$data = $this->statsService->getOverview($scope_type, $scope_ids);
return [
'code' => 0,
'message' => '获取成功',
'data' => $data,
];
}
/**
* 趋势数据
*/
#[OA\Get(
path: '/dashboard/trend',
summary: '获取趋势数据',
description: '返回指定时间范围内按日/周/月聚合的成功和失败同步趋势。',
security: [['bearerAuth' => []]],
tags: ['Dashboard'],
parameters: [
new OA\Parameter(name: 'from', in: 'query', required: false, description: '起始日期(默认30天前)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-02-15')),
new OA\Parameter(name: 'to', in: 'query', required: false, description: '结束日期(默认今天)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-03-17')),
new OA\Parameter(name: 'group_by', in: 'query', required: false, description: '聚合粒度', schema: new OA\Schema(type: 'string', enum: ['day', 'week', 'month'], default: 'day')),
new OA\Parameter(name: 'data_type', in: 'query', required: false, description: '数据类型筛选', schema: new OA\Schema(type: 'string', enum: ['order', 'product', 'refund', 'inventory'])),
],
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', type: 'array', items: new OA\Items(ref: '#/components/schemas/DashboardTrendItem')),
])
),
new OA\Response(response: 400, description: '参数错误', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
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: "trend", methods: "GET")]
public function trend(): ResponseInterface|array
{
$from = $this->request->input('from', date('Y-m-d', strtotime('-30 days')));
$to = $this->request->input('to', date('Y-m-d'));
$group_by = $this->request->input('group_by', 'day');
$data_type = $this->request->input('data_type');
// 校验 group_by
if (!in_array($group_by, DashboardStatsService::VALID_GROUP_BY, true)) {
return $this->response->json([
'code' => 400,
'message' => "无效的 group_by 参数: {$group_by},可选值: " . implode(', ', DashboardStatsService::VALID_GROUP_BY),
'data' => null,
])->withStatus(400);
}
// 校验 data_type
if ($data_type !== null && !in_array($data_type, DashboardStatsService::VALID_DATA_TYPES, true)) {
return $this->response->json([
'code' => 400,
'message' => "无效的 data_type 参数: {$data_type},可选值: " . implode(', ', DashboardStatsService::VALID_DATA_TYPES),
'data' => null,
])->withStatus(400);
}
// 校验日期格式
if (!$this->isValidDate($from) || !$this->isValidDate($to)) {
return $this->response->json([
'code' => 400,
'message' => '无效的日期格式,请使用 YYYY-MM-DD',
'data' => null,
])->withStatus(400);
}
// 校验 from <= to
if ($from > $to) {
return $this->response->json([
'code' => 400,
'message' => 'from 日期不能晚于 to 日期',
'data' => null,
])->withStatus(400);
}
$scope_type = $this->request->getAttribute('scope_type', 'all');
$scope_ids = $this->request->getAttribute('scope_ids', []);
$data = $this->statsService->getTrend($from, $to, $group_by, $data_type, $scope_type, $scope_ids);
return [
'code' => 0,
'message' => '获取成功',
'data' => $data,
];
}
/**
* 分组统计
*/
#[OA\Get(
path: '/dashboard/breakdown',
summary: '获取分组统计',
description: '按公司/平台/店铺维度统计成功和失败同步数。',
security: [['bearerAuth' => []]],
tags: ['Dashboard'],
parameters: [
new OA\Parameter(name: 'dimension', in: 'query', required: true, description: '分组维度', schema: new OA\Schema(type: 'string', enum: ['company', 'platform', 'store'])),
new OA\Parameter(name: 'from', in: 'query', required: false, description: '起始日期(默认今天)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-03-17')),
new OA\Parameter(name: 'to', in: 'query', required: false, description: '结束日期(默认今天)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-03-17')),
new OA\Parameter(name: 'data_type', in: 'query', required: false, description: '数据类型筛选', schema: new OA\Schema(type: 'string', enum: ['order', 'product', 'refund', 'inventory'])),
],
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', type: 'array', items: new OA\Items(ref: '#/components/schemas/DashboardBreakdownItem')),
])
),
new OA\Response(response: 400, description: '参数错误', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
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: "breakdown", methods: "GET")]
public function breakdown(): ResponseInterface|array
{
$dimension = $this->request->input('dimension');
// 校验 dimension 必填
if ($dimension === null || $dimension === '') {
return $this->response->json([
'code' => 400,
'message' => '缺少必填参数: dimension',
'data' => null,
])->withStatus(400);
}
// 校验 dimension 枚举
if (!in_array($dimension, DashboardStatsService::VALID_DIMENSIONS, true)) {
return $this->response->json([
'code' => 400,
'message' => "无效的 dimension 参数: {$dimension},可选值: " . implode(', ', DashboardStatsService::VALID_DIMENSIONS),
'data' => null,
])->withStatus(400);
}
$from = $this->request->input('from', date('Y-m-d'));
$to = $this->request->input('to', date('Y-m-d'));
$data_type = $this->request->input('data_type');
// 校验 data_type
if ($data_type !== null && !in_array($data_type, DashboardStatsService::VALID_DATA_TYPES, true)) {
return $this->response->json([
'code' => 400,
'message' => "无效的 data_type 参数: {$data_type},可选值: " . implode(', ', DashboardStatsService::VALID_DATA_TYPES),
'data' => null,
])->withStatus(400);
}
// 校验日期格式
if (!$this->isValidDate($from) || !$this->isValidDate($to)) {
return $this->response->json([
'code' => 400,
'message' => '无效的日期格式,请使用 YYYY-MM-DD',
'data' => null,
])->withStatus(400);
}
$scope_type = $this->request->getAttribute('scope_type', 'all');
$scope_ids = $this->request->getAttribute('scope_ids', []);
$data = $this->statsService->getBreakdown($dimension, $from, $to, $data_type, $scope_type, $scope_ids);
return [
'code' => 0,
'message' => '获取成功',
'data' => $data,
];
}
/**
* 校验日期格式 YYYY-MM-DD
*/
private function isValidDate(string $date): bool
{
$d = \DateTime::createFromFormat('Y-m-d', $date);
return $d !== false && $d->format('Y-m-d') === $date;
}
}