This commit is contained in:
2025-08-24 13:01:09 +08:00
parent 61e51ad014
commit f028913eb8
36 changed files with 10420 additions and 70 deletions

View File

@@ -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 示例

View 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. 考虑包的向后兼容性
*/

View 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. 合理使用包的可见性规则
*/

View File

@@ -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)
}

View 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
*/

View 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()
}

View 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 中存储过多数据
*/

View 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. 合理使用并行测试提高效率
*/

View File

@@ -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. 测试是保证代码质量的重要手段

View File

@@ -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. 添加单位换算功能

View File

@@ -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:]
}
}

View 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))))
}

View 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
}

View 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()
}

View File

@@ -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. 添加任务模板功能

View File

@@ -0,0 +1,2 @@
# 这个文件用于保持 data 目录在 git 中被跟踪
# 实际的 todos.json 文件会在程序运行时自动创建

View 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("再见!")
}

View 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
}

View 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)
}

View 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
}

View 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)
}

View 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()
}

View 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()
}
}

View File

@@ -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

View File

@@ -0,0 +1,2 @@
# 这个文件用于保持 data 目录在 git 中被跟踪
# 实际的 users.json 文件会在程序运行时自动创建

View File

@@ -0,0 +1,5 @@
module webserver
go 1.19
require github.com/gorilla/mux v1.8.0

View 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("✅ 服务器已安全关闭")
}
}

View 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:]
}

View 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)
}

View 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)
})
}

View 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)
}

View 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
}

View 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
}

View 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>&copy; 2024 Go Web服务器示例项目</p>
</footer>
</div>
<script src="/static/script.js"></script>
</body>
</html>

View 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)));
}
}
}

View 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;
}