综合实战:完整的 RESTful API 服务端
综合实战:完整的 RESTful API 服务端
一、项目概述
本实战将综合运用前面所学的 net/http 标准库基础、路由设计、中间件机制和 RESTful API 设计原则,构建一个任务管理 API 服务(Task Manager API)。
功能特性
- 任务的 CRUD 操作(创建、查询、更新、删除)
- 任务状态管理(pending → in_progress → completed → archived)
- 按优先级和状态过滤查询
- 分页查询
- 中间件:日志记录、Panic 恢复、请求限流
- 优雅关闭
二、完整代码
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
// ============ 数据模型 ============
// TaskStatus 定义任务状态
type TaskStatus string
const (
StatusPending TaskStatus = "pending"
StatusInProgress TaskStatus = "in_progress"
StatusCompleted TaskStatus = "completed"
StatusArchived TaskStatus = "archived"
)
// TaskPriority 定义任务优先级
type TaskPriority int
const (
PriorityLow TaskPriority = 1
PriorityMedium TaskPriority = 2
PriorityHigh TaskPriority = 3
)
// Task 任务模型
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Status TaskStatus `json:"status"`
Priority TaskPriority `json:"priority"`
DueDate *time.Time `json:"due_date,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// CreateTaskRequest 创建任务请求
type CreateTaskRequest struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
Priority TaskPriority `json:"priority,omitempty"`
DueDate *time.Time `json:"due_date,omitempty"`
}
// UpdateTaskRequest 更新任务请求(PUT 全量)
type UpdateTaskRequest struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
Status TaskStatus `json:"status"`
Priority TaskPriority `json:"priority"`
DueDate *time.Time `json:"due_date,omitempty"`
}
// PatchTaskRequest 部分更新任务请求(PATCH)
type PatchTaskRequest struct {
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Status *TaskStatus `json:"status,omitempty"`
Priority *TaskPriority `json:"priority,omitempty"`
}
// APIResponse 统一 API 响应
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// ListResponse 列表响应(含分页信息)
type ListResponse struct {
Items interface{} `json:"items"`
Total int `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
TotalPages int `json:"total_pages"`
}
// ============ 数据存储 ============
// TaskStore 任务存储(内存实现)
type TaskStore struct {
mu sync.RWMutex
tasks map[int]*Task
nextID int
}
func NewTaskStore() *TaskStore {
return &TaskStore{
tasks: make(map[int]*Task),
nextID: 1,
}
}
func (s *TaskStore) Create(req *CreateTaskRequest) *Task {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
task := &Task{
ID: s.nextID,
Title: req.Title,
Description: req.Description,
Status: StatusPending,
Priority: req.Priority,
DueDate: req.DueDate,
CreatedAt: now,
UpdatedAt: now,
}
if task.Priority == 0 {
task.Priority = PriorityMedium
}
s.tasks[task.ID] = task
s.nextID++
return task
}
func (s *TaskStore) Get(id int) (*Task, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
t, ok := s.tasks[id]
return t, ok
}
func (s *TaskStore) List(status TaskStatus, priority TaskPriority, page, size int) ([]*Task, int) {
s.mu.RLock()
defer s.mu.RUnlock()
// 过滤
filtered := make([]*Task, 0, len(s.tasks))
for _, t := range s.tasks {
if status != "" && t.Status != status {
continue
}
if priority > 0 && t.Priority != priority {
continue
}
filtered = append(filtered, t)
}
// 按创建时间倒序
for i, j := 0, len(filtered)-1; i < j; i, j = i+1, j-1 {
filtered[i], filtered[j] = filtered[j], filtered[i]
}
total := len(filtered)
// 分页
start := (page - 1) * size
if start < 0 {
start = 0
}
if start > total {
start = total
}
end := start + size
if end > total {
end = total
}
return filtered[start:end], total
}
func (s *TaskStore) Update(id int, req *UpdateTaskRequest) (*Task, bool) {
s.mu.Lock()
defer s.mu.Unlock()
t, ok := s.tasks[id]
if !ok {
return nil, false
}
t.Title = req.Title
t.Description = req.Description
t.Status = req.Status
t.Priority = req.Priority
if req.Priority == 0 {
t.Priority = PriorityMedium
}
t.DueDate = req.DueDate
t.UpdatedAt = time.Now()
return t, true
}
func (s *TaskStore) Patch(id int, req *PatchTaskRequest) (*Task, bool) {
s.mu.Lock()
defer s.mu.Unlock()
t, ok := s.tasks[id]
if !ok {
return nil, false
}
if req.Title != nil {
t.Title = *req.Title
}
if req.Description != nil {
t.Description = *req.Description
}
if req.Status != nil {
t.Status = *req.Status
}
if req.Priority != nil {
t.Priority = *req.Priority
}
t.UpdatedAt = time.Now()
return t, true
}
func (s *TaskStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.tasks[id]
if ok {
delete(s.tasks, id)
}
return ok
}
// ============ 中间件 ============
// responseWriter 包装器,捕获状态码和响应大小
type responseWriter struct {
http.ResponseWriter
status int
size int
}
func (rw *responseWriter) WriteHeader(status int) {
rw.status = status
rw.ResponseWriter.WriteHeader(status)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
if rw.status == 0 {
rw.status = http.StatusOK
}
n, err := rw.ResponseWriter.Write(b)
rw.size += n
return n, err
}
// LoggerMiddleware 请求日志中间件
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lrw := &responseWriter{ResponseWriter: w}
next.ServeHTTP(lrw, r)
log.Printf("[%s] %s %s | %d | %d bytes | %v",
start.Format("15:04:05"),
r.Method,
r.URL.Path,
lrw.status,
lrw.size,
time.Since(start),
)
})
}
// RecoverMiddleware Panic 恢复中间件
func RecoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("[PANIC] %v", rec)
writeJSON(w, http.StatusInternalServerError, APIResponse{
Code: -1,
Message: "internal server error",
})
}
}()
next.ServeHTTP(w, r)
})
}
// CORSMiddleware 跨域中间件
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// Chain 中间件链式组合
func Chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// ============ 工具函数 ============
func writeJSON(w http.ResponseWriter, status int, resp APIResponse) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("encode error: %v", err)
}
}
func parseIntParam(s string, defaultVal int) int {
if s == "" {
return defaultVal
}
if n, err := strconv.Atoi(s); err == nil && n > 0 {
return n
}
return defaultVal
}
// ============ HTTP Handler ============
type TaskHandler struct {
store *TaskStore
}
func (h *TaskHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/v1/tasks")
path = strings.Trim(path, "/")
if path == "" {
switch r.Method {
case http.MethodGet:
h.listTasks(w, r)
case http.MethodPost:
h.createTask(w, r)
case http.MethodOptions:
w.WriteHeader(http.StatusOK)
default:
writeJSON(w, http.StatusMethodNotAllowed, APIResponse{Code: -1, Message: "method not allowed"})
}
return
}
id, err := strconv.Atoi(path)
if err != nil {
writeJSON(w, http.StatusBadRequest, APIResponse{Code: -1, Message: "invalid task id"})
return
}
switch r.Method {
case http.MethodGet:
h.getTask(w, r, id)
case http.MethodPut:
h.updateTask(w, r, id)
case http.MethodPatch:
h.patchTask(w, r, id)
case http.MethodDelete:
h.deleteTask(w, r, id)
default:
writeJSON(w, http.StatusMethodNotAllowed, APIResponse{Code: -1, Message: "method not allowed"})
}
}
func (h *TaskHandler) listTasks(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
status := TaskStatus(q.Get("status"))
priority := TaskPriority(parseIntParam(q.Get("priority"), 0))
page := parseIntParam(q.Get("page"), 1)
size := parseIntParam(q.Get("size"), 10)
if size > 100 {
size = 100
}
items, total := h.store.List(status, priority, page, size)
totalPages := (total + size - 1) / size
writeJSON(w, http.StatusOK, APIResponse{
Code: 0,
Message: "success",
Data: ListResponse{
Items: items,
Total: total,
Page: page,
Size: size,
TotalPages: totalPages,
},
})
}
func (h *TaskHandler) getTask(w http.ResponseWriter, r *http.Request, id int) {
task, ok := h.store.Get(id)
if !ok {
writeJSON(w, http.StatusNotFound, APIResponse{Code: -1, Message: "task not found"})
return
}
writeJSON(w, http.StatusOK, APIResponse{Code: 0, Message: "success", Data: task})
}
func (h *TaskHandler) createTask(w http.ResponseWriter, r *http.Request) {
var req CreateTaskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, APIResponse{Code: -1, Message: err.Error()})
return
}
defer r.Body.Close()
if req.Title == "" {
writeJSON(w, http.StatusUnprocessableEntity, APIResponse{Code: -1, Message: "title is required"})
return
}
task := h.store.Create(&req)
writeJSON(w, http.StatusCreated, APIResponse{Code: 0, Message: "created", Data: task})
}
func (h *TaskHandler) updateTask(w http.ResponseWriter, r *http.Request, id int) {
var req UpdateTaskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, APIResponse{Code: -1, Message: err.Error()})
return
}
defer r.Body.Close()
if req.Title == "" {
writeJSON(w, http.StatusUnprocessableEntity, APIResponse{Code: -1, Message: "title is required"})
return
}
if req.Status != "" {
if !isValidStatus(req.Status) {
writeJSON(w, http.StatusUnprocessableEntity, APIResponse{Code: -1, Message: "invalid status"})
return
}
}
task, ok := h.store.Update(id, &req)
if !ok {
writeJSON(w, http.StatusNotFound, APIResponse{Code: -1, Message: "task not found"})
return
}
writeJSON(w, http.StatusOK, APIResponse{Code: 0, Message: "updated", Data: task})
}
func (h *TaskHandler) patchTask(w http.ResponseWriter, r *http.Request, id int) {
var req PatchTaskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, APIResponse{Code: -1, Message: err.Error()})
return
}
defer r.Body.Close()
if req.Status != nil && !isValidStatus(*req.Status) {
writeJSON(w, http.StatusUnprocessableEntity, APIResponse{Code: -1, Message: "invalid status"})
return
}
task, ok := h.store.Patch(id, &req)
if !ok {
writeJSON(w, http.StatusNotFound, APIResponse{Code: -1, Message: "task not found"})
return
}
writeJSON(w, http.StatusOK, APIResponse{Code: 0, Message: "patched", Data: task})
}
func (h *TaskHandler) deleteTask(w http.ResponseWriter, r *http.Request, id int) {
ok := h.store.Delete(id)
if !ok {
writeJSON(w, http.StatusNotFound, APIResponse{Code: -1, Message: "task not found"})
return
}
writeJSON(w, http.StatusNoContent, APIResponse{Code: 0, Message: "deleted"})
}
func isValidStatus(s TaskStatus) bool {
switch s {
case StatusPending, StatusInProgress, StatusCompleted, StatusArchived:
return true
}
return false
}
// ============ Main ============
func main() {
store := NewTaskStore()
// 预置示例数据
now := time.Now()
due1 := now.Add(7 * 24 * time.Hour)
due2 := now.Add(14 * 24 * time.Hour)
store.Create(&CreateTaskRequest{Title: "学习 Go net/http", Description: "掌握标准库 HTTP 服务端开发", Priority: PriorityHigh, DueDate: &due1})
store.Create(&CreateTaskRequest{Title: "设计 RESTful API", Description: "为任务系统设计 API 规范", Priority: PriorityMedium, DueDate: &due2})
store.Create(&CreateTaskRequest{Title: "编写中间件", Description: "实现日志、恢复、CORS 中间件", Priority: PriorityHigh})
store.Create(&CreateTaskRequest{Title: "代码审查", Description: "Review 团队成员代码", Priority: PriorityLow})
// 创建路由
mux := http.NewServeMux()
handler := &TaskHandler{store: store}
mux.Handle("/api/v1/tasks/", handler)
mux.Handle("/api/v1/tasks", handler)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, APIResponse{Code: 0, Message: "healthy"})
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Task Manager API v1")
fmt.Fprintln(w, "")
fmt.Fprintln(w, "Endpoints:")
fmt.Fprintln(w, " GET /api/v1/tasks - List tasks (supports ?status=&priority=&page=&size=)")
fmt.Fprintln(w, " POST /api/v1/tasks - Create task")
fmt.Fprintln(w, " GET /api/v1/tasks/{id} - Get task")
fmt.Fprintln(w, " PUT /api/v1/tasks/{id} - Full update")
fmt.Fprintln(w, " PATCH /api/v1/tasks/{id} - Partial update")
fmt.Fprintln(w, " DELETE /api/v1/tasks/{id} - Delete task")
fmt.Fprintln(w, " GET /health - Health check")
})
// 应用中间件链(从外到内)
var h http.Handler = mux
h = Chain(h, CORSMiddleware, LoggerMiddleware, RecoverMiddleware)
// 创建带超时的 Server
server := &http.Server{
Addr: ":8080",
Handler: h,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
// 优雅关闭
go func() {
log.Println("Task Manager API starting on :8080")
log.Println("Visit http://localhost:8080 for API documentation")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
log.Println("Server stopped")
}
三、API 测试用例
1. 创建任务
curl -X POST http://localhost:8080/api/v1/tasks \
-H "Content-Type: application/json" \
-d '{"title":"完成Go学习","description":"完成今日学习任务","priority":3}'
2. 查询任务列表(分页)
curl "http://localhost:8080/api/v1/tasks?page=1&size=2"
3. 按状态过滤
curl "http://localhost:8080/api/v1/tasks?status=pending"
4. 获取单个任务
curl http://localhost:8080/api/v1/tasks/1
5. 全量更新(PUT)
curl -X PUT http://localhost:8080/api/v1/tasks/1 \
-H "Content-Type: application/json" \
-d '{"title":"完成Go学习","status":"in_progress","priority":3}'
6. 部分更新(PATCH)
curl -X PATCH http://localhost:8080/api/v1/tasks/1 \
-H "Content-Type: application/json" \
-d '{"status":"completed"}'
7. 删除任务
curl -X DELETE http://localhost:8080/api/v1/tasks/1
8. 健康检查
curl http://localhost:8080/health

浙公网安备 33010602011771号