Heim >Backend-Entwicklung >Golang >Erstellen Sie einen OTP-basierten Authentifizierungsserver mit Go: Part File Uploads und Graceful Shutdown

Erstellen Sie einen OTP-basierten Authentifizierungsserver mit Go: Part File Uploads und Graceful Shutdown

Susan Sarandon
Susan SarandonOriginal
2025-01-17 18:03:14873Durchsuche

Build an OTP-Based Authentication Server with Go: Part  File Uploads and Graceful Shutdown

Diese Ausgabe verfeinert unseren Go-Authentifizierungsserver, indem er Funktionen zum Hochladen von Dateien hinzufügt, die Entwicklung mit einem Makefile rationalisiert und ein ordnungsgemäßes Herunterfahren des Servers implementiert. Dies verhindert ein abruptes Beenden und stellt sicher, dass alle laufenden Aufgaben abgeschlossen sind, bevor der Server stoppt.

Graceful Shutdown-Implementierung

Eine neue cmd/api/server.go Datei zentralisiert die Serververwaltung. Eine Goroutine überwacht Beendigungssignale (SIGINT, SIGTERM). Beim Empfang eines Signals wird der Server ordnungsgemäß heruntergefahren. Kommentare verdeutlichen jeden Schritt.

<code class="language-go">package main

import (
    "context"
    "errors"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

// ... (Existing application code) ...

func (app *application) serve(router http.Handler) error {
    // Server initialization with timeouts for idle, read, and write operations.
    srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", app.config.port),
        Handler:      app.recoverPanic(app.authenticate(router)),
        IdleTimeout:  time.Minute,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
    }

    // Channel to manage errors during shutdown.
    shutdownError := make(chan error)

    // Goroutine to listen for termination signals and initiate graceful shutdown.
    go func() {
        quit := make(chan os.Signal, 1)
        signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
        s := <-quit // Wait for signal
        fmt.Println("Shutting down server...")

        // Context with timeout for graceful shutdown.
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()

        // Attempt graceful shutdown.
        shutdownError <- srv.Shutdown(ctx)
    }()

    // Start the server and report any errors.
    fmt.Printf("Starting server on port %d\n", app.config.port)
    err := srv.ListenAndServe()
    if !errors.Is(err, http.ErrServerClosed) {
        return fmt.Errorf("server error: %w", err)
    }
    return <-shutdownError
}

// ... (Rest of application code) ...</code>

Ersetzen Sie in main.go den srv.ListenAndServe()-Block durch err = app.serve(router); if err != nil { logger.Fatal(err, nil) }. Um dies zu testen, müssen Sie den Wiederholungsmechanismus verwenden (Anmeldeinformationen für kontinuierliche Wiederholungsversuche entfernen) und den Server mit Strg C unterbrechen. Es sollte warten, bis die Wiederholungsversuche abgeschlossen sind, bevor es heruntergefahren wird.

Aufgaben mit Makefile automatisieren

A Makefile automatisiert sich wiederholende Befehle. Fügen Sie Folgendes zum Makefile Ihres Projekts hinzu:

<code class="language-makefile"># Include variables from the .envrc file
include .envrc

## help: print this help message
.PHONY: help
help:
    @echo 'Usage:'
    @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'

.PHONY: confirm
confirm:
    @echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]

## run/api: run the cmd/api application
.PHONY: run/api
run/api:
    go run ./cmd/api

## db/psql: connect to the database using psql
.PHONY: db/psql
db/psql:
    psql ${GREENLIGHT_DB_DSN}

## db/migrations/new name=: create a new database migration
.PHONY: db/migrations/new
db/migrations/new:
    @echo "Migrating ${name}"
    migrate create -seq -ext=.sql -dir=./migrations ${name}

## db/migrations/up: apply all up database migrations
.PHONY: db/migrations/up
db/migrations/up: confirm
    @echo "Running migrations"
    migrate -path=./migrations -database=${GREENLIGHT_DB_DSN} up

## audit: tidy and vendor dependencies and format, vet and test all code
.PHONY: audit
audit: vendor
    @echo 'Formatting code...'
    go fmt ./...
    @echo 'Vetting code...'
    go vet ./...
    staticcheck ./...
    @echo 'Running tests...'
    go test -race -vet=off ./...

## vendor: tidy and vendor dependencies
.PHONY: vendor
vendor:
    @echo 'Tidying and verifying module dependencies...'
    go mod tidy
    go mod verify
    @echo 'Vendoring dependencies...'
    go mod vendor

## build/api: build the cmd/api application
.PHONY: build/api
build/api:
    @echo 'Building cmd/api...'
    go build -o=./bin/api ./cmd/api
    GOOS=linux GOARCH=amd64 go build -o=./bin/linux_amd64/api ./cmd/api</code>

