Kaynağa Gözat

fix(report): 周报仅保留带时间戳 PDF,路由按 mtime 打开最新一份

Made-with: Cursor
yemeishu 1 ay önce
ebeveyn
işleme
af8ff1e941

+ 1 - 1
app/Console/Commands/ReportTeacherWeeklyPdfCommand.php

@@ -8,7 +8,7 @@ class ReportTeacherWeeklyPdfCommand extends Command
 {
     protected $signature = 'report:teacher-weekly-pdf';
 
-    protected $description = '生成老师组卷与学情分析周报 PDF,并更新 storage/app/reports/teacher-weekly-stats-latest.pdf';
+    protected $description = '生成老师组卷与学情分析周报 PDF(文件名带日期与时间戳 teacher-weekly-stats-Y-m-d_His.pdf)';
 
     public function handle(): int
     {

+ 12 - 8
app/Filament/Pages/TeacherWeeklyStatsReport.php

@@ -2,6 +2,7 @@
 
 namespace App\Filament\Pages;
 
+use App\Support\TeacherWeeklyStatsReportPaths;
 use Filament\Actions\Action;
 use Filament\Notifications\Notification;
 use Filament\Pages\Page;
@@ -26,26 +27,29 @@ class TeacherWeeklyStatsReport extends Page
     #[Computed(cache: false)]
     public function hasLatestPdf(): bool
     {
-        return is_file($this->latestPdfPath());
+        return TeacherWeeklyStatsReportPaths::resolveLatestPdfPath() !== null;
     }
 
     #[Computed(cache: false)]
-    public function latestPdfMtime(): ?string
+    public function latestPdfBasename(): ?string
     {
-        $p = $this->latestPdfPath();
+        $p = TeacherWeeklyStatsReportPaths::resolveLatestPdfPath();
 
-        return is_file($p) ? date('Y-m-d H:i:s', filemtime($p)) : null;
+        return $p !== null ? basename($p) : null;
     }
 
     #[Computed(cache: false)]
-    public function pdfOpenUrl(): string
+    public function latestPdfMtime(): ?string
     {
-        return route('filament.admin.reports.teacher-weekly-stats.open');
+        $p = TeacherWeeklyStatsReportPaths::resolveLatestPdfPath();
+
+        return $p !== null ? date('Y-m-d H:i:s', filemtime($p)) : null;
     }
 
-    protected function latestPdfPath(): string
+    #[Computed(cache: false)]
+    public function pdfOpenUrl(): string
     {
-        return storage_path('app/reports/teacher-weekly-stats-latest.pdf');
+        return route('filament.admin.reports.teacher-weekly-stats.open');
     }
 
     protected function getHeaderActions(): array

+ 7 - 4
app/Http/Controllers/TeacherWeeklyStatsReportController.php

@@ -2,27 +2,30 @@
 
 namespace App\Http\Controllers;
 
+use App\Support\TeacherWeeklyStatsReportPaths;
 use Illuminate\Http\Response;
 use Symfony\Component\HttpFoundation\BinaryFileResponse;
 
 class TeacherWeeklyStatsReportController extends Controller
 {
     /**
-     * 浏览器内联打开周报 PDF(固定文件名 teacher-weekly-stats-latest.pdf)
+     * 浏览器内联打开最近一次生成的周报 PDF(文件名带日期与 His 时间戳)。
      */
     public function open(): BinaryFileResponse|Response
     {
-        $path = storage_path('app/reports/teacher-weekly-stats-latest.pdf');
-        if (! is_file($path)) {
+        $path = TeacherWeeklyStatsReportPaths::resolveLatestPdfPath();
+        if ($path === null) {
             return response(
                 '尚未生成周报 PDF。请在后台「老师周报统计」页面点击「生成/刷新周报」,或执行:php artisan report:teacher-weekly-pdf',
                 404
             );
         }
 
+        $downloadName = basename($path);
+
         return response()->file($path, [
             'Content-Type' => 'application/pdf',
-            'Content-Disposition' => 'inline; filename="teacher-weekly-stats.pdf"',
+            'Content-Disposition' => 'inline; filename="'.$downloadName.'"',
         ]);
     }
 }

+ 38 - 0
app/Support/TeacherWeeklyStatsReportPaths.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Support;
+
+/**
+ * 周报产物仅保留「带时间戳」文件名:teacher-weekly-stats-{Y-m-d}_{His}.pdf
+ * 「打开最新」通过按修改时间选取目录内匹配该模式的文件。
+ */
+final class TeacherWeeklyStatsReportPaths
+{
+    private const PDF_PATTERN = '/^teacher-weekly-stats-\d{4}-\d{2}-\d{2}_\d{6}\.pdf$/';
+
+    public static function reportsDirectory(): string
+    {
+        return storage_path('app/reports');
+    }
+
+    /** 最近一次生成的带时间戳 PDF(按 mtime),无则 null */
+    public static function resolveLatestPdfPath(): ?string
+    {
+        $dir = self::reportsDirectory();
+        if (! is_dir($dir)) {
+            return null;
+        }
+
+        $files = glob($dir.'/teacher-weekly-stats-*.pdf') ?: [];
+        $files = array_values(array_filter($files, static fn ($p) => preg_match(self::PDF_PATTERN, basename((string) $p))));
+        if ($files === []) {
+            return null;
+        }
+
+        usort($files, static fn ($a, $b) => filemtime((string) $b) <=> filemtime((string) $a));
+
+        $path = $files[0];
+
+        return is_string($path) && is_file($path) ? $path : null;
+    }
+}

+ 8 - 6
resources/views/filament/pages/teacher-weekly-stats-report.blade.php

@@ -5,9 +5,7 @@
                 老师组卷与学情分析(近 7 天)
             </x-slot>
             <x-slot name="description">
-                数据来自当前环境数据库;生成带时间戳的 PDF 后,会同步覆盖
-                <code class="text-xs rounded bg-gray-100 px-1 py-0.5 dark:bg-gray-800">teacher-weekly-stats-latest.pdf</code>
-                ,便于固定链接打开。
+                数据来自当前环境数据库;每次生成一份带时间戳的 PDF,固定链接会打开<strong>目录内最新一次</strong>生成的文件。
             </x-slot>
 
             <div class="flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-center">
@@ -22,6 +20,10 @@
                         在浏览器中打开 PDF(新标签页)
                     </x-filament::button>
                     <span class="text-sm text-gray-600 dark:text-gray-400">
+                        @if($this->latestPdfBasename)
+                            <span class="font-mono text-xs">{{ $this->latestPdfBasename }}</span>
+                            ·
+                        @endif
                         最近生成:{{ $this->latestPdfMtime }}
                     </span>
                 @else
@@ -43,9 +45,9 @@
                     (时分秒为 PHP <code class="text-xs">date('His')</code>,例如 <strong>143052</strong> 表示 14:30:52)
                 </li>
                 <li>
-                    最新一份会复制为
-                    <code class="rounded bg-gray-100 px-1 py-0.5 text-xs dark:bg-gray-800">storage/app/reports/teacher-weekly-stats-latest.pdf</code>
-                    ,后台「打开 PDF」始终指向该文件
+                    不设无时间戳的 latest 文件;后台「打开 PDF」与路由
+                    <code class="rounded bg-gray-100 px-1 py-0.5 text-xs dark:bg-gray-800">/admin/reports/teacher-weekly-stats/open</code>
+                    会按修改时间选取<strong>最新一份</strong>符合上述命名的 PDF
                 </li>
             </ul>
         </x-filament::section>

+ 2 - 8
scripts/report_teacher_weekly_stats_pdf.php

@@ -8,8 +8,8 @@
  *   php scripts/report_teacher_weekly_stats_pdf.php /path/to/out.pdf
  *
  * 输出:
- *   - 带时间戳:teacher-weekly-stats-{Y-m-d}_{His}.pdf(His = 24 小时制的时-分-秒,如 143052)
- *   - 固定别名:storage/app/reports/teacher-weekly-stats-latest.pdf(覆盖为最近一次生成,供后台一键打开)
+ *   - teacher-weekly-stats-{Y-m-d}_{His}.pdf(His = 24 小时制六位数字时分秒,如 143052)
+ *   - 不配 latest 文件名;后台「打开 PDF」会自动选目录内最新的一份带时间戳 PDF。
  */
 
 use League\CommonMark\Environment\Environment;
@@ -69,10 +69,4 @@ $mpdf->Output($outPath, \Mpdf\Output\Destination::FILE);
 $mdPath = preg_replace('/\.pdf$/i', '.md', $outPath);
 file_put_contents($mdPath, $markdown);
 
-$latestPdf = storage_path('app/reports/teacher-weekly-stats-latest.pdf');
-$latestMd = storage_path('app/reports/teacher-weekly-stats-latest.md');
-@copy($outPath, $latestPdf);
-@copy($mdPath, $latestMd);
-
 fwrite(STDERR, "PDF: {$outPath}\nMD:  {$mdPath}\n");
-fwrite(STDERR, "最新别名(一键打开): {$latestPdf}\n");