从餐厅点餐到微服务:用Gin+Go语言手把手搭建你的第一个RESTful API

张开发
2026/6/8 14:22:13 15 分钟阅读
从餐厅点餐到微服务:用Gin+Go语言手把手搭建你的第一个RESTful API
从餐厅点餐到微服务用GinGo语言手把手搭建你的第一个RESTful API走进任何一家餐厅你都会看到相似的工作流程顾客浏览菜单、服务员记录订单、后厨准备菜品、最后将食物送到餐桌。这个看似简单的过程与当今互联网服务的运作方式惊人地相似。本文将带你用Go语言的Gin框架构建一个完整的数字餐厅系统把点餐流程转化为GET /menu、POST /order等技术实现让抽象的API开发变得像点菜一样直观。1. 搭建开发环境与项目初始化在开始编码之前我们需要准备好开发环境。Go语言的安装过程简洁明了从官网下载对应操作系统的安装包即可。安装完成后通过终端验证go version接下来创建项目目录结构这是保持代码整洁的基础restaurant-api/ ├── go.mod # Go模块定义文件 ├── go.sum # 依赖校验文件 ├── handlers/ # 请求处理逻辑 ├── models/ # 数据结构定义 ├── routes/ # 路由配置 └── main.go # 应用入口使用以下命令初始化Go模块并安装Gin框架go mod init restaurant-api go get -u github.com/gin-gonic/gin提示建议使用Go 1.16版本它改进了模块管理功能。如果遇到网络问题可以设置GOPROXY环境变量go env -w GOPROXYhttps://goproxy.cn,direct2. 设计API端点与餐厅模型映射让我们将餐厅的每个环节转化为API端点餐厅环节HTTP方法API端点描述浏览菜单GET/menu获取所有菜品信息提交订单POST/orders创建新订单查询订单状态GET/orders/:id获取特定订单详情取消订单DELETE/orders/:id删除未处理的订单更新订单PATCH/orders/:id修改订单内容在models/menu.go中定义菜品数据结构type MenuItem struct { ID int json:id Name string json:name binding:required Description string json:description Price float64 json:price binding:gte0 Category string json:category // 如前菜、主菜、甜点 Available bool json:available }订单模型(models/order.go)则需要更复杂的结构type Order struct { ID string json:id gorm:primaryKey TableNo int json:table_no binding:required Items []OrderItem json:items binding:gt0,dive Status string json:status // pending, preparing, ready, served CreatedAt time.Time json:created_at } type OrderItem struct { MenuItemID int json:menu_item_id binding:required Quantity int json:quantity binding:gte1 }3. 实现核心路由与控制器在routes/routes.go中设置基本路由结构func SetupRouter() *gin.Engine { r : gin.Default() // 添加中间件 r.Use(gin.Logger()) r.Use(gin.Recovery()) api : r.Group(/api) { menu : api.Group(/menu) { menu.GET(, handlers.GetMenu) menu.POST(, handlers.AddMenuItem) } orders : api.Group(/orders) { orders.GET(, handlers.ListOrders) orders.POST(, handlers.CreateOrder) orders.GET(/:id, handlers.GetOrder) orders.PATCH(/:id, handlers.UpdateOrderStatus) } } return r }handlers/menu.go中的菜单控制器示例var menuItems []models.MenuItem{ {ID: 1, Name: 凯撒沙拉, Description: 新鲜罗马生菜配凯撒酱, Price: 38.0, Category: 前菜, Available: true}, {ID: 2, Name: 牛排, Description: 安格斯牛排配时蔬, Price: 128.0, Category: 主菜, Available: true}, } func GetMenu(c *gin.Context) { // 实际项目中应从数据库获取 c.JSON(http.StatusOK, gin.H{ data: menuItems, meta: gin.H{ total: len(menuItems), }, }) } func AddMenuItem(c *gin.Context) { var item models.MenuItem if err : c.ShouldBindJSON(item); err ! nil { c.JSON(http.StatusBadRequest, gin.H{error: err.Error()}) return } // 生成ID并添加到列表 item.ID len(menuItems) 1 menuItems append(menuItems, item) c.JSON(http.StatusCreated, gin.H{ data: item, }) }4. 数据库集成与高级功能为了持久化数据我们集成GORM一个流行的Go ORM库go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite在models/db.go中初始化数据库var DB *gorm.DB func ConnectDatabase() { database, err : gorm.Open(sqlite.Open(restaurant.db), gorm.Config{}) if err ! nil { panic(无法连接数据库) } // 自动迁移模型 database.AutoMigrate(MenuItem{}, Order{}, OrderItem{}) DB database }更新订单处理器(handlers/order.go)使用数据库func CreateOrder(c *gin.Context) { var order models.Order if err : c.ShouldBindJSON(order); err ! nil { c.JSON(http.StatusBadRequest, gin.H{error: err.Error()}) return } // 设置初始状态 order.ID uuid.New().String() order.Status pending order.CreatedAt time.Now() // 验证所有菜品ID有效 for _, item : range order.Items { var menuItem models.MenuItem if result : models.DB.First(menuItem, item.MenuItemID); result.Error ! nil { c.JSON(http.StatusBadRequest, gin.H{ error: fmt.Sprintf(菜品ID %d 不存在, item.MenuItemID), }) return } } // 保存到数据库 if result : models.DB.Create(order); result.Error ! nil { c.JSON(http.StatusInternalServerError, gin.H{ error: 创建订单失败, }) return } c.JSON(http.StatusCreated, gin.H{ data: order, }) }5. 添加中间件与错误处理创建认证中间件(middleware/auth.go)func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { apiKey : c.GetHeader(X-API-KEY) if apiKey ! os.Getenv(API_KEY) { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ error: 无效的API密钥, }) return } c.Next() } }统一错误处理中间件(middleware/error.go)func ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() // 检查是否有错误 errors : c.Errors if len(errors) 0 { err : errors[0].Err var statusCode int switch { case errors.Is(err, gorm.ErrRecordNotFound): statusCode http.StatusNotFound default: statusCode http.StatusInternalServerError } c.JSON(statusCode, gin.H{ error: err.Error(), }) } }) }6. 测试与部署编写简单的测试用例(handlers/menu_test.go)func TestGetMenu(t *testing.T) { // 创建测试路由 r : gin.Default() r.GET(/api/menu, GetMenu) // 创建测试请求 req, _ : http.NewRequest(GET, /api/menu, nil) w : httptest.NewRecorder() // 发送请求 r.ServeHTTP(w, req) // 验证响应 assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), response) data : response[data].([]interface{}) assert.Greater(t, len(data), 0) }使用Docker部署的简单配置(Dockerfile)FROM golang:1.18-alpine AS builder WORKDIR /app COPY . . RUN go mod download RUN CGO_ENABLED0 GOOSlinux go build -o restaurant-api . FROM alpine:latest WORKDIR /app COPY --frombuilder /app/restaurant-api . COPY --frombuilder /app/restaurant.db . EXPOSE 8080 CMD [./restaurant-api]构建并运行容器docker build -t restaurant-api . docker run -p 8080:8080 -e API_KEYyour-secret-key restaurant-api7. 性能优化与扩展Gin本身已经非常高效但我们还可以进一步优化添加缓存层对菜单这类不常变的数据使用Redis缓存实现分页当数据量大时为GET请求添加分页支持请求限流防止API被滥用添加Swagger文档方便前端开发者理解API缓存中间件示例(middleware/cache.go)func CacheMiddleware(ttl time.Duration) gin.HandlerFunc { return func(c *gin.Context) { // 只缓存GET请求 if c.Request.Method ! GET { c.Next() return } key : c.Request.URL.String() if cached, found : cache.Get(key); found { c.JSON(http.StatusOK, cached) c.Abort() return } // 替换响应写入器以捕获响应 writer : newResponseWriter(c.Writer) c.Writer writer c.Next() // 缓存成功的响应 if c.Writer.Status() http.StatusOK { var response interface{} if err : json.Unmarshal(writer.body.Bytes(), response); err nil { cache.Set(key, response, ttl) } } } }在项目中使用这个中间件// 在路由设置中添加缓存 menu.GET(, CacheMiddleware(5*time.Minute), handlers.GetMenu)

更多文章