Führen Sie Befehle wie make help, make run/api, make db/migrations/new name=my_migration, make db/migrations/up, make audit oder make build/api aus. Nach dieser Struktur können weitere Befehle hinzugefügt werden.

Datei-Upload und -Verfolgung

Eine neue Datenbanktabelle verfolgt hochgeladene Dateien:

Migration erstellen make db/migrations/new name=create-creatives.

000003_create-creatives.up.sql:

<code class="language-sql">CREATE TABLE IF NOT EXISTS creatives (
    id bigserial PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES users ON DELETE CASCADE,
    creative_url text NOT NULL,
    scheduled_at DATE NOT NULL,
    created_at timestamp(0) with time zone NOT NULL DEFAULT NOW()
);</code>

000003_create-creatives.down.sql:

<code class="language-sql">DROP TABLE IF EXISTS creatives;</code>

internal/data/creatives.go:

<code class="language-go">package data

import (
    "context"
    "database/sql"
    "time"

    "github.com/lib/pq"
)

// ... (Creative struct and CreativeModel struct) ...

func (c *CreativeModel) Insert(creative *Creative) error {
    query := `INSERT INTO creatives (user_id, creative_url, scheduled_at)
            VALUES (, , )
            RETURNING id, created_at`

    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    args := []interface{}{creative.UserID, creative.CreativeURL, creative.ScheduledAt}
    return c.DB.QueryRowContext(ctx, query, args...).Scan(&creative.ID, &creative.CreatedAt)
}

func (c *CreativeModel) GetScheduledCreatives() (map[string][]Creative, error) {
    query := `
        SELECT id, user_id, creative_url, scheduled_at, created_at 
        FROM creatives 
        WHERE scheduled_at = ANY()
    `

    dates := []time.Time{
        time.Now().Truncate(24 * time.Hour),
        time.Now().AddDate(0, 0, 1).Truncate(24 * time.Hour),
    }

    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    rows, err := c.DB.QueryContext(ctx, query, pq.Array(dates))
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    creatives := map[string][]Creative{
        "today":    {},
        "tomorrow": {},
    }

    for rows.Next() {
        var creative Creative
        err := rows.Scan(&creative.ID, &creative.UserID, &creative.CreativeURL, &creative.ScheduledAt, &creative.CreatedAt)
        if err != nil {
            return nil, err
        }
        if creative.ScheduledAt.Equal(dates[0]) {
            creatives["today"] = append(creatives["today"], creative)
        } else if creative.ScheduledAt.Equal(dates[1]) {
            creatives["tomorrow"] = append(creatives["tomorrow"], creative)
        }
    }

    return creatives, rows.Err()
}</code>

cmd/api/creatives.go: (Verwaltet Datei-Uploads und den Abruf geplanter Motive)

<code class="language-go">package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "path"
    "strings"
    "time"

    "github.com/google/uuid"
    "github.com/vishaaxl/cheershare/internal/data"
)

const MaxFileSize = 10 << 20 // 10MB

// ... (Existing code) ...

func (app *application) uploadCreativeHandler(w http.ResponseWriter, r *http.Request) {
    // ... (File upload handling logic) ...
}

func (app *application) getScheduledCreativesHandler(w http.ResponseWriter, r *http.Request) {
    // ... (Retrieve scheduled creatives logic) ...
}</code>

Fügen Sie Folgendes zu Ihrer main.go oder Serverinitialisierung hinzu, um das Upload-Verzeichnis zu erstellen, falls es nicht vorhanden ist:

<code class="language-go">uploadDir := "./uploads"
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
    err := os.MkdirAll(uploadDir, os.ModePerm)
    if err != nil {
        fmt.Println("Unable to create uploads directory:", err)
    }
}

router.HandlerFunc(http.MethodPost, "/upload-creative", app.requireAuthenticatedUser(app.uploadCreativeHandler))
router.HandlerFunc(http.MethodGet, "/scheduled", app.requireAuthenticatedUser(app.getScheduledCreativesHandler))
</code>

Denken Sie daran, Platzhalterkommentare durch tatsächliche Implementierungsdetails für die Dateiverarbeitung und Fehlerverwaltung innerhalb der Handler zu ersetzen. Damit ist die Kernfunktionalität abgeschlossen, mit Ausnahme der Zahlungsabwicklung (wird in zukünftigen Updates behandelt). Der bereitgestellte Link zu Teil 3 bleibt erhalten.

Das obige ist der detaillierte Inhalt vonErstellen Sie einen OTP-basierten Authentifizierungsserver mit Go: Part File Uploads und Graceful Shutdown. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn