基于Scala的图书管理系统设计与实现
一、项目介绍
本项目是一个使用Scala语言开发的图书管理系统,实现了用户管理、图书管理、借阅记录管理等核心功能。
二、系统架构设计与实现
本系统采用MVC分层架构,包含数据模型层(Model)、数据访问层(DAO)、业务逻辑层(Service)和表示层(Presentation)。下面详细介绍各层的实现。
2.1 表示层:用户界面交互设计
表示层负责处理用户交互,根据用户角色显示不同的菜单选项。
案例:游客菜单界面实现
代码
package org.app
import org.app.models.{BookModel, UserModel}
import org.app.service.{BookService, UserService}
import scala.io.StdIn
import scala.io.StdIn.readLine
class LibrarayPresentation {
// ... 其他代码 ...
def showVisitorMenu(): Unit = {
var running = true
while (running) {
println("欢迎来到我的图书管理系统,请选择")
println("1. 查看所有图书")
println("2. 查询图书")
println("3. 登录")
println("4. 离开")
val choice = StdIn.readLine().trim
choice match {
case "1" =>
val results = BookService.searchBooks("")
if(results.nonEmpty){
results.foreach(println)
} else {
println("没有找到图书")
}
case "2" =>
val query = readLine("请输入查询关键字(书名, 作者):").trim
val results = BookService.searchBooks(query)
if(results.nonEmpty){
println("======查询图书的结果=======")
results.foreach(println)
} else {
println("没有找到图书")
}
case "3" =>
println("请输入用户名: ")
val username = StdIn.readLine().trim
println("请输入密码: ")
val password = StdIn.readLine().trim
val userOpt = UserService.authenticateUser(username, password)
if(userOpt.isEmpty){
println("用户名或密码错误")
} else {
println("登录成功")
val user = userOpt.get
user.role match {
case "管理员" => showAdminMenu(user)
case "普通用户" => showUserMenu(user)
}
}
case "4" =>
running = false
println("感谢你的使用, 下次再见")
case _ => println("无效的选择")
}
}
}
// ... 其他方法 ...
}
结果展示
代码分析
- 循环菜单设计:使用
while(running)循环保持菜单持续显示,直到用户选择离开 - 模式匹配处理用户输入:使用Scala的
match语句优雅地处理不同的用户选择 - 模块化设计:每个功能选项调用对应的Service层方法,实现了关注点分离
- 异常处理:用户登录失败时有明确的提示信息
- 角色路由:登录成功后根据用户角色(管理员/普通用户)跳转到对应的菜单界面
2.2 业务逻辑层:核心功能实现
业务逻辑层封装了系统的核心业务规则和处理逻辑。
案例:图书借阅业务逻辑
代码
package org.app.service
import org.app.dao.{BookDAO, BorrowRecordDAO}
import org.app.models.{BookModel, BorrowRecordModel}
import java.time.LocalDateTime
import scala.collection.mutable.ListBuffer
class BookService {
private val bookDAO = new BookDAO()
private val borrowRecordDAO = new BorrowRecordDAO()
def borrowBook(username: String, bookId: Int): Boolean = {
val books = bookDAO.loadBooks()
val records = borrowRecordDAO.loadBorrowRecords()
val book = books.find(b => b.id == bookId)
if (book.nonEmpty) {
val b = book.get
if (b.available) {
b.available = false
bookDAO.saveBooks(books)
records += BorrowRecordModel(username, b.id, b.name, LocalDateTime.now().toString)
borrowRecordDAO.saveBorrowRecords(records)
println("借阅成功,已保存借阅记录")
true
} else {
println("这本书被借走了")
false
}
} else {
println("没有找到这本书")
false
}
}
// ... 其他方法 ...
}
结果展示
代码分析
-
业务规则验证:
- 检查图书是否存在(
books.find(b => b.id == bookId)) - 检查图书是否可借(
if (b.available)) - 确保同一本书不会被重复借出
- 检查图书是否存在(
-
事务性操作:
- 更新图书状态:将
available设为false - 创建借阅记录:记录借阅人、图书信息、借阅时间
- 数据持久化:分别保存到图书文件和借阅记录文件
- 更新图书状态:将
-
状态管理:
- 使用
available字段管理图书借阅状态 - 借阅记录中包含时间戳,便于追踪和管理
- 使用
-
用户反馈:
- 成功借阅:"借阅成功,已保存借阅记录"
- 图书已借出:"这本书被借走了"
- 图书不存在:"没有找到这本书"
案例:图书归还业务逻辑
代码
def returnBook(username: String, bookId: Int): Boolean = {
queryBorrowRecords(username).find(r => r.bookID == bookId && r.returnDate.isEmpty) match {
case Some(record) =>
val books = bookDAO.loadBooks()
val b = books.find(_.id == bookId).get
b.available = true
bookDAO.saveBooks(books)
val records = borrowRecordDAO.loadBorrowRecords()
val r = records.find(r => r.bookID == bookId && r.userName == username && r.returnDate.isEmpty).get
r.returnDate = Some(LocalDateTime.now().toString)
borrowRecordDAO.saveBorrowRecords(records)
true
case None => false
}
}
结果展示
代码分析
-
权限验证:
- 通过
queryBorrowRecords(username).find(...)确保用户只能归还自己借阅的图书 - 检查图书是否处于"未归还"状态(
returnDate.isEmpty)
- 通过
-
状态更新:
- 图书状态:将
available设为true - 借阅记录:设置归还时间为当前时间
- 图书状态:将
-
数据一致性:
- 同时更新图书文件和借阅记录文件
- 使用
Option类型优雅处理空值情况
2.3 数据访问层:文件存储实现
数据访问层负责与数据存储(文本文件)的交互。
案例:图书数据持久化
代码
package org.app
package dao
import models.BookModel
import scala.collection.mutable.ListBuffer
class BookDAO {
def loadBooks(): ListBuffer[BookModel] = {
val books = new ListBuffer[BookModel]()
val source = scala.io.Source.fromFile("books.txt")
for(line <- source.getLines()){
val Array(id,name,author,available) = line.split(",")
books += BookModel(id.toInt,name,author,available.toBoolean)
}
source.close()
books
}
def saveBooks(books: ListBuffer[BookModel]): Unit = {
val writer = new java.io.PrintWriter(new java.io.File("books.txt"))
for(book <- books){
writer.println(book.id + "," + book.name + "," + book.author + "," + book.available)
}
writer.close()
}
}
代码分析
- 简单文件存储:使用纯文本文件(CSV格式)存储数据
- 数据序列化:将对象属性用逗号分隔保存为字符串
- 资源管理:正确关闭文件流(
source.close()、writer.close()) - 类型转换:从字符串转换为相应的数据类型(
toInt、toBoolean)
案例:借阅记录管理
代码
package org.app
package dao
import models.BorrowRecordModel
import scala.collection.mutable.ListBuffer
import scala.io.Source
class BorrowRecordDAO {
def loadBorrowRecords(): ListBuffer[BorrowRecordModel] = {
val borrowRecords = ListBuffer[BorrowRecordModel]()
val lines = Source.fromFile("borrow_records.txt")
for(line <- lines.getLines()){
val parts = line.split(",")
borrowRecords += BorrowRecordModel(
parts(0),
parts(1).toInt,
parts(2),
parts(3),
if(parts.length>4) Some(parts(4)) else None
)
}
borrowRecords
}
def saveBorrowRecords(records: ListBuffer[BorrowRecordModel]): Unit = {
val writer = new java.io.PrintWriter("borrow_records.txt")
for(record <- records){
writer.println(record.userName+","+record.bookID+","+record.bookName+","+record.borrowDate+","+record.returnDate.getOrElse(""))
}
writer.close()
}
}
结果展示
代码分析
-
灵活的字段处理:
- 归还日期使用
Option[String]类型,处理"未归还"情况 - 通过判断数组长度(
if(parts.length>4))处理可选字段
- 归还日期使用
-
数据完整性:
- 保存时使用
getOrElse("")处理None值 - 所有字段使用逗号分隔,保持格式一致
- 保存时使用
-
时间戳管理:
- 借阅时间使用ISO格式时间戳
- 便于按时间排序和查询
2.4 数据模型层:领域对象设计
数据模型层定义了系统的核心领域对象。
案例:图书模型设计
代码
package org.app.models
case class BookModel(id: Int, name: String, author: String, var available: Boolean) {
override def toString: String = {
val availableStr = if (available) "可外借" else "已借出"
// 计算字符串的显示宽度(中文2,英文1)
def displayWidth(s: String): Int = {
var width = 0
for (c <- s) {
if ((c >= 0x4E00 && c <= 0x9FFF) ||
(c >= 0x3400 && c <= 0x4DBF) ||
(c >= 0x20000 && c <= 0x2A6DF) ||
(c >= 0x3000 && c <= 0x303F) ||
(c >= 0xFF00 && c <= 0xFFEF)) {
width += 2
} else {
width += 1
}
}
width
}
// 补齐字符串到指定显示宽度
def padToDisplayWidth(s: String, targetWidth: Int): String = {
val currentWidth = displayWidth(s)
if (currentWidth >= targetWidth) s
else s + " " * (targetWidth - currentWidth)
}
val idTargetWidth = 4
val nameTargetWidth = 16
val authorTargetWidth = 12
val formattedId = padToDisplayWidth(id.toString, idTargetWidth)
val formattedName = padToDisplayWidth(name, nameTargetWidth)
val formattedAuthor = padToDisplayWidth(author, authorTargetWidth)
s"编号:$formattedId $formattedName $formattedAuthor $availableStr"
}
}
代码分析
-
Case Class特性:
- 使用Scala的
case class自动生成equals、hashCode、toString等方法 - 不可变性与可变字段结合:
id、name、author不可变,available可变
- 使用Scala的
-
格式化显示:
- 针对中英文混合排版,实现了字符宽度计算
- 支持中文全角字符(宽度为2)和英文半角字符(宽度为1)
- 自动对齐列,使输出更美观
-
业务状态表示:
available字段表示图书借阅状态toString方法中将布尔值转换为中文"可外借"/"已借出"
案例:借阅记录模型
代码
package org.app
package models
case class BorrowRecordModel(
userName: String,
bookID: Int,
bookName: String,
borrowDate: String,
var returnDate: Option[String] = None
)
代码分析
-
时间戳管理:
borrowDate:借阅时间(必须)returnDate:归还时间(可选,使用Option类型)
-
数据关联:
- 通过
userName关联用户 - 通过
bookID和bookName关联图书信息
- 通过
-
可变设计:
returnDate设为var,因为归还操作需要更新该字段- 其他字段设为不可变,保证数据一致性
三、核心功能模块详解
3.1 用户管理模块
代码
package org.app
package service
import dao.UserDAO
import org.app.models.UserModel
class UserService {
private val userDAO= new UserDAO()
def authenticateUser(username: String, password: String): Option[UserModel] = {
val users = userDAO.loadUsers()
users.find(user => user.username == username && user.password == password)
}
def addUser(username: String): Boolean = {
val users = userDAO.loadUsers()
val user = users.find(_.username == username)
if (user.isEmpty) {
users += UserModel(username, "123", "普通用户")
userDAO.saveUsers(users)
true
} else {
false
}
}
}
结果展示
代码分析
-
用户认证:
- 使用
Option[UserModel]类型处理用户查找结果 - 用户名和密码精确匹配
- 使用
-
用户添加:
- 检查用户名唯一性(
users.find(_.username == username)) - 默认密码为"123",角色为"普通用户"
- 返回布尔值表示操作结果
- 检查用户名唯一性(
-
数据存储:
- 使用
UserDAO进行数据持久化 - 每次操作都重新加载和保存整个用户列表
- 使用
3.2 图书查询模块
代码
def searchBooks(query: String): ListBuffer[BookModel] = {
val books = bookDAO.loadBooks()
query match {
case "" => books
case _ => books.filter(b => b.name.contains(query) || b.author.contains(query))
}
}
代码分析
-
灵活查询:
- 空查询字符串返回所有图书
- 非空查询在书名和作者名中搜索包含关键字的结果
-
大小写敏感:
- 使用
contains方法进行子串匹配 - 支持中文文本搜索
- 使用
-
返回类型:
- 返回
ListBuffer[BookModel],支持后续的添加和删除操作
- 返回
总结
本图书管理系统是一个完整的Scala应用示例,展示了如何使用Scala语言构建分层架构的实用系统。系统实现了用户管理、图书管理、借阅记录等核心功能,代码结构清晰,逻辑完整。通过这个项目,可以学习到Scala在实际项目中的应用,包括模式匹配、集合操作、文件IO、类型系统等关键特性。
注意: 本文中的代码中
图片中标注内容并未延展
下图为作者自己补充