Add Chapter 6: Web API Project - Full Stack Go Service with Middleware, DB, and Docker
This commit is contained in:
132
chapters/chapter-6/main.go
Normal file
132
chapters/chapter-6/main.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 模型
|
||||
type Task struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Priority int `json:"priority"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// 响应
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// 内存存储(模拟数据库)
|
||||
var tasks = make(map[int64]*Task)
|
||||
var nextID int64 = 1
|
||||
|
||||
// Handler
|
||||
func createTask(w http.ResponseWriter, r *http.Request) {
|
||||
var task Task
|
||||
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if task.Title == "" {
|
||||
respondError(w, http.StatusBadRequest, "title is required")
|
||||
return
|
||||
}
|
||||
|
||||
task.ID = nextID
|
||||
nextID++
|
||||
task.Status = "todo"
|
||||
task.CreatedAt = time.Now()
|
||||
task.UpdatedAt = time.Now()
|
||||
tasks[task.ID] = &task
|
||||
|
||||
respondJSON(w, http.StatusCreated, "created", task)
|
||||
}
|
||||
|
||||
func getTasks(w http.ResponseWriter, r *http.Request) {
|
||||
taskList := make([]*Task, 0, len(tasks))
|
||||
for _, t := range tasks {
|
||||
taskList = append(taskList, t)
|
||||
}
|
||||
respondJSON(w, http.StatusOK, "success", taskList)
|
||||
}
|
||||
|
||||
func respondJSON(w http.ResponseWriter, code int, msg string, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(Response{Code: code, Message: msg, Data: data})
|
||||
}
|
||||
|
||||
func respondError(w http.ResponseWriter, code int, msg string) {
|
||||
respondJSON(w, code, msg, nil)
|
||||
}
|
||||
|
||||
// 中间件
|
||||
func loggerMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
log.Printf("Started %s %s", r.Method, r.URL.Path)
|
||||
next(w, r)
|
||||
log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("Panic recovered: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 路由
|
||||
http.HandleFunc("/tasks", recoveryMiddleware(loggerMiddleware(createTask)))
|
||||
http.HandleFunc("/tasks", recoveryMiddleware(loggerMiddleware(getTasks)))
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: nil, // 使用默认 ServeMux
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
go func() {
|
||||
log.Println("Server starting on port 8080")
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 优雅关闭
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatal("Server forced to shutdown:", err)
|
||||
}
|
||||
|
||||
log.Println("Server exited")
|
||||
}
|
||||
Reference in New Issue
Block a user