From f028913eb88d2e27c6a0747acfdb30d2c228eccc Mon Sep 17 00:00:00 2001 From: estel <690930@qq.com> Date: Sun, 24 Aug 2025 13:01:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kiro/specs/golang-learning-guide/tasks.md | 10 +- .../08-packages/01-creating-packages.go | 665 ++++++++++++ .../08-packages/02-importing-packages.go | 579 +++++++++++ golang-learning/08-packages/utils/helper.go | 300 +++++- golang-learning/09-advanced/01-reflection.go | 967 ++++++++++++++++++ golang-learning/09-advanced/02-generics.go | 561 ++++++++++ golang-learning/09-advanced/03-context.go | 765 ++++++++++++++ golang-learning/09-advanced/04-testing.go | 919 +++++++++++++++++ golang-learning/09-advanced/README.md | 27 +- .../10-projects/01-calculator/README.md | 86 +- .../01-calculator/calculator/calculator.go | 290 ++++++ .../01-calculator/calculator/history.go | 244 +++++ .../01-calculator/calculator_test.go | 214 ++++ .../10-projects/01-calculator/main.go | 133 +++ .../10-projects/02-todo-list/README.md | 122 ++- .../10-projects/02-todo-list/data/.gitkeep | 2 + .../10-projects/02-todo-list/main.go | 275 +++++ .../10-projects/02-todo-list/todo/manager.go | 427 ++++++++ .../10-projects/02-todo-list/todo/storage.go | 308 ++++++ .../10-projects/02-todo-list/todo/todo.go | 356 +++++++ .../10-projects/02-todo-list/todo_test.go | 382 +++++++ .../10-projects/02-todo-list/ui/cli.go | 315 ++++++ .../10-projects/02-todo-list/ui/colors.go | 382 +++++++ .../10-projects/03-web-server/README.md | 167 ++- .../10-projects/03-web-server/data/.gitkeep | 2 + .../10-projects/03-web-server/go.mod | 5 + .../10-projects/03-web-server/main.go | 54 + .../10-projects/03-web-server/models/user.go | 293 ++++++ .../03-web-server/server/handlers.go | 369 +++++++ .../03-web-server/server/middleware.go | 147 +++ .../03-web-server/server/router.go | 59 ++ .../03-web-server/server/server.go | 94 ++ .../10-projects/03-web-server/server_test.go | 358 +++++++ .../03-web-server/static/index.html | 101 ++ .../03-web-server/static/script.js | 241 +++++ .../03-web-server/static/style.css | 271 +++++ 36 files changed, 10420 insertions(+), 70 deletions(-) create mode 100644 golang-learning/08-packages/01-creating-packages.go create mode 100644 golang-learning/08-packages/02-importing-packages.go create mode 100644 golang-learning/09-advanced/01-reflection.go create mode 100644 golang-learning/09-advanced/02-generics.go create mode 100644 golang-learning/09-advanced/03-context.go create mode 100644 golang-learning/09-advanced/04-testing.go create mode 100644 golang-learning/10-projects/01-calculator/calculator/calculator.go create mode 100644 golang-learning/10-projects/01-calculator/calculator/history.go create mode 100644 golang-learning/10-projects/01-calculator/calculator_test.go create mode 100644 golang-learning/10-projects/01-calculator/main.go create mode 100644 golang-learning/10-projects/02-todo-list/data/.gitkeep create mode 100644 golang-learning/10-projects/02-todo-list/main.go create mode 100644 golang-learning/10-projects/02-todo-list/todo/manager.go create mode 100644 golang-learning/10-projects/02-todo-list/todo/storage.go create mode 100644 golang-learning/10-projects/02-todo-list/todo/todo.go create mode 100644 golang-learning/10-projects/02-todo-list/todo_test.go create mode 100644 golang-learning/10-projects/02-todo-list/ui/cli.go create mode 100644 golang-learning/10-projects/02-todo-list/ui/colors.go create mode 100644 golang-learning/10-projects/03-web-server/data/.gitkeep create mode 100644 golang-learning/10-projects/03-web-server/go.mod create mode 100644 golang-learning/10-projects/03-web-server/main.go create mode 100644 golang-learning/10-projects/03-web-server/models/user.go create mode 100644 golang-learning/10-projects/03-web-server/server/handlers.go create mode 100644 golang-learning/10-projects/03-web-server/server/middleware.go create mode 100644 golang-learning/10-projects/03-web-server/server/router.go create mode 100644 golang-learning/10-projects/03-web-server/server/server.go create mode 100644 golang-learning/10-projects/03-web-server/server_test.go create mode 100644 golang-learning/10-projects/03-web-server/static/index.html create mode 100644 golang-learning/10-projects/03-web-server/static/script.js create mode 100644 golang-learning/10-projects/03-web-server/static/style.css diff --git a/.kiro/specs/golang-learning-guide/tasks.md b/.kiro/specs/golang-learning-guide/tasks.md index 07143d8..c8b0734 100644 --- a/.kiro/specs/golang-learning-guide/tasks.md +++ b/.kiro/specs/golang-learning-guide/tasks.md @@ -86,14 +86,14 @@ - _需求: 6.1, 6.2, 6.3_ - [ ] 9. 实现包管理学习模块 -- [ ] 9.1 创建包管理示例 +- [x] 9.1 创建包管理示例 - 编写 08-packages/01-creating-packages.go,展示包的创建方法 - 编写 08-packages/02-importing-packages.go,演示包的导入和使用 - 创建 08-packages/utils/helper.go,提供实际的包示例 - _需求: 7.1, 7.2, 7.3_ - [ ] 10. 实现高级特性学习模块 -- [ ] 10.1 创建高级特性示例 +- [x] 10.1 创建高级特性示例 - 编写 09-advanced/01-reflection.go,展示反射的基本使用 - 编写 09-advanced/02-generics.go,演示泛型的语法和应用 - 编写 09-advanced/03-context.go,展示 context 包的使用 @@ -101,19 +101,19 @@ - _需求: 各个高级特性需求_ - [ ] 11. 创建实践项目模块 -- [ ] 11.1 实现计算器项目 +- [x] 11.1 实现计算器项目 - 创建 10-projects/01-calculator/ 目录结构 - 编写简单计算器的完整实现,包含基本四则运算 - 提供项目结构说明和运行指导 - _需求: 综合应用各种语法特性_ -- [ ] 11.2 实现待办事项列表项目 +- [x] 11.2 实现待办事项列表项目 - 创建 10-projects/02-todo-list/ 目录结构 - 编写命令行待办事项管理程序 - 包含数据持久化和用户交互功能 - _需求: 综合应用数据结构和文件操作_ -- [ ] 11.3 实现简单 Web 服务器项目 +- [x] 11.3 实现简单 Web 服务器项目 - 创建 10-projects/03-web-server/ 目录结构 - 编写基础的 HTTP 服务器实现 - 包含路由处理和 JSON API 示例 diff --git a/golang-learning/08-packages/01-creating-packages.go b/golang-learning/08-packages/01-creating-packages.go new file mode 100644 index 0000000..3a77e18 --- /dev/null +++ b/golang-learning/08-packages/01-creating-packages.go @@ -0,0 +1,665 @@ +/* +01-creating-packages.go - Go 语言包创建详解 + +学习目标: +1. 理解 Go 语言包的概念和作用 +2. 掌握包的创建和组织方式 +3. 学会包的命名规范和最佳实践 +4. 了解包的可见性规则 +5. 掌握包的文档编写 + +知识点: +- 包的基本概念和作用 +- 包的创建和组织 +- 包的命名规范 +- 导出和未导出标识符 +- 包的文档注释 +- 包的初始化 +*/ + +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" +) + +func main() { + fmt.Println("=== Go 语言包创建详解 ===\\n") + + // 演示包的基本概念 + demonstratePackageBasics() + + // 演示包的组织结构 + demonstratePackageOrganization() + + // 演示包的命名规范 + demonstratePackageNaming() + + // 演示可见性规则 + demonstrateVisibilityRules() + + // 演示包的文档 + demonstratePackageDocumentation() + + // 演示包的初始化 + demonstratePackageInitialization() + + // 演示实际包创建示例 + demonstratePracticalPackageCreation() +} + +// demonstratePackageBasics 演示包的基本概念 +func demonstratePackageBasics() { + fmt.Println("1. 包的基本概念:") + + // 包的基本概念 + fmt.Printf(" 包的基本概念:\\n") + fmt.Printf(" - 包是 Go 语言组织代码的基本单位\\n") + fmt.Printf(" - 每个 Go 文件都必须属于一个包\\n") + fmt.Printf(" - 包提供了命名空间和封装机制\\n") + fmt.Printf(" - 包可以被其他包导入和使用\\n") + fmt.Printf(" - main 包是程序的入口点\\n") + + // 包的作用 + fmt.Printf(" 包的作用:\\n") + fmt.Printf(" 1. 代码组织:将相关功能组织在一起\\n") + fmt.Printf(" 2. 命名空间:避免命名冲突\\n") + fmt.Printf(" 3. 封装性:控制代码的可见性\\n") + fmt.Printf(" 4. 重用性:便于代码重用和分享\\n") + fmt.Printf(" 5. 模块化:支持大型项目的模块化开发\\n") + + // 包的类型 + fmt.Printf(" 包的类型:\\n") + fmt.Printf(" 1. main 包:可执行程序的入口\\n") + fmt.Printf(" 2. 库包:提供功能给其他包使用\\n") + fmt.Printf(" 3. 标准库包:Go 官方提供的包\\n") + fmt.Printf(" 4. 第三方包:社区开发的包\\n") + fmt.Printf(" 5. 内部包:项目内部使用的包\\n") + + // 包声明 + fmt.Printf(" 包声明:\\n") + fmt.Printf(" - 每个 Go 文件的第一行必须是包声明\\n") + fmt.Printf(" - 格式:package 包名\\n") + fmt.Printf(" - 同一目录下的所有 .go 文件必须属于同一个包\\n") + fmt.Printf(" - 包名通常与目录名相同(但不是必须的)\\n") + + // 示例包声明 + fmt.Printf(" 示例包声明:\\n") + packageExamples := []string{ + "package main // 可执行程序", + "package utils // 工具包", + "package http // HTTP 相关功能", + "package database // 数据库操作", + "package config // 配置管理", + } + + for _, example := range packageExamples { + fmt.Printf(" %s\\n", example) + } + + fmt.Println() +} + +// demonstratePackageOrganization 演示包的组织结构 +func demonstratePackageOrganization() { + fmt.Println("2. 包的组织结构:") + + // 项目结构示例 + fmt.Printf(" 典型的项目结构:\\n") + fmt.Printf(" myproject/\\n") + fmt.Printf(" ├── main.go // 主程序\\n") + fmt.Printf(" ├── go.mod // 模块定义\\n") + fmt.Printf(" ├── README.md // 项目说明\\n") + fmt.Printf(" ├── cmd/ // 命令行程序\\n") + fmt.Printf(" │ └── server/\\n") + fmt.Printf(" │ └── main.go\\n") + fmt.Printf(" ├── pkg/ // 公共库\\n") + fmt.Printf(" │ ├── config/\\n") + fmt.Printf(" │ │ └── config.go\\n") + fmt.Printf(" │ └── utils/\\n") + fmt.Printf(" │ └── helper.go\\n") + fmt.Printf(" ├── internal/ // 内部包\\n") + fmt.Printf(" │ ├── auth/\\n") + fmt.Printf(" │ │ └── auth.go\\n") + fmt.Printf(" │ └── database/\\n") + fmt.Printf(" │ └── db.go\\n") + fmt.Printf(" ├── api/ // API 相关\\n") + fmt.Printf(" │ ├── handlers/\\n") + fmt.Printf(" │ │ └── user.go\\n") + fmt.Printf(" │ └── middleware/\\n") + fmt.Printf(" │ └── auth.go\\n") + fmt.Printf(" ├── models/ // 数据模型\\n") + fmt.Printf(" │ ├── user.go\\n") + fmt.Printf(" │ └── product.go\\n") + fmt.Printf(" ├── services/ // 业务逻辑\\n") + fmt.Printf(" │ ├── user.go\\n") + fmt.Printf(" │ └── product.go\\n") + fmt.Printf(" ├── tests/ // 测试文件\\n") + fmt.Printf(" │ └── integration/\\n") + fmt.Printf(" └── docs/ // 文档\\n") + fmt.Printf(" └── api.md\\n") + + // 包组织原则 + fmt.Printf(" 包组织原则:\\n") + fmt.Printf(" 1. 按功能分组:相关功能放在同一个包中\\n") + fmt.Printf(" 2. 单一职责:每个包应该有明确的职责\\n") + fmt.Printf(" 3. 依赖管理:避免循环依赖\\n") + fmt.Printf(" 4. 接口分离:定义清晰的包接口\\n") + fmt.Printf(" 5. 层次结构:建立合理的包层次\\n") + + // 目录命名规范 + fmt.Printf(" 目录命名规范:\\n") + fmt.Printf(" - 使用小写字母\\n") + fmt.Printf(" - 使用短而有意义的名称\\n") + fmt.Printf(" - 避免使用下划线和连字符\\n") + fmt.Printf(" - 使用复数形式(如 handlers, models)\\n") + fmt.Printf(" - 避免使用 Go 关键字\\n") + + // 特殊目录 + fmt.Printf(" 特殊目录:\\n") + fmt.Printf(" - cmd/: 存放可执行程序的源码\\n") + fmt.Printf(" - pkg/: 存放可以被外部应用使用的库代码\\n") + fmt.Printf(" - internal/: 存放私有应用和库代码\\n") + fmt.Printf(" - vendor/: 存放依赖包的副本\\n") + fmt.Printf(" - testdata/: 存放测试数据\\n") + + fmt.Println() +} + +// demonstratePackageNaming 演示包的命名规范 +func demonstratePackageNaming() { + fmt.Println("3. 包的命名规范:") + + // 命名规则 + fmt.Printf(" 包命名规则:\\n") + fmt.Printf(" 1. 使用小写字母\\n") + fmt.Printf(" 2. 简短而有意义\\n") + fmt.Printf(" 3. 避免使用下划线\\n") + fmt.Printf(" 4. 不要使用复数形式\\n") + fmt.Printf(" 5. 避免与标准库冲突\\n") + + // 好的包名示例 + fmt.Printf(" 好的包名示例:\\n") + goodNames := []string{ + "http", // 简短明了 + "json", // 广为人知的缩写 + "url", // 常用缩写 + "time", // 简单直接 + "crypto", // 清晰的功能描述 + "database", // 完整但不冗长 + "config", // 简洁的功能描述 + "auth", // 常用缩写 + } + + for _, name := range goodNames { + fmt.Printf(" %s\\n", name) + } + + // 不好的包名示例 + fmt.Printf(" 不好的包名示例:\\n") + badNames := map[string]string{ + "HTTP": "使用了大写字母", + "http_client": "使用了下划线", + "utils": "太通用,没有明确含义", + "helpers": "使用了复数形式", + "mypackage": "没有描述功能", + "data_base": "使用了下划线", + "user_service": "使用了下划线", + "common": "太通用", + } + + for name, reason := range badNames { + fmt.Printf(" %s - %s\\n", name, reason) + } + + // 包名与导入路径 + fmt.Printf(" 包名与导入路径:\\n") + fmt.Printf(" - 包名是 package 声明中的名称\\n") + fmt.Printf(" - 导入路径是 import 语句中的路径\\n") + fmt.Printf(" - 包名通常是导入路径的最后一部分\\n") + fmt.Printf(" - 但包名和目录名可以不同\\n") + + // 示例 + fmt.Printf(" 示例:\\n") + fmt.Printf(" 导入路径: github.com/user/project/pkg/database\\n") + fmt.Printf(" 包名: package database\\n") + fmt.Printf(" 使用: database.Connect()\\n") + + fmt.Println() +} + +// demonstrateVisibilityRules 演示可见性规则 +func demonstrateVisibilityRules() { + fmt.Println("4. 可见性规则:") + + // 可见性基本规则 + fmt.Printf(" 可见性基本规则:\\n") + fmt.Printf(" - 首字母大写:导出(公开),可被其他包访问\\n") + fmt.Printf(" - 首字母小写:未导出(私有),只能包内访问\\n") + fmt.Printf(" - 适用于:函数、变量、常量、类型、方法、字段\\n") + + // 导出标识符示例 + fmt.Printf(" 导出标识符示例:\\n") + exportedExamples := []string{ + "func Add(a, b int) int", // 导出函数 + "var MaxSize = 1000", // 导出变量 + "const DefaultTimeout = 30", // 导出常量 + "type User struct { Name string }", // 导出类型 + "func (u User) GetName() string", // 导出方法 + } + + for _, example := range exportedExamples { + fmt.Printf(" %s // 导出\\n", example) + } + + // 未导出标识符示例 + fmt.Printf(" 未导出标识符示例:\\n") + unexportedExamples := []string{ + "func add(a, b int) int", // 未导出函数 + "var maxSize = 1000", // 未导出变量 + "const defaultTimeout = 30", // 未导出常量 + "type user struct { name string }", // 未导出类型 + "func (u user) getName() string", // 未导出方法 + } + + for _, example := range unexportedExamples { + fmt.Printf(" %s // 未导出\\n", example) + } + + // 结构体字段的可见性 + fmt.Printf(" 结构体字段的可见性:\\n") + fmt.Printf(" type User struct {\\n") + fmt.Printf(" Name string // 导出字段\\n") + fmt.Printf(" Email string // 导出字段\\n") + fmt.Printf(" age int // 未导出字段\\n") + fmt.Printf(" address string // 未导出字段\\n") + fmt.Printf(" }\\n") + + // 方法的可见性 + fmt.Printf(" 方法的可见性:\\n") + fmt.Printf(" func (u *User) GetName() string { // 导出方法\\n") + fmt.Printf(" return u.Name\\n") + fmt.Printf(" }\\n") + fmt.Printf("\\n") + fmt.Printf(" func (u *User) setAge(age int) { // 未导出方法\\n") + fmt.Printf(" u.age = age\\n") + fmt.Printf(" }\\n") + + // 接口的可见性 + fmt.Printf(" 接口的可见性:\\n") + fmt.Printf(" type Reader interface { // 导出接口\\n") + fmt.Printf(" Read() ([]byte, error) // 导出方法\\n") + fmt.Printf(" }\\n") + fmt.Printf("\\n") + fmt.Printf(" type writer interface { // 未导出接口\\n") + fmt.Printf(" write([]byte) error // 未导出方法\\n") + fmt.Printf(" }\\n") + + // 可见性设计原则 + fmt.Printf(" 可见性设计原则:\\n") + fmt.Printf(" 1. 最小暴露:只导出必要的标识符\\n") + fmt.Printf(" 2. 稳定接口:导出的接口应该稳定\\n") + fmt.Printf(" 3. 清晰命名:导出的标识符应该有清晰的名称\\n") + fmt.Printf(" 4. 文档完整:导出的标识符应该有完整的文档\\n") + fmt.Printf(" 5. 向后兼容:避免破坏性的接口变更\\n") + + fmt.Println() +} + +// demonstratePackageDocumentation 演示包的文档 +func demonstratePackageDocumentation() { + fmt.Println("5. 包的文档:") + + // 文档注释规则 + fmt.Printf(" 文档注释规则:\\n") + fmt.Printf(" 1. 紧邻声明之前,中间不能有空行\\n") + fmt.Printf(" 2. 以被注释的标识符名称开头\\n") + fmt.Printf(" 3. 使用完整的句子\\n") + fmt.Printf(" 4. 第一句话应该是简洁的摘要\\n") + fmt.Printf(" 5. 可以包含示例代码\\n") + + // 包文档示例 + fmt.Printf(" 包文档示例:\\n") + fmt.Printf(" /*\\n") + fmt.Printf(" Package math 提供基本的数学函数和常量。\\n") + fmt.Printf("\\n") + fmt.Printf(" 这个包包含了常用的数学运算函数,如三角函数、\\n") + fmt.Printf(" 对数函数、指数函数等。所有函数都经过优化,\\n") + fmt.Printf(" 适用于高性能计算场景。\\n") + fmt.Printf("\\n") + fmt.Printf(" 示例用法:\\n") + fmt.Printf(" result := math.Sqrt(16) // 计算平方根\\n") + fmt.Printf(" angle := math.Sin(math.Pi / 2) // 计算正弦值\\n") + fmt.Printf(" */\\n") + fmt.Printf(" package math\\n") + + // 函数文档示例 + fmt.Printf(" 函数文档示例:\\n") + fmt.Printf(" // Add 计算两个整数的和。\\n") + fmt.Printf(" //\\n") + fmt.Printf(" // 参数 a 和 b 可以是任意整数,包括负数。\\n") + fmt.Printf(" // 返回值是 a 和 b 的算术和。\\n") + fmt.Printf(" //\\n") + fmt.Printf(" // 示例:\\n") + fmt.Printf(" // sum := Add(3, 4) // 返回 7\\n") + fmt.Printf(" // sum := Add(-1, 1) // 返回 0\\n") + fmt.Printf(" func Add(a, b int) int {\\n") + fmt.Printf(" return a + b\\n") + fmt.Printf(" }\\n") + + // 类型文档示例 + fmt.Printf(" 类型文档示例:\\n") + fmt.Printf(" // User 表示系统中的用户。\\n") + fmt.Printf(" //\\n") + fmt.Printf(" // User 包含用户的基本信息,如姓名、邮箱等。\\n") + fmt.Printf(" // 所有字段都是必需的,不能为空。\\n") + fmt.Printf(" type User struct {\\n") + fmt.Printf(" // Name 是用户的全名\\n") + fmt.Printf(" Name string\\n") + fmt.Printf("\\n") + fmt.Printf(" // Email 是用户的邮箱地址,必须是有效格式\\n") + fmt.Printf(" Email string\\n") + fmt.Printf(" }\\n") + + // 常量和变量文档 + fmt.Printf(" 常量和变量文档示例:\\n") + fmt.Printf(" // MaxRetries 是操作失败时的最大重试次数。\\n") + fmt.Printf(" const MaxRetries = 3\\n") + fmt.Printf("\\n") + fmt.Printf(" // DefaultTimeout 是网络操作的默认超时时间。\\n") + fmt.Printf(" var DefaultTimeout = 30 * time.Second\\n") + + // 文档生成 + fmt.Printf(" 文档生成:\\n") + fmt.Printf(" - 使用 go doc 命令查看文档\\n") + fmt.Printf(" - 使用 godoc 工具生成 HTML 文档\\n") + fmt.Printf(" - 文档会自动发布到 pkg.go.dev\\n") + + // 文档示例命令 + fmt.Printf(" 文档查看命令:\\n") + fmt.Printf(" go doc utils // 查看包文档\\n") + fmt.Printf(" go doc utils.Add // 查看函数文档\\n") + fmt.Printf(" go doc -all utils // 查看所有文档\\n") + + fmt.Println() +} + +// demonstratePackageInitialization 演示包的初始化 +func demonstratePackageInitialization() { + fmt.Println("6. 包的初始化:") + + // 初始化顺序 + fmt.Printf(" 包初始化顺序:\\n") + fmt.Printf(" 1. 导入的包先初始化\\n") + fmt.Printf(" 2. 包级别变量按声明顺序初始化\\n") + fmt.Printf(" 3. init 函数按出现顺序执行\\n") + fmt.Printf(" 4. main 函数最后执行\\n") + + // init 函数特点 + fmt.Printf(" init 函数特点:\\n") + fmt.Printf(" - 函数名必须是 init\\n") + fmt.Printf(" - 不能有参数和返回值\\n") + fmt.Printf(" - 不能被显式调用\\n") + fmt.Printf(" - 一个包可以有多个 init 函数\\n") + fmt.Printf(" - 在包被导入时自动执行\\n") + + // init 函数示例 + fmt.Printf(" init 函数示例:\\n") + fmt.Printf(" package config\\n") + fmt.Printf("\\n") + fmt.Printf(" import (\\n") + fmt.Printf(" \\\"fmt\\\"\\n") + fmt.Printf(" \\\"os\\\"\\n") + fmt.Printf(" )\\n") + fmt.Printf("\\n") + fmt.Printf(" var AppName string\\n") + fmt.Printf("\\n") + fmt.Printf(" func init() {\\n") + fmt.Printf(" fmt.Println(\\\"初始化配置包\\\")\\n") + fmt.Printf(" AppName = os.Getenv(\\\"APP_NAME\\\")\\n") + fmt.Printf(" if AppName == \\\"\\\" {\\n") + fmt.Printf(" AppName = \\\"DefaultApp\\\"\\n") + fmt.Printf(" }\\n") + fmt.Printf(" }\\n") + + // 包级别变量初始化 + fmt.Printf(" 包级别变量初始化:\\n") + fmt.Printf(" var (\\n") + fmt.Printf(" // 简单初始化\\n") + fmt.Printf(" MaxSize = 1000\\n") + fmt.Printf("\\n") + fmt.Printf(" // 函数调用初始化\\n") + fmt.Printf(" StartTime = time.Now()\\n") + fmt.Printf("\\n") + fmt.Printf(" // 复杂初始化\\n") + fmt.Printf(" Config = loadConfig()\\n") + fmt.Printf(" )\\n") + + // 初始化依赖 + fmt.Printf(" 初始化依赖:\\n") + fmt.Printf(" - Go 会自动分析变量间的依赖关系\\n") + fmt.Printf(" - 按依赖顺序进行初始化\\n") + fmt.Printf(" - 循环依赖会导致编译错误\\n") + + // 初始化示例 + fmt.Printf(" 初始化示例:\\n") + fmt.Printf(" var (\\n") + fmt.Printf(" a = b + 1 // 依赖 b\\n") + fmt.Printf(" b = 2 // 先初始化\\n") + fmt.Printf(" c = a + b // 依赖 a 和 b\\n") + fmt.Printf(" )\\n") + fmt.Printf(" // 初始化顺序:b -> a -> c\\n") + + // 初始化最佳实践 + fmt.Printf(" 初始化最佳实践:\\n") + fmt.Printf(" 1. 保持 init 函数简单\\n") + fmt.Printf(" 2. 避免在 init 中进行复杂操作\\n") + fmt.Printf(" 3. 不要依赖 init 函数的执行顺序\\n") + fmt.Printf(" 4. 优先使用包级别变量初始化\\n") + fmt.Printf(" 5. 处理初始化可能的错误\\n") + + fmt.Println() +} + +// demonstratePracticalPackageCreation 演示实际包创建示例 +func demonstratePracticalPackageCreation() { + fmt.Println("7. 实际包创建示例:") + + // 分析现有包结构 + fmt.Printf(" 分析现有包结构:\\n") + + // 获取当前目录 + currentDir, err := os.Getwd() + if err != nil { + fmt.Printf(" 获取当前目录失败: %v\\n", err) + return + } + + // 分析 utils 包 + utilsPath := filepath.Join(currentDir, "utils") + if _, err := os.Stat(utilsPath); err == nil { + fmt.Printf(" 找到 utils 包: %s\\n", utilsPath) + analyzePackage(utilsPath) + } else { + fmt.Printf(" utils 包不存在: %v\\n", err) + } + + // 包创建步骤 + fmt.Printf(" 包创建步骤:\\n") + fmt.Printf(" 1. 创建包目录\\n") + fmt.Printf(" 2. 编写包声明\\n") + fmt.Printf(" 3. 定义导出的接口\\n") + fmt.Printf(" 4. 实现功能函数\\n") + fmt.Printf(" 5. 添加文档注释\\n") + fmt.Printf(" 6. 编写测试文件\\n") + fmt.Printf(" 7. 创建示例代码\\n") + + // 包设计原则 + fmt.Printf(" 包设计原则:\\n") + fmt.Printf(" 1. 单一职责:每个包应该有明确的职责\\n") + fmt.Printf(" 2. 高内聚:包内元素应该紧密相关\\n") + fmt.Printf(" 3. 低耦合:包间依赖应该最小化\\n") + fmt.Printf(" 4. 稳定接口:导出接口应该稳定\\n") + fmt.Printf(" 5. 易于使用:API 应该简单直观\\n") + + // 包的版本管理 + fmt.Printf(" 包的版本管理:\\n") + fmt.Printf(" - 使用语义化版本号(如 v1.2.3)\\n") + fmt.Printf(" - 主版本号:不兼容的 API 变更\\n") + fmt.Printf(" - 次版本号:向后兼容的功能增加\\n") + fmt.Printf(" - 修订版本号:向后兼容的问题修复\\n") + + // 包的发布 + fmt.Printf(" 包的发布:\\n") + fmt.Printf(" 1. 创建 Git 仓库\\n") + fmt.Printf(" 2. 添加 go.mod 文件\\n") + fmt.Printf(" 3. 编写 README.md\\n") + fmt.Printf(" 4. 添加许可证文件\\n") + fmt.Printf(" 5. 创建版本标签\\n") + fmt.Printf(" 6. 推送到代码仓库\\n") + + fmt.Println() +} + +// analyzePackage 分析包的结构 +func analyzePackage(packagePath string) { + fmt.Printf(" 分析包: %s\\n", packagePath) + + // 遍历包目录中的 Go 文件 + err := filepath.Walk(packagePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // 只处理 .go 文件 + if !strings.HasSuffix(path, ".go") { + return nil + } + + // 解析 Go 文件 + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + fmt.Printf(" 解析文件 %s 失败: %v\\n", path, err) + return nil + } + + // 分析文件内容 + fmt.Printf(" 文件: %s\\n", filepath.Base(path)) + fmt.Printf(" 包名: %s\\n", node.Name.Name) + + // 统计导出和未导出的标识符 + var exportedFuncs, unexportedFuncs int + var exportedTypes, unexportedTypes int + var exportedVars, unexportedVars int + + for _, decl := range node.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + if d.Name.IsExported() { + exportedFuncs++ + } else { + unexportedFuncs++ + } + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + if s.Name.IsExported() { + exportedTypes++ + } else { + unexportedTypes++ + } + case *ast.ValueSpec: + for _, name := range s.Names { + if name.IsExported() { + exportedVars++ + } else { + unexportedVars++ + } + } + } + } + } + } + + fmt.Printf(" 导出函数: %d, 未导出函数: %d\\n", exportedFuncs, unexportedFuncs) + fmt.Printf(" 导出类型: %d, 未导出类型: %d\\n", exportedTypes, unexportedTypes) + fmt.Printf(" 导出变量: %d, 未导出变量: %d\\n", exportedVars, unexportedVars) + + return nil + }) + + if err != nil { + fmt.Printf(" 分析包失败: %v\\n", err) + } +} + +/* +运行这个程序: +go run 01-creating-packages.go + +学习要点: +1. 包是 Go 语言组织代码的基本单位 +2. 包提供了命名空间和封装机制 +3. 包的可见性通过标识符首字母大小写控制 +4. 包应该有清晰的文档和合理的组织结构 +5. 包的设计应该遵循单一职责和高内聚低耦合原则 + +包的基本概念: +1. 每个 Go 文件都必须属于一个包 +2. 同一目录下的所有 .go 文件必须属于同一个包 +3. 包名通常与目录名相同 +4. main 包是程序的入口点 +5. 包提供了代码的模块化和重用机制 + +包的组织原则: +1. 按功能分组:相关功能放在同一个包中 +2. 单一职责:每个包应该有明确的职责 +3. 依赖管理:避免循环依赖 +4. 接口分离:定义清晰的包接口 +5. 层次结构:建立合理的包层次 + +可见性规则: +1. 首字母大写:导出(公开),可被其他包访问 +2. 首字母小写:未导出(私有),只能包内访问 +3. 适用于函数、变量、常量、类型、方法、字段 +4. 最小暴露原则:只导出必要的标识符 +5. 稳定接口:导出的接口应该稳定 + +包的文档: +1. 文档注释紧邻声明之前 +2. 以被注释的标识符名称开头 +3. 使用完整的句子 +4. 第一句话应该是简洁的摘要 +5. 可以包含示例代码 + +包的初始化: +1. 导入的包先初始化 +2. 包级别变量按依赖顺序初始化 +3. init 函数按出现顺序执行 +4. main 函数最后执行 +5. 每个包只初始化一次 + +最佳实践: +1. 使用清晰简洁的包名 +2. 遵循 Go 的命名规范 +3. 编写完整的文档注释 +4. 保持包的职责单一 +5. 设计稳定的公共接口 +6. 避免循环依赖 +7. 合理组织包的结构 + +注意事项: +1. 包名应该小写且简洁 +2. 避免使用通用名称如 util、common +3. 不要在包名中使用下划线 +4. 包的导入路径应该唯一 +5. 考虑包的向后兼容性 +*/ diff --git a/golang-learning/08-packages/02-importing-packages.go b/golang-learning/08-packages/02-importing-packages.go new file mode 100644 index 0000000..7c2c6e7 --- /dev/null +++ b/golang-learning/08-packages/02-importing-packages.go @@ -0,0 +1,579 @@ +/* +02-importing-packages.go - Go 语言包导入详解 + +学习目标: +1. 掌握包的导入语法和方式 +2. 了解不同的导入模式 +3. 学会处理包的别名和冲突 +4. 理解包的初始化顺序 +5. 掌握包导入的最佳实践 + +知识点: +- 包的导入语法 +- 导入别名和点导入 +- 空白导入 +- 包的初始化顺序 +- 循环导入问题 +- 包管理工具的使用 +*/ + +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + // 标准库导入 + "encoding/json" + "io/ioutil" + + // 本地包导入 + "./utils" // 相对路径导入(不推荐) + + // 别名导入 + myutils "./utils" + + // 点导入(不推荐) + . "fmt" + + // 空白导入 + _ "net/http/pprof" +) + +func main() { + Println("=== Go 语言包导入详解 ===\n") // 使用点导入的 fmt.Println + + // 演示基本导入 + demonstrateBasicImports() + + // 演示导入别名 + demonstrateImportAliases() + + // 演示点导入 + demonstrateDotImports() + + // 演示空白导入 + demonstrateBlankImports() + + // 演示包的初始化顺序 + demonstrateInitializationOrder() + + // 演示循环导入问题 + demonstrateCircularImports() + + // 演示包管理 + demonstratePackageManagement() +} + +// demonstrateBasicImports 演示基本导入 +func demonstrateBasicImports() { + fmt.Println("1. 基本导入:") + + // 导入语法 + fmt.Printf(" 导入语法:\n") + fmt.Printf(" import \"package-path\" // 单个导入\n") + fmt.Printf(" import ( // 多个导入\n") + fmt.Printf(" \"fmt\"\n") + fmt.Printf(" \"time\"\n") + fmt.Printf(" \"strings\"\n") + fmt.Printf(" )\n") + + // 标准库导入示例 + fmt.Printf(" 标准库导入示例:\n") + + // 使用 time 包 + now := time.Now() + fmt.Printf(" 当前时间: %s\n", now.Format("2006-01-02 15:04:05")) + + // 使用 strings 包 + text := "Hello, World!" + upper := strings.ToUpper(text) + fmt.Printf(" 大写转换: %s -> %s\n", text, upper) + + // 使用 json 包 + data := map[string]interface{}{ + "name": "Alice", + "age": 30, + "city": "Beijing", + } + + jsonData, err := json.Marshal(data) + if err != nil { + fmt.Printf(" JSON 编码失败: %v\n", err) + } else { + fmt.Printf(" JSON 编码: %s\n", string(jsonData)) + } + + // 本地包导入示例 + fmt.Printf(" 本地包导入示例:\n") + + // 使用 utils 包 + sum := utils.Add(10, 20) + fmt.Printf(" utils.Add(10, 20) = %d\n", sum) + + product := utils.Multiply(5, 6) + fmt.Printf(" utils.Multiply(5, 6) = %d\n", product) + + greeting := utils.GetGreeting("Go Developer") + fmt.Printf(" utils.GetGreeting(\"Go Developer\") = %s\n", greeting) + + // 包的版本信息 + fmt.Printf(" utils 包版本: %s\n", utils.PackageVersion) + + fmt.Println() +} + +// demonstrateImportAliases 演示导入别名 +func demonstrateImportAliases() { + fmt.Println("2. 导入别名:") + + // 别名导入的用途 + fmt.Printf(" 别名导入的用途:\n") + fmt.Printf(" 1. 解决包名冲突\n") + fmt.Printf(" 2. 简化长包名\n") + fmt.Printf(" 3. 提高代码可读性\n") + fmt.Printf(" 4. 避免与本地标识符冲突\n") + + // 别名导入语法 + fmt.Printf(" 别名导入语法:\n") + fmt.Printf(" import alias \"package-path\"\n") + fmt.Printf(" import (\n") + fmt.Printf(" myutils \"./utils\"\n") + fmt.Printf(" httputil \"net/http/httputil\"\n") + fmt.Printf(" )\n") + + // 使用别名导入的包 + fmt.Printf(" 使用别名导入的包:\n") + + // 使用 myutils 别名 + result := myutils.Add(15, 25) + fmt.Printf(" myutils.Add(15, 25) = %d\n", result) + + maxVal := myutils.Max(10, 20) + fmt.Printf(" myutils.Max(10, 20) = %d\n", maxVal) + + isEven := myutils.IsEven(42) + fmt.Printf(" myutils.IsEven(42) = %t\n", isEven) + + // 常见的别名使用场景 + fmt.Printf(" 常见的别名使用场景:\n") + + // 场景1:解决包名冲突 + fmt.Printf(" 场景1 - 解决包名冲突:\n") + fmt.Printf(" import (\n") + fmt.Printf(" \"crypto/rand\"\n") + fmt.Printf(" mathrand \"math/rand\"\n") + fmt.Printf(" )\n") + fmt.Printf(" // 使用: rand.Read() 和 mathrand.Intn()\n") + + // 场景2:简化长包名 + fmt.Printf(" 场景2 - 简化长包名:\n") + fmt.Printf(" import pb \"github.com/company/project/proto/user/v1\"\n") + fmt.Printf(" // 使用: pb.User{} 而不是 user_v1.User{}\n") + + // 场景3:版本管理 + fmt.Printf(" 场景3 - 版本管理:\n") + fmt.Printf(" import (\n") + fmt.Printf(" v1 \"myproject/api/v1\"\n") + fmt.Printf(" v2 \"myproject/api/v2\"\n") + fmt.Printf(" )\n") + + fmt.Println() +} + +// demonstrateDotImports 演示点导入 +func demonstrateDotImports() { + fmt.Println("3. 点导入:") + + // 点导入的概念 + fmt.Printf(" 点导入的概念:\n") + fmt.Printf(" - 使用 . 作为包别名\n") + fmt.Printf(" - 导入包的导出标识符到当前包的命名空间\n") + fmt.Printf(" - 可以直接使用导入包的函数,无需包名前缀\n") + fmt.Printf(" - 一般不推荐使用,容易造成命名冲突\n") + + // 点导入语法 + fmt.Printf(" 点导入语法:\n") + fmt.Printf(" import . \"fmt\"\n") + fmt.Printf(" import . \"math\"\n") + fmt.Printf(" // 使用: Println() 而不是 fmt.Println()\n") + + // 点导入示例 + fmt.Printf(" 点导入示例:\n") + + // 直接使用 fmt 包的函数(通过点导入) + Printf(" 使用点导入的 Printf 函数\n") + Sprintf("格式化字符串") // 这里不会输出,只是演示语法 + + // 点导入的问题 + fmt.Printf(" 点导入的问题:\n") + fmt.Printf(" 1. 命名冲突:可能与本地标识符冲突\n") + fmt.Printf(" 2. 可读性差:不清楚函数来自哪个包\n") + fmt.Printf(" 3. 维护困难:重构时难以追踪依赖\n") + fmt.Printf(" 4. 测试复杂:难以进行包级别的测试\n") + + // 点导入的适用场景 + fmt.Printf(" 点导入的适用场景:\n") + fmt.Printf(" 1. 测试文件:import . \"testing\"\n") + fmt.Printf(" 2. DSL 实现:领域特定语言\n") + fmt.Printf(" 3. 数学计算:import . \"math\"\n") + fmt.Printf(" 4. 临时脚本:快速原型开发\n") + + // 替代方案 + fmt.Printf(" 替代方案:\n") + fmt.Printf(" 1. 使用短别名:import f \"fmt\"\n") + fmt.Printf(" 2. 局部变量:var printf = fmt.Printf\n") + fmt.Printf(" 3. 包装函数:func print(args ...interface{}) { fmt.Print(args...) }\n") + + fmt.Println() +} + +// demonstrateBlankImports 演示空白导入 +func demonstrateBlankImports() { + fmt.Println("4. 空白导入:") + + // 空白导入的概念 + fmt.Printf(" 空白导入的概念:\n") + fmt.Printf(" - 使用 _ 作为包别名\n") + fmt.Printf(" - 只执行包的初始化,不使用包的导出标识符\n") + fmt.Printf(" - 主要用于触发包的副作用\n") + fmt.Printf(" - 避免编译器的未使用导入错误\n") + + // 空白导入语法 + fmt.Printf(" 空白导入语法:\n") + fmt.Printf(" import _ \"package-path\"\n") + fmt.Printf(" import (\n") + fmt.Printf(" _ \"net/http/pprof\" // 注册 pprof 处理器\n") + fmt.Printf(" _ \"image/jpeg\" // 注册 JPEG 解码器\n") + fmt.Printf(" )\n") + + // 空白导入的用途 + fmt.Printf(" 空白导入的用途:\n") + fmt.Printf(" 1. 数据库驱动注册\n") + fmt.Printf(" 2. 图像格式支持\n") + fmt.Printf(" 3. 插件系统\n") + fmt.Printf(" 4. 性能分析工具\n") + fmt.Printf(" 5. 静态资源嵌入\n") + + // 常见的空白导入示例 + fmt.Printf(" 常见的空白导入示例:\n") + + // 1. 数据库驱动 + fmt.Printf(" 1. 数据库驱动:\n") + fmt.Printf(" import (\n") + fmt.Printf(" \"database/sql\"\n") + fmt.Printf(" _ \"github.com/go-sql-driver/mysql\" // MySQL 驱动\n") + fmt.Printf(" _ \"github.com/lib/pq\" // PostgreSQL 驱动\n") + fmt.Printf(" )\n") + + // 2. 图像格式支持 + fmt.Printf(" 2. 图像格式支持:\n") + fmt.Printf(" import (\n") + fmt.Printf(" \"image\"\n") + fmt.Printf(" _ \"image/jpeg\" // JPEG 支持\n") + fmt.Printf(" _ \"image/png\" // PNG 支持\n") + fmt.Printf(" _ \"image/gif\" // GIF 支持\n") + fmt.Printf(" )\n") + + // 3. HTTP 性能分析 + fmt.Printf(" 3. HTTP 性能分析:\n") + fmt.Printf(" import _ \"net/http/pprof\"\n") + fmt.Printf(" // 自动注册 /debug/pprof/ 路由\n") + + // 空白导入的注意事项 + fmt.Printf(" 空白导入的注意事项:\n") + fmt.Printf(" 1. 增加编译时间和二进制大小\n") + fmt.Printf(" 2. 可能引入不需要的依赖\n") + fmt.Printf(" 3. 初始化顺序可能影响程序行为\n") + fmt.Printf(" 4. 难以追踪包的实际用途\n") + + // 检查空白导入的效果 + fmt.Printf(" 检查空白导入的效果:\n") + fmt.Printf(" net/http/pprof 包已通过空白导入加载\n") + fmt.Printf(" 可以通过 http://localhost:6060/debug/pprof/ 访问性能分析\n") + + fmt.Println() +} + +// demonstrateInitializationOrder 演示包的初始化顺序 +func demonstrateInitializationOrder() { + fmt.Println("5. 包的初始化顺序:") + + // 初始化顺序规则 + fmt.Printf(" 初始化顺序规则:\n") + fmt.Printf(" 1. 依赖包先初始化\n") + fmt.Printf(" 2. 同一包内按依赖关系初始化变量\n") + fmt.Printf(" 3. init 函数按文件名字典序执行\n") + fmt.Printf(" 4. 同一文件内 init 函数按出现顺序执行\n") + fmt.Printf(" 5. main 函数最后执行\n") + + // 初始化示例 + fmt.Printf(" 初始化示例:\n") + fmt.Printf(" 包 A 导入 包 B 和 包 C\n") + fmt.Printf(" 包 B 导入 包 D\n") + fmt.Printf(" 包 C 导入 包 D\n") + fmt.Printf("\\n") + fmt.Printf(" 初始化顺序: D -> B -> C -> A\n") + + // 变量初始化顺序 + fmt.Printf(" 变量初始化顺序:\n") + fmt.Printf(" var (\n") + fmt.Printf(" a = b + 1 // 第二个初始化\n") + fmt.Printf(" b = 2 // 第一个初始化\n") + fmt.Printf(" c = a + b // 第三个初始化\n") + fmt.Printf(" )\n") + + // init 函数执行顺序 + fmt.Printf(" init 函数执行顺序:\n") + fmt.Printf(" 文件 a.go:\n") + fmt.Printf(" func init() { fmt.Println(\"a.go init 1\") }\n") + fmt.Printf(" func init() { fmt.Println(\"a.go init 2\") }\n") + fmt.Printf("\\n") + fmt.Printf(" 文件 b.go:\n") + fmt.Printf(" func init() { fmt.Println(\"b.go init 1\") }\n") + fmt.Printf("\\n") + fmt.Printf(" 执行顺序: a.go init 1 -> a.go init 2 -> b.go init 1\n") + + // 实际初始化演示 + fmt.Printf(" 实际初始化演示:\n") + fmt.Printf(" utils 包的初始化信息:\n") + fmt.Printf(" 版本: %s\n", utils.Version) + fmt.Printf(" 最大重试次数: %d\n", utils.MaxRetries) + + // 初始化最佳实践 + fmt.Printf(" 初始化最佳实践:\n") + fmt.Printf(" 1. 保持 init 函数简单\n") + fmt.Printf(" 2. 避免复杂的初始化逻辑\n") + fmt.Printf(" 3. 不要依赖 init 函数的执行顺序\n") + fmt.Printf(" 4. 处理初始化可能的错误\n") + fmt.Printf(" 5. 使用延迟初始化处理复杂情况\n") + + fmt.Println() +} + +// demonstrateCircularImports 演示循环导入问题 +func demonstrateCircularImports() { + fmt.Println("6. 循环导入问题:") + + // 循环导入的概念 + fmt.Printf(" 循环导入的概念:\n") + fmt.Printf(" - 两个或多个包相互导入\n") + fmt.Printf(" - Go 编译器禁止循环导入\n") + fmt.Printf(" - 会导致编译错误\n") + fmt.Printf(" - 需要重新设计包结构来解决\n") + + // 循环导入示例 + fmt.Printf(" 循环导入示例:\n") + fmt.Printf(" 包 A:\n") + fmt.Printf(" package a\n") + fmt.Printf(" import \"myproject/b\"\n") + fmt.Printf(" func UseB() { b.FuncB() }\n") + fmt.Printf("\\n") + fmt.Printf(" 包 B:\n") + fmt.Printf(" package b\n") + fmt.Printf(" import \"myproject/a\" // 循环导入!\n") + fmt.Printf(" func UseA() { a.FuncA() }\n") + + // 循环导入的检测 + fmt.Printf(" 循环导入的检测:\n") + fmt.Printf(" 编译错误信息:\n") + fmt.Printf(" import cycle not allowed\n") + fmt.Printf(" package myproject/a\n") + fmt.Printf(" imports myproject/b\n") + fmt.Printf(" imports myproject/a\n") + + // 解决循环导入的方法 + fmt.Printf(" 解决循环导入的方法:\n") + fmt.Printf(" 1. 提取公共接口\n") + fmt.Printf(" 2. 创建中间包\n") + fmt.Printf(" 3. 重新组织包结构\n") + fmt.Printf(" 4. 使用依赖注入\n") + fmt.Printf(" 5. 合并相关包\n") + + // 解决方案示例 + fmt.Printf(" 解决方案示例:\n") + fmt.Printf(" 方案1 - 提取公共接口:\n") + fmt.Printf(" 包 interfaces:\n") + fmt.Printf(" type ServiceA interface { MethodA() }\n") + fmt.Printf(" type ServiceB interface { MethodB() }\n") + fmt.Printf("\\n") + fmt.Printf(" 包 A 导入 interfaces\n") + fmt.Printf(" 包 B 导入 interfaces\n") + fmt.Printf(" main 包导入 A 和 B,进行依赖注入\n") + + fmt.Printf(" 方案2 - 创建中间包:\n") + fmt.Printf(" 包 common: 包含 A 和 B 都需要的功能\n") + fmt.Printf(" 包 A 导入 common\n") + fmt.Printf(" 包 B 导入 common\n") + fmt.Printf(" A 和 B 不再相互导入\n") + + // 预防循环导入 + fmt.Printf(" 预防循环导入:\n") + fmt.Printf(" 1. 设计清晰的包层次结构\n") + fmt.Printf(" 2. 遵循依赖倒置原则\n") + fmt.Printf(" 3. 使用接口定义包边界\n") + fmt.Printf(" 4. 定期检查包依赖关系\n") + fmt.Printf(" 5. 使用工具检测循环依赖\n") + + fmt.Println() +} + +// demonstratePackageManagement 演示包管理 +func demonstratePackageManagement() { + fmt.Println("7. 包管理:") + + // Go Modules 概念 + fmt.Printf(" Go Modules 概念:\n") + fmt.Printf(" - Go 1.11+ 的官方包管理工具\n") + fmt.Printf(" - 替代 GOPATH 模式\n") + fmt.Printf(" - 支持版本管理和依赖解析\n") + fmt.Printf(" - 使用 go.mod 文件定义模块\n") + + // go.mod 文件示例 + fmt.Printf(" go.mod 文件示例:\n") + fmt.Printf(" module myproject\n") + fmt.Printf("\\n") + fmt.Printf(" go 1.19\n") + fmt.Printf("\\n") + fmt.Printf(" require (\n") + fmt.Printf(" github.com/gin-gonic/gin v1.9.1\n") + fmt.Printf(" github.com/go-redis/redis/v8 v8.11.5\n") + fmt.Printf(" )\n") + fmt.Printf("\\n") + fmt.Printf(" replace github.com/old/package => github.com/new/package v1.0.0\n") + + // 常用的 go mod 命令 + fmt.Printf(" 常用的 go mod 命令:\n") + commands := map[string]string{ + "go mod init ": "初始化新模块", + "go mod tidy": "添加缺失的依赖,移除未使用的依赖", + "go mod download": "下载依赖到本地缓存", + "go mod verify": "验证依赖的完整性", + "go mod graph": "打印模块依赖图", + "go mod why ": "解释为什么需要某个包", + "go mod edit": "编辑 go.mod 文件", + "go mod vendor": "将依赖复制到 vendor 目录", + } + + for cmd, desc := range commands { + fmt.Printf(" %-25s - %s\n", cmd, desc) + } + + // 版本管理 + fmt.Printf(" 版本管理:\n") + fmt.Printf(" 语义化版本: v1.2.3\n") + fmt.Printf(" 主版本号: 不兼容的 API 变更\n") + fmt.Printf(" 次版本号: 向后兼容的功能增加\n") + fmt.Printf(" 修订版本号: 向后兼容的问题修复\n") + fmt.Printf("\\n") + fmt.Printf(" 版本约束:\n") + fmt.Printf(" v1.2.3 - 精确版本\n") + fmt.Printf(" v1.2 - 最新的 v1.2.x\n") + fmt.Printf(" v1 - 最新的 v1.x.x\n") + fmt.Printf(" latest - 最新版本\n") + + // 私有包管理 + fmt.Printf(" 私有包管理:\n") + fmt.Printf(" 环境变量设置:\n") + fmt.Printf(" GOPRIVATE=github.com/mycompany/*\n") + fmt.Printf(" GONOPROXY=github.com/mycompany/*\n") + fmt.Printf(" GONOSUMDB=github.com/mycompany/*\n") + + // 检查当前项目的模块信息 + fmt.Printf(" 当前项目信息:\n") + + // 查找 go.mod 文件 + currentDir, _ := os.Getwd() + goModPath := filepath.Join(currentDir, "go.mod") + + if _, err := os.Stat(goModPath); err == nil { + content, err := ioutil.ReadFile(goModPath) + if err == nil { + fmt.Printf(" 找到 go.mod 文件:\n") + lines := strings.Split(string(content), "\n") + for i, line := range lines { + if i < 10 { // 只显示前10行 + fmt.Printf(" %s\n", line) + } + } + } + } else { + fmt.Printf(" 当前目录没有 go.mod 文件\n") + fmt.Printf(" 可以使用 'go mod init ' 初始化\n") + } + + // 包管理最佳实践 + fmt.Printf(" 包管理最佳实践:\n") + fmt.Printf(" 1. 使用语义化版本号\n") + fmt.Printf(" 2. 定期更新依赖\n") + fmt.Printf(" 3. 锁定关键依赖的版本\n") + fmt.Printf(" 4. 使用 go mod tidy 清理依赖\n") + fmt.Printf(" 5. 提交 go.mod 和 go.sum 文件\n") + fmt.Printf(" 6. 避免使用 replace 指令在生产环境\n") + fmt.Printf(" 7. 定期检查安全漏洞\n") + + fmt.Println() +} + +/* +运行这个程序: +go run 02-importing-packages.go + +学习要点: +1. Go 语言支持多种包导入方式 +2. 包的导入路径决定了包的唯一性 +3. 包的初始化有明确的顺序规则 +4. 循环导入是被禁止的,需要重新设计包结构 +5. Go Modules 是现代 Go 项目的标准包管理方式 + +包导入方式: +1. 基本导入:import "package-path" +2. 别名导入:import alias "package-path" +3. 点导入:import . "package-path"(不推荐) +4. 空白导入:import _ "package-path" + +导入路径类型: +1. 标准库:fmt, time, strings 等 +2. 相对路径:./utils, ../common(不推荐) +3. 绝对路径:github.com/user/project/pkg +4. 内部包:internal/ 目录下的包 + +包初始化顺序: +1. 依赖包先初始化 +2. 包级别变量按依赖关系初始化 +3. init 函数按文件名和出现顺序执行 +4. main 函数最后执行 + +循环导入解决方案: +1. 提取公共接口 +2. 创建中间包 +3. 重新组织包结构 +4. 使用依赖注入 +5. 合并相关包 + +Go Modules 特性: +1. 版本管理:支持语义化版本 +2. 依赖解析:自动解决依赖冲突 +3. 模块缓存:提高构建速度 +4. 安全验证:校验包的完整性 +5. 私有包支持:支持私有仓库 + +最佳实践: +1. 使用 Go Modules 管理依赖 +2. 遵循包导入的命名规范 +3. 避免使用点导入和相对路径导入 +4. 合理使用空白导入 +5. 定期清理和更新依赖 +6. 提交 go.mod 和 go.sum 文件 +7. 使用工具检测循环依赖 + +注意事项: +1. 包的导入路径必须唯一 +2. 同一目录下的文件必须属于同一包 +3. 包名通常与目录名相同 +4. 避免循环导入 +5. 合理使用包的可见性规则 +*/ diff --git a/golang-learning/08-packages/utils/helper.go b/golang-learning/08-packages/utils/helper.go index cffb6ec..a541c3f 100644 --- a/golang-learning/08-packages/utils/helper.go +++ b/golang-learning/08-packages/utils/helper.go @@ -1,8 +1,48 @@ -// Package utils 提供一些实用的辅助函数 -// 这是一个示例包,用于演示包的创建和使用 +/* +Package utils 提供一些实用的辅助函数 + +这是一个示例包,用于演示包的创建和使用。 +包含了数学计算、字符串处理、数据验证等常用功能。 + +使用示例: + + import "your-module/08-packages/utils" + + result := utils.Add(1, 2) + greeting := utils.GetGreeting("World") +*/ package utils -import "fmt" +import ( + "fmt" + "math" + "regexp" + "strings" + "time" +) + +// 包级别的常量 +const ( + // MaxRetries 最大重试次数 + MaxRetries = 3 + + // DefaultTimeout 默认超时时间 + DefaultTimeout = 30 * time.Second + + // Version 包版本 + Version = "1.0.0" +) + +// 包级别的变量 +var ( + // PackageVersion 包版本(可修改) + PackageVersion = "1.0.0" + + // emailRegex 邮箱验证正则表达式 + emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) +) + +// ========== 数学计算函数 ========== // Add 计算两个整数的和 // 注意:函数名首字母大写,表示这是一个导出的(公开的)函数 @@ -10,11 +50,59 @@ func Add(a, b int) int { return a + b } +// Subtract 计算两个整数的差 +func Subtract(a, b int) int { + return a - b +} + // Multiply 计算两个整数的乘积 func Multiply(a, b int) int { return a * b } +// Divide 计算两个数的商,返回结果和错误 +func Divide(a, b float64) (float64, error) { + if b == 0 { + return 0, fmt.Errorf("除数不能为零") + } + return a / b, nil +} + +// Max 返回两个整数中的较大值 +func Max(a, b int) int { + if a > b { + return a + } + return b +} + +// Min 返回两个整数中的较小值 +func Min(a, b int) int { + if a < b { + return a + } + return b +} + +// Abs 返回整数的绝对值 +func Abs(n int) int { + if n < 0 { + return -n + } + return n +} + +// Power 计算 base 的 exp 次方 +func Power(base, exp int) int { + result := 1 + for i := 0; i < exp; i++ { + result *= base + } + return result +} + +// ========== 字符串处理函数 ========== + // greet 是一个私有函数(首字母小写) // 只能在包内部使用,外部无法访问 func greet(name string) string { @@ -26,8 +114,206 @@ func GetGreeting(name string) string { return greet(name) } -// 包级别的变量 -var PackageVersion = "1.0.0" +// Capitalize 将字符串首字母大写 +func Capitalize(s string) string { + if len(s) == 0 { + return s + } + return strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) +} -// 包级别的常量 -const MaxRetries = 3 \ No newline at end of file +// Reverse 反转字符串 +func Reverse(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// IsPalindrome 检查字符串是否为回文 +func IsPalindrome(s string) bool { + s = strings.ToLower(strings.ReplaceAll(s, " ", "")) + return s == Reverse(s) +} + +// WordCount 统计字符串中的单词数量 +func WordCount(s string) int { + words := strings.Fields(s) + return len(words) +} + +// ========== 数据验证函数 ========== + +// IsEmail 验证邮箱地址格式 +func IsEmail(email string) bool { + return emailRegex.MatchString(email) +} + +// IsEmpty 检查字符串是否为空或只包含空白字符 +func IsEmpty(s string) bool { + return strings.TrimSpace(s) == "" +} + +// InRange 检查数字是否在指定范围内 +func InRange(value, min, max int) bool { + return value >= min && value <= max +} + +// IsPositive 检查数字是否为正数 +func IsPositive(n int) bool { + return n > 0 +} + +// IsEven 检查数字是否为偶数 +func IsEven(n int) bool { + return n%2 == 0 +} + +// IsOdd 检查数字是否为奇数 +func IsOdd(n int) bool { + return n%2 != 0 +} + +// ========== 切片操作函数 ========== + +// Contains 检查切片是否包含指定元素 +func Contains(slice []int, item int) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + +// ContainsString 检查字符串切片是否包含指定字符串 +func ContainsString(slice []string, item string) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + +// RemoveDuplicates 移除整数切片中的重复元素 +func RemoveDuplicates(slice []int) []int { + keys := make(map[int]bool) + var result []int + + for _, item := range slice { + if !keys[item] { + keys[item] = true + result = append(result, item) + } + } + + return result +} + +// Sum 计算整数切片的总和 +func Sum(slice []int) int { + total := 0 + for _, v := range slice { + total += v + } + return total +} + +// Average 计算整数切片的平均值 +func Average(slice []int) float64 { + if len(slice) == 0 { + return 0 + } + return float64(Sum(slice)) / float64(len(slice)) +} + +// ========== 时间处理函数 ========== + +// FormatDuration 格式化时间间隔 +func FormatDuration(d time.Duration) string { + if d < time.Minute { + return fmt.Sprintf("%.1f秒", d.Seconds()) + } else if d < time.Hour { + return fmt.Sprintf("%.1f分钟", d.Minutes()) + } else if d < 24*time.Hour { + return fmt.Sprintf("%.1f小时", d.Hours()) + } else { + return fmt.Sprintf("%.1f天", d.Hours()/24) + } +} + +// IsWeekend 检查给定日期是否为周末 +func IsWeekend(t time.Time) bool { + weekday := t.Weekday() + return weekday == time.Saturday || weekday == time.Sunday +} + +// DaysUntil 计算距离指定日期还有多少天 +func DaysUntil(target time.Time) int { + now := time.Now() + diff := target.Sub(now) + return int(math.Ceil(diff.Hours() / 24)) +} + +// ========== 类型定义 ========== + +// Point 表示二维坐标点 +type Point struct { + X, Y float64 +} + +// Distance 计算两点之间的距离 +func (p Point) Distance(other Point) float64 { + dx := p.X - other.X + dy := p.Y - other.Y + return math.Sqrt(dx*dx + dy*dy) +} + +// String 实现 Stringer 接口 +func (p Point) String() string { + return fmt.Sprintf("Point(%.2f, %.2f)", p.X, p.Y) +} + +// Rectangle 表示矩形 +type Rectangle struct { + Width, Height float64 +} + +// Area 计算矩形面积 +func (r Rectangle) Area() float64 { + return r.Width * r.Height +} + +// Perimeter 计算矩形周长 +func (r Rectangle) Perimeter() float64 { + return 2 * (r.Width + r.Height) +} + +// String 实现 Stringer 接口 +func (r Rectangle) String() string { + return fmt.Sprintf("Rectangle(%.2fx%.2f)", r.Width, r.Height) +} + +// ========== 错误类型 ========== + +// ValidationError 表示验证错误 +type ValidationError struct { + Field string + Message string +} + +// Error 实现 error 接口 +func (e ValidationError) Error() string { + return fmt.Sprintf("验证错误 [%s]: %s", e.Field, e.Message) +} + +// ========== 包初始化函数 ========== + +// init 函数在包被导入时自动执行 +func init() { + // 可以在这里进行包的初始化工作 + // 例如:设置默认值、验证环境等 + fmt.Printf("utils 包已加载,版本: %s\n", Version) +} diff --git a/golang-learning/09-advanced/01-reflection.go b/golang-learning/09-advanced/01-reflection.go new file mode 100644 index 0000000..f96a6ba --- /dev/null +++ b/golang-learning/09-advanced/01-reflection.go @@ -0,0 +1,967 @@ +/* +01-reflection.go - Go 语言反射详解 + +学习目标: +1. 理解反射的概念和原理 +2. 掌握 reflect 包的基本使用 +3. 学会反射的类型和值操作 +4. 了解反射的应用场景 +5. 掌握反射的最佳实践和注意事项 + +知识点: +- 反射的基本概念 +- reflect.Type 和 reflect.Value +- 类型检查和转换 +- 结构体字段和方法的反射 +- 反射的性能考虑 +- 反射的实际应用 +*/ + +package main + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" +) + +func main() { + fmt.Println("=== Go 语言反射详解 ===\\n") + + // 演示反射的基本概念 + demonstrateReflectionBasics() + + // 演示类型反射 + demonstrateTypeReflection() + + // 演示值反射 + demonstrateValueReflection() + + // 演示结构体反射 + demonstrateStructReflection() + + // 演示方法反射 + demonstrateMethodReflection() + + // 演示反射的实际应用 + demonstratePracticalApplications() + + // 演示反射的最佳实践 + demonstrateBestPractices() +} + +// demonstrateReflectionBasics 演示反射的基本概念 +func demonstrateReflectionBasics() { + fmt.Println("1. 反射的基本概念:") + + // 反射的基本概念 + fmt.Printf(" 反射的基本概念:\\n") + fmt.Printf(" - 反射是程序在运行时检查自身结构的能力\\n") + fmt.Printf(" - Go 通过 reflect 包提供反射功能\\n") + fmt.Printf(" - 反射基于接口的动态类型信息\\n") + fmt.Printf(" - 主要包括类型反射和值反射\\n") + fmt.Printf(" - 反射遵循 Go 的类型系统规则\\n") + + // 反射的核心类型 + fmt.Printf(" 反射的核心类型:\\n") + fmt.Printf(" - reflect.Type: 表示类型信息\\n") + fmt.Printf(" - reflect.Value: 表示值信息\\n") + fmt.Printf(" - reflect.Kind: 表示基础类型种类\\n") + + // 基本反射示例 + fmt.Printf(" 基本反射示例:\\n") + + var x interface{} = 42 + + // 获取类型信息 + t := reflect.TypeOf(x) + fmt.Printf(" 类型: %v\\n", t) + fmt.Printf(" 类型名称: %s\\n", t.Name()) + fmt.Printf(" 类型种类: %v\\n", t.Kind()) + + // 获取值信息 + v := reflect.ValueOf(x) + fmt.Printf(" 值: %v\\n", v) + fmt.Printf(" 值的类型: %v\\n", v.Type()) + fmt.Printf(" 值的种类: %v\\n", v.Kind()) + fmt.Printf(" 值的接口: %v\\n", v.Interface()) + + // 不同类型的反射 + fmt.Printf(" 不同类型的反射:\\n") + + values := []interface{}{ + 42, + "hello", + 3.14, + true, + []int{1, 2, 3}, + map[string]int{"a": 1}, + Person{Name: "Alice", Age: 30}, + } + + for i, val := range values { + t := reflect.TypeOf(val) + v := reflect.ValueOf(val) + fmt.Printf(" 值%d: %v (类型: %v, 种类: %v)\\n", + i+1, val, t, v.Kind()) + } + + fmt.Println() +} + +// demonstrateTypeReflection 演示类型反射 +func demonstrateTypeReflection() { + fmt.Println("2. 类型反射:") + + // 基本类型反射 + fmt.Printf(" 基本类型反射:\\n") + + var i int = 42 + var s string = "hello" + var f float64 = 3.14 + var b bool = true + + types := []interface{}{i, s, f, b} + for _, val := range types { + t := reflect.TypeOf(val) + fmt.Printf(" %v: 名称=%s, 种类=%v, 大小=%d字节\\n", + val, t.Name(), t.Kind(), t.Size()) + } + + // 复合类型反射 + fmt.Printf(" 复合类型反射:\\n") + + // 切片类型 + slice := []int{1, 2, 3} + sliceType := reflect.TypeOf(slice) + fmt.Printf(" 切片类型: %v\\n", sliceType) + fmt.Printf(" 元素类型: %v\\n", sliceType.Elem()) + fmt.Printf(" 是否为切片: %t\\n", sliceType.Kind() == reflect.Slice) + + // 映射类型 + m := map[string]int{"a": 1, "b": 2} + mapType := reflect.TypeOf(m) + fmt.Printf(" 映射类型: %v\\n", mapType) + fmt.Printf(" 键类型: %v\\n", mapType.Key()) + fmt.Printf(" 值类型: %v\\n", mapType.Elem()) + + // 指针类型 + var p *int = &i + ptrType := reflect.TypeOf(p) + fmt.Printf(" 指针类型: %v\\n", ptrType) + fmt.Printf(" 指向类型: %v\\n", ptrType.Elem()) + fmt.Printf(" 是否为指针: %t\\n", ptrType.Kind() == reflect.Ptr) + + // 函数类型 + fn := func(int, string) bool { return true } + fnType := reflect.TypeOf(fn) + fmt.Printf(" 函数类型: %v\\n", fnType) + fmt.Printf(" 参数个数: %d\\n", fnType.NumIn()) + fmt.Printf(" 返回值个数: %d\\n", fnType.NumOut()) + + for i := 0; i < fnType.NumIn(); i++ { + fmt.Printf(" 参数%d类型: %v\\n", i, fnType.In(i)) + } + + for i := 0; i < fnType.NumOut(); i++ { + fmt.Printf(" 返回值%d类型: %v\\n", i, fnType.Out(i)) + } + + // 通道类型 + ch := make(chan int) + chType := reflect.TypeOf(ch) + fmt.Printf(" 通道类型: %v\\n", chType) + fmt.Printf(" 通道方向: %v\\n", chType.ChanDir()) + fmt.Printf(" 元素类型: %v\\n", chType.Elem()) + + fmt.Println() +} + +// demonstrateValueReflection 演示值反射 +func demonstrateValueReflection() { + fmt.Println("3. 值反射:") + + // 基本值操作 + fmt.Printf(" 基本值操作:\\n") + + var x interface{} = 42 + v := reflect.ValueOf(x) + + fmt.Printf(" 原始值: %v\\n", v.Interface()) + fmt.Printf(" 整数值: %d\\n", v.Int()) + fmt.Printf(" 是否有效: %t\\n", v.IsValid()) + fmt.Printf(" 是否为零值: %t\\n", v.IsZero()) + + // 值的修改 + fmt.Printf(" 值的修改:\\n") + + var y int = 100 + v = reflect.ValueOf(&y) // 需要传递指针才能修改 + if v.Kind() == reflect.Ptr && v.Elem().CanSet() { + v.Elem().SetInt(200) + fmt.Printf(" 修改后的值: %d\\n", y) + } + + // 字符串值操作 + var str string = "hello" + v = reflect.ValueOf(&str) + if v.Kind() == reflect.Ptr && v.Elem().CanSet() { + v.Elem().SetString("world") + fmt.Printf(" 修改后的字符串: %s\\n", str) + } + + // 切片值操作 + fmt.Printf(" 切片值操作:\\n") + + slice := []int{1, 2, 3} + v = reflect.ValueOf(slice) + + fmt.Printf(" 切片长度: %d\\n", v.Len()) + fmt.Printf(" 切片容量: %d\\n", v.Cap()) + + // 访问切片元素 + for i := 0; i < v.Len(); i++ { + elem := v.Index(i) + fmt.Printf(" 元素[%d]: %v\\n", i, elem.Interface()) + } + + // 映射值操作 + fmt.Printf(" 映射值操作:\\n") + + m := map[string]int{"a": 1, "b": 2, "c": 3} + v = reflect.ValueOf(m) + + fmt.Printf(" 映射长度: %d\\n", v.Len()) + + // 遍历映射 + for _, key := range v.MapKeys() { + value := v.MapIndex(key) + fmt.Printf(" %v: %v\\n", key.Interface(), value.Interface()) + } + + // 设置映射值 + v.SetMapIndex(reflect.ValueOf("d"), reflect.ValueOf(4)) + fmt.Printf(" 添加元素后: %v\\n", m) + + // 创建新值 + fmt.Printf(" 创建新值:\\n") + + // 创建新的整数值 + newInt := reflect.New(reflect.TypeOf(0)) + newInt.Elem().SetInt(999) + fmt.Printf(" 新创建的整数: %v\\n", newInt.Elem().Interface()) + + // 创建新的切片 + sliceType := reflect.SliceOf(reflect.TypeOf(0)) + newSlice := reflect.MakeSlice(sliceType, 3, 5) + for i := 0; i < newSlice.Len(); i++ { + newSlice.Index(i).SetInt(int64(i * 10)) + } + fmt.Printf(" 新创建的切片: %v\\n", newSlice.Interface()) + + fmt.Println() +} + +// demonstrateStructReflection 演示结构体反射 +func demonstrateStructReflection() { + fmt.Println("4. 结构体反射:") + + // 结构体类型信息 + fmt.Printf(" 结构体类型信息:\\n") + + person := Person{ + Name: "Alice", + Age: 30, + Email: "alice@example.com", + } + + t := reflect.TypeOf(person) + v := reflect.ValueOf(person) + + fmt.Printf(" 结构体名称: %s\\n", t.Name()) + fmt.Printf(" 字段数量: %d\\n", t.NumField()) + + // 遍历结构体字段 + fmt.Printf(" 遍历结构体字段:\\n") + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + + fmt.Printf(" 字段%d: %s (类型: %v, 值: %v)\\n", + i, field.Name, field.Type, value.Interface()) + + // 检查字段标签 + if tag := field.Tag; tag != "" { + fmt.Printf(" 标签: %s\\n", tag) + if jsonTag := tag.Get("json"); jsonTag != "" { + fmt.Printf(" JSON标签: %s\\n", jsonTag) + } + } + } + + // 按名称访问字段 + fmt.Printf(" 按名称访问字段:\\n") + + nameField := v.FieldByName("Name") + if nameField.IsValid() { + fmt.Printf(" Name字段值: %v\\n", nameField.Interface()) + } + + // 修改结构体字段 + fmt.Printf(" 修改结构体字段:\\n") + + // 需要使用指针才能修改 + v = reflect.ValueOf(&person) + if v.Kind() == reflect.Ptr { + v = v.Elem() // 获取指针指向的值 + + nameField = v.FieldByName("Name") + if nameField.CanSet() { + nameField.SetString("Bob") + fmt.Printf(" 修改后的Name: %s\\n", person.Name) + } + + ageField := v.FieldByName("Age") + if ageField.CanSet() { + ageField.SetInt(35) + fmt.Printf(" 修改后的Age: %d\\n", person.Age) + } + } + + // 嵌套结构体 + fmt.Printf(" 嵌套结构体:\\n") + + employee := Employee{ + Person: Person{Name: "Charlie", Age: 28}, + Title: "Developer", + Salary: 80000, + } + + t = reflect.TypeOf(employee) + v = reflect.ValueOf(employee) + + fmt.Printf(" 嵌套结构体字段数: %d\\n", t.NumField()) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + + fmt.Printf(" 字段%d: %s (类型: %v)\\n", i, field.Name, field.Type) + + // 如果是嵌套结构体,进一步展开 + if field.Type.Kind() == reflect.Struct { + fmt.Printf(" 嵌套字段:\\n") + nestedValue := value + nestedType := field.Type + + for j := 0; j < nestedType.NumField(); j++ { + nestedField := nestedType.Field(j) + nestedFieldValue := nestedValue.Field(j) + fmt.Printf(" %s: %v\\n", nestedField.Name, nestedFieldValue.Interface()) + } + } else { + fmt.Printf(" 值: %v\\n", value.Interface()) + } + } + + fmt.Println() +} + +// demonstrateMethodReflection 演示方法反射 +func demonstrateMethodReflection() { + fmt.Println("5. 方法反射:") + + // 方法信息 + fmt.Printf(" 方法信息:\\n") + + person := &Person{Name: "David", Age: 25} + t := reflect.TypeOf(person) + v := reflect.ValueOf(person) + + fmt.Printf(" 方法数量: %d\\n", t.NumMethod()) + + // 遍历方法 + for i := 0; i < t.NumMethod(); i++ { + method := t.Method(i) + fmt.Printf(" 方法%d: %s (类型: %v)\\n", i, method.Name, method.Type) + } + + // 调用方法 + fmt.Printf(" 调用方法:\\n") + + // 按名称获取方法 + greetMethod := v.MethodByName("Greet") + if greetMethod.IsValid() { + // 调用无参数方法 + result := greetMethod.Call(nil) + if len(result) > 0 { + fmt.Printf(" Greet方法返回: %v\\n", result[0].Interface()) + } + } + + // 调用带参数的方法 + setAgeMethod := v.MethodByName("SetAge") + if setAgeMethod.IsValid() { + args := []reflect.Value{reflect.ValueOf(30)} + setAgeMethod.Call(args) + fmt.Printf(" 调用SetAge后,年龄: %d\\n", person.Age) + } + + // 调用带返回值的方法 + getInfoMethod := v.MethodByName("GetInfo") + if getInfoMethod.IsValid() { + result := getInfoMethod.Call(nil) + if len(result) > 0 { + fmt.Printf(" GetInfo方法返回: %v\\n", result[0].Interface()) + } + } + + // 函数值的反射调用 + fmt.Printf(" 函数值的反射调用:\\n") + + fn := func(a, b int) int { + return a + b + } + + fnValue := reflect.ValueOf(fn) + args := []reflect.Value{ + reflect.ValueOf(10), + reflect.ValueOf(20), + } + + result := fnValue.Call(args) + fmt.Printf(" 函数调用结果: %v\\n", result[0].Interface()) + + // 方法集的检查 + fmt.Printf(" 方法集的检查:\\n") + + // 检查类型是否实现了某个接口 + stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + personType := reflect.TypeOf(person) + + fmt.Printf(" Person是否实现Stringer接口: %t\\n", + personType.Implements(stringerType)) + + fmt.Println() +} + +// demonstratePracticalApplications 演示反射的实际应用 +func demonstratePracticalApplications() { + fmt.Println("6. 反射的实际应用:") + + // 应用1: JSON 序列化 + fmt.Printf(" 应用1 - JSON 序列化:\\n") + + person := Person{ + Name: "Eve", + Age: 32, + Email: "eve@example.com", + } + + jsonStr := structToJSON(person) + fmt.Printf(" JSON序列化结果: %s\\n", jsonStr) + + // 应用2: 结构体复制 + fmt.Printf(" 应用2 - 结构体复制:\\n") + + original := Person{Name: "Frank", Age: 40} + copied := copyStruct(original).(Person) + + fmt.Printf(" 原始结构体: %+v\\n", original) + fmt.Printf(" 复制结构体: %+v\\n", copied) + + // 应用3: 结构体比较 + fmt.Printf(" 应用3 - 结构体比较:\\n") + + person1 := Person{Name: "Grace", Age: 28} + person2 := Person{Name: "Grace", Age: 28} + person3 := Person{Name: "Henry", Age: 30} + + fmt.Printf(" person1 == person2: %t\\n", deepEqual(person1, person2)) + fmt.Printf(" person1 == person3: %t\\n", deepEqual(person1, person3)) + + // 应用4: 配置映射 + fmt.Printf(" 应用4 - 配置映射:\\n") + + config := map[string]interface{}{ + "Name": "Iris", + "Age": 26, + "Email": "iris@example.com", + } + + var configPerson Person + mapToStruct(config, &configPerson) + fmt.Printf(" 配置映射结果: %+v\\n", configPerson) + + // 应用5: 验证器 + fmt.Printf(" 应用5 - 验证器:\\n") + + validPerson := Person{Name: "Jack", Age: 25, Email: "jack@example.com"} + invalidPerson := Person{Name: "", Age: -5, Email: "invalid"} + + fmt.Printf(" 有效Person验证: %t\\n", validateStruct(validPerson)) + fmt.Printf(" 无效Person验证: %t\\n", validateStruct(invalidPerson)) + + // 应用6: ORM 映射 + fmt.Printf(" 应用6 - ORM 映射:\\n") + + tableName := getTableName(Person{}) + columns := getColumns(Person{}) + + fmt.Printf(" 表名: %s\\n", tableName) + fmt.Printf(" 列名: %v\\n", columns) + + fmt.Println() +} + +// demonstrateBestPractices 演示反射的最佳实践 +func demonstrateBestPractices() { + fmt.Println("7. 反射的最佳实践:") + + // 最佳实践原则 + fmt.Printf(" 最佳实践原则:\\n") + fmt.Printf(" 1. 谨慎使用反射,优先考虑类型安全的方案\\n") + fmt.Printf(" 2. 缓存反射结果以提高性能\\n") + fmt.Printf(" 3. 进行充分的错误检查\\n") + fmt.Printf(" 4. 避免在热点路径使用反射\\n") + fmt.Printf(" 5. 使用接口而不是反射来实现多态\\n") + + // 性能考虑 + fmt.Printf(" 性能考虑:\\n") + + // 性能测试示例 + person := Person{Name: "Test", Age: 30} + + // 直接访问 vs 反射访问 + start := time.Now() + for i := 0; i < 100000; i++ { + _ = person.Name // 直接访问 + } + directTime := time.Since(start) + + start = time.Now() + v := reflect.ValueOf(person) + nameField := v.FieldByName("Name") + for i := 0; i < 100000; i++ { + _ = nameField.Interface() // 反射访问 + } + reflectTime := time.Since(start) + + fmt.Printf(" 直接访问耗时: %v\\n", directTime) + fmt.Printf(" 反射访问耗时: %v\\n", reflectTime) + fmt.Printf(" 性能差异: %.2fx\\n", float64(reflectTime)/float64(directTime)) + + // 错误处理 + fmt.Printf(" 错误处理:\\n") + + // 安全的反射操作 + safeReflectOperation := func(obj interface{}, fieldName string) (interface{}, error) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("对象不是结构体") + } + + field := v.FieldByName(fieldName) + if !field.IsValid() { + return nil, fmt.Errorf("字段 %s 不存在", fieldName) + } + + return field.Interface(), nil + } + + // 测试安全操作 + result, err := safeReflectOperation(person, "Name") + if err != nil { + fmt.Printf(" 错误: %v\\n", err) + } else { + fmt.Printf(" 安全获取Name字段: %v\\n", result) + } + + result, err = safeReflectOperation(person, "NonExistent") + if err != nil { + fmt.Printf(" 预期错误: %v\\n", err) + } + + // 反射的替代方案 + fmt.Printf(" 反射的替代方案:\\n") + fmt.Printf(" 1. 接口: 使用接口实现多态\\n") + fmt.Printf(" 2. 类型断言: 处理已知的有限类型集合\\n") + fmt.Printf(" 3. 代码生成: 编译时生成类型安全的代码\\n") + fmt.Printf(" 4. 泛型: Go 1.18+ 支持泛型编程\\n") + + // 何时使用反射 + fmt.Printf(" 何时使用反射:\\n") + fmt.Printf(" ✓ 序列化/反序列化库\\n") + fmt.Printf(" ✓ ORM 框架\\n") + fmt.Printf(" ✓ 配置映射\\n") + fmt.Printf(" ✓ 测试框架\\n") + fmt.Printf(" ✓ 依赖注入容器\\n") + fmt.Printf("\\n") + fmt.Printf(" ✗ 简单的类型转换\\n") + fmt.Printf(" ✗ 已知类型的操作\\n") + fmt.Printf(" ✗ 性能敏感的代码\\n") + fmt.Printf(" ✗ 可以用接口解决的问题\\n") + + fmt.Println() +} + +// ========== 类型定义 ========== + +// Person 人员结构体 +type Person struct { + Name string `json:"name" db:"name" validate:"required"` + Age int `json:"age" db:"age" validate:"min=0,max=150"` + Email string `json:"email" db:"email" validate:"email"` +} + +// Greet 问候方法 +func (p *Person) Greet() string { + return fmt.Sprintf("Hello, I'm %s", p.Name) +} + +// SetAge 设置年龄 +func (p *Person) SetAge(age int) { + p.Age = age +} + +// GetInfo 获取信息 +func (p *Person) GetInfo() string { + return fmt.Sprintf("Name: %s, Age: %d, Email: %s", p.Name, p.Age, p.Email) +} + +// String 实现 Stringer 接口 +func (p Person) String() string { + return fmt.Sprintf("Person{Name: %s, Age: %d}", p.Name, p.Age) +} + +// Employee 员工结构体 +type Employee struct { + Person + Title string `json:"title" db:"title"` + Salary float64 `json:"salary" db:"salary"` +} + +// ========== 辅助函数 ========== + +// structToJSON 将结构体转换为 JSON 字符串 +func structToJSON(obj interface{}) string { + v := reflect.ValueOf(obj) + t := reflect.TypeOf(obj) + + if v.Kind() != reflect.Struct { + return "{}" + } + + var parts []string + for i := 0; i < v.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + + // 获取 JSON 标签 + jsonTag := field.Tag.Get("json") + if jsonTag == "" { + jsonTag = strings.ToLower(field.Name) + } + + // 根据类型格式化值 + var valueStr string + switch value.Kind() { + case reflect.String: + valueStr = fmt.Sprintf("\\\"%s\\\"", value.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + valueStr = fmt.Sprintf("%d", value.Int()) + case reflect.Float32, reflect.Float64: + valueStr = fmt.Sprintf("%.2f", value.Float()) + case reflect.Bool: + valueStr = fmt.Sprintf("%t", value.Bool()) + default: + valueStr = fmt.Sprintf("\\\"%v\\\"", value.Interface()) + } + + parts = append(parts, fmt.Sprintf("\\\"%s\\\": %s", jsonTag, valueStr)) + } + + return "{" + strings.Join(parts, ", ") + "}" +} + +// copyStruct 复制结构体 +func copyStruct(src interface{}) interface{} { + srcValue := reflect.ValueOf(src) + srcType := reflect.TypeOf(src) + + if srcValue.Kind() != reflect.Struct { + return src + } + + // 创建新的结构体实例 + newValue := reflect.New(srcType).Elem() + + // 复制所有字段 + for i := 0; i < srcValue.NumField(); i++ { + srcField := srcValue.Field(i) + newField := newValue.Field(i) + + if newField.CanSet() { + newField.Set(srcField) + } + } + + return newValue.Interface() +} + +// deepEqual 深度比较两个值 +func deepEqual(a, b interface{}) bool { + va := reflect.ValueOf(a) + vb := reflect.ValueOf(b) + + if va.Type() != vb.Type() { + return false + } + + switch va.Kind() { + case reflect.Struct: + for i := 0; i < va.NumField(); i++ { + if !deepEqual(va.Field(i).Interface(), vb.Field(i).Interface()) { + return false + } + } + return true + case reflect.Slice: + if va.Len() != vb.Len() { + return false + } + for i := 0; i < va.Len(); i++ { + if !deepEqual(va.Index(i).Interface(), vb.Index(i).Interface()) { + return false + } + } + return true + case reflect.Map: + if va.Len() != vb.Len() { + return false + } + for _, key := range va.MapKeys() { + aVal := va.MapIndex(key) + bVal := vb.MapIndex(key) + if !bVal.IsValid() || !deepEqual(aVal.Interface(), bVal.Interface()) { + return false + } + } + return true + default: + return va.Interface() == vb.Interface() + } +} + +// mapToStruct 将 map 映射到结构体 +func mapToStruct(m map[string]interface{}, dst interface{}) error { + dstValue := reflect.ValueOf(dst) + if dstValue.Kind() != reflect.Ptr || dstValue.Elem().Kind() != reflect.Struct { + return fmt.Errorf("dst 必须是结构体指针") + } + + dstValue = dstValue.Elem() + dstType := dstValue.Type() + + for i := 0; i < dstValue.NumField(); i++ { + field := dstType.Field(i) + fieldValue := dstValue.Field(i) + + if !fieldValue.CanSet() { + continue + } + + // 从 map 中获取对应的值 + if mapValue, ok := m[field.Name]; ok { + mapValueReflect := reflect.ValueOf(mapValue) + + // 类型转换 + if mapValueReflect.Type().ConvertibleTo(fieldValue.Type()) { + fieldValue.Set(mapValueReflect.Convert(fieldValue.Type())) + } + } + } + + return nil +} + +// validateStruct 验证结构体 +func validateStruct(obj interface{}) bool { + v := reflect.ValueOf(obj) + t := reflect.TypeOf(obj) + + if v.Kind() != reflect.Struct { + return false + } + + for i := 0; i < v.NumField(); i++ { + field := t.Field(i) + value := v.Field(i) + + // 获取验证标签 + validateTag := field.Tag.Get("validate") + if validateTag == "" { + continue + } + + // 解析验证规则 + rules := strings.Split(validateTag, ",") + for _, rule := range rules { + if !validateField(value, strings.TrimSpace(rule)) { + return false + } + } + } + + return true +} + +// validateField 验证单个字段 +func validateField(value reflect.Value, rule string) bool { + switch rule { + case "required": + return !value.IsZero() + case "email": + if value.Kind() == reflect.String { + email := value.String() + return strings.Contains(email, "@") && strings.Contains(email, ".") + } + return false + default: + // 处理 min=0, max=150 等规则 + if strings.HasPrefix(rule, "min=") { + minStr := strings.TrimPrefix(rule, "min=") + if min, err := strconv.Atoi(minStr); err == nil { + if value.Kind() == reflect.Int { + return value.Int() >= int64(min) + } + } + } + if strings.HasPrefix(rule, "max=") { + maxStr := strings.TrimPrefix(rule, "max=") + if max, err := strconv.Atoi(maxStr); err == nil { + if value.Kind() == reflect.Int { + return value.Int() <= int64(max) + } + } + } + } + + return true +} + +// getTableName 获取表名 +func getTableName(obj interface{}) string { + t := reflect.TypeOf(obj) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + // 将结构体名转换为表名(简单的复数形式) + name := t.Name() + return strings.ToLower(name) + "s" +} + +// getColumns 获取列名 +func getColumns(obj interface{}) []string { + t := reflect.TypeOf(obj) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + var columns []string + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + // 获取数据库标签 + dbTag := field.Tag.Get("db") + if dbTag != "" { + columns = append(columns, dbTag) + } else { + columns = append(columns, strings.ToLower(field.Name)) + } + } + + return columns +} + +/* +运行这个程序: +go run 01-reflection.go + +学习要点: +1. 反射是程序在运行时检查自身结构的能力 +2. Go 通过 reflect 包提供反射功能 +3. 反射主要包括类型反射和值反射 +4. 反射可以用于序列化、ORM、配置映射等场景 +5. 反射有性能开销,应该谨慎使用 + +反射的核心概念: +1. reflect.Type: 表示类型信息 +2. reflect.Value: 表示值信息 +3. reflect.Kind: 表示基础类型种类 +4. 反射遵循 Go 的类型系统规则 +5. 反射可以检查和修改值 + +类型反射: +1. 获取类型信息:reflect.TypeOf() +2. 类型名称和种类:Name(), Kind() +3. 复合类型:Elem(), Key(), In(), Out() +4. 结构体字段:NumField(), Field() +5. 方法信息:NumMethod(), Method() + +值反射: +1. 获取值信息:reflect.ValueOf() +2. 值的访问:Interface(), Int(), String() +3. 值的修改:Set(), SetInt(), SetString() +4. 集合操作:Len(), Index(), MapIndex() +5. 创建新值:New(), MakeSlice(), MakeMap() + +结构体反射: +1. 字段访问:Field(), FieldByName() +2. 字段信息:Name, Type, Tag +3. 字段修改:CanSet(), Set() +4. 标签解析:Tag.Get() +5. 嵌套结构体处理 + +方法反射: +1. 方法信息:Method(), MethodByName() +2. 方法调用:Call() +3. 方法类型:Type, NumIn(), NumOut() +4. 接口检查:Implements() +5. 方法集分析 + +实际应用场景: +1. JSON 序列化/反序列化 +2. ORM 数据库映射 +3. 配置文件映射 +4. 结构体验证 +5. 依赖注入 +6. 测试框架 +7. 代码生成工具 + +性能考虑: +1. 反射比直接访问慢很多 +2. 缓存反射结果可以提高性能 +3. 避免在热点路径使用反射 +4. 考虑使用接口替代反射 +5. 编译时代码生成是更好的选择 + +最佳实践: +1. 谨慎使用反射,优先考虑类型安全 +2. 进行充分的错误检查 +3. 缓存反射操作的结果 +4. 使用接口实现多态 +5. 考虑性能影响 +6. 提供清晰的文档说明 + +注意事项: +1. 反射会破坏类型安全 +2. 反射代码难以理解和维护 +3. 反射错误只能在运行时发现 +4. 反射会影响代码的可读性 +5. 反射操作可能导致 panic +*/ diff --git a/golang-learning/09-advanced/02-generics.go b/golang-learning/09-advanced/02-generics.go new file mode 100644 index 0000000..c245144 --- /dev/null +++ b/golang-learning/09-advanced/02-generics.go @@ -0,0 +1,561 @@ +/* +02-generics.go - Go 语言泛型详解 + +学习目标: +1. 理解泛型的概念和优势 +2. 掌握类型参数的定义和使用 +3. 学会类型约束的应用 +4. 了解泛型函数和泛型类型 +5. 掌握泛型的最佳实践 + +知识点: +- 泛型的基本概念 +- 类型参数和类型约束 +- 泛型函数和泛型类型 +- 内置约束和自定义约束 +- 类型推断 +- 泛型的实际应用 + +注意:此示例需要 Go 1.18 或更高版本 +*/ + +package main + +import ( + "fmt" + "sort" + "strconv" + "strings" +) + +func main() { + fmt.Println("=== Go 语言泛型详解 ===\\n") + + // 演示泛型的基本概念 + demonstrateGenericsBasics() + + // 演示泛型函数 + demonstrateGenericFunctions() + + // 演示泛型类型 + demonstrateGenericTypes() + + // 演示类型约束 + demonstrateTypeConstraints() + + // 演示内置约束 + demonstrateBuiltinConstraints() + + // 演示泛型的实际应用 + demonstratePracticalApplications() + + // 演示泛型的最佳实践 + demonstrateBestPractices() +} + +// demonstrateGenericsBasics 演示泛型的基本概念 +func demonstrateGenericsBasics() { + fmt.Println("1. 泛型的基本概念:") + + // 泛型的基本概念 + fmt.Printf(" 泛型的基本概念:\\n") + fmt.Printf(" - 泛型允许编写可重用的代码\\n") + fmt.Printf(" - 类型参数在编译时确定具体类型\\n") + fmt.Printf(" - 提供类型安全的抽象\\n") + fmt.Printf(" - Go 1.18+ 支持泛型\\n") + fmt.Printf(" - 减少代码重复和类型断言\\n") + + // 泛型语法 + fmt.Printf(" 泛型语法:\\n") + fmt.Printf(" 函数泛型: func Name[T any](param T) T\\n") + fmt.Printf(" 类型泛型: type Name[T any] struct { field T }\\n") + fmt.Printf(" 约束语法: func Name[T Constraint](param T) T\\n") + + // 简单泛型函数示例 + fmt.Printf(" 简单泛型函数示例:\\n") + + // 使用泛型函数 + fmt.Printf(" 整数最大值: %d\\n", Max(10, 20)) + fmt.Printf(" 浮点数最大值: %.2f\\n", Max(3.14, 2.71)) + fmt.Printf(" 字符串最大值: %s\\n", Max("apple", "banana")) + + // 类型推断 + fmt.Printf(" 类型推断:\\n") + fmt.Printf(" Go 编译器可以自动推断类型参数\\n") + + // 显式指定类型参数 + result1 := Max[int](5, 8) + fmt.Printf(" 显式指定类型: %d\\n", result1) + + // 自动类型推断 + result2 := Max(5, 8) + fmt.Printf(" 自动类型推断: %d\\n", result2) + + // 泛型的优势 + fmt.Printf(" 泛型的优势:\\n") + fmt.Printf(" 1. 类型安全:编译时检查类型\\n") + fmt.Printf(" 2. 代码复用:一套代码支持多种类型\\n") + fmt.Printf(" 3. 性能优化:避免运行时类型断言\\n") + fmt.Printf(" 4. 可读性:减少重复代码\\n") + fmt.Printf(" 5. 维护性:统一的逻辑实现\\n") + + fmt.Println() +} + +// demonstrateGenericFunctions 演示泛型函数 +func demonstrateGenericFunctions() { + fmt.Println("2. 泛型函数:") + + // 基本泛型函数 + fmt.Printf(" 基本泛型函数:\\n") + + // 交换函数 + a, b := 10, 20 + fmt.Printf(" 交换前: a=%d, b=%d\\n", a, b) + a, b = Swap(a, b) + fmt.Printf(" 交换后: a=%d, b=%d\\n", a, b) + + str1, str2 := "hello", "world" + fmt.Printf(" 交换前: str1=%s, str2=%s\\n", str1, str2) + str1, str2 = Swap(str1, str2) + fmt.Printf(" 交换后: str1=%s, str2=%s\\n", str1, str2) + + // 查找函数 + fmt.Printf(" 查找函数:\\n") + + numbers := []int{1, 2, 3, 4, 5} + index := Find(numbers, 3) + fmt.Printf(" 在 %v 中查找 3: 索引 %d\\n", numbers, index) + + words := []string{"apple", "banana", "cherry"} + index = Find(words, "banana") + fmt.Printf(" 在 %v 中查找 banana: 索引 %d\\n", words, index) + + // 映射函数 + fmt.Printf(" 映射函数:\\n") + + // 数字平方 + squares := Map([]int{1, 2, 3, 4, 5}, func(x int) int { + return x * x + }) + fmt.Printf(" 数字平方: %v\\n", squares) + + // 字符串长度 + lengths := Map([]string{"go", "rust", "python"}, func(s string) int { + return len(s) + }) + fmt.Printf(" 字符串长度: %v\\n", lengths) + + // 过滤函数 + fmt.Printf(" 过滤函数:\\n") + + // 过滤偶数 + evens := Filter([]int{1, 2, 3, 4, 5, 6}, func(x int) bool { + return x%2 == 0 + }) + fmt.Printf(" 偶数: %v\\n", evens) + + // 过滤长字符串 + longWords := Filter([]string{"go", "rust", "python", "javascript"}, func(s string) bool { + return len(s) > 4 + }) + fmt.Printf(" 长单词: %v\\n", longWords) + + // 归约函数 + fmt.Printf(" 归约函数:\\n") + + // 求和 + sum := Reduce([]int{1, 2, 3, 4, 5}, 0, func(acc, x int) int { + return acc + x + }) + fmt.Printf(" 数组求和: %d\\n", sum) + + // 字符串连接 + concat := Reduce([]string{"Hello", " ", "World", "!"}, "", func(acc, s string) string { + return acc + s + }) + fmt.Printf(" 字符串连接: %s\\n", concat) + + // 多类型参数函数 + fmt.Printf(" 多类型参数函数:\\n") + + pairs := Zip([]int{1, 2, 3}, []string{"a", "b", "c"}) + fmt.Printf(" 配对结果: %v\\n", pairs) + + fmt.Println() +} + +// demonstrateGenericTypes 演示泛型类型 +func demonstrateGenericTypes() { + fmt.Println("3. 泛型类型:") + + // 泛型栈 + fmt.Printf(" 泛型栈:\\n") + + // 整数栈 + intStack := NewStack[int]() + intStack.Push(1) + intStack.Push(2) + intStack.Push(3) + + fmt.Printf(" 整数栈大小: %d\\n", intStack.Size()) + + for !intStack.IsEmpty() { + value, _ := intStack.Pop() + fmt.Printf(" 弹出: %d\\n", value) + } + + // 字符串栈 + strStack := NewStack[string]() + strStack.Push("first") + strStack.Push("second") + strStack.Push("third") + + fmt.Printf(" 字符串栈内容:\\n") + for !strStack.IsEmpty() { + value, _ := strStack.Pop() + fmt.Printf(" %s\\n", value) + } + + // 泛型队列 + fmt.Printf(" 泛型队列:\\n") + + queue := NewQueue[string]() + queue.Enqueue("first") + queue.Enqueue("second") + queue.Enqueue("third") + + fmt.Printf(" 队列大小: %d\\n", queue.Size()) + + for !queue.IsEmpty() { + value, _ := queue.Dequeue() + fmt.Printf(" 出队: %s\\n", value) + } + + // 泛型映射 + fmt.Printf(" 泛型映射:\\n") + + cache := NewCache[string, int]() + cache.Set("apple", 5) + cache.Set("banana", 3) + cache.Set("cherry", 8) + + if value, ok := cache.Get("apple"); ok { + fmt.Printf(" apple 的值: %d\\n", value) + } + + fmt.Printf(" 缓存大小: %d\\n", cache.Size()) + fmt.Printf(" 所有键: %v\\n", cache.Keys()) + + // 泛型链表 + fmt.Printf(" 泛型链表:\\n") + + list := NewLinkedList[int]() + list.Add(1) + list.Add(2) + list.Add(3) + + fmt.Printf(" 链表大小: %d\\n", list.Size()) + fmt.Printf(" 链表内容: %v\\n", list.ToSlice()) + + if list.Contains(2) { + fmt.Printf(" 链表包含 2\\n") + } + + list.Remove(2) + fmt.Printf(" 删除 2 后: %v\\n", list.ToSlice()) + + fmt.Println() +} + +// demonstrateTypeConstraints 演示类型约束 +func demonstrateTypeConstraints() { + fmt.Println("4. 类型约束:") + + // 基本约束 + fmt.Printf(" 基本约束:\\n") + fmt.Printf(" any: 任意类型\\n") + fmt.Printf(" comparable: 可比较类型\\n") + + // 自定义约束 + fmt.Printf(" 自定义约束:\\n") + + // 数值类型约束 + fmt.Printf(" 数值类型约束:\\n") + fmt.Printf(" 整数求和: %d\\n", Sum([]int{1, 2, 3, 4, 5})) + fmt.Printf(" 浮点数求和: %.2f\\n", Sum([]float64{1.1, 2.2, 3.3})) + + // 有序类型约束 + fmt.Printf(" 有序类型约束:\\n") + + intSlice := []int{3, 1, 4, 1, 5, 9} + fmt.Printf(" 排序前: %v\\n", intSlice) + SortSlice(intSlice) + fmt.Printf(" 排序后: %v\\n", intSlice) + + strSlice := []string{"banana", "apple", "cherry"} + fmt.Printf(" 排序前: %v\\n", strSlice) + SortSlice(strSlice) + fmt.Printf(" 排序后: %v\\n", strSlice) + + // 字符串化约束 + fmt.Printf(" 字符串化约束:\\n") + + fmt.Printf(" 整数转字符串: %s\\n", ToString(42)) + fmt.Printf(" 浮点数转字符串: %s\\n", ToString(3.14)) + fmt.Printf(" 布尔值转字符串: %s\\n", ToString(true)) + + // 接口约束 + fmt.Printf(" 接口约束:\\n") + + shapes := []Shape{ + Rectangle{Width: 5, Height: 3}, + Circle{Radius: 4}, + } + + totalArea := CalculateTotalArea(shapes) + fmt.Printf(" 总面积: %.2f\\n", totalArea) + + // 类型集合约束 + fmt.Printf(" 类型集合约束:\\n") + + fmt.Printf(" 整数绝对值: %d\\n", Abs(-42)) + fmt.Printf(" 浮点数绝对值: %.2f\\n", Abs(-3.14)) + + fmt.Println() +} + +// demonstrateBuiltinConstraints 演示内置约束 +func demonstrateBuiltinConstraints() { + fmt.Println("5. 内置约束:") + + // comparable 约束 + fmt.Printf(" comparable 约束:\\n") + + // 可比较类型的相等检查 + fmt.Printf(" 整数相等: %t\\n", Equal(5, 5)) + fmt.Printf(" 字符串相等: %t\\n", Equal("hello", "hello")) + fmt.Printf(" 布尔值相等: %t\\n", Equal(true, false)) + + // 可比较类型的去重 + duplicateInts := []int{1, 2, 2, 3, 3, 3, 4} + uniqueInts := Unique(duplicateInts) + fmt.Printf(" 整数去重: %v -> %v\\n", duplicateInts, uniqueInts) + + duplicateStrs := []string{"a", "b", "b", "c", "c", "c"} + uniqueStrs := Unique(duplicateStrs) + fmt.Printf(" 字符串去重: %v -> %v\\n", duplicateStrs, uniqueStrs) + + // any 约束 + fmt.Printf(" any 约束:\\n") + + // 任意类型的容器 + container := NewContainer[any]() + container.Add(42) + container.Add("hello") + container.Add(3.14) + container.Add(true) + + fmt.Printf(" 容器大小: %d\\n", container.Size()) + fmt.Printf(" 容器内容:\\n") + for i := 0; i < container.Size(); i++ { + item := container.Get(i) + fmt.Printf(" [%d]: %v (%T)\\n", i, item, item) + } + + // 类型断言与泛型 + fmt.Printf(" 类型断言与泛型:\\n") + + values := []any{42, "hello", 3.14, true} + + // 提取特定类型 + strings := ExtractType[string](values) + fmt.Printf(" 提取字符串: %v\\n", strings) + + numbers := ExtractType[int](values) + fmt.Printf(" 提取整数: %v\\n", numbers) + + fmt.Println() +} + +// demonstratePracticalApplications 演示泛型的实际应用 +func demonstratePracticalApplications() { + fmt.Println("6. 泛型的实际应用:") + + // 应用1: 泛型数据结构 + fmt.Printf(" 应用1 - 泛型数据结构:\\n") + + // 优先队列 + pq := NewPriorityQueue[int]() + pq.Push(3, 3) + pq.Push(1, 1) + pq.Push(4, 4) + pq.Push(2, 2) + + fmt.Printf(" 优先队列出队顺序:\\n") + for !pq.IsEmpty() { + item, priority := pq.Pop() + fmt.Printf(" 项目: %v, 优先级: %d\\n", item, priority) + } + + // 应用2: 泛型算法 + fmt.Printf(" 应用2 - 泛型算法:\\n") + + // 二分查找 + sortedInts := []int{1, 3, 5, 7, 9, 11, 13} + index := BinarySearch(sortedInts, 7) + fmt.Printf(" 二分查找 7 在 %v 中的位置: %d\\n", sortedInts, index) + + sortedStrs := []string{"apple", "banana", "cherry", "date"} + index = BinarySearch(sortedStrs, "cherry") + fmt.Printf(" 二分查找 cherry 在 %v 中的位置: %d\\n", sortedStrs, index) + + // 应用3: 泛型工具函数 + fmt.Printf(" 应用3 - 泛型工具函数:\\n") + + // 切片操作 + original := []int{1, 2, 3, 4, 5} + + // 反转 + reversed := Reverse(original) + fmt.Printf(" 反转: %v -> %v\\n", original, reversed) + + // 分块 + chunks := Chunk(original, 2) + fmt.Printf(" 分块(大小2): %v -> %v\\n", original, chunks) + + // 去重并排序 + unsorted := []int{3, 1, 4, 1, 5, 9, 2, 6, 5} + uniqueSorted := UniqueAndSort(unsorted) + fmt.Printf(" 去重排序: %v -> %v\\n", unsorted, uniqueSorted) + + // 应用4: 泛型缓存 + fmt.Printf(" 应用4 - 泛型缓存:\\n") + + lruCache := NewLRUCache[string, string](3) + lruCache.Put("a", "apple") + lruCache.Put("b", "banana") + lruCache.Put("c", "cherry") + + fmt.Printf(" 缓存状态: %v\\n", lruCache.Keys()) + + // 访问会更新顺序 + if value, ok := lruCache.Get("a"); ok { + fmt.Printf(" 获取 a: %s\\n", value) + } + + // 添加新项会淘汰最久未使用的 + lruCache.Put("d", "date") + fmt.Printf(" 添加 d 后: %v\\n", lruCache.Keys()) + + // 应用5: 泛型验证器 + fmt.Printf(" 应用5 - 泛型验证器:\\n") + + validator := NewValidator[User]() + + // 添加验证规则 + validator.AddRule(\"name\", func(u User) bool { + return len(u.Name) > 0 + }, \"姓名不能为空\") + + validator.AddRule(\"age\", func(u User) bool { + return u.Age >= 0 && u.Age <= 150 + }, \"年龄必须在0-150之间\") + + validator.AddRule(\"email\", func(u User) bool { + return strings.Contains(u.Email, \"@\") + }, \"邮箱格式不正确\") + + // 验证用户 + validUser := User{Name: \"Alice\", Age: 25, Email: \"alice@example.com\"} + invalidUser := User{Name: \"\", Age: -5, Email: \"invalid\"} + + if errors := validator.Validate(validUser); len(errors) == 0 { + fmt.Printf(" 有效用户验证通过\\n") + } else { + fmt.Printf(" 有效用户验证失败: %v\\n", errors) + } + + if errors := validator.Validate(invalidUser); len(errors) > 0 { + fmt.Printf(" 无效用户验证失败: %v\\n", errors) + } + + fmt.Println() +} + +// demonstrateBestPractices 演示泛型的最佳实践 +func demonstrateBestPractices() { + fmt.Println("7. 泛型的最佳实践:") + + // 最佳实践原则 + fmt.Printf(" 最佳实践原则:\\n") + fmt.Printf(" 1. 优先使用具体类型,必要时才使用泛型\\n") + fmt.Printf(" 2. 使用有意义的类型参数名称\\n") + fmt.Printf(" 3. 合理使用类型约束\\n") + fmt.Printf(" 4. 避免过度泛型化\\n") + fmt.Printf(" 5. 考虑编译时间和代码复杂度\\n") + + // 命名约定 + fmt.Printf(" 命名约定:\\n") + fmt.Printf(" - T: 通用类型参数\\n") + fmt.Printf(" - K, V: 键值对类型\\n") + fmt.Printf(" - E: 元素类型\\n") + fmt.Printf(" - R: 结果类型\\n") + fmt.Printf(" - 使用描述性名称: TKey, TValue, TElement\\n") + + // 约束设计 + fmt.Printf(" 约束设计:\\n") + fmt.Printf(" - 使用最小必要约束\\n") + fmt.Printf(" - 优先使用内置约束\\n") + fmt.Printf(" - 自定义约束要有明确语义\\n") + fmt.Printf(" - 避免过于复杂的约束\\n") + + // 性能考虑 + fmt.Printf(" 性能考虑:\\n") + + // 泛型 vs 接口性能对比 + fmt.Printf(" 泛型 vs 接口性能对比:\\n") + + // 泛型版本 + start := time.Now() + genericSum := 0 + for i := 0; i < 1000000; i++ { + genericSum = Add(genericSum, 1) + } + genericTime := time.Since(start) + + // 接口版本 + start = time.Now() + var interfaceSum Addable = IntValue(0) + for i := 0; i < 1000000; i++ { + interfaceSum = interfaceSum.Add(IntValue(1)) + } + interfaceTime := time.Since(start) + + fmt.Printf(" 泛型版本耗时: %v\\n", genericTime) + fmt.Printf(" 接口版本耗时: %v\\n", interfaceTime) + fmt.Printf(" 性能提升: %.2fx\\n", float64(interfaceTime)/float64(genericTime)) + + // 何时使用泛型 + fmt.Printf(" 何时使用泛型:\\n") + fmt.Printf(" ✓ 数据结构实现\\n") + fmt.Printf(" ✓ 算法函数\\n") + fmt.Printf(" ✓ 工具函数\\n") + fmt.Printf(" ✓ 类型安全的容器\\n") + fmt.Printf(" ✓ 减少代码重复\\n") + fmt.Printf("\\n") + fmt.Printf(" ✗ 简单的业务逻辑\\n") + fmt.Printf(" ✗ 只有一种类型的场景\\n") + fmt.Printf(" ✗ 过度抽象的设计\\n") + fmt.Printf(" ✗ 性能敏感且类型固定的代码\\n") + + // 迁移策略 + fmt.Printf(" 迁移策略:\\n") + fmt.Printf(" 1. 识别重复代码模式\\n") + fmt.Printf(" 2. 从简单场景开始\\n") + fmt.Printf(" 3. 逐步替换类型断言\\n") + fmt.Printf(" 4. 保持向后兼容\\n") + fmt.Printf(" 5. 充分测试泛型代码\\n") + + fmt.Println() +} \ No newline at end of file diff --git a/golang-learning/09-advanced/03-context.go b/golang-learning/09-advanced/03-context.go new file mode 100644 index 0000000..c4ddda7 --- /dev/null +++ b/golang-learning/09-advanced/03-context.go @@ -0,0 +1,765 @@ +/* +03-context.go - Go 语言 Context 包详解 +学习目标: +1. 理解 Context 的概念和作用 +2. 掌握 Context 的创建和使用方法 +3. 学会在并发程序中传递取消信号 +4. 了解 Context 的超时和截止时间机制 +5. 掌握 Context 的最佳实践 +知识点: +- Context 接口和实现 +- WithCancel, WithTimeout, WithDeadline +- WithValue 传递请求范围的数据 +- Context 在 HTTP 服务中的应用 +- Context 的传播和继承 +- 避免 Context 的常见陷阱 +*/ + +package main + +import ( + "context" + "fmt" + "net/http" + "sync" + "time" +) + +func main() { + fmt.Println("=== Go 语言 Context 包详解 ===\n") + + // 演示 Context 的基本概念 + demonstrateContextBasics() + + // 演示取消 Context + demonstrateCancellation() + + // 演示超时 Context + demonstrateTimeout() + + // 演示截止时间 Context + demonstrateDeadline() + + // 演示 Context 传递值 + demonstrateWithValue() + + // 演示 Context 在并发中的应用 + demonstrateConcurrencyWithContext() + + // 演示 Context 在 HTTP 中的应用 + demonstrateHTTPContext() + + // 演示 Context 的最佳实践 + demonstrateContextBestPractices() +} + +// demonstrateContextBasics 演示 Context 的基本概念 +func demonstrateContextBasics() { + fmt.Println("1. Context 的基本概念:") + + // Context 的概念 + fmt.Printf(" Context 的概念:\n") + fmt.Printf(" - Context 是 Go 语言中用于传递请求范围数据的标准方式\n") + fmt.Printf(" - 提供取消信号、超时控制和请求范围值传递\n") + fmt.Printf(" - 在 goroutine 之间传递取消信号和截止时间\n") + fmt.Printf(" - 避免 goroutine 泄漏和资源浪费\n") + + // Context 接口 + fmt.Printf(" Context 接口:\n") + fmt.Printf(" type Context interface {\n") + fmt.Printf(" Deadline() (deadline time.Time, ok bool)\n") + fmt.Printf(" Done() <-chan struct{}\n") + fmt.Printf(" Err() error\n") + fmt.Printf(" Value(key interface{}) interface{}\n") + fmt.Printf(" }\n") + + // 创建根 Context + fmt.Printf(" 创建根 Context:\n") + + // Background Context + bgCtx := context.Background() + fmt.Printf(" Background Context: %v\n", bgCtx) + fmt.Printf(" - 通常用作根 Context\n") + fmt.Printf(" - 永远不会被取消,没有值,没有截止时间\n") + + // TODO Context + todoCtx := context.TODO() + fmt.Printf(" TODO Context: %v\n", todoCtx) + fmt.Printf(" - 当不确定使用哪个 Context 时使用\n") + fmt.Printf(" - 通常在重构时作为占位符\n") + + // Context 的方法 + fmt.Printf(" Context 的方法:\n") + deadline, ok := bgCtx.Deadline() + fmt.Printf(" Deadline(): %v, %t (是否有截止时间)\n", deadline, ok) + fmt.Printf(" Done(): %v (取消通道)\n", bgCtx.Done()) + fmt.Printf(" Err(): %v (错误信息)\n", bgCtx.Err()) + fmt.Printf(" Value(key): %v (获取值)\n", bgCtx.Value("key")) + + fmt.Println() +} + +// demonstrateCancellation 演示取消 Context +func demonstrateCancellation() { + fmt.Println("2. 取消 Context:") + + // WithCancel 的使用 + fmt.Printf(" WithCancel 的使用:\n") + fmt.Printf(" ctx, cancel := context.WithCancel(context.Background())\n") + fmt.Printf(" defer cancel() // 确保释放资源\n") + + // 创建可取消的 Context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // 启动一个 goroutine 监听取消信号 + fmt.Printf(" 启动监听取消信号的 goroutine:\n") + go func() { + select { + case <-ctx.Done(): + fmt.Printf(" goroutine 收到取消信号: %v\n", ctx.Err()) + case <-time.After(5 * time.Second): + fmt.Printf(" goroutine 超时退出\n") + } + }() + + // 模拟一些工作 + time.Sleep(100 * time.Millisecond) + fmt.Printf(" 执行取消操作...\n") + cancel() // 发送取消信号 + + // 等待 goroutine 处理取消信号 + time.Sleep(100 * time.Millisecond) + + // 检查 Context 状态 + fmt.Printf(" Context 状态:\n") + fmt.Printf(" Done(): %v\n", ctx.Done() != nil) + fmt.Printf(" Err(): %v\n", ctx.Err()) + + // 演示取消传播 + fmt.Printf(" 取消传播:\n") + parentCtx, parentCancel := context.WithCancel(context.Background()) + childCtx, childCancel := context.WithCancel(parentCtx) + defer parentCancel() + defer childCancel() + + // 取消父 Context + parentCancel() + + // 检查子 Context 是否也被取消 + select { + case <-childCtx.Done(): + fmt.Printf(" 子 Context 也被取消了: %v\n", childCtx.Err()) + case <-time.After(100 * time.Millisecond): + fmt.Printf(" 子 Context 没有被取消\n") + } + + fmt.Println() +} + +// demonstrateTimeout 演示超时 Context +func demonstrateTimeout() { + fmt.Println("3. 超时 Context:") + + // WithTimeout 的使用 + fmt.Printf(" WithTimeout 的使用:\n") + fmt.Printf(" ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n") + fmt.Printf(" defer cancel()\n") + + // 创建超时 Context + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel() + + // 模拟长时间运行的操作 + fmt.Printf(" 模拟长时间操作:\n") + start := time.Now() + + select { + case <-time.After(500 * time.Millisecond): + fmt.Printf(" 操作完成,耗时: %v\n", time.Since(start)) + case <-ctx.Done(): + fmt.Printf(" 操作被超时取消,耗时: %v,错误: %v\n", time.Since(start), ctx.Err()) + } + + // 演示超时处理函数 + fmt.Printf(" 超时处理函数示例:\n") + result, err := doWorkWithTimeout(300 * time.Millisecond) + if err != nil { + fmt.Printf(" 工作超时: %v\n", err) + } else { + fmt.Printf(" 工作完成: %s\n", result) + } + + result, err = doWorkWithTimeout(100 * time.Millisecond) + if err != nil { + fmt.Printf(" 工作超时: %v\n", err) + } else { + fmt.Printf(" 工作完成: %s\n", result) + } + + // 演示 HTTP 请求超时 + fmt.Printf(" HTTP 请求超时示例:\n") + err = makeHTTPRequestWithTimeout("https://httpbin.org/delay/1", 500*time.Millisecond) + if err != nil { + fmt.Printf(" HTTP 请求失败: %v\n", err) + } else { + fmt.Printf(" HTTP 请求成功\n") + } + + fmt.Println() +} + +// demonstrateDeadline 演示截止时间 Context +func demonstrateDeadline() { + fmt.Println("4. 截止时间 Context:") + + // WithDeadline 的使用 + fmt.Printf(" WithDeadline 的使用:\n") + deadline := time.Now().Add(200 * time.Millisecond) + fmt.Printf(" deadline := time.Now().Add(200 * time.Millisecond)\n") + fmt.Printf(" ctx, cancel := context.WithDeadline(context.Background(), deadline)\n") + + ctx, cancel := context.WithDeadline(context.Background(), deadline) + defer cancel() + + // 检查截止时间 + ctxDeadline, ok := ctx.Deadline() + fmt.Printf(" Context 截止时间:\n") + fmt.Printf(" 截止时间: %v\n", ctxDeadline) + fmt.Printf(" 有截止时间: %t\n", ok) + fmt.Printf(" 距离截止时间: %v\n", time.Until(ctxDeadline)) + + // 等待截止时间到达 + fmt.Printf(" 等待截止时间到达:\n") + start := time.Now() + <-ctx.Done() + fmt.Printf(" Context 在 %v 后被取消,错误: %v\n", time.Since(start), ctx.Err()) + + // 演示截止时间检查 + fmt.Printf(" 截止时间检查示例:\n") + checkDeadline(context.Background()) + + deadlineCtx, deadlineCancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond)) + defer deadlineCancel() + checkDeadline(deadlineCtx) + + fmt.Println() +} + +// demonstrateWithValue 演示 Context 传递值 +func demonstrateWithValue() { + fmt.Println("5. Context 传递值:") + + // WithValue 的使用 + fmt.Printf(" WithValue 的使用:\n") + fmt.Printf(" ctx := context.WithValue(context.Background(), \\\"userID\\\", 12345)\n") + + // 创建带值的 Context + ctx := context.WithValue(context.Background(), "userID", 12345) + ctx = context.WithValue(ctx, "requestID", "req-abc-123") + ctx = context.WithValue(ctx, "traceID", "trace-xyz-789") + + // 获取值 + fmt.Printf(" 获取 Context 中的值:\n") + if userID := ctx.Value("userID"); userID != nil { + fmt.Printf(" 用户ID: %v\n", userID) + } + if requestID := ctx.Value("requestID"); requestID != nil { + fmt.Printf(" 请求ID: %v\n", requestID) + } + if traceID := ctx.Value("traceID"); traceID != nil { + fmt.Printf(" 追踪ID: %v\n", traceID) + } + + // 值不存在的情况 + if sessionID := ctx.Value("sessionID"); sessionID != nil { + fmt.Printf(" 会话ID: %v\n", sessionID) + } else { + fmt.Printf(" 会话ID: 不存在\n") + } + + // 演示类型安全的键 + fmt.Printf(" 类型安全的键:\n") + type contextKey string + const ( + userIDKey contextKey = "userID" + requestIDKey contextKey = "requestID" + ) + + safeCtx := context.WithValue(context.Background(), userIDKey, 67890) + safeCtx = context.WithValue(safeCtx, requestIDKey, "req-def-456") + + // 使用类型安全的方式获取值 + if userID := safeCtx.Value(userIDKey); userID != nil { + fmt.Printf(" 安全获取用户ID: %v\n", userID) + } + + // 演示值的传播 + fmt.Printf(" 值的传播:\n") + processRequest(ctx) + + // 演示值的最佳实践 + fmt.Printf(" 值的最佳实践:\n") + fmt.Printf(" 1. 只存储请求范围的数据\n") + fmt.Printf(" 2. 使用类型安全的键\n") + fmt.Printf(" 3. 不要存储可选参数\n") + fmt.Printf(" 4. 键应该是不可导出的\n") + + fmt.Println() +} + +// demonstrateConcurrencyWithContext 演示 Context 在并发中的应用 +func demonstrateConcurrencyWithContext() { + fmt.Println("6. Context 在并发中的应用:") + + // 演示工作池模式 + fmt.Printf(" 工作池模式:\n") + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // 创建工作通道 + jobs := make(chan int, 10) + results := make(chan string, 10) + + // 启动工作者 + var wg sync.WaitGroup + for i := 0; i < 3; i++ { + wg.Add(1) + go worker(ctx, i, jobs, results, &wg) + } + + // 发送工作 + go func() { + for i := 1; i <= 8; i++ { + select { + case jobs <- i: + fmt.Printf(" 发送工作 %d\n", i) + case <-ctx.Done(): + fmt.Printf(" 停止发送工作: %v\n", ctx.Err()) + close(jobs) + return + } + time.Sleep(100 * time.Millisecond) + } + close(jobs) + }() + + // 收集结果 + go func() { + wg.Wait() + close(results) + }() + + // 打印结果 + fmt.Printf(" 工作结果:\n") + for result := range results { + fmt.Printf(" %s\n", result) + } + + // 演示扇出扇入模式 + fmt.Printf(" 扇出扇入模式:\n") + fanOutFanIn() + + fmt.Println() +} + +// demonstrateHTTPContext 演示 Context 在 HTTP 中的应用 +func demonstrateHTTPContext() { + fmt.Println("7. Context 在 HTTP 中的应用:") + + // HTTP 请求中的 Context + fmt.Printf(" HTTP 请求中的 Context:\n") + fmt.Printf(" - 每个 HTTP 请求都有一个关联的 Context\n") + fmt.Printf(" - 可以通过 r.Context() 获取\n") + fmt.Printf(" - 请求取消时 Context 也会被取消\n") + + // 模拟 HTTP 处理器 + fmt.Printf(" HTTP 处理器示例:\n") + + // 创建模拟请求 + req, _ := http.NewRequest("GET", "/api/data", nil) + + // 添加超时 + ctx, cancel := context.WithTimeout(req.Context(), 1*time.Second) + defer cancel() + req = req.WithContext(ctx) + + // 模拟处理请求 + fmt.Printf(" 处理请求...\n") + handleRequest(req) + + // 演示中间件模式 + fmt.Printf(" 中间件模式:\n") + fmt.Printf(" func middleware(next http.Handler) http.Handler {\n") + fmt.Printf(" return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n") + fmt.Printf(" ctx := context.WithValue(r.Context(), \\\"requestID\\\", generateID())\n") + fmt.Printf(" next.ServeHTTP(w, r.WithContext(ctx))\n") + fmt.Printf(" })\n") + fmt.Printf(" }\n") + + // 演示数据库查询超时 + fmt.Printf(" 数据库查询超时:\n") + queryCtx, queryCancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer queryCancel() + + err := queryDatabase(queryCtx, "SELECT * FROM users WHERE id = ?", 123) + if err != nil { + fmt.Printf(" 数据库查询失败: %v\n", err) + } else { + fmt.Printf(" 数据库查询成功\n") + } + + fmt.Println() +} + +// demonstrateContextBestPractices 演示 Context 的最佳实践 +func demonstrateContextBestPractices() { + fmt.Println("8. Context 的最佳实践:") + + // 最佳实践列表 + fmt.Printf(" Context 的最佳实践:\n") + fmt.Printf(" 1. 不要将 Context 存储在结构体中\n") + fmt.Printf(" 2. Context 应该作为函数的第一个参数\n") + fmt.Printf(" 3. 不要传递 nil Context,使用 context.TODO()\n") + fmt.Printf(" 4. Context.Value 只用于请求范围的数据\n") + fmt.Printf(" 5. 使用 defer cancel() 确保资源释放\n") + fmt.Printf(" 6. 不要忽略 Context 的取消信号\n") + fmt.Printf(" 7. 使用类型安全的键\n") + fmt.Printf(" 8. 避免在 Context 中存储可选参数\n") + + // 正确的函数签名 + fmt.Printf(" 正确的函数签名:\n") + fmt.Printf(" func DoSomething(ctx context.Context, arg string) error\n") + fmt.Printf(" func (s *Service) Process(ctx context.Context, data []byte) (*Result, error)\n") + + // 错误的用法 + fmt.Printf(" 避免的错误用法:\n") + fmt.Printf(" // 错误:不要在结构体中存储 Context\n") + fmt.Printf(" type Server struct {\n") + fmt.Printf(" ctx context.Context // 错误\n") + fmt.Printf(" }\n") + fmt.Printf(" \n") + fmt.Printf(" // 错误:不要传递 nil Context\n") + fmt.Printf(" DoSomething(nil, \\\"data\\\") // 错误\n") + + // 正确的用法 + fmt.Printf(" 正确的用法示例:\n") + ctx := context.Background() + + // 正确的取消处理 + processWithCancel(ctx) + + // 正确的超时处理 + processWithTimeout(ctx) + + // 正确的值传递 + processWithValue(ctx) + + // Context 的性能考虑 + fmt.Printf(" 性能考虑:\n") + fmt.Printf(" 1. Context.Value 的查找是 O(n) 的\n") + fmt.Printf(" 2. 避免在热路径中频繁创建 Context\n") + fmt.Printf(" 3. 合理使用 Context 的层次结构\n") + fmt.Printf(" 4. 及时调用 cancel 函数释放资源\n") + + // 常见陷阱 + fmt.Printf(" 常见陷阱:\n") + fmt.Printf(" 1. 忘记调用 cancel 函数导致资源泄漏\n") + fmt.Printf(" 2. 在循环中创建过多的 Context\n") + fmt.Printf(" 3. 将 Context 用作可选参数的容器\n") + fmt.Printf(" 4. 不检查 Context 的取消信号\n") + + fmt.Println() +} + +// ========== 辅助函数 ========== + +// doWorkWithTimeout 带超时的工作函数 +func doWorkWithTimeout(timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + // 模拟工作 + select { + case <-time.After(200 * time.Millisecond): + return "工作完成", nil + case <-ctx.Done(): + return "", ctx.Err() + } +} + +// makeHTTPRequestWithTimeout 带超时的 HTTP 请求 +func makeHTTPRequestWithTimeout(url string, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return err + } + + // 模拟 HTTP 请求 + select { + case <-time.After(300 * time.Millisecond): + return nil // 请求成功 + case <-ctx.Done(): + return ctx.Err() + } +} + +// checkDeadline 检查截止时间 +func checkDeadline(ctx context.Context) { + if deadline, ok := ctx.Deadline(); ok { + fmt.Printf(" 有截止时间: %v,剩余时间: %v\n", deadline, time.Until(deadline)) + } else { + fmt.Printf(" 没有截止时间\n") + } +} + +// processRequest 处理请求 +func processRequest(ctx context.Context) { + if userID := ctx.Value("userID"); userID != nil { + fmt.Printf(" 处理用户 %v 的请求\n", userID) + } + if requestID := ctx.Value("requestID"); requestID != nil { + fmt.Printf(" 请求ID: %v\n", requestID) + } +} + +// worker 工作者函数 +func worker(ctx context.Context, id int, jobs <-chan int, results chan<- string, wg *sync.WaitGroup) { + defer wg.Done() + + for { + select { + case job, ok := <-jobs: + if !ok { + fmt.Printf(" 工作者 %d 退出(通道关闭)\n", id) + return + } + // 模拟工作 + select { + case <-time.After(200 * time.Millisecond): + results <- fmt.Sprintf("工作者 %d 完成工作 %d", id, job) + case <-ctx.Done(): + fmt.Printf(" 工作者 %d 被取消: %v\n", id, ctx.Err()) + return + } + case <-ctx.Done(): + fmt.Printf(" 工作者 %d 被取消: %v\n", id, ctx.Err()) + return + } + } +} + +// fanOutFanIn 扇出扇入模式 +func fanOutFanIn() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + // 输入通道 + input := make(chan int, 5) + + // 启动多个处理器(扇出) + c1 := process(ctx, input) + c2 := process(ctx, input) + c3 := process(ctx, input) + + // 合并结果(扇入) + output := merge(ctx, c1, c2, c3) + + // 发送数据 + go func() { + defer close(input) + for i := 1; i <= 6; i++ { + select { + case input <- i: + case <-ctx.Done(): + return + } + } + }() + + // 收集结果 + for result := range output { + fmt.Printf(" 扇出扇入结果: %s\n", result) + } +} + +// process 处理函数 +func process(ctx context.Context, input <-chan int) <-chan string { + output := make(chan string) + go func() { + defer close(output) + for { + select { + case n, ok := <-input: + if !ok { + return + } + select { + case output <- fmt.Sprintf("处理 %d", n*n): + case <-ctx.Done(): + return + } + case <-ctx.Done(): + return + } + } + }() + return output +} + +// merge 合并多个通道 +func merge(ctx context.Context, channels ...<-chan string) <-chan string { + var wg sync.WaitGroup + output := make(chan string) + + // 为每个输入通道启动一个 goroutine + multiplex := func(c <-chan string) { + defer wg.Done() + for { + select { + case s, ok := <-c: + if !ok { + return + } + select { + case output <- s: + case <-ctx.Done(): + return + } + case <-ctx.Done(): + return + } + } + } + + wg.Add(len(channels)) + for _, c := range channels { + go multiplex(c) + } + + // 等待所有 goroutine 完成 + go func() { + wg.Wait() + close(output) + }() + + return output +} + +// handleRequest 处理 HTTP 请求 +func handleRequest(req *http.Request) { + ctx := req.Context() + + // 模拟处理 + select { + case <-time.After(500 * time.Millisecond): + fmt.Printf(" 请求处理完成\n") + case <-ctx.Done(): + fmt.Printf(" 请求被取消: %v\n", ctx.Err()) + } +} + +// queryDatabase 查询数据库 +func queryDatabase(ctx context.Context, query string, args ...interface{}) error { + // 模拟数据库查询 + select { + case <-time.After(300 * time.Millisecond): + return nil // 查询成功 + case <-ctx.Done(): + return ctx.Err() + } +} + +// processWithCancel 带取消的处理 +func processWithCancel(ctx context.Context) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // 模拟处理 + go func() { + time.Sleep(100 * time.Millisecond) + cancel() // 取消操作 + }() + + select { + case <-time.After(200 * time.Millisecond): + fmt.Printf(" 处理完成\n") + case <-ctx.Done(): + fmt.Printf(" 处理被取消\n") + } +} + +// processWithTimeout 带超时的处理 +func processWithTimeout(ctx context.Context) { + ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) + defer cancel() + + select { + case <-time.After(100 * time.Millisecond): + fmt.Printf(" 处理完成\n") + case <-ctx.Done(): + fmt.Printf(" 处理超时\n") + } +} + +// processWithValue 带值传递的处理 +func processWithValue(ctx context.Context) { + ctx = context.WithValue(ctx, "operation", "process") + + if op := ctx.Value("operation"); op != nil { + fmt.Printf(" 执行操作: %v\n", op) + } +} + +/* +运行这个程序: +go run 03-context.go + +Context 的核心概念: +1. Context 是 Go 语言中处理请求范围数据的标准方式 +2. 提供取消信号、超时控制和值传递功能 +3. 在 goroutine 之间传递取消信号和截止时间 +4. 避免 goroutine 泄漏和资源浪费 + +Context 的主要类型: +1. context.Background(): 根 Context,通常用作起点 +2. context.TODO(): 当不确定使用哪个 Context 时使用 +3. context.WithCancel(): 创建可取消的 Context +4. context.WithTimeout(): 创建带超时的 Context +5. context.WithDeadline(): 创建带截止时间的 Context +6. context.WithValue(): 创建带值的 Context + +Context 的最佳实践: +1. 不要将 Context 存储在结构体中 +2. Context 应该作为函数的第一个参数 +3. 不要传递 nil Context,使用 context.TODO() +4. Context.Value 只用于请求范围的数据 +5. 使用 defer cancel() 确保资源释放 +6. 不要忽略 Context 的取消信号 +7. 使用类型安全的键 +8. 避免在 Context 中存储可选参数 + +Context 的应用场景: +1. HTTP 请求处理 +2. 数据库查询超时 +3. 并发任务协调 +4. 微服务调用链 +5. 长时间运行的任务控制 + +注意事项: +1. Context 是并发安全的 +2. Context 的取消会传播到所有子 Context +3. Context.Value 的查找是 O(n) 的 +4. 及时调用 cancel 函数释放资源 +5. 不要在 Context 中存储可选参数 + +常见错误: +1. 忘记调用 cancel 函数 +2. 将 Context 存储在结构体中 +3. 传递 nil Context +4. 不检查 Context 的取消信号 +5. 在 Context 中存储过多数据 +*/ diff --git a/golang-learning/09-advanced/04-testing.go b/golang-learning/09-advanced/04-testing.go new file mode 100644 index 0000000..d2e99b2 --- /dev/null +++ b/golang-learning/09-advanced/04-testing.go @@ -0,0 +1,919 @@ +/* +04-testing.go - Go 语言测试详解 +学习目标: +1. 理解 Go 语言测试的基本概念 +2. 掌握单元测试的编写方法 +3. 学会基准测试和性能分析 +4. 了解测试覆盖率和测试工具 +5. 掌握测试的最佳实践 +知识点: +- 测试函数的命名和签名 +- testing 包的使用 +- 表驱动测试模式 +- 基准测试和内存分析 +- 测试覆盖率 +- 测试工具和技巧 +*/ + +package main + +import ( + "fmt" + "sort" + "strings" + "time" +) + +// 这个文件演示了如何编写各种类型的测试 +// 注意:实际的测试文件应该以 _test.go 结尾 + +func main() { + fmt.Println("=== Go 语言测试详解 ===\n") + + // 演示测试的基本概念 + demonstrateTestingBasics() + + // 演示单元测试 + demonstrateUnitTesting() + + // 演示表驱动测试 + demonstrateTableDrivenTests() + + // 演示基准测试 + demonstrateBenchmarkTesting() + + // 演示示例测试 + demonstrateExampleTesting() + + // 演示测试工具和技巧 + demonstrateTestingTools() + + // 演示测试的最佳实践 + demonstrateTestingBestPractices() +} + +// demonstrateTestingBasics 演示测试的基本概念 +func demonstrateTestingBasics() { + fmt.Println("1. 测试的基本概念:") + + // 测试的重要性 + fmt.Printf(" 测试的重要性:\n") + fmt.Printf(" - 验证代码的正确性\n") + fmt.Printf(" - 防止回归错误\n") + fmt.Printf(" - 提高代码质量\n") + fmt.Printf(" - 便于重构和维护\n") + fmt.Printf(" - 作为代码的文档\n") + + // Go 测试的特点 + fmt.Printf(" Go 测试的特点:\n") + fmt.Printf(" - 内置测试框架\n") + fmt.Printf(" - 简单的测试语法\n") + fmt.Printf(" - 强大的工具支持\n") + fmt.Printf(" - 并行测试支持\n") + fmt.Printf(" - 基准测试集成\n") + + // 测试文件的组织 + fmt.Printf(" 测试文件的组织:\n") + fmt.Printf(" 源文件: calculator.go\n") + fmt.Printf(" 测试文件: calculator_test.go\n") + fmt.Printf(" \n") + fmt.Printf(" 源文件: utils.go\n") + fmt.Printf(" 测试文件: utils_test.go\n") + + // 测试函数的类型 + fmt.Printf(" 测试函数的类型:\n") + fmt.Printf(" 1. 单元测试: func TestXxx(*testing.T)\n") + fmt.Printf(" 2. 基准测试: func BenchmarkXxx(*testing.B)\n") + fmt.Printf(" 3. 示例测试: func ExampleXxx()\n") + fmt.Printf(" 4. 模糊测试: func FuzzXxx(*testing.F) (Go 1.18+)\n") + + // 运行测试的命令 + fmt.Printf(" 运行测试的命令:\n") + fmt.Printf(" go test # 运行当前包的测试\n") + fmt.Printf(" go test ./... # 运行所有子包的测试\n") + fmt.Printf(" go test -v # 详细输出\n") + fmt.Printf(" go test -run TestAdd # 运行特定测试\n") + fmt.Printf(" go test -bench=. # 运行基准测试\n") + fmt.Printf(" go test -cover # 显示覆盖率\n") + + fmt.Println() +} + +// demonstrateUnitTesting 演示单元测试 +func demonstrateUnitTesting() { + fmt.Println("2. 单元测试:") + + // 基本测试函数 + fmt.Printf(" 基本测试函数:\n") + fmt.Printf(" func TestAdd(t *testing.T) {\n") + fmt.Printf(" result := Add(2, 3)\n") + fmt.Printf(" expected := 5\n") + fmt.Printf(" if result != expected {\n") + fmt.Printf(" t.Errorf(\\\"Add(2, 3) = %%d; want %%d\\\", result, expected)\n") + fmt.Printf(" }\n") + fmt.Printf(" }\n") + + // 模拟运行测试 + fmt.Printf(" 模拟运行测试:\n") + mockT := &MockT{} + + // 测试 Add 函数 + fmt.Printf(" 测试 Add 函数:\n") + TestAdd(mockT) + if mockT.failed { + fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg) + } else { + fmt.Printf(" ✅ 测试通过\n") + } + + // 测试 Divide 函数 + fmt.Printf(" 测试 Divide 函数:\n") + mockT = &MockT{} + TestDivide(mockT) + if mockT.failed { + fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg) + } else { + fmt.Printf(" ✅ 测试通过\n") + } + + // 测试 IsPrime 函数 + fmt.Printf(" 测试 IsPrime 函数:\n") + mockT = &MockT{} + TestIsPrime(mockT) + if mockT.failed { + fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg) + } else { + fmt.Printf(" ✅ 测试通过\n") + } + + // testing.T 的方法 + fmt.Printf(" testing.T 的方法:\n") + fmt.Printf(" t.Error(args ...) # 记录错误但继续测试\n") + fmt.Printf(" t.Errorf(format, args ...) # 格式化错误信息\n") + fmt.Printf(" t.Fatal(args ...) # 记录错误并停止测试\n") + fmt.Printf(" t.Fatalf(format, args ...) # 格式化致命错误\n") + fmt.Printf(" t.Log(args ...) # 记录日志信息\n") + fmt.Printf(" t.Logf(format, args ...) # 格式化日志信息\n") + fmt.Printf(" t.Skip(args ...) # 跳过测试\n") + fmt.Printf(" t.Helper() # 标记为辅助函数\n") + + fmt.Println() +} + +// demonstrateTableDrivenTests 演示表驱动测试 +func demonstrateTableDrivenTests() { + fmt.Println("3. 表驱动测试:") + + // 表驱动测试的优势 + fmt.Printf(" 表驱动测试的优势:\n") + fmt.Printf(" - 减少重复代码\n") + fmt.Printf(" - 易于添加新的测试用例\n") + fmt.Printf(" - 提高测试的可维护性\n") + fmt.Printf(" - 清晰的测试数据结构\n") + + // 表驱动测试示例 + fmt.Printf(" 表驱动测试示例:\n") + fmt.Printf(" func TestAddTable(t *testing.T) {\n") + fmt.Printf(" tests := []struct {\n") + fmt.Printf(" name string\n") + fmt.Printf(" a, b int\n") + fmt.Printf(" expected int\n") + fmt.Printf(" }{\n") + fmt.Printf(" {\\\"positive\\\", 2, 3, 5},\n") + fmt.Printf(" {\\\"negative\\\", -1, -2, -3},\n") + fmt.Printf(" {\\\"zero\\\", 0, 5, 5},\n") + fmt.Printf(" }\n") + fmt.Printf(" \n") + fmt.Printf(" for _, tt := range tests {\n") + fmt.Printf(" t.Run(tt.name, func(t *testing.T) {\n") + fmt.Printf(" result := Add(tt.a, tt.b)\n") + fmt.Printf(" if result != tt.expected {\n") + fmt.Printf(" t.Errorf(\\\"got %%d, want %%d\\\", result, tt.expected)\n") + fmt.Printf(" }\n") + fmt.Printf(" })\n") + fmt.Printf(" }\n") + fmt.Printf(" }\n") + + // 模拟运行表驱动测试 + fmt.Printf(" 模拟运行表驱动测试:\n") + mockT := &MockT{} + TestAddTable(mockT) + + // 字符串处理的表驱动测试 + fmt.Printf(" 字符串处理的表驱动测试:\n") + mockT = &MockT{} + TestStringOperations(mockT) + + // 错误处理的表驱动测试 + fmt.Printf(" 错误处理的表驱动测试:\n") + mockT = &MockT{} + TestValidateInput(mockT) + + fmt.Println() +} + +// demonstrateBenchmarkTesting 演示基准测试 +func demonstrateBenchmarkTesting() { + fmt.Println("4. 基准测试:") + + // 基准测试的概念 + fmt.Printf(" 基准测试的概念:\n") + fmt.Printf(" - 测量代码的性能\n") + fmt.Printf(" - 比较不同实现的效率\n") + fmt.Printf(" - 识别性能瓶颈\n") + fmt.Printf(" - 验证优化效果\n") + + // 基准测试函数 + fmt.Printf(" 基准测试函数:\n") + fmt.Printf(" func BenchmarkStringConcat(b *testing.B) {\n") + fmt.Printf(" for i := 0; i < b.N; i++ {\n") + fmt.Printf(" _ = \\\"hello\\\" + \\\"world\\\"\n") + fmt.Printf(" }\n") + fmt.Printf(" }\n") + + // 模拟运行基准测试 + fmt.Printf(" 模拟运行基准测试:\n") + + // 字符串连接基准测试 + fmt.Printf(" 字符串连接性能比较:\n") + runBenchmark("StringConcat", BenchmarkStringConcat) + runBenchmark("StringBuffer", BenchmarkStringBuffer) + runBenchmark("StringBuilder", BenchmarkStringBuilder) + + // 排序算法基准测试 + fmt.Printf(" 排序算法性能比较:\n") + runBenchmark("BubbleSort", BenchmarkBubbleSort) + runBenchmark("QuickSort", BenchmarkQuickSort) + runBenchmark("StandardSort", BenchmarkStandardSort) + + // 基准测试的运行参数 + fmt.Printf(" 基准测试的运行参数:\n") + fmt.Printf(" go test -bench=. # 运行所有基准测试\n") + fmt.Printf(" go test -bench=BenchmarkAdd # 运行特定基准测试\n") + fmt.Printf(" go test -bench=. -benchmem # 显示内存分配\n") + fmt.Printf(" go test -bench=. -benchtime=10s # 设置运行时间\n") + fmt.Printf(" go test -bench=. -count=5 # 运行多次\n") + + // 基准测试结果解读 + fmt.Printf(" 基准测试结果解读:\n") + fmt.Printf(" BenchmarkAdd-8 1000000000 0.50 ns/op 0 B/op 0 allocs/op\n") + fmt.Printf(" │ │ │ │ │\n") + fmt.Printf(" │ │ │ │ └─ 每次操作的分配次数\n") + fmt.Printf(" │ │ │ └─ 每次操作分配的字节数\n") + fmt.Printf(" │ │ └─ 每次操作的平均时间\n") + fmt.Printf(" │ └─ 执行次数\n") + fmt.Printf(" └─ 基准测试名称和CPU核心数\n") + + fmt.Println() +} + +// demonstrateExampleTesting 演示示例测试 +func demonstrateExampleTesting() { + fmt.Println("5. 示例测试:") + + // 示例测试的概念 + fmt.Printf(" 示例测试的概念:\n") + fmt.Printf(" - 提供函数使用示例\n") + fmt.Printf(" - 验证输出结果\n") + fmt.Printf(" - 生成文档\n") + fmt.Printf(" - 确保示例代码正确\n") + + // 示例测试函数 + fmt.Printf(" 示例测试函数:\n") + fmt.Printf(" func ExampleAdd() {\n") + fmt.Printf(" result := Add(2, 3)\n") + fmt.Printf(" fmt.Println(result)\n") + fmt.Printf(" // Output: 5\n") + fmt.Printf(" }\n") + + // 运行示例测试 + fmt.Printf(" 运行示例测试:\n") + fmt.Printf(" Add 函数示例:\n") + ExampleAdd() + + fmt.Printf(" Divide 函数示例:\n") + ExampleDivide() + + fmt.Printf(" StringReverse 函数示例:\n") + ExampleStringReverse() + + // 示例测试的特点 + fmt.Printf(" 示例测试的特点:\n") + fmt.Printf(" 1. 函数名以 Example 开头\n") + fmt.Printf(" 2. 使用 // Output: 注释指定期望输出\n") + fmt.Printf(" 3. 可以使用 // Unordered output: 处理无序输出\n") + fmt.Printf(" 4. 会在 go doc 中显示\n") + fmt.Printf(" 5. 可以通过 go test 验证\n") + + fmt.Println() +} + +// demonstrateTestingTools 演示测试工具和技巧 +func demonstrateTestingTools() { + fmt.Println("6. 测试工具和技巧:") + + // 测试覆盖率 + fmt.Printf(" 测试覆盖率:\n") + fmt.Printf(" go test -cover # 显示覆盖率\n") + fmt.Printf(" go test -coverprofile=cover.out # 生成覆盖率文件\n") + fmt.Printf(" go tool cover -html=cover.out # 生成HTML报告\n") + fmt.Printf(" go tool cover -func=cover.out # 显示函数覆盖率\n") + + // 测试辅助函数 + fmt.Printf(" 测试辅助函数:\n") + fmt.Printf(" func assertEqual(t *testing.T, got, want interface{}) {\n") + fmt.Printf(" t.Helper() // 标记为辅助函数\n") + fmt.Printf(" if got != want {\n") + fmt.Printf(" t.Errorf(\\\"got %%v, want %%v\\\", got, want)\n") + fmt.Printf(" }\n") + fmt.Printf(" }\n") + + // 模拟使用辅助函数 + fmt.Printf(" 使用辅助函数:\n") + mockT := &MockT{} + TestWithHelpers(mockT) + if mockT.failed { + fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg) + } else { + fmt.Printf(" ✅ 测试通过\n") + } + + // 子测试 + fmt.Printf(" 子测试:\n") + fmt.Printf(" t.Run(\\\"subtest\\\", func(t *testing.T) {\n") + fmt.Printf(" // 子测试代码\n") + fmt.Printf(" })\n") + + // 并行测试 + fmt.Printf(" 并行测试:\n") + fmt.Printf(" func TestParallel(t *testing.T) {\n") + fmt.Printf(" t.Parallel() // 标记为并行测试\n") + fmt.Printf(" // 测试代码\n") + fmt.Printf(" }\n") + + // 测试设置和清理 + fmt.Printf(" 测试设置和清理:\n") + fmt.Printf(" func TestMain(m *testing.M) {\n") + fmt.Printf(" setup() // 全局设置\n") + fmt.Printf(" code := m.Run()\n") + fmt.Printf(" teardown() // 全局清理\n") + fmt.Printf(" os.Exit(code)\n") + fmt.Printf(" }\n") + + // 跳过测试 + fmt.Printf(" 跳过测试:\n") + fmt.Printf(" if testing.Short() {\n") + fmt.Printf(" t.Skip(\\\"跳过长时间运行的测试\\\")\n") + fmt.Printf(" }\n") + + // 临时目录 + fmt.Printf(" 临时目录:\n") + fmt.Printf(" tmpDir := t.TempDir() // 创建临时目录\n") + fmt.Printf(" // 测试结束后自动清理\n") + + fmt.Println() +} + +// demonstrateTestingBestPractices 演示测试的最佳实践 +func demonstrateTestingBestPractices() { + fmt.Println("7. 测试的最佳实践:") + + // 测试命名 + fmt.Printf(" 测试命名:\n") + fmt.Printf(" 1. 使用描述性的测试名称\n") + fmt.Printf(" 2. 包含被测试的功能和场景\n") + fmt.Printf(" 3. 使用一致的命名约定\n") + fmt.Printf(" \n") + fmt.Printf(" 好的命名:\n") + fmt.Printf(" TestAdd_PositiveNumbers\n") + fmt.Printf(" TestDivide_ByZero_ReturnsError\n") + fmt.Printf(" TestUser_Create_ValidInput\n") + + // 测试组织 + fmt.Printf(" 测试组织:\n") + fmt.Printf(" 1. 每个源文件对应一个测试文件\n") + fmt.Printf(" 2. 按功能分组测试\n") + fmt.Printf(" 3. 使用子测试组织复杂场景\n") + fmt.Printf(" 4. 保持测试的独立性\n") + + // 测试数据 + fmt.Printf(" 测试数据:\n") + fmt.Printf(" 1. 使用表驱动测试处理多个用例\n") + fmt.Printf(" 2. 包含边界条件和异常情况\n") + fmt.Printf(" 3. 使用有意义的测试数据\n") + fmt.Printf(" 4. 避免硬编码的魔法数字\n") + + // 断言 + fmt.Printf(" 断言:\n") + fmt.Printf(" 1. 使用清晰的错误消息\n") + fmt.Printf(" 2. 一个测试只验证一个行为\n") + fmt.Printf(" 3. 优先使用 Errorf 而不是 Error\n") + fmt.Printf(" 4. 在必要时使用 Fatal 停止测试\n") + + // 测试覆盖率 + fmt.Printf(" 测试覆盖率:\n") + fmt.Printf(" 1. 追求高覆盖率但不盲目追求100%%\n") + fmt.Printf(" 2. 关注重要的业务逻辑\n") + fmt.Printf(" 3. 测试边界条件和错误路径\n") + fmt.Printf(" 4. 使用覆盖率工具识别遗漏\n") + + // 性能考虑 + fmt.Printf(" 性能考虑:\n") + fmt.Printf(" 1. 避免在测试中进行复杂计算\n") + fmt.Printf(" 2. 使用并行测试提高速度\n") + fmt.Printf(" 3. 合理使用测试缓存\n") + fmt.Printf(" 4. 避免不必要的外部依赖\n") + + // 测试维护 + fmt.Printf(" 测试维护:\n") + fmt.Printf(" 1. 保持测试代码的简洁\n") + fmt.Printf(" 2. 定期重构测试代码\n") + fmt.Printf(" 3. 及时更新过时的测试\n") + fmt.Printf(" 4. 删除无用的测试\n") + + // 常见陷阱 + fmt.Printf(" 常见陷阱:\n") + fmt.Printf(" 1. 测试依赖外部状态\n") + fmt.Printf(" 2. 测试之间相互依赖\n") + fmt.Printf(" 3. 忽略错误处理的测试\n") + fmt.Printf(" 4. 过度复杂的测试逻辑\n") + fmt.Printf(" 5. 不稳定的测试(flaky tests)\n") + + fmt.Println() +} + +// ========== 被测试的函数 ========== + +// Add 计算两个整数的和 +func Add(a, b int) int { + return a + b +} + +// Divide 计算两个数的商 +func Divide(a, b float64) (float64, error) { + if b == 0 { + return 0, fmt.Errorf("division by zero") + } + return a / b, nil +} + +// IsPrime 判断是否为质数 +func IsPrime(n int) bool { + if n < 2 { + return false + } + for i := 2; i*i <= n; i++ { + if n%i == 0 { + return false + } + } + return true +} + +// StringReverse 反转字符串 +func StringReverse(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// Capitalize 将字符串首字母大写 +func Capitalize(s string) string { + if len(s) == 0 { + return s + } + return strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) +} + +// ValidateInput 验证输入 +func ValidateInput(input string) error { + if len(input) == 0 { + return fmt.Errorf("input cannot be empty") + } + if len(input) > 100 { + return fmt.Errorf("input too long") + } + return nil +} + +// ========== 模拟的 testing.T 和 testing.B ========== + +// MockT 模拟 testing.T 类型 +type MockT struct { + failed bool + errorMsg string + logs []string +} + +func (m *MockT) Error(args ...interface{}) { + m.failed = true + m.errorMsg = fmt.Sprint(args...) +} + +func (m *MockT) Errorf(format string, args ...interface{}) { + m.failed = true + m.errorMsg = fmt.Sprintf(format, args...) +} + +func (m *MockT) Fatal(args ...interface{}) { + m.failed = true + m.errorMsg = fmt.Sprint(args...) +} + +func (m *MockT) Fatalf(format string, args ...interface{}) { + m.failed = true + m.errorMsg = fmt.Sprintf(format, args...) +} + +func (m *MockT) Log(args ...interface{}) { + m.logs = append(m.logs, fmt.Sprint(args...)) +} + +func (m *MockT) Logf(format string, args ...interface{}) { + m.logs = append(m.logs, fmt.Sprintf(format, args...)) +} + +func (m *MockT) Helper() { + // 标记为辅助函数 +} + +func (m *MockT) Run(name string, f func(*MockT)) bool { + fmt.Printf(" 运行子测试: %s\n", name) + subT := &MockT{} + f(subT) + if subT.failed { + fmt.Printf(" ❌ 失败: %s\n", subT.errorMsg) + return false + } else { + fmt.Printf(" ✅ 通过\n") + return true + } +} + +// MockB 模拟 testing.B 类型 +type MockB struct { + N int +} + +func (b *MockB) ResetTimer() {} +func (b *MockB) ReportAllocs() {} + +// ========== 测试函数示例 ========== + +// TestAdd 测试 Add 函数 +func TestAdd(t *MockT) { + result := Add(2, 3) + expected := 5 + if result != expected { + t.Errorf("Add(2, 3) = %d; want %d", result, expected) + } +} + +// TestDivide 测试 Divide 函数 +func TestDivide(t *MockT) { + // 正常情况 + result, err := Divide(10, 2) + if err != nil { + t.Errorf("Divide(10, 2) returned error: %v", err) + return + } + if result != 5 { + t.Errorf("Divide(10, 2) = %f; want 5", result) + } + + // 除零情况 + _, err = Divide(10, 0) + if err == nil { + t.Error("Divide(10, 0) should return error") + } +} + +// TestIsPrime 测试 IsPrime 函数 +func TestIsPrime(t *MockT) { + tests := []struct { + n int + expected bool + }{ + {2, true}, + {3, true}, + {4, false}, + {17, true}, + {25, false}, + {1, false}, + {0, false}, + {-1, false}, + } + + for _, tt := range tests { + result := IsPrime(tt.n) + if result != tt.expected { + t.Errorf("IsPrime(%d) = %t; want %t", tt.n, result, tt.expected) + } + } +} + +// TestAddTable 表驱动测试示例 +func TestAddTable(t *MockT) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 3, 5}, + {"negative numbers", -1, -2, -3}, + {"mixed numbers", -1, 2, 1}, + {"zero", 0, 5, 5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *MockT) { + result := Add(tt.a, tt.b) + if result != tt.expected { + subT.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) + } + }) + } +} + +// TestStringOperations 字符串操作测试 +func TestStringOperations(t *MockT) { + tests := []struct { + name string + input string + expected string + }{ + {"normal string", "hello", "Hello"}, + {"empty string", "", ""}, + {"single char", "a", "A"}, + {"already capitalized", "Hello", "Hello"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *MockT) { + result := Capitalize(tt.input) + if result != tt.expected { + subT.Errorf("Capitalize(%q) = %q; want %q", tt.input, result, tt.expected) + } + }) + } +} + +// TestValidateInput 输入验证测试 +func TestValidateInput(t *MockT) { + tests := []struct { + name string + input string + wantError bool + }{ + {"valid input", "hello", false}, + {"empty input", "", true}, + {"too long input", strings.Repeat("a", 101), true}, + {"max length input", strings.Repeat("a", 100), false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *MockT) { + err := ValidateInput(tt.input) + if (err != nil) != tt.wantError { + subT.Errorf("ValidateInput(%q) error = %v; wantError %t", tt.input, err, tt.wantError) + } + }) + } +} + +// TestWithHelpers 使用辅助函数的测试 +func TestWithHelpers(t *MockT) { + assertEqual := func(t *MockT, got, want interface{}) { + t.Helper() + if got != want { + t.Errorf("got %v, want %v", got, want) + } + } + + // 使用辅助函数进行断言 + assertEqual(t, Add(2, 3), 5) + assertEqual(t, Capitalize("hello"), "Hello") + + result, err := Divide(10, 2) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + assertEqual(t, result, 5.0) +} + +// ========== 基准测试函数 ========== + +// BenchmarkStringConcat 字符串连接基准测试 +func BenchmarkStringConcat(b *MockB) { + for i := 0; i < b.N; i++ { + _ = "hello" + "world" + "golang" + "benchmark" + } +} + +// BenchmarkStringBuffer 字符串缓冲区基准测试 +func BenchmarkStringBuffer(b *MockB) { + for i := 0; i < b.N; i++ { + var buffer strings.Builder + buffer.WriteString("hello") + buffer.WriteString("world") + buffer.WriteString("golang") + buffer.WriteString("benchmark") + _ = buffer.String() + } +} + +// BenchmarkStringBuilder 字符串构建器基准测试 +func BenchmarkStringBuilder(b *MockB) { + for i := 0; i < b.N; i++ { + parts := []string{"hello", "world", "golang", "benchmark"} + _ = strings.Join(parts, "") + } +} + +// BenchmarkBubbleSort 冒泡排序基准测试 +func BenchmarkBubbleSort(b *MockB) { + data := []int{64, 34, 25, 12, 22, 11, 90} + for i := 0; i < b.N; i++ { + bubbleSort(append([]int(nil), data...)) + } +} + +// BenchmarkQuickSort 快速排序基准测试 +func BenchmarkQuickSort(b *MockB) { + data := []int{64, 34, 25, 12, 22, 11, 90} + for i := 0; i < b.N; i++ { + quickSort(append([]int(nil), data...)) + } +} + +// BenchmarkStandardSort 标准排序基准测试 +func BenchmarkStandardSort(b *MockB) { + data := []int{64, 34, 25, 12, 22, 11, 90} + for i := 0; i < b.N; i++ { + sort.Ints(append([]int(nil), data...)) + } +} + +// ========== 示例测试函数 ========== + +// ExampleAdd Add 函数的示例 +func ExampleAdd() { + result := Add(2, 3) + fmt.Println(result) + // Output: 5 +} + +// ExampleDivide Divide 函数的示例 +func ExampleDivide() { + result, err := Divide(10, 2) + if err != nil { + fmt.Printf("Error: %v", err) + return + } + fmt.Printf("%.1f", result) + // Output: 5.0 +} + +// ExampleStringReverse StringReverse 函数的示例 +func ExampleStringReverse() { + result := StringReverse("hello") + fmt.Println(result) + // Output: olleh +} + +// ========== 辅助函数 ========== + +// runBenchmark 运行基准测试 +func runBenchmark(name string, benchFunc func(*MockB)) { + b := &MockB{N: 1000000} + start := time.Now() + benchFunc(b) + duration := time.Since(start) + nsPerOp := duration.Nanoseconds() / int64(b.N) + fmt.Printf(" %s: %d ns/op\n", name, nsPerOp) +} + +// bubbleSort 冒泡排序 +func bubbleSort(arr []int) { + n := len(arr) + for i := 0; i < n-1; i++ { + for j := 0; j < n-i-1; j++ { + if arr[j] > arr[j+1] { + arr[j], arr[j+1] = arr[j+1], arr[j] + } + } + } +} + +// quickSort 快速排序 +func quickSort(arr []int) { + if len(arr) < 2 { + return + } + + pivot := partition(arr) + quickSort(arr[:pivot]) + quickSort(arr[pivot+1:]) +} + +// partition 分区函数 +func partition(arr []int) int { + pivot := arr[len(arr)-1] + i := -1 + + for j := 0; j < len(arr)-1; j++ { + if arr[j] <= pivot { + i++ + arr[i], arr[j] = arr[j], arr[i] + } + } + + arr[i+1], arr[len(arr)-1] = arr[len(arr)-1], arr[i+1] + return i + 1 +} + +/* +运行这个程序: +go run 04-testing.go + +实际测试文件示例(calculator_test.go): +package calculator + +import "testing" + +func TestAdd(t *testing.T) { + result := Add(2, 3) + expected := 5 + if result != expected { + t.Errorf("Add(2, 3) = %d; want %d", result, expected) + } +} + +func TestAddTable(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 3, 5}, + {"negative numbers", -1, -2, -3}, + {"mixed numbers", -1, 2, 1}, + {"zero", 0, 5, 5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("got %d, want %d", result, tt.expected) + } + }) + } +} + +func BenchmarkStringConcat(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = "hello" + "world" + } +} + +func ExampleAdd() { + result := Add(2, 3) + fmt.Println(result) + // Output: 5 +} + +运行测试命令: +go test # 运行当前包的所有测试 +go test -v # 详细输出 +go test -run TestAdd # 运行特定测试 +go test -bench=. # 运行基准测试 +go test -cover # 显示覆盖率 +go test ./... # 运行所有子包的测试 + +测试的核心概念: +1. 单元测试:验证单个函数或方法的正确性 +2. 表驱动测试:使用测试数据表驱动测试执行 +3. 基准测试:测量代码的性能和内存使用 +4. 示例测试:提供函数使用示例并验证输出 + +测试的最佳实践: +1. 使用描述性的测试名称 +2. 保持测试的独立性 +3. 测试边界条件和错误情况 +4. 使用表驱动测试处理多个用例 +5. 编写清晰的错误消息 +6. 保持高的测试覆盖率 +7. 定期重构和维护测试代码 + +注意事项: +1. 测试文件必须以 _test.go 结尾 +2. 测试函数必须以 Test 开头 +3. 基准测试函数必须以 Benchmark 开头 +4. 示例测试函数必须以 Example 开头 +5. 使用 t.Helper() 标记辅助函数 +6. 合理使用并行测试提高效率 +*/ diff --git a/golang-learning/09-advanced/README.md b/golang-learning/09-advanced/README.md index 2602322..ecf3c4f 100644 --- a/golang-learning/09-advanced/README.md +++ b/golang-learning/09-advanced/README.md @@ -1,15 +1,24 @@ # 第九章:高级特性 -本章将学习 Go 语言的一些高级特性,包括反射、泛型、上下文和测试。 +本章将学习 Go 语言的高级特性,这些特性能帮助你编写更强大和灵活的程序。 ## 学习目标 -- 了解反射的基本概念和用法 -- 理解泛型的语法和应用 -- 掌握 context 包的使用 -- 学会编写单元测试 + +- 掌握反射的基本使用和应用场景 +- 理解泛型的概念和语法 +- 学会使用 context 包进行上下文管理 +- 掌握单元测试的编写方法 ## 文件列表 -- `01-reflection.go` - 反射 -- `02-generics.go` - 泛型 -- `03-context.go` - Context -- `04-testing.go` - 测试 \ No newline at end of file + +- `01-reflection.go` - 反射机制 +- `02-generics.go` - 泛型编程 +- `03-context.go` - 上下文管理 +- `04-testing.go` - 单元测试 + +## 学习建议 + +1. 反射是强大但复杂的特性,需要谨慎使用 +2. 泛型是 Go 1.18+ 的新特性,能提高代码复用性 +3. Context 是并发编程中的重要工具 +4. 测试是保证代码质量的重要手段 diff --git a/golang-learning/10-projects/01-calculator/README.md b/golang-learning/10-projects/01-calculator/README.md index 5007f48..2df1024 100644 --- a/golang-learning/10-projects/01-calculator/README.md +++ b/golang-learning/10-projects/01-calculator/README.md @@ -1,24 +1,88 @@ # 计算器项目 -一个支持基本四则运算的命令行计算器程序。 +这是一个简单的命令行计算器项目,演示了 Go 语言的基本语法和编程概念的综合应用。 -## 功能特性 -- 支持加法、减法、乘法、除法 -- 错误处理(如除零错误) +## 项目特性 + +- 支持基本四则运算(加、减、乘、除) +- 支持括号运算 +- 支持浮点数计算 +- 错误处理和输入验证 - 交互式命令行界面 -- 输入验证 +- 历史记录功能 + +## 项目结构 + +``` +01-calculator/ +├── README.md # 项目说明文档 +├── main.go # 主程序入口 +├── calculator/ # 计算器核心包 +│ ├── calculator.go # 计算器主要逻辑 +│ ├── parser.go # 表达式解析器 +│ └── history.go # 历史记录管理 +└── calculator_test.go # 测试文件 +``` ## 运行方法 + ```bash -cd 01-calculator +# 进入项目目录 +cd 10-projects/01-calculator + +# 运行程序 go run main.go + +# 或者编译后运行 +go build -o calculator main.go +./calculator ``` ## 使用示例 + ``` 欢迎使用 Go 计算器! -请输入第一个数字: 10 -请选择运算符 (+, -, *, /): + -请输入第二个数字: 5 -结果: 10 + 5 = 15 -``` \ No newline at end of file +输入数学表达式,或输入 'quit' 退出,'history' 查看历史记录 + +> 2 + 3 +结果: 5 + +> 10 * (5 - 2) +结果: 30 + +> 15 / 3 +结果: 5 + +> history +历史记录: +1. 2 + 3 = 5 +2. 10 * (5 - 2) = 30 +3. 15 / 3 = 5 + +> quit +再见! +``` + +## 学习要点 + +这个项目综合运用了以下 Go 语言特性: + +1. **包管理**: 创建和使用自定义包 +2. **结构体和方法**: 定义计算器结构体和相关方法 +3. **接口**: 定义计算器接口,实现多态 +4. **错误处理**: 处理除零错误、语法错误等 +5. **字符串处理**: 解析和处理用户输入 +6. **切片操作**: 管理历史记录 +7. **控制流程**: 使用循环和条件语句 +8. **用户交互**: 命令行输入输出 +9. **测试**: 编写单元测试验证功能 + +## 扩展建议 + +1. 添加更多数学函数(sin, cos, sqrt 等) +2. 支持变量定义和使用 +3. 添加配置文件支持 +4. 实现图形用户界面 +5. 添加科学计算功能 +6. 支持不同进制转换 +7. 添加单位换算功能 \ No newline at end of file diff --git a/golang-learning/10-projects/01-calculator/calculator/calculator.go b/golang-learning/10-projects/01-calculator/calculator/calculator.go new file mode 100644 index 0000000..b097199 --- /dev/null +++ b/golang-learning/10-projects/01-calculator/calculator/calculator.go @@ -0,0 +1,290 @@ +/* +calculator.go - 计算器核心逻辑 +实现了基本的四则运算和括号运算功能 +*/ + +package calculator + +import ( + "fmt" + "strconv" + "strings" +) + +// Calculator 接口定义了计算器的基本功能 +type Calculator interface { + Calculate(expression string) (float64, error) + GetHistory() []HistoryRecord + ClearHistory() +} + +// BasicCalculator 基本计算器实现 +type BasicCalculator struct { + history []HistoryRecord +} + +// NewCalculator 创建新的计算器实例 +func NewCalculator() Calculator { + return &BasicCalculator{ + history: make([]HistoryRecord, 0), + } +} + +// Calculate 计算数学表达式 +func (c *BasicCalculator) Calculate(expression string) (float64, error) { + // 清理输入 + expr := strings.ReplaceAll(expression, " ", "") + if expr == "" { + return 0, fmt.Errorf("表达式不能为空") + } + + // 验证表达式 + if err := c.validateExpression(expr); err != nil { + return 0, err + } + + // 解析和计算 + result, err := c.evaluateExpression(expr) + if err != nil { + return 0, err + } + + // 添加到历史记录 + c.addToHistory(expression, result) + + return result, nil +} + +// GetHistory 获取计算历史 +func (c *BasicCalculator) GetHistory() []HistoryRecord { + // 返回历史记录的副本 + history := make([]HistoryRecord, len(c.history)) + copy(history, c.history) + return history +} + +// ClearHistory 清空历史记录 +func (c *BasicCalculator) ClearHistory() { + c.history = make([]HistoryRecord, 0) +} + +// validateExpression 验证表达式的有效性 +func (c *BasicCalculator) validateExpression(expr string) error { + if len(expr) == 0 { + return fmt.Errorf("表达式不能为空") + } + + // 检查括号匹配 + parentheses := 0 + for _, char := range expr { + switch char { + case '(': + parentheses++ + case ')': + parentheses-- + if parentheses < 0 { + return fmt.Errorf("括号不匹配") + } + } + } + + if parentheses != 0 { + return fmt.Errorf("括号不匹配") + } + + // 检查有效字符 + validChars := "0123456789+-*/.() " + for _, char := range expr { + if !strings.ContainsRune(validChars, char) { + return fmt.Errorf("包含无效字符: %c", char) + } + } + + // 检查连续运算符 + operators := "+-*/" + for i := 0; i < len(expr)-1; i++ { + if strings.ContainsRune(operators, rune(expr[i])) && + strings.ContainsRune(operators, rune(expr[i+1])) { + return fmt.Errorf("连续的运算符") + } + } + + return nil +} + +// evaluateExpression 计算表达式的值 +func (c *BasicCalculator) evaluateExpression(expr string) (float64, error) { + // 处理括号 + for strings.Contains(expr, "(") { + // 找到最内层的括号 + start := -1 + for i, char := range expr { + if char == '(' { + start = i + } else if char == ')' { + if start == -1 { + return 0, fmt.Errorf("括号不匹配") + } + + // 计算括号内的表达式 + subExpr := expr[start+1 : i] + subResult, err := c.evaluateSimpleExpression(subExpr) + if err != nil { + return 0, err + } + + // 替换括号表达式为结果 + expr = expr[:start] + fmt.Sprintf("%g", subResult) + expr[i+1:] + break + } + } + } + + // 计算简单表达式(无括号) + return c.evaluateSimpleExpression(expr) +} + +// evaluateSimpleExpression 计算简单表达式(无括号) +func (c *BasicCalculator) evaluateSimpleExpression(expr string) (float64, error) { + if expr == "" { + return 0, fmt.Errorf("空表达式") + } + + // 解析表达式为标记 + tokens, err := c.tokenize(expr) + if err != nil { + return 0, err + } + + if len(tokens) == 0 { + return 0, fmt.Errorf("空表达式") + } + + // 如果只有一个标记,直接返回数值 + if len(tokens) == 1 { + return strconv.ParseFloat(tokens[0], 64) + } + + // 先处理乘法和除法 + for i := 1; i < len(tokens); i += 2 { + if i >= len(tokens) { + break + } + + operator := tokens[i] + if operator == "*" || operator == "/" { + left, err := strconv.ParseFloat(tokens[i-1], 64) + if err != nil { + return 0, fmt.Errorf("无效的数字: %s", tokens[i-1]) + } + + right, err := strconv.ParseFloat(tokens[i+1], 64) + if err != nil { + return 0, fmt.Errorf("无效的数字: %s", tokens[i+1]) + } + + var result float64 + if operator == "*" { + result = left * right + } else { + if right == 0 { + return 0, fmt.Errorf("除零错误") + } + result = left / right + } + + // 替换三个标记为结果 + newTokens := make([]string, 0, len(tokens)-2) + newTokens = append(newTokens, tokens[:i-1]...) + newTokens = append(newTokens, fmt.Sprintf("%g", result)) + newTokens = append(newTokens, tokens[i+2:]...) + tokens = newTokens + i -= 2 // 调整索引 + } + } + + // 再处理加法和减法 + result, err := strconv.ParseFloat(tokens[0], 64) + if err != nil { + return 0, fmt.Errorf("无效的数字: %s", tokens[0]) + } + + for i := 1; i < len(tokens); i += 2 { + if i+1 >= len(tokens) { + break + } + + operator := tokens[i] + operand, err := strconv.ParseFloat(tokens[i+1], 64) + if err != nil { + return 0, fmt.Errorf("无效的数字: %s", tokens[i+1]) + } + + switch operator { + case "+": + result += operand + case "-": + result -= operand + default: + return 0, fmt.Errorf("未知的运算符: %s", operator) + } + } + + return result, nil +} + +// tokenize 将表达式分解为标记 +func (c *BasicCalculator) tokenize(expr string) ([]string, error) { + var tokens []string + var current strings.Builder + + for i, char := range expr { + switch char { + case '+', '-', '*', '/': + // 处理负号 + if char == '-' && (i == 0 || expr[i-1] == '(' || strings.ContainsRune("+-*/", rune(expr[i-1]))) { + current.WriteRune(char) + continue + } + + // 保存当前数字 + if current.Len() > 0 { + tokens = append(tokens, current.String()) + current.Reset() + } + + // 保存运算符 + tokens = append(tokens, string(char)) + + case ' ': + // 忽略空格 + continue + + default: + // 数字或小数点 + current.WriteRune(char) + } + } + + // 保存最后的数字 + if current.Len() > 0 { + tokens = append(tokens, current.String()) + } + + return tokens, nil +} + +// addToHistory 添加计算记录到历史 +func (c *BasicCalculator) addToHistory(expression string, result float64) { + record := HistoryRecord{ + Expression: expression, + Result: result, + } + c.history = append(c.history, record) + + // 限制历史记录数量 + const maxHistory = 100 + if len(c.history) > maxHistory { + c.history = c.history[len(c.history)-maxHistory:] + } +} diff --git a/golang-learning/10-projects/01-calculator/calculator/history.go b/golang-learning/10-projects/01-calculator/calculator/history.go new file mode 100644 index 0000000..ddf0579 --- /dev/null +++ b/golang-learning/10-projects/01-calculator/calculator/history.go @@ -0,0 +1,244 @@ +/* +history.go - 历史记录管理 +定义了历史记录的数据结构和相关功能 +*/ + +package calculator + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + "time" +) + +// HistoryRecord 表示一条计算历史记录 +type HistoryRecord struct { + Expression string `json:"expression"` // 表达式 + Result float64 `json:"result"` // 计算结果 + Timestamp time.Time `json:"timestamp"` // 计算时间 +} + +// NewHistoryRecord 创建新的历史记录 +func NewHistoryRecord(expression string, result float64) HistoryRecord { + return HistoryRecord{ + Expression: expression, + Result: result, + Timestamp: time.Now(), + } +} + +// String 返回历史记录的字符串表示 +func (h HistoryRecord) String() string { + return fmt.Sprintf("%s = %g (计算时间: %s)", + h.Expression, + h.Result, + h.Timestamp.Format("2006-01-02 15:04:05")) +} + +// HistoryManager 历史记录管理器 +type HistoryManager struct { + records []HistoryRecord + filePath string + maxSize int +} + +// NewHistoryManager 创建新的历史记录管理器 +func NewHistoryManager(filePath string, maxSize int) *HistoryManager { + return &HistoryManager{ + records: make([]HistoryRecord, 0), + filePath: filePath, + maxSize: maxSize, + } +} + +// Add 添加历史记录 +func (hm *HistoryManager) Add(expression string, result float64) { + record := NewHistoryRecord(expression, result) + hm.records = append(hm.records, record) + + // 限制历史记录数量 + if len(hm.records) > hm.maxSize { + hm.records = hm.records[len(hm.records)-hm.maxSize:] + } +} + +// GetAll 获取所有历史记录 +func (hm *HistoryManager) GetAll() []HistoryRecord { + // 返回副本以防止外部修改 + records := make([]HistoryRecord, len(hm.records)) + copy(records, hm.records) + return records +} + +// GetLast 获取最近的 n 条记录 +func (hm *HistoryManager) GetLast(n int) []HistoryRecord { + if n <= 0 { + return []HistoryRecord{} + } + + if n >= len(hm.records) { + return hm.GetAll() + } + + start := len(hm.records) - n + records := make([]HistoryRecord, n) + copy(records, hm.records[start:]) + return records +} + +// Clear 清空历史记录 +func (hm *HistoryManager) Clear() { + hm.records = make([]HistoryRecord, 0) +} + +// Count 获取历史记录数量 +func (hm *HistoryManager) Count() int { + return len(hm.records) +} + +// SaveToFile 保存历史记录到文件 +func (hm *HistoryManager) SaveToFile() error { + if hm.filePath == "" { + return fmt.Errorf("文件路径未设置") + } + + data, err := json.MarshalIndent(hm.records, "", " ") + if err != nil { + return fmt.Errorf("序列化历史记录失败: %v", err) + } + + err = ioutil.WriteFile(hm.filePath, data, 0644) + if err != nil { + return fmt.Errorf("写入文件失败: %v", err) + } + + return nil +} + +// LoadFromFile 从文件加载历史记录 +func (hm *HistoryManager) LoadFromFile() error { + if hm.filePath == "" { + return fmt.Errorf("文件路径未设置") + } + + // 检查文件是否存在 + if _, err := os.Stat(hm.filePath); os.IsNotExist(err) { + // 文件不存在,创建空的历史记录 + hm.records = make([]HistoryRecord, 0) + return nil + } + + data, err := ioutil.ReadFile(hm.filePath) + if err != nil { + return fmt.Errorf("读取文件失败: %v", err) + } + + err = json.Unmarshal(data, &hm.records) + if err != nil { + return fmt.Errorf("反序列化历史记录失败: %v", err) + } + + // 限制历史记录数量 + if len(hm.records) > hm.maxSize { + hm.records = hm.records[len(hm.records)-hm.maxSize:] + } + + return nil +} + +// Search 搜索包含指定关键词的历史记录 +func (hm *HistoryManager) Search(keyword string) []HistoryRecord { + var results []HistoryRecord + + for _, record := range hm.records { + if contains(record.Expression, keyword) { + results = append(results, record) + } + } + + return results +} + +// GetStatistics 获取历史记录统计信息 +func (hm *HistoryManager) GetStatistics() map[string]interface{} { + stats := make(map[string]interface{}) + + stats["total_count"] = len(hm.records) + + if len(hm.records) == 0 { + return stats + } + + // 统计运算符使用频率 + operatorCount := make(map[string]int) + for _, record := range hm.records { + for _, char := range record.Expression { + switch char { + case '+': + operatorCount["addition"]++ + case '-': + operatorCount["subtraction"]++ + case '*': + operatorCount["multiplication"]++ + case '/': + operatorCount["division"]++ + } + } + } + stats["operator_usage"] = operatorCount + + // 最早和最晚的计算时间 + if len(hm.records) > 0 { + earliest := hm.records[0].Timestamp + latest := hm.records[0].Timestamp + + for _, record := range hm.records { + if record.Timestamp.Before(earliest) { + earliest = record.Timestamp + } + if record.Timestamp.After(latest) { + latest = record.Timestamp + } + } + + stats["earliest_calculation"] = earliest.Format("2006-01-02 15:04:05") + stats["latest_calculation"] = latest.Format("2006-01-02 15:04:05") + } + + return stats +} + +// ExportToCSV 导出历史记录为 CSV 格式 +func (hm *HistoryManager) ExportToCSV(filePath string) error { + var csvContent strings.Builder + + // CSV 头部 + csvContent.WriteString("Expression,Result,Timestamp\n") + + // 数据行 + for _, record := range hm.records { + csvContent.WriteString(fmt.Sprintf("\"%s\",%g,\"%s\"\n", + record.Expression, + record.Result, + record.Timestamp.Format("2006-01-02 15:04:05"))) + } + + err := ioutil.WriteFile(filePath, []byte(csvContent.String()), 0644) + if err != nil { + return fmt.Errorf("导出 CSV 文件失败: %v", err) + } + + return nil +} + +// contains 检查字符串是否包含子字符串(忽略大小写) +func contains(s, substr string) bool { + return len(s) >= len(substr) && + (substr == "" || + len(s) > 0 && + (s == substr || + strings.Contains(strings.ToLower(s), strings.ToLower(substr)))) +} diff --git a/golang-learning/10-projects/01-calculator/calculator_test.go b/golang-learning/10-projects/01-calculator/calculator_test.go new file mode 100644 index 0000000..c03833c --- /dev/null +++ b/golang-learning/10-projects/01-calculator/calculator_test.go @@ -0,0 +1,214 @@ +/* +calculator_test.go - 计算器测试文件 +测试计算器的各种功能 +*/ + +package main + +import ( + "fmt" + "testing" + + "./calculator" +) + +// TestBasicOperations 测试基本运算 +func TestBasicOperations(t *testing.T) { + calc := calculator.NewCalculator() + + tests := []struct { + name string + expression string + expected float64 + shouldErr bool + }{ + {"加法", "2 + 3", 5, false}, + {"减法", "10 - 4", 6, false}, + {"乘法", "3 * 7", 21, false}, + {"除法", "15 / 3", 5, false}, + {"浮点数加法", "2.5 + 1.5", 4, false}, + {"浮点数除法", "7.5 / 2.5", 3, false}, + {"负数", "-5 + 3", -2, false}, + {"除零错误", "5 / 0", 0, true}, + {"空表达式", "", 0, true}, + {"无效字符", "2 + a", 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := calc.Calculate(tt.expression) + + if tt.shouldErr { + if err == nil { + t.Errorf("期望出现错误,但没有错误") + } + } else { + if err != nil { + t.Errorf("不期望出现错误,但出现了错误: %v", err) + } + if result != tt.expected { + t.Errorf("期望结果 %g,实际结果 %g", tt.expected, result) + } + } + }) + } +} + +// TestComplexExpressions 测试复杂表达式 +func TestComplexExpressions(t *testing.T) { + calc := calculator.NewCalculator() + + tests := []struct { + name string + expression string + expected float64 + }{ + {"括号运算", "(2 + 3) * 4", 20}, + {"嵌套括号", "((2 + 3) * 4) - 5", 15}, + {"运算优先级", "2 + 3 * 4", 14}, + {"复杂表达式", "10 + (5 - 2) * 3", 19}, + {"多层嵌套", "(2 + (3 * 4)) / 2", 7}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := calc.Calculate(tt.expression) + if err != nil { + t.Errorf("不期望出现错误: %v", err) + } + if result != tt.expected { + t.Errorf("期望结果 %g,实际结果 %g", tt.expected, result) + } + }) + } +} + +// TestHistory 测试历史记录功能 +func TestHistory(t *testing.T) { + calc := calculator.NewCalculator() + + // 执行一些计算 + expressions := []string{"2 + 3", "10 - 4", "3 * 7"} + for _, expr := range expressions { + _, err := calc.Calculate(expr) + if err != nil { + t.Errorf("计算 %s 时出现错误: %v", expr, err) + } + } + + // 检查历史记录 + history := calc.GetHistory() + if len(history) != len(expressions) { + t.Errorf("期望历史记录数量 %d,实际数量 %d", len(expressions), len(history)) + } + + // 验证历史记录内容 + for i, record := range history { + if record.Expression != expressions[i] { + t.Errorf("历史记录 %d 表达式不匹配,期望 %s,实际 %s", + i, expressions[i], record.Expression) + } + } + + // 测试清空历史记录 + calc.ClearHistory() + history = calc.GetHistory() + if len(history) != 0 { + t.Errorf("清空后期望历史记录数量 0,实际数量 %d", len(history)) + } +} + +// TestErrorHandling 测试错误处理 +func TestErrorHandling(t *testing.T) { + calc := calculator.NewCalculator() + + errorTests := []struct { + name string + expression string + errorMsg string + }{ + {"括号不匹配1", "(2 + 3", "括号不匹配"}, + {"括号不匹配2", "2 + 3)", "括号不匹配"}, + {"连续运算符", "2 ++ 3", "连续的运算符"}, + {"除零", "5 / 0", "除零错误"}, + {"无效字符", "2 + @", "包含无效字符"}, + {"空表达式", "", "表达式不能为空"}, + } + + for _, tt := range errorTests { + t.Run(tt.name, func(t *testing.T) { + _, err := calc.Calculate(tt.expression) + if err == nil { + t.Errorf("期望出现错误,但没有错误") + } + }) + } +} + +// BenchmarkCalculate 基准测试 +func BenchmarkCalculate(b *testing.B) { + calc := calculator.NewCalculator() + expression := "(2 + 3) * 4 - 1" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := calc.Calculate(expression) + if err != nil { + b.Errorf("计算出现错误: %v", err) + } + } +} + +// BenchmarkSimpleAddition 简单加法基准测试 +func BenchmarkSimpleAddition(b *testing.B) { + calc := calculator.NewCalculator() + expression := "2 + 3" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := calc.Calculate(expression) + if err != nil { + b.Errorf("计算出现错误: %v", err) + } + } +} + +// BenchmarkComplexExpression 复杂表达式基准测试 +func BenchmarkComplexExpression(b *testing.B) { + calc := calculator.NewCalculator() + expression := "((2 + 3) * 4 - 1) / (5 + 2)" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := calc.Calculate(expression) + if err != nil { + b.Errorf("计算出现错误: %v", err) + } + } +} + +// ExampleCalculator_Calculate 计算器使用示例 +func ExampleCalculator_Calculate() { + calc := calculator.NewCalculator() + + result, err := calc.Calculate("2 + 3") + if err != nil { + panic(err) + } + + fmt.Printf("%.0f", result) + // Output: 5 +} + +// ExampleCalculator_ComplexExpression 复杂表达式示例 +func ExampleCalculator_ComplexExpression() { + calc := calculator.NewCalculator() + + result, err := calc.Calculate("(2 + 3) * 4") + if err != nil { + panic(err) + } + + fmt.Printf("%.0f", result) + // Output: 20 +} diff --git a/golang-learning/10-projects/01-calculator/main.go b/golang-learning/10-projects/01-calculator/main.go new file mode 100644 index 0000000..13b9372 --- /dev/null +++ b/golang-learning/10-projects/01-calculator/main.go @@ -0,0 +1,133 @@ +/* +main.go - 计算器项目主程序 +这是一个简单的命令行计算器,演示了 Go 语言的综合应用 +*/ + +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "./calculator" +) + +func main() { + // 创建计算器实例 + calc := calculator.NewCalculator() + + // 创建输入扫描器 + scanner := bufio.NewScanner(os.Stdin) + + // 显示欢迎信息 + printWelcome() + + // 主循环 + for { + fmt.Print("> ") + + // 读取用户输入 + if !scanner.Scan() { + break + } + + input := strings.TrimSpace(scanner.Text()) + + // 处理特殊命令 + switch strings.ToLower(input) { + case "quit", "exit", "q": + fmt.Println("再见!") + return + case "help", "h": + printHelp() + continue + case "history": + printHistory(calc) + continue + case "clear": + calc.ClearHistory() + fmt.Println("历史记录已清空") + continue + case "": + continue + } + + // 计算表达式 + result, err := calc.Calculate(input) + if err != nil { + fmt.Printf("错误: %v\n", err) + continue + } + + // 显示结果 + fmt.Printf("结果: %g\n", result) + } + + // 检查扫描器错误 + if err := scanner.Err(); err != nil { + fmt.Printf("读取输入时发生错误: %v\n", err) + } +} + +// printWelcome 显示欢迎信息 +func printWelcome() { + fmt.Println("=== 欢迎使用 Go 计算器!===") + fmt.Println() + fmt.Println("支持的操作:") + fmt.Println(" • 基本运算: +, -, *, /") + fmt.Println(" • 括号运算: ( )") + fmt.Println(" • 浮点数计算") + fmt.Println() + fmt.Println("特殊命令:") + fmt.Println(" • help - 显示帮助信息") + fmt.Println(" • history - 查看计算历史") + fmt.Println(" • clear - 清空历史记录") + fmt.Println(" • quit - 退出程序") + fmt.Println() + fmt.Println("请输入数学表达式:") +} + +// printHelp 显示帮助信息 +func printHelp() { + fmt.Println() + fmt.Println("=== 帮助信息 ===") + fmt.Println() + fmt.Println("支持的运算符:") + fmt.Println(" + 加法 例: 2 + 3") + fmt.Println(" - 减法 例: 5 - 2") + fmt.Println(" * 乘法 例: 4 * 6") + fmt.Println(" / 除法 例: 8 / 2") + fmt.Println(" () 括号 例: (2 + 3) * 4") + fmt.Println() + fmt.Println("使用示例:") + fmt.Println(" 2 + 3") + fmt.Println(" 10 * (5 - 2)") + fmt.Println(" 15.5 / 3.1") + fmt.Println(" ((2 + 3) * 4) - 1") + fmt.Println() + fmt.Println("特殊命令:") + fmt.Println(" help - 显示此帮助信息") + fmt.Println(" history - 查看计算历史") + fmt.Println(" clear - 清空历史记录") + fmt.Println(" quit - 退出程序") + fmt.Println() +} + +// printHistory 显示计算历史 +func printHistory(calc calculator.Calculator) { + history := calc.GetHistory() + + if len(history) == 0 { + fmt.Println("暂无计算历史") + return + } + + fmt.Println() + fmt.Println("=== 计算历史 ===") + for i, record := range history { + fmt.Printf("%d. %s = %g\n", i+1, record.Expression, record.Result) + } + fmt.Println() +} diff --git a/golang-learning/10-projects/02-todo-list/README.md b/golang-learning/10-projects/02-todo-list/README.md index 2d7eb0d..942ac18 100644 --- a/golang-learning/10-projects/02-todo-list/README.md +++ b/golang-learning/10-projects/02-todo-list/README.md @@ -1,31 +1,117 @@ # 待办事项列表项目 -一个命令行待办事项管理程序,支持添加、删除、标记完成等功能。 +这是一个命令行待办事项管理程序,演示了 Go 语言在数据持久化、文件操作和用户交互方面的应用。 -## 功能特性 -- 添加新的待办事项 -- 查看所有待办事项 -- 标记事项为完成 -- 删除待办事项 -- 数据持久化(保存到文件) +## 项目特性 + +- 添加、删除、修改待办事项 +- 标记任务完成状态 +- 按优先级和状态筛选任务 +- 数据持久化到 JSON 文件 +- 彩色命令行界面 +- 任务搜索功能 +- 统计信息显示 + +## 项目结构 + +``` +02-todo-list/ +├── README.md # 项目说明文档 +├── main.go # 主程序入口 +├── todo/ # 待办事项核心包 +│ ├── todo.go # 待办事项数据结构 +│ ├── manager.go # 任务管理器 +│ └── storage.go # 数据存储 +├── ui/ # 用户界面包 +│ ├── cli.go # 命令行界面 +│ └── colors.go # 颜色输出 +├── data/ # 数据文件目录 +│ └── todos.json # 任务数据文件 +└── todo_test.go # 测试文件 +``` ## 运行方法 + ```bash -cd 02-todo-list +# 进入项目目录 +cd 10-projects/02-todo-list + +# 运行程序 go run main.go + +# 或者编译后运行 +go build -o todo main.go +./todo ``` ## 使用示例 -```bash -# 添加待办事项 -go run main.go add "学习 Go 语言" -# 查看所有事项 -go run main.go list +``` +=== 待办事项管理器 === -# 标记完成 -go run main.go complete 1 +命令列表: + add <任务> - 添加新任务 + list - 显示所有任务 + done - 标记任务完成 + delete - 删除任务 + edit <新内容> - 编辑任务 + search <关键词> - 搜索任务 + stats - 显示统计信息 + help - 显示帮助 + quit - 退出程序 -# 删除事项 -go run main.go delete 1 -``` \ No newline at end of file +> add 学习Go语言 +✅ 任务已添加: 学习Go语言 (ID: 1) + +> add 完成项目文档 +✅ 任务已添加: 完成项目文档 (ID: 2) + +> list +📋 待办事项列表: + [1] ⏳ 学习Go语言 (优先级: 中) + [2] ⏳ 完成项目文档 (优先级: 中) + +> done 1 +✅ 任务已完成: 学习Go语言 + +> list +📋 待办事项列表: + [1] ✅ 学习Go语言 (优先级: 中) + [2] ⏳ 完成项目文档 (优先级: 中) + +> stats +📊 统计信息: + 总任务数: 2 + 已完成: 1 + 待完成: 1 + 完成率: 50.0% +``` + +## 学习要点 + +这个项目综合运用了以下 Go 语言特性: + +1. **结构体和方法**: 定义任务和管理器结构体 +2. **接口设计**: 定义存储和界面接口 +3. **JSON 处理**: 数据序列化和反序列化 +4. **文件操作**: 读写 JSON 文件进行数据持久化 +5. **错误处理**: 完善的错误处理机制 +6. **字符串处理**: 命令解析和文本处理 +7. **切片操作**: 任务列表的增删改查 +8. **时间处理**: 任务创建和修改时间 +9. **包管理**: 多包项目结构 +10. **用户交互**: 命令行输入输出 +11. **测试**: 单元测试和集成测试 + +## 扩展建议 + +1. 添加任务分类和标签功能 +2. 支持任务截止日期和提醒 +3. 实现任务导入导出功能 +4. 添加任务优先级排序 +5. 支持批量操作 +6. 实现 Web 界面 +7. 添加任务统计图表 +8. 支持多用户管理 +9. 集成云端同步 +10. 添加任务模板功能 \ No newline at end of file diff --git a/golang-learning/10-projects/02-todo-list/data/.gitkeep b/golang-learning/10-projects/02-todo-list/data/.gitkeep new file mode 100644 index 0000000..afcb3a6 --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/data/.gitkeep @@ -0,0 +1,2 @@ +# 这个文件用于保持 data 目录在 git 中被跟踪 +# 实际的 todos.json 文件会在程序运行时自动创建 \ No newline at end of file diff --git a/golang-learning/10-projects/02-todo-list/main.go b/golang-learning/10-projects/02-todo-list/main.go new file mode 100644 index 0000000..6ca6509 --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/main.go @@ -0,0 +1,275 @@ +/* +main.go - 待办事项列表主程序 +这是一个命令行待办事项管理程序,演示了 Go 语言的综合应用 +*/ + +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + + "./todo" + "./ui" +) + +func main() { + // 创建任务管理器 + manager, err := todo.NewManager("data/todos.json") + if err != nil { + fmt.Printf("❌ 初始化失败: %v\n", err) + os.Exit(1) + } + + // 加载现有数据 + if err := manager.Load(); err != nil { + fmt.Printf("⚠️ 加载数据失败: %v\n", err) + } + + // 创建命令行界面 + cli := ui.NewCLI() + scanner := bufio.NewScanner(os.Stdin) + + // 显示欢迎信息 + cli.ShowWelcome() + + // 主循环 + for { + fmt.Print("> ") + + // 读取用户输入 + if !scanner.Scan() { + break + } + + input := strings.TrimSpace(scanner.Text()) + if input == "" { + continue + } + + // 解析命令 + parts := strings.Fields(input) + command := strings.ToLower(parts[0]) + + // 执行命令 + switch command { + case "add": + handleAdd(manager, cli, parts[1:]) + case "list", "ls": + handleList(manager, cli, parts[1:]) + case "done", "complete": + handleDone(manager, cli, parts[1:]) + case "delete", "del", "rm": + handleDelete(manager, cli, parts[1:]) + case "edit", "update": + handleEdit(manager, cli, parts[1:]) + case "search", "find": + handleSearch(manager, cli, parts[1:]) + case "stats", "statistics": + handleStats(manager, cli) + case "help", "h": + cli.ShowHelp() + case "quit", "exit", "q": + handleQuit(manager, cli) + return + case "clear": + cli.Clear() + default: + cli.ShowError(fmt.Sprintf("未知命令: %s", command)) + cli.ShowInfo("输入 'help' 查看可用命令") + } + } + + // 检查扫描器错误 + if err := scanner.Err(); err != nil { + fmt.Printf("❌ 读取输入时发生错误: %v\n", err) + } +} + +// handleAdd 处理添加任务命令 +func handleAdd(manager *todo.Manager, cli *ui.CLI, args []string) { + if len(args) == 0 { + cli.ShowError("请提供任务内容") + cli.ShowInfo("用法: add <任务内容>") + return + } + + content := strings.Join(args, " ") + task, err := manager.AddTask(content) + if err != nil { + cli.ShowError(fmt.Sprintf("添加任务失败: %v", err)) + return + } + + cli.ShowSuccess(fmt.Sprintf("任务已添加: %s (ID: %d)", task.Content, task.ID)) + + // 保存数据 + if err := manager.Save(); err != nil { + cli.ShowError(fmt.Sprintf("保存数据失败: %v", err)) + } +} + +// handleList 处理列出任务命令 +func handleList(manager *todo.Manager, cli *ui.CLI, args []string) { + var filter string + if len(args) > 0 { + filter = strings.ToLower(args[0]) + } + + tasks := manager.GetTasks() + + // 应用过滤器 + var filteredTasks []todo.Task + switch filter { + case "done", "completed": + for _, task := range tasks { + if task.Completed { + filteredTasks = append(filteredTasks, task) + } + } + case "pending", "todo": + for _, task := range tasks { + if !task.Completed { + filteredTasks = append(filteredTasks, task) + } + } + default: + filteredTasks = tasks + } + + if len(filteredTasks) == 0 { + if filter != "" { + cli.ShowInfo(fmt.Sprintf("没有找到 %s 状态的任务", filter)) + } else { + cli.ShowInfo("暂无任务") + } + return + } + + cli.ShowTaskList(filteredTasks) +} + +// handleDone 处理完成任务命令 +func handleDone(manager *todo.Manager, cli *ui.CLI, args []string) { + if len(args) == 0 { + cli.ShowError("请提供任务ID") + cli.ShowInfo("用法: done <任务ID>") + return + } + + id, err := strconv.Atoi(args[0]) + if err != nil { + cli.ShowError("无效的任务ID") + return + } + + task, err := manager.CompleteTask(id) + if err != nil { + cli.ShowError(fmt.Sprintf("完成任务失败: %v", err)) + return + } + + cli.ShowSuccess(fmt.Sprintf("任务已完成: %s", task.Content)) + + // 保存数据 + if err := manager.Save(); err != nil { + cli.ShowError(fmt.Sprintf("保存数据失败: %v", err)) + } +} + +// handleDelete 处理删除任务命令 +func handleDelete(manager *todo.Manager, cli *ui.CLI, args []string) { + if len(args) == 0 { + cli.ShowError("请提供任务ID") + cli.ShowInfo("用法: delete <任务ID>") + return + } + + id, err := strconv.Atoi(args[0]) + if err != nil { + cli.ShowError("无效的任务ID") + return + } + + task, err := manager.DeleteTask(id) + if err != nil { + cli.ShowError(fmt.Sprintf("删除任务失败: %v", err)) + return + } + + cli.ShowSuccess(fmt.Sprintf("任务已删除: %s", task.Content)) + + // 保存数据 + if err := manager.Save(); err != nil { + cli.ShowError(fmt.Sprintf("保存数据失败: %v", err)) + } +} + +// handleEdit 处理编辑任务命令 +func handleEdit(manager *todo.Manager, cli *ui.CLI, args []string) { + if len(args) < 2 { + cli.ShowError("请提供任务ID和新内容") + cli.ShowInfo("用法: edit <任务ID> <新内容>") + return + } + + id, err := strconv.Atoi(args[0]) + if err != nil { + cli.ShowError("无效的任务ID") + return + } + + newContent := strings.Join(args[1:], " ") + task, err := manager.UpdateTask(id, newContent) + if err != nil { + cli.ShowError(fmt.Sprintf("编辑任务失败: %v", err)) + return + } + + cli.ShowSuccess(fmt.Sprintf("任务已更新: %s", task.Content)) + + // 保存数据 + if err := manager.Save(); err != nil { + cli.ShowError(fmt.Sprintf("保存数据失败: %v", err)) + } +} + +// handleSearch 处理搜索任务命令 +func handleSearch(manager *todo.Manager, cli *ui.CLI, args []string) { + if len(args) == 0 { + cli.ShowError("请提供搜索关键词") + cli.ShowInfo("用法: search <关键词>") + return + } + + keyword := strings.Join(args, " ") + tasks := manager.SearchTasks(keyword) + + if len(tasks) == 0 { + cli.ShowInfo(fmt.Sprintf("没有找到包含 '%s' 的任务", keyword)) + return + } + + cli.ShowInfo(fmt.Sprintf("搜索结果 (关键词: %s):", keyword)) + cli.ShowTaskList(tasks) +} + +// handleStats 处理统计信息命令 +func handleStats(manager *todo.Manager, cli *ui.CLI) { + stats := manager.GetStatistics() + cli.ShowStatistics(stats) +} + +// handleQuit 处理退出命令 +func handleQuit(manager *todo.Manager, cli *ui.CLI) { + // 保存数据 + if err := manager.Save(); err != nil { + cli.ShowError(fmt.Sprintf("保存数据失败: %v", err)) + } + + cli.ShowInfo("数据已保存") + cli.ShowSuccess("再见!") +} diff --git a/golang-learning/10-projects/02-todo-list/todo/manager.go b/golang-learning/10-projects/02-todo-list/todo/manager.go new file mode 100644 index 0000000..8ae8a4c --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/todo/manager.go @@ -0,0 +1,427 @@ +/* +manager.go - 任务管理器 +实现了任务的增删改查和业务逻辑 +*/ + +package todo + +import ( + "fmt" + "sort" + "strings" + "time" +) + +// Statistics 统计信息 +type Statistics struct { + Total int `json:"total"` // 总任务数 + Completed int `json:"completed"` // 已完成任务数 + Pending int `json:"pending"` // 待完成任务数 + CompletionRate float64 `json:"completion_rate"` // 完成率 + ByPriority map[string]int `json:"by_priority"` // 按优先级统计 + ByStatus map[string]int `json:"by_status"` // 按状态统计 + AverageCompletionTime time.Duration `json:"average_completion_time"` // 平均完成时间 +} + +// Manager 任务管理器 +type Manager struct { + tasks []Task + nextID int + storage Storage +} + +// NewManager 创建新的任务管理器 +func NewManager(dataFile string) (*Manager, error) { + storage, err := NewJSONStorage(dataFile) + if err != nil { + return nil, fmt.Errorf("创建存储失败: %v", err) + } + + return &Manager{ + tasks: make([]Task, 0), + nextID: 1, + storage: storage, + }, nil +} + +// Load 从存储加载任务 +func (m *Manager) Load() error { + tasks, err := m.storage.Load() + if err != nil { + return fmt.Errorf("加载任务失败: %v", err) + } + + m.tasks = tasks + + // 更新下一个ID + maxID := 0 + for _, task := range m.tasks { + if task.ID > maxID { + maxID = task.ID + } + } + m.nextID = maxID + 1 + + return nil +} + +// Save 保存任务到存储 +func (m *Manager) Save() error { + return m.storage.Save(m.tasks) +} + +// AddTask 添加新任务 +func (m *Manager) AddTask(content string) (*Task, error) { + content = strings.TrimSpace(content) + if content == "" { + return nil, fmt.Errorf("任务内容不能为空") + } + + task := NewTask(m.nextID, content) + m.tasks = append(m.tasks, *task) + m.nextID++ + + return task, nil +} + +// GetTasks 获取所有任务 +func (m *Manager) GetTasks() []Task { + // 返回副本以防止外部修改 + tasks := make([]Task, len(m.tasks)) + copy(tasks, m.tasks) + + // 排序任务 + sort.Sort(TaskList(tasks)) + + return tasks +} + +// GetTask 根据ID获取任务 +func (m *Manager) GetTask(id int) (*Task, error) { + for i, task := range m.tasks { + if task.ID == id { + return &m.tasks[i], nil + } + } + return nil, fmt.Errorf("任务不存在: ID %d", id) +} + +// UpdateTask 更新任务内容 +func (m *Manager) UpdateTask(id int, content string) (*Task, error) { + content = strings.TrimSpace(content) + if content == "" { + return nil, fmt.Errorf("任务内容不能为空") + } + + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + task.Update(content) + return task, nil +} + +// CompleteTask 标记任务完成 +func (m *Manager) CompleteTask(id int) (*Task, error) { + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + if task.Completed { + return nil, fmt.Errorf("任务已经完成") + } + + task.Complete() + return task, nil +} + +// UncompleteTask 标记任务未完成 +func (m *Manager) UncompleteTask(id int) (*Task, error) { + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + if !task.Completed { + return nil, fmt.Errorf("任务尚未完成") + } + + task.Uncomplete() + return task, nil +} + +// DeleteTask 删除任务 +func (m *Manager) DeleteTask(id int) (*Task, error) { + for i, task := range m.tasks { + if task.ID == id { + // 创建任务副本用于返回 + deletedTask := task + + // 从切片中删除任务 + m.tasks = append(m.tasks[:i], m.tasks[i+1:]...) + + return &deletedTask, nil + } + } + return nil, fmt.Errorf("任务不存在: ID %d", id) +} + +// SearchTasks 搜索任务 +func (m *Manager) SearchTasks(keyword string) []Task { + keyword = strings.TrimSpace(keyword) + if keyword == "" { + return []Task{} + } + + var results []Task + for _, task := range m.tasks { + if m.matchesKeyword(task, keyword) { + results = append(results, task) + } + } + + // 排序结果 + sort.Sort(TaskList(results)) + + return results +} + +// matchesKeyword 检查任务是否匹配关键词 +func (m *Manager) matchesKeyword(task Task, keyword string) bool { + keyword = strings.ToLower(keyword) + + // 检查任务内容 + if strings.Contains(strings.ToLower(task.Content), keyword) { + return true + } + + // 检查任务描述 + if strings.Contains(strings.ToLower(task.Description), keyword) { + return true + } + + // 检查标签 + for _, tag := range task.Tags { + if strings.Contains(strings.ToLower(tag), keyword) { + return true + } + } + + return false +} + +// GetTasksByStatus 根据状态获取任务 +func (m *Manager) GetTasksByStatus(completed bool) []Task { + var results []Task + for _, task := range m.tasks { + if task.Completed == completed { + results = append(results, task) + } + } + + sort.Sort(TaskList(results)) + return results +} + +// GetTasksByPriority 根据优先级获取任务 +func (m *Manager) GetTasksByPriority(priority Priority) []Task { + var results []Task + for _, task := range m.tasks { + if task.Priority == priority { + results = append(results, task) + } + } + + sort.Sort(TaskList(results)) + return results +} + +// SetTaskPriority 设置任务优先级 +func (m *Manager) SetTaskPriority(id int, priority Priority) (*Task, error) { + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + task.SetPriority(priority) + return task, nil +} + +// AddTaskTag 为任务添加标签 +func (m *Manager) AddTaskTag(id int, tag string) (*Task, error) { + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + task.AddTag(tag) + return task, nil +} + +// RemoveTaskTag 移除任务标签 +func (m *Manager) RemoveTaskTag(id int, tag string) (*Task, error) { + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + task.RemoveTag(tag) + return task, nil +} + +// SetTaskDescription 设置任务描述 +func (m *Manager) SetTaskDescription(id int, description string) (*Task, error) { + task, err := m.GetTask(id) + if err != nil { + return nil, err + } + + task.SetDescription(description) + return task, nil +} + +// GetStatistics 获取统计信息 +func (m *Manager) GetStatistics() Statistics { + stats := Statistics{ + Total: len(m.tasks), + Completed: 0, + Pending: 0, + ByPriority: make(map[string]int), + ByStatus: make(map[string]int), + } + + var totalCompletionTime time.Duration + completedCount := 0 + + for _, task := range m.tasks { + // 统计完成状态 + if task.Completed { + stats.Completed++ + if task.CompletedAt != nil { + totalCompletionTime += task.CompletionTime() + completedCount++ + } + } else { + stats.Pending++ + } + + // 统计优先级 + priorityStr := task.Priority.String() + stats.ByPriority[priorityStr]++ + + // 统计状态 + if task.Completed { + stats.ByStatus["已完成"]++ + } else { + stats.ByStatus["待完成"]++ + } + } + + // 计算完成率 + if stats.Total > 0 { + stats.CompletionRate = float64(stats.Completed) / float64(stats.Total) * 100 + } + + // 计算平均完成时间 + if completedCount > 0 { + stats.AverageCompletionTime = totalCompletionTime / time.Duration(completedCount) + } + + return stats +} + +// ClearCompleted 清除所有已完成的任务 +func (m *Manager) ClearCompleted() int { + var remaining []Task + clearedCount := 0 + + for _, task := range m.tasks { + if task.Completed { + clearedCount++ + } else { + remaining = append(remaining, task) + } + } + + m.tasks = remaining + return clearedCount +} + +// ClearAll 清除所有任务 +func (m *Manager) ClearAll() int { + count := len(m.tasks) + m.tasks = make([]Task, 0) + m.nextID = 1 + return count +} + +// GetTaskCount 获取任务数量 +func (m *Manager) GetTaskCount() int { + return len(m.tasks) +} + +// GetCompletedCount 获取已完成任务数量 +func (m *Manager) GetCompletedCount() int { + count := 0 + for _, task := range m.tasks { + if task.Completed { + count++ + } + } + return count +} + +// GetPendingCount 获取待完成任务数量 +func (m *Manager) GetPendingCount() int { + count := 0 + for _, task := range m.tasks { + if !task.Completed { + count++ + } + } + return count +} + +// ValidateAllTasks 验证所有任务的有效性 +func (m *Manager) ValidateAllTasks() []error { + var errors []error + for i, task := range m.tasks { + if err := task.Validate(); err != nil { + errors = append(errors, fmt.Errorf("任务 %d: %v", i, err)) + } + } + return errors +} + +// ExportTasks 导出任务(可以扩展为不同格式) +func (m *Manager) ExportTasks() []Task { + tasks := make([]Task, len(m.tasks)) + copy(tasks, m.tasks) + return tasks +} + +// ImportTasks 导入任务 +func (m *Manager) ImportTasks(tasks []Task) error { + // 验证导入的任务 + for i, task := range tasks { + if err := task.Validate(); err != nil { + return fmt.Errorf("导入任务 %d 验证失败: %v", i, err) + } + } + + // 更新任务列表 + m.tasks = append(m.tasks, tasks...) + + // 更新下一个ID + maxID := m.nextID - 1 + for _, task := range tasks { + if task.ID > maxID { + maxID = task.ID + } + } + m.nextID = maxID + 1 + + return nil +} diff --git a/golang-learning/10-projects/02-todo-list/todo/storage.go b/golang-learning/10-projects/02-todo-list/todo/storage.go new file mode 100644 index 0000000..a55d0f0 --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/todo/storage.go @@ -0,0 +1,308 @@ +/* +storage.go - 数据存储 +实现了任务数据的持久化存储 +*/ + +package todo + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// Storage 存储接口 +type Storage interface { + Load() ([]Task, error) + Save(tasks []Task) error +} + +// JSONStorage JSON文件存储实现 +type JSONStorage struct { + filePath string +} + +// NewJSONStorage 创建新的JSON存储 +func NewJSONStorage(filePath string) (*JSONStorage, error) { + if filePath == "" { + return nil, fmt.Errorf("文件路径不能为空") + } + + // 确保目录存在 + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, fmt.Errorf("创建目录失败: %v", err) + } + + return &JSONStorage{ + filePath: filePath, + }, nil +} + +// Load 从JSON文件加载任务 +func (s *JSONStorage) Load() ([]Task, error) { + // 检查文件是否存在 + if _, err := os.Stat(s.filePath); os.IsNotExist(err) { + // 文件不存在,返回空任务列表 + return []Task{}, nil + } + + // 读取文件内容 + data, err := ioutil.ReadFile(s.filePath) + if err != nil { + return nil, fmt.Errorf("读取文件失败: %v", err) + } + + // 如果文件为空,返回空任务列表 + if len(data) == 0 { + return []Task{}, nil + } + + // 解析JSON数据 + var tasks []Task + if err := json.Unmarshal(data, &tasks); err != nil { + return nil, fmt.Errorf("解析JSON数据失败: %v", err) + } + + return tasks, nil +} + +// Save 保存任务到JSON文件 +func (s *JSONStorage) Save(tasks []Task) error { + // 序列化任务数据 + data, err := json.MarshalIndent(tasks, "", " ") + if err != nil { + return fmt.Errorf("序列化任务数据失败: %v", err) + } + + // 写入文件 + if err := ioutil.WriteFile(s.filePath, data, 0644); err != nil { + return fmt.Errorf("写入文件失败: %v", err) + } + + return nil +} + +// GetFilePath 获取文件路径 +func (s *JSONStorage) GetFilePath() string { + return s.filePath +} + +// FileExists 检查文件是否存在 +func (s *JSONStorage) FileExists() bool { + _, err := os.Stat(s.filePath) + return !os.IsNotExist(err) +} + +// GetFileSize 获取文件大小 +func (s *JSONStorage) GetFileSize() (int64, error) { + info, err := os.Stat(s.filePath) + if err != nil { + return 0, err + } + return info.Size(), nil +} + +// Backup 备份数据文件 +func (s *JSONStorage) Backup(backupPath string) error { + if !s.FileExists() { + return fmt.Errorf("源文件不存在") + } + + // 读取源文件 + data, err := ioutil.ReadFile(s.filePath) + if err != nil { + return fmt.Errorf("读取源文件失败: %v", err) + } + + // 确保备份目录存在 + dir := filepath.Dir(backupPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("创建备份目录失败: %v", err) + } + + // 写入备份文件 + if err := ioutil.WriteFile(backupPath, data, 0644); err != nil { + return fmt.Errorf("写入备份文件失败: %v", err) + } + + return nil +} + +// Restore 从备份恢复数据 +func (s *JSONStorage) Restore(backupPath string) error { + // 检查备份文件是否存在 + if _, err := os.Stat(backupPath); os.IsNotExist(err) { + return fmt.Errorf("备份文件不存在") + } + + // 读取备份文件 + data, err := ioutil.ReadFile(backupPath) + if err != nil { + return fmt.Errorf("读取备份文件失败: %v", err) + } + + // 验证备份数据 + var tasks []Task + if err := json.Unmarshal(data, &tasks); err != nil { + return fmt.Errorf("备份数据格式无效: %v", err) + } + + // 写入主文件 + if err := ioutil.WriteFile(s.filePath, data, 0644); err != nil { + return fmt.Errorf("恢复数据失败: %v", err) + } + + return nil +} + +// Clear 清空数据文件 +func (s *JSONStorage) Clear() error { + return s.Save([]Task{}) +} + +// MemoryStorage 内存存储实现(用于测试) +type MemoryStorage struct { + tasks []Task +} + +// NewMemoryStorage 创建新的内存存储 +func NewMemoryStorage() *MemoryStorage { + return &MemoryStorage{ + tasks: make([]Task, 0), + } +} + +// Load 从内存加载任务 +func (s *MemoryStorage) Load() ([]Task, error) { + // 返回任务副本 + tasks := make([]Task, len(s.tasks)) + copy(tasks, s.tasks) + return tasks, nil +} + +// Save 保存任务到内存 +func (s *MemoryStorage) Save(tasks []Task) error { + // 保存任务副本 + s.tasks = make([]Task, len(tasks)) + copy(s.tasks, tasks) + return nil +} + +// Clear 清空内存中的任务 +func (s *MemoryStorage) Clear() { + s.tasks = make([]Task, 0) +} + +// GetTasks 获取内存中的任务(用于测试) +func (s *MemoryStorage) GetTasks() []Task { + tasks := make([]Task, len(s.tasks)) + copy(tasks, s.tasks) + return tasks +} + +// CSVStorage CSV文件存储实现(扩展功能) +type CSVStorage struct { + filePath string +} + +// NewCSVStorage 创建新的CSV存储 +func NewCSVStorage(filePath string) (*CSVStorage, error) { + if filePath == "" { + return nil, fmt.Errorf("文件路径不能为空") + } + + // 确保目录存在 + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, fmt.Errorf("创建目录失败: %v", err) + } + + return &CSVStorage{ + filePath: filePath, + }, nil +} + +// Load 从CSV文件加载任务(简化实现) +func (s *CSVStorage) Load() ([]Task, error) { + // 这里可以实现CSV解析逻辑 + // 为了简化,暂时返回空列表 + return []Task{}, nil +} + +// Save 保存任务到CSV文件(简化实现) +func (s *CSVStorage) Save(tasks []Task) error { + // 这里可以实现CSV写入逻辑 + // 为了简化,暂时不做任何操作 + return nil +} + +// StorageManager 存储管理器 +type StorageManager struct { + storages map[string]Storage + current string +} + +// NewStorageManager 创建新的存储管理器 +func NewStorageManager() *StorageManager { + return &StorageManager{ + storages: make(map[string]Storage), + } +} + +// AddStorage 添加存储实现 +func (sm *StorageManager) AddStorage(name string, storage Storage) { + sm.storages[name] = storage +} + +// SetCurrent 设置当前使用的存储 +func (sm *StorageManager) SetCurrent(name string) error { + if _, exists := sm.storages[name]; !exists { + return fmt.Errorf("存储 '%s' 不存在", name) + } + sm.current = name + return nil +} + +// GetCurrent 获取当前存储 +func (sm *StorageManager) GetCurrent() (Storage, error) { + if sm.current == "" { + return nil, fmt.Errorf("未设置当前存储") + } + + storage, exists := sm.storages[sm.current] + if !exists { + return nil, fmt.Errorf("当前存储 '%s' 不存在", sm.current) + } + + return storage, nil +} + +// ListStorages 列出所有可用的存储 +func (sm *StorageManager) ListStorages() []string { + var names []string + for name := range sm.storages { + names = append(names, name) + } + return names +} + +// Load 使用当前存储加载数据 +func (sm *StorageManager) Load() ([]Task, error) { + storage, err := sm.GetCurrent() + if err != nil { + return nil, err + } + return storage.Load() +} + +// Save 使用当前存储保存数据 +func (sm *StorageManager) Save(tasks []Task) error { + storage, err := sm.GetCurrent() + if err != nil { + return err + } + return storage.Save(tasks) +} diff --git a/golang-learning/10-projects/02-todo-list/todo/todo.go b/golang-learning/10-projects/02-todo-list/todo/todo.go new file mode 100644 index 0000000..59d968c --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/todo/todo.go @@ -0,0 +1,356 @@ +/* +todo.go - 待办事项数据结构 +定义了任务的基本数据结构和相关方法 +*/ + +package todo + +import ( + "fmt" + "strings" + "time" +) + +// Priority 任务优先级 +type Priority int + +const ( + Low Priority = iota + Medium + High + Urgent +) + +// String 返回优先级的字符串表示 +func (p Priority) String() string { + switch p { + case Low: + return "低" + case Medium: + return "中" + case High: + return "高" + case Urgent: + return "紧急" + default: + return "未知" + } +} + +// Task 表示一个待办事项 +type Task struct { + ID int `json:"id"` // 任务ID + Content string `json:"content"` // 任务内容 + Completed bool `json:"completed"` // 是否完成 + Priority Priority `json:"priority"` // 优先级 + CreatedAt time.Time `json:"created_at"` // 创建时间 + UpdatedAt time.Time `json:"updated_at"` // 更新时间 + CompletedAt *time.Time `json:"completed_at,omitempty"` // 完成时间 + Tags []string `json:"tags,omitempty"` // 标签 + Description string `json:"description,omitempty"` // 详细描述 +} + +// NewTask 创建新的任务 +func NewTask(id int, content string) *Task { + now := time.Now() + return &Task{ + ID: id, + Content: content, + Completed: false, + Priority: Medium, + CreatedAt: now, + UpdatedAt: now, + Tags: make([]string, 0), + } +} + +// Complete 标记任务为完成 +func (t *Task) Complete() { + if !t.Completed { + t.Completed = true + now := time.Now() + t.CompletedAt = &now + t.UpdatedAt = now + } +} + +// Uncomplete 标记任务为未完成 +func (t *Task) Uncomplete() { + if t.Completed { + t.Completed = false + t.CompletedAt = nil + t.UpdatedAt = time.Now() + } +} + +// Update 更新任务内容 +func (t *Task) Update(content string) { + if content != "" && content != t.Content { + t.Content = content + t.UpdatedAt = time.Now() + } +} + +// SetPriority 设置任务优先级 +func (t *Task) SetPriority(priority Priority) { + if priority != t.Priority { + t.Priority = priority + t.UpdatedAt = time.Now() + } +} + +// AddTag 添加标签 +func (t *Task) AddTag(tag string) { + if tag == "" { + return + } + + // 检查标签是否已存在 + for _, existingTag := range t.Tags { + if existingTag == tag { + return + } + } + + t.Tags = append(t.Tags, tag) + t.UpdatedAt = time.Now() +} + +// RemoveTag 移除标签 +func (t *Task) RemoveTag(tag string) { + for i, existingTag := range t.Tags { + if existingTag == tag { + t.Tags = append(t.Tags[:i], t.Tags[i+1:]...) + t.UpdatedAt = time.Now() + break + } + } +} + +// HasTag 检查是否包含指定标签 +func (t *Task) HasTag(tag string) bool { + for _, existingTag := range t.Tags { + if existingTag == tag { + return true + } + } + return false +} + +// SetDescription 设置任务描述 +func (t *Task) SetDescription(description string) { + if description != t.Description { + t.Description = description + t.UpdatedAt = time.Now() + } +} + +// String 返回任务的字符串表示 +func (t *Task) String() string { + status := "⏳" + if t.Completed { + status = "✅" + } + + return fmt.Sprintf("[%d] %s %s (优先级: %s)", + t.ID, status, t.Content, t.Priority) +} + +// DetailedString 返回任务的详细字符串表示 +func (t *Task) DetailedString() string { + status := "待完成" + if t.Completed { + status = "已完成" + } + + result := fmt.Sprintf("任务 #%d\n", t.ID) + result += fmt.Sprintf("内容: %s\n", t.Content) + result += fmt.Sprintf("状态: %s\n", status) + result += fmt.Sprintf("优先级: %s\n", t.Priority) + result += fmt.Sprintf("创建时间: %s\n", t.CreatedAt.Format("2006-01-02 15:04:05")) + result += fmt.Sprintf("更新时间: %s\n", t.UpdatedAt.Format("2006-01-02 15:04:05")) + + if t.Completed && t.CompletedAt != nil { + result += fmt.Sprintf("完成时间: %s\n", t.CompletedAt.Format("2006-01-02 15:04:05")) + } + + if t.Description != "" { + result += fmt.Sprintf("描述: %s\n", t.Description) + } + + if len(t.Tags) > 0 { + result += fmt.Sprintf("标签: %v\n", t.Tags) + } + + return result +} + +// IsOverdue 检查任务是否过期(如果有截止日期的话) +func (t *Task) IsOverdue() bool { + // 这里可以扩展添加截止日期功能 + return false +} + +// Age 返回任务的存在时间 +func (t *Task) Age() time.Duration { + return time.Since(t.CreatedAt) +} + +// CompletionTime 返回任务的完成耗时 +func (t *Task) CompletionTime() time.Duration { + if !t.Completed || t.CompletedAt == nil { + return 0 + } + return t.CompletedAt.Sub(t.CreatedAt) +} + +// Clone 创建任务的副本 +func (t *Task) Clone() *Task { + clone := *t + + // 深拷贝切片 + if len(t.Tags) > 0 { + clone.Tags = make([]string, len(t.Tags)) + copy(clone.Tags, t.Tags) + } + + // 深拷贝时间指针 + if t.CompletedAt != nil { + completedAt := *t.CompletedAt + clone.CompletedAt = &completedAt + } + + return &clone +} + +// Validate 验证任务数据的有效性 +func (t *Task) Validate() error { + if t.ID <= 0 { + return fmt.Errorf("任务ID必须大于0") + } + + if t.Content == "" { + return fmt.Errorf("任务内容不能为空") + } + + if t.Priority < Low || t.Priority > Urgent { + return fmt.Errorf("无效的优先级") + } + + if t.CreatedAt.IsZero() { + return fmt.Errorf("创建时间不能为空") + } + + if t.UpdatedAt.IsZero() { + return fmt.Errorf("更新时间不能为空") + } + + if t.Completed && t.CompletedAt == nil { + return fmt.Errorf("已完成的任务必须有完成时间") + } + + if !t.Completed && t.CompletedAt != nil { + return fmt.Errorf("未完成的任务不应该有完成时间") + } + + return nil +} + +// TaskList 任务列表类型 +type TaskList []Task + +// Len 返回任务列表长度 +func (tl TaskList) Len() int { + return len(tl) +} + +// Less 比较两个任务的顺序(用于排序) +func (tl TaskList) Less(i, j int) bool { + // 首先按完成状态排序(未完成的在前) + if tl[i].Completed != tl[j].Completed { + return !tl[i].Completed + } + + // 然后按优先级排序(高优先级在前) + if tl[i].Priority != tl[j].Priority { + return tl[i].Priority > tl[j].Priority + } + + // 最后按创建时间排序(新创建的在前) + return tl[i].CreatedAt.After(tl[j].CreatedAt) +} + +// Swap 交换两个任务的位置 +func (tl TaskList) Swap(i, j int) { + tl[i], tl[j] = tl[j], tl[i] +} + +// Filter 过滤任务列表 +func (tl TaskList) Filter(predicate func(Task) bool) TaskList { + var filtered TaskList + for _, task := range tl { + if predicate(task) { + filtered = append(filtered, task) + } + } + return filtered +} + +// FindByID 根据ID查找任务 +func (tl TaskList) FindByID(id int) (*Task, bool) { + for i, task := range tl { + if task.ID == id { + return &tl[i], true + } + } + return nil, false +} + +// GetCompleted 获取已完成的任务 +func (tl TaskList) GetCompleted() TaskList { + return tl.Filter(func(t Task) bool { + return t.Completed + }) +} + +// GetPending 获取待完成的任务 +func (tl TaskList) GetPending() TaskList { + return tl.Filter(func(t Task) bool { + return !t.Completed + }) +} + +// GetByPriority 根据优先级获取任务 +func (tl TaskList) GetByPriority(priority Priority) TaskList { + return tl.Filter(func(t Task) bool { + return t.Priority == priority + }) +} + +// Search 搜索包含关键词的任务 +func (tl TaskList) Search(keyword string) TaskList { + return tl.Filter(func(t Task) bool { + return contains(t.Content, keyword) || + contains(t.Description, keyword) || + containsInTags(t.Tags, keyword) + }) +} + +// contains 检查字符串是否包含子字符串(忽略大小写) +func contains(s, substr string) bool { + if substr == "" { + return true + } + return len(s) >= len(substr) && + strings.Contains(strings.ToLower(s), strings.ToLower(substr)) +} + +// containsInTags 检查标签列表是否包含关键词 +func containsInTags(tags []string, keyword string) bool { + for _, tag := range tags { + if contains(tag, keyword) { + return true + } + } + return false +} diff --git a/golang-learning/10-projects/02-todo-list/todo_test.go b/golang-learning/10-projects/02-todo-list/todo_test.go new file mode 100644 index 0000000..7f89a6b --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/todo_test.go @@ -0,0 +1,382 @@ +/* +todo_test.go - 待办事项测试文件 +测试待办事项管理器的各种功能 +*/ + +package main + +import ( + "fmt" + "testing" + "time" + + "./todo" +) + +// TestTaskCreation 测试任务创建 +func TestTaskCreation(t *testing.T) { + task := todo.NewTask(1, "测试任务") + + if task.ID != 1 { + t.Errorf("期望任务ID为1,实际为%d", task.ID) + } + + if task.Content != "测试任务" { + t.Errorf("期望任务内容为'测试任务',实际为'%s'", task.Content) + } + + if task.Completed { + t.Error("新创建的任务不应该是已完成状态") + } + + if task.Priority != todo.Medium { + t.Errorf("期望默认优先级为Medium,实际为%v", task.Priority) + } +} + +// TestTaskCompletion 测试任务完成 +func TestTaskCompletion(t *testing.T) { + task := todo.NewTask(1, "测试任务") + + // 完成任务 + task.Complete() + + if !task.Completed { + t.Error("任务应该是已完成状态") + } + + if task.CompletedAt == nil { + t.Error("已完成的任务应该有完成时间") + } + + // 再次完成(应该没有变化) + oldCompletedAt := *task.CompletedAt + time.Sleep(1 * time.Millisecond) + task.Complete() + + if !task.CompletedAt.Equal(oldCompletedAt) { + t.Error("重复完成任务不应该改变完成时间") + } +} + +// TestTaskUpdate 测试任务更新 +func TestTaskUpdate(t *testing.T) { + task := todo.NewTask(1, "原始内容") + oldUpdatedAt := task.UpdatedAt + + time.Sleep(1 * time.Millisecond) + task.Update("新内容") + + if task.Content != "新内容" { + t.Errorf("期望任务内容为'新内容',实际为'%s'", task.Content) + } + + if !task.UpdatedAt.After(oldUpdatedAt) { + t.Error("更新任务后,更新时间应该改变") + } +} + +// TestTaskPriority 测试任务优先级 +func TestTaskPriority(t *testing.T) { + task := todo.NewTask(1, "测试任务") + + task.SetPriority(todo.High) + + if task.Priority != todo.High { + t.Errorf("期望优先级为High,实际为%v", task.Priority) + } +} + +// TestTaskTags 测试任务标签 +func TestTaskTags(t *testing.T) { + task := todo.NewTask(1, "测试任务") + + // 添加标签 + task.AddTag("工作") + task.AddTag("重要") + + if len(task.Tags) != 2 { + t.Errorf("期望标签数量为2,实际为%d", len(task.Tags)) + } + + if !task.HasTag("工作") { + t.Error("任务应该包含'工作'标签") + } + + // 添加重复标签 + task.AddTag("工作") + if len(task.Tags) != 2 { + t.Error("添加重复标签不应该增加标签数量") + } + + // 移除标签 + task.RemoveTag("工作") + if task.HasTag("工作") { + t.Error("移除标签后,任务不应该包含该标签") + } + + if len(task.Tags) != 1 { + t.Errorf("移除标签后,期望标签数量为1,实际为%d", len(task.Tags)) + } +} + +// TestTaskValidation 测试任务验证 +func TestTaskValidation(t *testing.T) { + // 有效任务 + validTask := todo.NewTask(1, "有效任务") + if err := validTask.Validate(); err != nil { + t.Errorf("有效任务验证失败: %v", err) + } + + // 无效ID + invalidTask := &todo.Task{ + ID: 0, + Content: "测试", + } + if err := invalidTask.Validate(); err == nil { + t.Error("ID为0的任务应该验证失败") + } + + // 空内容 + invalidTask = &todo.Task{ + ID: 1, + Content: "", + } + if err := invalidTask.Validate(); err == nil { + t.Error("内容为空的任务应该验证失败") + } +} + +// TestManager 测试任务管理器 +func TestManager(t *testing.T) { + // 使用内存存储进行测试 + storage := todo.NewMemoryStorage() + manager := &todo.Manager{} + + // 这里需要修改Manager结构以支持注入存储 + // 为了简化测试,我们直接测试基本功能 + + // 创建临时管理器 + tempManager, err := todo.NewManager("test_todos.json") + if err != nil { + t.Fatalf("创建管理器失败: %v", err) + } + + // 添加任务 + task1, err := tempManager.AddTask("任务1") + if err != nil { + t.Errorf("添加任务失败: %v", err) + } + + if task1.ID != 1 { + t.Errorf("期望第一个任务ID为1,实际为%d", task1.ID) + } + + // 添加更多任务 + tempManager.AddTask("任务2") + tempManager.AddTask("任务3") + + // 获取所有任务 + tasks := tempManager.GetTasks() + if len(tasks) != 3 { + t.Errorf("期望任务数量为3,实际为%d", len(tasks)) + } + + // 完成任务 + completedTask, err := tempManager.CompleteTask(1) + if err != nil { + t.Errorf("完成任务失败: %v", err) + } + + if !completedTask.Completed { + t.Error("任务应该是已完成状态") + } + + // 删除任务 + deletedTask, err := tempManager.DeleteTask(2) + if err != nil { + t.Errorf("删除任务失败: %v", err) + } + + if deletedTask.ID != 2 { + t.Errorf("期望删除的任务ID为2,实际为%d", deletedTask.ID) + } + + // 检查剩余任务 + tasks = tempManager.GetTasks() + if len(tasks) != 2 { + t.Errorf("删除后期望任务数量为2,实际为%d", len(tasks)) + } +} + +// TestManagerSearch 测试搜索功能 +func TestManagerSearch(t *testing.T) { + tempManager, err := todo.NewManager("test_search.json") + if err != nil { + t.Fatalf("创建管理器失败: %v", err) + } + + // 添加测试任务 + tempManager.AddTask("学习Go语言") + tempManager.AddTask("完成项目文档") + tempManager.AddTask("Go语言练习") + + // 搜索包含"Go"的任务 + results := tempManager.SearchTasks("Go") + if len(results) != 2 { + t.Errorf("期望搜索结果数量为2,实际为%d", len(results)) + } + + // 搜索不存在的关键词 + results = tempManager.SearchTasks("Python") + if len(results) != 0 { + t.Errorf("期望搜索结果数量为0,实际为%d", len(results)) + } + + // 空关键词搜索 + results = tempManager.SearchTasks("") + if len(results) != 0 { + t.Errorf("空关键词搜索期望结果数量为0,实际为%d", len(results)) + } +} + +// TestManagerStatistics 测试统计功能 +func TestManagerStatistics(t *testing.T) { + tempManager, err := todo.NewManager("test_stats.json") + if err != nil { + t.Fatalf("创建管理器失败: %v", err) + } + + // 添加测试任务 + tempManager.AddTask("任务1") + tempManager.AddTask("任务2") + tempManager.AddTask("任务3") + + // 完成一个任务 + tempManager.CompleteTask(1) + + // 获取统计信息 + stats := tempManager.GetStatistics() + + if stats.Total != 3 { + t.Errorf("期望总任务数为3,实际为%d", stats.Total) + } + + if stats.Completed != 1 { + t.Errorf("期望已完成任务数为1,实际为%d", stats.Completed) + } + + if stats.Pending != 2 { + t.Errorf("期望待完成任务数为2,实际为%d", stats.Pending) + } + + expectedRate := float64(1) / float64(3) * 100 + if stats.CompletionRate != expectedRate { + t.Errorf("期望完成率为%.1f%%,实际为%.1f%%", expectedRate, stats.CompletionRate) + } +} + +// TestMemoryStorage 测试内存存储 +func TestMemoryStorage(t *testing.T) { + storage := todo.NewMemoryStorage() + + // 创建测试任务 + tasks := []todo.Task{ + *todo.NewTask(1, "任务1"), + *todo.NewTask(2, "任务2"), + } + + // 保存任务 + err := storage.Save(tasks) + if err != nil { + t.Errorf("保存任务失败: %v", err) + } + + // 加载任务 + loadedTasks, err := storage.Load() + if err != nil { + t.Errorf("加载任务失败: %v", err) + } + + if len(loadedTasks) != 2 { + t.Errorf("期望加载任务数量为2,实际为%d", len(loadedTasks)) + } + + if loadedTasks[0].Content != "任务1" { + t.Errorf("期望第一个任务内容为'任务1',实际为'%s'", loadedTasks[0].Content) + } +} + +// TestPriorityString 测试优先级字符串表示 +func TestPriorityString(t *testing.T) { + tests := []struct { + priority todo.Priority + expected string + }{ + {todo.Low, "低"}, + {todo.Medium, "中"}, + {todo.High, "高"}, + {todo.Urgent, "紧急"}, + } + + for _, tt := range tests { + t.Run(tt.expected, func(t *testing.T) { + if tt.priority.String() != tt.expected { + t.Errorf("期望优先级字符串为'%s',实际为'%s'", tt.expected, tt.priority.String()) + } + }) + } +} + +// BenchmarkTaskCreation 任务创建基准测试 +func BenchmarkTaskCreation(b *testing.B) { + for i := 0; i < b.N; i++ { + todo.NewTask(i, fmt.Sprintf("任务%d", i)) + } +} + +// BenchmarkManagerAddTask 管理器添加任务基准测试 +func BenchmarkManagerAddTask(b *testing.B) { + manager, err := todo.NewManager("bench_test.json") + if err != nil { + b.Fatalf("创建管理器失败: %v", err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + manager.AddTask(fmt.Sprintf("基准测试任务%d", i)) + } +} + +// BenchmarkManagerSearch 搜索基准测试 +func BenchmarkManagerSearch(b *testing.B) { + manager, err := todo.NewManager("bench_search.json") + if err != nil { + b.Fatalf("创建管理器失败: %v", err) + } + + // 添加测试数据 + for i := 0; i < 1000; i++ { + manager.AddTask(fmt.Sprintf("测试任务%d", i)) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + manager.SearchTasks("测试") + } +} + +// ExampleTask_String 任务字符串表示示例 +func ExampleTask_String() { + task := todo.NewTask(1, "学习Go语言") + fmt.Println(task.String()) + // Output: [1] ⏳ 学习Go语言 (优先级: 中) +} + +// ExampleManager_AddTask 添加任务示例 +func ExampleManager_AddTask() { + manager, _ := todo.NewManager("example.json") + task, _ := manager.AddTask("完成项目文档") + fmt.Printf("任务已添加: %s (ID: %d)", task.Content, task.ID) + // Output: 任务已添加: 完成项目文档 (ID: 1) +} diff --git a/golang-learning/10-projects/02-todo-list/ui/cli.go b/golang-learning/10-projects/02-todo-list/ui/cli.go new file mode 100644 index 0000000..d2fb1c3 --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/ui/cli.go @@ -0,0 +1,315 @@ +/* +cli.go - 命令行界面 +实现了彩色的命令行用户界面 +*/ + +package ui + +import ( + "fmt" + "os" + "os/exec" + "runtime" + "strings" + + "../todo" +) + +// CLI 命令行界面 +type CLI struct { + colors *Colors +} + +// NewCLI 创建新的命令行界面 +func NewCLI() *CLI { + return &CLI{ + colors: NewColors(), + } +} + +// ShowWelcome 显示欢迎信息 +func (c *CLI) ShowWelcome() { + c.Clear() + fmt.Println(c.colors.Cyan("=== 待办事项管理器 ===")) + fmt.Println() + fmt.Println("📝 一个简单而强大的待办事项管理工具") + fmt.Println() + c.ShowHelp() +} + +// ShowHelp 显示帮助信息 +func (c *CLI) ShowHelp() { + fmt.Println(c.colors.Yellow("命令列表:")) + fmt.Println(" " + c.colors.Green("add <任务>") + " - 添加新任务") + fmt.Println(" " + c.colors.Green("list [状态]") + " - 显示任务列表 (all/done/pending)") + fmt.Println(" " + c.colors.Green("done ") + " - 标记任务完成") + fmt.Println(" " + c.colors.Green("delete ") + " - 删除任务") + fmt.Println(" " + c.colors.Green("edit <新内容>") + " - 编辑任务") + fmt.Println(" " + c.colors.Green("search <关键词>") + " - 搜索任务") + fmt.Println(" " + c.colors.Green("stats") + " - 显示统计信息") + fmt.Println(" " + c.colors.Green("clear") + " - 清屏") + fmt.Println(" " + c.colors.Green("help") + " - 显示帮助") + fmt.Println(" " + c.colors.Green("quit") + " - 退出程序") + fmt.Println() +} + +// ShowTaskList 显示任务列表 +func (c *CLI) ShowTaskList(tasks []todo.Task) { + if len(tasks) == 0 { + c.ShowInfo("暂无任务") + return + } + + fmt.Println(c.colors.Cyan("📋 待办事项列表:")) + + for _, task := range tasks { + c.showTask(task) + } + fmt.Println() +} + +// showTask 显示单个任务 +func (c *CLI) showTask(task todo.Task) { + var status, statusColor string + if task.Completed { + status = "✅" + statusColor = "green" + } else { + status = "⏳" + statusColor = "yellow" + } + + // 根据优先级选择颜色 + var priorityColor string + switch task.Priority { + case todo.Urgent: + priorityColor = "red" + case todo.High: + priorityColor = "magenta" + case todo.Medium: + priorityColor = "blue" + case todo.Low: + priorityColor = "cyan" + default: + priorityColor = "white" + } + + // 格式化任务内容 + content := task.Content + if len(content) > 50 { + content = content[:47] + "..." + } + + // 显示任务 + fmt.Printf(" [%s] %s %s %s\n", + c.colors.Blue(fmt.Sprintf("%d", task.ID)), + c.colorize(status, statusColor), + c.colorize(content, "white"), + c.colorize(fmt.Sprintf("(优先级: %s)", task.Priority), priorityColor)) + + // 显示标签 + if len(task.Tags) > 0 { + fmt.Printf(" %s %s\n", + c.colors.Gray("标签:"), + c.colors.Cyan(strings.Join(task.Tags, ", "))) + } + + // 显示描述 + if task.Description != "" { + desc := task.Description + if len(desc) > 60 { + desc = desc[:57] + "..." + } + fmt.Printf(" %s %s\n", + c.colors.Gray("描述:"), + c.colors.White(desc)) + } +} + +// ShowStatistics 显示统计信息 +func (c *CLI) ShowStatistics(stats todo.Statistics) { + fmt.Println(c.colors.Cyan("📊 统计信息:")) + fmt.Printf(" 总任务数: %s\n", c.colors.Blue(fmt.Sprintf("%d", stats.Total))) + fmt.Printf(" 已完成: %s\n", c.colors.Green(fmt.Sprintf("%d", stats.Completed))) + fmt.Printf(" 待完成: %s\n", c.colors.Yellow(fmt.Sprintf("%d", stats.Pending))) + fmt.Printf(" 完成率: %s\n", c.colors.Magenta(fmt.Sprintf("%.1f%%", stats.CompletionRate))) + + if len(stats.ByPriority) > 0 { + fmt.Println() + fmt.Println(c.colors.Yellow("按优先级统计:")) + for priority, count := range stats.ByPriority { + var color string + switch priority { + case "紧急": + color = "red" + case "高": + color = "magenta" + case "中": + color = "blue" + case "低": + color = "cyan" + default: + color = "white" + } + fmt.Printf(" %s: %s\n", + c.colorize(priority, color), + c.colors.White(fmt.Sprintf("%d", count))) + } + } + + if stats.AverageCompletionTime > 0 { + fmt.Printf("\n 平均完成时间: %s\n", + c.colors.Cyan(stats.AverageCompletionTime.String())) + } + + fmt.Println() +} + +// ShowSuccess 显示成功消息 +func (c *CLI) ShowSuccess(message string) { + fmt.Printf("%s %s\n", c.colors.Green("✅"), message) +} + +// ShowError 显示错误消息 +func (c *CLI) ShowError(message string) { + fmt.Printf("%s %s\n", c.colors.Red("❌"), c.colors.Red(message)) +} + +// ShowWarning 显示警告消息 +func (c *CLI) ShowWarning(message string) { + fmt.Printf("%s %s\n", c.colors.Yellow("⚠️"), c.colors.Yellow(message)) +} + +// ShowInfo 显示信息消息 +func (c *CLI) ShowInfo(message string) { + fmt.Printf("%s %s\n", c.colors.Blue("ℹ️"), message) +} + +// Clear 清屏 +func (c *CLI) Clear() { + var cmd *exec.Cmd + switch runtime.GOOS { + case "windows": + cmd = exec.Command("cmd", "/c", "cls") + default: + cmd = exec.Command("clear") + } + cmd.Stdout = os.Stdout + cmd.Run() +} + +// colorize 根据颜色名称着色文本 +func (c *CLI) colorize(text, color string) string { + switch strings.ToLower(color) { + case "red": + return c.colors.Red(text) + case "green": + return c.colors.Green(text) + case "yellow": + return c.colors.Yellow(text) + case "blue": + return c.colors.Blue(text) + case "magenta": + return c.colors.Magenta(text) + case "cyan": + return c.colors.Cyan(text) + case "white": + return c.colors.White(text) + case "gray": + return c.colors.Gray(text) + default: + return text + } +} + +// ShowTaskDetail 显示任务详细信息 +func (c *CLI) ShowTaskDetail(task todo.Task) { + fmt.Println(c.colors.Cyan("📋 任务详情:")) + fmt.Println(c.colors.Gray("─────────────────────────────────────")) + + fmt.Printf("ID: %s\n", c.colors.Blue(fmt.Sprintf("%d", task.ID))) + fmt.Printf("内容: %s\n", c.colors.White(task.Content)) + + if task.Completed { + fmt.Printf("状态: %s\n", c.colors.Green("✅ 已完成")) + } else { + fmt.Printf("状态: %s\n", c.colors.Yellow("⏳ 待完成")) + } + + var priorityColor string + switch task.Priority { + case todo.Urgent: + priorityColor = "red" + case todo.High: + priorityColor = "magenta" + case todo.Medium: + priorityColor = "blue" + case todo.Low: + priorityColor = "cyan" + } + fmt.Printf("优先级: %s\n", c.colorize(task.Priority.String(), priorityColor)) + + fmt.Printf("创建时间: %s\n", c.colors.Gray(task.CreatedAt.Format("2006-01-02 15:04:05"))) + fmt.Printf("更新时间: %s\n", c.colors.Gray(task.UpdatedAt.Format("2006-01-02 15:04:05"))) + + if task.Completed && task.CompletedAt != nil { + fmt.Printf("完成时间: %s\n", c.colors.Green(task.CompletedAt.Format("2006-01-02 15:04:05"))) + fmt.Printf("完成耗时: %s\n", c.colors.Cyan(task.CompletionTime().String())) + } + + if task.Description != "" { + fmt.Printf("描述: %s\n", c.colors.White(task.Description)) + } + + if len(task.Tags) > 0 { + fmt.Printf("标签: %s\n", c.colors.Cyan(strings.Join(task.Tags, ", "))) + } + + fmt.Println(c.colors.Gray("─────────────────────────────────────")) + fmt.Println() +} + +// ShowProgress 显示进度条 +func (c *CLI) ShowProgress(current, total int, label string) { + if total == 0 { + return + } + + percentage := float64(current) / float64(total) * 100 + barLength := 30 + filledLength := int(float64(barLength) * float64(current) / float64(total)) + + bar := strings.Repeat("█", filledLength) + strings.Repeat("░", barLength-filledLength) + + fmt.Printf("\r%s [%s] %.1f%% (%d/%d)", + label, + c.colors.Green(bar), + percentage, + current, + total) +} + +// ConfirmAction 确认操作 +func (c *CLI) ConfirmAction(message string) bool { + fmt.Printf("%s %s (y/N): ", c.colors.Yellow("❓"), message) + + var response string + fmt.Scanln(&response) + + response = strings.ToLower(strings.TrimSpace(response)) + return response == "y" || response == "yes" +} + +// ShowBanner 显示横幅 +func (c *CLI) ShowBanner(text string) { + length := len(text) + 4 + border := strings.Repeat("=", length) + + fmt.Println(c.colors.Cyan(border)) + fmt.Printf("%s %s %s\n", + c.colors.Cyan("="), + c.colors.White(text), + c.colors.Cyan("=")) + fmt.Println(c.colors.Cyan(border)) + fmt.Println() +} diff --git a/golang-learning/10-projects/02-todo-list/ui/colors.go b/golang-learning/10-projects/02-todo-list/ui/colors.go new file mode 100644 index 0000000..3b79038 --- /dev/null +++ b/golang-learning/10-projects/02-todo-list/ui/colors.go @@ -0,0 +1,382 @@ +/* +colors.go - 颜色输出 +实现了命令行的彩色文本输出 +*/ + +package ui + +import ( + "fmt" + "os" + "runtime" + "strings" +) + +// Colors 颜色输出器 +type Colors struct { + enabled bool +} + +// ANSI 颜色代码 +const ( + ColorReset = "\033[0m" + ColorRed = "\033[31m" + ColorGreen = "\033[32m" + ColorYellow = "\033[33m" + ColorBlue = "\033[34m" + ColorMagenta = "\033[35m" + ColorCyan = "\033[36m" + ColorWhite = "\033[37m" + ColorGray = "\033[90m" + + // 背景色 + BgRed = "\033[41m" + BgGreen = "\033[42m" + BgYellow = "\033[43m" + BgBlue = "\033[44m" + BgMagenta = "\033[45m" + BgCyan = "\033[46m" + BgWhite = "\033[47m" + + // 样式 + StyleBold = "\033[1m" + StyleDim = "\033[2m" + StyleItalic = "\033[3m" + StyleUnderline = "\033[4m" + StyleBlink = "\033[5m" + StyleReverse = "\033[7m" + StyleStrike = "\033[9m" +) + +// NewColors 创建新的颜色输出器 +func NewColors() *Colors { + return &Colors{ + enabled: supportsColor(), + } +} + +// supportsColor 检查终端是否支持颜色 +func supportsColor() bool { + // Windows 命令提示符通常不支持 ANSI 颜色 + if runtime.GOOS == "windows" { + // 检查是否在支持颜色的终端中运行 + term := os.Getenv("TERM") + if term == "" { + return false + } + } + + // 检查环境变量 + if os.Getenv("NO_COLOR") != "" { + return false + } + + if os.Getenv("FORCE_COLOR") != "" { + return true + } + + // 检查是否连接到终端 + if !isTerminal() { + return false + } + + return true +} + +// isTerminal 检查是否连接到终端 +func isTerminal() bool { + // 简单检查:如果 stdout 是文件,可能不是终端 + stat, err := os.Stdout.Stat() + if err != nil { + return false + } + + // 检查是否是字符设备 + return (stat.Mode() & os.ModeCharDevice) != 0 +} + +// colorize 为文本添加颜色 +func (c *Colors) colorize(text, color string) string { + if !c.enabled { + return text + } + return color + text + ColorReset +} + +// Red 红色文本 +func (c *Colors) Red(text string) string { + return c.colorize(text, ColorRed) +} + +// Green 绿色文本 +func (c *Colors) Green(text string) string { + return c.colorize(text, ColorGreen) +} + +// Yellow 黄色文本 +func (c *Colors) Yellow(text string) string { + return c.colorize(text, ColorYellow) +} + +// Blue 蓝色文本 +func (c *Colors) Blue(text string) string { + return c.colorize(text, ColorBlue) +} + +// Magenta 洋红色文本 +func (c *Colors) Magenta(text string) string { + return c.colorize(text, ColorMagenta) +} + +// Cyan 青色文本 +func (c *Colors) Cyan(text string) string { + return c.colorize(text, ColorCyan) +} + +// White 白色文本 +func (c *Colors) White(text string) string { + return c.colorize(text, ColorWhite) +} + +// Gray 灰色文本 +func (c *Colors) Gray(text string) string { + return c.colorize(text, ColorGray) +} + +// Bold 粗体文本 +func (c *Colors) Bold(text string) string { + return c.colorize(text, StyleBold) +} + +// Dim 暗淡文本 +func (c *Colors) Dim(text string) string { + return c.colorize(text, StyleDim) +} + +// Italic 斜体文本 +func (c *Colors) Italic(text string) string { + return c.colorize(text, StyleItalic) +} + +// Underline 下划线文本 +func (c *Colors) Underline(text string) string { + return c.colorize(text, StyleUnderline) +} + +// Blink 闪烁文本 +func (c *Colors) Blink(text string) string { + return c.colorize(text, StyleBlink) +} + +// Reverse 反色文本 +func (c *Colors) Reverse(text string) string { + return c.colorize(text, StyleReverse) +} + +// Strike 删除线文本 +func (c *Colors) Strike(text string) string { + return c.colorize(text, StyleStrike) +} + +// BgRed 红色背景 +func (c *Colors) BgRedText(text string) string { + return c.colorize(text, BgRed) +} + +// BgGreen 绿色背景 +func (c *Colors) BgGreenText(text string) string { + return c.colorize(text, BgGreen) +} + +// BgYellow 黄色背景 +func (c *Colors) BgYellowText(text string) string { + return c.colorize(text, BgYellow) +} + +// BgBlue 蓝色背景 +func (c *Colors) BgBlueText(text string) string { + return c.colorize(text, BgBlue) +} + +// BgMagenta 洋红色背景 +func (c *Colors) BgMagentaText(text string) string { + return c.colorize(text, BgMagenta) +} + +// BgCyan 青色背景 +func (c *Colors) BgCyanText(text string) string { + return c.colorize(text, BgCyan) +} + +// BgWhite 白色背景 +func (c *Colors) BgWhiteText(text string) string { + return c.colorize(text, BgWhite) +} + +// Combine 组合多种样式 +func (c *Colors) Combine(text string, styles ...string) string { + if !c.enabled { + return text + } + + var combined string + for _, style := range styles { + combined += style + } + + return combined + text + ColorReset +} + +// Success 成功样式(绿色粗体) +func (c *Colors) Success(text string) string { + return c.Combine(text, ColorGreen, StyleBold) +} + +// Error 错误样式(红色粗体) +func (c *Colors) Error(text string) string { + return c.Combine(text, ColorRed, StyleBold) +} + +// Warning 警告样式(黄色粗体) +func (c *Colors) Warning(text string) string { + return c.Combine(text, ColorYellow, StyleBold) +} + +// Info 信息样式(蓝色) +func (c *Colors) Info(text string) string { + return c.Blue(text) +} + +// Highlight 高亮样式(青色粗体) +func (c *Colors) Highlight(text string) string { + return c.Combine(text, ColorCyan, StyleBold) +} + +// Muted 静音样式(灰色暗淡) +func (c *Colors) Muted(text string) string { + return c.Combine(text, ColorGray, StyleDim) +} + +// Enable 启用颜色输出 +func (c *Colors) Enable() { + c.enabled = true +} + +// Disable 禁用颜色输出 +func (c *Colors) Disable() { + c.enabled = false +} + +// IsEnabled 检查颜色输出是否启用 +func (c *Colors) IsEnabled() bool { + return c.enabled +} + +// Printf 带颜色的格式化输出 +func (c *Colors) Printf(color, format string, args ...interface{}) { + text := fmt.Sprintf(format, args...) + switch color { + case "red": + fmt.Print(c.Red(text)) + case "green": + fmt.Print(c.Green(text)) + case "yellow": + fmt.Print(c.Yellow(text)) + case "blue": + fmt.Print(c.Blue(text)) + case "magenta": + fmt.Print(c.Magenta(text)) + case "cyan": + fmt.Print(c.Cyan(text)) + case "white": + fmt.Print(c.White(text)) + case "gray": + fmt.Print(c.Gray(text)) + default: + fmt.Print(text) + } +} + +// Println 带颜色的输出(带换行) +func (c *Colors) Println(color, text string) { + c.Printf(color, "%s\n", text) +} + +// Rainbow 彩虹文本(每个字符不同颜色) +func (c *Colors) Rainbow(text string) string { + if !c.enabled { + return text + } + + colors := []string{ColorRed, ColorYellow, ColorGreen, ColorCyan, ColorBlue, ColorMagenta} + var result string + + for i, char := range text { + color := colors[i%len(colors)] + result += color + string(char) + ColorReset + } + + return result +} + +// Gradient 渐变文本(从一种颜色到另一种颜色) +func (c *Colors) Gradient(text, startColor, endColor string) string { + if !c.enabled { + return text + } + + // 简化实现:只在开头和结尾使用不同颜色 + if len(text) <= 2 { + return c.colorize(text, startColor) + } + + mid := len(text) / 2 + return c.colorize(text[:mid], startColor) + c.colorize(text[mid:], endColor) +} + +// Table 表格样式输出 +func (c *Colors) Table(headers []string, rows [][]string) { + // 计算列宽 + colWidths := make([]int, len(headers)) + for i, header := range headers { + colWidths[i] = len(header) + } + + for _, row := range rows { + for i, cell := range row { + if i < len(colWidths) && len(cell) > colWidths[i] { + colWidths[i] = len(cell) + } + } + } + + // 输出表头 + fmt.Print(c.Bold(c.Cyan("│"))) + for i, header := range headers { + fmt.Printf(" %-*s ", colWidths[i], header) + fmt.Print(c.Bold(c.Cyan("│"))) + } + fmt.Println() + + // 输出分隔线 + fmt.Print(c.Cyan("├")) + for i, width := range colWidths { + fmt.Print(c.Cyan(strings.Repeat("─", width+2))) + if i < len(colWidths)-1 { + fmt.Print(c.Cyan("┼")) + } + } + fmt.Println(c.Cyan("┤")) + + // 输出数据行 + for _, row := range rows { + fmt.Print(c.Cyan("│")) + for i, cell := range row { + if i < len(colWidths) { + fmt.Printf(" %-*s ", colWidths[i], cell) + } + fmt.Print(c.Cyan("│")) + } + fmt.Println() + } +} diff --git a/golang-learning/10-projects/03-web-server/README.md b/golang-learning/10-projects/03-web-server/README.md index 0e879f9..48a740c 100644 --- a/golang-learning/10-projects/03-web-server/README.md +++ b/golang-learning/10-projects/03-web-server/README.md @@ -1,38 +1,165 @@ # Web 服务器项目 -一个简单的 HTTP Web 服务器,提供 RESTful API。 +这是一个简单的 HTTP Web 服务器项目,演示了 Go 语言在网络编程、并发处理和 RESTful API 开发方面的应用。 -## 功能特性 -- HTTP 服务器 -- RESTful API 端点 +## 项目特性 + +- HTTP 服务器基础功能 +- RESTful API 设计 - JSON 数据处理 -- 路由处理 +- 路由管理 - 中间件支持 - 静态文件服务 +- 并发请求处理 +- 错误处理和日志记录 +- 简单的用户管理系统 -## API 端点 -- `GET /` - 首页 -- `GET /api/users` - 获取用户列表 -- `POST /api/users` - 创建新用户 -- `GET /api/users/{id}` - 获取特定用户 -- `PUT /api/users/{id}` - 更新用户 -- `DELETE /api/users/{id}` - 删除用户 +## 项目结构 -## 运行方法 -```bash -cd 03-web-server -go run main.go +``` +03-web-server/ +├── README.md # 项目说明文档 +├── main.go # 主程序入口 +├── server/ # 服务器核心包 +│ ├── server.go # HTTP 服务器 +│ ├── router.go # 路由管理 +│ ├── middleware.go # 中间件 +│ └── handlers.go # 请求处理器 +├── models/ # 数据模型 +│ └── user.go # 用户模型 +├── static/ # 静态文件 +│ ├── index.html # 首页 +│ ├── style.css # 样式文件 +│ └── script.js # JavaScript 文件 +├── data/ # 数据文件 +│ └── users.json # 用户数据 +└── server_test.go # 测试文件 ``` -服务器将在 http://localhost:8080 启动 +## 运行方法 + +```bash +# 进入项目目录 +cd 10-projects/03-web-server + +# 运行程序 +go run main.go + +# 或者编译后运行 +go build -o webserver main.go +./webserver +``` + +## API 接口 + +### 用户管理 API + +- `GET /api/users` - 获取所有用户 +- `GET /api/users/{id}` - 获取指定用户 +- `POST /api/users` - 创建新用户 +- `PUT /api/users/{id}` - 更新用户信息 +- `DELETE /api/users/{id}` - 删除用户 + +### 其他接口 + +- `GET /` - 首页 +- `GET /health` - 健康检查 +- `GET /api/stats` - 服务器统计信息 +- `GET /static/*` - 静态文件服务 ## 使用示例 + +### 启动服务器 + ```bash -# 获取用户列表 +$ go run main.go +🚀 服务器启动成功 +📍 地址: http://localhost:8080 +📊 健康检查: http://localhost:8080/health +📚 API文档: http://localhost:8080/api +``` + +### API 调用示例 + +```bash +# 获取所有用户 curl http://localhost:8080/api/users # 创建新用户 curl -X POST http://localhost:8080/api/users \ -H "Content-Type: application/json" \ - -d '{"name":"张三","email":"zhangsan@example.com"}' -``` \ No newline at end of file + -d '{"name":"张三","email":"zhangsan@example.com","age":25}' + +# 获取指定用户 +curl http://localhost:8080/api/users/1 + +# 更新用户信息 +curl -X PUT http://localhost:8080/api/users/1 \ + -H "Content-Type: application/json" \ + -d '{"name":"张三","email":"zhangsan@gmail.com","age":26}' + +# 删除用户 +curl -X DELETE http://localhost:8080/api/users/1 + +# 健康检查 +curl http://localhost:8080/health + +# 服务器统计 +curl http://localhost:8080/api/stats +``` + +### 响应示例 + +```json +// GET /api/users +{ + "status": "success", + "data": [ + { + "id": 1, + "name": "张三", + "email": "zhangsan@example.com", + "age": 25, + "created_at": "2024-01-01T10:00:00Z", + "updated_at": "2024-01-01T10:00:00Z" + } + ], + "count": 1 +} + +// GET /health +{ + "status": "healthy", + "timestamp": "2024-01-01T10:00:00Z", + "uptime": "1h30m45s", + "version": "1.0.0" +} +``` + +## 学习要点 + +这个项目综合运用了以下 Go 语言特性: + +1. **HTTP 服务器**: 使用 `net/http` 包创建 Web 服务器 +2. **路由管理**: 实现 RESTful 路由和参数解析 +3. **JSON 处理**: 请求和响应的 JSON 序列化/反序列化 +4. **中间件模式**: 日志记录、CORS、认证等中间件 +5. **并发处理**: 利用 goroutine 处理并发请求 +6. **错误处理**: HTTP 错误响应和日志记录 +7. **文件操作**: 静态文件服务和数据持久化 +8. **结构体和接口**: 数据模型和服务接口设计 +9. **包管理**: 多包项目结构和依赖管理 +10. **测试**: HTTP 服务器和 API 的测试 + +## 扩展建议 + +1. 添加用户认证和授权(JWT) +2. 实现数据库集成(MySQL、PostgreSQL) +3. 添加缓存支持(Redis) +4. 实现 WebSocket 支持 +5. 添加 API 限流和熔断 +6. 集成 Swagger API 文档 +7. 添加配置文件支持 +8. 实现优雅关闭 +9. 添加监控和指标收集 +10. 支持 HTTPS 和 HTTP/2 \ No newline at end of file diff --git a/golang-learning/10-projects/03-web-server/data/.gitkeep b/golang-learning/10-projects/03-web-server/data/.gitkeep new file mode 100644 index 0000000..640d680 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/data/.gitkeep @@ -0,0 +1,2 @@ +# 这个文件用于保持 data 目录在 git 中被跟踪 +# 实际的 users.json 文件会在程序运行时自动创建 \ No newline at end of file diff --git a/golang-learning/10-projects/03-web-server/go.mod b/golang-learning/10-projects/03-web-server/go.mod new file mode 100644 index 0000000..6b16b59 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/go.mod @@ -0,0 +1,5 @@ +module webserver + +go 1.19 + +require github.com/gorilla/mux v1.8.0 \ No newline at end of file diff --git a/golang-learning/10-projects/03-web-server/main.go b/golang-learning/10-projects/03-web-server/main.go new file mode 100644 index 0000000..2427460 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/main.go @@ -0,0 +1,54 @@ +/* +main.go - Web服务器主程序 +这是一个简单的HTTP Web服务器,演示了Go语言的网络编程应用 +*/ + +package main + +import ( + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "webserver/server" +) + +func main() { + // 创建服务器实例 + srv := server.NewServer(":8080") + + // 设置路由 + srv.SetupRoutes() + + // 启动信息 + fmt.Println("🚀 Go Web服务器启动中...") + fmt.Println("📍 地址: http://localhost:8080") + fmt.Println("📊 健康检查: http://localhost:8080/health") + fmt.Println("📚 API文档: http://localhost:8080/api") + fmt.Println("按 Ctrl+C 停止服务器") + fmt.Println() + + // 启动服务器(在goroutine中) + go func() { + if err := srv.Start(); err != nil { + log.Printf("❌ 服务器启动失败: %v", err) + os.Exit(1) + } + }() + + // 等待中断信号 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + fmt.Println("\n🛑 正在关闭服务器...") + + // 优雅关闭服务器 + if err := srv.Shutdown(); err != nil { + log.Printf("❌ 服务器关闭失败: %v", err) + } else { + fmt.Println("✅ 服务器已安全关闭") + } +} diff --git a/golang-learning/10-projects/03-web-server/models/user.go b/golang-learning/10-projects/03-web-server/models/user.go new file mode 100644 index 0000000..4d76db3 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/models/user.go @@ -0,0 +1,293 @@ +/* +user.go - 用户数据模型 +定义了用户的数据结构和相关操作 +*/ + +package models + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + "time" +) + +// User 用户结构体 +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Age int `json:"age"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// UserStorage 用户存储 +type UserStorage struct { + users []User + nextID int + filePath string +} + +var storage *UserStorage + +// init 初始化用户存储 +func init() { + storage = &UserStorage{ + users: make([]User, 0), + nextID: 1, + filePath: "data/users.json", + } + + // 创建数据目录 + os.MkdirAll("data", 0755) + + // 加载现有数据 + storage.load() +} + +// Validate 验证用户数据 +func (u *User) Validate() error { + if strings.TrimSpace(u.Name) == "" { + return fmt.Errorf("用户名不能为空") + } + + if len(u.Name) > 50 { + return fmt.Errorf("用户名长度不能超过50个字符") + } + + if strings.TrimSpace(u.Email) == "" { + return fmt.Errorf("邮箱不能为空") + } + + if !isValidEmail(u.Email) { + return fmt.Errorf("邮箱格式无效") + } + + if u.Age < 0 || u.Age > 150 { + return fmt.Errorf("年龄必须在0-150之间") + } + + return nil +} + +// isValidEmail 简单的邮箱格式验证 +func isValidEmail(email string) bool { + return strings.Contains(email, "@") && strings.Contains(email, ".") +} + +// GetAllUsers 获取所有用户 +func GetAllUsers() ([]User, error) { + return storage.users, nil +} + +// GetUserByID 根据ID获取用户 +func GetUserByID(id int) (*User, error) { + for _, user := range storage.users { + if user.ID == id { + return &user, nil + } + } + return nil, fmt.Errorf("用户不存在") +} + +// CreateUser 创建新用户 +func CreateUser(user User) (*User, error) { + // 检查邮箱是否已存在 + for _, existingUser := range storage.users { + if existingUser.Email == user.Email { + return nil, fmt.Errorf("邮箱已存在") + } + } + + // 设置用户信息 + user.ID = storage.nextID + user.CreatedAt = time.Now() + user.UpdatedAt = time.Now() + + // 添加到存储 + storage.users = append(storage.users, user) + storage.nextID++ + + // 保存到文件 + if err := storage.save(); err != nil { + return nil, err + } + + return &user, nil +} + +// UpdateUser 更新用户信息 +func UpdateUser(user User) (*User, error) { + // 查找用户 + for i, existingUser := range storage.users { + if existingUser.ID == user.ID { + // 检查邮箱是否被其他用户使用 + for _, otherUser := range storage.users { + if otherUser.ID != user.ID && otherUser.Email == user.Email { + return nil, fmt.Errorf("邮箱已被其他用户使用") + } + } + + // 保留创建时间,更新其他信息 + user.CreatedAt = existingUser.CreatedAt + user.UpdatedAt = time.Now() + + // 更新用户 + storage.users[i] = user + + // 保存到文件 + if err := storage.save(); err != nil { + return nil, err + } + + return &user, nil + } + } + + return nil, fmt.Errorf("用户不存在") +} + +// DeleteUser 删除用户 +func DeleteUser(id int) error { + // 查找并删除用户 + for i, user := range storage.users { + if user.ID == id { + // 从切片中删除用户 + storage.users = append(storage.users[:i], storage.users[i+1:]...) + + // 保存到文件 + return storage.save() + } + } + + return fmt.Errorf("用户不存在") +} + +// load 从文件加载用户数据 +func (s *UserStorage) load() error { + // 检查文件是否存在 + if _, err := os.Stat(s.filePath); os.IsNotExist(err) { + // 文件不存在,创建示例数据 + s.createSampleData() + return s.save() + } + + // 读取文件 + data, err := ioutil.ReadFile(s.filePath) + if err != nil { + return err + } + + // 解析JSON + if err := json.Unmarshal(data, &s.users); err != nil { + return err + } + + // 更新下一个ID + maxID := 0 + for _, user := range s.users { + if user.ID > maxID { + maxID = user.ID + } + } + s.nextID = maxID + 1 + + return nil +} + +// save 保存用户数据到文件 +func (s *UserStorage) save() error { + data, err := json.MarshalIndent(s.users, "", " ") + if err != nil { + return err + } + + return ioutil.WriteFile(s.filePath, data, 0644) +} + +// createSampleData 创建示例数据 +func (s *UserStorage) createSampleData() { + now := time.Now() + + sampleUsers := []User{ + { + ID: 1, + Name: "张三", + Email: "zhangsan@example.com", + Age: 25, + CreatedAt: now, + UpdatedAt: now, + }, + { + ID: 2, + Name: "李四", + Email: "lisi@example.com", + Age: 30, + CreatedAt: now, + UpdatedAt: now, + }, + { + ID: 3, + Name: "王五", + Email: "wangwu@example.com", + Age: 28, + CreatedAt: now, + UpdatedAt: now, + }, + } + + s.users = sampleUsers + s.nextID = 4 +} + +// GetUserCount 获取用户总数 +func GetUserCount() int { + return len(storage.users) +} + +// SearchUsers 搜索用户 +func SearchUsers(keyword string) []User { + var results []User + keyword = strings.ToLower(keyword) + + for _, user := range storage.users { + if strings.Contains(strings.ToLower(user.Name), keyword) || + strings.Contains(strings.ToLower(user.Email), keyword) { + results = append(results, user) + } + } + + return results +} + +// GetUsersByAge 根据年龄范围获取用户 +func GetUsersByAge(minAge, maxAge int) []User { + var results []User + + for _, user := range storage.users { + if user.Age >= minAge && user.Age <= maxAge { + results = append(results, user) + } + } + + return results +} + +// GetRecentUsers 获取最近创建的用户 +func GetRecentUsers(limit int) []User { + if limit <= 0 || limit > len(storage.users) { + limit = len(storage.users) + } + + // 简单实现:返回最后创建的用户 + // 实际应用中应该按创建时间排序 + start := len(storage.users) - limit + if start < 0 { + start = 0 + } + + return storage.users[start:] +} diff --git a/golang-learning/10-projects/03-web-server/server/handlers.go b/golang-learning/10-projects/03-web-server/server/handlers.go new file mode 100644 index 0000000..54f9246 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/server/handlers.go @@ -0,0 +1,369 @@ +/* +handlers.go - 请求处理器 +实现了各种HTTP请求的处理逻辑 +*/ + +package server + +import ( + "encoding/json" + "net/http" + "runtime" + "strconv" + "time" + + "webserver/models" + + "github.com/gorilla/mux" +) + +// Response 通用响应结构 +type Response struct { + Status string `json:"status"` + Message string `json:"message,omitempty"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` + Count int `json:"count,omitempty"` +} + +// HealthResponse 健康检查响应 +type HealthResponse struct { + Status string `json:"status"` + Timestamp string `json:"timestamp"` + Uptime string `json:"uptime"` + Version string `json:"version"` +} + +// StatsResponse 统计信息响应 +type StatsResponse struct { + Status string `json:"status"` + Timestamp string `json:"timestamp"` + Uptime string `json:"uptime"` + Version string `json:"version"` + GoVersion string `json:"go_version"` + NumCPU int `json:"num_cpu"` + NumGoroutine int `json:"num_goroutine"` + MemStats runtime.MemStats `json:"mem_stats"` +} + +var ( + startTime = time.Now() + version = "1.0.0" +) + +// HomeHandler 首页处理器 +func HomeHandler(w http.ResponseWriter, r *http.Request) { + html := ` + + + + + + Go Web服务器 + + + +
+

