Ver código fonte

Merge branch 'fix/修正报告Blob路径' of jyx/dcjxb.microservice into master

金逸霄 6 dias atrás
pai
commit
2e0373000d

+ 1 - 1
abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportProperties.java

@@ -118,7 +118,7 @@ public class ExamSprintReportProperties {
 
     public static class Storage {
         private String type = "memory";
-        private String containerName = "exam-sprint-reports";
+        private String containerName = "reports";
         private String downloadUrlPrefix;
         private String connectionString;
         private String endpoint;

+ 2 - 11
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/AzureBlobExamSprintReportStorage.java

@@ -46,7 +46,7 @@ public class AzureBlobExamSprintReportStorage implements ExamSprintReportStorage
 
     @Autowired
     public AzureBlobExamSprintReportStorage(
-            @Value("${ability.exam-sprint.report.storage.container-name:exam-sprint-reports}") String containerName,
+            @Value("${ability.exam-sprint.report.storage.container-name:reports}") String containerName,
             @Value("${ability.exam-sprint.report.storage.connection-string:}") String connectionString,
             @Value("${ability.exam-sprint.report.storage.endpoint:}") String endpoint,
             @Value("${ability.exam-sprint.report.storage.account-name:}") String accountName,
@@ -75,7 +75,7 @@ public class AzureBlobExamSprintReportStorage implements ExamSprintReportStorage
             byte[] pdfBytes,
             Instant expiresAt) {
         long uploadStartedNanos = System.nanoTime();
-        String blobName = blobName(reportId, reportType, fileName);
+        String blobName = normalizeSegment(fileName, "fileName");
         BlobClient blobClient = containerClient.getBlobClient(blobName);
         log.info(
                 "exam_sprint_report_azure_storage_upload_stage_completed reportId={} reportType={} stage=client_resolved storageObjectKey={} blobName={} fileName={} pdfByteLength={} durationMs={}",
@@ -163,15 +163,6 @@ public class AzureBlobExamSprintReportStorage implements ExamSprintReportStorage
         return storageObjectKey.substring(storageObjectKey.lastIndexOf('/') + 1);
     }
 
-    private String blobName(String reportId, ReportType reportType, String fileName) {
-        return "reports/"
-                + reportType.name().toLowerCase()
-                + "/"
-                + normalizeSegment(reportId, "reportId")
-                + "/"
-                + normalizeSegment(fileName, "fileName");
-    }
-
     private static String normalizeDownloadUrlPrefix(String value) {
         if (!hasText(value)) {
             throw new IllegalStateException("Azure storage download-url-prefix is incomplete");

+ 29 - 30
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/AzureBlobExamSprintReportStorageTest.java

@@ -70,7 +70,7 @@ class AzureBlobExamSprintReportStorageTest {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -79,7 +79,7 @@ class AzureBlobExamSprintReportStorageTest {
                 Duration.ofMinutes(15));
 
         assertThat(downloadUrl).isEqualTo(URI.create(
-                "https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-outlook-report-report-123.pdf"));
+                "https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-outlook-report-report-123.pdf"));
         verifyNoInteractions(containerClient);
     }
 
@@ -88,7 +88,7 @@ class AzureBlobExamSprintReportStorageTest {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "/report/",
+                "/reports/",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn/",
                 FIXED_CLOCK);
 
@@ -97,26 +97,26 @@ class AzureBlobExamSprintReportStorageTest {
                 Duration.ofMinutes(15));
 
         assertThat(downloadUrl).isEqualTo(URI.create(
-                "https://dcjxbtest.blob.core.chinacloudapi.cn/report/file.pdf"));
+                "https://dcjxbtest.blob.core.chinacloudapi.cn/reports/file.pdf"));
     }
 
     @Test
-    void generateDownloadUrlEncodesStorageObjectKeyPathSegmentsAndPreservesPathSeparators() {
+    void generateDownloadUrlEncodesFileNameWithoutReportScopedPath() {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
         URI downloadUrl = storage.generateDownloadUrl(
-                "reports/outlook/report-123/John Doe-报告 #1?.pdf",
+                "John Doe-报告 #1?.pdf",
                 Duration.ofMinutes(15));
 
         assertThat(downloadUrl.toString()).isEqualTo(
-                "https://dcjxbtest.blob.core.chinacloudapi.cn/report/reports/outlook/report-123/John%20Doe-报告%20%231%3F.pdf");
+                "https://dcjxbtest.blob.core.chinacloudapi.cn/reports/John%20Doe-报告%20%231%3F.pdf");
         assertThat(downloadUrl.getRawPath()).isEqualTo(
-                "/report/reports/outlook/report-123/John%20Doe-报告%20%231%3F.pdf");
+                "/reports/John%20Doe-报告%20%231%3F.pdf");
         assertThat(downloadUrl.getRawQuery()).isNull();
         assertThat(downloadUrl.getRawFragment()).isNull();
     }
