Tema
/
Interneto svetainių serverio dalies kūrimas GO

Įkėlimas nuo pradžios iki galo, su gyva progreso juosta

Aštuoni failai, pateikimas ant ką tik pastatyto sluoksnio: rodinio modelis, jo atvaizdavimas, sąrašo fragmentas, įkėlimo valdiklis ir keturi maži sujungimo pakeitimai, kurie viską pagyvina. Tvarka: documents_web.godocuments_view.godocuments.templdocuments.godashboard.templauth.gorouter.golayout.templ.

1. internal/web/documents_web.go (naujas) — DocumentView, šablonui skirta forma: paprastos eilutės ir loginė reikšmė, jokio pgtype. Šablonai niekada tiesiogiai neliečia db.Document.

2. internal/handlers/documents_view.go (naujas) — viewDocument atvaizduoja db.Document į DocumentView (suformatuoja laiko žymą, sužmogina baitų skaičių), viewDocuments atvaizduoja pjūvį, o humanizeBytes pateikia 1.4 MB.

3. internal/web/documents.templ (naujas) — DocumentList(docs), fragmentas su id="doc-list", ir documentRow. Būtent šį gabalą HTMX pakeičia: įkėlimo forma taikosi į #doc-list ir pakeičia jį šviežiu sąrašu. (Peržiūros ir trynimo veiksmai atkeliaus kitą pamoką kartu su juos palaikančiais maršrutais.)

4. internal/handlers/documents.go (naujas) — Dashboard (perkeltas čia iš auth.go, dabar rodantis savininko dokumentus) ir UploadDocument:

r.Body = http.MaxBytesReader(w, r.Body, h.Cfg.MaxUploadBytes+1024)
file, header, err := r.FormFile("file")
// ...
key, size, hash, err := h.Store.Save(r.Context(), file) // srautas + maiša, vienas perėjimas
doc, err := h.Queries.CreateDocument(r.Context(), db.CreateDocumentParams{ /* ... */ })
if err != nil {
	_ = h.Store.Delete(r.Context(), key) // nepalik našlaičio failo
	// ...
}

Jei eilutės įterpimas nepavyksta jau įrašius failą, Store.Delete pašalina našlaitį — saugykla ir duomenų bazė laikomos sinchronizuotos. respondWithDocList grąžina atnaujintą fragmentą HTMX skambintojams ir peradresuoja paprastą formos siuntimą. sanitizeFilename nušluosto kelio skirtukus ir valdymo simbolius iš kliento pateikto vardo.

5. internal/web/dashboard.templ (keitimas) — vietos rezervavimo skydelis išauga į tikrą: herojų, įkėlimo kortelę (hx-post="/documents", hx-encoding="multipart/form-data", hx-target="#doc-list", <progress> juostą) ir @DocumentList(docs). Mažas skriptas sujungia htmx xhr:progress įvykius su progreso elementu. Dashboard dabar priima docs []DocumentView.

6. internal/handlers/auth.go (keitimas) — senas vietos rezervavimo Dashboard čia pašalintas; jis persikėlė į documents.go, kur gali išvardyti dokumentus. Daugiau auth.go niekas nesikeičia.

7. internal/handlers/router.go (keitimas) — vienas maršrutas autentifikuotoje grupėje: pr.Post("/documents", h.UploadDocument).

8. internal/web/layout.templ (keitimas) — du papildymai, leidžiantys HTMX siuntimams praeiti CSRF: <meta name="csrf-token"> ir mažas htmx:configRequest klausytojas, kopijuojantis žetoną į X-CSRF-Token antraštę. Įkėlimo forma neneša paslėpto lauko, tad antraštė yra tai, kaip gorilla/csrf mato žetoną — ir jis nuskaito antraštę prieš apdorodamas kūną, tad multipart srautas nesuvartojamas du kartus.

Įkėlimo forma neturi paslėpto CSRF lauko

Kiekviena kita forma įterpia gorilla.csrf.Token kaip paslėptą įvestį. Multipart įkėlimas negali — žetonas atvyktų po failo sraute, o gorilla/csrf jį reikia pirma. X-CSRF-Token antraštė tai išsprendžia: htmx prideda ją prie kiekvienos užklausos iš meta žymos, ir ji nuskaitoma prieš kūną. Be šio sujungimo įkėlimo POST grąžina 403 Forbidden.

Patikra — perseeduok, go run ./cmd/signflow, prisijunk ir atverk /dashboard:

  • Pasirink failą ir spausk Upload. Progreso juosta prisipildo, o dokumentų sąrašas pasikeičia vietoje su tavo failu — be viso puslapio perkrovimo.
  • Eilutė rodo sužmogintą dydį ir draft ženkliuką.
  • Patikrink terminalą:
level=INFO msg="document uploaded" id=… owner=you@example.com size=1048576 sha256=4a7d1ed414474e4033ac29ccb8653d9b...
  • Įsitikink, kad baitai ir maiša nusėdo:
$ ls uploads/
7f3a9c1e0b2d4f6a8c5e1b3d7f9a2c4e
$ psql "$DATABASE_URL" -c "select filename, size, status, storage_key from documents;"
   filename   |  size   | status |           storage_key
--------------+---------+--------+----------------------------------
 contract.pdf | 1048576 | draft  | 7f3a9c1e0b2d4f6a8c5e1b3d7f9a2c4e

storage_key įvardija failą diske; jis nėra eilutės ID. file_hash buvo apskaičiuotas tuo pačiu perėjimu, kuris tuos baitus įrašė.

  • Pabandyk failą virš 25 MiB: įkėlimas atmetamas su 413, o į uploads/ ar lentelę nieko neįrašoma.