"添加第4章结构化工程内容,包含模块系统、包管理、工作空间及图书馆管理系统实现"
This commit is contained in:
parent
d3437f021e
commit
d39ddbfdbc
|
|
@ -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",
|
||||||
|
]
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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程序结束");
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
//! 系统中使用的数据模型
|
||||||
|
|
||||||
|
pub mod book;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
pub use book::Book;
|
||||||
|
pub use user::User;
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! 内部工具函数
|
||||||
|
|
||||||
|
pub mod validation;
|
||||||
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue