# 现实问题分析

本系统开发的目的是为了减轻图书管理工作的负担, 将那些原来需要用手工完成的事情让计算机来完成. 不但使得图书管理工作的效率大大提高, 同时也大大地降低了出错概率.

书籍管理系统

如上图所示是书籍管理系统整体功能结构图, 每个部分也就是一个模块. 系统由上图的各种模块组合而成, 每个模块都完成相应的功能, 他们协调一致进行工作.

# 技术选型

  • 本系统后端采用技术如下 :
  1. SpringBoot 2.7.7

    SpringBoot 框架采用约定大约配置的方式,大大简化了 Spring 应用的初始搭建以及开发过程.

  2. MyBatisPlus 3.5.2

    MyBatisPlus 是基于 MyBatis, 是一款优秀的持久化框架,支持基本的 mapper 以及 service 封装,几乎免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作.

  3. Druid 1.2.15

    Druid 连接池是阿里巴巴开源的数据库连接池项目,功能强大,能够防止 SQL 注入,内置 Loging, 能诊断 Hack 应用行为,具有较高的安全性.

  4. Hutool 5.8.10

    Hutool 是一个小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅.

  5. Lombok

    Lombok 是一个 Java 库,能自动插入编辑器并构建工具,简化 Java 开发。通过添加注解的方式,不需要为类编写 getter 或 eques 方法,同时可以自动化日志变量。简而言之 : Lombok 能以简单的注解形式来简化 Java 代码,提高开发人员的开发效率.

  • 本系统前端采用技术如下 :
  1. element-plus 2.0.10

    一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库。它是由饿了么前端团队推出的基于 Vue 封装的 UI 组件库,提供了丰富的 PC 端组件,简化了常用组件的封装,大大降低了开发难度.

  2. Axios 1.2.1

    Axios 是一个基于 promise 的网络请求库,作用于 node.js 和浏览器中。它是 isomorphic 的 (即同一套代码可以运行在浏览器和 node.js 中). 在服务端它使用原生 node.js http 模块,而在客户端 (浏览端) 则使用 XMLHttpRequests

  3. Vue 3.2.13

    Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML, CSS 和 JavaScript 构建,并提供了一套声明式的,组件化的编程模型,帮助高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任.

  4. Vuex 4.0.0

    Vuex 是一个转为 Vue.js 应用程序开发的状态管理模式 + 库。它采用了集中式储存管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.

  5. Vue-router 4.0.3

    Vue Router 是 Vue.js 的官方路由。它于 Vue.js 核心深度集成,让 Vue.js 构建单页应用变得轻而易举.

  6. less, less-loader

    Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了注入变量,混合 (mixin), 函数等功能,让 CSS 更易维护,方便制作主题,扩充. Less 可以运行再 Node 或浏览器端.

# 总体设计

# 整体功能结构

本系统的整体功能结构图如下所示 :

书籍管理系统

包含功能如下 :

  1. 查询所有书籍 (普通用户,管理员可用)
  2. 添加到购物车 (普通用户,管理员可用)
  3. 购物车结算 (普通用户,管理员可用)
  4. 查看个人信息 (普通用户,管理员可用)
  5. 管理员功能 (仅管理员可见)
  6. 用户管理 (仅管理员可用)
  7. 书籍管理 (仅管理员可用)

# 数据库设计

本系统采用 MySQL 作为后端数据库,共创建三个表 : book , orders , user , 其表结构如下 :

**book 表 : **

列名类型是否可 null说明
user_idbigintauto_increment 主键
user_namevarchar(256)用户名
passwordvarchar(256)密码
gendervarchar(10)性别
ageint年龄
user_phonevarchar(256)unqiue 手机号
majorvarchar(256)专业
levelvarchar(256)default 0 用户级别
book_countintdefault 0 已借阅总数

**order 表 : **

列名类型是否可 null说明
order_idbigintauto_increment 主键
book_idbigint书籍 id
user_idbigint用户 id
create_timedatetime创建时间

**user 表 : **

列名类型是否可 null说明
book_idbigintauto_increment 主键
author_namevarchar(30)作者姓名
book_namevarchar(256)书籍名称
book_typevarchar(20)书籍类别
book_countintdefault 3 书籍数量
borrow_countintdefault 0 借阅总数

数据库类结构图如下所示 :

image-20221229211026206

# 类结构设计

本系统采用MVC三层设计架构, 系统后端结构如下 : 

