Go 后端开发学习笔记

内容列表

Go 后端开发学习笔记

目录

  1. 项目初始化
  2. 项目结构
  3. 依赖管理
  4. API开发
  5. 数据持久化
  6. 常见问题

项目初始化

1. 创建项目目录结构

mkdir -p backend/{cmd/server,configs,internal/{api,config},pkg}

2. 初始化Go模块

cd backend
go mod init front-page

3. 创建主程序入口

cmd/server/main.go中创建主程序入口:

package main

import (
    "context"
    "flag"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "front-page/internal/api"
    "front-page/internal/config"
)

func main() {
    // 解析命令行参数
    configPath := flag.String("config", "configs/config.yaml", "配置文件路径")
    flag.Parse()

    // 加载配置
    cfg, err := config.Load(*configPath)
    if err != nil {
        log.Fatalf("加载配置文件失败: %v", err)
    }

    // 创建API服务器
    router := api.NewRouter()
    server := &http.Server{
        Addr:         cfg.Server.Addr,
        Handler:      router,
        ReadTimeout:  time.Duration(cfg.Server.ReadTimeout) * time.Second,
        WriteTimeout: time.Duration(cfg.Server.WriteTimeout) * time.Second,
    }

    // 启动服务器
    go func() {
        log.Printf("启动服务器在 %s\n", cfg.Server.Addr)
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("监听失败: %v", err)
        }
    }()

    // 等待中断信号优雅地关闭服务器
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("正在关闭服务器...")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("服务器强制关闭: %v", err)
    }

    log.Println("服务器优雅退出")
}

4. 创建配置文件

configs/config.yaml中创建配置文件:

server:
  addr: ":8080"
  read_timeout: 10
  write_timeout: 10

db:
  driver: "sqlite3"
  dsn: "file:todos.db?cache=shared&mode=rwc"
  max_idle: 10
  max_open: 100
  log_level: "info"

5. 创建配置加载器

internal/config/config.go中创建配置加载器:

package config

import (
    "os"
    "gopkg.in/yaml.v3"
)

// Config 应用程序配置
type Config struct {
    Server ServerConfig `yaml:"server"`
    DB     DBConfig     `yaml:"db"`
}

// ServerConfig 服务器配置
type ServerConfig struct {
    Addr         string `yaml:"addr"`
    ReadTimeout  int    `yaml:"read_timeout"`
    WriteTimeout int    `yaml:"write_timeout"`
}

// DBConfig 数据库配置
type DBConfig struct {
    Driver   string `yaml:"driver"`
    DSN      string `yaml:"dsn"`
    MaxIdle  int    `yaml:"max_idle"`
    MaxOpen  int    `yaml:"max_open"`
    LogLevel string `yaml:"log_level"`
}

// Load 从文件加载配置
func Load(file string) (*Config, error) {
    data, err := os.ReadFile(file)
    if err != nil {
        return nil, err
    }

    var config Config
    if err := yaml.Unmarshal(data, &config); err != nil {
        return nil, err
    }

    return &config, nil
}

6. 创建API路由器

internal/api/router.go中创建API路由器:

package api

import (
    "net/http"
    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

// NewRouter 创建并配置一个新的路由器
func NewRouter() *gin.Engine {
    router := gin.Default()

    // 配置CORS
    router.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"*"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
    }))

    // 健康检查
    router.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })

    // API v1 组
    v1 := router.Group("/api/v1")
    {
        // Todo API
        todoRoutes := v1.Group("/todos")
        {
            todoRoutes.GET("", listTodos)
            todoRoutes.GET("/:id", getTodo)
            todoRoutes.POST("", createTodo)
            todoRoutes.PUT("/:id", updateTodo)
            todoRoutes.DELETE("/:id", deleteTodo)
        }
    }

    return router
}

7. 创建业务逻辑处理程序

internal/api/todo_handler.go中创建业务逻辑处理程序:

package api

import (
    "net/http"
    "strconv"
    "sync"
    "github.com/gin-gonic/gin"
)

// Todo 表示一个待办事项
type Todo struct {
    ID        int    `json:"id"`
    Title     string `json:"title" binding:"required"`
    Completed bool   `json:"completed"`
}

// 内存中存储Todo的简单实现
var (
    todos     = make(map[int]Todo)
    todoMutex = &sync.Mutex{}
    lastID    = 0
)

// listTodos 获取所有Todo
func listTodos(c *gin.Context) {
    todoMutex.Lock()
    defer todoMutex.Unlock()

    todoList := make([]Todo, 0, len(todos))
    for _, todo := range todos {
        todoList = append(todoList, todo)
    }

    c.JSON(http.StatusOK, todoList)
}

// getTodo 获取单个Todo
func getTodo(c *gin.Context) {
    idParam := c.Param("id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
        return
    }

    todoMutex.Lock()
    defer todoMutex.Unlock()

    todo, exists := todos[id]
    if !exists {
        c.JSON(http.StatusNotFound, gin.H{"error": "Todo不存在"})
        return
    }

    c.JSON(http.StatusOK, todo)
}

