go语言项目--实例化(图书管理)--001
案例1:图书管理系统golang
一、项目概述
项目名称:图书管理系统
当前版本:v1.0 - 图书管理系统golang
业务功能:交互式命令行录入图书信息,保存到books.txt
学习目标:
Go基础语法(变量、常量、类型)
流程控制(if/else、for循环、break)
函数定义与多返回值
错误处理(error接口)
文件操作(os.Create、bufio.Writer)
字符串处理(strings.TrimSpace、strconv.ParseFloat)
运行方式:go run main.go
二、完整代码
文件路径:case1/main.go
go
复制
下载
// ============================================================ // 图书管理系统 v1.0 - 图书录入器 // 功能:交互式命令行录入图书信息,保存到 books.txt // 学习目标:基础语法、流程控制、函数、错误处理、文件操作 // 运行方式:go run main.go // ============================================================ package main import ( "bufio" "fmt" "os" "strconv" "strings" ) // ============================================================ // 常量定义 // ============================================================ const ( dataFile = "books.txt" // 数据文件名 exitCmd = "quit" // 退出命令 ) // ============================================================ // 主函数 - 程序入口 // ============================================================ func main() { printWelcome() // 打开文件(追加模式) file, err := os.OpenFile(dataFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { fmt.Printf("❌ 打开文件失败: %v\n", err) return } defer file.Close() writer := bufio.NewWriter(file) defer writer.Flush() scanner := bufio.NewScanner(os.Stdin) count := 0 // 主循环 for { fmt.Print("\n📖 请输入(书名,作者,价格),输入 quit 退出: ") scanner.Scan() input := strings.TrimSpace(scanner.Text()) if input == exitCmd { fmt.Printf("\n✅ 共录入 %d 本图书,已保存到 %s\n", count, dataFile) break } // 解析输入 title, author, priceStr, err := parseInput(input) if err != nil { fmt.Printf("❌ 格式错误: %v(正确格式:书名,作者,价格)\n", err) continue } // 校验 if err := validateTitle(title); err != nil { fmt.Printf("❌ %v\n", err) continue } if err := validateAuthor(author); err != nil { fmt.Printf("❌ %v\n", err) continue } price, err := parsePrice(priceStr) if err != nil { fmt.Printf("❌ %v\n", err) continue } // 写入文件 line := fmt.Sprintf("%s,%s,%.2f\n", title, author, price) if _, err := writer.WriteString(line); err != nil { fmt.Printf("❌ 写入失败: %v\n", err) continue } writer.Flush() count++ fmt.Printf("✅ 第 %d 本录入成功: %s\n", count, strings.TrimSpace(line)) } } // ============================================================ // 辅助函数 // ============================================================ func printWelcome() { fmt.Println("========================================") fmt.Println(" 📚 图书管理系统 v1.0 - 图书录入器") fmt.Println("========================================") fmt.Printf(" 💡 格式:书名,作者,价格\n") fmt.Printf(" 💡 输入 %s 退出\n", exitCmd) fmt.Printf(" 💡 数据保存到 %s\n", dataFile) fmt.Println("========================================") } // parseInput 按逗号分割输入,返回书名、作者、价格字符串 func parseInput(input string) (title, author, price string, err error) { parts := strings.Split(input, ",") if len(parts) != 3 { return "", "", "", fmt.Errorf("需要3个字段,实际%d个", len(parts)) } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), strings.TrimSpace(parts[2]), nil } // validateTitle 校验书名不能为空 func validateTitle(title string) error { if title == "" { return fmt.Errorf("书名不能为空") } return nil } // validateAuthor 校验作者不能为空 func validateAuthor(author string) error { if author == "" { return fmt.Errorf("作者不能为空") } return nil } // parsePrice 解析价格,必须为数字且 >= 0 func parsePrice(priceStr string) (float64, error) { price, err := strconv.ParseFloat(priceStr, 64) if err != nil { return 0, fmt.Errorf("价格格式错误") } if price < 0 { return 0, fmt.Errorf("价格不能为负数") } return price, nil } // ============================================================ // 备选交互方式:fmt.Scanln(注释掉,供参考对比) // 优点:写法简洁;缺点:无法处理带空格的输入 // ============================================================ // var title, author, priceStr string // fmt.Print("请输入书名: ") // fmt.Scanln(&title) // fmt.Print("请输入作者: ") // fmt.Scanln(&author) // fmt.Print("请输入价格: ") // fmt.Scanln(&priceStr) // ============================================================ // 本案例知识点: // 变量/常量 | if/for | 函数多返回值 | error处理 | 文件操作 | 字符串处理 // ============================================================三、测试代码(精简版)
文件路径:case1/main_test.go
go
复制
下载
package main import "testing" // 测试价格解析 func TestParsePrice(t *testing.T) { tests := []struct { name string input string want float64 ok bool // true表示期望成功 }{ {"正常价格", "59.00", 59.00, true}, {"整数", "59", 59.00, true}, {"零元", "0", 0, true}, {"负数", "-10", 0, false}, {"非数字", "abc", 0, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parsePrice(tt.input) if tt.ok && err != nil { t.Errorf("期望成功,但返回错误: %v", err) } if !tt.ok && err == nil { t.Errorf("期望失败,但成功了") } if tt.ok && got != tt.want { t.Errorf("got %f, want %f", got, tt.want) } }) } } // 测试书名校验 func TestValidateTitle(t *testing.T) { err := validateTitle("") if err == nil { t.Error("空书名应该返回错误") } err = validateTitle("Go语言") if err != nil { t.Errorf("正常书名不应该返回错误: %v", err) } } // 测试输入解析 func TestParseInput(t *testing.T) { title, author, price, err := parseInput("Go语言,张三,59.00") if err != nil { t.Errorf("解析失败: %v", err) } if title != "Go语言" || author != "张三" || price != "59.00" { t.Errorf("解析结果不符: %s, %s, %s", title, author, price) } _, _, _, err = parseInput("Go语言,张三") if err == nil { t.Error("字段不足应该返回错误") } } // 运行测试:go test -v // 运行指定测试:go test -v -run TestParsePrice四、运行指南
bash
复制
下载
# 运行程序 cd case1 go run main.go # 运行测试 go test -v # 测试覆盖率 go test -v -cover
五、交互示例
text
复制
下载
======================================== 📚 图书管理系统 v1.0 - 图书录入器 ======================================== 💡 格式:书名,作者,价格 💡 输入 quit 退出 💡 数据保存到 books.txt ======================================== 📖 请输入(书名,作者,价格),输入 quit 退出: Go语言实战,张三,59.00 ✅ 第 1 本录入成功: Go语言实战,张三,59.00 📖 请输入(书名,作者,价格),输入 quit 退出: ,王五,30 ❌ 书名不能为空 📖 请输入(书名,作者,价格),输入 quit 退出: 测试书,赵六,-10 ❌ 价格不能为负数 📖 请输入(书名,作者,价格),输入 quit 退出: quit ✅ 共录入 1 本图书,已保存到 books.txt
六、案例1 → 案例2 衔接
text
复制
下载
============================================================ 📌 案例1结束,案例2衔接说明 ============================================================ 1. 本案例已学知识点: ✅ 变量/常量、基本类型 ✅ if/else、for循环、break ✅ 函数定义与多返回值 ✅ error错误处理 ✅ 文件操作(os.OpenFile、defer) ✅ 缓冲写入(bufio.Writer) ✅ 字符串处理(TrimSpace、Split) ✅ 类型转换(strconv.ParseFloat) ✅ 单元测试(表驱动测试、t.Run) 2. 案例2将升级: 📄 books.txt → books.json(结构化数据) 📁 单文件 → 多包分层(model/repository/handler) 🔧 仅录入 → 完整CRUD(增删改查) 3. 本案例后续可优化(将在案例2中实现): 🔸 用结构体代替分散的字符串变量 🔸 用JSON替代纯文本,支持结构化存储 🔸 增加查询、更新、删除功能 🔸 增加内存存储,提高操作效率 ============================================================
