[Pro]GoLang Learn Data Day 5

ProGoLang Learn Data Day 5

原贴地址:https://www.cnblogs.com/Reisentyan/p/20232488

今天简要了解了一下Go的一些操作,写了个 任务记事本 。不是什么很复杂的东西,但是牵扯的东西比较多。

核心数据模型与 JSON 映射规范

无论是命令行还是 Web 接口,数据模型是整个程序的基础。我们首先定义 Task 结构体来承载任务数据。

go 复制代码
type Task struct {
    ID         int       `json:"id"`          // 任务编号
    Type       string    `json:"type"`        // 任务类型
    Name       string    `json:"name"`        // 任务名称
    Content    string    `json:"content"`     // 任务内容
    Statu      bool      `json:"statu"`       // 是否已完成
    CreateTime time.Time `json:"create_time"` // 创建时间
}

结构体字段导出的硬性规定

在 Go 语言中,结构体的字段名必须以大写字母开头 (例如 IDCreateTime)。这是因为只有首字母大写的字段才是"公开的",标准库(如 encoding/json)才能正确读取和序列化这些字段;若使用小写字母,字段将被视为"私有",在保存至本地文件或对外输出时会发生数据丢失。

JSON 命名规范与结构体标签

在业界规范中,本地存储或网络传输的 JSON 数据通常采用全小写蛇形命名法。 为了解决 Go 内部命名与外部 JSON 规范的冲突,我们引入了 结构体标签**。标签的作用在于显式告知 Go 编译器:"在代码逻辑中该字段名为 CreateTime,但在执行 JSON 序列化与反序列化时,请将其映射为 create_time。" 这种机制实现了内部数据结构与外部数据格式的优雅解耦。

阶段一:命令行版本 (CLI) 的实现

写了一个命令行记事本小程序,很简单,作为练手,代码如下:

系统功能与交互指南

本阶段基于 Go 标准库实现了完全通过终端指令交互的任务管理系统,数据自动持久化至本地 todos.json 文件。

  • 任务录入 (Add) :记录任务名称、分类、详情及系统自动生成的时间戳。
    • 示例go run main.go add 学习 Go语言 学习标准库与切片
  • 查看列表 (List) :输出当前所有待办事项及其状态(已完成标记为 [Accept!],未完成标记为 [Waiting])。
    • 示例go run main.go ls
  • 标记完成 (Accept) :根据任务编号,将特定任务的状态标记为已完成。
    • 示例go run main.go ac 1

核心源码实现

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"time"
)

const fileName = "todos.json"

type Task struct {
	Id         int       `json:"id"`
	Type       string    `json:"type"`
	Name       string    `json:"name"`
	Content    string    `json:"content"`
	CreateTime time.Time `json:"create_time"`
	Statu      bool      `json:"statu"`
}

// 核心函数:读取本地 JSON 文件并反序列化为切片
func loadTasks() []Task {
	data, err := os.ReadFile(fileName)
	if err != nil {
		return []Task{} // 发生错误或文件不存在时,返回空切片
	}
	var tasks []Task
	json.Unmarshal(data, &tasks)
	return tasks
}

// 核心函数:将切片序列化并持久化至本地硬盘
func saveTasks(task []Task) {
	// MarshalIndent 提供带缩进的格式化输出,增强文件可读性
	data, err := json.MarshalIndent(task, "", "  ")
	if err != nil {
		fmt.Println("JSON 序列化失败:", err)
		return
	}
	os.WriteFile(fileName, data, 0644)
}

