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

============================================================
相关推荐
破茧蝴蝶5 小时前
Python分布式爬虫完整教程
开发语言·python
数据库小学妹6 小时前
国产数据库怎么选?兼容、迁移、核心案例与融合架构四道关
数据库·架构·国产数据库·数据库迁移·融合数据库
狗凯之家源码网6 小时前
全新 PHP 轻量客服聊天室源码深度评测
开发语言·php·聊天室系统
远离UE46 小时前
UE5 Substrate Thin Film 源码拆析
开发语言·javascript·ue5
keyipatience6 小时前
JavaScript学习(1)书写位置,变量、数据类型、运算符
开发语言·前端·javascript·css·学习
IvorySQL6 小时前
PostgreSQL 日报| PgBouncer 修复三个 CVE 漏洞(10 月 15 日)
数据库·postgresql·区块链
饺子大魔王的男人6 小时前
后台表格、查询、流程总在重复写?我用ToolJet把PostgreSQL直接做成可远程访问的业务系统
数据库·postgresql
Gauss松鼠会7 小时前
高斯数据库(openGauss)的物理存储结构
运维·服务器·数据库·sql·opengauss·经验总结
栈帧之外7 小时前
【C++】STL之set
开发语言·c++