StatController.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <?php
  2. namespace App\Module\OpenAPI\AdminControllers;
  3. use App\Module\OpenAPI\Models\OpenApiStats;
  4. use App\Module\OpenAPI\Models\OpenApiApp;
  5. use App\Module\OpenAPI\Models\OpenApiLog;
  6. use App\Module\OpenAPI\Repositorys\OpenApiStatRepository;
  7. use App\Module\Admin\AdminControllers\Helper\GridHelper;
  8. use Dcat\Admin\Grid;
  9. use Dcat\Admin\Show;
  10. use Dcat\Admin\Layout\Content;
  11. use Illuminate\Http\Request;
  12. use Spatie\RouteAttributes\Attributes\Resource;
  13. use UCore\DcatAdmin\AdminController;
  14. /**
  15. * OpenAPI统计分析控制器
  16. *
  17. * 用于后台管理OpenAPI统计数据
  18. */
  19. #[Resource(
  20. resource: 'openapi-stats',
  21. names: [
  22. 'index' => 'admin.openapi.stats.index',
  23. 'show' => 'admin.openapi.stats.show',
  24. ]
  25. )]
  26. class StatController extends AdminController
  27. {
  28. protected OpenApiStatRepository $repository;
  29. public function __construct(OpenApiStatRepository $repository)
  30. {
  31. $this->repository = $repository;
  32. }
  33. /**
  34. * 统计概览页面
  35. *
  36. * @param Content $content
  37. * @return Content
  38. */
  39. public function index(Content $content): Content
  40. {
  41. return $content
  42. ->title('OpenAPI统计分析')
  43. ->description('API调用统计和分析')
  44. ->body($this->grid());
  45. }
  46. /**
  47. * 统计详情页面
  48. *
  49. * @param mixed $id
  50. * @param Content $content
  51. * @return Content
  52. */
  53. public function show($id, Content $content): Content
  54. {
  55. return $content
  56. ->title('统计详情')
  57. ->description('查看详细统计信息')
  58. ->body($this->detail($id));
  59. }
  60. /**
  61. * 构建数据表格
  62. *
  63. * @return Grid
  64. */
  65. protected function grid(): Grid
  66. {
  67. return Grid::make($this->repository->query()->with('app'), function (Grid $grid) {
  68. $grid->column('id', 'ID')->sortable();
  69. $grid->column('app.name', '应用名称')->display(function ($name) {
  70. return $name ?: '未知应用';
  71. });
  72. $grid->column('app_id', '应用ID');
  73. $grid->column('date', '统计日期')->display(function ($date) {
  74. return $date ? $date->format('Y-m-d') : '';
  75. });
  76. $grid->column('hour', '统计小时')->display(function ($hour) {
  77. return $hour !== null ? sprintf('%02d:00', $hour) : '全天';
  78. });
  79. $grid->column('endpoint', '接口端点')->limit(30);
  80. $grid->column('request_count', '请求次数')->sortable()->totalRow();
  81. $grid->column('success_count', '成功次数')->sortable()->totalRow();
  82. $grid->column('error_count', '错误次数')->sortable()->totalRow();
  83. $grid->column('success_rate', '成功率')->display(function () {
  84. return $this->success_rate . '%';
  85. })->label('success');
  86. $grid->column('avg_response_time', '平均响应时间')->display(function ($time) {
  87. return $this->formatted_avg_response_time;
  88. });
  89. $grid->column('rate_limit_hits', '限流次数')->sortable()->totalRow();
  90. $grid->column('unique_ips', '唯一IP数')->sortable();
  91. // 筛选器
  92. $grid->filter(function (Grid\Filter $filter) {
  93. $filter->equal('app_id', '应用ID')->select(
  94. OpenApiApp::pluck('name', 'app_id')
  95. );
  96. $filter->between('date', '统计日期')->date();
  97. $filter->equal('hour', '统计小时')->select(
  98. array_combine(range(0, 23), array_map(function($h) {
  99. return sprintf('%02d:00', $h);
  100. }, range(0, 23)))
  101. );
  102. $filter->like('endpoint', '接口端点');
  103. $filter->between('request_count', '请求次数');
  104. $filter->scope('today', '今日')->where('date', now()->toDateString());
  105. $filter->scope('yesterday', '昨日')->where('date', now()->subDay()->toDateString());
  106. $filter->scope('this_week', '本周')->whereBetween('date', [
  107. now()->startOfWeek()->toDateString(),
  108. now()->endOfWeek()->toDateString()
  109. ]);
  110. $filter->scope('this_month', '本月')->whereBetween('date', [
  111. now()->startOfMonth()->toDateString(),
  112. now()->endOfMonth()->toDateString()
  113. ]);
  114. });
  115. // 工具栏
  116. $grid->tools(function (Grid\Tools $tools) {
  117. $tools->append('<a href="' . admin_route('admin.openapi.stats.dashboard') . '" class="btn btn-primary">
  118. <i class="fa fa-dashboard"></i> 统计仪表板
  119. </a>');
  120. });
  121. // 排序
  122. $grid->model()->orderBy('date', 'desc')->orderBy('hour', 'desc');
  123. // 禁用操作
  124. $grid->disableCreateButton();
  125. $grid->disableEditButton();
  126. $grid->disableDeleteButton();
  127. $grid->disableBatchActions();
  128. // 行操作
  129. $grid->actions(function (Grid\Displayers\Actions $actions) {
  130. $actions->disableEdit();
  131. $actions->disableDelete();
  132. });
  133. });
  134. }
  135. /**
  136. * 构建详情页面
  137. *
  138. * @param mixed $id
  139. * @return Show
  140. */
  141. protected function detail($id): Show
  142. {
  143. return Show::make($id, $this->repository->query()->with('app'), function (Show $show) {
  144. $show->field('id', 'ID');
  145. $show->field('app.name', '应用名称');
  146. $show->field('app_id', '应用ID');
  147. $show->field('date', '统计日期');
  148. $show->field('hour', '统计小时')->as(function ($hour) {
  149. return $hour !== null ? sprintf('%02d:00', $hour) : '全天';
  150. });
  151. $show->field('endpoint', '接口端点');
  152. $show->divider('请求统计');
  153. $show->field('request_count', '总请求次数');
  154. $show->field('success_count', '成功次数');
  155. $show->field('error_count', '错误次数');
  156. $show->field('success_rate', '成功率')->as(function () {
  157. return $this->success_rate . '%';
  158. })->label('success');
  159. $show->divider('响应时间统计');
  160. $show->field('avg_response_time', '平均响应时间')->as(function () {
  161. return $this->formatted_avg_response_time;
  162. });
  163. $show->field('max_response_time', '最大响应时间')->as(function ($time) {
  164. return $time < 1000 ? round($time, 2) . 'ms' : round($time / 1000, 2) . 's';
  165. });
  166. $show->field('min_response_time', '最小响应时间')->as(function ($time) {
  167. return $time < 1000 ? round($time, 2) . 'ms' : round($time / 1000, 2) . 's';
  168. });
  169. $show->divider('其他统计');
  170. $show->field('rate_limit_hits', '限流命中次数');
  171. $show->field('unique_ips', '唯一IP数量');
  172. $show->field('error_details', '错误详情')->json();
  173. $show->field('created_at', '创建时间');
  174. $show->field('updated_at', '更新时间');
  175. // 禁用操作
  176. $show->disableEditButton();
  177. $show->disableDeleteButton();
  178. });
  179. }
  180. /**
  181. * 统计仪表板
  182. *
  183. * @param Content $content
  184. * @return Content
  185. */
  186. public function dashboard(Content $content): Content
  187. {
  188. return $content
  189. ->title('OpenAPI统计仪表板')
  190. ->description('API调用统计概览')
  191. ->row(function ($row) {
  192. // 今日统计卡片
  193. $row->column(3, $this->todayStatsCard());
  194. $row->column(3, $this->yesterdayStatsCard());
  195. $row->column(3, $this->weekStatsCard());
  196. $row->column(3, $this->monthStatsCard());
  197. })
  198. ->row(function ($row) {
  199. // 趋势图表
  200. $row->column(6, $this->requestTrendChart());
  201. $row->column(6, $this->errorRateChart());
  202. })
  203. ->row(function ($row) {
  204. // 热门接口和应用
  205. $row->column(6, $this->topEndpointsCard());
  206. $row->column(6, $this->topAppsCard());
  207. });
  208. }
  209. /**
  210. * 今日统计卡片
  211. *
  212. * @return string
  213. */
  214. protected function todayStatsCard(): string
  215. {
  216. $stats = OpenApiStats::today()->selectRaw('
  217. SUM(request_count) as total_requests,
  218. SUM(success_count) as total_success,
  219. SUM(error_count) as total_errors,
  220. AVG(avg_response_time) as avg_time
  221. ')->first();
  222. $successRate = $stats->total_requests > 0
  223. ? round(($stats->total_success / $stats->total_requests) * 100, 2)
  224. : 0;
  225. return view('admin::widgets.info-box', [
  226. 'style' => 'info',
  227. 'title' => '今日统计',
  228. 'number' => number_format($stats->total_requests ?? 0),
  229. 'description' => "成功率: {$successRate}%",
  230. 'icon' => 'fa-calendar-day',
  231. ])->render();
  232. }
  233. /**
  234. * 昨日统计卡片
  235. *
  236. * @return string
  237. */
  238. protected function yesterdayStatsCard(): string
  239. {
  240. $stats = OpenApiStats::yesterday()->selectRaw('
  241. SUM(request_count) as total_requests,
  242. SUM(success_count) as total_success,
  243. SUM(error_count) as total_errors
  244. ')->first();
  245. $successRate = $stats->total_requests > 0
  246. ? round(($stats->total_success / $stats->total_requests) * 100, 2)
  247. : 0;
  248. return view('admin::widgets.info-box', [
  249. 'style' => 'warning',
  250. 'title' => '昨日统计',
  251. 'number' => number_format($stats->total_requests ?? 0),
  252. 'description' => "成功率: {$successRate}%",
  253. 'icon' => 'fa-calendar-minus',
  254. ])->render();
  255. }
  256. /**
  257. * 本周统计卡片
  258. *
  259. * @return string
  260. */
  261. protected function weekStatsCard(): string
  262. {
  263. $stats = OpenApiStats::thisWeek()->selectRaw('
  264. SUM(request_count) as total_requests,
  265. SUM(success_count) as total_success,
  266. SUM(error_count) as total_errors
  267. ')->first();
  268. $successRate = $stats->total_requests > 0
  269. ? round(($stats->total_success / $stats->total_requests) * 100, 2)
  270. : 0;
  271. return view('admin::widgets.info-box', [
  272. 'style' => 'success',
  273. 'title' => '本周统计',
  274. 'number' => number_format($stats->total_requests ?? 0),
  275. 'description' => "成功率: {$successRate}%",
  276. 'icon' => 'fa-calendar-week',
  277. ])->render();
  278. }
  279. /**
  280. * 本月统计卡片
  281. *
  282. * @return string
  283. */
  284. protected function monthStatsCard(): string
  285. {
  286. $stats = OpenApiStats::thisMonth()->selectRaw('
  287. SUM(request_count) as total_requests,
  288. SUM(success_count) as total_success,
  289. SUM(error_count) as total_errors
  290. ')->first();
  291. $successRate = $stats->total_requests > 0
  292. ? round(($stats->total_success / $stats->total_requests) * 100, 2)
  293. : 0;
  294. return view('admin::widgets.info-box', [
  295. 'style' => 'primary',
  296. 'title' => '本月统计',
  297. 'number' => number_format($stats->total_requests ?? 0),
  298. 'description' => "成功率: {$successRate}%",
  299. 'icon' => 'fa-calendar-alt',
  300. ])->render();
  301. }
  302. /**
  303. * 请求趋势图表
  304. *
  305. * @return string
  306. */
  307. protected function requestTrendChart(): string
  308. {
  309. // 获取最近7天的数据
  310. $data = OpenApiStats::whereBetween('date', [
  311. now()->subDays(6)->toDateString(),
  312. now()->toDateString()
  313. ])->selectRaw('
  314. date,
  315. SUM(request_count) as total_requests,
  316. SUM(success_count) as total_success,
  317. SUM(error_count) as total_errors
  318. ')->groupBy('date')->orderBy('date')->get();
  319. return view('admin::widgets.chart', [
  320. 'title' => '请求趋势(最近7天)',
  321. 'type' => 'line',
  322. 'data' => $data,
  323. ])->render();
  324. }
  325. /**
  326. * 错误率图表
  327. *
  328. * @return string
  329. */
  330. protected function errorRateChart(): string
  331. {
  332. // 获取最近7天的错误率数据
  333. $data = OpenApiStats::whereBetween('date', [
  334. now()->subDays(6)->toDateString(),
  335. now()->toDateString()
  336. ])->selectRaw('
  337. date,
  338. SUM(request_count) as total_requests,
  339. SUM(error_count) as total_errors
  340. ')->groupBy('date')->orderBy('date')->get()->map(function ($item) {
  341. $item->error_rate = $item->total_requests > 0
  342. ? round(($item->total_errors / $item->total_requests) * 100, 2)
  343. : 0;
  344. return $item;
  345. });
  346. return view('admin::widgets.chart', [
  347. 'title' => '错误率趋势(最近7天)',
  348. 'type' => 'bar',
  349. 'data' => $data,
  350. ])->render();
  351. }
  352. /**
  353. * 热门接口卡片
  354. *
  355. * @return string
  356. */
  357. protected function topEndpointsCard(): string
  358. {
  359. $endpoints = OpenApiStats::today()->selectRaw('
  360. endpoint,
  361. SUM(request_count) as total_requests
  362. ')->groupBy('endpoint')
  363. ->orderBy('total_requests', 'desc')
  364. ->limit(10)
  365. ->get();
  366. return view('admin::widgets.table', [
  367. 'title' => '今日热门接口',
  368. 'headers' => ['接口', '请求次数'],
  369. 'data' => $endpoints->map(function ($item) {
  370. return [$item->endpoint, number_format($item->total_requests)];
  371. }),
  372. ])->render();
  373. }
  374. /**
  375. * 热门应用卡片
  376. *
  377. * @return string
  378. */
  379. protected function topAppsCard(): string
  380. {
  381. $apps = OpenApiStats::today()
  382. ->join('openapi_apps', 'openapi_stats.app_id', '=', 'openapi_apps.app_id')
  383. ->selectRaw('
  384. openapi_apps.name,
  385. SUM(openapi_stats.request_count) as total_requests
  386. ')->groupBy('openapi_apps.name')
  387. ->orderBy('total_requests', 'desc')
  388. ->limit(10)
  389. ->get();
  390. return view('admin::widgets.table', [
  391. 'title' => '今日热门应用',
  392. 'headers' => ['应用名称', '请求次数'],
  393. 'data' => $apps->map(function ($item) {
  394. return [$item->name, number_format($item->total_requests)];
  395. }),
  396. ])->render();
  397. }
  398. }