基于Scala的图书管理系统设计与实现

87 阅读8分钟

基于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("无效的选择")
      }
    }
  }
  
  // ... 其他方法 ...
}

结果展示

2.png

代码分析

  1. 循环菜单设计:使用while(running)循环保持菜单持续显示,直到用户选择离开
  2. 模式匹配处理用户输入:使用Scala的match语句优雅地处理不同的用户选择
  3. 模块化设计:每个功能选项调用对应的Service层方法,实现了关注点分离
  4. 异常处理:用户登录失败时有明确的提示信息
  5. 角色路由:登录成功后根据用户角色(管理员/普通用户)跳转到对应的菜单界面

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
    }
  }
  
  // ... 其他方法 ...
}

结果展示

3.1.png

代码分析

  1. 业务规则验证

    • 检查图书是否存在(books.find(b => b.id == bookId)
    • 检查图书是否可借(if (b.available)
    • 确保同一本书不会被重复借出
  2. 事务性操作

    • 更新图书状态:将available设为false
    • 创建借阅记录:记录借阅人、图书信息、借阅时间
    • 数据持久化:分别保存到图书文件和借阅记录文件
  3. 状态管理

    • 使用available字段管理图书借阅状态
    • 借阅记录中包含时间戳,便于追踪和管理
  4. 用户反馈

    • 成功借阅:"借阅成功,已保存借阅记录"
    • 图书已借出:"这本书被借走了"
    • 图书不存在:"没有找到这本书"
案例:图书归还业务逻辑

代码

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
  }
}

结果展示

3.3.png

代码分析

  1. 权限验证

    • 通过queryBorrowRecords(username).find(...)确保用户只能归还自己借阅的图书
    • 检查图书是否处于"未归还"状态(returnDate.isEmpty
  2. 状态更新

    • 图书状态:将available设为true
    • 借阅记录:设置归还时间为当前时间
  3. 数据一致性

    • 同时更新图书文件和借阅记录文件
    • 使用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()
  }
}

代码分析

  1. 简单文件存储:使用纯文本文件(CSV格式)存储数据
  2. 数据序列化:将对象属性用逗号分隔保存为字符串
  3. 资源管理:正确关闭文件流(source.close()writer.close()
  4. 类型转换:从字符串转换为相应的数据类型(toInttoBoolean
案例:借阅记录管理

代码

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()
  }
}

结果展示

3.2.png

代码分析

  1. 灵活的字段处理

    • 归还日期使用Option[String]类型,处理"未归还"情况
    • 通过判断数组长度(if(parts.length>4))处理可选字段
  2. 数据完整性

    • 保存时使用getOrElse("")处理None
    • 所有字段使用逗号分隔,保持格式一致
  3. 时间戳管理

    • 借阅时间使用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"
  }
}

代码分析

  1. Case Class特性

    • 使用Scala的case class自动生成equalshashCodetoString等方法
    • 不可变性与可变字段结合:idnameauthor不可变,available可变
  2. 格式化显示

    • 针对中英文混合排版,实现了字符宽度计算
    • 支持中文全角字符(宽度为2)和英文半角字符(宽度为1)
    • 自动对齐列,使输出更美观
  3. 业务状态表示

    • available字段表示图书借阅状态
    • toString方法中将布尔值转换为中文"可外借"/"已借出"
案例:借阅记录模型

代码

package org.app
package models

case class BorrowRecordModel(
  userName: String,
  bookID: Int,
  bookName: String,
  borrowDate: String,
  var returnDate: Option[String] = None
)

代码分析

  1. 时间戳管理

    • borrowDate:借阅时间(必须)
    • returnDate:归还时间(可选,使用Option类型)
  2. 数据关联

    • 通过userName关联用户
    • 通过bookIDbookName关联图书信息
  3. 可变设计

    • 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
    }
  }
}

结果展示

5.png

代码分析

  1. 用户认证

    • 使用Option[UserModel]类型处理用户查找结果
    • 用户名和密码精确匹配
  2. 用户添加

    • 检查用户名唯一性(users.find(_.username == username)
    • 默认密码为"123",角色为"普通用户"
    • 返回布尔值表示操作结果
  3. 数据存储

    • 使用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))
  }
}

代码分析

  1. 灵活查询

    • 空查询字符串返回所有图书
    • 非空查询在书名和作者名中搜索包含关键字的结果
  2. 大小写敏感

    • 使用contains方法进行子串匹配
    • 支持中文文本搜索
  3. 返回类型

    • 返回ListBuffer[BookModel],支持后续的添加和删除操作

总结

本图书管理系统是一个完整的Scala应用示例,展示了如何使用Scala语言构建分层架构的实用系统。系统实现了用户管理、图书管理、借阅记录等核心功能,代码结构清晰,逻辑完整。通过这个项目,可以学习到Scala在实际项目中的应用,包括模式匹配、集合操作、文件IO、类型系统等关键特性。

注意: 本文中的代码中

Snipaste_2026-01-03_21-44-33.png

图片中标注内容并未延展

下图为作者自己补充

1.png