WorldCupScheduleController.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <?php
  2. namespace App\Http\Controllers\Admin;
  3. use App\Services\WorldCup\WorldCupScheduleUpdateService;
  4. use Illuminate\Http\Request;
  5. class WorldCupScheduleController extends BaseController
  6. {
  7. private $service;
  8. public function __construct(WorldCupScheduleUpdateService $service)
  9. {
  10. $this->service = $service;
  11. }
  12. public function index(Request $request)
  13. {
  14. $scheduleDate = (string)$request->input('schedule_date', '');
  15. $matches = $scheduleDate !== ''
  16. ? $this->service->matchesByDate($scheduleDate)
  17. : $this->service->allMatches();
  18. return view('admin.world_cup.schedule', [
  19. 'scheduleDate' => $scheduleDate,
  20. 'matches' => $matches,
  21. 'result' => $request->session()->get('world_cup_schedule_update_result'),
  22. 'scheduleUrls' => $this->scheduleUrls(),
  23. ]);
  24. }
  25. public function update(Request $request)
  26. {
  27. $scheduleDate = (string)$request->input('schedule_date', '');
  28. $matches = $this->parseMatches($request);
  29. if ($matches === null) {
  30. $result = [
  31. 'success' => false,
  32. 'message' => 'Invalid matches payload',
  33. 'data' => [
  34. 'updated' => 0,
  35. 'skipped' => 0,
  36. 'errors' => [],
  37. ],
  38. ];
  39. return $this->respond($request, $result, $scheduleDate);
  40. }
  41. $result = $this->service->updateMatches($matches, $this->actor($request));
  42. return $this->respond($request, $result, $scheduleDate);
  43. }
  44. public function import(Request $request)
  45. {
  46. $rows = $this->parseCsvRows($request);
  47. $result = $rows === null
  48. ? [
  49. 'success' => false,
  50. 'message' => 'Invalid CSV file',
  51. 'data' => [
  52. 'updated' => 0,
  53. 'skipped' => 0,
  54. 'errors' => [],
  55. ],
  56. ]
  57. : $this->service->importExistingMatches($rows, $this->actor($request));
  58. return $this->respond($request, $result, (string)$request->input('schedule_date', ''));
  59. }
  60. private function parseMatches(Request $request): ?array
  61. {
  62. $matches = $request->input('matches');
  63. if (is_array($matches)) {
  64. return $matches;
  65. }
  66. return [];
  67. }
  68. private function respond(Request $request, array $result, string $scheduleDate)
  69. {
  70. if ($request->ajax() || $request->expectsJson() || $request->input('format') === 'json') {
  71. return $this->json($result['success'] ? 200 : 400, $result['message'], $result['data']);
  72. }
  73. $url = '/admin/world-cup/schedule';
  74. if ($scheduleDate !== '') {
  75. $url .= '?schedule_date=' . urlencode($scheduleDate);
  76. }
  77. return redirect($url)
  78. ->with('world_cup_schedule_update_result', $result);
  79. }
  80. private function actor(Request $request): string
  81. {
  82. return (string)($request->session()->get('admin.username') ?: 'admin');
  83. }
  84. private function scheduleUrls(): array
  85. {
  86. return [
  87. 'FIFA 官方赛程' => 'https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/match-schedule-fixtures-results-teams-stadiums',
  88. 'FIFA 赛程更新时间公告' => 'https://vod.fifa.com/media-releases/updated-world-cup-2026-match-schedule-venues-kick-off-times-104-matches',
  89. '可读赛程 PDF' => 'https://www.kickoffclock.com/downloads/world-cup-2026-schedule.pdf',
  90. ];
  91. }
  92. private function parseCsvRows(Request $request): ?array
  93. {
  94. if (!$request->hasFile('csv_file') || !$request->file('csv_file')->isValid()) {
  95. return null;
  96. }
  97. $handle = fopen($request->file('csv_file')->getRealPath(), 'r');
  98. if ($handle === false) {
  99. return null;
  100. }
  101. $headers = null;
  102. $rows = [];
  103. while (($data = fgetcsv($handle)) !== false) {
  104. if ($headers === null) {
  105. $headers = $this->normalizeCsvHeaders($data);
  106. continue;
  107. }
  108. if ($this->isEmptyCsvRow($data)) {
  109. continue;
  110. }
  111. $values = array_slice(array_pad($data, count($headers), ''), 0, count($headers));
  112. $rows[] = array_combine($headers, $values);
  113. }
  114. fclose($handle);
  115. return $headers === null ? null : $rows;
  116. }
  117. private function normalizeCsvHeaders(array $headers): array
  118. {
  119. return array_map(function ($header) {
  120. return trim(str_replace("\xEF\xBB\xBF", '', (string)$header));
  121. }, $headers);
  122. }
  123. private function isEmptyCsvRow(array $row): bool
  124. {
  125. foreach ($row as $value) {
  126. if (trim((string)$value) !== '') {
  127. return false;
  128. }
  129. }
  130. return true;
  131. }
  132. }