diff --git a/BackToTop/index.vue b/BackToTop/index.vue
new file mode 100644
index 0000000..d930fc8
--- /dev/null
+++ b/BackToTop/index.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Breadcrumb/index.vue b/Breadcrumb/index.vue
new file mode 100644
index 0000000..557c45f
--- /dev/null
+++ b/Breadcrumb/index.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+ {{ item.meta.title }}
+
+ {{ item.meta.title }}
+
+
+
+
+
+
+
+
diff --git a/DataTable/index.vue b/DataTable/index.vue
new file mode 100644
index 0000000..f2644e7
--- /dev/null
+++ b/DataTable/index.vue
@@ -0,0 +1,296 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DepartTreeSelect/index.vue b/DepartTreeSelect/index.vue
new file mode 100644
index 0000000..233ed41
--- /dev/null
+++ b/DepartTreeSelect/index.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/common.js b/api/common.js
new file mode 100644
index 0000000..4b6c671
--- /dev/null
+++ b/api/common.js
@@ -0,0 +1,58 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获取列表数据
+ * @param {string} url - 请求的 API 接口地址
+ * @param {Object} query - 请求携带的查询参数
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchList(url, query) {
+ // 发送 POST 请求到指定的 API 接口,并携带查询参数
+ return post(url, query)
+}
+
+/**
+ * 获取详情数据
+ * @param {string} url - 请求的 API 接口地址
+ * @param {string|number} id - 要获取详情数据的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchDetail(url, id) {
+ // 发送 POST 请求到指定的 API 接口,并携带包含 id 的参数对象
+ return post(url, { 'id': id })
+}
+
+/**
+ * 保存数据
+ * @param {string} url - 请求的 API 接口地址
+ * @param {Object} data - 需要保存的数据对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function saveData(url, data) {
+ // 发送 POST 请求到指定的 API 接口,并携带需要保存的数据
+ return post(url, data)
+}
+
+/**
+ * 删除数据
+ * @param {string} url - 请求的 API 接口地址
+ * @param {Array} ids - 要删除数据的唯一标识数组
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function deleteData(url, ids) {
+ // 发送 POST 请求到指定的 API 接口,并携带包含 ids 的参数对象
+ return post(url, { 'ids': ids })
+}
+
+/**
+ * 更改数据状态
+ * @param {string} url - 请求的 API 接口地址
+ * @param {Array} ids - 要更改状态的数据的唯一标识数组
+ * @param {string|number} state - 要更改成的状态值
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function changeState(url, ids, state) {
+ // 发送 POST 请求到指定的 API 接口,并携带包含 ids 和 state 的参数对象
+ return post(url, { 'ids': ids, 'state': state })
+}
diff --git a/api/exam/exam.js b/api/exam/exam.js
new file mode 100644
index 0000000..35829c0
--- /dev/null
+++ b/api/exam/exam.js
@@ -0,0 +1,31 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获取题库详情
+ * @param {string|number} id - 题库的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchDetail(id) {
+ // 发送 POST 请求到指定接口,携带题库 id 参数
+ return post('/exam/api/exam/exam/detail', { id: id })
+}
+
+/**
+ * 保存题库信息
+ * @param {Object} data - 包含题库信息的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function saveData(data) {
+ // 发送 POST 请求到指定接口,携带题库信息
+ return post('/exam/api/exam/exam/save', data)
+}
+
+/**
+ * 获取题库列表
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchList() {
+ // 发送 POST 请求到指定接口,设置当前页码为 1,每页显示 100 条记录
+ return post('/exam/api/exam/exam/paging', { current: 1, size: 100 })
+}
diff --git a/api/paper/exam.js b/api/paper/exam.js
new file mode 100644
index 0000000..c62fe36
--- /dev/null
+++ b/api/paper/exam.js
@@ -0,0 +1,81 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 创建试卷
+ * @param {Object} data - 创建试卷所需的数据对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function createPaper(data) {
+ // 发送 POST 请求到创建试卷的接口,并传递数据
+ return post('/exam/api/paper/paper/create-paper', data)
+}
+
+/**
+ * 获取试卷详情
+ * @param {Object} data - 获取试卷详情所需的数据对象,通常包含试卷 ID
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function paperDetail(data) {
+ // 发送 POST 请求到获取试卷详情的接口,并传递数据
+ return post('/exam/api/paper/paper/paper-detail', data)
+}
+
+/**
+ * 获取题目详情
+ * @param {Object} data - 获取题目详情所需的数据对象,通常包含题目 ID
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function quDetail(data) {
+ // 发送 POST 请求到获取题目详情的接口,并传递数据
+ return post('/exam/api/paper/paper/qu-detail', data)
+}
+
+/**
+ * 填充答案
+ * @param {Object} data - 填充答案所需的数据对象,包含题目答案等信息
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fillAnswer(data) {
+ // 发送 POST 请求到填充答案的接口,并传递数据
+ return post('/exam/api/paper/paper/fill-answer', data)
+}
+
+/**
+ * 交卷
+ * @param {Object} data - 交卷所需的数据对象,可能包含试卷 ID 等信息
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function handExam(data) {
+ // 发送 POST 请求到交卷的接口,并传递数据
+ return post('/exam/api/paper/paper/hand-exam', data)
+}
+
+/**
+ * 获取试卷结果
+ * @param {Object} data - 获取试卷结果所需的数据对象,通常包含试卷 ID
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function paperResult(data) {
+ // 发送 POST 请求到获取试卷结果的接口,并传递数据
+ return post('/exam/api/paper/paper/paper-result', data)
+}
+
+/**
+ * 错题训练
+ * @param {Object} data - 错题训练所需的数据对象,可能包含用户 ID、错题类型等信息
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function training(data) {
+ // 发送 POST 请求到错题训练的接口,并传递数据
+ return post('/exam/api/paper/paper/training', data)
+}
+
+/**
+ * 检查是否有进行中的考试
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function checkProcess() {
+ // 发送 POST 请求到检查进行中考试的接口,不传递额外数据
+ return post('/exam/api/paper/paper/check-process', {})
+}
diff --git a/api/paper/paper.js b/api/paper/paper.js
new file mode 100644
index 0000000..e686bfb
--- /dev/null
+++ b/api/paper/paper.js
@@ -0,0 +1,13 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获取试卷列表
+ * @param {string|number} userId - 用户的唯一标识,用于筛选该用户相关的试卷
+ * @param {string|number} examId - 考试的唯一标识,用于筛选该考试下的试卷
+ */
+export function listPaper(userId, examId) {
+ // 发送 POST 请求到指定的试卷分页接口
+ // 请求体中包含当前页码、每页显示数量以及筛选参数(用户 ID 和考试 ID)
+ return post('/exam/api/paper/paper/paging', { current: 1, size: 5, params: { userId: userId, examId: examId }})
+}
diff --git a/api/qu/qu.js b/api/qu/qu.js
new file mode 100644
index 0000000..5d56b77
--- /dev/null
+++ b/api/qu/qu.js
@@ -0,0 +1,54 @@
+// 从 '@/utils/request' 模块导入 post、upload 和 download 方法
+// post 方法用于发送 POST 请求
+// upload 方法用于文件上传请求
+// download 方法用于文件下载请求
+import { post, upload, download } from '@/utils/request'
+
+/**
+ * 获取题库详情
+ * @param {string|number} id - 题库的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchDetail(id) {
+ // 发送 POST 请求到指定接口,携带题库 id 参数
+ return post('/exam/api/qu/qu/detail', { id: id })
+}
+
+/**
+ * 保存题库信息
+ * @param {Object} data - 包含题库信息的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function saveData(data) {
+ // 发送 POST 请求到指定接口,携带题库信息
+ return post('/exam/api/qu/qu/save', data)
+}
+
+/**
+ * 导出题库数据为 Excel 文件
+ * @param {Object} data - 导出所需的参数对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function exportExcel(data) {
+ // 发送下载请求到指定接口,携带导出参数,设置文件名为 '导出的数据.xlsx'
+ return download('/exam/api/qu/qu/export', data, '导出的数据.xlsx')
+}
+
+/**
+ * 下载导入模板文件
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function importTemplate() {
+ // 发送下载请求到指定接口,不携带额外参数,设置文件名为 'qu-import-template.xlsx'
+ return download('/exam/api/qu/qu/import/template', {}, 'qu-import-template.xlsx')
+}
+
+/**
+ * 导入 Excel 文件到题库
+ * @param {File} file - 要上传的 Excel 文件
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function importExcel(file) {
+ // 发送上传请求到指定接口,携带要上传的文件
+ return upload('/exam/api/qu/qu/import', file)
+}
diff --git a/api/qu/repo.js b/api/qu/repo.js
new file mode 100644
index 0000000..6e07ed9
--- /dev/null
+++ b/api/qu/repo.js
@@ -0,0 +1,42 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获取题库详情
+ * @param {Object} data - 请求所需的参数对象,包含获取题库详情必要的信息
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchDetail(data) {
+ // 发送 POST 请求到指定的获取题库详情接口,并传递参数
+ return post('/exam/api/repo/detail', data)
+}
+
+/**
+ * 保存题库信息
+ * @param {Object} data - 请求所需的参数对象,包含需要保存的题库信息
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function saveData(data) {
+ // 发送 POST 请求到指定的保存题库信息接口,并传递参数
+ return post('/exam/api/repo/save', data)
+}
+
+/**
+ * 分页获取题库列表
+ * @param {Object} data - 请求所需的参数对象,包含分页信息和筛选条件等
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchPaging(data) {
+ // 发送 POST 请求到指定的分页获取题库列表接口,并传递参数
+ return post('/exam/api/repo/paging', data)
+}
+
+/**
+ * 对题库进行批量操作
+ * @param {Object} data - 请求所需的参数对象,包含批量操作的类型和相关数据
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function batchAction(data) {
+ // 发送 POST 请求到指定的题库批量操作接口,并传递参数
+ return post('/exam/api/repo/batch-action', data)
+}
diff --git a/api/sys/config/config.js b/api/sys/config/config.js
new file mode 100644
index 0000000..e51abd9
--- /dev/null
+++ b/api/sys/config/config.js
@@ -0,0 +1,21 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获得用户协议详情,固定 ID 为 1
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果。请求成功时,Promise 将解析为服务器响应数据;请求失败时,Promise 将被拒绝。
+ */
+export function fetchDetail() {
+ // 发送 POST 请求到指定接口,携带用户协议的 ID 参数
+ return post('/exam/api/sys/config/detail', { id: '1' })
+}
+
+/**
+ * 保存系统配置数据
+ * @param {Object} data - 包含要保存的系统配置信息的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果。请求成功时,Promise 将解析为服务器响应数据;请求失败时,Promise 将被拒绝。
+ */
+export function saveData(data) {
+ // 发送 POST 请求到指定接口,携带要保存的系统配置数据
+ return post('/exam/api/sys/config/save', data)
+}
diff --git a/api/sys/depart/depart.js b/api/sys/depart/depart.js
new file mode 100644
index 0000000..68fee27
--- /dev/null
+++ b/api/sys/depart/depart.js
@@ -0,0 +1,69 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 分页获取部门树数据
+ * @param {Object} data - 请求所需的参数对象,包含分页信息等
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function pagingTree(data) {
+ // 发送 POST 请求到分页获取部门数据的接口,并传递参数
+ return post('/exam/api/sys/depart/paging', data)
+}
+
+/**
+ * 获取部门树数据
+ * @param {Object} data - 请求所需的参数对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchTree(data) {
+ // 发送 POST 请求到获取部门树数据的接口,并传递参数
+ return post('/exam/api/sys/depart/tree', data)
+}
+
+/**
+ * 获取部门详情
+ * @param {string|number} id - 部门的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fetchDetail(id) {
+ // 构建包含部门 id 的请求数据对象
+ const data = { id: id }
+ // 发送 POST 请求到获取部门详情的接口,并传递参数
+ return post('/exam/api/sys/depart/detail', data)
+}
+
+/**
+ * 删除部门数据
+ * @param {Array} ids - 要删除的部门的唯一标识数组
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function deleteData(ids) {
+ // 构建包含要删除部门 ids 的请求数据对象
+ const data = { ids: ids }
+ // 发送 POST 请求到删除部门数据的接口,并传递参数
+ return post('/exam/api/sys/depart/delete', data)
+}
+
+/**
+ * 保存部门数据
+ * @param {Object} data - 包含要保存的部门信息的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function saveData(data) {
+ // 发送 POST 请求到保存部门数据的接口,并传递参数
+ return post('/exam/api/sys/depart/save', data)
+}
+
+/**
+ * 对部门进行排序
+ * @param {string|number} id - 要排序的部门的唯一标识
+ * @param {number} sort - 排序值
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function sortData(id, sort) {
+ // 构建包含部门 id 和排序值的请求数据对象
+ const data = { id: id, sort: sort }
+ // 发送 POST 请求到对部门进行排序的接口,并传递参数
+ return post('/exam/api/sys/depart/sort', data)
+}
diff --git a/api/sys/role/role.js b/api/sys/role/role.js
new file mode 100644
index 0000000..c12316f
--- /dev/null
+++ b/api/sys/role/role.js
@@ -0,0 +1,11 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获取角色列表
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果。当请求成功时,Promise 将解析为服务器返回的角色列表数据;请求失败时,Promise 将被拒绝。
+ */
+export function fetchList() {
+ // 发送 POST 请求到指定的获取角色列表接口,请求体为空对象
+ return post('/exam/api/sys/role/list', {})
+}
diff --git a/api/sys/user/user.js b/api/sys/user/user.js
new file mode 100644
index 0000000..3e5974a
--- /dev/null
+++ b/api/sys/user/user.js
@@ -0,0 +1,32 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 更新用户数据
+ * @param {Object} data - 包含要更新的用户数据的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function updateData(data) {
+ // 发送 POST 请求到更新用户数据的接口,并传递用户数据
+ return post('/exam/api/sys/user/update', data)
+}
+
+/**
+ * 保存用户数据
+ * @param {Object} data - 包含要保存的用户数据的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function saveData(data) {
+ // 发送 POST 请求到保存用户数据的接口,并传递用户数据
+ return post('/exam/api/sys/user/save', data)
+}
+
+/**
+ * 用户注册
+ * @param {Object} data - 包含用户注册信息的对象
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function userReg(data) {
+ // 发送 POST 请求到用户注册接口,并传递用户注册信息
+ return post('/exam/api/sys/user/reg', data)
+}
diff --git a/api/user.js b/api/user.js
new file mode 100644
index 0000000..2372b4b
--- /dev/null
+++ b/api/user.js
@@ -0,0 +1,41 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 用户登录接口
+ * @param {Object} data - 包含用户登录所需信息的对象,如用户名、密码等
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function login(data) {
+ // 发送 POST 请求到用户登录接口,并传递登录信息
+ return post('/exam/api/sys/user/login', data)
+}
+
+/**
+ * 获取用户信息接口
+ * @param {string} token - 用户的身份验证令牌
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function getInfo(token) {
+ // 发送 POST 请求到获取用户信息接口,将 token 附加到 URL 参数中
+ return post('/exam/api/sys/user/info?token=' + token)
+}
+
+/**
+ * 用户登出接口
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function logout() {
+ // 发送 POST 请求到用户登出接口,请求体为空对象
+ return post('/exam/api/sys/user/logout', {})
+}
+
+/**
+ * 用户注册接口
+ * @param {Object} data - 包含用户注册所需信息的对象,如用户名、密码、邮箱等
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function reg(data) {
+ // 发送 POST 请求到用户注册接口,并传递注册信息
+ return post('/exam/api/sys/user/reg', data)
+}
diff --git a/api/user/book.js b/api/user/book.js
new file mode 100644
index 0000000..b071265
--- /dev/null
+++ b/api/user/book.js
@@ -0,0 +1,13 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 获取下一道错题信息
+ * @param {string|number} examId - 考试的唯一标识
+ * @param {string|number} quId - 题目的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function nextQu(examId, quId) {
+ // 发送 POST 请求到指定接口,携带考试 ID 和题目 ID 参数
+ return post('/exam/api/user/wrong-book/next', { examId: examId, quId: quId })
+}
diff --git a/api/user/repo.js b/api/user/repo.js
new file mode 100644
index 0000000..4d7b1f1
--- /dev/null
+++ b/api/user/repo.js
@@ -0,0 +1,64 @@
+// 从 '@/utils/request' 模块导入 post 方法,用于发送 POST 请求
+import { post } from '@/utils/request'
+
+/**
+ * 开始训练
+ * @param {string} mode - 训练模式
+ * @param {string|number} repoId - 题库的唯一标识
+ * @param {string|number} userId - 用户的唯一标识
+ * @param {boolean} clear - 是否清除之前的训练数据
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function startTrain(mode, repoId, userId, clear) {
+ // 发送 POST 请求到开始训练的接口,携带训练模式、题库 ID、用户 ID 和是否清除数据的参数
+ return post('/exam/api/user/repo/start', { mode: mode, repoId: repoId, userId: userId, clear: clear })
+}
+
+/**
+ * 填充训练结果
+ * @param {string|number} id - 题目的唯一标识
+ * @param {Array|string} answers - 用户给出的答案
+ * @param {boolean} isRight - 答案是否正确
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function fillResult(id, answers, isRight) {
+ // 发送 POST 请求到填充结果的接口,携带题目 ID、用户答案和答案是否正确的参数
+ return post('/exam/api/user/repo/fill', { id: id, answers: answers, isRight: isRight })
+}
+
+/**
+ * 检查是否存在进行中的训练
+ * @param {string} mode - 训练模式
+ * @param {string|number} repoId - 题库的唯一标识
+ * @param {string|number} userId - 用户的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function hasTrain(mode, repoId, userId) {
+ // 发送 POST 请求到检查训练状态的接口,携带训练模式、题库 ID 和用户 ID 参数
+ return post('/exam/api/user/repo/check', { mode: mode, repoId: repoId, userId: userId })
+}
+
+/**
+ * 获取答题卡列表
+ * @param {string} mode - 训练模式
+ * @param {string|number} repoId - 题库的唯一标识
+ * @param {string|number} userId - 用户的唯一标识
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function listCard(mode, repoId, userId) {
+ // 发送 POST 请求到获取答题卡列表的接口,携带训练模式、题库 ID 和用户 ID 参数
+ return post('/exam/api/user/repo/card', { mode: mode, repoId: repoId, userId: userId })
+}
+
+/**
+ * 获取下一题
+ * @param {string} mode - 训练模式
+ * @param {string|number} repoId - 题库的唯一标识
+ * @param {string|number} userId - 用户的唯一标识
+ * @param {string|number} sequence - 题目序号
+ * @returns {Promise} - 返回一个 Promise 对象,用于处理请求结果
+ */
+export function nextQu(mode, repoId, userId, sequence) {
+ // 发送 POST 请求到获取下一题的接口,携带训练模式、题库 ID、用户 ID 和题目序号参数
+ return post('/exam/api/user/repo/next', { mode: mode, repoId: repoId, userId: userId, sequence: sequence })
+}
diff --git a/index.vue b/index.vue
new file mode 100644
index 0000000..0ced222
--- /dev/null
+++ b/index.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
diff --git a/mapper/exam/ExamDepartMapper.xml b/mapper/exam/ExamDepartMapper.xml
new file mode 100644
index 0000000..d4fa71f
--- /dev/null
+++ b/mapper/exam/ExamDepartMapper.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`exam_id`,`depart_id`
+
+
+
diff --git a/mapper/exam/ExamMapper.xml b/mapper/exam/ExamMapper.xml
new file mode 100644
index 0000000..e9e0edf
--- /dev/null
+++ b/mapper/exam/ExamMapper.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`title`,`content`,`open_type`,`join_type`,`level`,`state`,`time_limit`,`start_time`,`end_time`,`create_time`,`update_time`,`total_score`,`total_time`,`qualify_score`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/exam/ExamRepoMapper.xml b/mapper/exam/ExamRepoMapper.xml
new file mode 100644
index 0000000..1058901
--- /dev/null
+++ b/mapper/exam/ExamRepoMapper.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`exam_id`,`repo_id`,`radio_count`,`radio_score`,`multi_count`,`multi_score`,`judge_count`,`judge_score`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/paper/PaperMapper.xml b/mapper/paper/PaperMapper.xml
new file mode 100644
index 0000000..a09169d
--- /dev/null
+++ b/mapper/paper/PaperMapper.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`user_id`,`depart_id`,`exam_id`,`title`,`total_time`,`user_time`,`total_score`,`qualify_score`,`obj_score`,`subj_score`,`user_score`,`has_saq`,`state`,`create_time`,`update_time`,`limit_time`
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/paper/PaperQuAnswerMapper.xml b/mapper/paper/PaperQuAnswerMapper.xml
new file mode 100644
index 0000000..87e2e64
--- /dev/null
+++ b/mapper/paper/PaperQuAnswerMapper.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`paper_id`,`answer_id`,`qu_id`,`is_right`,`checked`,`sort`,`abc`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/paper/PaperQuMapper.xml b/mapper/paper/PaperQuMapper.xml
new file mode 100644
index 0000000..d3a45f5
--- /dev/null
+++ b/mapper/paper/PaperQuMapper.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`paper_id`,`qu_id`,`qu_type`,`answered`,`answer`,`sort`,`score`,`actual_score`,`is_right`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/qu/QuAnswerMapper.xml b/mapper/qu/QuAnswerMapper.xml
new file mode 100644
index 0000000..6c81bb4
--- /dev/null
+++ b/mapper/qu/QuAnswerMapper.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`qu_id`,`is_right`,`image`,`content`,`analysis`
+
+
+
diff --git a/mapper/qu/QuMapper.xml b/mapper/qu/QuMapper.xml
new file mode 100644
index 0000000..cbb2321
--- /dev/null
+++ b/mapper/qu/QuMapper.xml
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`qu_type`,`level`,`image`,`content`,`create_time`,`update_time`,`remark`,`analysis`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AND q.qu_type = #{query.quType}
+
+
+
+
+ AND po.repo_id IN
+
+ #{repoId}
+
+
+
+
+ AND q.content LIKE CONCAT('%',#{query.content},'%')
+
+
+
+
+ AND q.id NOT IN
+
+
+ #{quId}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/qu/QuRepoMapper.xml b/mapper/qu/QuRepoMapper.xml
new file mode 100644
index 0000000..e883c6f
--- /dev/null
+++ b/mapper/qu/QuRepoMapper.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`qu_id`,`repo_id`,`qu_type`,`sort`
+
+
+
+
diff --git a/mapper/repo/RepoMapper.xml b/mapper/repo/RepoMapper.xml
new file mode 100644
index 0000000..1d87759
--- /dev/null
+++ b/mapper/repo/RepoMapper.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`code`,`title`,`radio_count`,`multi_count`,`judge_count`,`remark`,`create_time`,`update_time`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/sys/depart/SysDepartMapper.xml b/mapper/sys/depart/SysDepartMapper.xml
new file mode 100644
index 0000000..38992f6
--- /dev/null
+++ b/mapper/sys/depart/SysDepartMapper.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`dept_type`,`parent_id`,`dept_name`,`dept_code`,`sort`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/sys/system/SysDictMapper.xml b/mapper/sys/system/SysDictMapper.xml
new file mode 100644
index 0000000..59996cb
--- /dev/null
+++ b/mapper/sys/system/SysDictMapper.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapper/sys/user/SysRoleMapper.xml b/mapper/sys/user/SysRoleMapper.xml
new file mode 100644
index 0000000..5547d55
--- /dev/null
+++ b/mapper/sys/user/SysRoleMapper.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`role_name`
+
+
+
diff --git a/mapper/sys/user/SysUserMapper.xml b/mapper/sys/user/SysUserMapper.xml
new file mode 100644
index 0000000..a9965db
--- /dev/null
+++ b/mapper/sys/user/SysUserMapper.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -- 定义需要查询的列,使用反引号避免与 SQL 关键字冲突
+ `id`,`user_name`,`real_name`,`password`,`salt`,`role_ids`,`depart_id`,`create_time`,`update_time`,`state`
+
+
+
diff --git a/mapper/sys/user/SysUserRoleMapper.xml b/mapper/sys/user/SysUserRoleMapper.xml
new file mode 100644
index 0000000..6a6790d
--- /dev/null
+++ b/mapper/sys/user/SysUserRoleMapper.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`user_id`,`role_id`
+
+
+
diff --git a/mapper/user/UserBookMapper.xml b/mapper/user/UserBookMapper.xml
new file mode 100644
index 0000000..943aedb
--- /dev/null
+++ b/mapper/user/UserBookMapper.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -- 定义需要查询的列,使用反引号避免与 SQL 关键字冲突
+ `id`,`exam_id`,`user_id`,`qu_id`,`create_time`,`update_time`,`wrong_count`,`title`,`sort`
+
+
+
diff --git a/mapper/user/UserExamMapper.xml b/mapper/user/UserExamMapper.xml
new file mode 100644
index 0000000..51943ae
--- /dev/null
+++ b/mapper/user/UserExamMapper.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `id`,`user_id`,`exam_id`,`try_count`,`max_score`,`passed`,`create_time`,`update_time`
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repo/controller/RepoController.java b/repo/controller/RepoController.java
new file mode 100644
index 0000000..7d1c420
--- /dev/null
+++ b/repo/controller/RepoController.java
@@ -0,0 +1,132 @@
+package com.yf.exam.modules.repo.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yf.exam.core.api.ApiRest;
+import com.yf.exam.core.api.controller.BaseController;
+import com.yf.exam.core.api.dto.BaseIdReqDTO;
+import com.yf.exam.core.api.dto.BaseIdsReqDTO;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.qu.dto.request.QuRepoBatchReqDTO;
+import com.yf.exam.modules.qu.service.QuRepoService;
+import com.yf.exam.modules.repo.dto.RepoDTO;
+import com.yf.exam.modules.repo.dto.request.RepoReqDTO;
+import com.yf.exam.modules.repo.dto.response.RepoRespDTO;
+import com.yf.exam.modules.repo.entity.Repo;
+import com.yf.exam.modules.repo.service.RepoService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ * 题库控制器,处理与题库相关的 HTTP 请求
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:25
+ */
+@Api(tags={"题库"})
+@RestController
+@RequestMapping("/exam/api/repo")
+public class RepoController extends BaseController {
+
+ /**
+ * 注入题库服务,用于处理题库相关的业务逻辑
+ */
+ @Autowired
+ private RepoService baseService;
+
+ /**
+ * 注入题目与题库关联服务,用于处理题目与题库的批量操作
+ */
+ @Autowired
+ private QuRepoService quRepoService;
+
+ /**
+ * 添加或修改题库信息
+ * @param reqDTO 包含题库信息的请求数据传输对象
+ * @return 操作结果,封装在 ApiRest 对象中
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "添加或修改")
+ @RequestMapping(value = "/save", method = { RequestMethod.POST})
+ public ApiRest save(@RequestBody RepoDTO reqDTO) {
+ // 调用服务层方法保存或更新题库信息
+ baseService.save(reqDTO);
+ // 返回操作成功的响应
+ return super.success();
+ }
+
+ /**
+ * 批量删除题库
+ * @param reqDTO 包含要删除的题库 ID 列表的请求数据传输对象
+ * @return 操作结果,封装在 ApiRest 对象中
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "批量删除")
+ @RequestMapping(value = "/delete", method = { RequestMethod.POST})
+ public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) {
+ // 根据 ID 列表删除题库
+ baseService.removeByIds(reqDTO.getIds());
+ // 返回操作成功的响应
+ return super.success();
+ }
+
+ /**
+ * 查找单个题库的详情信息
+ * @param reqDTO 包含要查找的题库 ID 的请求数据传输对象
+ * @return 包含题库详情信息的操作结果,封装在 ApiRest 对象中
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "查找详情")
+ @RequestMapping(value = "/detail", method = { RequestMethod.POST})
+ public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) {
+ // 根据 ID 获取题库实体
+ Repo entity = baseService.getById(reqDTO.getId());
+ // 创建一个新的 DTO 对象
+ RepoDTO dto = new RepoDTO();
+ // 将实体对象的属性复制到 DTO 对象中
+ BeanUtils.copyProperties(entity, dto);
+ // 返回包含 DTO 对象的操作成功响应
+ return super.success(dto);
+ }
+
+ /**
+ * 分页查找题库信息
+ * @param reqDTO 包含分页和查询条件的请求数据传输对象
+ * @return 包含分页查询结果的操作结果,封装在 ApiRest 对象中
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "分页查找")
+ @RequestMapping(value = "/paging", method = { RequestMethod.POST})
+ public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) {
+
+ // 调用服务层方法进行分页查询并转换结果
+ IPage page = baseService.paging(reqDTO);
+
+ // 返回包含分页结果的操作成功响应
+ return super.success(page);
+ }
+
+ /**
+ * 批量操作,批量加入或从题库移除题目
+ * @param reqDTO 包含批量操作信息的请求数据传输对象
+ * @return 操作结果,封装在 ApiRest 对象中
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "批量操作", notes = "批量加入或从题库移除")
+ @RequestMapping(value = "/batch-action", method = { RequestMethod.POST})
+ public ApiRest batchAction(@RequestBody QuRepoBatchReqDTO reqDTO) {
+
+ // 调用服务层方法进行批量操作
+ quRepoService.batchAction(reqDTO);
+ // 返回操作成功的响应
+ return super.success();
+ }
+}
diff --git a/repo/dto/RepoDTO.java b/repo/dto/RepoDTO.java
new file mode 100644
index 0000000..c2063da
--- /dev/null
+++ b/repo/dto/RepoDTO.java
@@ -0,0 +1,67 @@
+package com.yf.exam.modules.repo.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ * 题库请求类,用于在与题库相关的业务操作中传输数据,实现了 Serializable 接口,可进行序列化操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+@Data
+@ApiModel(value="题库", description="题库")
+public class RepoDTO implements Serializable {
+
+ // 序列化版本号,确保序列化和反序列化的兼容性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 题库的唯一标识 ID,在业务操作中用于唯一确定一个题库。
+ * 该属性为必填项。
+ */
+ @ApiModelProperty(value = "题库ID", required=true)
+ private String id;
+
+ /**
+ * 题库的编号,可用于对题库进行分类或标识。
+ * 该属性为必填项。
+ */
+ @ApiModelProperty(value = "题库编号", required=true)
+ private String code;
+
+ /**
+ * 题库的名称,用于直观地表示题库的主题或内容。
+ * 该属性为必填项。
+ */
+ @ApiModelProperty(value = "题库名称", required=true)
+ private String title;
+
+ /**
+ * 题库的备注信息,可用于记录关于题库的额外说明或注意事项。
+ * 该属性为必填项。
+ */
+ @ApiModelProperty(value = "题库备注", required=true)
+ private String remark;
+
+ /**
+ * 题库的创建时间,记录该题库创建的具体时间点。
+ * 该属性为必填项。
+ */
+ @ApiModelProperty(value = "创建时间", required=true)
+ private Date createTime;
+
+ /**
+ * 题库的更新时间,记录该题库最后一次被修改的具体时间点。
+ * 该属性为必填项。
+ */
+ @ApiModelProperty(value = "更新时间", required=true)
+ private Date updateTime;
+
+}
diff --git a/repo/dto/request/RepoReqDTO.java b/repo/dto/request/RepoReqDTO.java
new file mode 100644
index 0000000..89cef56
--- /dev/null
+++ b/repo/dto/request/RepoReqDTO.java
@@ -0,0 +1,40 @@
+package com.yf.exam.modules.repo.dto.request;
+
+import com.yf.exam.modules.repo.dto.RepoDTO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ *
+ * 题库请求类,用于在分页查询题库信息时传递请求参数,继承自 RepoDTO 类,可复用其属性。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+@Data
+@ApiModel(value="题库分页请求类", description="题库分页请求类")
+public class RepoReqDTO extends RepoDTO {
+
+ // 序列化版本号,确保序列化和反序列化的兼容性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 排除题库 ID 列表,在查询题库时,会排除这些 ID 对应的题库。
+ * 该参数为必填项。
+ */
+ @ApiModelProperty(value = "排除题库ID", required=true)
+ private List excludes;
+
+ /**
+ * 题库标题,用于筛选具有特定标题的题库。
+ * 此处注解中的描述与字段名不符,原注解描述为单选题数量,可能存在错误,应根据实际需求调整。
+ * 该参数为必填项。
+ */
+ @ApiModelProperty(value = "单选题数量", required=true)
+ private String title;
+
+}
diff --git a/repo/dto/response/RepoRespDTO.java b/repo/dto/response/RepoRespDTO.java
new file mode 100644
index 0000000..e871768
--- /dev/null
+++ b/repo/dto/response/RepoRespDTO.java
@@ -0,0 +1,45 @@
+package com.yf.exam.modules.repo.dto.response;
+
+import com.yf.exam.modules.repo.dto.RepoDTO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ *
+ * 题库分页响应类,用于封装题库分页查询的响应数据。
+ * 该类继承自 RepoDTO,在其基础上扩展了不同题型数量的属性。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+@Data
+@ApiModel(value="题库分页响应类", description="题库分页响应类")
+public class RepoRespDTO extends RepoDTO {
+
+ // 序列化版本号,用于在序列化和反序列化过程中确保版本的兼容性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 多选题数量,代表该题库中多选题的总数。
+ * 该属性为必填项,在响应数据中必须包含。
+ */
+ @ApiModelProperty(value = "多选题数量", required=true)
+ private Integer multiCount;
+
+ /**
+ * 单选题数量,代表该题库中单选题的总数。
+ * 该属性为必填项,在响应数据中必须包含。
+ */
+ @ApiModelProperty(value = "单选题数量", required=true)
+ private Integer radioCount;
+
+ /**
+ * 判断题数量,代表该题库中判断题的总数。
+ * 该属性为必填项,在响应数据中必须包含。
+ */
+ @ApiModelProperty(value = "判断题数量", required=true)
+ private Integer judgeCount;
+
+}
diff --git a/repo/entity/Repo.java b/repo/entity/Repo.java
new file mode 100644
index 0000000..cb37a3c
--- /dev/null
+++ b/repo/entity/Repo.java
@@ -0,0 +1,62 @@
+package com.yf.exam.modules.repo.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ *
+ * 题库实体类,对应数据库中的 `el_repo` 表,用于封装题库相关的数据。
+ * 继承自 MyBatis-Plus 的 Model 类,可使用其提供的 ActiveRecord 功能进行数据库操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+@Data
+// 表明该实体类对应数据库中的 el_repo 表
+@TableName("el_repo")
+public class Repo extends Model {
+
+ // 序列化版本号,确保序列化和反序列化的兼容性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 题库ID,作为表的主键,使用 MyBatis-Plus 的 ASSIGN_ID 策略自动分配 ID。
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 题库编号,用于标识不同的题库。
+ */
+ private String code;
+
+ /**
+ * 题库名称,直观反映题库的主题或内容。
+ */
+ private String title;
+
+ /**
+ * 题库备注,可用于记录关于题库的额外说明信息。
+ */
+ private String remark;
+
+ /**
+ * 创建时间,记录题库创建的时间,对应数据库表中的 create_time 字段。
+ */
+ @TableField("create_time")
+ private Date createTime;
+
+ /**
+ * 更新时间,记录题库最后一次更新的时间,对应数据库表中的 update_time 字段。
+ */
+ @TableField("update_time")
+ private Date updateTime;
+
+}
diff --git a/repo/mapper/RepoMapper.java b/repo/mapper/RepoMapper.java
new file mode 100644
index 0000000..f6d009e
--- /dev/null
+++ b/repo/mapper/RepoMapper.java
@@ -0,0 +1,31 @@
+package com.yf.exam.modules.repo.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yf.exam.modules.repo.dto.request.RepoReqDTO;
+import com.yf.exam.modules.repo.dto.response.RepoRespDTO;
+import com.yf.exam.modules.repo.entity.Repo;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ *
+ * 题库Mapper接口,继承自 MyBatis-Plus 的 BaseMapper 接口,用于操作题库实体类与数据库的交互。
+ * BaseMapper 接口提供了基本的增删改查操作,该接口在此基础上扩展了分页查询功能。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+public interface RepoMapper extends BaseMapper {
+
+ /**
+ * 分页查询题库信息
+ *
+ * @param page 分页对象,包含分页的相关信息,如当前页码、每页记录数等,用于控制查询结果的分页展示。
+ * @param query 题库查询请求对象,包含查询条件,如排除题库ID、题库标题等,用于筛选符合条件的题库记录。
+ * @return 返回一个包含 RepoRespDTO 对象的分页数据,RepoRespDTO 是题库分页响应类,封装了题库的相关信息以及不同题型的数量。
+ */
+ IPage paging(Page page, @Param("query") RepoReqDTO query);
+
+}
diff --git a/repo/service/RepoService.java b/repo/service/RepoService.java
new file mode 100644
index 0000000..85dbed7
--- /dev/null
+++ b/repo/service/RepoService.java
@@ -0,0 +1,38 @@
+package com.yf.exam.modules.repo.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.repo.dto.RepoDTO;
+import com.yf.exam.modules.repo.dto.request.RepoReqDTO;
+import com.yf.exam.modules.repo.dto.response.RepoRespDTO;
+import com.yf.exam.modules.repo.entity.Repo;
+
+/**
+ *
+ * 题库业务类,定义了与题库相关的业务操作接口。
+ * 该接口继承自 MyBatis-Plus 的 IService 接口,可使用其提供的基础 CRUD 操作方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+public interface RepoService extends IService {
+
+ /**
+ * 分页查询题库数据
+ *
+ * @param reqDTO 包含分页信息和查询条件的请求对象。
+ * PagingReqDTO 封装了分页参数,RepoReqDTO 封装了具体的查询条件,如排除题库 ID、题库标题等。
+ * @return 一个 IPage 对象,包含分页查询结果,结果元素为 RepoRespDTO 类型,该类型封装了题库的详细信息及题型数量。
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 保存或更新题库信息
+ *
+ * @param reqDTO 包含题库信息的数据传输对象,包含题库的 ID、编号、名称、备注等信息。
+ * 若 DTO 中的 ID 存在,则进行更新操作;若 ID 不存在,则进行新增操作。
+ */
+ void save(RepoDTO reqDTO);
+}
diff --git a/repo/service/impl/RepoServiceImpl.java b/repo/service/impl/RepoServiceImpl.java
new file mode 100644
index 0000000..454d167
--- /dev/null
+++ b/repo/service/impl/RepoServiceImpl.java
@@ -0,0 +1,53 @@
+package com.yf.exam.modules.repo.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.core.utils.BeanMapper;
+import com.yf.exam.modules.repo.dto.RepoDTO;
+import com.yf.exam.modules.repo.dto.request.RepoReqDTO;
+import com.yf.exam.modules.repo.dto.response.RepoRespDTO;
+import com.yf.exam.modules.repo.entity.Repo;
+import com.yf.exam.modules.repo.mapper.RepoMapper;
+import com.yf.exam.modules.repo.service.RepoService;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 题库服务实现类,实现了 RepoService 接口,继承自 MyBatis-Plus 的 ServiceImpl,
+ * 用于处理题库相关的业务逻辑,与数据库进行交互。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-25 13:23
+ */
+@Service
+public class RepoServiceImpl extends ServiceImpl implements RepoService {
+
+ /**
+ * 分页查询题库信息
+ *
+ * @param reqDTO 包含分页信息和查询参数的请求对象
+ * @return 包含分页结果的 IPage 对象,其中元素为 RepoRespDTO 类型
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+ // 调用 RepoMapper 的 paging 方法进行分页查询,传入分页对象和查询参数
+ return baseMapper.paging(reqDTO.toPage(), reqDTO.getParams());
+ }
+
+ /**
+ * 保存或更新题库信息
+ *
+ * @param reqDTO 包含题库信息的请求数据传输对象
+ */
+ @Override
+ public void save(RepoDTO reqDTO) {
+
+ // 复制 DTO 对象的属性到实体对象
+ Repo entity = new Repo();
+ BeanMapper.copy(reqDTO, entity);
+ // 调用 MyBatis-Plus 的 saveOrUpdate 方法保存或更新实体对象
+ this.saveOrUpdate(entity);
+ }
+}
diff --git a/sys/config/controller/SysConfigController.java b/sys/config/controller/SysConfigController.java
new file mode 100644
index 0000000..fa9cfe4
--- /dev/null
+++ b/sys/config/controller/SysConfigController.java
@@ -0,0 +1,92 @@
+package com.yf.exam.modules.sys.config.controller;
+
+// 导入 API 响应结果类,用于封装接口返回数据
+import com.yf.exam.core.api.ApiRest;
+// 导入基础控制器类,提供一些通用的控制器方法
+import com.yf.exam.core.api.controller.BaseController;
+// 导入基础 ID 响应数据传输对象类
+import com.yf.exam.core.api.dto.BaseIdRespDTO;
+// 导入 Bean 映射工具类,用于对象属性的复制
+import com.yf.exam.core.utils.BeanMapper;
+// 导入图片校验工具类
+import com.yf.exam.modules.qu.utils.ImageCheckUtils;
+// 导入系统配置数据传输对象类
+import com.yf.exam.modules.sys.config.dto.SysConfigDTO;
+// 导入系统配置实体类
+import com.yf.exam.modules.sys.config.entity.SysConfig;
+// 导入系统配置服务接口
+import com.yf.exam.modules.sys.config.service.SysConfigService;
+// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签
+import io.swagger.annotations.Api;
+// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述
+import io.swagger.annotations.ApiOperation;
+// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口
+import org.apache.shiro.authz.annotation.RequiresRoles;
+// 导入 Spring 依赖注入注解
+import org.springframework.beans.factory.annotation.Autowired;
+// 导入 Spring 请求体注解,用于将请求体中的数据绑定到方法参数上
+import org.springframework.web.bind.annotation.RequestBody;
+// 导入 Spring 请求映射注解,用于映射请求路径
+import org.springframework.web.bind.annotation.RequestMapping;
+// 导入 Spring 请求方法注解,用于指定请求的 HTTP 方法
+import org.springframework.web.bind.annotation.RequestMethod;
+// 导入 Spring 控制器注解,标记该类为 RESTful 风格的控制器
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ * 通用配置控制器,处理与系统通用配置相关的接口请求
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-17 09:12
+ */
+@Api(tags={"通用配置"})
+@RestController
+@RequestMapping("/exam/api/sys/config")
+public class SysConfigController extends BaseController {
+
+ // 自动注入系统配置服务实例,用于调用系统配置相关的业务逻辑
+ @Autowired
+ private SysConfigService baseService;
+
+ // 自动注入图片校验工具实例,用于校验图片地址
+ @Autowired
+ private ImageCheckUtils imageCheckUtils;
+
+ /**
+ * 添加或修改系统配置信息
+ * @param reqDTO 系统配置数据传输对象,包含要添加或修改的配置信息
+ * @return 封装了操作结果和配置 ID 的 API 响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "添加或修改")
+ @RequestMapping(value = "/save", method = { RequestMethod.POST})
+ public ApiRest save(@RequestBody SysConfigDTO reqDTO) {
+
+ // 复制请求数据传输对象中的属性到系统配置实体对象中
+ SysConfig entity = new SysConfig();
+ BeanMapper.copy(reqDTO, entity);
+
+ // 调用图片校验工具类的方法,校验系统配置中的背景 LOGO 地址是否合法
+ imageCheckUtils.checkImage(entity.getBackLogo(), "系统LOGO地址错误!");
+
+ // 调用系统配置服务的方法,保存或更新系统配置信息
+ baseService.saveOrUpdate(entity);
+ // 调用父类的成功响应方法,返回包含配置 ID 的响应对象
+ return super.success(new BaseIdRespDTO(entity.getId()));
+ }
+
+ /**
+ * 查找系统配置详情
+ * @return 封装了系统配置详情数据传输对象的 API 响应对象
+ */
+ @ApiOperation(value = "查找详情")
+ @RequestMapping(value = "/detail", method = { RequestMethod.POST})
+ public ApiRest find() {
+ // 调用系统配置服务的方法,获取系统配置详情数据传输对象
+ SysConfigDTO dto = baseService.find();
+ // 调用父类的成功响应方法,返回包含系统配置详情的响应对象
+ return super.success(dto);
+ }
+}
diff --git a/sys/config/dto/SysConfigDTO.java b/sys/config/dto/SysConfigDTO.java
new file mode 100644
index 0000000..f85b824
--- /dev/null
+++ b/sys/config/dto/SysConfigDTO.java
@@ -0,0 +1,61 @@
+// 定义包名,表明该类所属的模块和功能目录
+package com.yf.exam.modules.sys.config.dto;
+
+// 导入 Swagger 注解,用于生成 API 文档,标记类或属性的描述信息
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+// 导入 Serializable 接口,表明该类的对象可以被序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 通用配置请求类,用于在不同层之间传输系统通用配置信息
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-17 09:12
+ */
+// 自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 为 Swagger 文档提供类的描述信息
+@ApiModel(value="通用配置", description="通用配置")
+public class SysConfigDTO implements Serializable {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 系统配置的唯一标识
+ * 该属性在请求和响应中是必需的
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 系统的名称
+ */
+ @ApiModelProperty(value = "系统名称")
+ private String siteName;
+
+ /**
+ * 前端页面显示的 LOGO 地址
+ */
+ @ApiModelProperty(value = "前端LOGO")
+ private String frontLogo;
+
+ /**
+ * 后台管理页面显示的 LOGO 地址
+ */
+ @ApiModelProperty(value = "后台LOGO")
+ private String backLogo;
+
+ /**
+ * 系统的版权信息
+ */
+ @ApiModelProperty(value = "版权信息")
+ private String copyRight;
+
+}
diff --git a/sys/config/entity/SysConfig.java b/sys/config/entity/SysConfig.java
new file mode 100644
index 0000000..070ab06
--- /dev/null
+++ b/sys/config/entity/SysConfig.java
@@ -0,0 +1,53 @@
+package com.yf.exam.modules.sys.config.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+
+/**
+*
+* 通用配置实体类
+*
+*
+* @author 聪明笨狗
+* @since 2020-04-17 09:12
+*/
+@Data
+@TableName("sys_config")
+public class SysConfig extends Model {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 系统名称
+ */
+ @TableField("site_name")
+ private String siteName;
+
+ /**
+ * 前端LOGO
+ */
+ @TableField("front_logo")
+ private String frontLogo;
+
+ /**
+ * 后台LOGO
+ */
+ @TableField("back_logo")
+ private String backLogo;
+
+ /**
+ * 版权信息
+ */
+ @TableField("copy_right")
+ private String copyRight;
+}
diff --git a/sys/config/mapper/SysConfigMapper.java b/sys/config/mapper/SysConfigMapper.java
new file mode 100644
index 0000000..35eea21
--- /dev/null
+++ b/sys/config/mapper/SysConfigMapper.java
@@ -0,0 +1,25 @@
+/**
+ * 定义包名,表明该类所在的模块和目录结构,此包为系统配置模块的映射器包
+ */
+package com.yf.exam.modules.sys.config.mapper;
+
+/**
+ * 导入 MyBatis-Plus 框架的 BaseMapper 接口,用于提供基本的数据库操作方法
+ */
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+/**
+ * 导入系统配置实体类,该实体类对应数据库中的系统配置表
+ */
+import com.yf.exam.modules.sys.config.entity.SysConfig;
+
+/**
+ *
+ * 通用配置Mapper,用于与数据库中的系统配置表进行交互,继承自 BaseMapper 可使用其提供的基础 CRUD 操作
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-17 09:12
+ */
+public interface SysConfigMapper extends BaseMapper {
+ // 该接口目前未自定义额外的数据库操作方法,可根据需求在此添加
+}
diff --git a/sys/config/service/SysConfigService.java b/sys/config/service/SysConfigService.java
new file mode 100644
index 0000000..321b533
--- /dev/null
+++ b/sys/config/service/SysConfigService.java
@@ -0,0 +1,34 @@
+/**
+ * 定义包名,表明该类所属的模块和功能目录,这里是系统配置服务模块
+ */
+package com.yf.exam.modules.sys.config.service;
+
+/**
+ * 导入 MyBatis-Plus 框架的扩展服务接口,该接口提供了一些通用的服务层方法
+ */
+import com.baomidou.mybatisplus.extension.service.IService;
+/**
+ * 导入系统配置数据传输对象类,用于在不同层之间传输系统配置相关的数据
+ */
+import com.yf.exam.modules.sys.config.dto.SysConfigDTO;
+/**
+ * 导入系统配置实体类,对应数据库中的系统配置表
+ */
+import com.yf.exam.modules.sys.config.entity.SysConfig;
+
+/**
+ *
+ * 通用配置业务类,定义了与系统通用配置相关的业务方法,继承自 IService 接口可使用其提供的基础服务方法
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-17 09:12
+ */
+public interface SysConfigService extends IService {
+
+ /**
+ * 查找配置信息,从数据库或其他数据源中获取系统配置信息并封装成 SysConfigDTO 对象返回
+ * @return 系统配置数据传输对象,包含系统配置的相关信息
+ */
+ SysConfigDTO find();
+}
diff --git a/sys/config/service/impl/SysConfigServiceImpl.java b/sys/config/service/impl/SysConfigServiceImpl.java
new file mode 100644
index 0000000..c02cf8e
--- /dev/null
+++ b/sys/config/service/impl/SysConfigServiceImpl.java
@@ -0,0 +1,54 @@
+// 定义当前类所在的包
+package com.yf.exam.modules.sys.config.service.impl;
+
+// 导入 MyBatis-Plus 框架的查询条件构造器类
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 框架的服务实现基类
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+// 导入自定义的 Bean 映射工具类,用于对象属性的复制
+import com.yf.exam.core.utils.BeanMapper;
+// 导入系统配置数据传输对象类
+import com.yf.exam.modules.sys.config.dto.SysConfigDTO;
+// 导入系统配置实体类
+import com.yf.exam.modules.sys.config.entity.SysConfig;
+// 导入系统配置映射器接口
+import com.yf.exam.modules.sys.config.mapper.SysConfigMapper;
+// 导入系统配置服务接口
+import com.yf.exam.modules.sys.config.service.SysConfigService;
+// 导入 Spring 框架的服务注解,将该类标记为服务层组件
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 语言设置 服务实现类,负责处理系统配置的具体业务逻辑
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-17 09:12
+ */
+// 将该类标记为 Spring 服务组件,使其可以被 Spring 容器管理
+@Service
+public class SysConfigServiceImpl extends ServiceImpl implements SysConfigService {
+
+ /**
+ * 查找系统配置信息,返回第一个系统配置的 DTO 对象
+ *
+ * @return 系统配置数据传输对象
+ */
+ @Override
+ public SysConfigDTO find() {
+ // 创建一个查询条件构造器,用于构建对 SysConfig 实体的查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ // 在查询语句末尾添加 "LIMIT 1",表示只查询一条记录
+ wrapper.last(" LIMIT 1");
+
+ // 调用父类的 getOne 方法,根据查询条件获取一个 SysConfig 实体对象,false 表示不严格校验是否只返回一条记录
+ SysConfig entity = this.getOne(wrapper, false);
+ // 创建一个新的系统配置数据传输对象
+ SysConfigDTO dto = new SysConfigDTO();
+ // 使用 BeanMapper 工具类将 SysConfig 实体对象的属性复制到 SysConfigDTO 对象中
+ BeanMapper.copy(entity, dto);
+ // 返回填充好数据的系统配置数据传输对象
+ return dto;
+ }
+}
diff --git a/sys/depart/controller/SysDepartController.java b/sys/depart/controller/SysDepartController.java
new file mode 100644
index 0000000..fe7be06
--- /dev/null
+++ b/sys/depart/controller/SysDepartController.java
@@ -0,0 +1,186 @@
+// 定义当前类所在的包,表明该类属于系统部门控制器模块
+package com.yf.exam.modules.sys.depart.controller;
+
+// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入自定义的 API 统一响应类,用于封装接口返回数据
+import com.yf.exam.core.api.ApiRest;
+// 导入自定义的基础控制器类,提供一些通用的控制器方法
+import com.yf.exam.core.api.controller.BaseController;
+// 导入自定义的基础单个 ID 请求数据传输对象类
+import com.yf.exam.core.api.dto.BaseIdReqDTO;
+// 导入自定义的基础多个 ID 请求数据传输对象类
+import com.yf.exam.core.api.dto.BaseIdsReqDTO;
+// 导入自定义的分页请求数据传输对象类
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入自定义的 Bean 映射工具类,用于对象属性的复制
+import com.yf.exam.core.utils.BeanMapper;
+// 导入系统部门数据传输对象类,用于在不同层之间传输部门信息
+import com.yf.exam.modules.sys.depart.dto.SysDepartDTO;
+// 导入部门排序请求数据传输对象类,用于接收部门排序的请求信息
+import com.yf.exam.modules.sys.depart.dto.request.DepartSortReqDTO;
+// 导入系统部门树状数据传输对象类,用于返回部门的树状结构信息
+import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO;
+// 导入系统部门实体类,对应数据库中的部门表
+import com.yf.exam.modules.sys.depart.entity.SysDepart;
+// 导入系统部门服务接口,用于调用部门相关的业务逻辑
+import com.yf.exam.modules.sys.depart.service.SysDepartService;
+// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签
+import io.swagger.annotations.Api;
+// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述
+import io.swagger.annotations.ApiOperation;
+// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口
+import org.apache.shiro.authz.annotation.RequiresRoles;
+// 导入 Spring 框架的依赖注入注解,用于自动注入依赖的 Bean
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+// 导入 Spring 框架的请求体注解,用于将请求体中的数据绑定到方法参数上
+import org.springframework.web.bind.annotation.RequestBody;
+// 导入 Spring 框架的请求映射注解,用于映射请求路径
+import org.springframework.web.bind.annotation.RequestMapping;
+// 导入 Spring 框架的请求方法注解,用于指定请求的 HTTP 方法
+import org.springframework.web.bind.annotation.RequestMethod;
+// 导入 Spring 框架的控制器注解,标记该类为 RESTful 风格的控制器
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ *
+ * 部门信息控制器,处理与系统部门信息相关的接口请求
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-02 17:25
+ */
+// 为 Swagger 文档标记该控制器的标签为 "部门信息"
+@Api(tags={"部门信息"})
+// 标记该类为 RESTful 风格的控制器,返回数据直接作为 HTTP 响应体
+@RestController
+// 定义该控制器处理的请求路径前缀
+@RequestMapping("/exam/api/sys/depart")
+public class SysDepartController extends BaseController {
+
+ // 自动注入系统部门服务实例,用于调用部门相关的业务逻辑
+ @Autowired
+ private SysDepartService baseService;
+
+ /**
+ * 添加或修改部门信息
+ * @param reqDTO 部门信息数据传输对象,包含要添加或修改的部门信息
+ * @return 封装了操作结果的 API 统一响应对象
+ */
+ // 要求用户具有 "sa" 角色才能访问该接口
+ @RequiresRoles("sa")
+ // 为 Swagger 文档标记该接口的操作描述为 "添加或修改"
+ @ApiOperation(value = "添加或修改")
+ // 映射请求路径为 "/save",并指定请求方法为 POST
+ @RequestMapping(value = "/save", method = { RequestMethod.POST})
+ public ApiRest save(@RequestBody SysDepartDTO reqDTO) {
+ // 调用部门服务的保存方法,保存或更新部门信息
+ baseService.save(reqDTO);
+ // 调用父类的成功响应方法,返回操作成功的响应对象
+ return super.success();
+ }
+
+ /**
+ * 批量删除部门信息
+ * @param reqDTO 包含多个部门 ID 的请求数据传输对象,用于指定要删除的部门
+ * @return 封装了操作结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "批量删除")
+ @RequestMapping(value = "/delete", method = { RequestMethod.POST})
+ public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) {
+ // 根据传入的部门 ID 列表,调用部门服务的删除方法删除部门信息
+ baseService.removeByIds(reqDTO.getIds());
+ return super.success();
+ }
+
+ /**
+ * 查找部门详情信息
+ * @param reqDTO 包含单个部门 ID 的请求数据传输对象,用于指定要查找的部门
+ * @return 封装了部门详情信息的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "查找详情")
+ @RequestMapping(value = "/detail", method = { RequestMethod.POST})
+ public ApiRest find(@RequestBody BaseIdReqDTO reqDTO) {
+ // 根据传入的部门 ID,调用部门服务的获取方法获取部门实体对象
+ SysDepart entity = baseService.getById(reqDTO.getId());
+ // 创建一个新的部门数据传输对象
+ SysDepartDTO dto = new SysDepartDTO();
+ // 将部门实体对象的属性复制到部门数据传输对象中
+ BeanUtils.copyProperties(entity, dto);
+ return super.success(dto);
+ }
+
+ /**
+ * 分页查找部门信息
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数
+ * @return 封装了分页查询结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "分页查找")
+ @RequestMapping(value = "/paging", method = { RequestMethod.POST})
+ public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) {
+
+ // 调用部门服务的分页查询方法,进行分页查询并将结果转换为树状数据传输对象
+ IPage page = baseService.paging(reqDTO);
+
+ return super.success(page);
+ }
+
+ /**
+ * 查找部门列表,每次最多返回 200 条数据
+ * @param reqDTO 部门信息数据传输对象,包含查询部门列表的条件
+ * @return 封装了部门列表信息的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "查找列表")
+ @RequestMapping(value = "/list", method = { RequestMethod.POST})
+ public ApiRest> list(@RequestBody SysDepartDTO reqDTO) {
+
+ // 创建一个 MyBatis-Plus 的查询条件构造器
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ // 调用部门服务的列表查询方法,根据查询条件获取部门实体列表
+ List list = baseService.list(wrapper);
+
+ // 使用 Bean 映射工具类将部门实体列表转换为部门数据传输对象列表
+ List dtoList = BeanMapper.mapList(list, SysDepartDTO.class);
+
+ return super.success(dtoList);
+ }
+
+
+ /**
+ * 获取部门树列表
+ * @return 封装了部门树列表信息的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "树列表")
+ @RequestMapping(value = "/tree", method = { RequestMethod.POST})
+ public ApiRest> tree() {
+ // 调用部门服务的查找树列表方法,获取部门树列表数据传输对象列表
+ List dtoList = baseService.findTree();
+ return super.success(dtoList);
+ }
+
+
+ /**
+ * 对部门进行分类排序
+ * @param reqDTO 部门排序请求数据传输对象,包含要排序的部门 ID 和排序值
+ * @return 封装了操作结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "分类排序")
+ @RequestMapping(value = "/sort", method = { RequestMethod.POST})
+ public ApiRest sort(@RequestBody DepartSortReqDTO reqDTO) {
+ // 调用部门服务的排序方法,根据传入的部门 ID 和排序值对部门进行排序
+ baseService.sort(reqDTO.getId(), reqDTO.getSort());
+ return super.success();
+ }
+}
diff --git a/sys/depart/dto/SysDepartDTO.java b/sys/depart/dto/SysDepartDTO.java
new file mode 100644
index 0000000..70d8efb
--- /dev/null
+++ b/sys/depart/dto/SysDepartDTO.java
@@ -0,0 +1,73 @@
+// 定义该类所在的包,表明其在项目模块中的位置
+package com.yf.exam.modules.sys.depart.dto;
+
+// 导入 Swagger 注解,用于在生成 API 文档时描述类的信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于在生成 API 文档时描述类属性的信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 部门信息数据传输类,用于在不同层之间传输部门相关信息,如在控制器与服务层、服务层与数据访问层之间传递数据。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-02 17:25
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息
+@ApiModel(value="部门信息", description="部门信息")
+public class SysDepartDTO implements Serializable {
+
+ // 序列化版本号,确保序列化和反序列化时类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 部门的唯一标识 ID
+ * 在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 部门类型,1 表示公司,2 表示部门
+ * 在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "1公司2部门", required=true)
+ private Integer deptType;
+
+ /**
+ * 该部门所属的上级部门 ID
+ * 在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "所属上级", required=true)
+ private String parentId;
+
+ /**
+ * 部门的名称
+ * 在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "部门名称", required=true)
+ private String deptName;
+
+ /**
+ * 部门的编码
+ * 在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "部门编码", required=true)
+ private String deptCode;
+
+ /**
+ * 部门的排序值,用于对部门进行排序展示
+ * 在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "排序", required=true)
+ private Integer sort;
+
+}
diff --git a/sys/depart/dto/request/DepartSortReqDTO.java b/sys/depart/dto/request/DepartSortReqDTO.java
new file mode 100644
index 0000000..40fd441
--- /dev/null
+++ b/sys/depart/dto/request/DepartSortReqDTO.java
@@ -0,0 +1,42 @@
+// 声明该类所在的包,表明其在项目模块中的位置
+package com.yf.exam.modules.sys.depart.dto.request;
+
+// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+// 导入 Serializable 接口,表明该类的对象可以被序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 部门排序请求类,用于封装部门排序操作所需的请求参数
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-03-14 10:37
+ */
+// 自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 为 Swagger 文档提供类的描述信息
+@ApiModel(value="部门排序请求类", description="部门排序请求类")
+public class DepartSortReqDTO implements Serializable {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 分类 ID,标识要进行排序操作的部门
+ */
+ @ApiModelProperty(value = "分类ID")
+ private String id;
+
+ /**
+ * 排序方式,0 表示下降排序,1 表示上升排序
+ */
+ @ApiModelProperty(value = "排序,0下降,1上升")
+ private Integer sort;
+}
diff --git a/sys/depart/dto/response/SysDepartTreeDTO.java b/sys/depart/dto/response/SysDepartTreeDTO.java
new file mode 100644
index 0000000..bb7137b
--- /dev/null
+++ b/sys/depart/dto/response/SysDepartTreeDTO.java
@@ -0,0 +1,41 @@
+// 定义包名,表明该类所属的模块和功能目录
+package com.yf.exam.modules.sys.depart.dto.response;
+
+// 导入 SysDepartDTO 类,该类是当前类的父类,包含部门的基本信息
+import com.yf.exam.modules.sys.depart.dto.SysDepartDTO;
+// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+// 导入 List 接口,用于存储子部门列表
+import java.util.List;
+
+/**
+ *
+ * 部门树结构响应类,继承自 SysDepartDTO,用于返回部门的树状结构信息
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-02 17:25
+ */
+// 自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 为 Swagger 文档提供类的描述信息
+@ApiModel(value="部门树结构响应类", description="部门树结构响应类")
+public class SysDepartTreeDTO extends SysDepartDTO {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 子部门列表,存储当前部门的所有子部门信息
+ * 该属性在 API 文档中标记为必需项
+ */
+ @ApiModelProperty(value = "子列表", required=true)
+ private List children;
+
+
+}
diff --git a/sys/depart/entity/SysDepart.java b/sys/depart/entity/SysDepart.java
new file mode 100644
index 0000000..71ca156
--- /dev/null
+++ b/sys/depart/entity/SysDepart.java
@@ -0,0 +1,59 @@
+package com.yf.exam.modules.sys.depart.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+
+/**
+*
+* 部门信息实体类
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-02 17:25
+*/
+@Data
+@TableName("sys_depart")
+public class SysDepart extends Model {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 1公司2部门
+ */
+ @TableField("dept_type")
+ private Integer deptType;
+
+ /**
+ * 所属上级
+ */
+ @TableField("parent_id")
+ private String parentId;
+
+ /**
+ * 部门名称
+ */
+ @TableField("dept_name")
+ private String deptName;
+
+ /**
+ * 部门编码
+ */
+ @TableField("dept_code")
+ private String deptCode;
+
+ /**
+ * 排序
+ */
+ private Integer sort;
+
+}
diff --git a/sys/depart/mapper/SysDepartMapper.java b/sys/depart/mapper/SysDepartMapper.java
new file mode 100644
index 0000000..221ab89
--- /dev/null
+++ b/sys/depart/mapper/SysDepartMapper.java
@@ -0,0 +1,28 @@
+package com.yf.exam.modules.sys.depart.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yf.exam.modules.sys.depart.dto.SysDepartDTO;
+import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO;
+import com.yf.exam.modules.sys.depart.entity.SysDepart;
+import org.apache.ibatis.annotations.Param;
+
+/**
+*
+* 部门信息Mapper
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-02 17:25
+*/
+public interface SysDepartMapper extends BaseMapper {
+
+ /**
+ * 部门树分页
+ * @param page
+ * @param query
+ * @return
+ */
+ IPage paging(Page page, @Param("query") SysDepartDTO query);
+}
diff --git a/sys/depart/service/SysDepartService.java b/sys/depart/service/SysDepartService.java
new file mode 100644
index 0000000..1b70e83
--- /dev/null
+++ b/sys/depart/service/SysDepartService.java
@@ -0,0 +1,62 @@
+package com.yf.exam.modules.sys.depart.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.sys.depart.dto.SysDepartDTO;
+import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO;
+import com.yf.exam.modules.sys.depart.entity.SysDepart;
+
+import java.util.List;
+
+/**
+*
+* 部门信息业务类
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-02 17:25
+*/
+public interface SysDepartService extends IService {
+
+ /**
+ * 保存
+ * @param reqDTO
+ */
+ void save(SysDepartDTO reqDTO);
+
+ /**
+ * 分页查询数据
+ * @param reqDTO
+ * @return
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 查找部门树结构
+ * @return
+ */
+ List findTree();
+
+ /**
+ * 查找部门树
+ * @param ids
+ * @return
+ */
+ List findTree(List ids);
+
+ /**
+ * 排序
+ * @param id
+ * @param sort
+ */
+ void sort(String id, Integer sort);
+
+
+ /**
+ * 获取某个部门ID下的所有子部门ID
+ * @param id
+ * @return
+ */
+ List listAllSubIds( String id);
+}
diff --git a/sys/depart/service/impl/SysDepartServiceImpl.java b/sys/depart/service/impl/SysDepartServiceImpl.java
new file mode 100644
index 0000000..e0a610a
--- /dev/null
+++ b/sys/depart/service/impl/SysDepartServiceImpl.java
@@ -0,0 +1,368 @@
+package com.yf.exam.modules.sys.depart.service.impl;
+
+// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 的分页类,用于创建分页对象
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+// 导入 MyBatis-Plus 的服务实现基类,提供通用的服务层方法
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入自定义的 Bean 映射工具类,用于对象属性的复制
+import com.yf.exam.core.utils.BeanMapper;
+// 导入系统部门数据传输对象类,用于在不同层之间传输部门信息
+import com.yf.exam.modules.sys.depart.dto.SysDepartDTO;
+// 导入系统部门树状数据传输对象类,用于返回部门的树状结构信息
+import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO;
+// 导入系统部门实体类,对应数据库中的部门表
+import com.yf.exam.modules.sys.depart.entity.SysDepart;
+// 导入系统部门映射器接口,用于与数据库进行交互
+import com.yf.exam.modules.sys.depart.mapper.SysDepartMapper;
+// 导入系统部门服务接口,定义部门相关的业务方法
+import com.yf.exam.modules.sys.depart.service.SysDepartService;
+// 导入 Apache Commons Lang3 工具类,用于字符串操作
+import org.apache.commons.lang3.StringUtils;
+// 导入 Spring 工具类,用于集合操作
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * 部门信息业务实现类,实现了系统部门服务接口,处理部门相关的业务逻辑
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-02 17:25
+ */
+// 将该类标记为 Spring 服务组件,使其可以被 Spring 容器管理
+@Service
+public class SysDepartServiceImpl extends ServiceImpl implements SysDepartService {
+
+ /**
+ * 0标识为顶级分类,作为根部门的标记
+ */
+ private static final String ROOT_TAG = "0";
+
+ /**
+ * 保存或更新部门信息
+ * 如果传入的部门 ID 为空,则填充部门编码;否则,清空排序和部门编码信息
+ * @param reqDTO 部门信息数据传输对象,包含要保存或更新的部门信息
+ */
+ @Override
+ public void save(SysDepartDTO reqDTO) {
+ // 判断传入的部门 ID 是否为空
+ if(StringUtils.isBlank(reqDTO.getId())) {
+ // 若为空,则填充部门编码
+ this.fillCode(reqDTO);
+ } else {
+ // 若不为空,则清空排序和部门编码信息
+ reqDTO.setSort(null);
+ reqDTO.setDeptCode(null);
+ }
+
+ // 创建一个新的部门实体对象
+ SysDepart entity = new SysDepart();
+ // 使用 BeanMapper 工具类将部门数据传输对象的属性复制到部门实体对象中
+ BeanMapper.copy(reqDTO, entity);
+ // 调用 MyBatis-Plus 的保存或更新方法,保存或更新部门信息
+ this.saveOrUpdate(entity);
+ }
+
+ /**
+ * 分页查询部门信息,并将结果转换为树状结构
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数
+ * @return 分页的部门树状结构数据传输对象列表
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+ // 创建分页对象,传入当前页码和每页记录数
+ Page query = new Page(reqDTO.getCurrent(), reqDTO.getSize());
+ // 获取请求参数中的部门信息
+ SysDepartDTO params = reqDTO.getParams();
+ // 调用映射器的分页查询方法,将结果转换为部门树状结构数据传输对象列表
+ IPage pageData = baseMapper.paging(query, params);
+
+ return pageData;
+ }
+
+ /**
+ * 查找部门树状结构,调用重载方法,不传入部门 ID 列表
+ * @return 部门树状结构数据传输对象列表
+ */
+ @Override
+ public List findTree() {
+ return this.findTree(null);
+ }
+
+ /**
+ * 根据指定的部门 ID 列表查找部门树状结构
+ * @param ids 部门 ID 列表,若为空则查找所有部门的树状结构
+ * @return 部门树状结构数据传输对象列表
+ */
+ @Override
+ public List findTree(List ids) {
+ // 创建查询条件构造器
+ QueryWrapper wrapper = new QueryWrapper();
+ // 按部门排序字段升序排序
+ wrapper.lambda().orderByAsc(SysDepart::getSort);
+
+ // 判断传入的部门 ID 列表是否不为空
+ if(!CollectionUtils.isEmpty(ids)) {
+ // 存储所有相关部门 ID 的列表
+ List fullIds = new ArrayList<>();
+ // 遍历传入的部门 ID 列表
+ for(String id: ids) {
+ // 递归获取该部门及其所有父部门的 ID 并添加到 fullIds 列表中
+ this.cycleAllParent(fullIds, id);
+ }
+
+ // 判断 fullIds 列表是否不为空
+ if(!CollectionUtils.isEmpty(fullIds)) {
+ // 将查询条件限制为 fullIds 列表中的部门 ID
+ wrapper.lambda().in(SysDepart::getId, fullIds);
+ }
+ }
+
+ // 根据查询条件获取所有部门列表
+ List list = this.list(wrapper);
+ // 使用 BeanMapper 工具类将部门实体列表转换为部门树状结构数据传输对象列表
+ List dtoList = BeanMapper.mapList(list, SysDepartTreeDTO.class);
+
+ // 存储每个父部门 ID 对应的子部门列表的映射
+ Map> map = new HashMap<>(16);
+
+ // 遍历部门树状结构数据传输对象列表
+ for(SysDepartTreeDTO item: dtoList) {
+ // 判断映射中是否已存在该父部门 ID
+ if(map.containsKey(item.getParentId())) {
+ // 若存在,则将该部门添加到对应的子部门列表中
+ map.get(item.getParentId()).add(item);
+ continue;
+ }
+
+ // 若不存在,则创建一个新的子部门列表,并将该部门添加到列表中
+ List a = new ArrayList<>();
+ a.add(item);
+ // 将该父部门 ID 和对应的子部门列表添加到映射中
+ map.put(item.getParentId(), a);
+ }
+
+ // 获取根部门(父部门 ID 为 0)的子部门列表
+ List topList = map.get(ROOT_TAG);
+ // 判断根部门的子部门列表是否不为空
+ if(!CollectionUtils.isEmpty(topList)) {
+ // 遍历根部门的子部门列表
+ for(SysDepartTreeDTO item: topList) {
+ // 递归填充每个部门的子部门信息
+ this.fillChildren(map, item);
+ }
+ }
+
+ return topList;
+ }
+
+ /**
+ * 对部门进行排序操作,可实现部门的上升或下降排序
+ * @param id 要排序的部门 ID
+ * @param sort 排序方式,0 表示上升,1 表示下降
+ */
+ @Override
+ public void sort(String id, Integer sort) {
+ // 根据部门 ID 获取要排序的部门实体对象
+ SysDepart depart = this.getById(id);
+ // 用于交换排序的部门实体对象
+ SysDepart exchange = null;
+
+ // 创建查询条件构造器
+ QueryWrapper wrapper = new QueryWrapper<>();
+ // 限制查询条件为与要排序的部门同级(父部门相同)
+ wrapper.lambda()
+ .eq(SysDepart::getParentId, depart.getParentId());
+ // 限制查询结果只返回一条记录
+ wrapper.last("LIMIT 1");
+
+ // 判断排序方式是否为上升
+ if(sort == 0) {
+ // 限制查询条件为排序值小于要排序部门的排序值,并按排序值降序排序
+ wrapper.lambda()
+ .lt(SysDepart::getSort, depart.getSort())
+ .orderByDesc(SysDepart::getSort);
+ // 获取满足条件的部门实体对象
+ exchange = this.getOne(wrapper, false);
+ }
+
+ // 判断排序方式是否为下降
+ if(sort == 1) {
+ // 限制查询条件为排序值大于要排序部门的排序值,并按排序值升序排序
+ wrapper.lambda()
+ .gt(SysDepart::getSort, depart.getSort())
+ .orderByAsc(SysDepart::getSort);
+ // 获取满足条件的部门实体对象
+ exchange = this.getOne(wrapper, false);
+ }
+
+ // 判断是否找到可交换排序的部门实体对象
+ if(exchange != null) {
+ // 创建一个新的部门实体对象,用于更新要排序的部门的排序值
+ SysDepart a = new SysDepart();
+ a.setId(id);
+ a.setSort(exchange.getSort());
+ // 创建一个新的部门实体对象,用于更新可交换排序的部门的排序值
+ SysDepart b = new SysDepart();
+ b.setId(exchange.getId());
+ b.setSort(depart.getSort());
+ // 调用 MyBatis-Plus 的更新方法,更新两个部门的排序值
+ this.updateById(a);
+ this.updateById(b);
+ }
+ }
+
+ /**
+ * 填充部门编码,根据父部门信息和同级部门排序生成新的部门编码
+ * @param reqDTO 部门信息数据传输对象,用于填充部门编码
+ */
+ private void fillCode(SysDepartDTO reqDTO) {
+ // 部门编码的前缀
+ String code = "";
+
+ // 判断传入的父部门 ID 是否不为空且不为根部门 ID
+ if(StringUtils.isNotBlank(reqDTO.getParentId())
+ && !ROOT_TAG.equals(reqDTO.getParentId())) {
+ // 根据父部门 ID 获取父部门实体对象
+ SysDepart parent = this.getById(reqDTO.getParentId());
+ // 将父部门的编码作为前缀
+ code = parent.getDeptCode();
+ }
+
+ // 创建查询条件构造器
+ QueryWrapper wrapper = new QueryWrapper<>();
+ // 限制查询条件为与要填充编码的部门同级(父部门相同),并按排序值降序排序
+ wrapper.lambda()
+ .eq(SysDepart::getParentId, reqDTO.getParentId())
+ .orderByDesc(SysDepart::getSort);
+ // 限制查询结果只返回一条记录
+ wrapper.last("LIMIT 1");
+ // 获取满足条件的部门实体对象
+ SysDepart depart = this.getOne(wrapper, false);
+
+ // 判断是否找到同级部门实体对象
+ if(depart != null) {
+ // 生成新的部门编码,将同级部门的排序值加 1 并格式化后添加到前缀后面
+ code += this.formatCode(depart.getSort() + 1);
+ // 设置要填充编码的部门的排序值为同级部门的排序值加 1
+ reqDTO.setSort(depart.getSort() + 1);
+ } else {
+ // 若未找到同级部门实体对象,则生成新的部门编码,排序值为 1 并格式化后添加到前缀后面
+ code += this.formatCode(1);
+ // 设置要填充编码的部门的排序值为 1
+ reqDTO.setSort(1);
+ }
+
+ // 设置要填充编码的部门的部门编码
+ reqDTO.setDeptCode(code);
+ }
+
+ /**
+ * 格式化排序值,根据排序值生成对应的部门编码前缀
+ * @param sort 排序值
+ * @return 格式化后的部门编码前缀
+ */
+ private String formatCode(Integer sort) {
+ // 判断排序值是否小于 10
+ if(sort < 10) {
+ // 若小于 10,则在排序值前添加 "A0"
+ return "A0" + sort;
+ }
+ // 若不小于 10,则在排序值前添加 "A"
+ return "A" + sort;
+ }
+
+ /**
+ * 递归填充部门的子部门信息
+ * @param map 存储每个父部门 ID 对应的子部门列表的映射
+ * @param item 当前要填充子部门信息的部门树状结构数据传输对象
+ */
+ private void fillChildren(Map> map, SysDepartTreeDTO item) {
+ // 判断映射中是否存在该部门 ID 对应的子部门列表
+ if(map.containsKey(item.getId())) {
+ // 获取该部门 ID 对应的子部门列表
+ List children = map.get(item.getId());
+ // 判断子部门列表是否不为空
+ if(!CollectionUtils.isEmpty(children)) {
+ // 遍历子部门列表
+ for(SysDepartTreeDTO sub: children) {
+ // 递归填充每个子部门的子部门信息
+ this.fillChildren(map, sub);
+ }
+ }
+ // 设置该部门的子部门列表
+ item.setChildren(children);
+ }
+ }
+
+ /**
+ * 获取指定部门及其所有子部门的 ID 列表
+ * @param id 要获取子部门 ID 列表的部门 ID
+ * @return 包含指定部门及其所有子部门 ID 的列表
+ */
+ @Override
+ public List listAllSubIds( String id) {
+ // 存储指定部门及其所有子部门 ID 的列表
+ List ids = new ArrayList<>();
+ // 递归获取指定部门及其所有子部门的 ID 并添加到列表中
+ this.cycleAllSubs(ids, id);
+ return ids;
+ }
+
+ /**
+ * 递归获取指定部门及其所有子部门的 ID 并添加到列表中
+ * @param list 存储部门 ID 的列表
+ * @param id 当前要处理的部门 ID
+ */
+ private void cycleAllSubs(List list, String id) {
+ // 将当前部门 ID 添加到列表中
+ list.add(id);
+
+ // 创建查询条件构造器
+ QueryWrapper wrapper = new QueryWrapper<>();
+ // 限制查询条件为父部门 ID 等于当前部门 ID,并按排序值降序排序
+ wrapper.lambda()
+ .eq(SysDepart::getParentId, id)
+ .orderByDesc(SysDepart::getSort);
+ // 获取满足条件的子部门列表
+ List subList = this.list(wrapper);
+ // 判断子部门列表是否不为空
+ if(!CollectionUtils.isEmpty(subList)) {
+ // 遍历子部门列表
+ for(SysDepart item: subList) {
+ // 递归处理每个子部门
+ this.cycleAllSubs(list, item.getId());
+ }
+ }
+ }
+
+ /**
+ * 递归获取指定部门及其所有父部门的 ID 并添加到列表中
+ * @param list 存储部门 ID 的列表
+ * @param id 当前要处理的部门 ID
+ */
+ private void cycleAllParent(List list, String id) {
+ // 将当前部门 ID 添加到列表中
+ list.add(id);
+ // 根据部门 ID 获取部门实体对象
+ SysDepart depart = this.getById(id);
+
+ // 判断该部门的父部门 ID 是否不为空且不为根部门 ID
+ if(StringUtils.isNotBlank(depart.getParentId())
+ && !ROOT_TAG.equals(depart.getParentId())) {
+ // 递归处理该部门的父部门
+ this.cycleAllParent(list, depart.getParentId());
+ }
+ }
+}
diff --git a/sys/system/mapper/SysDictMapper.java b/sys/system/mapper/SysDictMapper.java
new file mode 100644
index 0000000..a5c92ab
--- /dev/null
+++ b/sys/system/mapper/SysDictMapper.java
@@ -0,0 +1,40 @@
+/**
+ * 定义包名,表明该类所属的模块和目录结构,这里是系统模块下的 mapper 包。
+ */
+package com.yf.exam.modules.sys.system.mapper;
+
+/**
+ * 导入 MyBatis 的 Mapper 注解,用于标识该接口是一个 MyBatis 的映射器接口。
+ */
+import org.apache.ibatis.annotations.Mapper;
+/**
+ * 导入 MyBatis 的 Param 注解,用于为方法参数指定名称,在 SQL 语句中可以使用该名称引用参数。
+ */
+import org.apache.ibatis.annotations.Param;
+
+/**
+ *
+ * 机主信息Mapper,用于与数据库中机主信息相关表进行交互,定义数据库操作方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-08-22 13:46
+ */
+// 标识该接口是 MyBatis 的映射器接口,Spring 会自动扫描并将其注册到 MyBatis 中。
+@Mapper
+public interface SysDictMapper {
+
+ /**
+ * 查找数据字典,根据传入的表名、文本、键和值在数据库中查找对应的字典数据。
+ *
+ * @param table 要查询的数据库表名
+ * @param text 用于查询的文本条件
+ * @param key 用于查询的键条件
+ * @param value 用于查询的值条件
+ * @return 返回查询到的字典数据,如果未找到则返回 null。
+ */
+ String findDict(@Param("table") String table,
+ @Param("text") String text,
+ @Param("key") String key,
+ @Param("value") String value);
+}
diff --git a/sys/system/service/SysDictService.java b/sys/system/service/SysDictService.java
new file mode 100644
index 0000000..ae6d0ce
--- /dev/null
+++ b/sys/system/service/SysDictService.java
@@ -0,0 +1,26 @@
+/**
+ * 定义包名,表明该类所属的模块和目录结构,此包下存放系统相关的服务类
+ */
+package com.yf.exam.modules.sys.system.service;
+
+/**
+ * 数据字典工具类
+ * 该接口定义了与数据字典查找相关的方法,可被不同的实现类实现以提供具体的查找逻辑。
+ * @author bool
+ */
+public interface SysDictService {
+
+ /**
+ * 查找数据字典
+ * 该方法用于根据传入的表名、文本、键和值,在对应的数据字典中查找匹配的结果。
+ * @param table 数据字典所在的表名,指定从哪个表中进行查找操作。
+ * @param text 查找时匹配的文本信息,可用于筛选符合条件的数据。
+ * @param key 数据字典中的键,用于定位特定的数据项。
+ * @param value 数据字典中的值,与键配合使用,进一步精确查找。
+ * @return 返回查找到的数据字典值,如果未找到则可能返回 null。
+ */
+ String findDict(String table,
+ String text,
+ String key,
+ String value);
+}
diff --git a/sys/system/service/impl/SysDictServiceImpl.java b/sys/system/service/impl/SysDictServiceImpl.java
new file mode 100644
index 0000000..d7771a4
--- /dev/null
+++ b/sys/system/service/impl/SysDictServiceImpl.java
@@ -0,0 +1,45 @@
+// 定义包名,指定该类所在的包路径
+package com.yf.exam.modules.sys.system.service.impl;
+
+// 导入系统字典映射器接口,用于与数据库进行交互
+import com.yf.exam.modules.sys.system.mapper.SysDictMapper;
+// 导入系统字典服务接口,定义了系统字典相关的业务方法
+import com.yf.exam.modules.sys.system.service.SysDictService;
+// 导入 Spring 框架的自动装配注解,用于依赖注入
+import org.springframework.beans.factory.annotation.Autowired;
+// 导入 Spring 框架的服务注解,将该类标记为服务层组件
+import org.springframework.stereotype.Service;
+
+/**
+ * SysDictServiceImpl 类实现了 SysDictService 接口,
+ * 负责处理系统字典相关的业务逻辑,通过调用 SysDictMapper 与数据库进行交互。
+ *
+ * @author bool
+ */
+// 将该类标记为 Spring 服务组件,使其可以被 Spring 容器管理
+@Service
+public class SysDictServiceImpl implements SysDictService {
+
+ /**
+ * 注入系统字典映射器实例,用于执行数据库操作。
+ * 通过 Spring 的自动装配机制,将 SysDictMapper 的实例注入到该类中。
+ */
+ @Autowired
+ private SysDictMapper sysDictMapper;
+
+ /**
+ * 根据传入的表名、文本、键和值,从数据库中查找对应的字典数据。
+ * 该方法调用了 SysDictMapper 的 findDict 方法来执行实际的数据库查询操作。
+ *
+ * @param table 要查询的数据库表名
+ * @param text 用于查询的文本条件
+ * @param key 用于查询的键条件
+ * @param value 用于查询的值条件
+ * @return 返回从数据库中查询到的字典数据,如果未找到则返回 null
+ */
+ @Override
+ public String findDict(String table, String text, String key, String value) {
+ // 调用 SysDictMapper 的 findDict 方法执行数据库查询
+ return sysDictMapper.findDict(table, text, key, value);
+ }
+}
diff --git a/sys/user/controller/SysRoleController.java b/sys/user/controller/SysRoleController.java
new file mode 100644
index 0000000..d2932af
--- /dev/null
+++ b/sys/user/controller/SysRoleController.java
@@ -0,0 +1,106 @@
+// 声明当前类所在的包,表明该类属于系统用户控制器模块
+package com.yf.exam.modules.sys.user.controller;
+
+// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入自定义的 API 统一响应类,用于封装接口返回数据
+import com.yf.exam.core.api.ApiRest;
+// 导入自定义的基础控制器类,提供一些通用的控制器方法
+import com.yf.exam.core.api.controller.BaseController;
+// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入自定义的 Bean 映射工具类,用于对象属性的复制
+import com.yf.exam.core.utils.BeanMapper;
+// 导入系统角色数据传输对象类,用于在不同层之间传输角色信息
+import com.yf.exam.modules.sys.user.dto.SysRoleDTO;
+// 导入系统角色实体类,对应数据库中的角色表
+import com.yf.exam.modules.sys.user.entity.SysRole;
+// 导入系统角色服务接口,用于调用角色相关的业务逻辑
+import com.yf.exam.modules.sys.user.service.SysRoleService;
+// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签
+import io.swagger.annotations.Api;
+// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述
+import io.swagger.annotations.ApiOperation;
+// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口
+import org.apache.shiro.authz.annotation.RequiresRoles;
+// 导入 Spring 框架的依赖注入注解,用于自动注入依赖的 Bean
+import org.springframework.beans.factory.annotation.Autowired;
+// 导入 Spring 框架的请求体注解,用于将请求体中的数据绑定到方法参数上
+import org.springframework.web.bind.annotation.RequestBody;
+// 导入 Spring 框架的请求映射注解,用于映射请求路径
+import org.springframework.web.bind.annotation.RequestMapping;
+// 导入 Spring 框架的请求方法注解,用于指定请求的 HTTP 方法
+import org.springframework.web.bind.annotation.RequestMethod;
+// 导入 Spring 框架的控制器注解,标记该类为 RESTful 风格的控制器
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ *
+ * 管理用户控制器,处理与系统角色相关的接口请求
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 为 Swagger 文档标记该控制器的标签为 "管理用户"
+@Api(tags = {"管理用户"})
+// 标记该类为 RESTful 风格的控制器,返回数据直接作为 HTTP 响应体
+@RestController
+// 定义该控制器处理的请求路径前缀
+@RequestMapping("/exam/api/sys/role")
+public class SysRoleController extends BaseController {
+
+ // 自动注入系统角色服务实例,用于调用角色相关的业务逻辑
+ @Autowired
+ private SysRoleService baseService;
+
+ /**
+ * 分页查找系统角色信息
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数
+ * @return 封装了分页查询结果的 API 统一响应对象
+ */
+ // 要求用户具有 "sa" 角色才能访问该接口
+ @RequiresRoles("sa")
+ // 为 Swagger 文档标记该接口的操作描述为 "分页查找"
+ @ApiOperation(value = "分页查找")
+ // 映射请求路径为 "/paging",并指定请求方法为 POST
+ @RequestMapping(value = "/paging", method = { RequestMethod.POST})
+ public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) {
+
+ // 调用角色服务的分页查询方法,进行分页查询并将结果转换为角色数据传输对象
+ IPage page = baseService.paging(reqDTO);
+ // 调用父类的成功响应方法,返回包含分页结果的响应对象
+ return super.success(page);
+ }
+
+ /**
+ * 查找系统角色列表,每次最多返回 200 条数据
+ *
+ * @return 封装了角色列表信息的 API 统一响应对象
+ */
+ // 要求用户具有 "sa" 角色才能访问该接口
+ @RequiresRoles("sa")
+ // 为 Swagger 文档标记该接口的操作描述为 "查找列表"
+ @ApiOperation(value = "查找列表")
+ // 映射请求路径为 "/list",并指定请求方法为 POST
+ @RequestMapping(value = "/list", method = { RequestMethod.POST})
+ public ApiRest> list() {
+
+ // 创建一个 MyBatis-Plus 的查询条件构造器,用于构建查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ // 调用角色服务的列表查询方法,根据查询条件获取角色实体列表
+ List list = baseService.list(wrapper);
+
+ // 使用 Bean 映射工具类将角色实体列表转换为角色数据传输对象列表
+ List dtoList = BeanMapper.mapList(list, SysRoleDTO.class);
+
+ // 调用父类的成功响应方法,返回包含角色列表的响应对象
+ return super.success(dtoList);
+ }
+}
diff --git a/sys/user/controller/SysUserController.java b/sys/user/controller/SysUserController.java
new file mode 100644
index 0000000..7b989ad
--- /dev/null
+++ b/sys/user/controller/SysUserController.java
@@ -0,0 +1,245 @@
+// 声明当前类所在的包,该包属于系统用户控制器模块
+package com.yf.exam.modules.sys.user.controller;
+
+// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于处理分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入自定义的 API 统一响应类,用于封装接口返回数据
+import com.yf.exam.core.api.ApiRest;
+// 导入自定义的基础控制器类,提供一些通用的控制器方法
+import com.yf.exam.core.api.controller.BaseController;
+// 导入自定义的基础多个 ID 请求数据传输对象类,用于批量操作时传递 ID 列表
+import com.yf.exam.core.api.dto.BaseIdsReqDTO;
+// 导入自定义的基础状态请求数据传输对象类,用于传递状态修改请求
+import com.yf.exam.core.api.dto.BaseStateReqDTO;
+// 导入自定义的分页请求数据传输对象类,用于分页查询时传递请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入系统用户数据传输对象类,用于在不同层之间传输用户信息
+import com.yf.exam.modules.sys.user.dto.SysUserDTO;
+// 导入系统用户登录请求数据传输对象类,用于用户登录时传递请求信息
+import com.yf.exam.modules.sys.user.dto.request.SysUserLoginReqDTO;
+// 导入系统用户保存请求数据传输对象类,用于保存或修改用户信息时传递请求信息
+import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO;
+// 导入系统用户登录响应数据传输对象类,用于用户登录成功后返回响应信息
+import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
+// 导入系统用户实体类,对应数据库中的用户表
+import com.yf.exam.modules.sys.user.entity.SysUser;
+// 导入系统用户服务接口,用于调用用户相关的业务逻辑
+import com.yf.exam.modules.sys.user.service.SysUserService;
+// 导入 Swagger 注解,用于生成 API 文档,标记控制器的标签
+import io.swagger.annotations.Api;
+// 导入 Swagger 注解,用于生成 API 文档,标记接口的操作描述
+import io.swagger.annotations.ApiOperation;
+// 导入 Shiro 注解,用于权限控制,要求用户具有指定角色才能访问接口
+import org.apache.shiro.authz.annotation.RequiresRoles;
+// 导入 Spring 框架的依赖注入注解,用于自动注入依赖的 Bean
+import org.springframework.beans.factory.annotation.Autowired;
+// 导入 Spring 框架的跨域请求注解,允许跨域访问该接口
+import org.springframework.web.bind.annotation.CrossOrigin;
+// 导入 Spring 框架的请求体注解,用于将请求体中的数据绑定到方法参数上
+import org.springframework.web.bind.annotation.RequestBody;
+// 导入 Spring 框架的请求映射注解,用于映射请求路径
+import org.springframework.web.bind.annotation.RequestMapping;
+// 导入 Spring 框架的请求方法注解,用于指定请求的 HTTP 方法
+import org.springframework.web.bind.annotation.RequestMethod;
+// 导入 Spring 框架的请求参数注解,用于获取请求参数
+import org.springframework.web.bind.annotation.RequestParam;
+// 导入 Spring 框架的控制器注解,标记该类为 RESTful 风格的控制器
+import org.springframework.web.bind.annotation.RestController;
+
+// 导入 HttpServletRequest 类,用于获取 HTTP 请求信息
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *
+ * 管理用户控制器,处理与系统用户相关的各种请求,如登录、登出、修改资料等操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 为 Swagger 文档标记该控制器的标签为 "管理用户"
+@Api(tags = {"管理用户"})
+// 标记该类为 RESTful 风格的控制器,返回数据直接作为 HTTP 响应体
+@RestController
+// 定义该控制器处理的请求路径前缀
+@RequestMapping("/exam/api/sys/user")
+public class SysUserController extends BaseController {
+
+ // 自动注入系统用户服务实例,用于调用用户相关的业务逻辑
+ @Autowired
+ private SysUserService baseService;
+
+ /**
+ * 用户登录接口,接收用户登录请求信息,调用服务层方法进行登录验证,并返回登录结果。
+ *
+ * @param reqDTO 包含用户登录信息的请求数据传输对象
+ * @return 封装了用户登录响应信息的 API 统一响应对象
+ */
+ @CrossOrigin
+ @ApiOperation(value = "用户登录")
+ @RequestMapping(value = "/login", method = {RequestMethod.POST})
+ public ApiRest login(@RequestBody SysUserLoginReqDTO reqDTO) {
+ // 调用服务层的登录方法,传入用户名和密码,获取登录响应信息
+ SysUserLoginDTO respDTO = baseService.login(reqDTO.getUsername(), reqDTO.getPassword());
+ // 调用父类的成功响应方法,返回包含登录响应信息的响应对象
+ return super.success(respDTO);
+ }
+
+ /**
+ * 用户登出接口,接收 HTTP 请求,从请求头中获取 token,调用服务层方法进行登出操作,并返回操作结果。
+ *
+ * @param request HTTP 请求对象,用于获取请求头中的 token
+ * @return 封装了登出操作结果的 API 统一响应对象
+ */
+ @CrossOrigin
+ @ApiOperation(value = "用户登录")
+ @RequestMapping(value = "/logout", method = {RequestMethod.POST})
+ public ApiRest logout(HttpServletRequest request) {
+ // 从请求头中获取 token
+ String token = request.getHeader("token");
+ // 打印当前会话的 token 信息
+ System.out.println("+++++当前会话为:"+token);
+ // 调用服务层的登出方法,传入 token 进行登出操作
+ baseService.logout(token);
+ // 调用父类的成功响应方法,返回操作成功的响应对象
+ return super.success();
+ }
+
+ /**
+ * 获取会话信息接口,接收 token 参数,调用服务层方法根据 token 获取用户会话信息,并返回会话信息。
+ *
+ * @param token 用户的 token,用于验证用户身份并获取会话信息
+ * @return 封装了用户会话信息的 API 统一响应对象
+ */
+ @ApiOperation(value = "获取会话")
+ @RequestMapping(value = "/info", method = {RequestMethod.POST})
+ public ApiRest info(@RequestParam("token") String token) {
+ // 调用服务层的 token 方法,传入 token 获取用户会话信息
+ SysUserLoginDTO respDTO = baseService.token(token);
+ // 调用父类的成功响应方法,返回包含会话信息的响应对象
+ return success(respDTO);
+ }
+
+ /**
+ * 修改用户资料接口,接收用户资料修改请求信息,调用服务层方法进行用户资料修改,并返回操作结果。
+ *
+ * @param reqDTO 包含用户资料修改信息的请求数据传输对象
+ * @return 封装了用户资料修改操作结果的 API 统一响应对象
+ */
+ @ApiOperation(value = "修改用户资料")
+ @RequestMapping(value = "/update", method = {RequestMethod.POST})
+ public ApiRest update(@RequestBody SysUserDTO reqDTO) {
+ // 调用服务层的更新方法,传入用户资料修改信息进行用户资料修改
+ baseService.update(reqDTO);
+ // 调用父类的成功响应方法,返回操作成功的响应对象
+ return success();
+ }
+
+ /**
+ * 保存或修改系统用户接口,要求用户具有 "sa" 角色,接收用户保存或修改请求信息,调用服务层方法进行用户保存或修改操作,并返回操作结果。
+ *
+ * @param reqDTO 包含用户保存或修改信息的请求数据传输对象
+ * @return 封装了用户保存或修改操作结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "保存或修改")
+ @RequestMapping(value = "/save", method = {RequestMethod.POST})
+ public ApiRest save(@RequestBody SysUserSaveReqDTO reqDTO) {
+ // 调用服务层的保存方法,传入用户保存或修改信息进行用户保存或修改操作
+ baseService.save(reqDTO);
+ // 调用父类的成功响应方法,返回操作成功的响应对象
+ return success();
+ }
+
+ /**
+ * 批量删除用户接口,要求用户具有 "sa" 角色,接收包含多个用户 ID 的请求信息,调用服务层方法根据 ID 批量删除用户,并返回操作结果。
+ *
+ * @param reqDTO 包含多个用户 ID 的请求数据传输对象,用于指定要删除的用户
+ * @return 封装了批量删除用户操作结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "批量删除")
+ @RequestMapping(value = "/delete", method = { RequestMethod.POST})
+ public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) {
+ // 根据传入的用户 ID 列表,调用服务层的删除方法批量删除用户
+ baseService.removeByIds(reqDTO.getIds());
+ // 调用父类的成功响应方法,返回操作成功的响应对象
+ return super.success();
+ }
+
+ /**
+ * 分页查找用户接口,要求用户具有 "sa" 角色,接收分页请求信息,调用服务层方法进行分页查询,并返回分页查询结果。
+ *
+ * @param reqDTO 包含分页查询条件和参数的请求数据传输对象
+ * @return 封装了分页查询结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "分页查找")
+ @RequestMapping(value = "/paging", method = { RequestMethod.POST})
+ public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) {
+ // 调用服务层的分页查询方法,传入分页请求信息进行分页查询并转换结果
+ IPage page = baseService.paging(reqDTO);
+ // 调用父类的成功响应方法,返回包含分页查询结果的响应对象
+ return super.success(page);
+ }
+
+ /**
+ * 修改用户状态接口,要求用户具有 "sa" 角色,接收用户状态修改请求信息,构建查询条件,调用服务层方法修改用户状态,并返回操作结果。
+ *
+ * @param reqDTO 包含用户状态修改信息的请求数据传输对象
+ * @return 封装了用户状态修改操作结果的 API 统一响应对象
+ */
+ @RequiresRoles("sa")
+ @ApiOperation(value = "修改状态")
+ @RequestMapping(value = "/state", method = { RequestMethod.POST})
+ public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) {
+ // 创建 MyBatis-Plus 的查询条件构造器,用于构建查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ // 使用 Lambda 表达式构建查询条件,筛选出 ID 在请求 ID 列表中且用户名不为 "admin" 的用户
+ wrapper.lambda()
+ .in(SysUser::getId, reqDTO.getIds())
+ .ne(SysUser::getUserName, "admin");
+
+ // 创建一个新的用户实体对象,用于设置要修改的状态
+ SysUser record = new SysUser();
+ // 设置用户状态为请求中的状态
+ record.setState(reqDTO.getState());
+ // 调用服务层的更新方法,根据查询条件更新用户状态
+ baseService.update(record, wrapper);
+
+ // 调用父类的成功响应方法,返回操作成功的响应对象
+ return super.success();
+ }
+
+ /**
+ * 学员注册接口,接收学员注册请求信息,调用服务层方法进行学员注册,并返回注册结果。
+ *
+ * @param reqDTO 包含学员注册信息的请求数据传输对象
+ * @return 封装了学员注册响应信息的 API 统一响应对象
+ */
+ @ApiOperation(value = "学员注册")
+ @RequestMapping(value = "/reg", method = {RequestMethod.POST})
+ public ApiRest reg(@RequestBody SysUserDTO reqDTO) {
+ // 调用服务层的注册方法,传入学员注册信息进行学员注册,获取注册响应信息
+ SysUserLoginDTO respDTO = baseService.reg(reqDTO);
+ // 调用父类的成功响应方法,返回包含注册响应信息的响应对象
+ return success(respDTO);
+ }
+
+ /**
+ * 快速注册接口,接收用户信息,调用服务层方法进行快速注册,如果手机号存在则登录,不存在就注册,并返回操作结果。
+ *
+ * @param reqDTO 包含用户信息的请求数据传输对象
+ * @return 封装了快速注册响应信息的 API 统一响应对象
+ */
+ @ApiOperation(value = "快速注册")
+ @RequestMapping(value = "/quick-reg", method = {RequestMethod.POST})
+ public ApiRest quick(@RequestBody SysUserDTO reqDTO) {
+ // 调用服务层的快速注册方法,传入用户信息进行快速注册,获取注册或登录响应信息
+ SysUserLoginDTO respDTO = baseService.quickReg(reqDTO);
+ // 调用父类的成功响应方法,返回包含注册或登录响应信息的响应对象
+ return success(respDTO);
+ }
+}
diff --git a/sys/user/dto/SysRoleDTO.java b/sys/user/dto/SysRoleDTO.java
new file mode 100644
index 0000000..7cc31e4
--- /dev/null
+++ b/sys/user/dto/SysRoleDTO.java
@@ -0,0 +1,45 @@
+// 定义该类所在的包,表明其在项目模块中的位置
+package com.yf.exam.modules.sys.user.dto;
+
+// 导入 Swagger 注解,用于在生成 API 文档时描述类的信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于在生成 API 文档时描述类属性的信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 角色请求类,用于在不同层之间传输角色相关的数据,如在控制器与服务层、服务层与数据访问层之间传递数据。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息
+@ApiModel(value="角色", description="角色")
+public class SysRoleDTO implements Serializable {
+
+ // 序列化版本号,确保序列化和反序列化时类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 角色的唯一标识 ID
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供
+ */
+ @ApiModelProperty(value = "角色ID", required=true)
+ private String id;
+
+ /**
+ * 角色的名称
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供
+ */
+ @ApiModelProperty(value = "角色名称", required=true)
+ private String roleName;
+
+}
diff --git a/sys/user/dto/SysUserDTO.java b/sys/user/dto/SysUserDTO.java
new file mode 100644
index 0000000..8fc9ad0
--- /dev/null
+++ b/sys/user/dto/SysUserDTO.java
@@ -0,0 +1,108 @@
+/**
+ * 定义包名,表明该类属于系统用户模块下的数据传输对象包
+ */
+package com.yf.exam.modules.sys.user.dto;
+
+// 导入 Swagger 注解,用于为 API 文档提供类的描述信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于为 API 文档提供类属性的描述信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+// 导入 Serializable 接口,使该类的对象可以被序列化
+import java.io.Serializable;
+// 导入 Date 类,用于表示日期和时间
+import java.util.Date;
+
+/**
+ *
+ * 管理用户请求类,用于在不同层之间传输管理用户的相关信息,如从控制器传递到服务层,或从服务层传递到数据访问层。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息
+@ApiModel(value="管理用户", description="管理用户")
+public class SysUserDTO implements Serializable {
+
+ /**
+ * 序列化版本号,确保序列化和反序列化时类的版本一致性。
+ * 当类的结构发生变化时,可能需要更新该版本号以避免反序列化错误。
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 管理用户的唯一标识 ID。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 管理用户的用户名,用于系统登录和身份识别。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "用户名", required=true)
+ private String userName;
+
+ /**
+ * 管理用户的真实姓名,用于更准确地识别用户身份和展示信息。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "真实姓名", required=true)
+ private String realName;
+
+ /**
+ * 管理用户的登录密码,用于系统登录验证。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "密码", required=true)
+ private String password;
+
+ /**
+ * 密码盐,用于增强密码的安全性,通常与密码一起进行哈希处理。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "密码盐", required=true)
+ private String salt;
+
+ /**
+ * 管理用户所属的角色列表,以字符串形式存储角色 ID,多个 ID 之间可能用特定分隔符分隔。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "角色列表", required=true)
+ private String roleIds;
+
+ /**
+ * 管理用户所属部门的 ID,用于关联部门信息。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "部门ID", required=true)
+ private String departId;
+
+ /**
+ * 管理用户记录的创建时间,使用 Date 类型表示。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "创建时间", required=true)
+ private Date createTime;
+
+ /**
+ * 管理用户记录的更新时间,使用 Date 类型表示。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "更新时间", required=true)
+ private Date updateTime;
+
+ /**
+ * 管理用户的状态,使用整数类型表示不同的状态值,具体状态含义需根据业务逻辑定义。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "状态", required=true)
+ private Integer state;
+
+}
diff --git a/sys/user/dto/SysUserRoleDTO.java b/sys/user/dto/SysUserRoleDTO.java
new file mode 100644
index 0000000..99dd349
--- /dev/null
+++ b/sys/user/dto/SysUserRoleDTO.java
@@ -0,0 +1,55 @@
+// 定义包名,指定该类所属的模块和目录结构
+package com.yf.exam.modules.sys.user.dto;
+
+// 导入 Swagger 注解,用于为 API 文档定义模型信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于为 API 文档定义模型属性信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 用户角色请求类,用于在不同层之间传输用户角色相关的数据,如在控制器与服务层之间传递数据。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档定义该类的信息
+@ApiModel(value="用户角色", description="用户角色")
+public class SysUserRoleDTO implements Serializable {
+
+ /**
+ * 序列化版本号,确保序列化和反序列化时类的版本一致性。
+ * 当类的结构发生变化时,可能需要更新该版本号以避免反序列化错误。
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户角色的唯一标识 ID。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 关联的用户的唯一标识 ID。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "用户ID", required=true)
+ private String userId;
+
+ /**
+ * 关联的角色的唯一标识 ID。
+ * 在 API 文档中标记为必需项,表明在使用该数据传输对象时,该字段必须提供。
+ */
+ @ApiModelProperty(value = "角色ID", required=true)
+ private String roleId;
+
+}
diff --git a/sys/user/dto/request/SysUserLoginReqDTO.java b/sys/user/dto/request/SysUserLoginReqDTO.java
new file mode 100644
index 0000000..91e738a
--- /dev/null
+++ b/sys/user/dto/request/SysUserLoginReqDTO.java
@@ -0,0 +1,45 @@
+// 定义当前类所在的包,表明该类属于系统用户模块下的请求数据传输对象包
+package com.yf.exam.modules.sys.user.dto.request;
+
+// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+// 导入 Serializable 接口,表明该类的对象可以被序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 管理员登录请求类,用于封装管理员登录时所需的请求参数
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 为 Swagger 文档提供类的描述信息
+@ApiModel(value="管理员登录请求类", description="管理员登录请求类")
+public class SysUserLoginReqDTO implements Serializable {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 管理员登录使用的用户名
+ * 在 API 文档中标记为必需项,表明该参数在请求时必须提供
+ */
+ @ApiModelProperty(value = "用户名", required=true)
+ private String username;
+
+ /**
+ * 管理员登录使用的密码
+ * 在 API 文档中标记为必需项,表明该参数在请求时必须提供
+ */
+ @ApiModelProperty(value = "密码", required=true)
+ private String password;
+
+}
diff --git a/sys/user/dto/request/SysUserSaveReqDTO.java b/sys/user/dto/request/SysUserSaveReqDTO.java
new file mode 100644
index 0000000..5ce334c
--- /dev/null
+++ b/sys/user/dto/request/SysUserSaveReqDTO.java
@@ -0,0 +1,83 @@
+// 声明该类所在的包,明确其在项目模块中的位置
+package com.yf.exam.modules.sys.user.dto.request;
+
+// 导入 Swagger 注解,用于在生成 API 文档时描述类的信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于在生成 API 文档时描述类属性的信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+// 导入 Serializable 接口,使该类的对象可以进行序列化和反序列化操作
+import java.io.Serializable;
+// 导入 List 接口,用于存储角色 ID 列表
+import java.util.List;
+
+/**
+ *
+ * 管理员保存请求类,用于封装管理员信息保存操作所需的请求参数。
+ * 该类作为数据传输对象,在不同层之间传递管理员保存相关的数据。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档描述该类的信息
+@ApiModel(value="管理员保存请求类", description="管理员保存请求类")
+public class SysUserSaveReqDTO implements Serializable {
+
+ // 序列化版本号,确保序列化和反序列化时类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 管理员的唯一标识 ID
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 管理员的用户名,用于系统登录和身份识别
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "用户名", required=true)
+ private String userName;
+
+ /**
+ * 管理员的头像链接,用于在系统中展示管理员的头像
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "头像", required=true)
+ private String avatar;
+
+ /**
+ * 管理员的真实姓名,用于在系统中进行更准确的身份识别和展示
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "真实姓名", required=true)
+ private String realName;
+
+ /**
+ * 管理员的登录密码,用于系统登录验证
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "密码", required=true)
+ private String password;
+
+ /**
+ * 管理员所属部门的 ID,用于关联管理员所在的部门信息
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "部门", required=true)
+ private String departId;
+
+ /**
+ * 管理员拥有的角色列表,存储角色的 ID 集合
+ * 在 API 文档中标记为必需项,表明保存操作时该字段必须提供
+ */
+ @ApiModelProperty(value = "角色列表", required=true)
+ private List roles;
+
+}
diff --git a/sys/user/dto/request/SysUserTokenReqDTO.java b/sys/user/dto/request/SysUserTokenReqDTO.java
new file mode 100644
index 0000000..e945041
--- /dev/null
+++ b/sys/user/dto/request/SysUserTokenReqDTO.java
@@ -0,0 +1,38 @@
+// 定义包名,表明该类所属的模块和功能目录
+package com.yf.exam.modules.sys.user.dto.request;
+
+// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+// 导入 Serializable 接口,使该类的对象可以被序列化和反序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 会话检查请求类,用于封装会话检查所需的请求参数
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 为 Swagger 文档提供类的描述信息
+@ApiModel(value="会话检查请求类", description="会话检查请求类")
+public class SysUserTokenReqDTO implements Serializable {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用于会话检查的令牌
+ * 在 API 文档中标记为必需项,表明该参数在请求时必须提供
+ */
+ @ApiModelProperty(value = "用户名", required=true)
+ private String token;
+
+}
diff --git a/sys/user/dto/response/SysUserLoginDTO.java b/sys/user/dto/response/SysUserLoginDTO.java
new file mode 100644
index 0000000..244086d
--- /dev/null
+++ b/sys/user/dto/response/SysUserLoginDTO.java
@@ -0,0 +1,106 @@
+// 定义包名,表明该类属于系统用户模块下的响应数据传输对象包
+package com.yf.exam.modules.sys.user.dto.response;
+
+// 导入 Swagger 注解,用于生成 API 文档,标记类的描述信息
+import io.swagger.annotations.ApiModel;
+// 导入 Swagger 注解,用于生成 API 文档,标记类属性的描述信息
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+// 导入 Serializable 接口,表明该类的对象可以被序列化
+import java.io.Serializable;
+// 导入 Date 类,用于表示日期和时间
+import java.util.Date;
+// 导入 List 接口,用于存储角色列表
+import java.util.List;
+
+/**
+ *
+ * 管理用户登录响应类,用于封装管理用户登录成功后返回的信息。
+ * 该类作为数据传输对象,在不同层之间传递登录响应数据。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 为 Swagger 文档提供类的描述信息
+@ApiModel(value="管理用户登录响应类", description="管理用户登录响应类")
+public class SysUserLoginDTO implements Serializable {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 管理用户的唯一标识 ID
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 管理用户的用户名
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "用户名", required=true)
+ private String userName;
+
+ /**
+ * 管理用户的真实姓名
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "真实姓名", required=true)
+ private String realName;
+
+ /**
+ * 管理用户的角色 ID 列表,以字符串形式存储
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "角色列表", required=true)
+ private String roleIds;
+
+ /**
+ * 管理用户所属部门的 ID
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "部门ID", required=true)
+ private String departId;
+
+ /**
+ * 管理用户记录的创建时间
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "创建时间", required=true)
+ private Date createTime;
+
+ /**
+ * 管理用户记录的更新时间
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "更新时间", required=true)
+ private Date updateTime;
+
+ /**
+ * 管理用户的状态,使用整数表示
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "状态", required=true)
+ private Integer state;
+
+ /**
+ * 管理用户的角色列表,以字符串列表形式存储
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "角色列表", required=true)
+ private List roles;
+
+ /**
+ * 管理用户登录后生成的令牌,用于后续的身份验证
+ * 在 API 文档中标记为必需项,表明该字段在响应数据中必须存在
+ */
+ @ApiModelProperty(value = "登录令牌", required=true)
+ private String token;
+
+}
diff --git a/sys/user/entity/SysRole.java b/sys/user/entity/SysRole.java
new file mode 100644
index 0000000..8386f64
--- /dev/null
+++ b/sys/user/entity/SysRole.java
@@ -0,0 +1,54 @@
+/**
+ * 定义包名,指定该类所属的模块和实体类所在的目录结构
+ */
+package com.yf.exam.modules.sys.user.entity;
+
+// 导入 MyBatis-Plus 提供的主键生成策略枚举类
+import com.baomidou.mybatisplus.annotation.IdType;
+// 导入 MyBatis-Plus 提供的表字段注解,用于指定数据库表字段名
+import com.baomidou.mybatisplus.annotation.TableField;
+// 导入 MyBatis-Plus 提供的表主键注解,用于指定数据库表的主键
+import com.baomidou.mybatisplus.annotation.TableId;
+// 导入 MyBatis-Plus 提供的表名注解,用于指定实体类对应的数据库表名
+import com.baomidou.mybatisplus.annotation.TableName;
+// 导入 MyBatis-Plus 提供的 ActiveRecord 模式基类,方便进行数据库操作
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+// 导入 Lombok 提供的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+/**
+ *
+ * 角色实体类,用于映射数据库中的 sys_role 表,封装角色相关的属性。
+ * 该类继承自 MyBatis-Plus 的 Model 类,可使用 ActiveRecord 模式进行数据库操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法
+@Data
+// 指定该实体类对应的数据库表名为 sys_role
+@TableName("sys_role")
+public class SysRole extends Model {
+
+ /**
+ * 序列化版本号,用于在反序列化时验证版本一致性。
+ * 当实体类的结构发生变化时,可能需要更新该版本号以避免反序列化错误。
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 角色ID,对应数据库表中的 id 字段,作为表的主键。
+ * 使用 MyBatis-Plus 的 TableId 注解指定主键生成策略为 ASSIGN_ID,通常为分布式 ID 生成策略。
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 角色名称,对应数据库表中的 role_name 字段。
+ * 使用 MyBatis-Plus 的 TableField 注解明确指定数据库表字段名。
+ */
+ @TableField("role_name")
+ private String roleName;
+
+}
diff --git a/sys/user/entity/SysUser.java b/sys/user/entity/SysUser.java
new file mode 100644
index 0000000..e6fc698
--- /dev/null
+++ b/sys/user/entity/SysUser.java
@@ -0,0 +1,103 @@
+// 声明当前类所在的包,明确该类属于系统用户实体类所在的包
+package com.yf.exam.modules.sys.user.entity;
+
+// 导入 MyBatis-Plus 注解,用于指定主键生成策略
+import com.baomidou.mybatisplus.annotation.IdType;
+// 导入 MyBatis-Plus 注解,用于指定数据库表字段名
+import com.baomidou.mybatisplus.annotation.TableField;
+// 导入 MyBatis-Plus 注解,用于指定数据库表的主键
+import com.baomidou.mybatisplus.annotation.TableId;
+// 导入 MyBatis-Plus 注解,用于指定实体类对应的数据库表名
+import com.baomidou.mybatisplus.annotation.TableName;
+// 导入 MyBatis-Plus 扩展的 ActiveRecord 模式基类
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+// 导入 Lombok 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+// 导入 Date 类,用于表示日期和时间
+import java.util.Date;
+
+/**
+ *
+ * 管理用户实体类,用于映射数据库中的 sys_user 表,封装管理用户的相关属性。
+ * 该类继承自 MyBatis-Plus 的 Model 类,可使用 ActiveRecord 模式进行数据库操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 @Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+@Data
+// 指定该实体类对应的数据库表名为 sys_user
+@TableName("sys_user")
+public class SysUser extends Model {
+
+ // 序列化版本号,用于在反序列化时验证版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户的唯一标识 ID
+ * 使用 MyBatis-Plus 的 @TableId 注解指定该字段为主键,
+ * value 属性指定数据库表中的字段名,type 属性指定主键生成策略为 ASSIGN_ID
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 用户名,对应数据库表中的 user_name 字段
+ * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名
+ */
+ @TableField("user_name")
+ private String userName;
+
+ /**
+ * 真实姓名,对应数据库表中的 real_name 字段
+ * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名
+ */
+ @TableField("real_name")
+ private String realName;
+
+ /**
+ * 用户密码,未指定 @TableField 时,默认字段名与属性名相同
+ */
+ private String password;
+
+ /**
+ * 密码盐,用于增强密码的安全性,未指定 @TableField 时,默认字段名与属性名相同
+ */
+ private String salt;
+
+ /**
+ * 角色列表,以字符串形式存储角色 ID,对应数据库表中的 role_ids 字段
+ * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名
+ */
+ @TableField("role_ids")
+ private String roleIds;
+
+ /**
+ * 部门 ID,对应数据库表中的 depart_id 字段
+ * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名
+ */
+ @TableField("depart_id")
+ private String departId;
+
+ /**
+ * 用户记录的创建时间,对应数据库表中的 create_time 字段
+ * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名
+ */
+ @TableField("create_time")
+ private Date createTime;
+
+ /**
+ * 用户记录的更新时间,对应数据库表中的 update_time 字段
+ * 使用 MyBatis-Plus 的 @TableField 注解指定数据库表中的字段名
+ */
+ @TableField("update_time")
+ private Date updateTime;
+
+ /**
+ * 用户状态,未指定 @TableField 时,默认字段名与属性名相同
+ */
+ private Integer state;
+
+}
diff --git a/sys/user/entity/SysUserRole.java b/sys/user/entity/SysUserRole.java
new file mode 100644
index 0000000..fb33b77
--- /dev/null
+++ b/sys/user/entity/SysUserRole.java
@@ -0,0 +1,53 @@
+// 声明当前类所在的包,该包属于系统用户模块下的实体类包
+package com.yf.exam.modules.sys.user.entity;
+
+// 导入 MyBatis-Plus 提供的主键生成策略枚举类
+import com.baomidou.mybatisplus.annotation.IdType;
+// 导入 MyBatis-Plus 提供的表字段注解,用于指定数据库表字段名
+import com.baomidou.mybatisplus.annotation.TableField;
+// 导入 MyBatis-Plus 提供的表主键注解,用于指定数据库表的主键
+import com.baomidou.mybatisplus.annotation.TableId;
+// 导入 MyBatis-Plus 提供的表名注解,用于指定实体类对应的数据库表名
+import com.baomidou.mybatisplus.annotation.TableName;
+// 导入 MyBatis-Plus 提供的 ActiveRecord 模式基类,方便进行数据库操作
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+// 导入 Lombok 提供的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+
+/**
+ *
+ * 用户角色实体类,用于映射数据库中的 sys_user_role 表,存储用户与角色的关联信息。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用的 getter、setter 等方法
+@Data
+// 指定该实体类对应的数据库表名为 sys_user_role
+@TableName("sys_user_role")
+public class SysUserRole extends Model {
+
+ // 序列化版本号,确保序列化和反序列化时类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户角色关联记录的唯一标识 ID
+ * 使用 TableId 注解指定该字段为主键,采用 ASSIGN_ID 主键生成策略
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 关联的用户 ID,对应数据库表中的 user_id 字段
+ */
+ @TableField("user_id")
+ private String userId;
+
+ /**
+ * 关联的角色 ID,对应数据库表中的 role_id 字段
+ */
+ @TableField("role_id")
+ private String roleId;
+
+}
diff --git a/sys/user/mapper/SysRoleMapper.java b/sys/user/mapper/SysRoleMapper.java
new file mode 100644
index 0000000..af66098
--- /dev/null
+++ b/sys/user/mapper/SysRoleMapper.java
@@ -0,0 +1,32 @@
+/**
+ * 定义包名,表明该类属于系统用户模块下的映射器包。
+ * 包名的组织有助于代码的模块化管理和避免命名冲突。
+ */
+package com.yf.exam.modules.sys.user.mapper;
+
+/**
+ * 导入 MyBatis-Plus 核心的 BaseMapper 接口,
+ * 该接口提供了通用的数据库 CRUD 操作方法,
+ * 可以减少开发者编写重复的 SQL 映射代码。
+ */
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 导入系统角色实体类,该类用于封装角色相关的属性,
+ * 作为 Mapper 接口操作的对象类型。
+ */
+import com.yf.exam.modules.sys.user.entity.SysRole;
+
+/**
+ *
+ * 角色Mapper接口,继承自 MyBatis-Plus 的 BaseMapper 接口。
+ * 该接口用于定义与系统角色相关的数据库操作方法,
+ * 借助 BaseMapper 提供的通用方法,可以直接进行角色数据的增删改查操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+public interface SysRoleMapper extends BaseMapper {
+ // 目前该接口仅继承 BaseMapper 的通用方法,未自定义额外的数据库操作方法
+}
diff --git a/sys/user/mapper/SysUserMapper.java b/sys/user/mapper/SysUserMapper.java
new file mode 100644
index 0000000..7c81b34
--- /dev/null
+++ b/sys/user/mapper/SysUserMapper.java
@@ -0,0 +1,32 @@
+/**
+ * 声明该接口所在的包,用于组织代码结构,避免命名冲突。
+ * 此包属于系统用户模块下的映射器包。
+ */
+package com.yf.exam.modules.sys.user.mapper;
+
+/**
+ * 导入 MyBatis-Plus 框架的核心接口 BaseMapper。
+ * BaseMapper 提供了一系列通用的数据库 CRUD 操作方法,
+ * 继承该接口可以减少重复编写基础数据库操作代码的工作量。
+ */
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 导入系统用户实体类 SysUser。
+ * 该实体类用于封装系统用户的相关属性,
+ * 作为当前 Mapper 接口操作的对象类型。
+ */
+import com.yf.exam.modules.sys.user.entity.SysUser;
+
+/**
+ *
+ * 管理用户Mapper接口,用于与数据库中管理用户相关的数据表进行交互。
+ * 继承自 MyBatis-Plus 的 BaseMapper 接口,可直接使用其提供的通用数据库操作方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+public interface SysUserMapper extends BaseMapper {
+ // 目前该接口仅继承 BaseMapper 的通用方法,未自定义额外的数据库操作方法
+}
diff --git a/sys/user/mapper/SysUserRoleMapper.java b/sys/user/mapper/SysUserRoleMapper.java
new file mode 100644
index 0000000..38f9ac9
--- /dev/null
+++ b/sys/user/mapper/SysUserRoleMapper.java
@@ -0,0 +1,32 @@
+/**
+ * 定义包名,表明该类所属的模块和功能目录,这里是系统用户模块下的 mapper 目录。
+ */
+package com.yf.exam.modules.sys.user.mapper;
+
+/**
+ * 导入 MyBatis-Plus 框架的 BaseMapper 接口,
+ * 该接口提供了通用的数据库 CRUD 操作方法,
+ * 继承它可以让我们快速实现对数据库表的基本操作。
+ */
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 导入系统用户角色实体类,
+ * 该实体类用于封装用户角色的相关属性,
+ * 作为当前 Mapper 接口操作的对象类型。
+ */
+import com.yf.exam.modules.sys.user.entity.SysUserRole;
+
+/**
+ *
+ * 用户角色Mapper接口,继承自 MyBatis-Plus 的 BaseMapper 接口。
+ * 该接口用于与数据库中用户角色相关的数据表进行交互,
+ * 借助 BaseMapper 提供的通用方法,可以直接进行用户角色数据的增删改查操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+public interface SysUserRoleMapper extends BaseMapper {
+ // 目前该接口仅继承 BaseMapper 的通用方法,未自定义额外的数据库操作方法
+}
diff --git a/sys/user/service/SysRoleService.java b/sys/user/service/SysRoleService.java
new file mode 100644
index 0000000..4855f8f
--- /dev/null
+++ b/sys/user/service/SysRoleService.java
@@ -0,0 +1,52 @@
+/**
+ * 定义包名,表明该接口所属的模块和服务层目录结构。
+ * 此包路径用于组织系统用户模块下的服务接口类。
+ */
+package com.yf.exam.modules.sys.user.service;
+
+/**
+ * 导入 MyBatis-Plus 框架的分页元数据接口,
+ * 该接口用于表示分页查询的结果,包含总记录数、当前页码等信息。
+ */
+import com.baomidou.mybatisplus.core.metadata.IPage;
+/**
+ * 导入 MyBatis-Plus 框架的扩展服务接口,
+ * 该接口提供了通用的 CRUD 操作方法,可减少重复代码编写。
+ */
+import com.baomidou.mybatisplus.extension.service.IService;
+/**
+ * 导入系统角色数据传输对象类,
+ * 用于在不同层之间传输角色相关的数据,避免直接暴露实体类。
+ */
+import com.yf.exam.modules.sys.user.dto.SysRoleDTO;
+/**
+ * 导入系统角色实体类,
+ * 该类用于映射数据库中的角色表,封装角色的属性信息。
+ */
+import com.yf.exam.modules.sys.user.entity.SysRole;
+/**
+ * 导入自定义的分页请求数据传输对象类,
+ * 用于封装分页查询的请求参数,如当前页码、每页记录数等。
+ */
+import com.yf.exam.core.api.dto.PagingReqDTO;
+
+/**
+ *
+ * 角色业务类接口,定义了与系统角色相关的业务方法。
+ * 继承自 MyBatis-Plus 的 IService 接口,可使用其提供的通用服务方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+public interface SysRoleService extends IService {
+
+ /**
+ * 分页查询角色数据。
+ * 根据传入的分页请求参数,从数据库中查询角色数据并进行分页处理。
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数。
+ * @return 分页查询结果,包含符合条件的角色数据传输对象列表。
+ */
+ IPage paging(PagingReqDTO reqDTO);
+}
diff --git a/sys/user/service/SysUserRoleService.java b/sys/user/service/SysUserRoleService.java
new file mode 100644
index 0000000..774b663
--- /dev/null
+++ b/sys/user/service/SysUserRoleService.java
@@ -0,0 +1,94 @@
+/**
+ * 定义包名,明确该接口所属的模块和服务层目录,用于组织系统用户角色相关的服务类。
+ */
+package com.yf.exam.modules.sys.user.service;
+
+/**
+ * 导入 MyBatis-Plus 框架的分页元数据接口,用于表示分页查询结果,包含总记录数、当前页码等信息。
+ */
+import com.baomidou.mybatisplus.core.metadata.IPage;
+/**
+ * 导入 MyBatis-Plus 框架的扩展服务接口,提供通用的 CRUD 操作方法,减少重复代码编写。
+ */
+import com.baomidou.mybatisplus.extension.service.IService;
+/**
+ * 导入系统用户角色数据传输对象类,用于在不同层之间传输用户角色相关的数据,避免直接暴露实体类。
+ */
+import com.yf.exam.modules.sys.user.dto.SysUserRoleDTO;
+/**
+ * 导入系统用户角色实体类,用于映射数据库中的用户角色表,封装用户角色的属性信息。
+ */
+import com.yf.exam.modules.sys.user.entity.SysUserRole;
+/**
+ * 导入自定义的分页请求数据传输对象类,用于封装分页查询的请求参数,如当前页码、每页记录数等。
+ */
+import com.yf.exam.core.api.dto.PagingReqDTO;
+
+import java.util.List;
+
+/**
+ *
+ * 用户角色业务类接口,定义了与系统用户角色相关的业务方法。
+ * 继承自 MyBatis-Plus 的 IService 接口,可使用其提供的通用服务方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+public interface SysUserRoleService extends IService {
+
+ /**
+ * 分页查询用户角色数据。
+ * 根据传入的分页请求参数,从数据库中查询用户角色数据并进行分页处理。
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数。
+ * @return 分页查询结果,包含符合条件的用户角色数据传输对象列表。
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 查找指定用户的角色列表。
+ * 根据用户 ID 从数据库中查询该用户关联的所有角色 ID。
+ *
+ * @param userId 用户的唯一标识。
+ * @return 该用户关联的角色 ID 列表。
+ */
+ List listRoles(String userId);
+
+ /**
+ * 保存指定用户的全部角色。
+ * 先删除该用户原有的所有角色关联,再添加新的角色关联。
+ *
+ * @param userId 用户的唯一标识。
+ * @param ids 要保存的角色 ID 列表。
+ * @return 保存操作完成后,返回保存的角色 ID 相关信息(具体格式由实现类定义)。
+ */
+ String saveRoles(String userId, List ids);
+
+ /**
+ * 判断指定用户是否为学生角色。
+ * 根据用户 ID 检查该用户是否关联了学生角色。
+ *
+ * @param userId 用户的唯一标识。
+ * @return 如果用户是学生角色返回 true,否则返回 false。
+ */
+ boolean isStudent(String userId);
+
+ /**
+ * 判断指定用户是否为老师角色。
+ * 根据用户 ID 检查该用户是否关联了老师角色。
+ *
+ * @param userId 用户的唯一标识。
+ * @return 如果用户是老师角色返回 true,否则返回 false。
+ */
+ boolean isTeacher(String userId);
+
+ /**
+ * 判断指定用户是否为管理员角色。
+ * 根据用户 ID 检查该用户是否关联了管理员角色。
+ *
+ * @param userId 用户的唯一标识。
+ * @return 如果用户是管理员角色返回 true,否则返回 false。
+ */
+ boolean isAdmin(String userId);
+}
diff --git a/sys/user/service/SysUserService.java b/sys/user/service/SysUserService.java
new file mode 100644
index 0000000..c35b999
--- /dev/null
+++ b/sys/user/service/SysUserService.java
@@ -0,0 +1,99 @@
+// 声明当前接口所在的包,明确该接口属于系统用户服务模块
+package com.yf.exam.modules.sys.user.service;
+
+// 导入 MyBatis-Plus 分页元数据接口,用于处理分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 扩展服务接口,提供通用的 CRUD 操作方法
+import com.baomidou.mybatisplus.extension.service.IService;
+// 导入系统用户数据传输对象类,用于在不同层之间传输用户相关数据
+import com.yf.exam.modules.sys.user.dto.SysUserDTO;
+// 导入系统用户保存请求数据传输对象类,用于封装用户保存操作的请求参数
+import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO;
+// 导入系统用户登录响应数据传输对象类,用于封装用户登录操作的响应信息
+import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
+// 导入系统用户实体类,用于映射数据库中的用户表
+import com.yf.exam.modules.sys.user.entity.SysUser;
+// 导入自定义的分页请求数据传输对象类,用于封装分页查询的请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+
+/**
+ *
+ * 管理用户业务类接口,定义了系统用户相关的业务操作方法。
+ * 继承自 MyBatis-Plus 的 IService 接口,可使用其提供的通用服务方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+public interface SysUserService extends IService {
+
+ /**
+ * 分页查询系统用户数据。
+ * 根据传入的分页请求参数,从数据库中查询符合条件的用户数据并进行分页处理。
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的条件和参数
+ * @return 分页查询结果,包含系统用户数据传输对象列表
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 用户登录方法。
+ * 根据传入的用户名和密码,验证用户身份,若验证通过则返回登录信息。
+ *
+ * @param userName 用户名,用于标识用户身份
+ * @param password 用户密码,用于验证用户身份
+ * @return 系统用户登录响应数据传输对象,包含登录成功后的用户信息和令牌等
+ */
+ SysUserLoginDTO login(String userName, String password);
+
+ /**
+ * 根据令牌获取管理会话信息。
+ * 验证传入的令牌是否有效,若有效则返回对应的用户登录信息。
+ *
+ * @param token 用户登录后生成的令牌,用于验证用户身份和保持会话
+ * @return 系统用户登录响应数据传输对象,包含令牌对应的用户信息
+ */
+ SysUserLoginDTO token(String token);
+
+ /**
+ * 用户退出登录方法。
+ * 使传入的令牌失效,清除用户的登录会话信息。
+ *
+ * @param token 用户登录后生成的令牌,用于标识用户会话
+ */
+ void logout(String token);
+
+ /**
+ * 修改用户资料方法。
+ * 根据传入的用户数据传输对象,更新数据库中对应的用户信息。
+ *
+ * @param reqDTO 系统用户数据传输对象,包含要更新的用户信息
+ */
+ void update(SysUserDTO reqDTO);
+
+ /**
+ * 保存添加系统用户方法。
+ * 根据传入的用户保存请求数据传输对象,将新用户信息保存到数据库中。
+ *
+ * @param reqDTO 系统用户保存请求数据传输对象,包含要添加的用户信息
+ */
+ void save(SysUserSaveReqDTO reqDTO);
+
+ /**
+ * 用户注册方法。
+ * 根据传入的用户数据传输对象,在数据库中创建新的用户记录,并返回注册后的登录信息。
+ *
+ * @param reqDTO 系统用户数据传输对象,包含要注册的用户信息
+ * @return 系统用户登录响应数据传输对象,包含注册成功后的用户信息和令牌等
+ */
+ SysUserLoginDTO reg(SysUserDTO reqDTO);
+
+ /**
+ * 快速注册方法。
+ * 根据传入的用户数据传输对象,快速创建新的用户记录,并返回注册后的登录信息。
+ *
+ * @param reqDTO 系统用户数据传输对象,包含要快速注册的用户信息
+ * @return 系统用户登录响应数据传输对象,包含快速注册成功后的用户信息和令牌等
+ */
+ SysUserLoginDTO quickReg(SysUserDTO reqDTO);
+}
diff --git a/sys/user/service/impl/SysRoleServiceImpl.java b/sys/user/service/impl/SysRoleServiceImpl.java
new file mode 100644
index 0000000..845939f
--- /dev/null
+++ b/sys/user/service/impl/SysRoleServiceImpl.java
@@ -0,0 +1,62 @@
+// 声明该类所在的包,明确其在项目中的模块和层级位置
+package com.yf.exam.modules.sys.user.service.impl;
+
+// 导入阿里巴巴的 FastJSON 库,用于 JSON 数据的序列化和反序列化
+import com.alibaba.fastjson.JSON;
+// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化
+import com.alibaba.fastjson.TypeReference;
+// 导入 MyBatis-Plus 的查询条件构造器类,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询的结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+// 导入 MyBatis-Plus 的服务实现基类,提供了通用的服务层方法实现
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+// 导入系统角色数据传输对象类,用于在不同层之间传输角色相关的数据
+import com.yf.exam.modules.sys.user.dto.SysRoleDTO;
+// 导入系统角色实体类,用于映射数据库中的角色表
+import com.yf.exam.modules.sys.user.entity.SysRole;
+// 导入系统角色数据访问接口,用于与数据库进行角色数据的交互
+import com.yf.exam.modules.sys.user.mapper.SysRoleMapper;
+// 导入系统角色服务接口,定义了角色相关的业务方法
+import com.yf.exam.modules.sys.user.service.SysRoleService;
+// 导入分页请求数据传输对象类,用于封装分页查询的请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入 Spring 的服务注解,将该类标记为一个服务组件,由 Spring 容器进行管理
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 系统角色 服务实现类,实现了系统角色相关的业务逻辑。
+ * 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysRoleMapper 操作 SysRole 实体。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+@Service
+public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService {
+
+ /**
+ * 分页查询系统角色信息。
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的当前页码、每页记录数等信息
+ * @return 分页查询结果,包含系统角色数据传输对象列表
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+
+ // 创建分页对象,根据请求中的当前页码和每页记录数初始化
+ IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 创建查询条件构造器,用于构建数据库查询条件,当前未添加具体查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ // 调用父类的 page 方法进行分页查询,获取包含 SysRole 实体的分页结果
+ IPage page = this.page(query, wrapper);
+ // 将包含 SysRole 实体的分页结果转换为 JSON 字符串,再反序列化为包含 SysRoleDTO 的分页结果
+ IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
+ return pageData;
+ }
+}
diff --git a/sys/user/service/impl/SysUserRoleServiceImpl.java b/sys/user/service/impl/SysUserRoleServiceImpl.java
new file mode 100644
index 0000000..dc67c40
--- /dev/null
+++ b/sys/user/service/impl/SysUserRoleServiceImpl.java
@@ -0,0 +1,200 @@
+package com.yf.exam.modules.sys.user.service.impl;
+
+// 导入阿里巴巴的 FastJSON 库,用于 JSON 数据的序列化和反序列化
+import com.alibaba.fastjson.JSON;
+// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化
+import com.alibaba.fastjson.TypeReference;
+// 导入 MyBatis-Plus 的查询条件构造器类,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询的结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+// 导入 MyBatis-Plus 的服务实现基类,提供了通用的服务层方法实现
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+// 导入系统用户角色数据传输对象类,用于在不同层之间传输用户角色相关的数据
+import com.yf.exam.modules.sys.user.dto.SysUserRoleDTO;
+// 导入系统用户角色实体类,用于映射数据库中的用户角色表
+import com.yf.exam.modules.sys.user.entity.SysUserRole;
+// 导入系统用户角色数据访问接口,用于与数据库进行用户角色数据的交互
+import com.yf.exam.modules.sys.user.mapper.SysUserRoleMapper;
+// 导入系统用户角色服务接口,定义了用户角色相关的业务方法
+import com.yf.exam.modules.sys.user.service.SysUserRoleService;
+// 导入分页请求数据传输对象类,用于封装分页查询的请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入 Spring 的服务注解,将该类标记为一个服务组件,由 Spring 容器进行管理
+import org.springframework.stereotype.Service;
+// 导入 Spring 的集合工具类,用于判断集合是否为空
+import org.springframework.util.CollectionUtils;
+// 导入 Spring 的字符串工具类,用于判断字符串是否为空
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * 用户角色 服务实现类,实现了用户角色相关的业务逻辑。
+ * 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysUserRoleMapper 操作 SysUserRole 实体。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+@Service
+public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService {
+
+ /**
+ * 分页查询用户角色信息。
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页查询的当前页码、每页记录数等信息
+ * @return 分页查询结果,包含用户角色数据传输对象列表
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+
+ // 创建分页对象,根据请求中的当前页码和每页记录数初始化
+ IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 创建查询条件构造器,用于构建数据库查询条件,当前未添加具体查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ // 调用父类的 page 方法进行分页查询,获取包含 SysUserRole 实体的分页结果
+ IPage page = this.page(query, wrapper);
+ // 将包含 SysUserRole 实体的分页结果转换为 JSON 字符串,再反序列化为包含 SysUserRoleDTO 的分页结果
+ IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
+ return pageData;
+ }
+
+ /**
+ * 根据用户 ID 查询用户的角色列表。
+ *
+ * @param userId 用户的唯一标识
+ * @return 用户的角色 ID 列表
+ */
+ @Override
+ public List listRoles(String userId) {
+
+ // 创建查询条件构造器,添加用户 ID 作为查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUserRole::getUserId, userId);
+
+ // 调用父类的 list 方法进行查询,获取符合条件的用户角色实体列表
+ List list = this.list(wrapper);
+ // 初始化一个空的角色 ID 列表
+ List roles = new ArrayList<>();
+ // 判断查询结果列表是否不为空
+ if(!CollectionUtils.isEmpty(list)){
+ // 遍历用户角色实体列表,将每个实体的角色 ID 添加到角色 ID 列表中
+ for(SysUserRole item: list){
+ roles.add(item.getRoleId());
+ }
+ }
+
+ return roles;
+ }
+
+ /**
+ * 保存用户的角色信息,先删除用户原有的所有角色,再添加新的角色。
+ *
+ * @param userId 用户的唯一标识
+ * @param ids 新的角色 ID 列表
+ * @return 保存的角色 ID 以逗号连接的字符串
+ */
+ @Override
+ public String saveRoles(String userId, List ids) {
+
+ // 创建查询条件构造器,添加用户 ID 作为查询条件,用于删除用户原有的所有角色
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUserRole::getUserId, userId);
+ // 调用父类的 remove 方法删除符合条件的用户角色记录
+ this.remove(wrapper);
+
+ // 判断新的角色 ID 列表是否不为空
+ if(!CollectionUtils.isEmpty(ids)){
+
+ // 初始化一个空的用户角色实体列表
+ List list = new ArrayList<>();
+ // 用于存储以逗号连接的角色 ID 字符串
+ String roleIds = null;
+
+ // 遍历新的角色 ID 列表
+ for(String item: ids){
+ // 创建一个新的用户角色实体对象
+ SysUserRole role = new SysUserRole();
+ // 设置角色 ID
+ role.setRoleId(item);
+ // 设置用户 ID
+ role.setUserId(userId);
+ // 将用户角色实体对象添加到列表中
+ list.add(role);
+ // 判断 roleIds 是否为空
+ if(StringUtils.isEmpty(roleIds)){
+ // 若为空,则直接赋值为当前角色 ID
+ roleIds = item;
+ }else{
+ // 若不为空,则将当前角色 ID 追加到 roleIds 后面,用逗号分隔
+ roleIds+=","+item;
+ }
+ }
+
+ // 调用父类的 saveBatch 方法批量保存用户角色实体列表
+ this.saveBatch(list);
+ return roleIds;
+ }
+
+ return "";
+ }
+
+ /**
+ * 判断用户是否具有学生角色。
+ *
+ * @param userId 用户的唯一标识
+ * @return 若用户具有学生角色返回 true,否则返回 false
+ */
+ @Override
+ public boolean isStudent(String userId) {
+
+ // 创建查询条件构造器,添加用户 ID 和角色 ID(student)作为查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUserRole::getUserId, userId)
+ .eq(SysUserRole::getRoleId, "student");
+
+ // 调用父类的 count 方法统计符合条件的记录数,若大于 0 则表示用户具有学生角色
+ return this.count(wrapper) > 0;
+ }
+
+ /**
+ * 判断用户是否具有教师角色。
+ *
+ * @param userId 用户的唯一标识
+ * @return 若用户具有教师角色返回 true,否则返回 false
+ */
+ @Override
+ public boolean isTeacher(String userId) {
+ // 创建查询条件构造器,添加用户 ID 和角色 ID(teacher)作为查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUserRole::getUserId, userId)
+ .eq(SysUserRole::getRoleId, "teacher");
+
+ // 调用父类的 count 方法统计符合条件的记录数,若大于 0 则表示用户具有教师角色
+ return this.count(wrapper) > 0;
+ }
+
+ /**
+ * 判断用户是否具有管理员角色。
+ *
+ * @param userId 用户的唯一标识
+ * @return 若用户具有管理员角色返回 true,否则返回 false
+ */
+ @Override
+ public boolean isAdmin(String userId) {
+ // 创建查询条件构造器,添加用户 ID 和角色 ID(sa)作为查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUserRole::getUserId, userId)
+ .eq(SysUserRole::getRoleId, "sa");
+
+ // 调用父类的 count 方法统计符合条件的记录数,若大于 0 则表示用户具有管理员角色
+ return this.count(wrapper) > 0;
+ }
+}
diff --git a/sys/user/service/impl/SysUserServiceImpl.java b/sys/user/service/impl/SysUserServiceImpl.java
new file mode 100644
index 0000000..6aa7169
--- /dev/null
+++ b/sys/user/service/impl/SysUserServiceImpl.java
@@ -0,0 +1,383 @@
+// 声明该类所在的包,表明它属于系统用户服务实现模块
+package com.yf.exam.modules.sys.user.service.impl;
+
+// 导入阿里巴巴 FastJSON 库,用于 JSON 数据的序列化和反序列化
+import com.alibaba.fastjson.JSON;
+// 导入 FastJSON 的 TypeReference 类,用于处理泛型类型的 JSON 反序列化
+import com.alibaba.fastjson.TypeReference;
+// 导入 MyBatis-Plus 的查询条件构造器,用于构建数据库查询条件
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+// 导入 MyBatis-Plus 的分页元数据接口,用于表示分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 的 ID 生成工具类,用于生成分布式 ID
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+// 导入 MyBatis-Plus 的分页实现类,用于创建分页对象
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+// 导入 MyBatis-Plus 的服务实现基类,提供通用的服务层方法实现
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+// 导入自定义的 API 错误类,用于封装 API 错误信息
+import com.yf.exam.core.api.ApiError;
+// 导入自定义的分页请求数据传输对象类,用于封装分页查询请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入自定义的通用状态枚举类,定义通用的状态常量
+import com.yf.exam.core.enums.CommonState;
+// 导入自定义的服务异常类,用于抛出服务层异常
+import com.yf.exam.core.exception.ServiceException;
+// 导入自定义的 Bean 映射工具类,用于对象属性的复制
+import com.yf.exam.core.utils.BeanMapper;
+// 导入自定义的密码处理工具类,用于密码的加密和验证
+import com.yf.exam.core.utils.passwd.PassHandler;
+// 导入自定义的密码信息类,用于封装密码和盐值
+import com.yf.exam.core.utils.passwd.PassInfo;
+// 导入自定义的 JWT 工具类,用于生成和验证 JWT 令牌
+import com.yf.exam.ability.shiro.jwt.JwtUtils;
+// 导入自定义的系统用户数据传输对象类,用于在不同层之间传输用户数据
+import com.yf.exam.modules.sys.user.dto.SysUserDTO;
+// 导入自定义的系统用户保存请求数据传输对象类,用于封装用户保存请求参数
+import com.yf.exam.modules.sys.user.dto.request.SysUserSaveReqDTO;
+// 导入自定义的系统用户登录响应数据传输对象类,用于封装用户登录响应信息
+import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
+// 导入自定义的系统用户实体类,用于映射数据库中的用户表
+import com.yf.exam.modules.sys.user.entity.SysUser;
+// 导入自定义的系统用户数据访问接口,用于与数据库进行用户数据交互
+import com.yf.exam.modules.sys.user.mapper.SysUserMapper;
+// 导入自定义的系统用户角色服务接口,用于处理用户角色相关业务
+import com.yf.exam.modules.sys.user.service.SysUserRoleService;
+// 导入自定义的系统用户服务接口,定义用户相关的业务方法
+import com.yf.exam.modules.sys.user.service.SysUserService;
+// 导入自定义的用户工具类,提供用户相关的工具方法
+import com.yf.exam.modules.user.UserUtils;
+// 导入 Apache Commons Lang3 的字符串工具类,提供字符串操作方法
+import org.apache.commons.lang3.StringUtils;
+// 导入 Apache Shiro 的安全工具类,用于进行身份验证和授权操作
+import org.apache.shiro.SecurityUtils;
+// 导入 Spring 的自动装配注解,用于自动注入依赖的 Bean
+import org.springframework.beans.factory.annotation.Autowired;
+// 导入 Spring 的服务注解,将该类标记为服务组件,由 Spring 容器管理
+import org.springframework.stereotype.Service;
+// 导入 Spring 的事务管理注解,用于声明事务方法
+import org.springframework.transaction.annotation.Transactional;
+// 导入 Spring 的集合工具类,用于判断集合是否为空
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * 系统用户 服务实现类,实现了系统用户相关的业务逻辑。
+ * 继承自 MyBatis-Plus 的 ServiceImpl 类,使用 SysUserMapper 操作 SysUser 实体。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-04-13 16:57
+ */
+@Service
+public class SysUserServiceImpl extends ServiceImpl implements SysUserService {
+
+ // 自动注入系统用户角色服务类的实例,用于处理用户角色相关业务
+ @Autowired
+ private SysUserRoleService sysUserRoleService;
+
+ /**
+ * 分页查询系统用户信息。
+ *
+ * @param reqDTO 分页请求数据传输对象,包含分页信息和查询参数
+ * @return 分页查询结果,包含系统用户数据传输对象列表
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+
+ // 创建分页对象,指定当前页码和每页记录数
+ IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 创建查询条件构造器,用于构建数据库查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ // 获取查询参数
+ SysUserDTO params = reqDTO.getParams();
+
+ // 如果查询参数不为空
+ if(params!=null){
+ // 如果用户名不为空,添加用户名模糊查询条件
+ if(!StringUtils.isBlank(params.getUserName())){
+ wrapper.lambda().like(SysUser::getUserName, params.getUserName());
+ }
+
+ // 如果真实姓名不为空,添加真实姓名模糊查询条件
+ if(!StringUtils.isBlank(params.getRealName())){
+ wrapper.lambda().like(SysUser::getRealName, params.getRealName());
+ }
+ }
+
+ // 执行分页查询,获取包含系统用户实体的分页结果
+ IPage page = this.page(query, wrapper);
+ // 将包含系统用户实体的分页结果转换为包含系统用户数据传输对象的分页结果
+ IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
+ return pageData;
+ }
+
+ /**
+ * 用户登录方法。
+ *
+ * @param userName 用户名
+ * @param password 密码
+ * @return 系统用户登录响应数据传输对象,包含登录成功后的用户信息和令牌
+ * @throws ServiceException 若用户名不存在、用户被禁用或密码错误,抛出服务异常
+ */
+ @Override
+ public SysUserLoginDTO login(String userName, String password) {
+
+ // 创建查询条件构造器,添加用户名等于查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUser::getUserName, userName);
+
+ // 根据查询条件获取一个用户实体
+ SysUser user = this.getOne(wrapper, false);
+ // 如果用户不存在,抛出用户名或密码错误异常
+ if(user == null){
+ throw new ServiceException(ApiError.ERROR_90010002);
+ }
+
+ // 如果用户状态为异常(被禁用),抛出用户被禁用异常
+ if(user.getState().equals(CommonState.ABNORMAL)){
+ throw new ServiceException(ApiError.ERROR_90010005);
+ }
+
+ // 验证密码是否正确
+ boolean check = PassHandler.checkPass(password,user.getSalt(), user.getPassword());
+ // 如果密码不正确,抛出用户名或密码错误异常
+ if(!check){
+ throw new ServiceException(ApiError.ERROR_90010002);
+ }
+
+ // 为用户设置令牌并返回登录响应信息
+ return this.setToken(user);
+ }
+
+ /**
+ * 根据令牌验证用户信息并返回登录响应信息。
+ *
+ * @param token 令牌
+ * @return 系统用户登录响应数据传输对象,包含验证通过后的用户信息和令牌
+ * @throws ServiceException 若令牌验证失败、用户不存在或用户被禁用,抛出服务异常
+ */
+ @Override
+ public SysUserLoginDTO token(String token) {
+
+ // 从令牌中获取用户名
+ String username = JwtUtils.getUsername(token);
+
+ // 验证令牌是否有效
+ boolean check = JwtUtils.verify(token, username);
+
+ // 如果令牌验证失败,抛出用户名或密码错误异常
+ if(!check){
+ throw new ServiceException(ApiError.ERROR_90010002);
+ }
+
+ // 创建查询条件构造器,添加用户名等于查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUser::getUserName, username);
+
+ // 根据查询条件获取一个用户实体
+ SysUser user = this.getOne(wrapper, false);
+ // 如果用户不存在,抛出用户不存在异常
+ if(user == null){
+ throw new ServiceException(ApiError.ERROR_10010002);
+ }
+
+ // 如果用户状态为异常(被禁用),抛出用户被禁用异常
+ if(user.getState().equals(CommonState.ABNORMAL)){
+ throw new ServiceException(ApiError.ERROR_90010005);
+ }
+
+ // 为用户设置令牌并返回登录响应信息
+ return this.setToken(user);
+ }
+
+ /**
+ * 用户退出登录方法。
+ *
+ * @param token 令牌
+ */
+ @Override
+ public void logout(String token) {
+
+ // 仅退出当前会话
+ SecurityUtils.getSubject().logout();
+ }
+
+ /**
+ * 更新用户信息,主要处理密码更新。
+ *
+ * @param reqDTO 系统用户数据传输对象,包含要更新的用户信息
+ */
+ @Override
+ public void update(SysUserDTO reqDTO) {
+
+ // 获取用户输入的新密码
+ String pass = reqDTO.getPassword();
+ // 如果新密码不为空
+ if(!StringUtils.isBlank(pass)){
+ // 生成新的密码信息,包含加密后的密码和盐值
+ PassInfo passInfo = PassHandler.buildPassword(pass);
+ // 根据当前用户 ID 获取用户实体
+ SysUser user = this.getById(UserUtils.getUserId());
+ // 更新用户密码
+ user.setPassword(passInfo.getPassword());
+ // 更新用户密码盐值
+ user.setSalt(passInfo.getSalt());
+ // 更新用户信息到数据库
+ this.updateById(user);
+ }
+ }
+
+ /**
+ * 保存或更新用户信息,同时处理用户角色信息。
+ *
+ * @param reqDTO 系统用户保存请求数据传输对象,包含要保存的用户信息和角色列表
+ * @throws ServiceException 若角色列表为空,抛出服务异常
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void save(SysUserSaveReqDTO reqDTO) {
+
+ // 获取用户的角色列表
+ List roles = reqDTO.getRoles();
+
+ // 如果角色列表为空,抛出角色不能为空异常
+ if(CollectionUtils.isEmpty(roles)){
+ throw new ServiceException(ApiError.ERROR_90010003);
+ }
+
+ // 创建系统用户实体对象
+ SysUser user = new SysUser();
+ // 将请求数据传输对象的属性复制到用户实体对象中
+ BeanMapper.copy(reqDTO, user);
+
+ // 如果用户 ID 为空,说明是新增用户,生成新的用户 ID
+ if(StringUtils.isBlank(user.getId())){
+ user.setId(IdWorker.getIdStr());
+ }
+
+ // 如果请求中包含密码,更新用户密码信息
+ if(!StringUtils.isBlank(reqDTO.getPassword())){
+ // 生成新的密码信息,包含加密后的密码和盐值
+ PassInfo pass = PassHandler.buildPassword(reqDTO.getPassword());
+ // 更新用户密码
+ user.setPassword(pass.getPassword());
+ // 更新用户密码盐值
+ user.setSalt(pass.getSalt());
+ }
+
+ // 保存用户角色信息,并返回角色 ID 字符串
+ String roleIds = sysUserRoleService.saveRoles(user.getId(), roles);
+ // 设置用户的角色 ID 字符串
+ user.setRoleIds(roleIds);
+ // 保存或更新用户信息到数据库
+ this.saveOrUpdate(user);
+ }
+
+ /**
+ * 用户注册方法。
+ *
+ * @param reqDTO 系统用户数据传输对象,包含要注册的用户信息
+ * @return 系统用户登录响应数据传输对象,包含注册成功后的用户信息和令牌
+ * @throws ServiceException 若用户名已存在,抛出服务异常
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public SysUserLoginDTO reg(SysUserDTO reqDTO) {
+
+ // 创建查询条件构造器,添加用户名等于查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName());
+
+ // 统计符合条件的用户数量
+ int count = this.count(wrapper);
+
+ // 如果用户数量大于 0,说明用户名已存在,抛出用户名已存在异常
+ if(count > 0){
+ throw new ServiceException(1, "用户名已存在,换一个吧!");
+ }
+
+ // 创建系统用户实体对象
+ SysUser user = new SysUser();
+ // 生成新的用户 ID
+ user.setId(IdWorker.getIdStr());
+ // 设置用户名
+ user.setUserName(reqDTO.getUserName());
+ // 设置真实姓名
+ user.setRealName(reqDTO.getRealName());
+ // 生成新的密码信息,包含加密后的密码和盐值
+ PassInfo passInfo = PassHandler.buildPassword(reqDTO.getPassword());
+ // 设置用户密码
+ user.setPassword(passInfo.getPassword());
+ // 设置用户密码盐值
+ user.setSalt(passInfo.getSalt());
+
+ // 创建角色列表,默认添加学生角色
+ List roles = new ArrayList<>();
+ roles.add("student");
+ // 保存用户角色信息,并返回角色 ID 字符串
+ String roleIds = sysUserRoleService.saveRoles(user.getId(), roles);
+ // 设置用户的角色 ID 字符串
+ user.setRoleIds(roleIds);
+ // 保存用户信息到数据库
+ this.save(user);
+
+ // 为用户设置令牌并返回登录响应信息
+ return this.setToken(user);
+ }
+
+ /**
+ * 快速注册方法,如果用户已存在则直接返回登录信息,否则进行注册。
+ *
+ * @param reqDTO 系统用户数据传输对象,包含要注册的用户信息
+ * @return 系统用户登录响应数据传输对象,包含注册或登录成功后的用户信息和令牌
+ */
+ @Override
+ public SysUserLoginDTO quickReg(SysUserDTO reqDTO) {
+
+ // 创建查询条件构造器,添加用户名等于查询条件,并限制查询结果为 1 条
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(SysUser::getUserName, reqDTO.getUserName());
+ wrapper.last(" LIMIT 1 ");
+ // 根据查询条件获取一个用户实体
+ SysUser user = this.getOne(wrapper);
+ // 如果用户存在,为用户设置令牌并返回登录响应信息
+ if(user!=null){
+ return this.setToken(user);
+ }
+
+ // 如果用户不存在,进行注册操作
+ return this.reg(reqDTO);
+ }
+
+ /**
+ * 为用户设置令牌并返回登录响应信息。
+ *
+ * @param user 系统用户实体对象
+ * @return 系统用户登录响应数据传输对象,包含用户信息和令牌
+ */
+ private SysUserLoginDTO setToken(SysUser user){
+
+ // 创建系统用户登录响应数据传输对象
+ SysUserLoginDTO respDTO = new SysUserLoginDTO();
+ // 将用户实体对象的属性复制到登录响应数据传输对象中
+ BeanMapper.copy(user, respDTO);
+
+ // 生成 JWT 令牌
+ String token = JwtUtils.sign(user.getUserName());
+ // 设置登录响应数据传输对象的令牌
+ respDTO.setToken(token);
+
+ // 获取用户的角色列表
+ List roles = sysUserRoleService.listRoles(user.getId());
+ // 设置登录响应数据传输对象的角色列表
+ respDTO.setRoles(roles);
+
+ return respDTO;
+ }
+}
diff --git a/user/UserUtils.java b/user/UserUtils.java
new file mode 100644
index 0000000..7341166
--- /dev/null
+++ b/user/UserUtils.java
@@ -0,0 +1,75 @@
+package com.yf.exam.modules.user;
+
+// 导入自定义的 API 错误信息类
+import com.yf.exam.core.api.ApiError;
+// 导入自定义的服务异常类
+import com.yf.exam.core.exception.ServiceException;
+// 导入系统用户登录信息响应数据传输对象
+import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
+// 导入 Shiro 安全工具类
+import org.apache.shiro.SecurityUtils;
+
+/**
+ * 用户静态工具类,提供获取当前登录用户信息的静态方法。
+ * @author bool
+ */
+public class UserUtils {
+
+ /**
+ * 获取当前登录用户的ID。
+ *
+ * @param throwable 一个布尔值,指示在获取用户 ID 失败时是否抛出异常。
+ * 若为 true,则在失败时抛出 ServiceException 异常;
+ * 若为 false,则在失败时返回 null。
+ * @return 当前登录用户的 ID,如果获取失败且 throwable 为 false,则返回 null。
+ */
+ public static String getUserId(boolean throwable){
+ try {
+ // 从 Shiro 的 Subject 中获取当前用户的主身份信息,并转换为 SysUserLoginDTO 对象,然后获取用户 ID
+ return ((SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal()).getId();
+ }catch (Exception e){
+ if(throwable){
+ // 若 throwable 为 true,获取用户 ID 失败时抛出服务异常
+ throw new ServiceException(ApiError.ERROR_10010002);
+ }
+ // 若 throwable 为 false,获取用户 ID 失败时返回 null
+ return null;
+ }
+ }
+
+ /**
+ * 判断当前登录用户是否为管理员。
+ *
+ * @param throwable 一个布尔值,指示在获取用户信息失败时是否抛出异常。
+ * 若为 true,则在失败时抛出 ServiceException 异常;
+ * 若为 false,则在失败时返回 false。
+ * @return 若当前登录用户的角色列表包含 "sa",则返回 true;否则返回 false。
+ * 若获取用户信息失败且 throwable 为 false,也返回 false。
+ */
+ public static boolean isAdmin(boolean throwable){
+ try {
+ // 从 Shiro 的 Subject 中获取当前用户的主身份信息,并转换为 SysUserLoginDTO 对象
+ SysUserLoginDTO dto = ((SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal());
+ // 判断用户的角色列表是否包含 "sa"
+ return dto.getRoles().contains("sa");
+ }catch (Exception e){
+ if(throwable){
+ // 若 throwable 为 true,获取用户信息失败时抛出服务异常
+ throw new ServiceException(ApiError.ERROR_10010002);
+ }
+ }
+
+ // 若获取用户信息失败且 throwable 为 false,返回 false
+ return false;
+ }
+
+ /**
+ * 获取当前登录用户的 ID,默认在获取失败时会抛出异常。
+ * 该方法调用了 getUserId(boolean throwable) 方法,并将 throwable 参数设置为 true。
+ *
+ * @return 当前登录用户的 ID,如果获取失败则抛出 ServiceException 异常。
+ */
+ public static String getUserId(){
+ return getUserId(true);
+ }
+}
diff --git a/user/book/controller/UserBookController.java b/user/book/controller/UserBookController.java
new file mode 100644
index 0000000..9ff16d0
--- /dev/null
+++ b/user/book/controller/UserBookController.java
@@ -0,0 +1,79 @@
+package com.yf.exam.modules.user.book.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yf.exam.core.api.ApiRest;
+import com.yf.exam.core.api.controller.BaseController;
+import com.yf.exam.core.api.dto.BaseIdRespDTO;
+import com.yf.exam.core.api.dto.BaseIdsReqDTO;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.user.book.dto.UserBookDTO;
+import com.yf.exam.modules.user.book.service.UserBookService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ * 错题本控制器,处理与错题本相关的HTTP请求
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-27 17:56
+ */
+@Api(tags={"错题本"})
+@RestController
+@RequestMapping("/exam/api/user/wrong-book")
+public class UserBookController extends BaseController {
+
+ /**
+ * 注入错题本服务类,用于处理错题本相关的业务逻辑
+ */
+ @Autowired
+ private UserBookService baseService;
+
+ /**
+ * 批量删除错题本记录
+ * @param reqDTO 包含要删除记录ID列表的请求对象
+ * @return 操作结果的统一响应对象
+ */
+ @ApiOperation(value = "批量删除")
+ @RequestMapping(value = "/delete", method = { RequestMethod.POST})
+ public ApiRest delete(@RequestBody BaseIdsReqDTO reqDTO) {
+ // 根据传入的ID列表删除对应的错题本记录
+ baseService.removeByIds(reqDTO.getIds());
+ // 返回操作成功的响应
+ return super.success();
+ }
+
+ /**
+ * 分页查找错题本记录
+ * @param reqDTO 包含分页信息和查询条件的请求对象
+ * @return 包含分页结果的统一响应对象
+ */
+ @ApiOperation(value = "分页查找")
+ @RequestMapping(value = "/paging", method = { RequestMethod.POST})
+ public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) {
+ // 调用服务层方法进行分页查询,并将结果转换为UserBookDTO对象
+ IPage page = baseService.paging(reqDTO);
+ // 返回包含分页结果的成功响应
+ return super.success(page);
+ }
+
+ /**
+ * 查找下一个错题记录,每次最多返回200条数据
+ * @param reqDTO 包含考试ID和当前题目ID的请求对象
+ * @return 包含下一个题目ID的统一响应对象
+ */
+ @ApiOperation(value = "查找列表")
+ @RequestMapping(value = "/next", method = { RequestMethod.POST})
+ public ApiRest nextQu(@RequestBody UserBookDTO reqDTO) {
+ // 调用服务层方法查找下一个错题的题目ID
+ String quId = baseService.findNext(reqDTO.getExamId(), reqDTO.getQuId());
+ // 将下一个题目ID封装到响应对象中并返回成功响应
+ return super.success(new BaseIdRespDTO(quId));
+ }
+}
diff --git a/user/book/dto/UserBookDTO.java b/user/book/dto/UserBookDTO.java
new file mode 100644
index 0000000..96e4cad
--- /dev/null
+++ b/user/book/dto/UserBookDTO.java
@@ -0,0 +1,79 @@
+package com.yf.exam.modules.user.book.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ * 错题本请求类,用于在不同层之间传输错题本相关的数据。
+ * 该类包含了错题本记录的基本信息,如考试ID、用户ID、题目ID等。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-27 17:56
+ */
+@Data
+@ApiModel(value="错题本", description="错题本")
+public class UserBookDTO implements Serializable {
+
+ // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 错题本记录的唯一标识ID
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 关联的考试ID,表明该错题属于哪次考试
+ */
+ @ApiModelProperty(value = "考试ID", required=true)
+ private String examId;
+
+ /**
+ * 关联的用户ID,表明该错题属于哪个用户
+ */
+ @ApiModelProperty(value = "用户ID", required=true)
+ private String userId;
+
+ /**
+ * 关联的题目ID,表明具体是哪道题目做错了
+ */
+ @ApiModelProperty(value = "题目ID", required=true)
+ private String quId;
+
+ /**
+ * 该错题加入错题本的时间
+ */
+ @ApiModelProperty(value = "加入时间", required=true)
+ private Date createTime;
+
+ /**
+ * 该错题最近一次出错的时间
+ */
+ @ApiModelProperty(value = "最近错误时间", required=true)
+ private Date updateTime;
+
+ /**
+ * 该错题累计出错的次数
+ */
+ @ApiModelProperty(value = "错误时间", required=true)
+ private Integer wrongCount;
+
+ /**
+ * 错题的题目标题,用于快速识别题目内容
+ */
+ @ApiModelProperty(value = "题目标题", required=true)
+ private String title;
+
+ /**
+ * 错题在错题本中的排序序号
+ */
+ @ApiModelProperty(value = "错题序号", required=true)
+ private Integer sort;
+}
diff --git a/user/book/entity/UserBook.java b/user/book/entity/UserBook.java
new file mode 100644
index 0000000..a36fb6a
--- /dev/null
+++ b/user/book/entity/UserBook.java
@@ -0,0 +1,80 @@
+package com.yf.exam.modules.user.book.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ *
+ * 错题本实体类,对应数据库表 `el_user_book`,用于存储用户错题相关信息。
+ * 该类继承自 MyBatis-Plus 的 `Model` 类,可使用其提供的 ActiveRecord 功能。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-27 17:56
+ */
+@Data
+@TableName("el_user_book")
+public class UserBook extends Model {
+
+ // 序列化版本号,确保序列化和反序列化过程中类版本的一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 错题本记录的唯一标识 ID,在插入数据时由系统自动分配。
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 关联的考试 ID,表明该错题所属的考试。
+ */
+ @TableField("exam_id")
+ private String examId;
+
+ /**
+ * 关联的用户 ID,表明该错题所属的用户。
+ */
+ @TableField("user_id")
+ private String userId;
+
+ /**
+ * 关联的题目 ID,表明具体是哪道题目被做错。
+ */
+ @TableField("qu_id")
+ private String quId;
+
+ /**
+ * 错题被加入错题本的时间,记录首次出错时间。
+ */
+ @TableField("create_time")
+ private Date createTime;
+
+ /**
+ * 该错题最近一次出错的时间,反映错题的最新错误情况。
+ */
+ @TableField("update_time")
+ private Date updateTime;
+
+ /**
+ * 该错题累计出错的次数,用于统计错题的出错频率。
+ */
+ @TableField("wrong_count")
+ private Integer wrongCount;
+
+ /**
+ * 错题的题目标题,方便用户快速识别错题内容。
+ */
+ private String title;
+
+ /**
+ * 错题在错题本中的排序序号,用于对错题进行排序展示。
+ */
+ private Integer sort;
+
+}
diff --git a/user/book/mapper/UserBookMapper.java b/user/book/mapper/UserBookMapper.java
new file mode 100644
index 0000000..68af834
--- /dev/null
+++ b/user/book/mapper/UserBookMapper.java
@@ -0,0 +1,18 @@
+package com.yf.exam.modules.user.book.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yf.exam.modules.user.book.entity.UserBook;
+
+/**
+ *
+ * 错题本Mapper接口,用于与数据库中错题本相关表进行数据交互。
+ * 该接口继承自MyBatis-Plus的BaseMapper接口,可直接使用BaseMapper提供的基础CRUD操作方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-27 17:56
+ */
+public interface UserBookMapper extends BaseMapper {
+ // 若需要自定义数据库操作方法,可在此处添加接口方法声明
+ // 对应的SQL语句可以在XML文件中实现,或者使用@Select、@Insert等注解
+}
diff --git a/user/book/service/UserBookService.java b/user/book/service/UserBookService.java
new file mode 100644
index 0000000..b4c0f74
--- /dev/null
+++ b/user/book/service/UserBookService.java
@@ -0,0 +1,41 @@
+package com.yf.exam.modules.user.book.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.user.book.dto.UserBookDTO;
+import com.yf.exam.modules.user.book.entity.UserBook;
+
+/**
+ *
+ * 错题本业务类接口,定义了错题本相关的业务操作方法。
+ * 该接口继承自 MyBatis-Plus 的 IService 接口,可使用其提供的基础服务方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-27 17:56
+ */
+public interface UserBookService extends IService {
+
+ /**
+ * 分页查询错题本数据
+ * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserBookDTO
+ * @return 包含分页结果的 UserBookDTO 对象集合,封装在 IPage 中
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 将指定题目加入错题本
+ * @param examId 关联的考试 ID,标识该错题所属的考试
+ * @param quId 关联的题目 ID,标识具体是哪道题目
+ */
+ void addBook(String examId, String quId);
+
+ /**
+ * 查找当前错题的下一个错题
+ * @param examId 关联的考试 ID,限定查找范围为该考试内的错题
+ * @param quId 当前错题的题目 ID,基于此查找下一个错题
+ * @return 下一个错题的题目 ID,如果不存在则可能返回 null
+ */
+ String findNext(String examId, String quId);
+}
diff --git a/user/book/service/impl/UserBookServiceImpl.java b/user/book/service/impl/UserBookServiceImpl.java
new file mode 100644
index 0000000..38d8b48
--- /dev/null
+++ b/user/book/service/impl/UserBookServiceImpl.java
@@ -0,0 +1,192 @@
+package com.yf.exam.modules.user.book.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.qu.entity.Qu;
+import com.yf.exam.modules.qu.service.QuService;
+import com.yf.exam.modules.user.UserUtils;
+import com.yf.exam.modules.user.book.dto.UserBookDTO;
+import com.yf.exam.modules.user.book.entity.UserBook;
+import com.yf.exam.modules.user.book.mapper.UserBookMapper;
+import com.yf.exam.modules.user.book.service.UserBookService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+/**
+ *
+ * 错题本服务实现类,实现了错题本相关的业务逻辑。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-05-27 17:56
+ */
+@Service
+public class UserBookServiceImpl extends ServiceImpl implements UserBookService {
+
+ /**
+ * 注入题目服务类,用于获取题目相关信息
+ */
+ @Autowired
+ private QuService quService;
+
+ /**
+ * 分页查询用户错题本记录
+ * @param reqDTO 包含分页信息和查询条件的请求对象
+ * @return 包含分页结果的错题本记录 DTO 对象
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+
+ // 创建分页对象,指定当前页码和每页显示数量
+ Page query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 构建查询条件包装器
+ QueryWrapper wrapper = new QueryWrapper<>();
+ // 只查询当前用户的错题记录
+ wrapper.lambda().eq(UserBook::getUserId, UserUtils.getUserId(true));
+
+ // 获取查询参数
+ UserBookDTO params = reqDTO.getParams();
+ if (params != null) {
+ // 如果标题参数不为空,添加模糊查询条件
+ if (!StringUtils.isEmpty(params.getTitle())) {
+ wrapper.lambda().like(UserBook::getTitle, params.getTitle());
+ }
+
+ // 如果考试 ID 参数不为空,添加精确查询条件
+ if (!StringUtils.isEmpty(params.getExamId())) {
+ wrapper.lambda().eq(UserBook::getExamId, params.getExamId());
+ }
+ }
+
+ // 执行分页查询,获取错题本实体分页数据
+ IPage page = this.page(query, wrapper);
+ // 将实体分页数据转换为 DTO 分页数据
+ IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
+ return pageData;
+ }
+
+ /**
+ * 将错题添加到用户错题本中
+ * @param examId 考试 ID
+ * @param quId 题目 ID
+ */
+ @Override
+ public void addBook(String examId, String quId) {
+
+ // 构建查询条件,查找该用户在本次考试中该题目的错题记录
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda()
+ .eq(UserBook::getUserId, UserUtils.getUserId())
+ .eq(UserBook::getExamId, examId)
+ .eq(UserBook::getQuId, quId);
+
+ // 查找已有的错题信息
+ UserBook book = this.getOne(wrapper, false);
+
+ // 获取题目信息
+ Qu qu = quService.getById(quId);
+
+ if (book == null) {
+ // 如果该错题记录不存在,则创建新的错题记录
+ book = new UserBook();
+ book.setExamId(examId);
+ book.setUserId(UserUtils.getUserId());
+ book.setTitle(qu.getContent());
+ book.setQuId(quId);
+ book.setWrongCount(1);
+ // 获取当前考试中用户错题的最大排序值,并加 1 作为新记录的排序值
+ Integer maxSort = this.findMaxSort(examId, UserUtils.getUserId());
+ book.setSort(maxSort + 1);
+
+ // 保存新的错题记录
+ this.save(book);
+ } else {
+ // 如果该错题记录已存在,错误次数加 1
+ book.setWrongCount(book.getWrongCount() + 1);
+ // 更新错题记录
+ this.updateById(book);
+ }
+ }
+
+ /**
+ * 查找当前错题的下一个错题的题目 ID
+ * @param examId 考试 ID
+ * @param quId 当前错题的题目 ID
+ * @return 下一个错题的题目 ID,如果不存在则返回 null
+ */
+ @Override
+ public String findNext(String examId, String quId) {
+
+ // 初始化排序值为一个较大值
+ Integer sort = 999999;
+
+ if (!StringUtils.isEmpty(quId)) {
+ // 构建查询条件,查找当前错题记录
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda()
+ .eq(UserBook::getUserId, UserUtils.getUserId())
+ .eq(UserBook::getExamId, examId)
+ .eq(UserBook::getQuId, quId);
+ // 按排序值降序排序
+ wrapper.last(" ORDER BY `sort` DESC");
+
+ // 获取当前错题记录
+ UserBook last = this.getOne(wrapper, false);
+ if (last != null) {
+ // 如果找到当前错题记录,获取其排序值
+ sort = last.getSort();
+ }
+ }
+
+ // 构建查询条件,查找排序值小于当前错题的下一个错题记录
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda()
+ .eq(UserBook::getUserId, UserUtils.getUserId())
+ .eq(UserBook::getExamId, examId)
+ .lt(UserBook::getSort, sort);
+ // 按排序值降序排序
+ wrapper.last(" ORDER BY `sort` DESC");
+
+ // 获取下一个错题记录
+ UserBook next = this.getOne(wrapper, false);
+ if (next != null) {
+ // 如果找到下一个错题记录,返回其题目 ID
+ return next.getQuId();
+ }
+
+ return null;
+ }
+
+ /**
+ * 查找指定考试中用户错题的最大排序值
+ * @param examId 考试 ID
+ * @param userId 用户 ID
+ * @return 最大排序值,如果没有记录则返回 0
+ */
+ private Integer findMaxSort(String examId, String userId) {
+
+ // 构建查询条件,查找指定考试中用户的错题记录
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda()
+ .eq(UserBook::getExamId, examId)
+ .eq(UserBook::getUserId, userId);
+ // 按排序值降序排序
+ wrapper.last(" ORDER BY `sort` DESC");
+
+ // 获取排序值最大的错题记录
+ UserBook book = this.getOne(wrapper, false);
+ if (book == null) {
+ // 如果没有记录,返回 0
+ return 0;
+ }
+ // 返回最大排序值
+ return book.getSort();
+ }
+}
diff --git a/user/exam/controller/UserExamController.java b/user/exam/controller/UserExamController.java
new file mode 100644
index 0000000..ae03687
--- /dev/null
+++ b/user/exam/controller/UserExamController.java
@@ -0,0 +1,65 @@
+package com.yf.exam.modules.user.exam.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yf.exam.core.api.ApiRest;
+import com.yf.exam.core.api.controller.BaseController;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO;
+import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO;
+import com.yf.exam.modules.user.exam.service.UserExamService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ * 考试记录控制器,处理与用户考试记录相关的 HTTP 请求。
+ * 该控制器提供了分页查询考试记录的接口。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+@Api(tags={"考试记录"})
+@RestController
+@RequestMapping("/exam/api/user/exam")
+public class UserExamController extends BaseController {
+
+ /**
+ * 注入用户考试记录服务类,用于处理考试记录相关的业务逻辑。
+ */
+ @Autowired
+ private UserExamService baseService;
+
+ /**
+ * 分页查找考试记录
+ * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO
+ * @return 包含分页结果的统一响应对象,分页结果为 UserExamRespDTO 类型
+ */
+ @ApiOperation(value = "分页查找")
+ @RequestMapping(value = "/paging", method = { RequestMethod.POST})
+ public ApiRest> paging(@RequestBody PagingReqDTO reqDTO) {
+ // 调用服务层的分页查询方法,获取分页后的考试记录
+ IPage page = baseService.paging(reqDTO);
+ // 返回包含分页结果的成功响应
+ return super.success(page);
+ }
+
+ /**
+ * 分页查找当前用户的考试记录
+ * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO
+ * @return 包含分页结果的统一响应对象,分页结果为 UserExamRespDTO 类型
+ */
+ @ApiOperation(value = "分页查找")
+ @RequestMapping(value = "/my-paging", method = { RequestMethod.POST})
+ public ApiRest> myPaging(@RequestBody PagingReqDTO reqDTO) {
+ // 调用服务层的分页查询方法,获取当前用户分页后的考试记录
+ IPage page = baseService.myPaging(reqDTO);
+ // 返回包含分页结果的成功响应
+ return super.success(page);
+ }
+}
diff --git a/user/exam/dto/UserExamDTO.java b/user/exam/dto/UserExamDTO.java
new file mode 100644
index 0000000..e6d915d
--- /dev/null
+++ b/user/exam/dto/UserExamDTO.java
@@ -0,0 +1,84 @@
+// 声明该类所在的包,此包用于存放用户考试相关的数据传输对象
+package com.yf.exam.modules.user.exam.dto;
+
+// 导入自定义的字典注解,用于处理数据字典映射
+import com.yf.exam.core.annon.Dict;
+// 导入 Swagger 相关注解,用于生成 API 文档
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+// 导入日期类,用于表示时间相关的属性
+import java.util.Date;
+// 导入序列化接口,使该类的对象可以被序列化和反序列化
+import java.io.Serializable;
+
+/**
+ *
+ * 考试记录数据传输类,用于在不同层(如控制层、服务层、数据访问层)之间传输考试记录相关的数据。
+ * 该类封装了考试记录的基本信息,如用户ID、考试ID、考试次数、最高分数等。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+// 使用 Lombok 的 Data 注解,简化代码,自动生成常用方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档提供该类的描述信息
+@ApiModel(value="考试记录", description="考试记录")
+public class UserExamDTO implements Serializable {
+
+ // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性,避免版本不一致导致的错误
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 考试记录的唯一标识ID,用于在数据库中唯一确定一条考试记录。
+ */
+ private String id;
+
+ /**
+ * 参加考试的用户ID,该字段为必填项,用于关联参加考试的用户。
+ */
+ @ApiModelProperty(value = "用户ID", required=true)
+ private String userId;
+
+ /**
+ * 关联的考试ID,该字段为必填项。
+ * @Dict 注解用于从 `el_exam` 表中根据 `id` 字段获取对应的 `title` 作为字典文本,
+ * 方便在前端展示考试的标题信息。
+ */
+ @Dict(dictTable = "el_exam", dicText = "title", dicCode = "id")
+ @ApiModelProperty(value = "考试ID", required=true)
+ private String examId;
+
+ /**
+ * 用户参加该考试的次数,该字段为必填项,用于统计用户参加特定考试的频率。
+ */
+ @ApiModelProperty(value = "考试次数", required=true)
+ private Integer tryCount;
+
+ /**
+ * 用户参加该考试获得的最高分数,该字段为必填项,用于记录用户在该考试中的最佳成绩。
+ */
+ @ApiModelProperty(value = "最高分数", required=true)
+ private Integer maxScore;
+
+ /**
+ * 用户是否通过该考试,该字段为必填项,用于判断用户在考试中的最终结果。
+ */
+ @ApiModelProperty(value = "是否通过", required=true)
+ private Boolean passed;
+
+ /**
+ * 考试记录的创建时间,记录该考试记录首次被创建的时间。
+ */
+ @ApiModelProperty(value = "创建时间")
+ private Date createTime;
+
+ /**
+ * 考试记录的更新时间,记录该考试记录最后一次被修改的时间。
+ */
+ @ApiModelProperty(value = "更新时间")
+ private Date updateTime;
+
+}
diff --git a/user/exam/dto/request/UserExamReqDTO.java b/user/exam/dto/request/UserExamReqDTO.java
new file mode 100644
index 0000000..594eeb7
--- /dev/null
+++ b/user/exam/dto/request/UserExamReqDTO.java
@@ -0,0 +1,42 @@
+package com.yf.exam.modules.user.exam.dto.request;
+
+// 导入 UserExamDTO 类,作为当前类的父类
+import com.yf.exam.modules.user.exam.dto.UserExamDTO;
+// 导入 Swagger 注解,用于 API 文档生成
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、toString 等方法
+import lombok.Data;
+
+/**
+ *
+ * 考试记录数据传输类,用于封装考试记录相关的请求数据。
+ * 该类继承自 UserExamDTO,可复用其属性,同时添加了额外的请求所需属性。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+// 使用 Lombok 的 Data 注解,自动生成常用方法
+@Data
+// 使用 Swagger 的 ApiModel 注解,为 API 文档提供模型描述
+@ApiModel(value="考试记录", description="考试记录")
+public class UserExamReqDTO extends UserExamDTO {
+
+ // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 考试名称,用于标识具体的考试,在请求中为必填项。
+ */
+ // 使用 Swagger 的 ApiModelProperty 注解,为 API 文档描述该属性
+ @ApiModelProperty(value = "考试名称", required=true)
+ private String title;
+
+ /**
+ * 参考人员的真实姓名,用于关联参加考试的人员,在请求中为必填项。
+ */
+ // 使用 Swagger 的 ApiModelProperty 注解,为 API 文档描述该属性
+ @ApiModelProperty(value = "人员名称", required=true)
+ private String realName;
+}
diff --git a/user/exam/dto/response/UserExamRespDTO.java b/user/exam/dto/response/UserExamRespDTO.java
new file mode 100644
index 0000000..982a836
--- /dev/null
+++ b/user/exam/dto/response/UserExamRespDTO.java
@@ -0,0 +1,36 @@
+package com.yf.exam.modules.user.exam.dto.response;
+
+import com.yf.exam.modules.user.exam.dto.UserExamDTO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ *
+ * 考试记录响应数据传输类,用于封装返回给前端的考试记录相关信息。
+ * 该类继承自 UserExamDTO,复用其属性,同时添加了额外的响应所需属性。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+@Data
+@ApiModel(value="考试记录", description="考试记录")
+public class UserExamRespDTO extends UserExamDTO {
+
+ // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 考试名称,标识具体的考试,该信息在响应中为必填项。
+ */
+ @ApiModelProperty(value = "考试名称", required=true)
+ private String title;
+
+ /**
+ * 参考人员的真实姓名,用于关联参加考试的人员,该信息在响应中为必填项。
+ */
+ @ApiModelProperty(value = "人员名称", required=true)
+ private String realName;
+
+}
diff --git a/user/exam/entity/UserExam.java b/user/exam/entity/UserExam.java
new file mode 100644
index 0000000..b1104ae
--- /dev/null
+++ b/user/exam/entity/UserExam.java
@@ -0,0 +1,85 @@
+package com.yf.exam.modules.user.exam.entity;
+
+// 导入 MyBatis-Plus 主键类型注解,用于指定主键生成策略
+import com.baomidou.mybatisplus.annotation.IdType;
+// 导入 MyBatis-Plus 表字段注解,用于指定实体类属性与数据库表字段的映射关系
+import com.baomidou.mybatisplus.annotation.TableField;
+// 导入 MyBatis-Plus 表主键注解,用于指定实体类的主键属性
+import com.baomidou.mybatisplus.annotation.TableId;
+// 导入 MyBatis-Plus 表名注解,用于指定实体类对应的数据库表名
+import com.baomidou.mybatisplus.annotation.TableName;
+// 导入 MyBatis-Plus 活动记录模型类,实体类继承该类可使用活动记录模式操作数据库
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+// 导入 Lombok 的 Data 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法
+import lombok.Data;
+// 导入日期类,用于表示时间相关的属性
+import java.util.Date;
+
+/**
+ *
+ * 考试记录实体类,对应数据库中的 `el_user_exam` 表,用于封装考试记录的相关信息。
+ * 该类继承自 MyBatis-Plus 的 Model 类,可使用活动记录模式进行数据库操作。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+// 使用 Lombok 的 Data 注解,简化代码,自动生成常用方法
+@Data
+// 使用 MyBatis-Plus 的 TableName 注解,指定该实体类对应的数据库表名为 `el_user_exam`
+@TableName("el_user_exam")
+public class UserExam extends Model {
+
+ // 序列化版本号,用于保证序列化和反序列化过程中类的版本一致性
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 考试记录的唯一标识 ID,对应数据库表中的 `id` 字段。
+ * 使用 MyBatis-Plus 的 TableId 注解指定主键,采用 ASSIGN_ID 策略自动生成 ID。
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 用户 ID,对应数据库表中的 `user_id` 字段,用于关联参加考试的用户。
+ */
+ @TableField("user_id")
+ private String userId;
+
+ /**
+ * 考试 ID,对应数据库表中的 `exam_id` 字段,用于关联具体的考试。
+ */
+ @TableField("exam_id")
+ private String examId;
+
+ /**
+ * 考试次数,对应数据库表中的 `try_count` 字段,记录用户参加该考试的次数。
+ */
+ @TableField("try_count")
+ private Integer tryCount;
+
+ /**
+ * 最高分数,对应数据库表中的 `max_score` 字段,记录用户参加该考试获得的最高分数。
+ */
+ @TableField("max_score")
+ private Integer maxScore;
+
+ /**
+ * 是否通过,对应数据库表中未显式指定字段名(默认按属性名映射),
+ * 记录用户是否通过该考试。
+ */
+ private Boolean passed;
+
+ /**
+ * 创建时间,对应数据库表中的 `create_time` 字段,记录该考试记录的创建时间。
+ */
+ @TableField("create_time")
+ private Date createTime;
+
+ /**
+ * 更新时间,对应数据库表中的 `update_time` 字段,记录该考试记录的更新时间。
+ */
+ @TableField("update_time")
+ private Date updateTime;
+
+}
diff --git a/user/exam/mapper/UserExamMapper.java b/user/exam/mapper/UserExamMapper.java
new file mode 100644
index 0000000..24aa5c5
--- /dev/null
+++ b/user/exam/mapper/UserExamMapper.java
@@ -0,0 +1,37 @@
+package com.yf.exam.modules.user.exam.mapper;
+
+// 导入 MyBatis-Plus 基础 Mapper 接口,提供基本的数据库 CRUD 操作
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+// 导入 MyBatis-Plus 分页查询结果接口,用于封装分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 分页对象,用于设置分页查询的参数
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+// 导入考试记录请求数据传输对象,用于封装查询条件
+import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO;
+// 导入考试记录响应数据传输对象,用于封装查询结果
+import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO;
+// 导入考试记录实体类,对应数据库中的考试记录表
+import com.yf.exam.modules.user.exam.entity.UserExam;
+// 导入 MyBatis 参数注解,用于在 SQL 语句中引用方法参数
+import org.apache.ibatis.annotations.Param;
+
+/**
+ *
+ * 考试记录Mapper接口,用于定义与考试记录相关的数据库操作方法。
+ * 该接口继承自 MyBatis-Plus 的 BaseMapper 接口,可使用其提供的基础数据库操作方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+public interface UserExamMapper extends BaseMapper {
+
+ /**
+ * 分页查询我的考试记录
+ * @param page 分页对象,包含当前页码、每页显示数量等分页信息
+ * @param query 考试记录请求数据传输对象,包含查询条件,如考试名称、参考人员姓名等
+ * @return 包含分页结果的考试记录响应数据传输对象集合,封装在 IPage 中
+ */
+ IPage paging(Page page, @Param("query") UserExamReqDTO query);
+
+}
diff --git a/user/exam/service/UserExamService.java b/user/exam/service/UserExamService.java
new file mode 100644
index 0000000..4c80bf4
--- /dev/null
+++ b/user/exam/service/UserExamService.java
@@ -0,0 +1,50 @@
+package com.yf.exam.modules.user.exam.service;
+
+// 导入 MyBatis-Plus 分页元数据接口,用于封装分页查询结果
+import com.baomidou.mybatisplus.core.metadata.IPage;
+// 导入 MyBatis-Plus 扩展服务接口,提供基础的 CRUD 操作
+import com.baomidou.mybatisplus.extension.service.IService;
+// 导入自定义的分页请求数据传输对象,用于封装分页查询的请求参数
+import com.yf.exam.core.api.dto.PagingReqDTO;
+// 导入考试记录请求数据传输对象,用于封装考试记录查询的具体条件
+import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO;
+// 导入考试记录响应数据传输对象,用于封装考试记录查询的返回结果
+import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO;
+// 导入考试记录实体类,对应数据库中的考试记录数据
+import com.yf.exam.modules.user.exam.entity.UserExam;
+
+/**
+ *
+ * 考试记录业务类,定义了与考试记录相关的业务操作接口。
+ * 该接口继承自 MyBatis-Plus 的 IService 接口,可使用其提供的基础服务方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+public interface UserExamService extends IService {
+
+ /**
+ * 分页查询考试记录数据
+ * @param reqDTO 包含分页信息和考试记录查询条件的请求对象,泛型为 UserExamReqDTO
+ * @return 包含分页结果的考试记录响应对象集合,封装在 IPage 中
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 分页查询当前用户的考试记录数据
+ * @param reqDTO 包含分页信息和考试记录查询条件的请求对象,泛型为 UserExamReqDTO
+ * @return 包含分页结果的考试记录响应对象集合,封装在 IPage 中
+ */
+ IPage myPaging(PagingReqDTO reqDTO);
+
+ /**
+ * 考试完成后,将用户的考试成绩信息加入到考试记录中
+ * 如果该用户针对此考试已有记录,则更新相关信息;若不存在,则创建新记录。
+ * @param userId 参加考试的用户 ID
+ * @param examId 考试的 ID
+ * @param score 用户本次考试获得的分数
+ * @param passed 用户是否通过本次考试
+ */
+ void joinResult(String userId, String examId, Integer score, boolean passed);
+}
diff --git a/user/exam/service/impl/UserExamServiceImpl.java b/user/exam/service/impl/UserExamServiceImpl.java
new file mode 100644
index 0000000..1d0062a
--- /dev/null
+++ b/user/exam/service/impl/UserExamServiceImpl.java
@@ -0,0 +1,116 @@
+package com.yf.exam.modules.user.exam.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.modules.user.UserUtils;
+import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO;
+import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO;
+import com.yf.exam.modules.user.exam.entity.UserExam;
+import com.yf.exam.modules.user.exam.mapper.UserExamMapper;
+import com.yf.exam.modules.user.exam.service.UserExamService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ *
+ * 考试记录业务实现类,实现了 UserExamService 接口,处理考试记录相关的业务逻辑。
+ * 继承自 MyBatis-Plus 的 ServiceImpl 类,可使用其提供的基础服务方法。
+ *
+ *
+ * @author 聪明笨狗
+ * @since 2020-09-21 15:13
+ */
+@Service
+public class UserExamServiceImpl extends ServiceImpl implements UserExamService {
+
+ /**
+ * 分页查询考试记录
+ * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO
+ * @return 包含分页结果的 UserExamRespDTO 对象集合,封装在 IPage 中
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+ // 调用 Mapper 层的分页查询方法,将请求的分页信息和查询参数传入
+ // 并将查询结果转换为包含 UserExamRespDTO 的分页数据
+ IPage pageData = baseMapper.paging(reqDTO.toPage(), reqDTO.getParams());
+ return pageData;
+ }
+
+ /**
+ * 分页查询当前用户的考试记录
+ * @param reqDTO 包含分页信息和查询条件的请求对象,泛型为 UserExamReqDTO
+ * @return 包含分页结果的 UserExamRespDTO 对象集合,封装在 IPage 中
+ */
+ @Override
+ public IPage myPaging(PagingReqDTO reqDTO) {
+ // 获取请求中的查询参数
+ UserExamReqDTO params = reqDTO.getParams();
+
+ // 若查询参数为空,则创建一个新的 UserExamReqDTO 对象
+ if(params == null){
+ params = new UserExamReqDTO();
+ }
+
+ // 设置当前用户的 ID 到查询参数中
+ params.setUserId(UserUtils.getUserId());
+
+ // 调用 Mapper 层的分页查询方法,将请求的分页信息和更新后的查询参数传入
+ // 并将查询结果转换为包含 UserExamRespDTO 的分页数据
+ IPage pageData = baseMapper.paging(reqDTO.toPage(), params);
+ return pageData;
+ }
+
+ /**
+ * 记录用户考试结果,更新或创建考试记录
+ * @param userId 用户 ID
+ * @param examId 考试 ID
+ * @param score 用户本次考试得分
+ * @param passed 用户是否通过本次考试
+ */
+ @Override
+ public void joinResult(String userId, String examId, Integer score, boolean passed) {
+ // 构建查询条件,根据用户 ID 和考试 ID 查询考试记录
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(UserExam::getUserId, userId)
+ .eq(UserExam::getExamId, examId);
+
+ // 根据查询条件获取一条考试记录
+ UserExam record = this.getOne(wrapper, false);
+ if(record == null){
+ // 若记录不存在,则创建一条新的考试记录
+ record = new UserExam();
+ // 设置记录创建时间
+ record.setCreateTime(new Date());
+ // 设置记录更新时间
+ record.setUpdateTime(new Date());
+ // 设置用户 ID
+ record.setUserId(userId);
+ // 设置考试 ID
+ record.setExamId(examId);
+ // 设置最高分数为本次考试得分
+ record.setMaxScore(score);
+ // 设置是否通过考试
+ record.setPassed(passed);
+ // 保存新的考试记录
+ this.save(record);
+ return;
+ }
+
+ // 修复低分数不加入统计问题,增加考试次数
+ record.setTryCount(record.getTryCount() + 1);
+ // 更新记录更新时间
+ record.setUpdateTime(new Date());
+
+ // 若本次考试得分高于之前的最高分数,则更新最高分数和是否通过考试状态
+ if(record.getMaxScore() < score){
+ record.setMaxScore(score);
+ record.setPassed(passed);
+ }
+
+ // 根据记录 ID 更新考试记录
+ this.updateById(record);
+ }
+}