func main() {
	// 边界检查:防止未输入参数导致程序 Panic
	if len(os.Args) < 2 {
		fmt.Println("未输入任何指令")
		return
	}

	command := os.Args[1]
	tasks := loadTasks()

	if command == "add" {
		if len(os.Args) < 5 {
			fmt.Println("参数不足:需提供任务名称、类型和内容")
			return
		}
		name, tasktype, content := os.Args[2], os.Args[3], os.Args[4]

		newtask := Task{
			Id:         len(tasks) + 1,
			Type:       tasktype,
			Content:    content,
			Name:       name,
			CreateTime: time.Now(),
			Statu:      false,
		}
		tasks = append(tasks, newtask)
		saveTasks(tasks)
		fmt.Println("任务录入成功")

	} else if command == "ls" {
		if len(tasks) < 1 {
			fmt.Println("当前任务列表为空")
			return
		}
		fmt.Println("--- 您的任务列表 ---")
		for _, va := range tasks {
			icon := "[Waiting]"
			if va.Statu {
				icon = "[Accept!]"
			}
			fmt.Printf("%d. %s %s (%s) - %s\n", va.Id, icon, va.Name, va.Type, va.Content)
		}
		fmt.Println("任务列举完毕")

	} else if command == "ac" {
		if len(os.Args) < 3 {
			fmt.Println("请提供需标记完成的任务编号")
			return
		}
		id, err := strconv.Atoi(os.Args[2])
		if err != nil {
			fmt.Println("格式错误:任务编号必须为数字")
			return
		}

		index := id - 1
		// 边界防护:检查切片索引是否越界
		if index < 0 || index >= len(tasks) {
			fmt.Println("该任务不存在")
			return
		}

		tasks[index].Statu = true
		saveTasks(tasks)
		fmt.Println("任务已标记为完成")
	} else {
		fmt.Println("未识别的命令")
	}
}

CLI 版本核心技术点总结

  • 标准库的高效整合
    • os.Args:捕获终端执行指令与参数。
    • os.ReadFile / os.WriteFile:实现数据的本地持久化覆盖。
    • strconv.Atoi:安全地将终端输入的字符串 ID 转换为整型。
  • 内存与逻辑的安全防护
    • 输入校验 :严格控制 len(os.Args) 长度,避免数组越界引发 Panic
    • 索引映射与校验 :处理了"逻辑 ID(从 1 开始)"与"切片索引(从 0 开始)"的转换(index := id - 1),并在修改前进行严格的越界拦截(index < 0 || index >= len(tasks))。
  • 规范的错误处理 :在文件读取、数据转换等关键节点,严格遵守 Go 语言 if err != nil 的错误拦截规范,提升了程序的鲁棒性。

向 RESTful Web API 演进

随着项目的升级,我们摒弃了纯命令行的单机交互模式,将其改造成标准的 Web 后端服务。这涉及 HTTP 协议基础与服务端网络编程原理。

HTTP 请求报文结构解析

标准的 HTTP 请求报文由三个核心部分组成:

  1. 请求行 (Request Line)
    • 结构请求方法 访问路径 协议版本(例如:POST /api/tasks HTTP/1.1)。
    • 作用:声明核心网络动作与目标资源。
  2. 请求头 (Headers)
    • 结构 :键值对结构(例如:Content-Type: application/json)。
    • 作用:传输元数据 (Metadata)。在服务器解析主体数据前,提前声明业务数据的格式、字符集及客户端环境,确保服务端采用正确的规则进行解码。
    • (注:HTTP 协议强制要求使用一个空行作为请求头与请求体的物理分界线。)
  3. 请求体 (Body/Payload)
    • 结构:实际的业务数据载荷。
    • 作用 :承载客户端提交的具体内容(如 JSON 字符串)。通常存在于 POSTPUT 等涉及数据写入的操作中;GET 请求通常无请求体。

Web API 源码实现

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

const fileName = "todos.json"

type Task struct {
	Id         int       `json:"id"`
	Type       string    `json:"type"`
	Name       string    `json:"name"`
	Content    string    `json:"content"`
	CreateTime time.Time `json:"create_time"`
	Statu      bool      `json:"statu"`
}

func loadTasks() []Task {
	data, err := os.ReadFile(fileName)
	if err != nil {
		return []Task{}
	}
	var tasks []Task
	json.Unmarshal(data, &tasks)
	return tasks
}

func saveTasks(task []Task) {
	data, err := json.MarshalIndent(task, "", "  ")
	if err != nil {
		fmt.Println("数据保存失败:", err)
		return
	}
	os.WriteFile(fileName, data, 0644)
}

// tasksHandler 负责处理 Web 接口的 HTTP 请求
func tasksHandler(w http.ResponseWriter, r *http.Request) {
	// 统一定义响应头:告知客户端返回的数据格式为 JSON
	w.Header().Set("Content-Type", "application/json")

	if r.Method == "GET" {
		// 动作:获取并返回任务列表
		tasks := loadTasks()
		// 将 Go 内存切片直接编码并推入网络响应流中
		json.NewEncoder(w).Encode(tasks)

	} else if r.Method == "POST" {
		// 动作:创建新任务
		var newTask Task
		
		// 将网络请求流中的 JSON 载荷解码到 newTask 对象中
		err := json.NewDecoder(r.Body).Decode(&newTask)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			fmt.Fprint(w, `{"error": "无效的JSON数据"}`)
			return
		}

		// 补充系统自动生成的业务字段
		tasks := loadTasks()
		newTask.Id = len(tasks) + 1
		newTask.CreateTime = time.Now()
		newTask.Statu = false

		tasks = append(tasks, newTask)
		saveTasks(tasks)

		// 响应 201 Created,并返回创建成功的数据
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(newTask)

	} else {
		// 拦截不支持的 HTTP 方法
		w.WriteHeader(http.StatusMethodNotAllowed)
		fmt.Fprint(w, `{"error":"不支持的请求方法"}`)
	}
}

