Gin 前后端分离项目实战:从 0 到上线一个多端 Gin+Gorm项目
2026/8/6 3:33:29 网站建设 项目流程

Gin 前后端分离项目实战:从 0 到上线一个多端 Gin+Gorm项目

前言

最近为了系统练习 Go 后端开发,我做了一个完整的多端小项目:一间清单 Todo-list

它不是一个只写几个接口的 Demo,而是把一个后端项目从功能设计、数据库建模、接口开发、前端联调,到最后部署上线完整走了一遍。项目本身不复杂,但覆盖了 Gin 入门到实战里非常常见的一套流程。

项目核心功能是:用户输入一个房间名称,进入独立的任务空间。在每个房间中,可以分别管理「学习清单」和「任务清单」。

项目地址:

https://github.com/tuoxie423/Todo-List.git

在线体验:

https://list.tuoxie.asia

小程序端:

开发中的微信小程序:一间清单

为什么做这个项目

学习 Gin 的时候,如果只写单个接口,很容易停留在“会用”的阶段。真正做一个项目时,还会遇到很多实际问题:

  • 路由怎么组织
  • 表结构怎么设计
  • 前后端怎么联调
  • 配置文件怎么管理
  • 数据怎么隔离
  • 部署后 502 怎么排查
  • 服务怎么后台稳定运行

所以我选择做一个小而完整的任务清单项目,用一个清晰的业务场景,把这些知识点串起来。

项目功能

这个项目主要实现了这些功能:

  • 输入房间名称,创建或进入房间
  • 每个房间拥有独立任务数据
  • 管理学习清单
  • 管理任务清单
  • 新增任务
  • 标记完成或恢复未完成
  • 删除任务
  • 浏览器端记录最近进入过的房间
  • 网站端和小程序端共用同一套后端 API

整体可以理解为一个轻量级的多端任务清单系统。

项目结构

项目目录大致如下:

. ├── backend │ ├── main.go │ ├── config │ │ └── config.go │ ├── global │ │ └── global.go │ └── model │ ├── room.go │ └── task.go ├── frontend │ ├── home.html │ ├── home.js │ ├── tasks.html │ ├── app.js │ └── server.js ├── miniprogram │ ├── pages │ └── utils ├── config │ └── config.example.yaml └── README.md

其中:

  • backend:Gin 后端服务
  • frontend:浏览器端页面
  • miniprogram:微信小程序端
  • config:统一管理端口、数据库等配置

数据库模型设计

项目里有两个核心模型:房间和任务。

房间表

房间用于区分不同用户输入的任务空间。

typeRoomstruct{IDint`gorm:"primaryKey" json:"id"`Namestring`gorm:"type:varchar(100);not null;uniqueIndex" json:"name"`CreatedAt time.Time`json:"createdAt"`}

对应数据库表可以理解为:

rooms --- id 主键 name 房间名称,唯一 created_at 创建时间

这里给name加了唯一索引。这样用户再次输入同一个房间名时,会进入原来的清单,而不是创建一个重复房间。

任务表

任务表通过room_id和房间关联。

typeTaskstruct{IDint`gorm:"primaryKey" json:"id"`RoomIDint`gorm:"not null;index" json:"roomId"`Titlestring`gorm:"type:varchar(100);not null" json:"title"`Levelstring`gorm:"type:varchar(20);not null;default:'基础'" json:"level"`Kindstring`gorm:"type:varchar(30);not null;default:'learning'" json:"kind"`Donebool`gorm:"not null;default:false" json:"done"`CreatedAt time.Time`json:"createdAt"`}

对应数据库表可以理解为:

tasks --- id 主键 room_id 所属房间 ID title 任务标题 level 任务难度 kind 任务类型:learning / optimization done 是否完成 created_at 创建时间

这样设计之后,查询任务时必须带上room_id,可以保证不同房间之间的数据互不影响。

接口设计

后端接口按照 RESTful 风格设计:

POST /api/rooms GET /api/rooms/:roomID/tasks POST /api/rooms/:roomID/tasks PATCH /api/rooms/:roomID/tasks/:taskID/toggle DELETE /api/rooms/:roomID/tasks/:taskID

创建或进入房间

POST /api/rooms

请求体:

{"name":"Go 后端练习"}

后端会先根据房间名查询。如果房间不存在就创建;如果已经存在,就直接返回已有房间。

查询房间任务

GET /api/rooms/:roomID/tasks

返回:

{"items":[{"id":1,"roomId":1,"title":"跑通 Gin 后端服务","level":"基础","kind":"learning","done":false,"createdAt":"2026-06-27T..."}]}

新增任务

POST /api/rooms/:roomID/tasks

请求体:

{"title":"学习 GORM 表关联","level":"进阶","kind":"learning"}

其中kind用来区分不同清单:

learning 学习清单 optimization 任务清单

Gin 路由实现

后端使用 Gin 创建路由:

