GO语言工程实践课后作业:实现思路、代码以及路径记录 | 青训营

77 阅读8分钟

系列文章目录

Go语言入门指南:基础语法和常用特性解析 | 青训营

高质量编程与性能调优实战 | 青训营

Git 的正确使用姿势与最佳实践:团队协作和版本控制的最佳实践| 青训营

使用 GORM(Go 的 ORM 库)连接数据库,并实现增删改查操作 | 青训营

如何将我的服务开放给用户:构建 API 接口和用户认证的实践指南 | 青训营

分析抖音的互联网架构 | 青训营

下面是关于 "GO语言工程实践课后作业:实现思路、代码以及路径记录" 、创建一个待办事项列表应用,用户可以通过API添加、编辑和删除待办任务的学习大纲:

@TOC


Prerequisite

  1. Go语言基础: 了解Go语言的基础结构,数据类型,和控制结构。
  2. Go包管理和模块: 学习如何使用Go的包管理工具和模块系统。
  3. Go并发模型: 了解goroutines, channels以及select的用法。
  4. Go的网络编程: 掌握如何使用Go进行网络编程,创建API。
  5. Go的数据库交互: 学习如何在Go中与数据库交互,例如SQL数据库。
  6. Go的单元测试和基准测试: 学习如何为Go程序编写单元测试和基准测试。
  7. Go应用程序的部署和监控: 了解如何部署和监控Go应用程序。

Main Curriculum

  1. 创建To-Do List应用程序的结构: 设计应用程序的基本结构和模型。
  2. 实现CRUD操作: 学习如何为To-Do List应用程序实现创建、读取、更新和删除操作。
  3. Go中的错误处理: 掌握如何在Go中进行有效的错误处理。
  4. To-Do List应用程序的安全性: 学习如何使应用程序安全,包括验证和授权。
  5. 优化Go应用程序的性能: 探索如何优化Go应用程序的性能和响应时间。
  6. To-Do List应用程序的前端交互: 学习如何使用Go为前端提供API。
  7. 部署To-Do List应用程序: 掌握如何部署和扩展To-Do List应用程序。
  8. 监控和日志: 了解如何为应用程序添加监控和日志功能。
  9. 持续集成和持续部署: 学习如何为Go应用程序设置CI/CD。
  10. 回顾和评估: 回顾所学内容并进行评估。

1.1 创建To-Do List应用程序的结构

当开始创建一个新的应用程序时,最重要的是先定义其结构。这有助于组织代码,并使未来的开发更加容易。以下是为To-Do List应用程序设计的推荐结构:

1. 目录结构

为了保持代码的清晰和有组织,通常会根据功能来分隔代码。例如:

  • cmd/: 主程序入口
  • models/: 存放数据模型,如任务模型
  • handlers/: 处理HTTP请求的函数
  • db/: 数据库相关的代码
  • config/: 配置文件和解析配置的代码

2. 数据模型

models/目录中,可以定义一个名为task.go的文件,该文件描述一个任务:

type Task struct {
    ID          int    `json:"id"`
    Title       string `json:"title"`
    Description string `json:"description"`
    Completed   bool   `json:"completed"`
}

3. 路由

需要定义应用程序的路由,以便知道如何响应不同的HTTP请求。例如:

  • GET /tasks: 获取所有任务
  • POST /tasks: 创建新任务
  • PUT /tasks/{id}: 更新指定任务
  • DELETE /tasks/{id}: 删除指定任务

4. 数据库

需要一个数据库来存储任务。通常,会选择一个关系型数据库,如PostgreSQL或MySQL。在db/目录中,可以有一个文件来初始化数据库连接,并创建所需的表。

5. 配置

应用程序可能需要外部配置,例如数据库凭据。可以在config/目录中存放这些配置,并提供一个方法来读取它们。

6. 处理函数

handlers/目录下,我们会为每个路由定义一个处理函数。这些函数负责接收HTTP请求、处理它,并返回适当的响应。

例如,为了获取所有任务,我们可以有如下的处理函数:

func GetAllTasks(w http.ResponseWriter, r *http.Request) {
    tasks := db.GetTasks()
    json.NewEncoder(w).Encode(tasks)
}

7. 错误处理

在处理请求时,可能会遇到错误。例如,数据库可能无法连接,或者请求的数据格式可能不正确。在这些情况下,我们需要返回适当的错误响应。例如:

func handleError(w http.ResponseWriter, err error) {
    w.WriteHeader(http.StatusInternalServerError)
    w.Write([]byte(fmt.Sprintf("Error: %s", err.Error())))
}

8. 中间件

在处理请求之前或之后,我们可能想要执行一些共同的操作。例如,我们可能想要记录每个请求,或者检查请求中是否包含有效的身份验证令牌。这种操作通常由中间件来处理。

例如,一个简单的日志中间件可能如下所示:

func logMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

9. 主程序

最后,在cmd/目录中,我们的主程序会初始化所有组件,并开始监听HTTP请求。例如:

