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

============================================================
相关推荐
认真的酒窝9 小时前
自己动手开发编译器(十一)语义分析
java·开发语言
白帽小丑9 小时前
# 一次 MySQL DELETE 误操作的数据恢复尝试实录
数据库·mysql
wbs_scy11 小时前
Linux C++ 高并发编程:线程池全链路深度解析,从原理到手撕实现
java·开发语言
JAVA面经实录91711 小时前
Linux 常用命令完整知识体系
java·linux·开发语言·汇编
贪玩的蛋挞11 小时前
C#与闭包
开发语言·c#
端庄的战斗机12 小时前
javascript 设计模式(文章很长,请自备瓜子,水果和眼药水)
开发语言·javascript·设计模式
Quincy_Freak12 小时前
信创内网数据规范实践:银河麒麟下SQLite本地数据安全管理方案
数据库·sqlite·arm·数据库管理·大数据分析·银河麒麟·aarch64
开发小程序的之朴12 小时前
认识安企CMS-安装安企CMS的环境要求
nginx·golang·系统架构
Full Stack Developme12 小时前
Java LRU 与 LFU 算法及应用
java·开发语言·算法
2601_9626838914 小时前
治理遗留系统中的“生肉 SQL”:一次用多模型协作优化慢查询的实战复盘
数据库·人工智能·sql