image-20221229214158847

  1. common

    common 目录中存放着全局通用的 Bean, 如前端参数映射 DTO, 通用返回类,全局 UserHolder 类存放登录用户信息.

  2. config

    config 目录内存放的是 McvConfig 类,主要作用是注册一个自定义拦截器.

  3. controller

    controller 目录内存放的是所有的请求处理类,经过 DispatherServlet 的处理后,由这些 Controller 类来处理与返回响应.

  4. entity

    entity 层存放数据库表所映射的 JavaBean 实例对象.

  5. filter

    filter 目录中存放着 CorsFilter 类,用来处理跨域问题.

  6. interceptor

    interceptor 目录内存放着 LoginInterceptor, 用来拦截所有请求,未登录而发出的请求会被拦截且返回状态码 401.

  7. mapper

    mapper 目录中存放着所有的 mapper, mapper 负责生成对应的 sqlSession, 直接控制数据库.

  8. service

    service 目录中存放着系统的所有的业务逻辑,通过 controller 层调用对应的 service 来处理问题.

  9. resources/mapper

    resources/mapper 中存放着 mapper 对应的 xml, xml 中存放着 mapper 中的相应方法的映射,与数据库表列名于 entity 实体类字段的映射关系.

** 以下是所有类的类结构图 : **

book

# 前端设计

# 前端页面

前端通过elementui-plus中的Container布局容器与el-row, 实现了响应式布局, 可以根据浏览器窗口大小自行排版, 已达到一个较为美观的水平.

image-20221229220020134

image-20221229220034889

# router 权限控制

前端通过router进行权限控制, 需要登录才能展示的页面, 如果sessionStorage中没有token信息, 会自动跳转到/login登录界面, 在前端避免了错误请求的发送, 增强了安全性

# API 接口封装

前端通过对axios接口的封装, 配合elementui-plus的消息通知组件, 实现了智能化的消息通知机制

image-20221229220638145

image-20221229220704132

# 各功能模块详细设计与实现

# 登录界面

系统登录界面通过`el-form`配合axios实现, 前端页面如下 : 

image-20221229220923128

在前端发出请求后, 后端不会经过拦截器(登录请求被拦截器排除以避免死循环), 被UserController中的login方法所处理, login方法调用UserService中的verifyLogin方法对登录进行校验, 如果账号和密码都正确则返回成功数据, 不然则返回失败数据. 前端接收到成功数据后, 自动跳转至主页.

image-20221229223852958

以下是后端鉴权关键代码 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public Result verifyLogin(User user, HttpServletRequest request) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq("user_phone", user.getUserPhone())
.eq("password", user.getPassword());
User one = getOne(queryWrapper);
if (BeanUtil.isEmpty(one)) {
return Result.fail("用户名或密码错误");
} else {
UserHolder.saveUser(one);
String token = UUID.randomUUID().toString(true);
request.getSession().setAttribute(token, one);
return Result.ok(token);
}
}

# 书籍查询功能

点击书籍查询功能模块后, 前端会自动发起请求, 获取所有书籍, 用户可以添加书籍进入购物车, 也可以利用搜索框, 通过书籍名称或作者姓名查询对应书籍. 如果书籍剩余本数为0, 代表此书籍已全部被借阅, 无法借阅. 用户无法选中.

前端搜索功能通过filter实现, 获取到的list会被vuex暂时存放, 并通过计算属性, 配合搜索关键词返回筛选后的列表.

image-20221229223925050

image-20221229223934159

image-20221229224002677

image-20221229224511184

image-20221229224521561

image-20221229224530365

书籍查询模块关键代码如下 : 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
computed: {
books () {
const lists = this.$store.state.books
const search = this.$store.state.searchText
if (search.length) {
return lists.filter(item => item.bookName.includes(search) || item.authorName.includes(search) || item.bookType.includes(search))
} else {
return lists
}
}
},
async created () {
await this.getBookList()
await this.$store.commit('updateBooks', this.bookList)
this.isLoading = false
}

# 书籍购物车功能

考虑到绝大多数用户在将书籍加入购物车后, 都会立即结算, 即生成订单, 所以购物车部分并未在后端实现, 而是通过vuex暂存, 这样可以节约后端资源, 且实现更为方便. 在用户在购物车页面勾选完成, 点击提交后, 前端会发起请求, 生成订单, 同时减少此书籍的库存, 增加此书籍的借阅数, 同时增加用户的总借阅数量. 后端通过`@Transactional`实现事务, 可在系统出错时回滚数据, 避免损失.

image-20221229224550176

image-20221229224602802