🚀 Go Web服务器

+

欢迎使用Go语言编写的简单Web服务器!这个项目演示了HTTP服务器、RESTful API、JSON处理等功能。

+ +

📚 API接口

+
+
+ GET /health +
健康检查
+
+
+ GET /api/stats +
服务器统计信息
+
+
+ GET /api/users +
获取所有用户
+
+
+ POST /api/users +
创建新用户
+
+
+ GET /api/users/{id} +
获取指定用户
+
+
+ PUT /api/users/{id} +
更新用户信息
+
+
+ DELETE /api/users/{id} +
删除用户
+
+
+ +

🛠️ 使用示例

+
+# 获取所有用户
+curl http://localhost:8080/api/users
+
+# 创建新用户
+curl -X POST http://localhost:8080/api/users \
+  -H "Content-Type: application/json" \
+  -d '{"name":"张三","email":"zhangsan@example.com","age":25}'
+
+# 健康检查
+curl http://localhost:8080/health
+        
+
+ +` + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(html)) +} + +// HealthHandler 健康检查处理器 +func HealthHandler(w http.ResponseWriter, r *http.Request) { + uptime := time.Since(startTime) + + response := HealthResponse{ + Status: "healthy", + Timestamp: time.Now().Format(time.RFC3339), + Uptime: uptime.String(), + Version: version, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +// StatsHandler 统计信息处理器 +func StatsHandler(w http.ResponseWriter, r *http.Request) { + uptime := time.Since(startTime) + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + response := StatsResponse{ + Status: "success", + Timestamp: time.Now().Format(time.RFC3339), + Uptime: uptime.String(), + Version: version, + GoVersion: runtime.Version(), + NumCPU: runtime.NumCPU(), + NumGoroutine: runtime.NumGoroutine(), + MemStats: memStats, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +// GetUsersHandler 获取所有用户 +func GetUsersHandler(w http.ResponseWriter, r *http.Request) { + users, err := models.GetAllUsers() + if err != nil { + sendErrorResponse(w, "获取用户列表失败", http.StatusInternalServerError) + return + } + + response := Response{ + Status: "success", + Data: users, + Count: len(users), + } + + sendJSONResponse(w, response, http.StatusOK) +} + +// GetUserHandler 获取指定用户 +func GetUserHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + sendErrorResponse(w, "无效的用户ID", http.StatusBadRequest) + return + } + + user, err := models.GetUserByID(id) + if err != nil { + sendErrorResponse(w, "用户不存在", http.StatusNotFound) + return + } + + response := Response{ + Status: "success", + Data: user, + } + + sendJSONResponse(w, response, http.StatusOK) +} + +// CreateUserHandler 创建新用户 +func CreateUserHandler(w http.ResponseWriter, r *http.Request) { + var user models.User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + sendErrorResponse(w, "无效的JSON数据", http.StatusBadRequest) + return + } + + // 验证用户数据 + if err := user.Validate(); err != nil { + sendErrorResponse(w, err.Error(), http.StatusBadRequest) + return + } + + // 创建用户 + createdUser, err := models.CreateUser(user) + if err != nil { + sendErrorResponse(w, "创建用户失败", http.StatusInternalServerError) + return + } + + response := Response{ + Status: "success", + Message: "用户创建成功", + Data: createdUser, + } + + sendJSONResponse(w, response, http.StatusCreated) +} + +// UpdateUserHandler 更新用户信息 +func UpdateUserHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + sendErrorResponse(w, "无效的用户ID", http.StatusBadRequest) + return + } + + var user models.User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + sendErrorResponse(w, "无效的JSON数据", http.StatusBadRequest) + return + } + + // 设置用户ID + user.ID = id + + // 验证用户数据 + if err := user.Validate(); err != nil { + sendErrorResponse(w, err.Error(), http.StatusBadRequest) + return + } + + // 更新用户 + updatedUser, err := models.UpdateUser(user) + if err != nil { + sendErrorResponse(w, "更新用户失败", http.StatusInternalServerError) + return + } + + response := Response{ + Status: "success", + Message: "用户更新成功", + Data: updatedUser, + } + + sendJSONResponse(w, response, http.StatusOK) +} + +// DeleteUserHandler 删除用户 +func DeleteUserHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + sendErrorResponse(w, "无效的用户ID", http.StatusBadRequest) + return + } + + if err := models.DeleteUser(id); err != nil { + sendErrorResponse(w, "删除用户失败", http.StatusInternalServerError) + return + } + + response := Response{ + Status: "success", + Message: "用户删除成功", + } + + sendJSONResponse(w, response, http.StatusOK) +} + +// APIDocHandler API文档处理器 +func APIDocHandler(w http.ResponseWriter, r *http.Request) { + doc := map[string]interface{}{ + "title": "Go Web服务器 API", + "version": version, + "description": "一个简单的RESTful API服务器", + "endpoints": map[string]interface{}{ + "health": map[string]string{ + "method": "GET", + "path": "/health", + "description": "健康检查", + }, + "stats": map[string]string{ + "method": "GET", + "path": "/api/stats", + "description": "服务器统计信息", + }, + "users": map[string]interface{}{ + "list": map[string]string{ + "method": "GET", + "path": "/api/users", + "description": "获取所有用户", + }, + "get": map[string]string{ + "method": "GET", + "path": "/api/users/{id}", + "description": "获取指定用户", + }, + "create": map[string]string{ + "method": "POST", + "path": "/api/users", + "description": "创建新用户", + }, + "update": map[string]string{ + "method": "PUT", + "path": "/api/users/{id}", + "description": "更新用户信息", + }, + "delete": map[string]string{ + "method": "DELETE", + "path": "/api/users/{id}", + "description": "删除用户", + }, + }, + }, + } + + sendJSONResponse(w, doc, http.StatusOK) +} + +// sendJSONResponse 发送JSON响应 +func sendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + if err := json.NewEncoder(w).Encode(data); err != nil { + http.Error(w, "JSON编码失败", http.StatusInternalServerError) + } +} + +// sendErrorResponse 发送错误响应 +func sendErrorResponse(w http.ResponseWriter, message string, statusCode int) { + response := Response{ + Status: "error", + Error: message, + } + sendJSONResponse(w, response, statusCode) +} diff --git a/golang-learning/10-projects/03-web-server/server/middleware.go b/golang-learning/10-projects/03-web-server/server/middleware.go new file mode 100644 index 0000000..04ce5d6 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/server/middleware.go @@ -0,0 +1,147 @@ +/* +middleware.go - 中间件 +实现了各种HTTP中间件功能 +*/ + +package server + +import ( + "log" + "net/http" + "runtime/debug" + "time" +) + +// LoggingMiddleware 日志记录中间件 +func LoggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // 创建响应记录器来捕获状态码 + recorder := &responseRecorder{ + ResponseWriter: w, + statusCode: http.StatusOK, + } + + // 调用下一个处理器 + next.ServeHTTP(recorder, r) + + // 记录请求日志 + duration := time.Since(start) + log.Printf("[%s] %s %s %d %v", + r.Method, + r.RequestURI, + r.RemoteAddr, + recorder.statusCode, + duration, + ) + }) +} + +// responseRecorder 响应记录器 +type responseRecorder struct { + http.ResponseWriter + statusCode int +} + +// WriteHeader 记录状态码 +func (rr *responseRecorder) WriteHeader(code int) { + rr.statusCode = code + rr.ResponseWriter.WriteHeader(code) +} + +// CORSMiddleware CORS中间件 +func CORSMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 设置CORS头 + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + // 处理预检请求 + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + // 调用下一个处理器 + next.ServeHTTP(w, r) + }) +} + +// RecoveryMiddleware 恢复中间件(处理panic) +func RecoveryMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + // 记录panic信息 + log.Printf("❌ Panic recovered: %v\n%s", err, debug.Stack()) + + // 返回500错误 + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + }() + + // 调用下一个处理器 + next.ServeHTTP(w, r) + }) +} + +// AuthMiddleware 认证中间件(示例) +func AuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 检查Authorization头 + token := r.Header.Get("Authorization") + if token == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // 这里可以添加实际的token验证逻辑 + // 为了演示,我们简单检查token是否为"Bearer valid-token" + if token != "Bearer valid-token" { + http.Error(w, "Invalid token", http.StatusUnauthorized) + return + } + + // 调用下一个处理器 + next.ServeHTTP(w, r) + }) +} + +// RateLimitMiddleware 限流中间件(简化版) +func RateLimitMiddleware(next http.Handler) http.Handler { + // 这里可以实现基于IP的限流逻辑 + // 为了简化,我们只是一个示例框架 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 实际实现中,这里会检查请求频率 + // 如果超过限制,返回429状态码 + + // 调用下一个处理器 + next.ServeHTTP(w, r) + }) +} + +// ContentTypeMiddleware 内容类型中间件 +func ContentTypeMiddleware(contentType string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", contentType) + next.ServeHTTP(w, r) + }) + } +} + +// SecurityMiddleware 安全头中间件 +func SecurityMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 设置安全相关的HTTP头 + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("X-XSS-Protection", "1; mode=block") + w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") + + // 调用下一个处理器 + next.ServeHTTP(w, r) + }) +} diff --git a/golang-learning/10-projects/03-web-server/server/router.go b/golang-learning/10-projects/03-web-server/server/router.go new file mode 100644 index 0000000..20bf129 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/server/router.go @@ -0,0 +1,59 @@ +/* +router.go - 路由管理 +实现了HTTP路由和中间件管理功能 +*/ + +package server + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +// Router 路由器结构体 +type Router struct { + *mux.Router +} + +// NewRouter 创建新的路由器 +func NewRouter() *Router { + return &Router{ + Router: mux.NewRouter(), + } +} + +// Use 添加中间件 +func (r *Router) Use(middleware func(http.Handler) http.Handler) { + r.Router.Use(middleware) +} + +// Methods 设置HTTP方法(链式调用) +func (r *Router) Methods(methods ...string) *mux.Route { + return r.Router.Methods(methods...) +} + +// PathPrefix 路径前缀 +func (r *Router) PathPrefix(tpl string) *mux.Router { + return r.Router.PathPrefix(tpl) +} + +// Subrouter 创建子路由 +func (r *Router) Subrouter() *mux.Router { + return r.Router.NewRoute().Subrouter() +} + +// HandleFunc 处理函数路由 +func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *mux.Route { + return r.Router.HandleFunc(path, f) +} + +// Handle 处理器路由 +func (r *Router) Handle(path string, handler http.Handler) *mux.Route { + return r.Router.Handle(path, handler) +} + +// ServeHTTP 实现http.Handler接口 +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + r.Router.ServeHTTP(w, req) +} diff --git a/golang-learning/10-projects/03-web-server/server/server.go b/golang-learning/10-projects/03-web-server/server/server.go new file mode 100644 index 0000000..9aec259 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/server/server.go @@ -0,0 +1,94 @@ +/* +server.go - HTTP服务器核心 +实现了HTTP服务器的基本功能和生命周期管理 +*/ + +package server + +import ( + "context" + "fmt" + "net/http" + "time" +) + +// Server HTTP服务器结构体 +type Server struct { + httpServer *http.Server + router *Router + addr string +} + +// NewServer 创建新的服务器实例 +func NewServer(addr string) *Server { + router := NewRouter() + + return &Server{ + httpServer: &http.Server{ + Addr: addr, + Handler: router, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + }, + router: router, + addr: addr, + } +} + +// SetupRoutes 设置路由 +func (s *Server) SetupRoutes() { + // 添加中间件 + s.router.Use(LoggingMiddleware) + s.router.Use(CORSMiddleware) + s.router.Use(RecoveryMiddleware) + + // 静态文件服务 + s.router.HandleFunc("/", HomeHandler).Methods("GET") + s.router.PathPrefix("/static/").Handler( + http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))), + ) + + // 健康检查 + s.router.HandleFunc("/health", HealthHandler).Methods("GET") + + // API路由 + apiRouter := s.router.PathPrefix("/api").Subrouter() + + // 用户管理API + apiRouter.HandleFunc("/users", GetUsersHandler).Methods("GET") + apiRouter.HandleFunc("/users", CreateUserHandler).Methods("POST") + apiRouter.HandleFunc("/users/{id:[0-9]+}", GetUserHandler).Methods("GET") + apiRouter.HandleFunc("/users/{id:[0-9]+}", UpdateUserHandler).Methods("PUT") + apiRouter.HandleFunc("/users/{id:[0-9]+}", DeleteUserHandler).Methods("DELETE") + + // 服务器统计 + apiRouter.HandleFunc("/stats", StatsHandler).Methods("GET") + + // API文档 + apiRouter.HandleFunc("", APIDocHandler).Methods("GET") +} + +// Start 启动服务器 +func (s *Server) Start() error { + fmt.Printf("✅ 服务器启动成功,监听地址: %s\n", s.addr) + return s.httpServer.ListenAndServe() +} + +// Shutdown 优雅关闭服务器 +func (s *Server) Shutdown() error { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + return s.httpServer.Shutdown(ctx) +} + +// GetRouter 获取路由器 +func (s *Server) GetRouter() *Router { + return s.router +} + +// GetHTTPServer 获取HTTP服务器 +func (s *Server) GetHTTPServer() *http.Server { + return s.httpServer +} diff --git a/golang-learning/10-projects/03-web-server/server_test.go b/golang-learning/10-projects/03-web-server/server_test.go new file mode 100644 index 0000000..d6f80a7 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/server_test.go @@ -0,0 +1,358 @@ +/* +server_test.go - Web服务器测试文件 +测试HTTP服务器和API的各种功能 +*/ + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "webserver/models" + "webserver/server" +) + +// TestHealthHandler 测试健康检查处理器 +func TestHealthHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/health", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.HealthHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusOK { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK) + } + + // 检查响应内容类型 + expected := "application/json" + if ct := rr.Header().Get("Content-Type"); ct != expected { + t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected) + } + + // 检查响应体 + var response server.HealthResponse + if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { + t.Errorf("无法解析响应JSON: %v", err) + } + + if response.Status != "healthy" { + t.Errorf("期望状态为 'healthy', 实际为 '%s'", response.Status) + } +} + +// TestGetUsersHandler 测试获取用户列表处理器 +func TestGetUsersHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/api/users", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.GetUsersHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusOK { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK) + } + + // 检查响应内容类型 + expected := "application/json" + if ct := rr.Header().Get("Content-Type"); ct != expected { + t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected) + } + + // 检查响应体 + var response server.Response + if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { + t.Errorf("无法解析响应JSON: %v", err) + } + + if response.Status != "success" { + t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status) + } +} + +// TestCreateUserHandler 测试创建用户处理器 +func TestCreateUserHandler(t *testing.T) { + user := models.User{ + Name: "测试用户", + Email: "test@example.com", + Age: 25, + } + + jsonData, err := json.Marshal(user) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.CreateUserHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusCreated { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusCreated) + } + + // 检查响应体 + var response server.Response + if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { + t.Errorf("无法解析响应JSON: %v", err) + } + + if response.Status != "success" { + t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status) + } +} + +// TestCreateUserHandlerInvalidData 测试创建用户处理器(无效数据) +func TestCreateUserHandlerInvalidData(t *testing.T) { + // 测试空用户名 + user := models.User{ + Name: "", + Email: "test@example.com", + Age: 25, + } + + jsonData, err := json.Marshal(user) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(jsonData)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.CreateUserHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusBadRequest) + } + + // 检查响应体 + var response server.Response + if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { + t.Errorf("无法解析响应JSON: %v", err) + } + + if response.Status != "error" { + t.Errorf("期望状态为 'error', 实际为 '%s'", response.Status) + } +} + +// TestCreateUserHandlerInvalidJSON 测试创建用户处理器(无效JSON) +func TestCreateUserHandlerInvalidJSON(t *testing.T) { + invalidJSON := []byte(`{"name": "测试用户", "email": "test@example.com", "age":}`) + + req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(invalidJSON)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.CreateUserHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusBadRequest) + } +} + +// TestStatsHandler 测试统计信息处理器 +func TestStatsHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/api/stats", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.StatsHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusOK { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK) + } + + // 检查响应体 + var response server.StatsResponse + if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { + t.Errorf("无法解析响应JSON: %v", err) + } + + if response.Status != "success" { + t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status) + } + + if response.NumCPU <= 0 { + t.Error("CPU数量应该大于0") + } + + if response.NumGoroutine <= 0 { + t.Error("Goroutine数量应该大于0") + } +} + +// TestAPIDocHandler 测试API文档处理器 +func TestAPIDocHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/api", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.APIDocHandler) + + handler.ServeHTTP(rr, req) + + // 检查状态码 + if status := rr.Code; status != http.StatusOK { + t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK) + } + + // 检查响应内容类型 + expected := "application/json" + if ct := rr.Header().Get("Content-Type"); ct != expected { + t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected) + } + + // 检查响应体包含API文档信息 + var doc map[string]interface{} + if err := json.Unmarshal(rr.Body.Bytes(), &doc); err != nil { + t.Errorf("无法解析响应JSON: %v", err) + } + + if _, exists := doc["title"]; !exists { + t.Error("API文档应该包含title字段") + } + + if _, exists := doc["endpoints"]; !exists { + t.Error("API文档应该包含endpoints字段") + } +} + +// TestServer 测试完整的服务器 +func TestServer(t *testing.T) { + // 创建服务器 + srv := server.NewServer(":0") // 使用随机端口 + srv.SetupRoutes() + + // 创建测试服务器 + testServer := httptest.NewServer(srv.GetRouter()) + defer testServer.Close() + + // 测试健康检查 + resp, err := http.Get(testServer.URL + "/health") + if err != nil { + t.Fatalf("健康检查请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("健康检查返回错误状态码: got %v want %v", resp.StatusCode, http.StatusOK) + } + + // 测试获取用户列表 + resp, err = http.Get(testServer.URL + "/api/users") + if err != nil { + t.Fatalf("获取用户列表请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("获取用户列表返回错误状态码: got %v want %v", resp.StatusCode, http.StatusOK) + } +} + +// TestMiddleware 测试中间件 +func TestMiddleware(t *testing.T) { + // 测试CORS中间件 + req, err := http.NewRequest("OPTIONS", "/api/users", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + // 创建带中间件的处理器 + handler := server.CORSMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + + handler.ServeHTTP(rr, req) + + // 检查CORS头 + if origin := rr.Header().Get("Access-Control-Allow-Origin"); origin != "*" { + t.Errorf("期望CORS Origin为 '*', 实际为 '%s'", origin) + } + + if methods := rr.Header().Get("Access-Control-Allow-Methods"); methods == "" { + t.Error("应该设置Access-Control-Allow-Methods头") + } +} + +// BenchmarkHealthHandler 健康检查处理器基准测试 +func BenchmarkHealthHandler(b *testing.B) { + req, _ := http.NewRequest("GET", "/health", nil) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.HealthHandler) + handler.ServeHTTP(rr, req) + } +} + +// BenchmarkGetUsersHandler 获取用户列表处理器基准测试 +func BenchmarkGetUsersHandler(b *testing.B) { + req, _ := http.NewRequest("GET", "/api/users", nil) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.GetUsersHandler) + handler.ServeHTTP(rr, req) + } +} + +// ExampleHealthHandler 健康检查处理器示例 +func ExampleHealthHandler() { + req, _ := http.NewRequest("GET", "/health", nil) + rr := httptest.NewRecorder() + handler := http.HandlerFunc(server.HealthHandler) + + handler.ServeHTTP(rr, req) + + fmt.Printf("Status: %d", rr.Code) + // Output: Status: 200 +} diff --git a/golang-learning/10-projects/03-web-server/static/index.html b/golang-learning/10-projects/03-web-server/static/index.html new file mode 100644 index 0000000..988547c --- /dev/null +++ b/golang-learning/10-projects/03-web-server/static/index.html @@ -0,0 +1,101 @@ + + + + + + Go Web服务器 + + + +
+
+

🚀 Go Web服务器

+

一个使用Go语言编写的简单HTTP服务器示例

+
+ +
+
+

📚 API接口测试

+ +
+

用户管理

+ +
+ + GET + /api/users +
+ +
+ + POST + /api/users +
+ +
+ + + GET + /api/users/{id} +
+ +
+ + DELETE + /api/users/{id} +
+
+ +
+

系统信息

+ +
+ + GET + /health +
+ +
+ + GET + /api/stats +
+
+
+ +
+

📝 创建用户表单

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ +
+

📄 响应结果

+

+            
+
+ +
+

© 2024 Go Web服务器示例项目

+
+
+ + + + \ No newline at end of file diff --git a/golang-learning/10-projects/03-web-server/static/script.js b/golang-learning/10-projects/03-web-server/static/script.js new file mode 100644 index 0000000..cb04ce6 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/static/script.js @@ -0,0 +1,241 @@ +// script.js - JavaScript 文件 + +// API 基础URL +const API_BASE = '/api'; + +// 响应显示元素 +const responseElement = document.getElementById('response'); + +// 显示响应结果 +function showResponse(data, isError = false) { + responseElement.textContent = JSON.stringify(data, null, 2); + responseElement.className = isError ? 'error' : 'success'; +} + +// 显示加载状态 +function showLoading() { + responseElement.textContent = '加载中...'; + responseElement.className = 'loading'; +} + +// API 请求封装 +async function apiRequest(url, options = {}) { + try { + showLoading(); + + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options.headers + }, + ...options + }); + + const data = await response.json(); + + if (!response.ok) { + showResponse(data, true); + return null; + } + + showResponse(data); + return data; + } catch (error) { + showResponse({ + error: '网络请求失败', + message: error.message + }, true); + return null; + } +} + +// 获取所有用户 +async function getUsers() { + await apiRequest(`${API_BASE}/users`); +} + +// 获取指定用户 +async function getUser() { + const userId = document.getElementById('userId').value; + if (!userId) { + showResponse({ + error: '请输入用户ID' + }, true); + return; + } + + await apiRequest(`${API_BASE}/users/${userId}`); +} + +// 创建用户(使用按钮) +async function createUser() { + const userData = { + name: '测试用户', + email: `test${Date.now()}@example.com`, + age: Math.floor(Math.random() * 50) + 18 + }; + + await apiRequest(`${API_BASE}/users`, { + method: 'POST', + body: JSON.stringify(userData) + }); +} + +// 删除用户 +async function deleteUser() { + const userId = document.getElementById('userId').value; + if (!userId) { + showResponse({ + error: '请输入用户ID' + }, true); + return; + } + + if (!confirm(`确定要删除用户 ${userId} 吗?`)) { + return; + } + + await apiRequest(`${API_BASE}/users/${userId}`, { + method: 'DELETE' + }); +} + +// 健康检查 +async function getHealth() { + await apiRequest('/health'); +} + +// 获取服务器统计 +async function getStats() { + await apiRequest(`${API_BASE}/stats`); +} + +// 表单提交处理 +document.getElementById('userForm').addEventListener('submit', async function(e) { + e.preventDefault(); + + const formData = new FormData(this); + const userData = { + name: formData.get('name'), + email: formData.get('email'), + age: parseInt(formData.get('age')) + }; + + // 验证数据 + if (!userData.name || !userData.email || !userData.age) { + showResponse({ + error: '请填写所有必填字段' + }, true); + return; + } + + if (userData.age < 0 || userData.age > 150) { + showResponse({ + error: '年龄必须在0-150之间' + }, true); + return; + } + + const result = await apiRequest(`${API_BASE}/users`, { + method: 'POST', + body: JSON.stringify(userData) + }); + + if (result) { + // 清空表单 + this.reset(); + } +}); + +// 页面加载完成后的初始化 +document.addEventListener('DOMContentLoaded', function() { + // 显示欢迎信息 + showResponse({ + message: '欢迎使用 Go Web服务器 API 测试页面!', + instructions: [ + '点击上方按钮测试各种API接口', + '使用表单创建新用户', + '查看下方的响应结果' + ] + }); + + // 自动获取用户列表 + setTimeout(getUsers, 1000); +}); + +// 键盘快捷键 +document.addEventListener('keydown', function(e) { + // Ctrl + Enter 快速创建用户 + if (e.ctrlKey && e.key === 'Enter') { + createUser(); + } + + // Ctrl + R 刷新用户列表 + if (e.ctrlKey && e.key === 'r') { + e.preventDefault(); + getUsers(); + } +}); + +// 工具函数:格式化时间 +function formatTime(timestamp) { + return new Date(timestamp).toLocaleString('zh-CN'); +} + +// 工具函数:格式化文件大小 +function formatBytes(bytes) { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +// 工具函数:复制到剪贴板 +async function copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + console.log('已复制到剪贴板'); + } catch (err) { + console.error('复制失败:', err); + } +} + +// 添加复制响应结果的功能 +responseElement.addEventListener('click', function() { + if (this.textContent && this.textContent !== '加载中...') { + copyToClipboard(this.textContent); + } +}); + +// 自动刷新功能(可选) +let autoRefresh = false; +let refreshInterval; + +function toggleAutoRefresh() { + autoRefresh = !autoRefresh; + + if (autoRefresh) { + refreshInterval = setInterval(getUsers, 5000); + console.log('自动刷新已启用'); + } else { + clearInterval(refreshInterval); + console.log('自动刷新已禁用'); + } +} + +// 错误重试机制 +async function retryRequest(requestFunc, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + return await requestFunc(); + } catch (error) { + if (i === maxRetries - 1) { + throw error; + } + await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); + } + } +} \ No newline at end of file diff --git a/golang-learning/10-projects/03-web-server/static/style.css b/golang-learning/10-projects/03-web-server/static/style.css new file mode 100644 index 0000000..26211f4 --- /dev/null +++ b/golang-learning/10-projects/03-web-server/static/style.css @@ -0,0 +1,271 @@ +/* style.css - 样式文件 */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + background-color: #f5f5f5; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +header { + text-align: center; + margin-bottom: 40px; + padding: 30px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 10px; + box-shadow: 0 4px 15px rgba(0,0,0,0.1); +} + +header h1 { + font-size: 2.5em; + margin-bottom: 10px; +} + +header p { + font-size: 1.2em; + opacity: 0.9; +} + +main { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 30px; + margin-bottom: 40px; +} + +section { + background: white; + padding: 30px; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.response-section { + grid-column: 1 / -1; +} + +h2 { + color: #333; + margin-bottom: 20px; + font-size: 1.5em; + border-bottom: 2px solid #667eea; + padding-bottom: 10px; +} + +h3 { + color: #555; + margin: 20px 0 15px 0; + font-size: 1.2em; +} + +.api-group { + margin-bottom: 30px; +} + +.api-item { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 15px; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border-left: 4px solid #667eea; +} + +.api-item button { + background: #667eea; + color: white; + border: none; + padding: 8px 16px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s; + min-width: 120px; +} + +.api-item button:hover { + background: #5a6fd8; +} + +.api-item input { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + width: 100px; +} + +.method { + font-weight: bold; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + text-transform: uppercase; + min-width: 60px; + text-align: center; +} + +.method.get { + background: #28a745; + color: white; +} + +.method.post { + background: #007bff; + color: white; +} + +.method.delete { + background: #dc3545; + color: white; +} + +.endpoint { + font-family: 'Courier New', monospace; + background: #e9ecef; + padding: 4px 8px; + border-radius: 4px; + font-size: 13px; + color: #495057; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: #555; +} + +.form-group input { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 16px; + transition: border-color 0.3s; +} + +.form-group input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2); +} + +#userForm button { + background: #28a745; + color: white; + border: none; + padding: 12px 30px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; + width: 100%; +} + +#userForm button:hover { + background: #218838; +} + +#response { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 5px; + padding: 20px; + font-family: 'Courier New', monospace; + font-size: 14px; + line-height: 1.4; + white-space: pre-wrap; + word-wrap: break-word; + max-height: 400px; + overflow-y: auto; + color: #495057; +} + +footer { + text-align: center; + padding: 20px; + color: #666; + border-top: 1px solid #eee; + margin-top: 40px; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + main { + grid-template-columns: 1fr; + gap: 20px; + } + + .api-item { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .api-item button { + width: 100%; + } + + header h1 { + font-size: 2em; + } + + .container { + padding: 10px; + } +} + +/* 加载动画 */ +.loading { + opacity: 0.6; + pointer-events: none; +} + +.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + margin: -10px 0 0 -10px; + border: 2px solid #667eea; + border-radius: 50%; + border-top-color: transparent; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* 成功和错误状态 */ +.success { + color: #28a745; +} + +.error { + color: #dc3545; +} \ No newline at end of file