chap 5: Umsetzung — 11 Abschnitte + module-components/zip-stream/race-condition diagrams

This commit is contained in:
2026-08-25 20:33:15 +02:00
parent ce6b01626c
commit f857a244dd
17 changed files with 437 additions and 110 deletions
+39 -29
View File
@@ -1,29 +1,39 @@
classDiagram
class DocumentsPage {
+OnGetAsync()
+OnGetDownloadAsync()
+OnGetZipAsync()
}
class DocumentsService {
+ListDocumentsAsync(orgId, search, type, token)
+GetDownloadUrlAsync(orgId, key)
+GetZipStreamAsync(orgId, keys)
+ResolveCustomerPrefixAsync(orgId, orgName)
}
class S3DocumentsClient {
-IAmazonS3 _s3
+ListObjectsAsync(prefix, delimiter, token)
+GetObjectMetadataAsync(key)
+GetPresignedUrlAsync(key)
}
class OrgSlug {
+Slugify(name) string
}
class S3Settings {
+BucketName string
+ServiceUrl string
}
DocumentsPage --> DocumentsService
DocumentsService --> S3DocumentsClient
DocumentsService --> OrgSlug
S3DocumentsClient --> S3Settings
flowchart TB
subgraph Pages["Pages"]
P1["Documents.cshtml<br/>+ .cshtml.cs<br/><i>Liste, Suche, Filter, Seiten</i>"]
P2["Document/Download.cshtml.cs<br/><i>Einzel- und ZIP-Download</i>"]
P3["Document/Share.cshtml.cs<br/><i>OpenGraph-Vorschau</i>"]
end
subgraph Services["Services"]
S["DocumentsService<br/><i>Auflisten, Filtern, Aufloesen<br/>des Kundenordners</i>"]
C["S3DocumentsClient<br/><i>Kapselung von IAmazonS3</i>"]
end
subgraph Model["Model/Documents"]
M1["DocumentType<br/><i>Enum + Ableitung<br/>aus Ordnername</i>"]
M2["DocumentName<br/><i>Anzeigename,<br/>.url-Erkennung</i>"]
M3["DocumentKey /<br/>RelativePath<br/><i>validierte Pfade</i>"]
M4["OrgSlug<br/><i>Normalisierung des<br/>Firmennamens</i>"]
M5["DocumentViewModel"]
end
EXT["IAmazonS3<br/>(AWS SDK)"]
CFG["S3Settings<br/><i>keyed service</i>"]
P1 --> S
P2 --> S
P3 --> S
S --> C
S --> M1
S --> M2
S --> M4
S --> M5
P2 --> M3
C --> EXT
C --> CFG
style S fill:#dae8fc,stroke:#6c8ebf
style C fill:#dae8fc,stroke:#6c8ebf
style EXT fill:#f5f5f5,stroke:#999
style CFG fill:#f5f5f5,stroke:#999
Binary file not shown.
+18 -12
View File
@@ -1,15 +1,21 @@
sequenceDiagram
participant A as Request A
autonumber
participant A as Anfrage A
participant S3 as S3-Speicher
participant B as Request B
participant B as Anfrage B
Note over A,B: Ordner AltName/ mit 200 Objekten, Zielname: Kunde GmbH/
A->>S3: CanClaim(Kunde GmbH) → Missing → true
B->>S3: CanClaim(Kunde GmbH) → Missing → true
A->>S3: Kopiere 200 Objekte nach Kunde GmbH/
B->>S3: Kopiere Objekte #1–60 nach Kunde GmbH/
A->>S3: DeleteObjects(AltName/*) ✓
B->>S3: Kopie #61 → NoSuchKey ✗
B->>S3: catch: DeleteObjects(copiedKeys #1–60)
Note over S3: 60 Objekte aus Kunde GmbH/ gelöscht!
Note over S3: AltName/ existiert nicht mehr → Datenverlust
Note over A,B: Ausgangslage: Ordner "AltName/" mit 200 Objekten,<br/>Zielname "Kunde GmbH/". Beide Anfragen gehoeren derselben Org.
A->>S3: CanClaim("Kunde GmbH") → frei
B->>S3: CanClaim("Kunde GmbH") → frei
Note over A,B: Beide halten den Zielnamen fuer beanspruchbar
A->>S3: kopiert alle 200 Objekte
B->>S3: kopiert Objekte 1 bis 60
A->>S3: DeleteObjects("AltName/*")
Note over S3: Quelle ist jetzt geloescht
B->>S3: kopiert Objekt 61 → NoSuchKey
B->>S3: catch: DeleteObjects(eigene 60 Kopien)
Note over S3: Die 60 geloeschten Objekte sind genau die,<br/>die A erfolgreich geschrieben hatte.<br/>Die Quelle existiert nicht mehr — Datenverlust.
Binary file not shown.
+16 -10
View File
@@ -1,15 +1,21 @@
sequenceDiagram
participant B as Browser
participant P as PageModel
autonumber
actor U as Benutzer
participant P as Download-PageModel
participant S as DocumentsService
participant Z as ZipArchive
participant S3 as S3-Speicher
B->>P: POST /documents?handler=Zip&keys=[...]
P->>S: GetZipStreamAsync(orgId, keys)
loop je Dokument
S->>S3: GetObjectAsync(key)
S3-->>S: Stream
S->>S: ZipArchive.CreateEntry + CopyToAsync
U->>P: Auswahl + Klick auf<br/>"Als ZIP herunterladen"
P->>S: Schluessel gegen Kundenpraefix pruefen
S-->>P: validierte Schluessel
P->>U: Antwort-Header:<br/>application/zip
loop je ausgewaehltes Dokument
S->>S3: GetObject(key)
S3-->>S: Inhalts-Stream
S->>Z: Eintrag mit relativem Pfad anlegen
Z->>U: komprimierte Bytes<br/>direkt in die Antwort
end
S-->>P: ZipStream
P-->>B: application/zip (streaming)
Z->>U: Archiv abschliessen
Note over Z,U: Kein Zwischenspeichern im<br/>Arbeitsspeicher oder auf Platte
Binary file not shown.