首页 > 后端开发 > Golang > 使用 Go 构建基于 OTP 的身份验证服务器:部分文件上传和正常关闭

使用 Go 构建基于 OTP 的身份验证服务器:部分文件上传和正常关闭

Susan Sarandon
发布: 2025-01-17 18:03:14
原创
873 人浏览过

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

本期通过添加文件上传功能、使用 Makefile 简化开发以及实现优雅的服务器关闭来完善我们的 Go 身份验证服务器。 这可以防止突然终止并确保所有正在进行的任务在服务器停止之前完成。

正常关闭实施

新的cmd/api/server.go 文件集中了服务器管理。 Goroutine 监视终止信号(SIGINT、SIGTERM)。收到信号后,它会正常关闭服务器。 评论阐明了每个步骤。

<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>
登录后复制

main.go 中,将 srv.ListenAndServe() 块替换为 err = app.serve(router); if err != nil { logger.Fatal(err, nil) }。 测试涉及使用重试机制(删除连续重试的凭据)并使用 Ctrl C 中断服务器;它应该等待重试完成后再关闭。

使用 Makefile 自动化任务

A Makefile 自动执行重复命令。 将以下内容添加到项目的 Makefile 中:

<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>
登录后复制

运行 make helpmake run/apimake db/migrations/new name=my_migrationmake db/migrations/upmake auditmake build/api 等命令。 可以按照此结构添加其他命令。

文件上传和跟踪

新的数据库表跟踪上传的文件:

创建迁移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:(处理文件上传和预定广告素材的检索)

<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>
登录后复制

将以下内容添加到您的 main.go 或服务器初始化中以创建上传目录(如果不存在):

<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>
登录后复制

请记住将占位符注释替换为处理程序中文件处理和错误管理的实际实现细节。 这完成了核心功能,不包括支付处理(将在未来的更新中涵盖)。 所提供的第 3 部分链接将被保留。

以上是使用 Go 构建基于 OTP 的身份验证服务器:部分文件上传和正常关闭的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板