// createTodo 创建新Todo
func createTodo(c *gin.Context) {
    var newTodo Todo
    if err := c.ShouldBindJSON(&newTodo); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    todoMutex.Lock()
    defer todoMutex.Unlock()

    lastID++
    newTodo.ID = lastID
    todos[newTodo.ID] = newTodo

    c.JSON(http.StatusCreated, newTodo)
}

// updateTodo 更新Todo
func updateTodo(c *gin.Context) {
    idParam := c.Param("id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
        return
    }

    var updateTodo Todo
    if err := c.ShouldBindJSON(&updateTodo); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    todoMutex.Lock()
    defer todoMutex.Unlock()

    _, exists := todos[id]
    if !exists {
        c.JSON(http.StatusNotFound, gin.H{"error": "Todo不存在"})
        return
    }

    updateTodo.ID = id
    todos[id] = updateTodo

    c.JSON(http.StatusOK, updateTodo)
}

// deleteTodo 删除Todo
func deleteTodo(c *gin.Context) {
    idParam := c.Param("id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
        return
    }

    todoMutex.Lock()
    defer todoMutex.Unlock()

    _, exists := todos[id]
    if !exists {
        c.JSON(http.StatusNotFound, gin.H{"error": "Todo不存在"})
        return
    }

    delete(todos, id)

    c.JSON(http.StatusOK, gin.H{"message": "Todo已删除"})
}

8. 运行项目

# 安装依赖
go mod download

# 运行项目
go run ./cmd/server/main.go

项目结构

标准Go项目布局

backend/
├── cmd/           # 主应用程序入口
├── configs/       # 配置文件
├── internal/      # 私有应用程序和库代码
│   ├── api/       # API处理程序
│   └── config/    # 配置加载
├── pkg/           # 公共库代码
├── vendor/        # 依赖管理
└── go.mod         # Go模块定义

重要目录说明

  • /cmd: 应用程序入口点
  • /internal: 私有代码,不允许外部导入
  • /pkg: 可被外部项目导入的公共代码
  • /configs: 配置文件目录
  • /vendor: 依赖管理(可选)

依赖管理

Go Modules

  • 使用go.mod文件管理依赖
  • 常用命令:
    go mod init      # 初始化模块
    go mod download  # 下载依赖
    go mod tidy      # 清理依赖
    go mod vendor    # 创建vendor目录
    

依赖代理设置

在中国大陆地区,建议设置国内代理:

go env -w GOPROXY=https://goproxy.cn,direct

API开发

使用Gin框架

  • 创建路由
  • 处理请求
  • 返回响应

API示例

// 健康检查
router.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "pong",
    })
})

// Todo API
todoRoutes := v1.Group("/todos")
{
    todoRoutes.GET("", listTodos)
    todoRoutes.GET("/:id", getTodo)
    todoRoutes.POST("", createTodo)
    todoRoutes.PUT("/:id", updateTodo)
    todoRoutes.DELETE("/:id", deleteTodo)
}

数据持久化

SQLite数据库

  1. 安装SQLite:

    brew install sqlite3
    
  2. 安装Go SQLite驱动:

    go get github.com/mattn/go-sqlite3
    
  3. 数据库配置示例:

    db:
      driver: "sqlite3"
      dsn: "file:todos.db?cache=shared&mode=rwc"
    

常见问题

1. 依赖下载超时

解决方案:

go env -w GOPROXY=https://goproxy.cn,direct

2. vendor目录问题

  • 忽略vendor目录运行:
    go run -mod=mod ./cmd/server/main.go
    
  • 重新生成vendor:
    go mod vendor
    

3. 配置文件加载失败

解决方案:

  • 在正确目录下运行:
    cd backend
    go run ./cmd/server/main.go
    
  • 或指定配置文件路径:
    go run ./cmd/server/main.go -config=./backend/configs/config.yaml
    

4. .DS_Store文件

  • .gitignore中添加:
    .DS_Store
    
  • 删除已跟踪的.DS_Store文件:
    git rm --cached .DS_Store
    

最佳实践

  1. 项目结构:遵循标准Go项目布局
  2. 依赖管理:使用Go Modules
  3. 代码组织:将公共代码放在pkg,私有代码放在internal
  4. 配置管理:使用YAML或JSON格式的配置文件
  5. 错误处理:使用适当的错误处理机制
  6. 测试:编写单元测试和集成测试

参考资料

相关

Linux-Vim 编辑器

2018-04-27

在 Linux 这样的命令行操作系统中,必须有一款功能强大的编辑器支持我们快速完成文本编辑,这就是 Vi 编辑器;通过对其加强和升级,Vim 编辑器比前者更为强大,拥有更多的功能和颜色高亮的特性,是程序员在 Linux 下编码的利器。

了解更多

DOM-文档片段

2018-05-23

DOM 操作是非常消耗性能的,如果要进行大量的 DOM 操作,我们可以选择在内存中先构建一个文档片段然后一次性插入 DOM 树。

了解更多

开发web中遇到的一些兼容问题汇总

2025-06-20

在使用vue或者react新时代框架开发前端页面的时候总会遇到这样或者那样的问题

了解更多