func main() {
    db.Init()
    router := http.NewServeMux()

    router.HandleFunc("/tasks", handlers.GetAllTasks)
    // ... 其他路由 ...

    log.Println("Server started on :8080")
    http.ListenAndServe(":8080", logMiddleware(router))
}

10. 数据验证

当用户提交数据到我们的API时,我们不能简单地信任这些数据。我们需要验证数据的完整性和格式。例如,当添加一个新任务时,我们可能想要确保任务的描述不是空的。

func validateTask(task Task) error {
    if task.Description == "" {
        return errors.New("Description is required")
    }
    return nil
}

11. 测试

为了确保我们的应用程序正常工作,我们需要为其编写测试。Go语言提供了一个内置的testing包,可以帮助我们编写和运行测试。

例如,要为上面的validateTask函数编写测试,我们可以创建一个名为validate_test.go的文件:

func TestValidateTask(t *testing.T) {
    task := Task{Description: ""}
    err := validateTask(task)
    if err == nil {
        t.Errorf("Expected error, got nil")
    }
}

12. 部署

一旦我们的应用程序开发完成并通过所有测试,就可以将其部署到生产环境。为此,我们首先需要构建应用程序:

go build cmd/main.go

然后,我们可以将生成的二进制文件传输到我们的服务器,并在那里运行它。

13. 持续集成/持续部署 (CI/CD)

随着我们的应用程序的发展,我们可能会频繁地进行更改和更新。为了确保每次更改都不会破坏任何东西,我们可以设置一个CI/CD流程。这可以自动化测试和部署的过程。


1.2 实现CRUD操作

接下来我们会详细讨论如何在Go语言中为To-Do List应用程序实现CRUD(创建、读取、更新、删除)操作。🦌


1. 创建任务 (Create)

首先,我们需要定义一个结构体来表示任务:

type Task struct {
    ID          int    `json:"id"`
    Description string `json:"description"`
    Completed   bool   `json:"completed"`
}

然后,我们可以创建一个函数来添加新任务:

var tasks []Task

func CreateTask(task Task) int {
    task.ID = len(tasks) + 1
    tasks = append(tasks, task)
    return task.ID
}

2. 读取任务 (Read)

我们可以创建两个函数:一个用于获取所有任务,另一个用于根据ID获取特定任务。

func GetAllTasks() []Task {
    return tasks
}

func GetTaskByID(id int) (Task, error) {
    for _, task := range tasks {
        if task.ID == id {
            return task, nil
        }
    }
    return Task{}, errors.New("Task not found")
}

3. 更新任务 (Update)

为了更新任务,我们需要找到任务并修改它的属性。

func UpdateTask(updatedTask Task) error {
    for i, task := range tasks {
        if task.ID == updatedTask.ID {
            tasks[i] = updatedTask
            return nil
        }
    }
    return errors.New("Task not found")
}

4. 删除任务 (Delete)

删除任务需要找到任务并从列表中移除它。

func DeleteTask(id int) error {
    for i, task := range tasks {
        if task.ID == id {
            tasks = append(tasks[:i], tasks[i+1:]...)
            return nil
        }
    }
    return errors.New("Task not found")
}

鉴于已经有了CRUD操作的基本实现。接下来,我们需要将这些操作集成到API中,这样用户就可以通过HTTP请求来与我们的应用程序互动了。

接下来我们将为To-Do List应用程序的CRUD操作创建API接口。🦌

为了方便起见,我们将使用net/http标准库来创建HTTP服务器。

1. 设置HTTP服务器

首先,我们需要导入必要的包并设置一个简单的HTTP服务器:

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/tasks", tasksHandler)
	http.ListenAndServe(":8080", nil)
}

2. 创建任务处理器

tasksHandler函数将处理所有与任务相关的请求。

func tasksHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		tasks, err := GetAllTasks()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		json.NewEncoder(w).Encode(tasks)
	case "POST":
		var newTask Task
		if err := json.NewDecoder(r.Body).Decode(&newTask); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		id := CreateTask(newTask)
		w.WriteHeader(http.StatusCreated)
		fmt.Fprintf(w, "Task with ID %d created.", id)
	default:
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

3. 处理特定任务的请求

为了处理与特定任务相关的请求(如获取、更新或删除任务),我们需要另一个处理器:

func taskHandler(w http.ResponseWriter, r *http.Request) {
	// Extract task ID from URL
	taskID := extractTaskID(r.URL.Path)

	switch r.Method {
	case "GET":
		task, err := GetTaskByID(taskID)
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}
		json.NewEncoder(w).Encode(task)
	case "PUT":
		var updatedTask Task
		if err := json.NewDecoder(r.Body).Decode(&updatedTask); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		if err := UpdateTask(updatedTask); err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "Task updated successfully.")
	case "DELETE":
		if err := DeleteTask(taskID); err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "Task deleted successfully.")
	default:
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

4. 从URL提取任务ID

为了从URL中获取任务ID,我们需要一个简单的函数:

func extractTaskID(path string) int {
	// ... logic to extract task ID from URL path ...
}

这里只是一个简化版的To-Do List API。在实际应用中还需要加入更多的功能,例如身份验证、权限管理、请求验证等。