Go 语言编程基础:项目实战案例

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 进行日志记录,支持文件输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟生啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值