func main() {
	// 路由注册:将指定路径与业务处理函数绑定
	http.HandleFunc("/api/tasks", tasksHandler)

	port := ":9527"
	fmt.Println("Web API 服务已启动,正在监听端口", port)

	// 启动底层 HTTP 监听器,采用默认的全局路由分发器 (nil)
	err := http.ListenAndServe(port, nil)
	if err != nil {
		fmt.Println("服务运行异常:", err)
	}
}

Go 语言 Web 服务的宏观与微观剖析

收发双向的业务闭环 (宏观视角)

当前系统已演进为一个标准的 RESTful API。其交互基于 HTTP 的"请求-响应"模型:

  • 数据拉取(GET 请求) :客户端意图获取数据。服务端拦截请求后,读取磁盘中的 todos.json 加载至内存,随后将其转换为跨平台通用的 JSON 文本,附带 200 OK 状态码沿网络链路原路返回。
  • 数据提交(POST 请求) :客户端通过请求体携带 JSON 载荷发起写入请求。服务端解包验证无误后,将其反序列化为 Go 内存对象。系统自动为其分配 ID 与时间戳,随后覆盖写入物理硬盘完成持久化,并向客户端返回 201 Created 回执。

底层运行机制与流式处理 (微观视角)

Go 语言的标准库通过精密配合,以极简的代码实现了高并发的网络服务:

  • 并发监听与路由分发
    • 当客户端发起 TCP 连接时,http.ListenAndServe 启动的底层监听器会率先截获握手请求。
    • 并发模型 :Go 底层会自动为每一个接入的请求分配一个独立的**协程,自动将网络字节流解析为 http.Request 对象,确保多用户并发访问时不阻塞主线程。
    • 路由命中 :底层全局路由器根据请求路径提取信息,在注册表中匹配到 /api/tasks 后,回调手写的 tasksHandler 函数。
  • 极致的流式 JSON 编解码 : Go 在处理网络 JSON 数据时,没有采用低效的"先转字符串再发网络"的二段式操作,而是利用了高效的**流式处理:
    • 解码 (POST)json.NewDecoder(r.Body).Decode(&newTask) 将网络输入流与解码器绑定,实现边接收边解析,极大节省内存开销。
    • 编码 (GET)json.NewEncoder(w).Encode(tasks) 将编码器直接绑定至网络输出流,内存中的切片被边序列化边推入网卡发往客户端。

结语:通过实现这个小巧的系统,不仅巩固了 Go 语言切片、结构体和标准库的基本用法,更理清了客户端与服务端网络交互的底层逻辑。随着 Web 框架的深入,后续引入的高并发处理模块将更加值得期待。

相关推荐
zhangfeng11331 小时前
华为昇腾910A NPU 的模型加密方案 ASCEND-CC
开发语言·人工智能·神经网络·transformer
聆风吟º1 小时前
【Python编程日志】Python基础语法:常量 | 表达式 | 变量
开发语言·python·变量·常量·表达式
凯瑟琳.奥古斯特1 小时前
10道数据库原理精选题
开发语言·数据库·职场和发展·数据库开发
z落落1 小时前
C# Stack栈 / Queue队列+所有集合 终极一页汇总(全覆盖、零遗漏)
java·开发语言·c#
skywalk81631 小时前
设计和实现一门中文编程语言,有什么工具可以使用吗?是不是ANTLR 和LLVM都可以使用?Racket恐怕不适用吧
开发语言·编程
磊 子2 小时前
STL之set以及set和map区别
开发语言·c++·算法
Halo_tjn2 小时前
NIO 技术的使用
java·开发语言·nio
砍材农夫2 小时前
物联网 基于netty核心实战-安全tls
java·开发语言·前端·物联网·安全