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

@@ -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
命令列表:
add <任务> - 添加新任务
list - 显示所有任务
done <ID> - 标记任务完成
delete <ID> - 删除任务
edit <ID> <新内容> - 编辑任务
search <关键词> - 搜索任务
stats - 显示统计信息
help - 显示帮助
quit - 退出程序
# 删除事项
go run main.go delete 1
```
> 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;
}