252 lines
11 KiB
PHP
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: '/api/v1/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: '/api/v1/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: '/api/v1/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;
|
||
|
|
}
|
||
|
|
}
|