133 lines
3.1 KiB
Go
133 lines
3.1 KiB
Go
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")
|
|
}
|