案例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替代纯文本,支持结构化存储
🔸 增加查询、更新、删除功能
🔸 增加内存存储,提高操作效率
============================================================