Go 语言编程基础:项目实战案例
本部分通过一个完整的实战项目来帮助你将所学的 Go 语言知识应用到实际开发中。我们将从项目的需求分析开始,逐步实现一个简单的 RESTful API 服务,涵盖路由、数据库操作、用户认证、日志管理等。
1. 项目需求
我们要开发一个简单的任务管理系统,用户可以通过 API 来创建任务、查看任务列表、更新任务状态以及删除任务。每个任务包含标题、描述、创建时间、状态(待办、完成)等属性。
1.1 API 功能
- 获取任务列表:
GET /tasks
- 获取单个任务:
GET /tasks/:id
- 创建任务:
POST /tasks
- 更新任务状态:
PUT /tasks/:id
- 删除任务:
DELETE /tasks/:id
2. 技术栈选择
- Web 框架:使用 Gin 框架来处理 HTTP 路由和请求。
- 数据库:使用 SQLite 数据库(通过 GORM 进行操作)。
- 日志记录:使用 Logrus 进行日志管理。
- 配置管理:使用 Viper 管理项目的配置文件。
3. 项目结构
我们将按照 Go 的最佳实践来组织项目结构:
task-manager/
├── cmd/
│ └── server/
│ └── main.go # 应用入口
├── internal/
│ ├── database/
│ │ └── db.go # 数据库初始化与操作
│ ├── models/
│ │ └── task.go # 任务模型定义
│ ├── routes/
│ │ └── task_routes.go # 路由和控制器
├── config/
│ └── config.yaml # 配置文件
├── logs/
│ └── app.log # 日志文件
├── go.mod # Go 模块依赖
├── go.sum # 依赖锁定文件
└── README.md # 项目文档
4. 项目实现
4.1 初始化项目
首先,我们使用 go mod init
初始化 Go 模块:
go mod init task-manager
安装依赖:
go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
go get -u github.com/sirupsen/logrus
go get -u github.com/spf13/viper
4.2 配置文件
在 config/config.yaml
文件中定义应用的基本配置,如数据库路径和服务器端口:
app:
port: ":8080"
database:
path: "./tasks.db"
logging:
file: "./logs/app.log"
4.3 数据库初始化
在 internal/database/db.go
中初始化 SQLite 数据库,并使用 GORM 进行操作。
package database
import (
"log"
"task-manager/internal/models"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var DB *gorm.DB
func InitDatabase(path string) {
var err error
DB, err = gorm.Open(sqlite.Open(path), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
// 自动迁移数据库结构
DB.AutoMigrate(&models.Task{})
}
4.4 任务模型
在 internal/models/task.go
中定义任务的模型结构:
package models
import (
"time"
)
type Task struct {
ID uint `gorm:"primaryKey"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
Status string `json:"status"` // "pending" or "completed"
}
4.5 任务路由与控制器
在 internal/routes/task_routes.go
中定义任务相关的 API 路由和控制器:
package routes
import (
"net/http"
"task-manager/internal/database"
"task-manager/internal/models"
"github.com/gin-gonic/gin"
)
// 获取任务列表
func GetTasks(c *gin.Context) {
var tasks []models.Task
database.DB.Find(&tasks)
c.JSON(http.StatusOK, tasks)
}
// 获取单个任务
func GetTask(c *gin.Context) {
var task models.Task
if err := database.DB.First(&task, c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
return
}
c.JSON(http.StatusOK, task)
}
// 创建任务
func CreateTask(c *gin.Context) {
var task models.Task
if err := c.ShouldBindJSON(&task); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
task.CreatedAt = time.Now()
task.Status = "pending"
database.DB.Create(&task)
c.JSON(http.StatusOK, task)
}
// 更新任务状态
func UpdateTask(c *gin.Context) {
var task models.Task
if err := database.DB.First(&task, c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
return
}
var input models.Task
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
task.Status = input.Status
database.DB.Save(&task)
c.JSON(http.StatusOK, task)
}
// 删除任务
func DeleteTask(c *gin.Context) {
var task models.Task
if err := database.DB.First(&task, c.Param("id")).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
return
}
database.DB.Delete(&task)
c.JSON(http.StatusOK, gin.H{"message": "Task deleted"})
}
4.6 服务器入口
在 cmd/server/main.go
中配置服务器并启动应用:
package main
import (
"task-manager/internal/database"
"task-manager/internal/routes"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"os"
)
func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./config")
if err := viper.ReadInConfig(); err != nil {
panic("failed to read config file")
}
}
func setupLogger() *logrus.Logger {
log := logrus.New()
logFile, err := os.OpenFile(viper.GetString("logging.file"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.Out = logFile
} else {
log.Info("Failed to log to file, using default stderr")
}
return log
}
func main() {
initConfig()
database.InitDatabase(viper.GetString("database.path"))
log := setupLogger()
router := gin.Default()
router.GET("/tasks", routes.GetTasks)
router.GET("/tasks/:id", routes.GetTask)
router.POST("/tasks", routes.CreateTask)
router.PUT("/tasks/:id", routes.UpdateTask)
router.DELETE("/tasks/:id", routes.DeleteTask)
log.Info("Starting server at", viper.GetString("app.port"))
router.Run(viper.GetString("app.port"))
}
5. 测试 API
我们可以通过 curl
命令或 Postman 工具来测试 API。
5.1 创建任务
curl -X POST http://localhost:8080/tasks -H "Content-Type: application/json" -d '{"title": "Buy groceries", "description": "Buy milk, eggs, and bread"}'
5.2 获取任务列表
curl http://localhost:8080/tasks
5.3 获取单个任务
curl http://localhost:8080/tasks/1
5.4 更新任务状态
curl -X PUT http://localhost:8080/tasks/1 -H "Content-Type: application/json" -d '{"status": "completed"}'
5.5 删除任务
curl -X DELETE http://localhost:8080/tasks/1
6. 小结
- 配置管理:使用 Viper 管理项目的配置,支持 YAML 格式配置文件。
- 数据库操作:使用 GORM 进行 SQLite 数据库操作,实现了任务的增删改查。
- API 路由:使用 Gin 框架实现 RESTful API 路由。
- 日志记录:使用 Logrus 进行日志记录,支持文件输出。