This commit is contained in:
2023-11-27 14:43:26 +00:00
commit 8bf2a83f09
17 changed files with 32714 additions and 0 deletions

View File

@ -0,0 +1,162 @@
# 书店管理系统
SJTU ACM 班 2023 级程序设计课程大作业
## 目录
* [简介](#简介)
* [作业说明](#作业说明)
* [评测方式](#评测方式)
* [分数组成](#分数组成)
* [时间表](#时间表)
* [项目说明](#项目说明)
* [开发要求](#开发要求)
* [需求分析文档](#需求分析文档)
* [总体设计文档](#总体设计文档)
* [开发文档](#开发文档)
* [Git 版本管理](#git-版本管理)
* [代码风格](#代码风格)
* [Bonus](#bonus)
* [扣分](#扣分)
## 简介
实现一个用 C++ 语言编写的书店管理系统,用于向店家和顾客提供图书检索与购买相关服务。
本项目目标如下:
- 培养学生工程开发的能力(全套的开发过程与程序的封装等);
- 培养学生编写文档的能力(需求文档/设计文档/开发文档的编写);
- 培养学生的代码管理能力git 的使用),拥有良好的代码规范意识;
- 提升学生的编程学习与实践能力(块状链表的学习实现);
- 培养学生对编写软件的兴趣(见 [bonus](#bonus))。
### 关于本仓库
关于本仓库有任何问题欢迎联系助教,也可以直接 pull request本仓库内容将维护至本作业结束请保持关注题面更改助教也会及时发布相关通知。
## 作业说明
### 评测方式
公开的评测数据可以从 [SJTU jBox](https://jbox.sjtu.edu.cn/l/b1x5MN) 下载。
注意,每个 testcase 为最小评测数据单元;在测试同一 testcase 的多个输入(.in过程中对于每个输入都会运行学生程序完成后关闭程序再以下一个输入运行学生程序但过程中不会清除学生程序运行生成的数据文件。而每个 testcase 测试结束后会清除所有学生程序生成的文件,再运行下一个 testcase。
Online Judge以下简称 OJ提交方式为提交 git repo 链接,提交内容应为形如 `https://github.com/username/repo` 的链接。编译评测方式详见 OJ 首页说明。OJ 使用沙盒评测文件读写。编译生成的可执行文件名应为 `code`,否则 OJ 无法运行。
由于网络原因可能会导致评测机 clone 仓库失败。
<!-- 仓库内容过大(如将解压后的数据文件或 build 内容一并放入仓库导致的问题后果自负。Tip可以使用 Github 仓库加速通道链接提交作业,例如 Chrome 插件 [GitHub 加速](https://chrome.google.com/webstore/detail/github%E5%8A%A0%E9%80%9F/mfnkflidjnladnkldfonnaicljppahpg)-->
本地评测可用脚本评测,评测脚本已包含于数据包中,在含有 `./code` 的目录下运行脚本即可。例如你的 `code``./cmake-build-debug`,数据在 `./data`,则运行 `cd cmake-build-debug; ../data/judge` 即可。如运行评测脚本出现问题,请联系助教。
评测数据量(帐户和图书数量)不会超过 $1\times10^5$ 数量级。
### 分数组成
本作业满分为 100%,最终将以本作业占本课程成绩总分分数折算入课程成绩。
| 得分项 | 分值 | 说明 |
| ----------- | -------: | --------------------------------- |
| 正确性 | 55% | 通过所有公开数据点 |
| 鲁棒性 | 10% | 通过所有非公开数据点 |
| 文档编写 | 15% | 助教评分 |
| Code Review | 20% | Git 仓库管理、代码规范等 |
| Bonus | 最高 10% | 具体各项得分见 [bonus](#bonus) |
| 总计 | 110% | 未通过正确性测试者 bonus 不能得分 |
### 时间表
助教将在固定时间节点检查学生的完成进度。如遇困难请**提前**联系助教。
- **Week 0** *(对应校历 2023-2024 学年第一学期第 12 周)*
- 周一11 月 27 日):发布项目和 [业务要求文档](业务要求.md),开始完成**需求分析文档**和**总体设计文档**
- **Week 1** *(对应校历 2023-2024学年第一学期第 13 周)*
- 周二12 月 5 日):检查需求分析文档和总体设计文档(分数占比 5pt发布**标准要求**文档;
- **Week 2**
- 周一12 月 11 日 18:30 前):**通过**前置作业评测;
- 周二12 月 12 日):**中期检查**,检查开发文档编写情况以及主体部分构思(分数占比 5pt
- **Week 4** *(对应校历第 16 周)*
- 周一12 月 25 日 18:30 前):**通过**测试点评测;
- 非公开测试点和 Bonus 部分截止日期同主体逻辑,超时完成的部分不计分;
- 周二12 月 26 日晚进行代码检查Code Review
**友情提醒:临近期末,请尽早开工。**
## 项目说明
### 开发要求
每位学生均需完成一份需求分析文档、总体设计文档和开发文档。文档 **请使用 Markdown** 书写。
#### 需求分析文档
根据给出的要求文档,你需要用图或文字描述**书店系统**各个子系统的业务流程和数据流图。
#### 总体设计文档
你的总体设计文档应当包括如下部分:
- 功能设计
- 请概述各模块的功能和功能结构图;
- 用户交互设计
- 请设计所有的命令格式和对应的输出;
- 数据库设计
- 请指出你需要存储的数据以及存储方式;
- 类、结构体设计
#### 开发文档
开发文档内容**无硬性要求**,内容可以参考以下条目:
- 项目名称 / 文档作者
- 程序功能概述
- 主体逻辑说明
- 代码文件结构,即 `main` 函数与各个类的关系
- 各个类的接口及成员说明
- 文件存储说明
- 其他补充说明
如果最后提交代码不符合开发文档则会酌情扣分。合理的文档修订是允许的,但请写明修订记录。
#### Git 版本管理
你需要了解版本库、工作区、暂存区等 git 基础概念,并掌握 git 的常用指令。
你需要合理使用 commit 功能维护代码版本。具体来说:
- Commit 频率不可以太低(**例如,不可以整个项目只有三四个 commit**
- 频繁的 commit 有助于细致地保存代码变更的历史。
- Commit message 内容可以清晰简要但不能缺失、不能是无意义内容。
- 对于比较大的 commit鼓励用多行的 commit message 具体描述干了什么;
- 合理的 message 示例:
- docs: clarify design reasoning and details
- partial implementation of unrolled linked list
- fix: 切换帐户之前检查权限
- 不合理的 message 示例:
- update
- Add files via upload
- Update README.md
- bugfix *(具体修了什么?)*
- 20231122 *git 会自动存储 commit 时间,不用在 commit message 里说明。)*
你的 git 仓库需要整体文件结构清晰,了解并自主设置 `.gitignore` 等文件,保证编译产物(`./code``cmake-build-debug` 等)和下发的测试数据点不进入 git 仓库。Git repository 总大小(包括 `.git` 文件夹在内的 worktree不得超过 5 MiB。
**违反者会在 code review 中被扣分。**
#### 代码风格
选择合适的代码风格,严格遵守选定风格的代码规范。
### Bonus
见 [bonus 文档](bonus.md)。
### 扣分
请保证自己项目结构的可读性,可以包括优化项目结构、适当的文件树指南等,晦涩难懂的项目可能会加大助教的工作量,也可能会影响你的成绩。
**如有出现任何抄袭现象按 0 分计。**

View File

@ -0,0 +1,214 @@
# Bonus
括号里的分值为最高得分,最终得分由 code review 确定。另请注意,总分值不能超过 10 分。
## 缓存 *(1.5 pts)*
如果将一些常用数据存在内存中,可以省去大量从外存上读取的时间,这就是缓存的意义。
此 bonus 根据实现难度和工作量给分。
**注:** 若选择实现并发功能,则此 bonus 分不再计入总分。
## 中文输入验证 *(1.5 pts)*
在实际使用中,书名、作者名等只支持 ASCII 显然是不够的;我们有很多中文的书名。但是,为了贯彻落实文化自信,我们也不允许书名中出现不是汉字的文字。
我们把图书相关信息做扩展定义如下:(其他定义不变)
- `[BookName]`, `[Author]`:图书名字,图书作者名字;
- 合法字符集:除不可见字符和英文双引号以外 ASCII 字符,以及汉字和允许的特殊符号;
- 最大长度60一个汉字或特殊符号算作一个字符。
- `[Keyword]`:图书关键词;
- 合法字符集:除不可见字符和英文双引号以外 ASCII 字符,以及汉字和允许的特殊符号;
- 最大长度60一个汉字或特殊符号算作一个字符。
其中,汉字的定义是:最新 Unicode 标准Unicode 15.0.0)中指定的 Han Ideographs汉字区块内的字。网上能查到的大部分匹配方式都不符合这个要求。例如它们都不能匹配「𰻝」字音 biángU+30EDD位于 CJK 扩展 G 区)。所以你需要自行查阅相关文档来找出具体的字汇。
允许的特殊符号有:
- 间隔号「·」U+00B7
- Em Dash「—」U+2014
- 括号「」「U+FF08 U+FF09
- 冒号「U+FF1A
你需要自己了解并手写解析 UTF-8 编码的代码。使用 C++ 标准库解析 UTF-8 不能得分。
## Validator *(1.5 pts)*
本任务的主要目的是练习一些 C++ 语法的使用。
在写 Bookstore 的时候,你一定会写到很多参数的验证:指令需要验证,帐户信息需要验证,图书信息也需要验证。这个任务提供了一个参数验证方法。例如,如果想验证 `quantity` 在 1 到 10 之间且不是 4就可以写
```cpp
expect(quantity).ge(1).And.le(10).And.Not().toBe(4);
```
就相当于写:
```cpp
if (!(quantity >= 1 && quantity <= 10 && quantity != 4)) {
throw std::exception();
}
```
本任务要求你实现一个这样的 `expect` 函数(写 bookstore 主体的时候这个函数也能用上),若验证不通过则 `throw` 任意值。它只有一个输入参数,其他参数以*链式调用 (method chaining)* 的方式传入。`expect` 函数是多态的。你需要实现以下接口:
```cpp
struct T {};
struct U : T {};
T x, y, z;
U u;
T &ref = u;
// T 的 == 需要有定义,验证是否有 x == y
expect(x).toBe(y);
// 验证 ref 是否为 U 的实例。
// **注意,此处如果 U 根本不是 T 的子类,则需要编译报错。**
expect(ref).toBe<U>();
// T 的 == 需要有定义,接收任意多个(至少一个)参数,验证 x 是否与参数列表中的至少一个值相等
expect(x).toBeOneOf(x, y, z, ...);
// T 的 <= 或 >= 需要有定义,验证是否有 x <= y 或 x >= y
expect(x).le(y);
expect(x).ge(y);
// Not() 将后面跟的所有条件取反
// 意为 x 不能为 1 或 2
expect(x).Not().toBe(1).Or.toBe(2);
```
如果传入的 x 是 [`std::basic_string<CharT>`](https://en.cppreference.com/w/cpp/string/basic_string)(注意不一定是 `std::string`,点击链接跳转到 C++ Reference则还需实现以下函数
```cpp
std::basic_string<CharT> str1, str2;
// 验证 str1 中的所有字符都包含于 str2 中
// 例如 expect(std::string("1024.00")).toBeConsistedOf("42.01")
// 提示std::basic_string<CharT>::find()
expect(str1).consistedOf(str2);
// 验证 str1 符合 str2 这个正则表达式
// 提示:可以使用 std::basic_regex<CharT> 实现
expect(str1).toMatch(str2);
```
这些方法需要能够任意连接:(中间的 `And`, `Or``but` 为无意义连词,直接令其为 `*this` 即可):
```cpp
// 意为 x 需要在 10 到 100 之间,且不是 2 3 5 7 11 中的任意一个
expect(x)
.ge(10)
.And.le(100)
.but.Not().toBeOneOf(2, 3, 5, 7)
.Or.toBe(11);
```
## 并发 *(10 pts)*
对于一个强大的数据库系统来说,并发功能是非常重要的。在本任务中,你需要实现块链的并发功能。
从 Wikipedia 上可以找到对于并发的定义:
> In computer science, concurrency is the ability of different parts or units of a program, algorithm, or problem to be executed out-of-order or in partial order, without affecting the final outcome.
如果想要进一步了解 C++ 中如何实现一个并发的块状链表,你需要:
- 学习 C++ 中如何对线程进行操作,可参看 [C++多线程并发基础入门教程](https://zhuanlan.zhihu.com/p/194198073)
- 参考链表的线程安全实现,可参看 [使用线程安全型双向链表实现简单 LRU Cache 模拟](https://cloud.tencent.com/developer/article/1902797)
以上提供的资料仅用于初步了解并发,如果想要实现一个并发的块状链表,你可能还需要搜索、参看更多资料。
**Hint:** 简单来说,块状链表应该能够同时处理多个块,主要需要考虑如何分块、并块。如果不同线程同时处理的块恰为同一个块,如何让其中一个线程知道这个块正在被占用?将块正在被占用的信息写回外存显然在时间上是不可接受的。
此 bonus 仅是对于块状链表的要求,如果感兴趣,你也可以让你的主体支持高并发操作。
**注意:** 准备尝试此任务的同学请务必与助教联系并讨论自己的实现思路。
## 快照 *(10 pts)*
对于一个强大的数据库系统来说,支持快照与恢复功能是非常重要的。在本任务中,你需要实现一个快照系统,实现任意快照间的切换。
一个快照几乎不会占用额外存储空间:
- 在一个状态上创建一个快照,应该只会占用很小的额外存储来记录这个快照本身的信息,不会将快照的内容完全复制一遍;
- 在同样的内容上创建 100 个快照,应该只会占用很小的额外存储,不会使磁盘占用增加 100 倍;
- 在创建快照之后改动一小部分内容,再创建一个快照,也应该只会占用很小的额外存储,不会使磁盘占用增加一倍。
要做到这点,可以利用 [Copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write) 的做法。简单来说,就是令所有块都是不可变的,在试图修改一个块的时候,对这个块做复制,然后在复制出的新块上做修改。但是如果在不快照的时候也对这些块做 copy-on-write 的话,每修改一次数据,就会增加一个新块,这是不可接受的。因此,你需要只在快照的时候做 copy-on-write。
本任务引入了一条新的指令 `snapshot`,需要权限等级为 7
- `[SnapshotID]`
- 合法字符集:大小写英文字母和数字;
- 最大长度16
- 保证所有 `SnapshotID` 均合法。
- **创建快照**
- {7} `snapshot create [SnapshotID]`
- 在当前状态上创建一个快照,名称为 `SnapshotID`
- 保证 `SnapshotID` 不会重复。
- **还原快照**
- {7} `snapshot restore [SnapshotID]`
- 还原到名称为 `SnapshotID` 的快照状态,并清空登录栈。
- 清空登录栈是为了避免书籍信息被修改所带来的不必要的麻烦;
- 保证 `SnapshotID` 是存在的。
- **删除快照**
- {7} `snapshot delete [SnapshotID]`
- 删除名称为 `SnapshotID` 的快照状态,不改变当前状态。
- 其占用的存储空间也需一并释放(下文中说明的情况除外);
- 保证 `SnapshotID` 是存在的。
- **合并快照(仅 A 班需要实现)**
- {7} `snapshot merge [SnapshotID]`
- 将名称为 `SnapshotID` 的快照状态合并到当前状态(不一定是一个快照)中,并清空登录栈;
- 只改变当前状态,快照状态不变;
- 清空登录栈是为了避免书籍信息被修改所带来的不必要的麻烦;
- 测试数据保证 `SnapshotID` 是存在的。
- 「合并」的定义是:
- 日志系统不做合并,保留当前日志;
- 首先,每个快照都需要记录一个「父节点」的信息,表示这个快照之前的快照;
- 对于第一个快照来说,其父节点是空状态。
- 找出当前状态与需要合并的快照状态的最小公共祖先;
- 即使 LCA 状态所对应的快照被删除了,它的状态也需要被保留。
- 将当前状态和需要合并的快照中,每个对象(一本书、一个帐户)的状态分别与 LCA 中的状态作合并。
- 如果存在两侧都修改(不包括删除)了同一个对象的情况,则输出 `Invalid\n` 并不做任何合并;
- 如果存在两侧都新增了同一个对象的情况(用户以 UserID 为准,书籍以 ISBN 为准),则输出 `Invalid\n` 并不做任何合并;
- 如果其中至少一侧删除了一个对象(包括两侧删除了同一个对象),则删除这个对象;
- 如果其中恰有一侧修改了一个对象,则保留修改过的对象;
- 如果其中恰有一侧新增了一个对象,则保留新增的对象;
- 数据保证 ISBN 不会被修改。
- 你可以暂时把所有数据加载到内存,但是合并结束之后需要将这些数据写回文件,并从内存中删除;
- 实现中不需要考虑时间复杂度。
**注意:** 准备尝试此任务的同学请务必与助教联系并讨论自己的实现思路。
## 容错 *(10 pts)*
程序执行时,我们可能会遇到突然断电等意外情况,导致操作中断。如果此时恰好在对磁盘进行读写操作,那么原本的文件系统就会被破坏,从而造成数据损坏的情况。
在 Unix 中常用的文件系统中,删除一个文件可以分为 3 步:
1. 删除访问的 entry 指针
2. 释放对应的 index node
3. 归还对应的磁盘空间
如果操作发生中断,一个常见的恢复方式是从第一条输入指令开始,重新执行一整轮操作,但是时间成本显然难以接受。
因此,我们可以实现一个 journaling file system利用日志来恢复文件保证文件的数据不被损坏。
我们可以考虑把一个指令分为多个 **原子操作** (atomic operation),在 journal 中保存每个原子操作。当程序中断时,我们就可以知道保存了哪些原子操作,进而进行文件恢复。
**Hint**:请思考一下在不同步骤发生中断时,应该如何选取合适的原子操作,并通过原子操作来恢复文件。 [reference link](https://en.wikipedia.org/wiki/Journaling_file_system)
**注意:** 准备尝试此任务的同学请务必与助教联系并讨论自己的实现思路。
## GUI 前端 *(8.5 pts)*
提供一个用户友好的图形化前端Qt、Gtk、Web、Tcl/Tk 等均可)。基本要求为:
- 前后端分离,即前端代码不过度侵入主体逻辑;
- 用户能通过用户友好的图形化界面完成**所有**操作;
- 提供完整的《系统安装手册》《用户手册》。
**注意:** 准备尝试此任务的同学请务必与助教联系并讨论自己的实现思路。

View File

@ -0,0 +1,40 @@
# 业务要求
## 基本要求
设计并实现一个书店管理系统。该书店管理系统应当面向三类人员顾客、销售人员和店长。最终交接物为需求分析文档、总体设计文档13 周周二前和源代码16 周周一前)。关于程序有以下要求:
- 编程语言限制:软件主体(除 bonus 部分外)完全使用 C++ 编写;
- 系统数据需要存储在外存中;
- 系统首次运行时应当创建店主账户,其具有最高权限;
- 管理系统应当使用命令行进行交互;
- 系统支持嵌套登录,即允许多个帐户同时处于登录状态;允许同一帐户同时多次登录。输入的指令视为最后登录的帐户操作;
## 游客
游客只能注册/登录用户。
## 顾客
顾客可以查询和购买图书。
- 每种图书包含的信息有ISBN 号20 个数字每本正版书都有惟一的标识、书名60 个字符、作者60 个字符)、关键字(可以有多个,总共不超过 60 个字符)、库存量(整型)、单价(非负实数);
- 查询图书时可以根据 ISBN 号、作者名字、关键字、书名中的任意组合进行查询;
- 购买图书时,我们通过给定的 ISBN 号和购买数量来进行操作;
## 销售人员
销售人员可以执行所有顾客执行的操作,同时也可以进行进货、图书信息录入、图书信息修改、用户创建。
- 进货时需指定 ISBN 号、数量、进货价格;
- 第一次录入图书时,应当输入其完整信息;
## 店长
店长可以执行所有销售人员和顾客可以执行的功能,同时也可以查询某段时间的采购信息(即按照采购顺序输出采购图书的 ISBN 号、数量、单价等)、销售情况(即按照销售顺序输出售出图书的 ISBN 号、数量、单价等)、盈利信息(即输出该段时间的收入,支出以及利润)。除此之外,店长也可以查看各员工的工作情况报告(即按操作顺序输出每次操作类型及其对象,如创建用户操作以及对应的用户信息)和系统的整体工作日志。
## 性能需求
考虑到应用场景下帐户和图书数量较大,故**禁止将主体数据存储于内存**,应实时读写文件数据。运行过程中程序创建的文件数量不得超过 20 个。