이번 기사에서는 파일 업로드 기능을 추가하고, Makefile
을 사용하여 개발을 간소화하고, 서버 종료를 정상적으로 구현하여 Go 인증 서버를 개선합니다. 이렇게 하면 갑작스러운 종료를 방지하고 서버가 중지되기 전에 진행 중인 모든 작업이 완료되도록 할 수 있습니다.
정상적인 종료 구현
새로운 cmd/api/server.go
파일이 서버 관리를 중앙 집중화합니다. 고루틴은 종료 신호(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로 작업 자동화
AMakefile
는 반복적인 명령을 자동화합니다. 프로젝트의 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 help
, make run/api
, make db/migrations/new name=my_migration
, make db/migrations/up
, make audit
또는 make 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!