基于Go语言的记账demo设计与实现
前言
巩固新学的go语言语法基础,特此来练手回顾。由于是小demo,无GUI,数据只保留在本地,未接入数据库。
开发环境
os:Windows10
选用技术:Go
软件集成开发环境(IDE):Goland 2022.3.4
需求分析
完成个人日常的记账、查账,修改和删除等基本功能。要求写入本地实现数据的持久化
功能分析
新建账本
初始化切片充当数据容器,从本地读入或新建账本
记账
向账本内写入数据
查看账本
查看已记录的账本,并动态的展示余额。
修改账本
根据id号来修改相应的记账记录
删除记账
根据id号来删除相应的记账记录
退出
将内存中数据写入本地保存,然后实现程序正常退出即可
代码实现
使用结构体来表示每一条记账,内容包括:
type Transaction struct {
Id int
Amount float32 //金额
Type string //类型(支出或收入)
Description string // 备注
Date string //日期
}
实现结构体的构造方法:
func NewTransaction(id int, amount float32, date string, Type, Description string) *Transaction {
return &Transaction{id, amount, Type, Description, date}
}
菜单
func menu(ts []basic.Transaction) ([]basic.Transaction, int) {
fmt.Println("欢迎进入欢乐记账系统")
fmt.Println("-----1.新建账本 -----")
fmt.Println("-----2. 记账 -----")
fmt.Println("-----3.查看账本 -----")
fmt.Println("-----4.修改账本 -----")
fmt.Println("-----5.删除账本 -----")
fmt.Println("-----6. 退出 -----")
fmt.Printf("请输入对应数字进入您所需要的功能:")
var options int
_, err := fmt.Scanf("%d\n", &options)
if err != nil {
fmt.Println("输入有误:", err)
}
path := "E:\\code\\go\\person_budget\\Ledger.txt"
switch {
case options == 1:
if ts == nil {
tmp, flag := utils.ReadFile(ts, path)
if !flag {
ts = newLedger()
fmt.Println("新建账本成功!")
} else {
ts = tmp
fmt.Println("读取账本成功!")
}
} else {
fmt.Println("已存在账本")
}
case options == 2:
ts = insertLedger(ts)
case options == 3:
printLedger(ts)
case options == 4:
ts = modifyLedger(ts)
case options == 5:
ts = deleteLedger(ts)
case options == 6:
utils.WriteFile(ts, path)
return ts, 6
}
return ts, 0
}
新建账本/读取本地文件
切片存储每一个记账结构体
// 初始化账单
func newLedger() []basic.Transaction {
transaction := make([]basic.Transaction, 1)
//fmt.Println(transaction, len(transaction))
return transaction
}
读取本地文件
func ReadFile(ts []basic.Transaction, path string) ([]basic.Transaction, bool) {
file, err := os.Open(path)
if err != nil {
fmt.Println("打开文件错误:", err)
return nil, false
}
defer file.Close()
ts = make([]basic.Transaction, 0)
scanner := bufio.NewScanner(file)
// 逐行读取文件
for scanner.Scan() {
line := scanner.Text() // 获取当前行的内容
str_tmp := strings.Split(line, "--")
// 使用 strconv.Atoi 解析字符串
num, err := strconv.Atoi(str_tmp[0][5:])
if err != nil {
fmt.Println("转换错误:", err)
return nil, false
}
//fmt.Println(str_tmp[1][9:])
f64, _ := strconv.ParseFloat(str_tmp[1][9:], 32) // 解析为 float64
if err != nil {
fmt.Println("转换错误:", err)
return nil, false
}
tmp := basic.NewTransaction(num, float32(f64), str_tmp[4][9:], str_tmp[2][9:], str_tmp[3][9:])
ts = append(ts, *tmp)
}
// 检查读取过程中是否发生错误
if err := scanner.Err(); err != nil {
fmt.Println("读取文件错误:", err)
}
return ts, len(ts) != 0
}
记账
func insertLedger(ts []basic.Transaction) []basic.Transaction {
//fmt.Println(cap(ts))
if cap(ts) != 0 {
var amount float32
var Type string
var description string
fmt.Printf("金额:")
fmt.Scanf("%f\n", &amount)
fmt.Printf("类型:")
fmt.Scanf("%s\n", &Type)
fmt.Printf("描述:")
fmt.Scanf("%s\n", &description)
var id int
if ts[0].Id == 0 {
id = 1
} else {
id = len(ts) + 1
}
t := basic.NewTransaction(id, amount, time.Now().Format("2006-01-02 15:04:05"), Type, description)
if ts[0].Id == 0 {
ts[0] = *t
} else {
ts = append(ts, *t)
}
} else {
fmt.Println("账本为空")
return nil
}
return ts
}
查看账本
func printLedger(ts []basic.Transaction) {
if cap(ts) != 0 && ts[0].Id != 0 {
var sum float32
for _, data := range ts {
if data.Type == "支出" {
sum -= data.Amount
} else if data.Type == "收入" {
sum += data.Amount
}
fmt.Printf("id :%d,金额:%.2f,类型:%s,描述:%s,时间:%s\n", data.Id, data.Amount, data.Type, data.Description, data.Date)
}
fmt.Println("余额:", sum)
} else {
fmt.Println("账本为空")
}
}
修改账本
func modifyLedger(ts []basic.Transaction) []basic.Transaction {
if cap(ts) != 0 && ts[0].Id != 0 {
//打印账本
printLedger(ts)
//输入id修改
var id int
var amount float32
var Type string
var description string
for {
fmt.Printf("请输入要修改的账单id:")
fmt.Scanf("%d\n", &id)
fmt.Printf("修改后的金额:")
fmt.Scanf("%f\n", &amount)
fmt.Printf("修改后的类型:")
fmt.Scanf("%s\n", &Type)
fmt.Printf("修改后的描述:")
fmt.Scanf("%s\n", &description)
flag := false
for i, data := range ts {
if data.Id == id {
x := basic.NewTransaction(data.Id, amount, time.Now().Format("2006-01-02 15:04:05"), Type, description)
ts[i] = *x
fmt.Println("修改后:", ts[i])
flag = true
break
}
}
if !flag {
fmt.Println("请输入正确的id")
} else {
break
}
}
return ts
} else {
fmt.Println("账本为空")
return nil
}
}
删除记账
func deleteLedger(ts []basic.Transaction) []basic.Transaction {
//打印账本
printLedger(ts)
//输入id修改
var id int
flag := false
for {
fmt.Printf("请输入要修改的账单id:")
fmt.Scanf("%d\n", &id)
for i, data := range ts {
if data.Id == id {
fmt.Println("是否删除:(Y/N)", data)
var r string
fmt.Scanf("%s\n", &r)
if r == "Y" {
ts = append(ts[:i], ts[i+1:]...)
}
flag = true
break
}
}
if !flag {
fmt.Println("请输入正确的id")
} else {
break
}
}
return ts
}
退出
本模块逻辑十分简单,break跳出死循环即可。但退出前需将内存中的账本写入本地,实现数据的持久化。
写入代码:
func WriteFile(ts []basic.Transaction, path string) {
file, err := os.Create(path)
if err != nil {
fmt.Println("文件打开失败", err)
return
}
defer file.Close()
lines := make([]string, 0)
for _, data := range ts {
str := fmt.Sprintf("Id:%d--金额:%.2f--类型:%s--描述:%s--时间:%s", data.Id, data.Amount, data.Type, data.Description, data.Date)
lines = append(lines, str)
}
writer := bufio.NewWriter(file)
// 将切片内容写入文件
for _, line := range lines {
_, err := writer.WriteString(line + "\n") // 每行后添加换行符
if err != nil {
fmt.Println("写入文件错误:", err)
return
}
}
// 刷新缓冲区,将数据写入文件
writer.Flush()
}
总结
fmt.Scanf()。后加\n来吸收用户输入的回车,不然输入区里潜藏的回车将作用于下一个接收
切片的append会返回一个新的切片,故需返回新的切片