Add Chapter 6: Web API Project - Full Stack Go Service with Middleware, DB, and Docker

This commit is contained in:
openclaw
2026-03-24 00:07:10 +00:00
parent 0e57ae2b44
commit c0ea858072
3 changed files with 873 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
module task-api
go 1.21

132
chapters/chapter-6/main.go Normal file
View 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")
}