"添加第4章结构化工程内容,包含模块系统、包管理、工作空间及图书馆管理系统实现"

This commit is contained in:
kingecg 2025-07-03 23:30:51 +08:00
parent d3437f021e
commit d39ddbfdbc
13 changed files with 2166 additions and 0 deletions

1142
04.md Normal file

File diff suppressed because it is too large Load Diff

297
examples/ex04/Cargo.lock generated Normal file
View File

@ -0,0 +1,297 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cc"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "library"
version = "0.1.0"
dependencies = [
"chrono",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
]

17
examples/ex04/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "library"
version = "0.1.0"
edition = "2021"
authors = ["Rust Book Author <author@example.com>"]
description = "A modular library management system"
[dependencies]
chrono = "0.4"
[lib]
name = "library"
path = "src/lib.rs"
[[bin]]
name = "library-cli"
path = "src/main.rs"

19
examples/ex04/src/lib.rs Normal file
View File

@ -0,0 +1,19 @@
//! # 图书馆管理系统
//!
//! 一个模块化的图书馆管理系统,提供图书编目、用户管理和借阅服务。
pub mod models;
pub mod services;
mod utils; // 内部工具模块,不对外暴露
// 重新导出核心类型,简化用户导入
pub use models::{Book, User};
pub use services::{catalog::Catalog, lending::LendingService};
/// 系统版本号
pub const VERSION: &str = "0.1.0";
/// 创建一个新的图书馆实例
pub fn new_library() -> services::Library {
services::Library::new()
}

86
examples/ex04/src/main.rs Normal file
View File

@ -0,0 +1,86 @@
//! 图书馆管理系统命令行界面
use library::{Book, User, new_library};
fn main() {
println!("图书馆管理系统 v{}", library::VERSION);
println!("======================");
// 创建图书馆实例
let mut library = new_library();
// 添加一些示例图书
let books = [
Book::new("Rust编程之道", "张三", "978-1234567890"),
Book::new("数据结构与算法", "李四", "978-2345678901"),
Book::new("计算机网络", "王五", "978-3456789012"),
Book::new("操作系统原理", "赵六", "978-4567890123"),
Book::new("数据库系统概念", "钱七", "978-5678901234"),
];
println!("\n添加图书到目录...");
for book in &books {
match library.catalog_mut().add_book(book.clone()) {
Ok(_) => println!(" 已添加: {}", book),
Err(e) => println!(" 添加失败: {:?}", e),
}
}
// 添加一些用户
let users = [
User::new(1, "张明", "zhangming@example.com"),
User::new(2, "李华", "lihua@example.com"),
User::new(3, "王芳", "wangfang@example.com"),
];
println!("\n添加用户...");
for user in &users {
library.lending_mut().add_user(user.clone());
println!(" 已添加: {}", user);
}
// 演示借书
println!("\n借阅图书...");
let borrow_examples = [
(1, "978-1234567890"),
(2, "978-2345678901"),
(2, "978-3456789012"),
(3, "978-4567890123"),
];
for (user_id, isbn) in &borrow_examples {
match library.borrow_book(*user_id, isbn) {
Ok(_) => {
let user = library.lending().find_user(*user_id as u32).unwrap();
let book = library.catalog().find_by_isbn(isbn).unwrap();
println!(" {} 成功借阅: {}", user.name(), book.title());
},
Err(e) => println!(" 借阅失败: {:?}", e),
}
}
// 显示目录状态
println!("\n目录状态:");
println!(" 总图书数: {}", library.catalog().total_count());
println!(" 可借图书数: {}", library.catalog().available_count());
// 演示查找图书
println!("\n查找图书 (标题包含'数据'):");
for book in library.catalog().find_by_title("数据") {
println!(" 找到: {} ({})", book.title(), if book.is_available() { "可借" } else { "已借出" });
}
// 演示归还图书
println!("\n归还图书...");
match library.return_book(2, "978-2345678901") {
Ok(_) => println!(" 李华成功归还《数据结构与算法》"),
Err(e) => println!(" 归还失败: {:?}", e),
}
// 再次显示目录状态
println!("\n更新后的目录状态:");
println!(" 总图书数: {}", library.catalog().total_count());
println!(" 可借图书数: {}", library.catalog().available_count());
println!("\n程序结束");
}

View File