@@ -126,7 +126,7 @@ class AzureBlobExamSprintReportStorageTest {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -140,7 +140,7 @@ class AzureBlobExamSprintReportStorageTest {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -155,7 +155,7 @@ class AzureBlobExamSprintReportStorageTest {
 
         assertThatThrownBy(() -> new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "///",
                 FIXED_CLOCK))
                 .isInstanceOf(IllegalStateException.class)
@@ -168,7 +168,7 @@ class AzureBlobExamSprintReportStorageTest {
 
         assertThatThrownBy(() -> new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 " / ",
                 FIXED_CLOCK))
                 .isInstanceOf(IllegalStateException.class)
@@ -176,13 +176,13 @@ class AzureBlobExamSprintReportStorageTest {
     }
 
     @Test
-    void uploadUsesReportScopedBlobNameAndSendsHeadersAndMetadataInUploadRequest() {
+    void uploadUsesFileNameAsBlobNameAndSendsHeadersAndMetadataInUploadRequest() {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         BlobClient blobClient = mock(BlobClient.class);
         when(containerClient.getBlobClient(anyString())).thenReturn(blobClient);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -193,9 +193,9 @@ class AzureBlobExamSprintReportStorageTest {
                 new byte[]{1, 2, 3},
                 Instant.parse("2026-01-10T00:00:00Z"));
 
-        assertThat(storedFile.storageObjectKey()).isEqualTo("reports/outlook/report-123/file.pdf");
+        assertThat(storedFile.storageObjectKey()).isEqualTo("file.pdf");
         assertThat(storedFile.fileName()).isEqualTo("file.pdf");
-        verify(containerClient).getBlobClient("reports/outlook/report-123/file.pdf");
+        verify(containerClient).getBlobClient("file.pdf");
         verify(blobClient).uploadWithResponse(argThat(options ->
                         options.getLength() == 3
                                 && options.getHeaders() != null
@@ -216,7 +216,7 @@ class AzureBlobExamSprintReportStorageTest {
                 .thenReturn(successfulUploadResponse());
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -234,8 +234,8 @@ class AzureBlobExamSprintReportStorageTest {
                 .contains("exam_sprint_report_azure_storage_upload_completed")
                 .contains("reportId=report-123")
                 .contains("reportType=OUTLOOK")
-                .contains("storageObjectKey=reports/outlook/report-123/file.pdf")
-                .contains("blobName=reports/outlook/report-123/file.pdf")
+                .contains("storageObjectKey=file.pdf")
+                .contains("blobName=file.pdf")
                 .contains("fileName=file.pdf")
                 .contains("pdfByteLength=3")
                 .contains("durationMs=")
@@ -258,7 +258,7 @@ class AzureBlobExamSprintReportStorageTest {
                 .thenThrow(blobStorageException());
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -274,8 +274,8 @@ class AzureBlobExamSprintReportStorageTest {
                 .contains("exam_sprint_report_azure_storage_upload_failed")
                 .contains("reportId=report-123")
                 .contains("reportType=OUTLOOK")
-                .contains("storageObjectKey=reports/outlook/report-123/file.pdf")
-                .contains("blobName=reports/outlook/report-123/file.pdf")
+                .contains("storageObjectKey=file.pdf")
+                .contains("blobName=file.pdf")
                 .contains("fileName=file.pdf")
                 .contains("pdfByteLength=3")
                 .contains("durationMs=")
@@ -290,13 +290,13 @@ class AzureBlobExamSprintReportStorageTest {
     }
 
     @Test
-    void uploadDoesNotUseDisplayFileNameAsTheUniqueStorageObjectKey() {
+    void uploadUsesDisplayFileNameAsTheStorageObjectKey() {
         BlobContainerClient containerClient = mock(BlobContainerClient.class);
         BlobClient blobClient = mock(BlobClient.class);
         when(containerClient.getBlobClient(anyString())).thenReturn(blobClient);
         AzureBlobExamSprintReportStorage storage = new AzureBlobExamSprintReportStorage(
                 containerClient,
-                "report",
+                "reports",
                 "https://dcjxbtest.blob.core.chinacloudapi.cn",
                 FIXED_CLOCK);
 
@@ -313,9 +313,8 @@ class AzureBlobExamSprintReportStorageTest {
                 new byte[]{2},
                 Instant.parse("2026-01-10T00:00:00Z"));
 
-        assertThat(firstStoredFile.storageObjectKey()).isEqualTo("reports/outlook/report-123/same-display-name.pdf");
-        assertThat(secondStoredFile.storageObjectKey()).isEqualTo("reports/outlook/report-456/same-display-name.pdf");
-        assertThat(firstStoredFile.storageObjectKey()).isNotEqualTo(secondStoredFile.storageObjectKey());
+        assertThat(firstStoredFile.storageObjectKey()).isEqualTo("same-display-name.pdf");
+        assertThat(secondStoredFile.storageObjectKey()).isEqualTo("same-display-name.pdf");
         assertThat(firstStoredFile.fileName()).isEqualTo("same-display-name.pdf");
         assertThat(secondStoredFile.fileName()).isEqualTo("same-display-name.pdf");
     }
@@ -331,7 +330,7 @@ class AzureBlobExamSprintReportStorageTest {
                 true,
                 null);
         return new SimpleResponse<>(
-                new HttpRequest(HttpMethod.PUT, "https://example.test/report/file.pdf"),
+                new HttpRequest(HttpMethod.PUT, "https://example.test/reports/file.pdf"),
                 201,
                 headers,
                 blockBlobItem);
@@ -346,7 +345,7 @@ class AzureBlobExamSprintReportStorageTest {
                 .set("x-ms-error-code", "ServerBusy"));
         when(response.getHeaderValue(HttpHeaderName.X_MS_REQUEST_ID)).thenReturn("azure-request-failed");
         when(response.getHeaderValue(HttpHeaderName.X_MS_CLIENT_REQUEST_ID)).thenReturn("client-request-failed");
-        when(response.getRequest()).thenReturn(new HttpRequest(HttpMethod.PUT, new URL("https://example.test/report/file.pdf")));
+        when(response.getRequest()).thenReturn(new HttpRequest(HttpMethod.PUT, new URL("https://example.test/reports/file.pdf")));
         return new BlobStorageException("ServerBusy", response, null);
     }
 }

+ 1 - 1
ability-center-runtime/src/main/resources/application.yml

@@ -15,7 +15,7 @@ ability:
         render-timeout: 30s
       storage:
         type: memory
-        container-name: exam-sprint-reports
+        container-name: reports
         connection-string:
         endpoint:
         account-name:

+ 6 - 6
ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerWebMvcTest.java

@@ -124,7 +124,7 @@ class ExamSprintReportControllerWebMvcTest {
                 Instant.parse("2026-01-01T00:00:00Z"),
                 Instant.parse("2026-01-01T00:01:00Z"),
                 Instant.parse("2026-01-02T00:00:00Z"),
-                "https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-outlook-report-report-sync-001.pdf"));
+                "https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-outlook-report-report-sync-001.pdf"));
 
         String requestJson = requestJson("requests/exam-sprint-outlook-report-request.json");
 
@@ -134,7 +134,7 @@ class ExamSprintReportControllerWebMvcTest {
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.data.reportType").value("OUTLOOK"))
                 .andExpect(jsonPath("$.data.generationStatus").value("SUCCESS"))
-                .andExpect(jsonPath("$.data.downloadUrl").value("https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-outlook-report-report-sync-001.pdf"));
+                .andExpect(jsonPath("$.data.downloadUrl").value("https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-outlook-report-report-sync-001.pdf"));
 
         verify(applicationService).createOutlookReportSync(any());
     }
@@ -149,7 +149,7 @@ class ExamSprintReportControllerWebMvcTest {
                 Instant.parse("2026-01-01T00:00:00Z"),
                 Instant.parse("2026-01-01T00:01:00Z"),
                 Instant.parse("2026-01-02T00:00:00Z"),
-                "https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-achievement-report-report-sync-002.pdf"));
+                "https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-achievement-report-report-sync-002.pdf"));
 
         String requestJson = requestJson("requests/exam-sprint-achievement-report-request.json");
 
@@ -159,7 +159,7 @@ class ExamSprintReportControllerWebMvcTest {
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.data.reportType").value("ACHIEVEMENT"))
                 .andExpect(jsonPath("$.data.generationStatus").value("SUCCESS"))
-                .andExpect(jsonPath("$.data.downloadUrl").value("https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-achievement-report-report-sync-002.pdf"));
+                .andExpect(jsonPath("$.data.downloadUrl").value("https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-achievement-report-report-sync-002.pdf"));
 
         verify(applicationService).createAchievementReportSync(any());
     }
@@ -214,7 +214,7 @@ class ExamSprintReportControllerWebMvcTest {
                 Instant.parse("2026-01-01T00:00:00Z"),
                 Instant.parse("2026-01-01T00:05:00Z"),
                 Instant.parse("2026-01-02T00:00:00Z"),
-                "https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-outlook-report-report-001.pdf",
+                "https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-outlook-report-report-001.pdf",
                 null));
         given(applicationService.downloadReport("report-001")).willReturn(new ReportDownloadContent(
                 "exam-sprint-outlook-report-report-001.pdf",
@@ -224,7 +224,7 @@ class ExamSprintReportControllerWebMvcTest {
         mockMvc.perform(get("/api/exam-sprint/reports/{reportId}", "report-001"))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.data.reportId").value("report-001"))
-                .andExpect(jsonPath("$.data.downloadUrl").value("https://dcjxbtest.blob.core.chinacloudapi.cn/report/exam-sprint-outlook-report-report-001.pdf"));
+                .andExpect(jsonPath("$.data.downloadUrl").value("https://dcjxbtest.blob.core.chinacloudapi.cn/reports/exam-sprint-outlook-report-report-001.pdf"));
 
         mockMvc.perform(get("/api/exam-sprint/reports/{reportId}/download", "report-001"))
                 .andExpect(status().isOk())