funcsetupRouter(db*gorm.DB)*gin.Engine{router:=gin.New()router.Use(gin.Logger(),gin.Recovery(),corsMiddleware())router.GET("/health",func(c*gin.Context){c.JSON(http.StatusOK,gin.H{"status":"ok","time":time.Now().Format(time.RFC3339),})})api:=router.Group("/api"){api.POST("/rooms",func(c*gin.Context){// 创建或进入房间})roomTasks:=api.Group("/rooms/:roomID/tasks"){roomTasks.GET("",func(c*gin.Context){// 查询任务})roomTasks.POST("",func(c*gin.Context){// 创建任务})roomTasks.PATCH("/:taskID/toggle",func(c*gin.Context){// 切换任务状态})roomTasks.DELETE("/:taskID",func(c*gin.Context){// 删除任务})}}returnrouter}

这里把任务接口都放在:

/api/rooms/:roomID/tasks

下面,是因为任务必须属于某一个房间。这样接口语义更清楚,也能避免误操作其他房间的数据。

参数校验

项目里对房间 ID 和任务 ID 做了统一校验:

funcparseParamID(c*gin.Context,namestring,messagestring)(int,bool){id,err:=strconv.Atoi(c.Param(name))iferr!=nil||id<=0{c.JSON(http.StatusBadRequest,gin.H{"message":message})return0,false}returnid,true}

使用时很直接:

roomID,ok:=parseParamID(c,"roomID","房间 ID 必须是正整数")if!ok{return}

除了 ID 校验,还要判断房间是否存在:

funcroomExists(c*gin.Context,db*gorm.DB,roomIDint)bool{varcountint64iferr:=db.Model(&model.Room{}).Where("id = ?",roomID).Count(&count).Error;err!=nil{c.JSON(http.StatusInternalServerError,gin.H{"message":"查询房间失败"})returnfalse}ifcount==0{c.JSON(http.StatusNotFound,gin.H{"message":"房间不存在"})returnfalse}returntrue}

这样可以避免给不存在的房间添加任务。

创建任务流程

创建任务时,后端主要做了这些事:

  1. 解析roomID
  2. 判断房间是否存在
  3. 解析 JSON 请求体
  4. 校验任务标题不能为空
  5. 规范化难度和任务类型
  6. 写入数据库
  7. 返回创建后的任务数据

核心代码:

varpayload createTaskRequestiferr:=c.ShouldBindJSON(&payload);err!=nil{c.JSON(http.StatusBadRequest,gin.H{"message":"请求体必须是 JSON"})return}title:=strings.TrimSpace(payload.Title)iftitle==""{c.JSON(http.StatusBadRequest,gin.H{"message":"任务标题不能为空"})return}task:=model.Task{RoomID:roomID,Title:title,Level:normalizeLevel(payload.Level),Kind:normalizeKind(payload.Kind),}iferr:=db.Create(&task).Error;err!=nil{c.JSON(http.StatusInternalServerError,gin.H{"message":"创建任务失败"})return}c.JSON(http.StatusCreated,task)

这里前端虽然也会做表单校验,但后端仍然必须校验。因为后端不能完全相信客户端传来的数据。

前端如何调用接口

前端通过fetch调用后端接口。

例如进入房间:

constroom=awaitrequest("/api/rooms",{method:"POST",body:JSON.stringify({name}),});

新增任务:

consttask=awaitrequest(`/api/rooms/${currentRoom.id}/tasks`,{method:"POST",body:JSON.stringify({title,level:levelInput.value,kind,}),});

前端页面本身不直接操作数据库,只负责收集用户输入,然后调用后端接口。真正的数据保存逻辑都在后端完成。

多端适配

这个项目除了浏览器端,还保留了微信小程序端目录。

小程序端和网站端共用同一套 API,只是请求方式不同:

浏览器端:fetch 小程序端:wx.request

小程序「一间清单」目前仍在开发/发布中。等后端接口和网站稳定后,小程序只需要把接口域名配置为:

https://list.tuoxie.asia

并在微信公众平台配置 request 合法域名即可。

配置文件管理

项目使用 YAML 文件统一管理配置。

示例:

backend:port:18080mode:releasefrontend:port:18090apiBase:http://localhost:18080database:host:localhostport:3306user:your_usernamepassword:your_passwordname:listcharset:utf8mb4

真实配置文件不应该上传仓库,只上传示例文件:

config/config.example.yaml

真实文件:

config/config.yaml

应该加入.gitignore,避免数据库账号密码泄露。

跨域处理

前后端分离时,浏览器请求后端接口可能遇到跨域问题,所以后端加了一个简单的 CORS 中间件:

funccorsMiddleware()gin.HandlerFunc{returnfunc(c*gin.Context){c.Writer.Header().Set("Access-Control-Allow-Origin","*")c.Writer.Header().Set("Access-Control-Allow-Methods","GET,POST,PATCH,DELETE,OPTIONS")c.Writer.Header().Set("Access-Control-Allow-Headers","Content-Type")ifc.Request.Method==http.MethodOptions{c.AbortWithStatus(http.StatusNoContent)return}c.Next()}}

练习项目这样写比较简单。正式项目可以根据具体域名收紧允许的来源。

部署上线

项目上线时,我使用 Nginx 对外提供 HTTPS,再反向代理到本机前后端服务:

https://list.tuoxie.asia ↓ Nginx 443 ↓ / -> 127.0.0.1:18090 /api/ -> 127.0.0.1:18080 /health -> 127.0.0.1:18080

后端编译命令:

go build-buildvcs=false-otodo-list.

服务后台运行建议使用systemd,这样进程异常退出后可以自动重启,也方便查看日志。

项目总结

这个项目虽然功能简单,但覆盖了 Go 后端开发中很常见的一套流程:

  • Gin 路由分组
  • RESTful API 设计
  • JSON 参数解析
  • 参数校验
  • GORM 操作数据库
  • 一对多表关联
  • 前后端分离调用
  • YAML 配置管理
  • 网站部署上线
  • 小程序多端适配

对初学者来说,它比单独写几个接口更有练习价值。因为它不是只关注某一个知识点,而是把一个后端项目从开发到上线的链路完整走了一遍。

后续还可以继续扩展:

  • 增加用户登录
  • 增加房间权限
  • 增加任务编辑
  • 增加分页查询
  • 增加接口日志
  • 增加更完整的单元测试

如果你也在学习 Gin,可以把这个项目当作一个练手模板:先跑通,再理解接口设计,最后尝试自己扩展功能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询