@ -0,0 +1,68 @@
//! 图书相关的数据模型
use std::fmt;
/// 表示图书馆中的一本书
#[derive(Debug, Clone, PartialEq)]
pub struct Book {
title: String,
author: String,
isbn: String,
available: bool,
}
impl Book {
/// 创建一本新书
///
/// # 示例
/// ```
/// use library::Book;
/// let book = Book::new("Rust编程艺术", "张三", "978-1234567890");
/// assert_eq!(book.title(), "Rust编程艺术");
/// assert!(book.is_available());
/// ```
pub fn new(title: &str, author: &str, isbn: &str) -> Self {
Self {
title: title.to_string(),
author: author.to_string(),
isbn: isbn.to_string(),
available: true,
}
}
/// 获取书名
pub fn title(&self) -> &str {
&self.title
}
/// 获取作者
pub fn author(&self) -> &str {
&self.author
}
/// 获取ISBN
pub fn isbn(&self) -> &str {
&self.isbn
}
/// 检查书是否可借
pub fn is_available(&self) -> bool {
self.available
}
/// 标记为已借出
pub(crate) fn mark_borrowed(&mut self) {
self.available = false;
}
/// 标记为已归还
pub(crate) fn mark_returned(&mut self) {
self.available = true;
}
}
impl fmt::Display for Book {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} by {} (ISBN: {})", self.title, self.author, self.isbn)
}
}

View File

@ -0,0 +1,7 @@
//! 系统中使用的数据模型
pub mod book;
pub mod user;
pub use book::Book;
pub use user::User;

View File

@ -0,0 +1,93 @@
//! 用户相关的数据模型
use std::fmt;
/// 用户ID类型
pub type UserId = u32;
/// 表示图书馆的一名用户
#[derive(Debug, Clone, PartialEq)]
pub struct User {
id: UserId,
name: String,
email: String,
active: bool,
borrowed_books: Vec<String>, // 存储已借阅书籍的ISBN
}
impl User {
/// 创建一个新用户
///
/// # 示例
/// ```
/// use library::User;
/// let user = User::new(1, "李四", "lisi@example.com");
/// assert_eq!(user.name(), "李四");
/// assert!(user.is_active());
/// ```
pub fn new(id: UserId, name: &str, email: &str) -> Self {
Self {
id,
name: name.to_string(),
email: email.to_string(),
active: true,
borrowed_books: Vec::new(),
}
}
/// 获取用户ID
pub fn id(&self) -> UserId {
self.id
}
/// 获取用户名
pub fn name(&self) -> &str {
&self.name
}
/// 获取用户邮箱
pub fn email(&self) -> &str {
&self.email
}
/// 检查用户是否激活
pub fn is_active(&self) -> bool {
self.active
}
/// 获取用户已借阅的书籍ISBN列表
pub fn borrowed_books(&self) -> &[String] {
&self.borrowed_books
}
/// 记录用户借阅了一本书
pub(crate) fn borrow_book(&mut self, isbn: &str) {
self.borrowed_books.push(isbn.to_string());
}
/// 记录用户归还了一本书
pub(crate) fn return_book(&mut self, isbn: &str) -> bool {
if let Some(pos) = self.borrowed_books.iter().position(|b| b == isbn) {
self.borrowed_books.remove(pos);
true
} else {
false
}
}
/// 停用用户账户
pub(crate) fn deactivate(&mut self) {
self.active = false;
}
/// 重新激活用户账户
pub(crate) fn activate(&mut self) {
self.active = true;
}
}
impl fmt::Display for User {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (ID: {}, Email: {})", self.name, self.id, self.email)
}
}

View File

