完成
This commit is contained in:
@@ -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 示例
|
||||
|
665
golang-learning/08-packages/01-creating-packages.go
Normal file
665
golang-learning/08-packages/01-creating-packages.go
Normal file
@@ -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. 考虑包的向后兼容性
|
||||
*/
|
579
golang-learning/08-packages/02-importing-packages.go
Normal file
579
golang-learning/08-packages/02-importing-packages.go
Normal file
@@ -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 <module>": "初始化新模块",
|
||||
"go mod tidy": "添加缺失的依赖,移除未使用的依赖",
|
||||
"go mod download": "下载依赖到本地缓存",
|
||||
"go mod verify": "验证依赖的完整性",
|
||||
"go mod graph": "打印模块依赖图",
|
||||
"go mod why <package>": "解释为什么需要某个包",
|
||||
"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 <module-name>' 初始化\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. 合理使用包的可见性规则
|
||||
*/
|
@@ -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
|
||||
// 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)
|
||||
}
|
||||
|
967
golang-learning/09-advanced/01-reflection.go
Normal file
967
golang-learning/09-advanced/01-reflection.go
Normal file
@@ -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
|
||||
*/
|
561
golang-learning/09-advanced/02-generics.go
Normal file
561
golang-learning/09-advanced/02-generics.go
Normal file
@@ -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()
|
||||
}
|
765
golang-learning/09-advanced/03-context.go
Normal file
765
golang-learning/09-advanced/03-context.go
Normal file
@@ -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 中存储过多数据
|
||||
*/
|
919
golang-learning/09-advanced/04-testing.go
Normal file
919
golang-learning/09-advanced/04-testing.go
Normal file
@@ -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. 合理使用并行测试提高效率
|
||||
*/
|
@@ -1,15 +1,24 @@
|
||||
# 第九章:高级特性
|
||||
|
||||
本章将学习 Go 语言的一些高级特性,包括反射、泛型、上下文和测试。
|
||||
本章将学习 Go 语言的高级特性,这些特性能帮助你编写更强大和灵活的程序。
|
||||
|
||||
## 学习目标
|
||||
- 了解反射的基本概念和用法
|
||||
- 理解泛型的语法和应用
|
||||
- 掌握 context 包的使用
|
||||
- 学会编写单元测试
|
||||
|
||||
- 掌握反射的基本使用和应用场景
|
||||
- 理解泛型的概念和语法
|
||||
- 学会使用 context 包进行上下文管理
|
||||
- 掌握单元测试的编写方法
|
||||
|
||||
## 文件列表
|
||||
- `01-reflection.go` - 反射
|
||||
- `02-generics.go` - 泛型
|
||||
- `03-context.go` - Context
|
||||
- `04-testing.go` - 测试
|
||||
|
||||
- `01-reflection.go` - 反射机制
|
||||
- `02-generics.go` - 泛型编程
|
||||
- `03-context.go` - 上下文管理
|
||||
- `04-testing.go` - 单元测试
|
||||
|
||||
## 学习建议
|
||||
|
||||
1. 反射是强大但复杂的特性,需要谨慎使用
|
||||
2. 泛型是 Go 1.18+ 的新特性,能提高代码复用性
|
||||
3. Context 是并发编程中的重要工具
|
||||
4. 测试是保证代码质量的重要手段
|
||||
|
@@ -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
|
||||
输入数学表达式,或输入 '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. 添加单位换算功能
|
@@ -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:]
|
||||
}
|
||||
}
|
244
golang-learning/10-projects/01-calculator/calculator/history.go
Normal file
244
golang-learning/10-projects/01-calculator/calculator/history.go
Normal file
@@ -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))))
|
||||
}
|
214
golang-learning/10-projects/01-calculator/calculator_test.go
Normal file
214
golang-learning/10-projects/01-calculator/calculator_test.go
Normal file
@@ -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
|
||||
}
|
133
golang-learning/10-projects/01-calculator/main.go
Normal file
133
golang-learning/10-projects/01-calculator/main.go
Normal file
@@ -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()
|
||||
}
|
@@ -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
|
||||
|
||||
# 删除事项
|
||||
go run main.go delete 1
|
||||
```
|
||||
=== 待办事项管理器 ===
|
||||
|
||||
命令列表:
|
||||
add <任务> - 添加新任务
|
||||
list - 显示所有任务
|
||||
done <ID> - 标记任务完成
|
||||
delete <ID> - 删除任务
|
||||
edit <ID> <新内容> - 编辑任务
|
||||
search <关键词> - 搜索任务
|
||||
stats - 显示统计信息
|
||||
help - 显示帮助
|
||||
quit - 退出程序
|
||||
|
||||
> 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. 添加任务模板功能
|
2
golang-learning/10-projects/02-todo-list/data/.gitkeep
Normal file
2
golang-learning/10-projects/02-todo-list/data/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
||||
# 这个文件用于保持 data 目录在 git 中被跟踪
|
||||
# 实际的 todos.json 文件会在程序运行时自动创建
|
275
golang-learning/10-projects/02-todo-list/main.go
Normal file
275
golang-learning/10-projects/02-todo-list/main.go
Normal file
@@ -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("再见!")
|
||||
}
|
427
golang-learning/10-projects/02-todo-list/todo/manager.go
Normal file
427
golang-learning/10-projects/02-todo-list/todo/manager.go
Normal file
@@ -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
|
||||
}
|
308
golang-learning/10-projects/02-todo-list/todo/storage.go
Normal file
308
golang-learning/10-projects/02-todo-list/todo/storage.go
Normal file
@@ -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)
|
||||
}
|
356
golang-learning/10-projects/02-todo-list/todo/todo.go
Normal file
356
golang-learning/10-projects/02-todo-list/todo/todo.go
Normal file
@@ -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
|
||||
}
|
382
golang-learning/10-projects/02-todo-list/todo_test.go
Normal file
382
golang-learning/10-projects/02-todo-list/todo_test.go
Normal file
@@ -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)
|
||||
}
|
315
golang-learning/10-projects/02-todo-list/ui/cli.go
Normal file
315
golang-learning/10-projects/02-todo-list/ui/cli.go
Normal file
@@ -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 <ID>") + " - 标记任务完成")
|
||||
fmt.Println(" " + c.colors.Green("delete <ID>") + " - 删除任务")
|
||||
fmt.Println(" " + c.colors.Green("edit <ID> <新内容>") + " - 编辑任务")
|
||||
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()
|
||||
}
|
382
golang-learning/10-projects/02-todo-list/ui/colors.go
Normal file
382
golang-learning/10-projects/02-todo-list/ui/colors.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
@@ -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"}'
|
||||
-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
|
2
golang-learning/10-projects/03-web-server/data/.gitkeep
Normal file
2
golang-learning/10-projects/03-web-server/data/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
||||
# 这个文件用于保持 data 目录在 git 中被跟踪
|
||||
# 实际的 users.json 文件会在程序运行时自动创建
|
5
golang-learning/10-projects/03-web-server/go.mod
Normal file
5
golang-learning/10-projects/03-web-server/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module webserver
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/gorilla/mux v1.8.0
|
54
golang-learning/10-projects/03-web-server/main.go
Normal file
54
golang-learning/10-projects/03-web-server/main.go
Normal file
@@ -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("✅ 服务器已安全关闭")
|
||||
}
|
||||
}
|
293
golang-learning/10-projects/03-web-server/models/user.go
Normal file
293
golang-learning/10-projects/03-web-server/models/user.go
Normal file
@@ -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:]
|
||||
}
|
369
golang-learning/10-projects/03-web-server/server/handlers.go
Normal file
369
golang-learning/10-projects/03-web-server/server/handlers.go
Normal file
@@ -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 := `
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Go Web服务器</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
|
||||
.container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
h1 { color: #333; text-align: center; }
|
||||
.api-list { background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0; }
|
||||
.api-item { margin: 10px 0; padding: 10px; background: white; border-left: 4px solid #007bff; }
|
||||
.method { font-weight: bold; color: #007bff; }
|
||||
.endpoint { font-family: monospace; background: #e9ecef; padding: 2px 6px; border-radius: 3px; }
|
||||
.description { color: #666; margin-top: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 Go Web服务器</h1>
|
||||
<p>欢迎使用Go语言编写的简单Web服务器!这个项目演示了HTTP服务器、RESTful API、JSON处理等功能。</p>
|
||||
|
||||
<h2>📚 API接口</h2>
|
||||
<div class="api-list">
|
||||
<div class="api-item">
|
||||
<span class="method">GET</span> <span class="endpoint">/health</span>
|
||||
<div class="description">健康检查</div>
|
||||
</div>
|
||||
<div class="api-item">
|
||||
<span class="method">GET</span> <span class="endpoint">/api/stats</span>
|
||||
<div class="description">服务器统计信息</div>
|
||||
</div>
|
||||
<div class="api-item">
|
||||
<span class="method">GET</span> <span class="endpoint">/api/users</span>
|
||||
<div class="description">获取所有用户</div>
|
||||
</div>
|
||||
<div class="api-item">
|
||||
<span class="method">POST</span> <span class="endpoint">/api/users</span>
|
||||
<div class="description">创建新用户</div>
|
||||
</div>
|
||||
<div class="api-item">
|
||||
<span class="method">GET</span> <span class="endpoint">/api/users/{id}</span>
|
||||
<div class="description">获取指定用户</div>
|
||||
</div>
|
||||
<div class="api-item">
|
||||
<span class="method">PUT</span> <span class="endpoint">/api/users/{id}</span>
|
||||
<div class="description">更新用户信息</div>
|
||||
</div>
|
||||
<div class="api-item">
|
||||
<span class="method">DELETE</span> <span class="endpoint">/api/users/{id}</span>
|
||||
<div class="description">删除用户</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>🛠️ 使用示例</h2>
|
||||
<pre style="background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto;">
|
||||
# 获取所有用户
|
||||
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
|
||||
</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
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)
|
||||
}
|
147
golang-learning/10-projects/03-web-server/server/middleware.go
Normal file
147
golang-learning/10-projects/03-web-server/server/middleware.go
Normal file
@@ -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)
|
||||
})
|
||||
}
|
59
golang-learning/10-projects/03-web-server/server/router.go
Normal file
59
golang-learning/10-projects/03-web-server/server/router.go
Normal file
@@ -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)
|
||||
}
|
94
golang-learning/10-projects/03-web-server/server/server.go
Normal file
94
golang-learning/10-projects/03-web-server/server/server.go
Normal file
@@ -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
|
||||
}
|
358
golang-learning/10-projects/03-web-server/server_test.go
Normal file
358
golang-learning/10-projects/03-web-server/server_test.go
Normal file
@@ -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
|
||||
}
|
101
golang-learning/10-projects/03-web-server/static/index.html
Normal file
101
golang-learning/10-projects/03-web-server/static/index.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Go Web服务器</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🚀 Go Web服务器</h1>
|
||||
<p>一个使用Go语言编写的简单HTTP服务器示例</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="api-section">
|
||||
<h2>📚 API接口测试</h2>
|
||||
|
||||
<div class="api-group">
|
||||
<h3>用户管理</h3>
|
||||
|
||||
<div class="api-item">
|
||||
<button onclick="getUsers()">获取所有用户</button>
|
||||
<span class="method get">GET</span>
|
||||
<span class="endpoint">/api/users</span>
|
||||
</div>
|
||||
|
||||
<div class="api-item">
|
||||
<button onclick="createUser()">创建用户</button>
|
||||
<span class="method post">POST</span>
|
||||
<span class="endpoint">/api/users</span>
|
||||
</div>
|
||||
|
||||
<div class="api-item">
|
||||
<input type="number" id="userId" placeholder="用户ID" min="1">
|
||||
<button onclick="getUser()">获取用户</button>
|
||||
<span class="method get">GET</span>
|
||||
<span class="endpoint">/api/users/{id}</span>
|
||||
</div>
|
||||
|
||||
<div class="api-item">
|
||||
<button onclick="deleteUser()">删除用户</button>
|
||||
<span class="method delete">DELETE</span>
|
||||
<span class="endpoint">/api/users/{id}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="api-group">
|
||||
<h3>系统信息</h3>
|
||||
|
||||
<div class="api-item">
|
||||
<button onclick="getHealth()">健康检查</button>
|
||||
<span class="method get">GET</span>
|
||||
<span class="endpoint">/health</span>
|
||||
</div>
|
||||
|
||||
<div class="api-item">
|
||||
<button onclick="getStats()">服务器统计</button>
|
||||
<span class="method get">GET</span>
|
||||
<span class="endpoint">/api/stats</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="form-section">
|
||||
<h2>📝 创建用户表单</h2>
|
||||
<form id="userForm">
|
||||
<div class="form-group">
|
||||
<label for="name">姓名:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">邮箱:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="age">年龄:</label>
|
||||
<input type="number" id="age" name="age" min="0" max="150" required>
|
||||
</div>
|
||||
|
||||
<button type="submit">创建用户</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="response-section">
|
||||
<h2>📄 响应结果</h2>
|
||||
<pre id="response"></pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2024 Go Web服务器示例项目</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="/static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
241
golang-learning/10-projects/03-web-server/static/script.js
Normal file
241
golang-learning/10-projects/03-web-server/static/script.js
Normal file
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
271
golang-learning/10-projects/03-web-server/static/style.css
Normal file
271
golang-learning/10-projects/03-web-server/static/style.css
Normal file
@@ -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;
|
||||
}
|
Reference in New Issue
Block a user