chap 5: Umsetzung — 11 Abschnitte + module-components/zip-stream/race-condition diagrams
This commit is contained in:
@@ -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.
@@ -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.
@@ -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.
Reference in New Issue
Block a user