@ -0,0 +1,113 @@
//! 图书目录管理服务
use std::collections::HashMap;
use crate::models::Book;
use crate::utils::validation;
/// 图书目录错误类型
#[derive(Debug)]
pub enum CatalogError {
/// ISBN已存在
DuplicateIsbn(String),
/// ISBN不存在
IsbnNotFound(String),
/// ISBN格式无效
InvalidIsbn(String),
}
/// 图书目录服务
pub struct Catalog {
books: HashMap<String, Book>,
}
impl Catalog {
/// 创建一个新的目录服务
pub fn new() -> Self {
Self {
books: HashMap::new(),
}
}
/// 添加一本新书到目录
///
/// # 错误
/// 如果ISBN已存在或无效返回错误
///
/// # 示例
/// ```
/// use library::{Book, Catalog};
///
/// let mut catalog = Catalog::new();
/// let book = Book::new("Rust编程", "张三", "978-1234567890");
///
/// // 首次添加成功
/// assert!(catalog.add_book(book.clone()).is_ok());
///
/// // 重复添加失败
/// assert!(catalog.add_book(book).is_err());
/// ```
pub fn add_book(&mut self, book: Book) -> Result<(), CatalogError> {
let isbn = book.isbn();
// 验证ISBN格式
if !validation::is_valid_isbn(isbn) {
return Err(CatalogError::InvalidIsbn(isbn.to_string()));
}
// 检查ISBN是否已存在
if self.books.contains_key(isbn) {
return Err(CatalogError::DuplicateIsbn(isbn.to_string()));
}
// 添加图书
self.books.insert(isbn.to_string(), book);
Ok(())
}
/// 根据ISBN查找图书
pub fn find_by_isbn(&self, isbn: &str) -> Option<&Book> {
self.books.get(isbn)
}
/// 根据ISBN查找可变图书引用
pub(crate) fn find_by_isbn_mut(&mut self, isbn: &str) -> Option<&mut Book> {
self.books.get_mut(isbn)
}
/// 根据标题查找图书
pub fn find_by_title(&self, title: &str) -> Vec<&Book> {
self.books.values()
.filter(|book| book.title().contains(title))
.collect()
}
/// 根据作者查找图书
pub fn find_by_author(&self, author: &str) -> Vec<&Book> {
self.books.values()
.filter(|book| book.author().contains(author))
.collect()
}
/// 获取所有图书
pub fn all_books(&self) -> Vec<&Book> {
self.books.values().collect()
}
/// 获取可用图书数量
pub fn available_count(&self) -> usize {
self.books.values()
.filter(|book| book.is_available())
.count()
}
/// 获取总图书数量
pub fn total_count(&self) -> usize {
self.books.len()
}
}
impl Default for Catalog {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,183 @@
//! 图书借阅管理服务
use std::collections::HashMap;
use crate::models::User;
use crate::models::user::UserId;
use crate::services::catalog::Catalog;
/// 借阅记录
#[derive(Debug)]
pub struct LoanRecord {
user_id: UserId,
isbn: String,
loan_date: chrono::DateTime<chrono::Utc>,
due_date: chrono::DateTime<chrono::Utc>,
}
/// 借阅错误类型
#[derive(Debug)]
pub enum LendingError {
/// 用户不存在
UserNotFound(UserId),
/// 图书不存在
BookNotFound(String),
/// 图书不可用(已借出)
BookUnavailable(String),
/// 用户账户未激活
UserInactive(UserId),
/// 用户已达到最大借阅限制
BorrowLimitReached(UserId),
/// 借阅记录不存在
LoanNotFound,
}
/// 借阅服务
pub struct LendingService {
users: HashMap<UserId, User>,
active_loans: Vec<LoanRecord>,
max_loans_per_user: usize,
loan_duration_days: i64,
}
impl LendingService {
/// 创建一个新的借阅服务
pub fn new() -> Self {
Self {
users: HashMap::new(),
active_loans: Vec::new(),
max_loans_per_user: 5,
loan_duration_days: 14,
}
}
/// 设置每位用户的最大借阅数量
pub fn set_max_loans_per_user(&mut self, max: usize) {
self.max_loans_per_user = max;
}
/// 设置借阅期限(天数)
pub fn set_loan_duration(&mut self, days: i64) {
self.loan_duration_days = days;
}
/// 添加用户
pub fn add_user(&mut self, user: User) {
self.users.insert(user.id(), user);
}
/// 查找用户
pub fn find_user(&self, user_id: UserId) -> Option<&User> {
self.users.get(&user_id)
}
/// 查找可变用户引用
fn find_user_mut(&mut self, user_id: UserId) -> Option<&mut User> {
self.users.get_mut(&user_id)
}
/// 借阅图书
pub fn borrow_book(
&mut self,
user_id: UserId,
isbn: &str,
catalog: &mut Catalog
) -> Result<(), LendingError> {
// 先获取最大借阅数量
let max_loans = self.max_loans_per_user;
// 检查用户是否存在
let user = self.find_user_mut(user_id)
.ok_or(LendingError::UserNotFound(user_id))?;
// 检查用户是否激活
if !user.is_active() {
return Err(LendingError::UserInactive(user_id));
}
// 检查用户是否达到借阅上限
if user.borrowed_books().len() >= max_loans {
return Err(LendingError::BorrowLimitReached(user_id));
}
// 检查图书是否存在并可借
let book = catalog.find_by_isbn_mut(isbn)
.ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?;
if !book.is_available() {
return Err(LendingError::BookUnavailable(isbn.to_string()));
}
// 更新图书状态
book.mark_borrowed();
// 更新用户借阅记录
user.borrow_book(isbn);
// 创建借阅记录
let now = chrono::Utc::now();
let due_date = now + chrono::Duration::days(self.loan_duration_days);
self.active_loans.push(LoanRecord {
user_id,
isbn: isbn.to_string(),
loan_date: now,
due_date,
});
Ok(())
}
/// 归还图书
pub fn return_book(
&mut self,
user_id: UserId,
isbn: &str,
catalog: &mut Catalog
) -> Result<(), LendingError> {
// 检查用户是否存在
let user = self.find_user_mut(user_id)
.ok_or(LendingError::UserNotFound(user_id))?;
// 检查图书是否存在
let book = catalog.find_by_isbn_mut(isbn)
.ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?;
// 更新用户借阅记录
if !user.return_book(isbn) {
return Err(LendingError::LoanNotFound);
}
// 更新图书状态
book.mark_returned();
// 移除借阅记录
let loan_index = self.active_loans.iter()
.position(|loan| loan.user_id == user_id && loan.isbn == isbn)
.ok_or(LendingError::LoanNotFound)?;
self.active_loans.remove(loan_index);
Ok(())
}
/// 获取用户的所有借阅
pub fn get_user_loans(&self, user_id: UserId) -> Vec<&LoanRecord> {
self.active_loans.iter()
.filter(|loan| loan.user_id == user_id)
.collect()
}
/// 获取所有逾期借阅
pub fn get_overdue_loans(&self) -> Vec<&LoanRecord> {
let now = chrono::Utc::now();
self.active_loans.iter()
.filter(|loan| loan.due_date < now)
.collect()
}
}
impl Default for LendingService {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,58 @@
//! 图书馆业务服务模块
pub mod catalog;
pub mod lending;
// 不需要在这里导入,因为每个子模块都有自己的导入
/// 图书馆主服务
pub struct Library {
catalog: catalog::Catalog,
lending: lending::LendingService,
}
impl Library {
/// 创建一个新的图书馆实例
pub fn new() -> Self {
Self {
catalog: catalog::Catalog::new(),
lending: lending::LendingService::new(),
}
}
/// 获取目录服务
pub fn catalog(&self) -> &catalog::Catalog {
&self.catalog
}
/// 获取可变目录服务
pub fn catalog_mut(&mut self) -> &mut catalog::Catalog {
&mut self.catalog
}
/// 获取借阅服务
pub fn lending(&self) -> &lending::LendingService {
&self.lending
}
/// 获取可变借阅服务
pub fn lending_mut(&mut self) -> &mut lending::LendingService {
&mut self.lending
}
/// 借阅图书
pub fn borrow_book(&mut self, user_id: usize, isbn: &str) -> Result<(), lending::LendingError> {
self.lending.borrow_book(user_id as u32, isbn, &mut self.catalog)
}
/// 归还图书
pub fn return_book(&mut self, user_id: usize, isbn: &str) -> Result<(), lending::LendingError> {
self.lending.return_book(user_id as u32, isbn, &mut self.catalog)
}
}
impl Default for Library {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,3 @@
//! 内部工具函数
pub mod validation;

View File

@ -0,0 +1,80 @@
//! 数据验证工具
/// 检查ISBN是否有效
///
/// 简化的ISBN-13验证仅检查格式和长度
///
/// # 示例
/// ```
/// use library::utils::validation::is_valid_isbn;
///
/// assert!(is_valid_isbn("978-1234567890"));
/// assert!(!is_valid_isbn("invalid-isbn"));
/// ```
pub fn is_valid_isbn(isbn: &str) -> bool {
// 移除所有连字符
let cleaned = isbn.replace('-', "");
// 检查长度ISBN-13应为13位数字
if cleaned.len() != 13 {
return false;
}
// 检查是否全为数字
cleaned.chars().all(|c| c.is_ascii_digit())
}
/// 检查电子邮件格式是否有效
///
/// 简化的电子邮件验证,仅检查基本格式
pub fn is_valid_email(email: &str) -> bool {
// 检查是否包含@符号
if !email.contains('@') {
return false;
}
// 检查@符号前后是否有内容
let parts: Vec<&str> = email.split('@').collect();
if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
return false;
}
// 检查域名部分是否包含至少一个点
if !parts[1].contains('.') {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_isbn() {
assert!(is_valid_isbn("9781234567890"));
assert!(is_valid_isbn("978-1234567890"));
}
#[test]
fn test_invalid_isbn() {
assert!(!is_valid_isbn("123"));
assert!(!is_valid_isbn("abcdefghijklm"));
assert!(!is_valid_isbn("978-12345"));
}
#[test]
fn test_valid_email() {
assert!(is_valid_email("user@example.com"));
assert!(is_valid_email("name.surname@domain.co.uk"));
}
#[test]
fn test_invalid_email() {
assert!(!is_valid_email("invalid"));
assert!(!is_valid_email("@domain.com"));
assert!(!is_valid_email("user@"));
assert!(!is_valid_email("user@domain"));
}
}