Maison >développement back-end >Golang >Créez un serveur d'authentification basé sur OTP avec Go : téléchargements de fichiers partiels et arrêt progressif
Cette version affine notre serveur d'authentification Go en ajoutant des capacités de téléchargement de fichiers, en rationalisant le développement avec un Makefile
et en mettant en œuvre un arrêt progressif du serveur. Cela évite une interruption brutale et garantit que toutes les tâches en cours sont terminées avant l'arrêt du serveur.
Mise en œuvre de l'arrêt progressif
Un nouveau fichier cmd/api/server.go
centralise la gestion des serveurs. Une goroutine surveille les signaux de terminaison (SIGINT, SIGTERM). Dès réception d'un signal, il arrête gracieusement le serveur. Les commentaires clarifient chaque étape.
<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>
Dans main.go
, remplacez le bloc srv.ListenAndServe()
par err = app.serve(router); if err != nil { logger.Fatal(err, nil) }
. Tester cela implique d'utiliser le mécanisme de nouvelle tentative (supprimer les informations d'identification pour les tentatives continues) et d'interrompre le serveur avec Ctrl C ; il devrait attendre la fin des tentatives avant de s'arrêter.
Automatiser les tâches avec Makefile
Un Makefile
automatise les commandes répétitives. Ajoutez ce qui suit au Makefile
de votre projet :
<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>
Exécutez des commandes telles que make help
, make run/api
, make db/migrations/new name=my_migration
, make db/migrations/up
, make audit
ou make build/api
. Des commandes supplémentaires peuvent être ajoutées en suivant cette structure.
Téléchargement et suivi de fichiers
Une nouvelle table de base de données suit les fichiers téléchargés :
Créer une migration 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
: (Gère les téléchargements de fichiers et la récupération des créations programmées)
<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>
Ajoutez ce qui suit à votre main.go
ou à l'initialisation de votre serveur pour créer le répertoire de téléchargement s'il n'existe pas :
<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>
N'oubliez pas de remplacer les commentaires fictifs par des détails d'implémentation réels pour la gestion des fichiers et la gestion des erreurs au sein des gestionnaires. Ceci complète la fonctionnalité de base, à l'exclusion du traitement des paiements (à couvrir dans les futures mises à jour). Le lien fourni vers la partie 3 est maintenu.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!