创建订单关键代码如下 : 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
@Transactional
public Result createOrderByList(List<Long> bookIdList, HttpServletResponse response) {
User user = UserHolder.getUser();
if (user == null) {
response.setStatus(401);
}
bookIdList.forEach(bookId -> {
Orders order = new Orders();
order.setBookId(bookId);
order.setUserId(user.getUserId());
order.setCreateTime(new Date());
save(order);
bookService.reduceInventory(bookId);
bookService.increaseCount(bookId);
userService.increaseCount(user.getUserPhone());
});
List<OrderDTO> orderDTOS = listUserByUserPhone(user.getUserPhone());
return Result.ok(orderDTOS);
}

# 书籍订单功能

用户再点击书籍订单时, 前端会向后端发起请求, 获取该用户的所有订单, 并通过table展示出来. 用户可以看到订单编号, 书籍名称, 作者姓名, 用户名, 创建时间.

image-20221229224620012

书籍订单功能关键代码如下 : 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
methods: {
async getOrders () {
const { data: res } = await getOrdersAPI()
this.list = res.data
}
},
computed: {
orderList () {
return this.$store.state.orderList
}
},
async created () {
await this.getOrders()
await this.$store.commit('updateOrderList', this.list)
}

# 管理员功能

管理员功能对默认用户隐藏, 只有管理员用户拥有相应的权限, 才会显示对应的跳转菜单. 这部分通过读取用户信息中的level, 配合`v-if`标签动态显示与隐藏, 保证了数据安全

image-20221229224711668

image-20221229224738547

# 用户管理功能

用户管理功能属于管理员功能中的一个, 当用户点击此模块时, 前端会向后端发出请求, 获取用户列表, 之后通过`el-table`渲染出来. 

当用户点击删除按钮时, 会触发删除事件, 该事件会向前端发起删除请求, 在后端成功删除并响应后, 显示删除成功的信息.

当用户点击新增按钮或删除按钮时, 会触发新增/删除时间, 会弹出一个`el-dialog`, 新增界面通过自己添加数据, 修改界面会将该用户信息注入到输入框, 修改完成后点击保存即可. `el-dialog`配置了校验规则, 所有选项均为必填项, 如果不填, 则会发出提醒且不会保存成功.

image-20221229224830194

image-20221229224852090

image-20221229224945582

image-20221229225215002

用户管理关键代码如下 : 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<template>
<el-row>
<el-col :span="3">
<el-button type="primary" @click="this.dialogVisible = true">新增用户</el-button>
</el-col>
</el-row>
<div id="create">
<el-dialog
@closed="clearValue"
v-model="dialogVisible">
<template #header="{ titleId, titleClass}">
<div class="my-header">
<h4 :id="titleId" :class="titleClass">新建用户</h4>
</div>
</template>
<el-form
:model="userInfo"
label-width="80px"
:rules="baseRules"
ref="userForm"
label-position="right">
<el-form-item label="用户名 :" prop="userName">
<el-input v-model="userInfo.userName"/>
</el-form-item>
<el-form-item label="密码 :" prop="password">
<el-input v-model="userInfo.password"/>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input-number v-model="userInfo.age" :max="100" :min="1"/>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="userInfo.gender">
<el-option
v-for="item in sex"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form-item label="手机号" prop="userPhone">
<el-input v-model="userInfo.userPhone"/>
</el-form-item>
<el-form-item label="专业" prop="major">
<el-input v-model="userInfo.major"/>
</el-form-item>
<el-form-item label="用户权限" prop="level">
<el-select v-model="userInfo.level">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-row :gutter="100">
<el-col :span="12">
<el-button type="primary" @click="confirmOperation">保存</el-button>
</el-col>
<el-col :span="12">
<el-button @click="this.dialogVisible = false">取消</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
</el-dialog>
</div>
<div id="user-table">
<el-table
:data="users"
style="width: 100%"
height="500"
:row-key="getRowKey"
border stripe>
<el-table-column prop="userName" label="用户名"/>
<el-table-column prop="gender" label="性别"/>
<el-table-column prop="age" label="年龄"/>
<el-table-column prop="userPhone" label="手机号"/>
<el-table-column prop="major" label="专业"/>
<el-table-column prop="level" label="用户权限(0: 普通用户, 1: 管理员)"/>
<el-table-column prop="bookCount" label="累计借阅总数"/>
<el-table-column label="操作" width="140">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.$index, scope.row)"
>编辑
</el-button
>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>删除
</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</template>

# 书籍管理功能

书籍管理功能同为管理员功能中的一个. 用户点击书籍管理时, 前端会向vuex获取暂存的书籍列表并通过`el-table`渲染出来, 和用户管理一样, 管理员可以删除, 新增与修改书籍.

image-20221229225545047

image-20221229225557130

image-20221229225609247

image-20221229225703635

image-20221229225643059

image-20221229225719672

更新于 阅读次数