Compare commits

..

1 Commits

Author SHA1 Message Date
1224137507 c4a0683e96 333
10 months ago

@ -1,155 +0,0 @@
<template>
<!-- 使用 Vue 的过渡组件根据传入的 transitionName 决定过渡效果 -->
<transition :name="transitionName">
<!-- visible true 时显示返回顶部按钮应用自定义样式 -->
<div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
<!-- 返回顶部按钮的图标 -->
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height:16px;width:16px"><path d="M12.036 15.59a1 1 0 0 1-.997.995H5.032a.996.996 0 0 1-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" /></svg>
</div>
</transition>
</template>
<script>
export default {
//
name: 'BackToTop',
//
props: {
// 400
visibilityHeight: {
type: Number,
default: 400
},
// 0
backPosition: {
type: Number,
default: 0
},
//
customStyle: {
type: Object,
default: function() {
return {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px',
background: '#e7eaf1'
}
}
},
// 'fade'
transitionName: {
type: String,
default: 'fade'
}
},
//
data() {
return {
//
visible: false,
//
interval: null,
//
isMoving: false
}
},
//
mounted() {
//
window.addEventListener('scroll', this.handleScroll)
},
//
beforeDestroy() {
//
window.removeEventListener('scroll', this.handleScroll)
//
if (this.interval) {
clearInterval(this.interval)
}
},
//
methods: {
//
handleScroll() {
// visibilityHeight
this.visible = window.pageYOffset > this.visibilityHeight
},
//
backToTop() {
//
if (this.isMoving) return
//
const start = window.pageYOffset
let i = 0
//
this.isMoving = true
//
this.interval = setInterval(() => {
// 使
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
if (next <= this.backPosition) {
//
window.scrollTo(0, this.backPosition)
//
clearInterval(this.interval)
//
this.isMoving = false
} else {
//
window.scrollTo(0, next)
}
i++
}, 16.7)
},
/**
* 缓动函数实现二次方的缓入缓出效果
* @param {number} t - 当前时间从动画开始到现在的时间
* @param {number} b - 起始值
* @param {number} c - 变化量最终值 - 起始值
* @param {number} d - 持续时间
* @returns {number} - 当前时间对应的数值
*/
easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b
return -c / 2 * (--t * (t - 2) - 1) + b
}
}
}
</script>
<style scoped>
/* 返回顶部按钮的基础样式 */
.back-to-ceiling {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
/* 返回顶部按钮的悬停样式 */
.back-to-ceiling:hover {
background: #d5dbe7;
}
/* 淡入动画的激活状态样式,过渡时间为 0.5 秒 */
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
/* 淡入动画的起始状态和淡出动画的结束状态样式,透明度为 0 */
.fade-enter,
.fade-leave-to {
opacity: 0
}
/* 返回顶部按钮图标的样式 */
.back-to-ceiling .Icon {
fill: #9aaabf;
background: none;
}
</style>

@ -1,138 +0,0 @@
<template>
<!-- 定义面包屑导航组件使用 Element UI el-breadcrumb 组件设置分隔符为 / -->
<el-breadcrumb class="app-breadcrumb" separator="/">
<!-- 使用过渡组件实现面包屑项切换时的动画效果动画名称为 breadcrumb -->
<transition-group name="breadcrumb">
<!-- 遍历 levelList 数组为每个元素生成一个面包屑项 -->
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<!-- 如果当前项不需要重定向或者是最后一项则显示为普通文本 -->
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
<!-- 否则显示为可点击的链接点击时调用 handleLink 方法 -->
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
// pathToRegexp
import pathToRegexp from 'path-to-regexp'
export default {
/**
* 组件的数据函数返回组件的初始数据
* @returns {Object} 包含 levelList 的数据对象
*/
data() {
return {
//
levelList: null
}
},
/**
* 组件的监听器监听路由变化
*/
watch: {
/**
* 监听 $route 对象的变化
* @param {Object} route - 新的路由对象
*/
$route(route) {
//
if (route.path.startsWith('/redirect/')) {
return
}
// getBreadcrumb
this.getBreadcrumb()
}
},
/**
* 组件创建完成后的钩子函数
*/
created() {
// getBreadcrumb
this.getBreadcrumb()
},
/**
* 组件的方法
*/
methods: {
/**
* 获取面包屑导航数据
*/
getBreadcrumb() {
// meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
//
const first = matched[0]
//
if (!this.isDashboard(first)) {
matched = [{ path: '/dashboard', meta: { title: '控制台' }}].concat(matched)
}
// meta.title meta.breadcrumb false
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
/**
* 判断路由是否为控制台页面
* @param {Object} route - 路由对象
* @returns {boolean} 是否为控制台页面
*/
isDashboard(route) {
//
const name = route && route.name
// false
if (!name) {
return false
}
//
return name.trim().toLocaleLowerCase() === '控制台'.toLocaleLowerCase()
},
/**
* 编译路径解决路径参数问题
* @param {string} path - 原始路径
* @returns {string} 编译后的路径
*/
pathCompile(path) {
//
const { params } = this.$route
// 使 pathToRegexp
var toPath = pathToRegexp.compile(path)
//
return toPath(params)
},
/**
* 处理面包屑项的点击事件
* @param {Object} item - 点击的面包屑项
*/
handleLink(item) {
//
const { redirect, path } = item
//
if (redirect) {
this.$router.push(redirect)
return
}
//
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style lang="scss" scoped>
/* 定义面包屑导航的样式 */
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
/* 定义不可点击的面包屑项的样式 */
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

@ -1,296 +0,0 @@
<template>
<!-- 组件容器使用 app-container 类名 -->
<div class="app-container">
<!-- 筛选条件容器 -->
<div class="filter-container">
<!-- 插槽用于插入自定义筛选内容 -->
<slot name="filter-content" />
<!-- Element UI 行布局 -->
<el-row>
<!-- Element UI 列布局 -->
<el-col>
<!-- options.addRoute 存在时显示添加按钮 -->
<el-button v-if="options.addRoute" type="primary" icon="el-icon-plus" @click="handleAdd"></el-button>
</el-col>
</el-row>
</div>
<!-- multiShow true options.multiActions 存在时显示批量操作容器 -->
<div v-show="multiShow && options.multiActions" class="filter-container">
<!-- Element UI 下拉选择框用于选择批量操作 -->
<el-select v-model="multiNow" :placeholder="selectedLabel" class="filter-item" style="width: 130px" @change="handleOption">
<!-- 循环渲染批量操作选项 -->
<el-option
v-for="item in options.multiActions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<!-- Element UI 表格显示数据列表 -->
<el-table
v-loading="listLoading"
:data="dataList.records"
:header-cell-style="{'background':'#f2f3f4', 'color':'#555', 'font-weight':'bold', 'line-height':'32px'}"
border
fit
highlight-current-row
@selection-change="handleSelection"
>
<!-- options.multi true 时显示多选列 -->
<el-table-column
v-if="options.multi"
align="center"
type="selection"
width="55"
/>
<!-- 插槽用于插入自定义表格列 -->
<slot name="data-columns" />
</el-table>
<!-- 分页组件当总数据量大于 0 时显示 -->
<pagination v-show="dataList.total>0" :total="dataList.total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="getList" />
</div>
</template>
<script>
// API
import { fetchList, deleteData, changeState } from '@/api/common'
//
import Pagination from '@/components/Pagination'
export default {
//
name: 'PagingTable',
//
components: { Pagination },
//
props: {
options: {
type: Object,
default: () => {
return {
//
multiActions: [],
// API
listUrl: '/exam/api',
// API
deleteUrl: '',
// API
stateUrl: '',
//
multi: false
}
}
},
//
listQuery: {
type: Object,
default: () => {
return {
//
current: 1,
//
size: 10,
//
params: {},
//
t: 0
}
}
}
},
data() {
return {
//
dataList: {
//
total: 0
},
//
listLoading: true,
// ID
selectedIds: [],
//
selectedObjs: [],
//
selectedLabel: '',
//
multiShow: false,
//
multiNow: ''
}
},
watch: {
//
listQuery: {
handler() {
this.getList()
},
deep: true
}
},
//
created() {
this.getList()
},
methods: {
/**
* 添加数据跳转方法
* 如果 options.addRoute 存在则跳转到指定路由否则打印提示信息
*/
handleAdd() {
if (this.options.addRoute) {
this.$router.push({ name: this.options.addRoute, params: {}})
return
}
console.log('未设置添加数据跳转路由!')
},
/**
* 查询数据列表方法
* 发送请求获取数据列表并更新组件数据
*/
getList() {
this.listLoading = true
this.listQuery.t = new Date().getTime()
fetchList(this.options.listUrl, this.listQuery).then(response => {
this.dataList = response.data
this.listLoading = false
})
},
/**
* 搜索方法
* 重新调用获取数据列表的方法
*/
handleFilter() {
//
this.getList()
},
/**
* 批量操作回调方法
* 根据选中的批量操作标识执行相应操作
* @param {string} v - 当前选中的批量操作标识
*/
handleOption(v) {
this.multiNow = ''
//
if (v === 'delete') {
this.handleDelete()
return
}
if (v === 'enable') {
this.handleState(0)
return
}
if (v === 'disable') {
this.handleState(1)
return
}
//
this.$emit('multi-actions', { opt: v, ids: this.selectedIds })
},
/**
* 修改状态方法用于启用或禁用数据
* 发送请求修改选中数据的状态并在成功后重新获取数据列表
* @param {number} state - 要修改成的状态值
*/
handleState(state) {
//
changeState(this.options.stateUrl, this.selectedIds, state).then(response => {
if (response.code === 0) {
this.$message({
type: 'success',
message: '状态修改成功!'
})
//
this.getList()
}
})
},
/**
* 删除数据方法
* 先检查是否有选中数据有则弹出确认对话框确认后发送删除请求并重新获取数据列表
*/
handleDelete() {
if (this.selectedIds.length === 0) {
this.$message({
message: '请至少选择一条数据!',
type: 'warning'
})
return
}
//
this.$confirm('确实要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteData(this.options.deleteUrl, this.selectedIds).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
})
this.getList()
})
})
},
/**
* 列表多选操作方法
* 处理表格多选变化事件更新选中数据的相关信息并触发事件
* @param {Array} val - 当前选中的数据对象数组
*/
handleSelection(val) {
const ids = []
val.forEach(row => {
ids.push(row.id)
})
this.selectedObjs = val
this.selectedIds = ids
this.multiShow = ids.length > 0
this.selectedLabel = '已选' + ids.length + '项'
this.$emit('select-changed', { ids: this.selectedIds, objs: this.selectedObjs })
}
}
}
</script>
<style>
/* 筛选容器内筛选项的样式,设置左外边距 */
.filter-container .filter-item{
margin-left: 5px;
}
/* 筛选容器内第一个筛选项的样式,取消左外边距 */
.filter-container .filter-item:first-child{
margin-left: 0px;
}
</style>

@ -1,250 +0,0 @@
<!-- 树状选择器 -->
<template>
<!-- Element UI 弹出框组件用于显示树状菜单 -->
<el-popover
ref="popover"
placement="bottom-start"
trigger="click"
@show="onShowPopover"
@hide="onHidePopover"
>
<!-- Element UI 树组件用于显示部门树状结构 -->
<el-tree
ref="tree"
:style="`min-width: ${treeWidth}`"
:data="data"
:props="props"
:expand-on-click-node="false"
:filter-node-method="filterNode"
placeholder="选择部门"
class="select-tree"
:check-strictly="false"
highlight-current
default-expand-all
@node-click="onClickNode"
/>
<!-- Element UI 输入框组件作为弹出框的触发元素 -->
<el-input
slot="reference"
ref="input"
v-model="labelModel"
:style="`width: ${width}px`"
:class="{ 'rotate': showStatus }"
:placeholder="placeholder"
clearable
suffix-icon="el-icon-arrow-down"
/>
</el-popover>
</template>
<script>
export default {
//
name: 'DepartTree',
// prop value selected
model: {
prop: 'value',
event: 'selected'
},
//
props: {
//
value: String,
//
width: String,
//
options: {
type: Array,
required: true
},
//
placeholder: {
type: String,
required: false,
default: '请选择'
},
//
props: {
type: Object,
required: false,
default: () => ({
parent: 'parentId',
value: 'rowGuid',
label: 'areaName',
children: 'children'
})
}
},
//
data() {
return {
//
showStatus: false,
//
treeWidth: 'auto',
//
labelModel: '',
//
valueModel: '0'
}
},
//
computed: {
//
dataType() {
const jsonStr = JSON.stringify(this.options)
return jsonStr.indexOf(this.props.children) !== -1
},
//
data() {
return this.dataType ? this.options : this.switchTree()
}
},
//
watch: {
//
labelModel(val) {
if (!val) {
this.valueModel = ''
}
this.$refs.tree.filter(val)
},
//
value(val) {
this.labelModel = this.queryTree(this.data, val)
}
},
//
created() {
//
if (this.value) {
this.labelModel = this.queryTree(this.data, this.value)
}
// DOM
this.$nextTick(() => {
this.treeWidth = `${(this.width || this.$refs.input.$refs.input.clientWidth) - 24}px`
})
},
//
methods: {
//
onClickNode(node) {
//
this.labelModel = node[this.props.label]
//
this.valueModel = node[this.props.value]
//
this.onCloseTree()
},
//
switchTree() {
return this.cleanChildren(this.buildTree(this.options, '0'))
},
//
onCloseTree() {
this.$refs.popover.showPopper = false
},
//
onShowPopover() {
//
this.showStatus = true
//
this.$refs.tree.filter(false)
},
//
onHidePopover() {
//
this.showStatus = false
// selected
this.$emit('selected', this.valueModel)
},
//
filterNode(query, data) {
//
if (!query) return true
// label
return data[this.props.label].indexOf(query) !== -1
},
// ID label
queryTree(tree, id) {
let stark = []
stark = stark.concat(tree)
while (stark.length) {
const temp = stark.shift()
if (temp[this.props.children]) {
stark = stark.concat(temp[this.props.children])
}
if (temp[this.props.value] === id) {
return temp[this.props.label]
}
}
return ''
},
//
buildTree(data, id = '0') {
const fa = (parentId) => {
const temp = []
for (let i = 0; i < data.length; i++) {
const n = data[i]
if (n[this.props.parent] === parentId) {
n.children = fa(n.rowGuid)
temp.push(n)
}
}
return temp
}
return fa(id)
},
// children
cleanChildren(data) {
const fa = (list) => {
list.map((e) => {
if (e.children.length) {
fa(e.children)
} else {
delete e.children
}
return e
})
return list
}
return fa(data)
}
}
}
</script>
<style>
/* 带后缀图标的输入框样式 */
.el-input.el-input--suffix {
cursor: pointer;
overflow: hidden;
}
/* 带后缀图标且旋转状态的输入框样式,使后缀图标旋转 180 度 */
.el-input.el-input--suffix.rotate .el-input__suffix {
transform: rotate(180deg);
}
/* 树状选择器样式,设置最大高度和垂直滚动条 */
.select-tree {
max-height: 350px;
overflow-y: scroll;
}
/* 菜单滚动条样式 */
.select-tree::-webkit-scrollbar {
z-index: 11;
width: 6px;
}
.select-tree::-webkit-scrollbar-track,
.select-tree::-webkit-scrollbar-corner {
background: #fff;
}
.select-tree::-webkit-scrollbar-thumb {
border-radius: 5px;
width: 6px;
background: #b4bccc;
}
.select-tree::-webkit-scrollbar-track-piece {
background: #fff;
width: 6px;
}
</style>

@ -1,94 +0,0 @@
<template>
<el-select
v-model="currentValue"
:multiple="multi"
:remote-method="fetchData"
filterable
remote
clearable
placeholder="选择或搜索考试"
class="filter-item"
@change="handlerChange"
>
<el-option
v-for="item in dataList"
:key="item.id"
:label="item.title"
:value="item.id"
/>
</el-select>
</template>
<script>
import { fetchList } from '@/api/exam/exam'
export default {
name: 'ExamSelect',
props: {
// /**
// *
// */
multi: Boolean,
// /**
// *
// */
value: Array,
// /**
// * valueArray
// */
default: String
},
data() {
return {
// /**
// *
// */
dataList: [],
// /**
// *
// */
currentValue: []
}
},
watch: {
// /**
// * valuevaluecurrentValue
// */
value: {
handler() {
this.currentValue = this.value
}
}
},
created() {
// /**
// * currentValuefetchData
// */
this.currentValue = this.value
this.fetchData()
},
methods: {
// /**
// *
// */
fetchData() {
fetchList().then(response => {
this.dataList = response.data.records
})
},
// /**
// *
// * @param e
// */
handlerChange(e) {
console.log(e)
this.$emit('change', e)
this.$emit('input', e)
}
}
}
</script>

@ -1,58 +0,0 @@
// 从 '@/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 })
}

@ -1,31 +0,0 @@
// 从 '@/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 })
}

@ -1,81 +0,0 @@
// 从 '@/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', {})
}

@ -1,13 +0,0 @@
// 从 '@/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 }})
}

@ -1,54 +0,0 @@
// 从 '@/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)
}

@ -1,42 +0,0 @@
// 从 '@/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)
}

@ -1,21 +0,0 @@
// 从 '@/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)
}

@ -1,69 +0,0 @@
// 从 '@/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)
}

@ -1,11 +0,0 @@
// 从 '@/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', {})
}

@ -1,32 +0,0 @@
// 从 '@/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)
}

@ -1,41 +0,0 @@
// 从 '@/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)
}

@ -1,13 +0,0 @@
// 从 '@/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 })
}

@ -1,64 +0,0 @@
// 从 '@/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 })
}

@ -1,82 +0,0 @@
<template>
<!-- 组件根容器 -->
<div>
<!-- 引入本地文件上传子组件使用 v-model 双向绑定文件地址通过 props 传递文件类型提示信息和列表类型 -->
<file-upload-local v-model="fileUrl" :accept="accept" :tips="tips" :list-type="listType" />
</div>
</template>
<script>
//
import FileUploadLocal from './local'
export default {
//
name: 'FileUpload',
//
components: { FileUploadLocal },
//
props: {
//
value: String,
//
accept: {
type: String,
default: '*'
},
//
tips: String,
//
listType: {
type: String,
default: 'picture'
}
},
//
data() {
return {
//
fileUrl: ''
}
},
//
watch: {
// value
value: {
handler() {
// value fillValue
this.fillValue()
}
},
//
fileUrl: {
handler() {
// input
this.$emit('input', this.fileUrl)
}
}
},
//
mounted() {
},
//
created() {
// fillValue
this.fillValue()
},
//
methods: {
/**
* 将父组件传递的 value 属性值赋值给本地的 fileUrl
*/
fillValue() {
this.fileUrl = this.value
}
}
}
</script>

@ -1,30 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,并指定字符编码为 UTF-8 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 的 DTD 规范,用于验证当前 XML 文档结构是否符合 MyBatis Mapper 3.0 标准 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义一个 MyBatis Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来 -->
<!-- namespace 属性值为 Java Mapper 接口的全限定名 -->
<mapper namespace="com.yf.exam.modules.exam.mapper.ExamDepartMapper">
<!-- 通用查询映射结果 -->
<!-- 定义一个结果映射,将数据库查询结果映射到 Java 对象 -->
<!-- id 属性为结果映射的唯一标识,可在其他 SQL 语句中引用 -->
<!-- type 属性指定映射的 Java 对象的全限定类名 -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.exam.entity.ExamDepart">
<!-- 映射数据库表的主键列到 Java 对象的属性 -->
<!-- column 属性为数据库表的列名 -->
<!-- property 属性为 Java 对象的属性名 -->
<id column="id" property="id" />
<!-- 映射数据库表的普通列到 Java 对象的属性 -->
<result column="exam_id" property="examId" />
<result column="depart_id" property="departId" />
</resultMap>
<!-- 通用查询结果列 -->
<!-- 定义一段可复用的 SQL 片段,包含常用的查询列 -->
<!-- id 属性为 SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用 -->
<sql id="Base_Column_List">
`id`,`exam_id`,`depart_id`
</sql>
</mapper>

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证 XML 文档结构是否符合 MyBatis 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,关联到对应的 Java Mapper 接口,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.exam.mapper.ExamMapper">
<!-- 通用查询映射结果,将数据库查询结果映射到 Java 对象 com.yf.exam.modules.exam.entity.Exam -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.exam.entity.Exam">
<!-- 映射数据库表的主键列 id 到 Java 对象的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 title 列到 Java 对象的 title 属性 -->
<result column="title" property="title" />
<!-- 映射数据库表的 content 列到 Java 对象的 content 属性 -->
<result column="content" property="content" />
<!-- 映射数据库表的 open_type 列到 Java 对象的 openType 属性 -->
<result column="open_type" property="openType" />
<!-- 映射数据库表的 state 列到 Java 对象的 state 属性 -->
<result column="state" property="state" />
<!-- 映射数据库表的 time_limit 列到 Java 对象的 timeLimit 属性 -->
<result column="time_limit" property="timeLimit" />
<!-- 映射数据库表的 start_time 列到 Java 对象的 startTime 属性 -->
<result column="start_time" property="startTime" />
<!-- 映射数据库表的 end_time 列到 Java 对象的 endTime 属性 -->
<result column="end_time" property="endTime" />
<!-- 映射数据库表的 create_time 列到 Java 对象的 createTime 属性 -->
<result column="create_time" property="createTime" />
<!-- 映射数据库表的 update_time 列到 Java 对象的 updateTime 属性 -->
<result column="update_time" property="updateTime" />
<!-- 映射数据库表的 total_score 列到 Java 对象的 totalScore 属性 -->
<result column="total_score" property="totalScore" />
<!-- 映射数据库表的 total_time 列到 Java 对象的 totalTime 属性 -->
<result column="total_time" property="totalTime" />
<!-- 映射数据库表的 qualify_score 列到 Java 对象的 qualifyScore 属性 -->
<result column="qualify_score" property="qualifyScore" />
</resultMap>
<!-- 定义通用查询结果列,可在其他 SQL 语句中通过 <include> 标签引用 -->
<sql id="Base_Column_List">
`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`
</sql>
<!-- 定义用于审核分页查询的结果映射,继承自 BaseResultMap扩展了 examUser 和 unreadPaper 字段的映射 -->
<resultMap id="ReviewResultMap"
type="com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO"
extends="BaseResultMap">
<!-- 映射子查询得到的 examUser 列到 Java 对象的 examUser 属性 -->
<result column="examUser" property="examUser" />
<!-- 映射子查询得到的 unreadPaper 列到 Java 对象的 unreadPaper 属性 -->
<result column="unreadPaper" property="unreadPaper" />
</resultMap>
<!-- 定义用于列表查询的结果映射,继承自 BaseResultMap -->
<resultMap id="ListResultMap"
type="com.yf.exam.modules.exam.dto.ExamDTO"
extends="BaseResultMap">
</resultMap>
<!-- 定义分页查询的 SQL 语句,使用 ListResultMap 进行结果映射 -->
<select id="paging" resultMap="ListResultMap">
SELECT * FROM el_exam
<!-- <where> 标签会自动处理 SQL 语句中的 AND 或 OR 关键字,避免多余的连接词 -->
<where>
<!-- 判断查询参数 query 是否不为空 -->
<if test="query!=null">
<!-- 判断查询参数中的 title 是否不为空且不为空字符串 -->
<if test="query.title!=null and query.title!=''">
AND title LIKE CONCAT('%',#{query.title},'%')
</if>
<!-- 判断查询参数中的 openType 是否不为空 -->
<if test="query.openType!=null">
AND open_type = #{query.openType}
</if>
<!-- 判断查询参数中的 startTime 是否不为空 -->
<if test="query.startTime!=null">
AND start_time >= #{query.startTime}
</if>
<!-- 判断查询参数中的 endTime 是否不为空 -->
<if test="query.endTime!=null">
AND end_time &lt;= #{query.endTime}
</if>
</if>
</where>
</select>
<!-- 定义审核分页查询的 SQL 语句,使用 ReviewResultMap 进行结果映射 -->
<select id="reviewPaging" resultMap="ReviewResultMap">
SELECT ex.*,
-- 子查询,统计不同用户参加考试的数量
(SELECT COUNT(DISTINCT user_id) FROM el_paper WHERE exam_id=ex.id) as examUser,
-- 子查询,统计状态为 1 的未批阅试卷数量
(SELECT COUNT(0) FROM el_paper WHERE exam_id=ex.id AND state=1) as unreadPaper
FROM el_exam ex
WHERE ex.has_saq=1
</select>
<!-- 定义在线考试查询的结果映射,继承自 BaseResultMap -->
<resultMap id="OnlineResultMap"
type="com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO"
extends="BaseResultMap">
</resultMap>
<!-- 定义在线考试查询的 SQL 语句,使用 OnlineResultMap 进行结果映射 -->
<select id="online" resultMap="OnlineResultMap">
SELECT ex.*
FROM el_exam ex
LEFT JOIN el_exam_depart dept ON ex.id=dept.exam_id AND ex.open_type=2
LEFT JOIN sys_user uc ON uc.depart_id=dept.depart_id
WHERE ex.state=0 AND (ex.open_type=1 OR ex.open_type=3 OR uc.id='{{userId}}')
<!-- 判断查询参数 query 是否不为空 -->
<if test="query!=null">
<!-- 判断查询参数中的 title 是否不为空且不为空字符串 -->
<if test="query.title!=null and query.title!=''">
AND ex.title LIKE CONCAT('%',#{query.title},'%')
</if>
<!-- 判断查询参数中的 openType 是否不为空 -->
<if test="query.openType!=null">
AND ex.open_type=#{query.openType}
</if>
</if>
</select>
</mapper>

@ -1,60 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.exam.mapper.ExamRepoMapper">
<!-- 通用查询映射结果,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.exam.entity.ExamRepo -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.exam.entity.ExamRepo">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 exam_id 列到 Java 实体类的 examId 属性 -->
<result column="exam_id" property="examId" />
<!-- 映射数据库表的 repo_id 列到 Java 实体类的 repoId 属性 -->
<result column="repo_id" property="repoId" />
<!-- 映射数据库表的 radio_count 列到 Java 实体类的 radioCount 属性 -->
<result column="radio_count" property="radioCount" />
<!-- 映射数据库表的 radio_score 列到 Java 实体类的 radioScore 属性 -->
<result column="radio_score" property="radioScore" />
<!-- 映射数据库表的 multi_count 列到 Java 实体类的 multiCount 属性 -->
<result column="multi_count" property="multiCount" />
<!-- 映射数据库表的 multi_score 列到 Java 实体类的 multiScore 属性 -->
<result column="multi_score" property="multiScore" />
<!-- 映射数据库表的 judge_count 列到 Java 实体类的 judgeCount 属性 -->
<result column="judge_count" property="judgeCount" />
<!-- 映射数据库表的 judge_score 列到 Java 实体类的 judgeScore 属性 -->
<result column="judge_score" property="judgeScore" />
</resultMap>
<!-- 定义通用查询结果列,可在其他 SQL 语句中通过 <include> 标签引用,提高代码复用性 -->
<sql id="Base_Column_List">
`id`,`exam_id`,`repo_id`,`radio_count`,`radio_score`,`multi_count`,`multi_score`,`judge_count`,`judge_score`
</sql>
<!-- 扩展查询映射结果,继承 BaseResultMap将额外的查询结果映射到 Java DTO 类 com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO -->
<resultMap id="ExtResultMap" type="com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO" extends="BaseResultMap">
<!-- 映射子查询结果 totalRadio 列到 Java DTO 类的 totalRadio 属性 -->
<result column="totalRadio" property="totalRadio" />
<!-- 映射子查询结果 totalMulti 列到 Java DTO 类的 totalMulti 属性 -->
<result column="totalMulti" property="totalMulti" />
<!-- 映射子查询结果 totalJudge 列到 Java DTO 类的 totalJudge 属性 -->
<result column="totalJudge" property="totalJudge" />
</resultMap>
<!-- 定义查询方法,根据 examId 查询考试仓库信息,并使用 ExtResultMap 进行结果映射 -->
<select id="listByExam" resultMap="ExtResultMap">
<!-- 查询 el_exam_repo 表的所有列 -->
SELECT ep.*,
<!-- 子查询统计题库中题型为单选题qu_type=1的题目数量 -->
(SELECT COUNT(0) FROM el_qu_repo WHERE repo_id=ep.repo_id AND qu_type=1) AS totalRadio,
<!-- 子查询统计题库中题型为多选题qu_type=2的题目数量 -->
(SELECT COUNT(0) FROM el_qu_repo WHERE repo_id=ep.repo_id AND qu_type=2) AS totalMulti,
<!-- 子查询统计题库中题型为判断题qu_type=3的题目数量 -->
(SELECT COUNT(0) FROM el_qu_repo WHERE repo_id=ep.repo_id AND qu_type=3) AS totalJudge
FROM el_exam_repo ep
<!-- 根据传入的 examId 进行筛选 -->
WHERE ep.exam_id=#{examId}
</select>
</mapper>

@ -1,102 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.paper.mapper.PaperMapper">
<!-- 通用查询映射结果,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.paper.entity.Paper -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.paper.entity.Paper">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 user_id 列到 Java 实体类的 userId 属性 -->
<result column="user_id" property="userId" />
<!-- 映射数据库表的 depart_id 列到 Java 实体类的 departId 属性 -->
<result column="depart_id" property="departId" />
<!-- 映射数据库表的 exam_id 列到 Java 实体类的 examId 属性 -->
<result column="exam_id" property="examId" />
<!-- 映射数据库表的 title 列到 Java 实体类的 title 属性 -->
<result column="title" property="title" />
<!-- 映射数据库表的 total_time 列到 Java 实体类的 totalTime 属性 -->
<result column="total_time" property="totalTime" />
<!-- 映射数据库表的 user_time 列到 Java 实体类的 userTime 属性 -->
<result column="user_time" property="userTime" />
<!-- 映射数据库表的 total_score 列到 Java 实体类的 totalScore 属性 -->
<result column="total_score" property="totalScore" />
<!-- 映射数据库表的 qualify_score 列到 Java 实体类的 qualifyScore 属性 -->
<result column="qualify_score" property="qualifyScore" />
<!-- 映射数据库表的 obj_score 列到 Java 实体类的 objScore 属性 -->
<result column="obj_score" property="objScore" />
<!-- 映射数据库表的 subj_score 列到 Java 实体类的 subjScore 属性 -->
<result column="subj_score" property="subjScore" />
<!-- 映射数据库表的 user_score 列到 Java 实体类的 userScore 属性 -->
<result column="user_score" property="userScore" />
<!-- 映射数据库表的 has_saq 列到 Java 实体类的 hasSaq 属性 -->
<result column="has_saq" property="hasSaq" />
<!-- 映射数据库表的 state 列到 Java 实体类的 state 属性 -->
<result column="state" property="state" />
<!-- 映射数据库表的 create_time 列到 Java 实体类的 createTime 属性 -->
<result column="create_time" property="createTime" />
<!-- 映射数据库表的 update_time 列到 Java 实体类的 updateTime 属性 -->
<result column="update_time" property="updateTime" />
<!-- 映射数据库表的 limit_time 列到 Java 实体类的 limitTime 属性 -->
<result column="limit_time" property="limitTime" />
</resultMap>
<!-- 定义通用查询结果列,可在其他 SQL 语句中通过 <include> 标签引用,提高代码复用性 -->
<sql id="Base_Column_List">
`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`
</sql>
<!-- 定义列表查询结果映射,继承 BaseResultMap将结果映射到 Java DTO 类 com.yf.exam.modules.paper.dto.response.PaperListRespDTO -->
<resultMap id="ListResultMap"
extends="BaseResultMap"
type="com.yf.exam.modules.paper.dto.response.PaperListRespDTO">
<!-- 映射数据库表的 real_name 列到 Java DTO 类的 realName 属性 -->
<result column="real_name" property="realName" />
</resultMap>
<!-- 定义分页查询方法,使用 ListResultMap 进行结果映射 -->
<select id="paging" resultMap="ListResultMap">
<!-- 查询 el_paper 表的所有列和 sys_user 表的 real_name 列 -->
SELECT pp.*,uc.real_name FROM el_paper pp
<!-- 左连接 sys_user 表,通过 user_id 关联 -->
LEFT JOIN sys_user uc ON pp.user_id=uc.id
<!-- <where> 标签会自动处理 SQL 语句中的 AND 关键字,避免多余的连接词 -->
<where>
<!-- 判断查询参数 query 是否不为空 -->
<if test="query!=null">
<!-- 判断查询参数中的 examId 是否不为空且不为空字符串 -->
<if test="query.examId!=null and query.examId!=''">
<!-- 添加筛选条件,根据 examId 过滤 -->
AND pp.exam_id=#{query.examId}
</if>
<!-- 判断查询参数中的 userId 是否不为空且不为空字符串 -->
<if test="query.userId!=null and query.userId!=''">
<!-- 添加筛选条件,根据 userId 过滤 -->
AND pp.user_id=#{query.userId}
</if>
<!-- 判断查询参数中的 departId 是否不为空且不为空字符串 -->
<if test="query.departId!=null and query.departId!=''">
<!-- 添加筛选条件,根据 departId 过滤 -->
AND pp.depart_id=#{query.departId}
</if>
<!-- 判断查询参数中的 state 是否不为空 -->
<if test="query.state!=null">
<!-- 添加筛选条件,根据 state 过滤 -->
AND pp.state=#{query.state}
</if>
<!-- 判断查询参数中的 realName 是否不为空且不为空字符串 -->
<if test="query.realName!=null and query.realName!=''">
<!-- 添加模糊查询条件,根据 realName 过滤 -->
AND uc.real_name LIKE CONCAT('%',#{query.realName},'%')
</if>
</if>
</where>
<!-- 按创建时间倒序排序 -->
ORDER BY create_time DESC
</select>
</mapper>

@ -1,75 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,并指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper">
<!-- 通用查询映射结果 -->
<!-- 定义一个基础结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.paper.entity.PaperQuAnswer -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.paper.entity.PaperQuAnswer">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 paper_id 列到 Java 实体类的 paperId 属性 -->
<result column="paper_id" property="paperId" />
<!-- 映射数据库表的 answer_id 列到 Java 实体类的 answerId 属性 -->
<result column="answer_id" property="answerId" />
<!-- 映射数据库表的 qu_id 列到 Java 实体类的 quId 属性 -->
<result column="qu_id" property="quId" />
<!-- 映射数据库表的 is_right 列到 Java 实体类的 isRight 属性 -->
<result column="is_right" property="isRight" />
<!-- 映射数据库表的 checked 列到 Java 实体类的 checked 属性 -->
<result column="checked" property="checked" />
<!-- 映射数据库表的 sort 列到 Java 实体类的 sort 属性 -->
<result column="sort" property="sort" />
<!-- 映射数据库表的 abc 列到 Java 实体类的 abc 属性 -->
<result column="abc" property="abc" />
</resultMap>
<!-- 通用查询结果列 -->
<!-- 定义一段可复用的 SQL 片段,包含常用的查询列,可在其他 SQL 语句中通过 <include> 标签引用 -->
<sql id="Base_Column_List">
`id`,`paper_id`,`answer_id`,`qu_id`,`is_right`,`checked`,`sort`,`abc`
</sql>
<!-- 定义一个扩展的结果映射,继承自 BaseResultMap将结果映射到 Java DTO 类 com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO -->
<resultMap id="ListResultMap"
type="com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO"
extends="BaseResultMap">
<!-- 映射数据库表的 image 列到 Java DTO 类的 image 属性 -->
<result column="image" property="image" />
<!-- 映射数据库表的 content 列到 Java DTO 类的 content 属性 -->
<result column="content" property="content" />
</resultMap>
<!-- 定义一个查询方法,用于根据试卷 ID 和题目 ID 查询相关答案列表 -->
<!-- id 属性为查询方法的唯一标识,对应 Java Mapper 接口中的方法名 -->
<!-- resultMap 属性指定使用 ListResultMap 进行结果映射 -->
<select id="list" resultMap="ListResultMap">
<!-- 查询指定列,包括 el_paper_qu_answer 表和 el_qu_answer 表的相关列 -->
SELECT pa.`id`,pa.`paper_id`,pa.`answer_id`,pa.`qu_id`,pa.`checked`,pa.`sort`,pa.`abc`,qa.content,qa.image
FROM el_paper_qu_answer pa
<!-- 左连接 el_qu_answer 表,通过 answer_id 和 id 关联 -->
LEFT JOIN el_qu_answer qa ON pa.answer_id=qa.id
<!-- 根据传入的 paperId 和 quId 进行筛选 -->
WHERE pa.paper_id=#{paperId} AND pa.qu_id=#{quId}
<!-- 按 sort 字段升序排序 -->
ORDER BY pa.sort ASC
</select>
<!-- 定义一个查询方法,用于根据试卷 ID 和题目 ID 查询相关答案列表,用于展示场景 -->
<!-- id 属性为查询方法的唯一标识,对应 Java Mapper 接口中的方法名 -->
<!-- resultMap 属性指定使用 ListResultMap 进行结果映射 -->
<select id="listForShow" resultMap="ListResultMap">
<!-- 查询指定列,包括 el_paper_qu_answer 表和 el_qu_answer 表的相关列,包含 is_right 列 -->
SELECT pa.`id`,pa.`paper_id`,pa.`answer_id`,pa.`qu_id`,pa.`checked`,pa.`sort`,pa.`abc`,qa.content,qa.is_right,qa.image
FROM el_paper_qu_answer pa
<!-- 左连接 el_qu_answer 表,通过 answer_id 和 id 关联 -->
LEFT JOIN el_qu_answer qa ON pa.answer_id=qa.id
<!-- 根据传入的 paperId 和 quId 进行筛选 -->
WHERE pa.paper_id=#{paperId} AND pa.qu_id=#{quId}
<!-- 按 sort 字段升序排序 -->
ORDER BY pa.sort ASC
</select>
</mapper>

@ -1,94 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.paper.mapper.PaperQuMapper">
<!-- 通用查询映射结果,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.paper.entity.PaperQu -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.paper.entity.PaperQu">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 paper_id 列到 Java 实体类的 paperId 属性 -->
<result column="paper_id" property="paperId" />
<!-- 映射数据库表的 qu_id 列到 Java 实体类的 quId 属性 -->
<result column="qu_id" property="quId" />
<!-- 映射数据库表的 qu_type 列到 Java 实体类的 quType 属性 -->
<result column="qu_type" property="quType" />
<!-- 映射数据库表的 answered 列到 Java 实体类的 answered 属性 -->
<result column="answered" property="answered" />
<!-- 映射数据库表的 answer 列到 Java 实体类的 answer 属性 -->
<result column="answer" property="answer" />
<!-- 映射数据库表的 sort 列到 Java 实体类的 sort 属性 -->
<result column="sort" property="sort" />
<!-- 映射数据库表的 score 列到 Java 实体类的 score 属性 -->
<result column="score" property="score" />
<!-- 映射数据库表的 actual_score 列到 Java 实体类的 actualScore 属性 -->
<result column="actual_score" property="actualScore" />
<!-- 映射数据库表的 is_right 列到 Java 实体类的 isRight 属性 -->
<result column="is_right" property="isRight" />
</resultMap>
<!-- 通用查询结果列,定义一段可复用的 SQL 片段,包含常用的查询列 -->
<!-- 可在其他 SQL 语句中通过 <include refid="Base_Column_List"/> 标签引用 -->
<sql id="Base_Column_List">
`id`,`paper_id`,`qu_id`,`qu_type`,`answered`,`answer`,`sort`,`score`,`actual_score`,`is_right`
</sql>
<!-- 计算客观题总分 -->
<!-- id: 查询方法的唯一标识,对应 Java Mapper 接口中的方法名 -->
<!-- resultType: 查询结果的数据类型,这里为整数类型 -->
<select id="sumObjective" resultType="int">
<!-- 使用 IFNULL 函数处理 SUM(actual_score) 为 NULL 的情况,若为 NULL 则返回 0将结果命名为 total -->
SELECT IFNULL(SUM(actual_score),0) as total
FROM el_paper_qu
<!-- 根据传入的 paperId 进行筛选 -->
WHERE paper_id=#{paperId}
<!-- 筛选出回答正确的记录 -->
AND is_right=true
<!-- 筛选出题型小于 4 的客观题 -->
AND qu_type &lt; 4
</select>
<!-- 计算主观题总分 -->
<!-- id: 查询方法的唯一标识,对应 Java Mapper 接口中的方法名 -->
<!-- resultType: 查询结果的数据类型,这里为整数类型 -->
<select id="sumSubjective" resultType="int">
<!-- 使用 IFNULL 函数处理 SUM(actual_score) 为 NULL 的情况,若为 NULL 则返回 0将结果命名为 total -->
SELECT IFNULL(SUM(actual_score),0) as total
FROM el_paper_qu
<!-- 根据传入的 paperId 进行筛选 -->
WHERE paper_id=#{paperId}
<!-- 筛选出题型为 4 的主观题 -->
AND qu_type=4
</select>
<!-- 扩展查询结果映射,继承自 BaseResultMap将结果映射到 Java DTO 类 com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO -->
<resultMap id="ListResultMap" extends="BaseResultMap" type="com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO">
<!-- 映射数据库表的 image 列到 Java DTO 类的 image 属性 -->
<result column="image" property="image" />
<!-- 映射数据库表的 content 列到 Java DTO 类的 content 属性 -->
<result column="content" property="content" />
<!-- 定义一个集合映射,将关联查询结果映射到 Java DTO 类的 answerList 属性 -->
<!-- column: 传递给关联查询的参数paperId 和 quId -->
<!-- select: 指定关联查询的方法,调用 PaperQuAnswerMapper 中的 listForShow 方法 -->
<collection property="answerList" column="{paperId=paper_id,quId=qu_id}"
select="com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper.listForShow" />
</resultMap>
<!-- 根据试卷 ID 查询题目列表 -->
<!-- id: 查询方法的唯一标识,对应 Java Mapper 接口中的方法名 -->
<!-- resultMap: 指定使用 ListResultMap 进行结果映射 -->
<select id="listByPaper" resultMap="ListResultMap">
<!-- 查询 el_paper_qu 表的所有列以及 el_qu 表的 content 和 image 列 -->
SELECT pq.*,eq.content,eq.image
FROM el_paper_qu pq
<!-- 左连接 el_qu 表,通过 qu_id 和 id 关联 -->
LEFT JOIN el_qu eq ON pq.qu_id = eq.id
<!-- 根据传入的 paperId 进行筛选 -->
WHERE pq.paper_id=#{paperId}
<!-- 按 sort 字段升序排序 -->
ORDER BY pq.sort ASC
</select>
</mapper>

@ -1,38 +0,0 @@
<!-- 声明 XML 文档的版本为 1.0,并指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD 规范,用于验证当前 XML 文档是否符合 MyBatis Mapper 标准 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.qu.mapper.QuAnswerMapper">
<!-- 通用查询映射结果
定义一个基础结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.qu.entity.QuAnswer
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.qu.entity.QuAnswer">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射数据库表的普通列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<result column="qu_id" property="quId" />
<result column="is_right" property="isRight" />
<result column="image" property="image" />
<result column="content" property="content" />
<result column="analysis" property="analysis" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
`id`,`qu_id`,`is_right`,`image`,`content`,`analysis`
</sql>
</mapper>

@ -1,158 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证 XML 文档结构是否符合 MyBatis 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,关联到对应的 Java Mapper 接口,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.qu.mapper.QuMapper">
<!-- 通用查询映射结果 -->
<!-- 定义一个结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.qu.entity.Qu -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.qu.entity.Qu">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 qu_type 列到 Java 实体类的 quType 属性 -->
<result column="qu_type" property="quType" />
<!-- 映射数据库表的 level 列到 Java 实体类的 level 属性 -->
<result column="level" property="level" />
<!-- 映射数据库表的 image 列到 Java 实体类的 image 属性 -->
<result column="image" property="image" />
<!-- 映射数据库表的 content 列到 Java 实体类的 content 属性 -->
<result column="content" property="content" />
<!-- 映射数据库表的 create_time 列到 Java 实体类的 createTime 属性 -->
<result column="create_time" property="createTime" />
<!-- 映射数据库表的 update_time 列到 Java 实体类的 updateTime 属性 -->
<result column="update_time" property="updateTime" />
<!-- 映射数据库表的 remark 列到 Java 实体类的 remark 属性 -->
<result column="remark" property="remark" />
<!-- 映射数据库表的 analysis 列到 Java 实体类的 analysis 属性 -->
<result column="analysis" property="analysis" />
</resultMap>
<!-- 通用查询结果列 -->
<!-- 定义一段可复用的 SQL 片段,包含常用的查询列,可在其他 SQL 语句中通过 <include> 标签引用 -->
<sql id="Base_Column_List">
`id`,`qu_type`,`level`,`image`,`content`,`create_time`,`update_time`,`remark`,`analysis`
</sql>
<!-- 随机取数据 -->
<!-- 定义一个查询方法,根据条件随机获取题目数据 -->
<select id="listByRandom" resultMap="BaseResultMap">
<!-- 从 el_qu 表中选取所有列,别名为 a -->
SELECT a.*
FROM el_qu a
<!-- 左连接 el_qu_repo 表,别名为 b通过 id 和 qu_id 关联 -->
LEFT JOIN el_qu_repo b ON a.id=b.qu_id
<!-- 筛选条件:指定题库 ID 和题目类型 -->
WHERE b.repo_id=#{repoId} AND a.qu_type=#{quType}
<!-- 判断 excludes 参数是否不为空 -->
<if test="excludes!=null">
<!-- 排除指定 ID 的题目 -->
AND a.id NOT IN
<!-- 遍历 excludes 集合,将集合元素用逗号分隔,用括号包裹 -->
<foreach item="item" collection="excludes" separator="," open="(" close=")" index="">'${item}'</foreach>
</if>
<!-- 按随机顺序排序 -->
ORDER BY RAND()
<!-- 限制返回结果数量 -->
LIMIT ${size}
</select>
<!-- 定义导出数据的结果映射,将数据库查询结果映射到 Java DTO 类 com.yf.exam.modules.qu.dto.export.QuExportDTO -->
<resultMap id="ExportResultMap" type="com.yf.exam.modules.qu.dto.export.QuExportDTO">
<!-- 映射数据库表的 q_id 列到 Java DTO 类的 qId 属性 -->
<id column="q_id" property="qId" />
<!-- 映射数据库表的 qu_type 列到 Java DTO 类的 quType 属性 -->
<result column="qu_type" property="quType" />
<!-- 映射数据库表的 q_content 列到 Java DTO 类的 qContent 属性 -->
<result column="q_content" property="qContent" />
<!-- 映射数据库表的 q_analysis 列到 Java DTO 类的 qAnalysis 属性 -->
<result column="q_analysis" property="qAnalysis" />
<!-- 映射数据库表的 a_is_right 列到 Java DTO 类的 aIsRight 属性 -->
<result column="a_is_right" property="aIsRight" />
<!-- 映射数据库表的 a_content 列到 Java DTO 类的 aContent 属性 -->
<result column="a_content" property="aContent" />
<!-- 映射数据库表的 a_analysis 列到 Java DTO 类的 aAnalysis 属性 -->
<result column="a_analysis" property="aAnalysis" />
<!-- 定义一个集合映射,通过 q_id 作为参数调用 selectRepos 方法,将结果映射到 repoList 属性 -->
<collection property="repoList" column="q_id" select="selectRepos"/>
</resultMap>
<!-- 定义一个查询方法,根据题目 ID 查询所属题库 ID 列表 -->
<select id="selectRepos" resultType="String">
<!-- 从 el_qu_repo 表中选取 repo_id 列 -->
SELECT repo_id FROM el_qu_repo po WHERE po.qu_id=#{qId}
</select>
<!-- 定义查询条件的 SQL 片段,可在其他 SQL 语句中通过 <include> 标签引用 -->
<sql id="query">
<!-- <where> 标签会自动处理 SQL 语句中的 AND 关键字,避免多余的连接词 -->
<where>
<!-- 判断 query 参数是否不为空 -->
<if test="query!=null">
<!-- 判断 query 中的 quType 属性是否不为空 -->
<if test="query.quType!=null">
<!-- 添加筛选条件,根据题目类型过滤 -->
AND q.qu_type = #{query.quType}
</if>
<!-- 判断 query 中的 repoIds 属性是否不为空且集合大小大于 0 -->
<if test="query.repoIds!=null and query.repoIds.size()>0">
<!-- 添加筛选条件,根据题库 ID 列表过滤 -->
AND po.repo_id IN
<!-- 遍历 query.repoIds 集合,将集合元素用逗号分隔,用括号包裹 -->
<foreach collection="query.repoIds" open="(" close=")" separator="," item="repoId">#{repoId}</foreach>
</if>
<!-- 判断 query 中的 content 属性是否不为空且不为空字符串 -->
<if test="query.content!=null and query.content!=''">
<!-- 添加模糊查询条件,根据题目内容过滤 -->
AND q.content LIKE CONCAT('%',#{query.content},'%')
</if>
<!-- 判断 query 中的 excludes 属性是否不为空且集合大小大于 0 -->
<if test="query.excludes!=null and query.excludes.size()>0">
<!-- 添加排除条件,排除指定 ID 的题目 -->
AND q.id NOT IN
<!-- 遍历 query.excludes 集合,将集合元素用逗号分隔,用括号包裹 -->
<foreach collection="query.excludes" open="(" close=")" separator="," item="quId">
#{quId}
</foreach>
</if>
</if>
</where>
</sql>
<!-- 定义分页查询方法,使用 BaseResultMap 进行结果映射 -->
<select id="paging" resultMap="BaseResultMap">
<!-- 从 el_qu 表中选取所有列,别名为 q -->
SELECT q.*
FROM el_qu q
<!-- 左连接 el_qu_repo 表,别名为 po通过 id 和 qu_id 关联 -->
LEFT JOIN el_qu_repo po ON q.id=po.qu_id
<!-- 引用 query SQL 片段,添加查询条件 -->
<include refid="query" />
<!-- 按题目 ID 分组,按更新时间倒序排序 -->
GROUP BY q.id ORDER BY q.update_time DESC
</select>
<!-- 定义导出数据的查询方法,使用 ExportResultMap 进行结果映射 -->
<select id="listForExport" resultMap="ExportResultMap">
<!-- 选取需要导出的列,并指定别名 -->
SELECT
q.id as q_id,
q.qu_type,
q.content AS q_content,
q.analysis as q_analysis,
a.content as a_content,
a.is_right as a_is_right,
a.analysis as a_analysis
FROM el_qu q
<!-- 左连接 el_qu_answer 表,别名为 a通过 id 和 qu_id 关联 -->
LEFT JOIN el_qu_answer a ON q.id=a.qu_id
<!-- 左连接 el_qu_repo 表,别名为 po通过 id 和 qu_id 关联 -->
LEFT JOIN el_qu_repo po ON q.id=po.qu_id
<!-- 引用 query SQL 片段,添加查询条件 -->
<include refid="query" />
<!-- 按答案 ID 分组,按题目 ID 排序 -->
GROUP BY a.id ORDER BY q.id
<!-- 限制返回结果数量为 10000 条 -->
LIMIT 10000
</select>
</mapper>

@ -1,38 +0,0 @@
<!-- 声明 XML 文档的版本为 1.0,并指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.qu.mapper.QuRepoMapper">
<!-- 通用查询映射结果
定义一个结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.qu.entity.QuRepo
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.qu.entity.QuRepo">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射数据库表的普通列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<result column="qu_id" property="quId" />
<result column="repo_id" property="repoId" />
<result column="qu_type" property="quType" />
<result column="sort" property="sort" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
`id`,`qu_id`,`repo_id`,`qu_type`,`sort`
</sql>
</mapper>

@ -1,76 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.repo.mapper.RepoMapper">
<!-- 通用查询映射结果 -->
<!-- 定义一个基础结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.repo.entity.Repo -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.repo.entity.Repo">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 code 列到 Java 实体类的 code 属性 -->
<result column="code" property="code" />
<!-- 映射数据库表的 title 列到 Java 实体类的 title 属性 -->
<result column="title" property="title" />
<!-- 映射数据库表的 remark 列到 Java 实体类的 remark 属性 -->
<result column="remark" property="remark" />
<!-- 映射数据库表的 create_time 列到 Java 实体类的 createTime 属性 -->
<result column="create_time" property="createTime" />
<!-- 映射数据库表的 update_time 列到 Java 实体类的 updateTime 属性 -->
<result column="update_time" property="updateTime" />
</resultMap>
<!-- 通用查询结果列 -->
<!-- 定义一段可复用的 SQL 片段,包含常用的查询列,可在其他 SQL 语句中通过 <include> 标签引用 -->
<sql id="Base_Column_List">
`id`,`code`,`title`,`radio_count`,`multi_count`,`judge_count`,`remark`,`create_time`,`update_time`
</sql>
<!-- 定义列表查询结果映射,继承自 BaseResultMap将结果映射到 Java DTO 类 com.yf.exam.modules.repo.dto.response.RepoRespDTO -->
<resultMap id="ListResultMap"
type="com.yf.exam.modules.repo.dto.response.RepoRespDTO"
extends="BaseResultMap">
<!-- 映射数据库表的 radio_count 列到 Java DTO 类的 radioCount 属性 -->
<result column="radio_count" property="radioCount" />
<!-- 映射数据库表的 multi_count 列到 Java DTO 类的 multiCount 属性 -->
<result column="multi_count" property="multiCount" />
<!-- 映射数据库表的 judge_count 列到 Java DTO 类的 judgeCount 属性 -->
<result column="judge_count" property="judgeCount" />
</resultMap>
<!-- 定义分页查询方法,使用 ListResultMap 进行结果映射 -->
<select id="paging" resultMap="ListResultMap">
<!-- 查询指定列,包括子查询得到的题型数量 -->
SELECT `id`, `code`, `title`, `remark`, `create_time`, `update_time`,
-- 子查询统计题库中题型为单选题qu_type=1的题目数量
(SELECT COUNT(0) FROM el_qu_repo WHERE repo_id=repo.id AND qu_type=1) AS radio_count,
-- 子查询统计题库中题型为多选题qu_type=2的题目数量
(SELECT COUNT(0) FROM el_qu_repo WHERE repo_id=repo.id AND qu_type=2) AS multi_count,
-- 子查询统计题库中题型为判断题qu_type=3的题目数量
(SELECT COUNT(0) FROM el_qu_repo WHERE repo_id=repo.id AND qu_type=3) AS judge_count
FROM el_repo repo
<!-- <where> 标签会自动处理 SQL 语句中的 AND 关键字,避免多余的连接词 -->
<where>
<!-- 判断查询参数 query 是否不为空 -->
<if test="query!=null">
<!-- 判断查询参数中的 title 是否不为空且不为空字符串 -->
<if test="query.title!=null and query.title!=''">
-- 添加模糊查询条件,根据 title 过滤
AND repo.title LIKE CONCAT('%',#{query.title}, '%')
</if>
<!-- 判断查询参数中的 excludes 集合是否不为空且元素数量大于 0 -->
<if test="query.excludes!=null and query.excludes.size()>0">
-- 添加排除条件,排除指定 id 的记录
AND repo.id NOT IN
<!-- 遍历 query.excludes 集合,将集合元素用逗号分隔,用括号包裹 -->
<foreach collection="query.excludes" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</if>
</if>
</where>
</select>
</mapper>

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD 规范,用于验证 XML 文档结构是否符合 MyBatis Mapper 标准 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,关联到对应的 Java Mapper 接口,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.sys.depart.mapper.SysDepartMapper">
<!-- 通用查询映射结果,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.sys.depart.entity.SysDepart -->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.sys.depart.entity.SysDepart">
<!-- 映射数据库表的主键列 id 到 Java 实体类的 id 属性 -->
<id column="id" property="id" />
<!-- 映射数据库表的 dept_type 列到 Java 实体类的 deptType 属性 -->
<result column="dept_type" property="deptType" />
<!-- 映射数据库表的 parent_id 列到 Java 实体类的 parentId 属性 -->
<result column="parent_id" property="parentId" />
<!-- 映射数据库表的 dept_name 列到 Java 实体类的 deptName 属性 -->
<result column="dept_name" property="deptName" />
<!-- 映射数据库表的 dept_code 列到 Java 实体类的 deptCode 属性 -->
<result column="dept_code" property="deptCode" />
<!-- 映射数据库表的 sort 列到 Java 实体类的 sort 属性 -->
<result column="sort" property="sort" />
</resultMap>
<!-- 通用查询结果列,定义可复用的 SQL 片段,包含常用查询列,可在其他 SQL 语句中引用 -->
<sql id="Base_Column_List">
`id`,`dept_type`,`parent_id`,`dept_name`,`dept_code`,`sort`
</sql>
<!-- 树形结构查询结果映射,将结果映射到 Java DTO 类 com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO继承自 BaseResultMap -->
<resultMap id="TreeResultMap"
type="com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO"
extends="BaseResultMap">
<!-- 集合映射,将关联查询结果映射到 Java DTO 类的 children 属性 -->
<!-- column: 传递给关联查询的参数,这里传递 id 列的值 -->
<!-- select: 指定关联查询的方法,调用本 Mapper 中的 findChildren 方法 -->
<collection property="children" column="id" select="findChildren"></collection>
</resultMap>
<!-- 查询指定父部门下的子部门列表,使用 TreeResultMap 进行结果映射 -->
<select id="findChildren" resultMap="TreeResultMap">
<!-- 从 sys_depart 表中查询所有列,筛选出 parent_id 等于传入参数 id 的记录 -->
SELECT * FROM sys_depart WHERE parent_id=#{id}
</select>
<!-- 分页查询顶级部门列表,使用 TreeResultMap 进行结果映射 -->
<select id="paging" resultMap="TreeResultMap">
<!-- 从 sys_depart 表中查询所有列,筛选出 parent_id 等于 '0' 的顶级部门记录 -->
SELECT * FROM sys_depart WHERE parent_id='0'
<!-- 判断查询参数 query 是否不为空 -->
<if test="query!=null">
<!-- 判断查询参数中的 deptName 属性是否不为空且不为空字符串 -->
<if test="query.deptName!=null and query.deptName!=''">
<!-- 添加模糊查询条件,根据部门名称过滤 -->
AND dept_name LIKE CONCAT('%',#{query.deptName},'%')
</if>
</if>
</select>
</mapper>

@ -1,16 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.sys.system.mapper.SysDictMapper">
<!-- 定义一个查询方法,方法名为 findDict将查询结果映射为 String 类型 -->
<!-- 该方法用于从指定表中根据键值对查询单个字段的值 -->
<select id="findDict" resultType="String">
<!-- 使用动态参数构建 SQL 语句,从指定表中查询指定字段 -->
<!-- 注意:使用 ${} 进行参数替换,存在 SQL 注入风险 -->
SELECT ${text} FROM ${table} WHERE ${key}=${value} LIMIT 1
</select>
</mapper>

@ -1,34 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.sys.user.mapper.SysRoleMapper">
<!-- 通用查询映射结果
定义一个结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.sys.user.entity.SysRole
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.sys.user.entity.SysRole">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射数据库表的普通列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<result column="role_name" property="roleName" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
`id`,`role_name`
</sql>
</mapper>

@ -1,48 +0,0 @@
<!-- 声明 XML 文档版本为 1.0,指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.sys.user.mapper.SysUserMapper">
<!-- 通用查询映射结果
定义一个结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.sys.user.entity.SysUser
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.sys.user.entity.SysUser">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射数据库表的用户名字段到 Java 实体类的属性 -->
<result column="user_name" property="userName" />
<!-- 映射数据库表的真实姓名字段到 Java 实体类的属性 -->
<result column="real_name" property="realName" />
<!-- 映射数据库表的密码字段到 Java 实体类的属性 -->
<result column="password" property="password" />
<!-- 映射数据库表的盐值字段到 Java 实体类的属性,通常用于密码加密 -->
<result column="salt" property="salt" />
<!-- 映射数据库表的角色 ID 字段到 Java 实体类的属性 -->
<result column="role_ids" property="roleIds" />
<!-- 映射数据库表的部门 ID 字段到 Java 实体类的属性 -->
<result column="depart_id" property="departId" />
<!-- 映射数据库表的创建时间字段到 Java 实体类的属性 -->
<result column="create_time" property="createTime" />
<!-- 映射数据库表的更新时间字段到 Java 实体类的属性 -->
<result column="update_time" property="updateTime" />
<!-- 映射数据库表的状态字段到 Java 实体类的属性 -->
<result column="state" property="state" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
-- 定义需要查询的列,使用反引号避免与 SQL 关键字冲突
`id`,`user_name`,`real_name`,`password`,`salt`,`role_ids`,`depart_id`,`create_time`,`update_time`,`state`
</sql>
</mapper>

@ -1,33 +0,0 @@
<!-- 声明 XML 文档的版本为 1.0,并指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.sys.user.mapper.SysUserRoleMapper">
<!-- 通用查询映射结果
定义一个结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.sys.user.entity.SysUserRole
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.sys.user.entity.SysUserRole">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射数据库表的用户 ID 列到 Java 实体类的属性 -->
<result column="user_id" property="userId" />
<!-- 映射数据库表的角色 ID 列到 Java 实体类的属性 -->
<result column="role_id" property="roleId" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
`id`,`user_id`,`role_id`
</sql>
</mapper>

@ -1,46 +0,0 @@
<!-- 声明 XML 文档的版本为 1.0,并指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.user.book.mapper.UserBookMapper">
<!-- 通用查询映射结果
定义一个结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.user.book.entity.UserBook
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.user.book.entity.UserBook">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射数据库表的 exam_id 列到 Java 实体类的 examId 属性 -->
<result column="exam_id" property="examId" />
<!-- 映射数据库表的 user_id 列到 Java 实体类的 userId 属性 -->
<result column="user_id" property="userId" />
<!-- 映射数据库表的 qu_id 列到 Java 实体类的 quId 属性 -->
<result column="qu_id" property="quId" />
<!-- 映射数据库表的 create_time 列到 Java 实体类的 createTime 属性 -->
<result column="create_time" property="createTime" />
<!-- 映射数据库表的 update_time 列到 Java 实体类的 updateTime 属性 -->
<result column="update_time" property="updateTime" />
<!-- 映射数据库表的 wrong_count 列到 Java 实体类的 wrongCount 属性 -->
<result column="wrong_count" property="wrongCount" />
<!-- 映射数据库表的 title 列到 Java 实体类的 title 属性 -->
<result column="title" property="title" />
<!-- 映射数据库表的 sort 列到 Java 实体类的 sort 属性 -->
<result column="sort" property="sort" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
-- 定义需要查询的列,使用反引号避免与 SQL 关键字冲突
`id`,`exam_id`,`user_id`,`qu_id`,`create_time`,`update_time`,`wrong_count`,`title`,`sort`
</sql>
</mapper>

@ -1,101 +0,0 @@
<!-- 声明 XML 文档的版本为 1.0,并指定字符编码为 UTF-8确保文档能正确处理各种字符 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义文档类型,引用 MyBatis Mapper 3.0 的 DTD用于验证当前 XML 文档是否符合 MyBatis Mapper 规范 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义 Mapper 命名空间,将此 XML 映射文件与对应的 Java Mapper 接口关联起来,方便 MyBatis 找到对应的操作方法 -->
<mapper namespace="com.yf.exam.modules.user.exam.mapper.UserExamMapper">
<!-- 通用查询映射结果
定义一个基础结果映射,将数据库查询结果映射到 Java 实体类 com.yf.exam.modules.user.exam.entity.UserExam
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java 实体类的全限定名
-->
<resultMap id="BaseResultMap" type="com.yf.exam.modules.user.exam.entity.UserExam">
<!-- 映射数据库表的主键列到 Java 实体类的属性
column: 数据库表的列名
property: Java 实体类的属性名
-->
<id column="id" property="id" />
<!-- 映射用户 ID 列到 Java 实体类的 userId 属性 -->
<result column="user_id" property="userId" />
<!-- 映射考试 ID 列到 Java 实体类的 examId 属性 -->
<result column="exam_id" property="examId" />
<!-- 映射尝试次数列到 Java 实体类的 tryCount 属性 -->
<result column="try_count" property="tryCount" />
<!-- 映射最高分数列到 Java 实体类的 maxScore 属性 -->
<result column="max_score" property="maxScore" />
<!-- 映射是否通过列到 Java 实体类的 passed 属性 -->
<result column="passed" property="passed" />
<!-- 映射创建时间列到 Java 实体类的 createTime 属性 -->
<result column="create_time" property="createTime" />
<!-- 映射更新时间列到 Java 实体类的 updateTime 属性 -->
<result column="update_time" property="updateTime" />
</resultMap>
<!-- 通用查询结果列
定义一段可复用的 SQL 片段,包含常用的查询列
id: SQL 片段的唯一标识,可在其他 SQL 语句中通过 <include> 标签引用
-->
<sql id="Base_Column_List">
`id`,`user_id`,`exam_id`,`try_count`,`max_score`,`passed`,`create_time`,`update_time`
</sql>
<!-- 列表查询结果映射
定义一个结果映射,继承自 BaseResultMap将数据库查询结果映射到 Java DTO 类 com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO
id: 结果映射的唯一标识,可在其他 SQL 语句中引用
type: 指定映射的 Java DTO 类的全限定名
extends: 继承的基础结果映射
-->
<resultMap id="ListResultMap"
type="com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO"
extends="BaseResultMap">
<!-- 映射考试标题列到 Java DTO 类的 title 属性 -->
<result column="title" property="title" />
<!-- 映射用户真实姓名列到 Java DTO 类的 realName 属性 -->
<result column="real_name" property="realName" />
</resultMap>
<!-- 分页查询方法
定义一个查询方法,根据条件进行分页查询,使用 ListResultMap 进行结果映射
id: 查询方法的唯一标识,对应 Java Mapper 接口中的方法名
resultMap: 指定使用的结果映射
-->
<select id="paging" resultMap="ListResultMap">
<!-- 从 el_user_exam 表中选取所有列,同时选取 el_exam 表的 title 列和 sys_user 表的 real_name 列 -->
SELECT ue.*,ee.title,uc.real_name FROM el_user_exam ue
<!-- 左连接 el_exam 表,通过 exam_id 和 id 关联 -->
LEFT JOIN el_exam ee ON ue.exam_id=ee.id
<!-- 左连接 sys_user 表,通过 user_id 和 id 关联 -->
LEFT JOIN sys_user uc ON ue.user_id=uc.id
<!-- 筛选条件:确保 el_exam 表和 sys_user 表的关联记录存在 -->
WHERE ee.id IS NOT NULL AND uc.id IS NOT NULL
<!-- 判断查询参数 query 是否不为空 -->
<if test="query!=null">
<!-- 判断查询参数中的 userId 属性是否不为空且不为空字符串 -->
<if test="query.userId!=null and query.userId!=''">
<!-- 注意:此处存在错误,应使用 #{query.userId} 进行参数绑定,避免 SQL 注入
正确写法AND ue.user_id = #{query.userId}
-->
AND ue.user_id='{{userId}}'
</if>
<!-- 判断查询参数中的 examId 属性是否不为空且不为空字符串 -->
<if test="query.examId!=null and query.examId!=''">
<!-- 添加筛选条件,根据考试 ID 过滤 -->
AND ue.exam_id = #{query.examId}
</if>
<!-- 判断查询参数中的 title 属性是否不为空且不为空字符串 -->
<if test="query.title!=null and query.title!=''">
<!-- 添加模糊查询条件,根据考试标题过滤 -->
AND ee.title LIKE CONCAT('%',#{query.title},'%')
</if>
<!-- 判断查询参数中的 realName 属性是否不为空且不为空字符串 -->
<if test="query.realName!=null and query.realName!=''">
<!-- 添加模糊查询条件,根据用户真实姓名过滤 -->
AND uc.real_name LIKE CONCAT('%',#{query.realName},'%')
</if>
</if>
</select>
</mapper>

@ -1,132 +0,0 @@
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;
/**
* <p>
* HTTP
* </p>
*
* @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<RepoDTO> 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<IPage<RepoRespDTO>> paging(@RequestBody PagingReqDTO<RepoReqDTO> reqDTO) {
// 调用服务层方法进行分页查询并转换结果
IPage<RepoRespDTO> 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();
}
}

@ -1,67 +0,0 @@
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;
/**
* <p>
* Serializable
* </p>
*
* @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;
}

@ -1,40 +0,0 @@
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;
/**
* <p>
* RepoDTO
* </p>
*
* @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<String> excludes;
/**
*
*
*
*/
@ApiModelProperty(value = "单选题数量", required=true)
private String title;
}

@ -1,45 +0,0 @@
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;
/**
* <p>
*
* RepoDTO
* </p>
*
* @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;
}

@ -1,62 +0,0 @@
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;
/**
* <p>
* `el_repo`
* MyBatis-Plus Model 使 ActiveRecord
* </p>
*
* @author
* @since 2020-05-25 13:23
*/
@Data
// 表明该实体类对应数据库中的 el_repo 表
@TableName("el_repo")
public class Repo extends Model<Repo> {
// 序列化版本号,确保序列化和反序列化的兼容性
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;
}

@ -1,31 +0,0 @@
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;
/**
* <p>
* Mapper MyBatis-Plus BaseMapper
* BaseMapper
* </p>
*
* @author
* @since 2020-05-25 13:23
*/
public interface RepoMapper extends BaseMapper<Repo> {
/**
*
*
* @param page
* @param query ID
* @return RepoRespDTO RepoRespDTO
*/
IPage<RepoRespDTO> paging(Page page, @Param("query") RepoReqDTO query);
}

@ -1,38 +0,0 @@
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;
/**
* <p>
*
* MyBatis-Plus IService 使 CRUD
* </p>
*
* @author
* @since 2020-05-25 13:23
*/
public interface RepoService extends IService<Repo> {
/**
*
*
* @param reqDTO
* PagingReqDTO RepoReqDTO ID
* @return IPage RepoRespDTO
*/
IPage<RepoRespDTO> paging(PagingReqDTO<RepoReqDTO> reqDTO);
/**
*
*
* @param reqDTO ID
* DTO ID ID
*/
void save(RepoDTO reqDTO);
}

@ -1,53 +0,0 @@
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;
/**
* <p>
* RepoService MyBatis-Plus ServiceImpl
*
* </p>
*
* @author
* @since 2020-05-25 13:23
*/
@Service
public class RepoServiceImpl extends ServiceImpl<RepoMapper, Repo> implements RepoService {
/**
*
*
* @param reqDTO
* @return IPage RepoRespDTO
*/
@Override
public IPage<RepoRespDTO> paging(PagingReqDTO<RepoReqDTO> 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);
}
}

@ -1,50 +0,0 @@
package com.yf.exam.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
//
// * 网关全局设置,允许跨域
// * @author bool
// * @date 2019-08-13 17:28
//
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
// 创建一个基于URL的CORS配置源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 创建一个新的CORS配置对象
CorsConfiguration config = new CorsConfiguration();
// 允许发送凭据如cookies
config.setAllowCredentials(true);
// 允许所有来源的请求
config.addAllowedOrigin(CorsConfiguration.ALL);
// 允许所有请求头
config.addAllowedHeader(CorsConfiguration.ALL);
// 允许所有请求方法
config.addAllowedMethod(CorsConfiguration.ALL);
// 将CORS配置注册到所有路径
source.registerCorsConfiguration("/**", config);
// 创建一个新的FilterRegistrationBean实例并注册CorsFilter
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
// 设置该过滤器的优先级为最高
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
// 返回配置好的FilterRegistrationBean实例
return bean;
}
}

@ -1,28 +0,0 @@
package com.yf.exam.config;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import javax.servlet.MultipartConfigElement;
// * 文件上传配置
// * @author bool
// * @date 2019-07-29 16:23
//
@Configuration
public class MultipartConfig {
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 单个数据大小
factory.setMaxFileSize(DataSize.ofMegabytes(5000L));
// 总上传数据大小
factory.setMaxRequestSize(DataSize.ofMegabytes(5000L));
return factory.createMultipartConfig();
}
}

@ -1,37 +0,0 @@
package com.yf.exam.config;
import com.yf.exam.aspect.mybatis.QueryInterceptor;
import com.yf.exam.aspect.mybatis.UpdateInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// * Mybatis过滤器配置
// * 注意:必须按顺序进行配置,否则容易出现业务异常
// * @author bool
@Configuration
@MapperScan("com.yf.exam.modules.**.mapper")
public class MybatisConfig {
// * 数据查询过滤器
@Bean
public QueryInterceptor queryInterceptor() {
QueryInterceptor query = new QueryInterceptor();
query.setLimit(-1L);
return query;
}
// * 插入数据过滤器
@Bean
public UpdateInterceptor updateInterceptor() {
return new UpdateInterceptor();
}
}

@ -1,93 +0,0 @@
package com.yf.exam.config;
import lombok.extern.log4j.Log4j2;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
// * 任务调度配置类,启用定时任务和异步任务的支持。
// *
// * @author bool
@Log4j2
@Configuration
@EnableScheduling
@EnableAsync
public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer {
// * 定义定时任务使用的线程池。
// *
// * @return 配置好的ThreadPoolTaskScheduler实例
@Bean(destroyMethod = "shutdown", name = "taskScheduler")
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 设置线程池大小为10
scheduler.setThreadNamePrefix("task-"); // 设置线程名称前缀为"task-"
scheduler.setAwaitTerminationSeconds(600); // 设置等待终止时间为600秒
scheduler.setWaitForTasksToCompleteOnShutdown(true); // 设置在关闭时等待任务完成
return scheduler;
}
// * 定义异步任务执行使用的线程池。
// *
// * @return 配置好的ThreadPoolTaskExecutor实例
@Bean(name = "asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 设置核心线程池大小为10
executor.setQueueCapacity(1000); // 设置队列容量为1000
executor.setKeepAliveSeconds(600); // 设置线程空闲时间为600秒
executor.setMaxPoolSize(20); // 设置最大线程池大小为20
executor.setThreadNamePrefix("taskExecutor-"); // 设置线程名称前缀为"taskExecutor-"
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 设置拒绝策略为CallerRunsPolicy
executor.initialize(); // 初始化线程池
return executor;
}
// * 配置定时任务注册器,使用自定义的线程池。
// *
// * @param scheduledTaskRegistrar 定时任务注册器
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = taskScheduler(); // 获取自定义的线程池调度器
scheduledTaskRegistrar.setTaskScheduler(taskScheduler); // 设置定时任务注册器使用的线程池调度器
}
// * 获取异步任务执行器。
// *
// * @return 自定义的ThreadPoolTaskExecutor实例
@Override
public Executor getAsyncExecutor() {
return asyncExecutor(); // 返回自定义的异步任务执行器
}
// * 定义异步任务未捕获异常的处理逻辑。
// *
// * @return 异步任务未捕获异常的处理程序
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
log.error("异步任务执行出现异常, message {}, method {}, params {}", throwable.getMessage(), method, objects);
};
}
}

@ -1,153 +0,0 @@
package com.yf.exam.config;
import com.yf.exam.ability.shiro.CNFilterFactoryBean;
import com.yf.exam.ability.shiro.ShiroRealm;
import com.yf.exam.ability.shiro.aop.JwtFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
// * Shiro配置类。
// * 该类配置了Shiro的安全管理器、过滤器链等组件。
// *
// * @author bool
// */
@Slf4j
@Configuration
public class ShiroConfig {
// * Filter Chain定义说明
// * 1、一个URL可以配置多个Filter使用逗号分隔。
// * 2、当设置多个过滤器时全部验证通过才视为通过。
// * 3、部分过滤器可指定参数如permsroles。
// /**
// * 创建ShiroFilterFactoryBean实例。
// *
// * @param securityManager 安全管理器。
// * @return ShiroFilterFactoryBean实例。
// */
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器链定义
Map<String, String> map = new LinkedHashMap<>();
// 需要排除的一些接口,允许匿名访问
map.put("/exam/api/sys/user/login", "anon");
map.put("/exam/api/sys/user/reg", "anon");
map.put("/exam/api/sys/user/quick-reg", "anon");
// 获取网站基本信息,允许匿名访问
map.put("/exam/api/sys/config/detail", "anon");
// 文件读取接口,允许匿名访问
map.put("/upload/file/**", "anon");
map.put("/", "anon");
map.put("/v2/**", "anon");
map.put("/doc.html", "anon");
map.put("/**/*.js", "anon");
map.put("/**/*.css", "anon");
map.put("/**/*.html", "anon");
map.put("/**/*.svg", "anon");
map.put("/**/*.pdf", "anon");
map.put("/**/*.jpg", "anon");
map.put("/**/*.png", "anon");
map.put("/**/*.ico", "anon");
// 字体文件,允许匿名访问
map.put("/**/*.ttf", "anon");
map.put("/**/*.woff", "anon");
map.put("/**/*.woff2", "anon");
map.put("/druid/**", "anon");
map.put("/swagger-ui.html", "anon");
map.put("/swagger**/**", "anon");
map.put("/webjars/**", "anon");
// 添加自定义的JWT过滤器
Map<String, Filter> filterMap = new HashMap<>(1);
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// 其他所有URL使用JWT过滤器
map.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
// /**
// * 创建DefaultWebSecurityManager实例。
// *
// * @param myRealm 自定义的Shiro Realm。
// * @return DefaultWebSecurityManager实例。
// */
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
// 禁用session存储
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
// /**
// * 添加注解支持创建DefaultAdvisorAutoProxyCreator实例。
// *
// * @return DefaultAdvisorAutoProxyCreator实例。
// */
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
return defaultAdvisorAutoProxyCreator;
}
// /**
// * 创建LifecycleBeanPostProcessor实例。
// *
// * @return LifecycleBeanPostProcessor实例。
// */
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
// /**
// * 创建AuthorizationAttributeSourceAdvisor实例。
// *
// * @param securityManager 安全管理器。
// * @return AuthorizationAttributeSourceAdvisor实例。
// */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}

@ -1,72 +0,0 @@
package com.yf.exam.config;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
// * Swagger配置类启用Swagger和SwaggerBootstrapUI配置API文档信息和安全方案。
// *
// * @author bool
// * @date 2020/8/19 20:53
// */
@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
@ConfigurationProperties(prefix = "swagger")
public class SwaggerConfig {
//
// /**
// * 创建并配置Swagger Docket实例用于生成考试模块接口的API文档。
// *
// * @return 配置好的Docket实例
// */
@Bean
public Docket examApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()) // 设置API信息
.groupName("考试模块接口") // 设置API分组名称
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 选择带有ApiOperation注解的方法
.paths(PathSelectors.ant("/exam/api/**")) // 选择路径匹配/exam/api/**的API
.build()
.securitySchemes(Collections.singletonList(securityScheme())); // 添加安全方案
}
// /**
// * 构建API信息对象包含标题、描述、联系人信息和版本号。
// *
// * @return API信息对象
// */
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("考试系统接口") // 设置API文档的标题
.description("考试系统接口") // 设置API文档的描述
.contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) // 设置联系人信息
.version("1.0.0") // 设置API文档的版本号
.build();
}
// /**
// * 定义授权头部用于API的安全验证。
// *
// * @return 安全方案对象
// */
@Bean
SecurityScheme securityScheme() {
return new ApiKey("token", "token", "header"); // 定义一个API Key名称为"token",位于请求头中
}
}

@ -1,32 +0,0 @@
package com.yf.exam.core.annon;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
///**
// * 数据字典注解,用于标识字段需要从数据字典中获取对应的文本信息。
// * @author bool
// */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
// /**
// * 必须指定的数据字典编码,用于唯一标识一个数据字典。
// * @return 数据字典编码。
// */
String dicCode();
// /**
// * 可选的数据字典文本,默认为空字符串。
// * @return 数据字典文本。
// */
String dicText() default "";
// /**
// * 可选的数据字典表名,默认为空字符串。
// * @return 数据字典表名。
// */
String dictTable() default "";
}

@ -1,118 +0,0 @@
package com.yf.exam.core.api;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.Serializable;
///**
// * 全局错误码定义,用于定义接口的响应数据。
// * 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。
// * @author bool
// * @date 2019-06-14 21:15
// */
@NoArgsConstructor
@AllArgsConstructor
public enum ApiError implements Serializable {
//通用错误,接口参数不全
ERROR_10010001("参数不全或类型错误!"),
// /**
// * 用户未登录错误
// */
ERROR_10010002("您还未登录,请先登录!"),
// /**
// * 数据不存在错误
// */
ERROR_10010003("数据不存在!"),
// /**
// * 图形验证码错误
// */
ERROR_10010012("图形验证码错误!"),
// /**
// * 短信验证码错误
// */
ERROR_10010013("短信验证码错误!"),
// /**
// * 重复评论错误
// */
ERROR_10010014("不允许重复评论!"),
// /**
// * 考试相关错误,试题被删除
// */
ERROR_20010001("试题被删除,无法继续考试!"),
// /**
// * 考试相关错误,用户有正在进行的考试
// */
ERROR_20010002("您有正在进行的考试!"),
// /**
// * 账号不存在错误
// */
ERROR_90010001("账号不存在,请确认!"),
// /**
// * 账号或密码错误
// */
ERROR_90010002("账号或密码错误!"),
// /**
// * 角色配置错误,至少要包含一个角色
// */
ERROR_90010003("至少要包含一个角色!"),
// /**
// * 管理员账号错误,无法修改
// */
ERROR_90010004("管理员账号无法修改!"),
// /**
// * 账号被禁用错误
// */
ERROR_90010005("账号被禁用,请联系管理员!"),
// /**
// * 活动用户不足错误,无法开启竞拍
// */
ERROR_90010006("活动用户不足,无法开启竞拍!"),
// /**
// * 旧密码错误
// */
ERROR_90010007("旧密码不正确,请确认!"),
// /**
// * 数据不存在错误(重复定义)
// */
ERROR_60000001("数据不存在!");
// /**
// * 错误消息
// */
public String msg;
// /**
// * 生成Markdown格式文档用于更新文档用的
// *
// * @param args 命令行参数
// */
public static void main(String[] args) {
for (ApiError e : ApiError.values()) {
System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',");
}
}
// /**
// * 获取错误码
// *
// * @return 错误码
// */
public Integer getCode(){
return Integer.parseInt(this.name().replace("ERROR_", ""));
}
}

@ -1,64 +0,0 @@
package com.yf.exam.core.api;
import com.yf.exam.core.api.ApiError;
import com.yf.exam.core.exception.ServiceException;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
///**
// * 数据结果返回的封装
// * @author bool
// * @date 2018/11/20 09:48
// */
@Data
@NoArgsConstructor
@ApiModel(value="接口响应", description="接口响应")
public class ApiRest<T>{
// /**
// * 响应消息
// */
@ApiModelProperty(value = "响应消息")
private String msg;
// /**
// * 响应代码
// */
@ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true)
private Integer code;
// /**
// * 请求或响应body
// */
@ApiModelProperty(value = "响应内容")
protected T data;
// /**
// * 是否成功
// * @return
// */
public boolean isSuccess(){
return code.equals(0);
}
// /**
// * 构造函数
// * @param error
// */
public ApiRest(ServiceException error){
this.code = error.getCode();
this.msg = error.getMsg();
}
// /**
// * 构造函数
// * @param error
// */
public ApiRest(ApiError error){
this.code = error.getCode();
this.msg = error.msg;
}
}

@ -1,160 +0,0 @@
package com.yf.exam.core.api.controller;
import com.yf.exam.core.api.ApiError;
import com.yf.exam.core.api.ApiRest;
import com.yf.exam.core.exception.ServiceException;
///**
// * 基础控制器提供构建API响应的通用方法。
// * @author Dav
// */
public class BaseController {
// /**
// * 成功默认状态码。
// */
private static final Integer CODE_SUCCESS = 0;
// /**
// * 成功默认消息。
// */
private static final String MSG_SUCCESS = "操作成功!";
// /**
// * 失败默认状态码。
// */
private static final Integer CODE_FAILURE = 1;
// /**
// * 失败默认消息。
// */
private static final String MSG_FAILURE = "请求失败!";
// /**
// * 构造一个包含状态码、消息和数据的API响应对象。
// *
// * @param code 表示响应的状态码,通常用于标识请求的成功或失败。
// * @param message 与状态码相关联的简短消息,描述响应的结果或错误原因。
// * @param data 响应中包含的业务数据可以是任何类型的对象。如果数据为null则不设置响应的数据部分。
// * @param <T> 泛型类型,表示数据对象的类型。
// * @return 构建好的ApiRest对象包含了传入的状态码、消息和数据。
// */
protected <T> ApiRest<T> message(Integer code, String message, T data){
ApiRest<T> response = new ApiRest<>();
response.setCode(code);
response.setMsg(message);
if(data != null) {
response.setData(data);
}
return response;
}
// /**
// * 请求成功时返回一个空数据的API响应对象。
// *
// * @param <T> 数据对象的类型。
// * @return 包含成功状态码和默认成功消息的ApiRest对象。
// */
protected <T> ApiRest<T> success(){
return message(CODE_SUCCESS, MSG_SUCCESS, null);
}
// /**
// * 请求成功时返回一个包含自定义消息和数据的API响应对象。
// *
// * @param message 自定义的成功消息。
// * @param data 响应中包含的业务数据。
// * @param <T> 数据对象的类型。
// * @return 包含成功状态码、自定义消息和数据的ApiRest对象。
// */
protected <T> ApiRest<T> success(String message, T data){
return message(CODE_SUCCESS, message, data);
}
// /**
// * 请求成功时返回一个仅包含默认消息和数据的API响应对象。
// *
// * @param data 响应中包含的业务数据。
// * @param <T> 数据对象的类型。
// * @return 包含成功状态码、默认成功消息和数据的ApiRest对象。
// */
protected <T> ApiRest<T> success(T data){
return message(CODE_SUCCESS, MSG_SUCCESS, data);
}
// /**
// * 请求失败时返回一个包含自定义状态码、消息和数据的API响应对象。
// *
// * @param code 自定义的失败状态码。
// * @param message 自定义的失败消息。
// * @param data 响应中包含的业务数据。
// * @param <T> 数据对象的类型。
// * @return 包含自定义状态码、消息和数据的ApiRest对象。
// */
protected <T> ApiRest<T> failure(Integer code, String message, T data){
return message(code, message, data);
}
// /**
// * 请求失败时返回一个包含自定义消息和数据的API响应对象。
// *
// * @param message 自定义的失败消息。
// * @param data 响应中包含的业务数据。
// * @param <T> 数据对象的类型。
// * @return 包含失败状态码、自定义消息和数据的ApiRest对象。
// */
protected <T> ApiRest<T> failure(String message, T data){
return message(CODE_FAILURE, message, data);
}
// /**
// * 请求失败时返回一个仅包含自定义消息的API响应对象。
// *
// * @param message 自定义的失败消息。
// * @return 包含失败状态码和自定义消息的ApiRest对象。
// */
protected <T> ApiRest<T> failure(String message){
return message(CODE_FAILURE, message, null);
}
// /**
// * 请求失败时返回一个包含默认失败消息和数据的API响应对象。
// *
// * @param data 响应中包含的业务数据。
// * @param <T> 数据对象的类型。
// * @return 包含失败状态码、默认失败消息和数据的ApiRest对象。
// */
protected <T> ApiRest<T> failure(T data){
return message(CODE_FAILURE, MSG_FAILURE, data);
}
// /**
// * 请求失败时返回一个仅包含默认失败消息的API响应对象。
// *
// * @param <T> 数据对象的类型。
// * @return 包含失败状态码和默认失败消息的ApiRest对象。
// */
protected <T> ApiRest<T> failure(){
return message(CODE_FAILURE, MSG_FAILURE, null);
}
// /**
// * 请求失败时返回一个包含ApiError中定义的状态码、消息和数据的API响应对象。
// *
// * @param error 包含错误信息的ApiError对象。
// * @param data 响应中包含的业务数据。
// * @param <T> 数据对象的类型。
// * @return 包含ApiError中定义的状态码、消息和数据的ApiRest对象。
// */
protected <T> ApiRest<T> failure(ApiError error, T data){
return message(error.getCode(), error.msg, data);
}
// /**
// * 请求失败时返回一个包含ServiceException中定义的状态码和消息的API响应对象。
// *
// * @param ex 包含异常信息的ServiceException对象。
// * @param <T> 数据对象的类型。
// * @return 包含ServiceException中定义的状态码和消息的ApiRest对象。
// */
protected <T> ApiRest<T> failure(ServiceException ex){
ApiRest<T> apiRest = message(ex.getCode(), ex.getMsg(), null);
return apiRest;
}
}

@ -1,15 +0,0 @@
package com.yf.exam.core.api.dto;
import lombok.Data;
import java.io.Serializable;
///**
// * 请求和响应的基础类,用于处理序列化
// * @author dav
// * @date 2019/3/16 15:56
// */
@Data
public class BaseDTO implements Serializable {
}

@ -1,32 +0,0 @@
package com.yf.exam.core.api.dto;
import com.yf.exam.core.api.dto.BaseDTO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
///**
// * <p>
// * 主键通用请求类用于根据ID查询
// * </p>
// * @author 聪明笨狗
// * @since 2019-04-20 12:15
// */
@Data
@ApiModel(value="主键通用请求类", description="用于根据ID查询的通用请求类")
public class BaseIdReqDTO extends BaseDTO {
// /**
// * 主键ID用于标识需要查询的实体。
// * @since 2019-04-20 12:15
// */
@ApiModelProperty(value = "主键ID", required=true)
private String id;
// /**
// * 用户ID用于标识发起请求的用户。
// * 该字段在序列化时将被忽略,不返回给客户端。
// */
@JsonIgnore
private String userId;
}

@ -1,28 +0,0 @@
package com.yf.exam.core.api.dto;
import com.yf.exam.core.api.dto.BaseDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
///**
// * <p>
// * 主键通用响应类,用于在添加操作后返回内容
// * </p>
// * @author 聪明笨狗
// * @since 2019-04-20 12:15
// */
@Data
@ApiModel(value="主键通用响应类", description="在添加操作后返回的通用响应类")
@AllArgsConstructor
@NoArgsConstructor
public class BaseIdRespDTO extends BaseDTO {
// /**
// * 主键ID用于标识添加的实体。
// * @since 2019-04-20 12:15
// */
@ApiModelProperty(value = "主键ID", required=true)
private String id;
}

@ -1,34 +0,0 @@
package com.yf.exam.core.api.dto;
import com.yf.exam.core.api.dto.BaseDTO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
// * <p>
// * 通用ID列表类操作用于批量删除、修改状态等
// * </p>
// * @author bool
// * @date 2019-08-01 19:07
// */
@Data
@ApiModel(value="删除参数", description="用于批量删除或修改状态的请求参数")
public class BaseIdsReqDTO extends BaseDTO {
// /**
// * 用户ID用于标识发起请求的用户。
// * 该字段在序列化时将被忽略,不返回给客户端。
// */
@JsonIgnore
private String userId;
// /**
// * 要删除的ID列表用于指定需要批量删除或修改状态的实体。
// *
// * @since 2019-08-01 19:07
// */
@ApiModelProperty(value = "要删除的ID列表", required = true)
private List<String> ids;
}

@ -1,34 +0,0 @@
package com.yf.exam.core.api.dto;
import com.yf.exam.core.api.dto.BaseDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
///**
// * <p>
// * 通用状态请求类,用于批量修改对象的状态
// * </p>
// * @author 聪明笨狗
// * @since 2019-04-20 12:15
// */
@Data
@ApiModel(value="通用状态请求类", description="用于批量修改对象状态的请求参数")
@AllArgsConstructor
@NoArgsConstructor
public class BaseStateReqDTO extends BaseDTO {
// /**
// * 要修改对象的ID列表。该字段是必需的。
// */
@ApiModelProperty(value = "要修改对象的ID列表", required=true)
private List<String> ids;
// /**
// * 通用状态0表示正常1表示禁用。该字段是必需的。
// */
@ApiModelProperty(value = "通用状态0为正常1为禁用", required=true)
private Integer state;
}

@ -1,61 +0,0 @@
package com.yf.exam.core.api.dto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
///**
// * 分页查询类,用于封装分页请求参数
// * @param <T> 查询参数的类型
// * @author bool
// */
@ApiModel(value="分页参数", description="用于分页查询的请求参数")
@Data
public class PagingReqDTO<T> {
// /**
// * 当前页码,必需字段。
// * 例如1 表示第一页。
// */
@ApiModelProperty(value = "当前页码", required = true, example = "1")
private Integer current;
// /**
// * 每页数量,必需字段。
// * 例如10 表示每页显示10条记录。
// */
@ApiModelProperty(value = "每页数量", required = true, example = "10")
private Integer size;
// /**
// * 查询参数,可以包含具体的查询条件。
// */
@ApiModelProperty(value = "查询参数")
private T params;
// /**
// * 排序字符,用于指定查询结果的排序方式。
// */
@ApiModelProperty(value = "排序字符")
private String orderBy;
// /**
// * 当前用户的ID用于标识发起请求的用户。
// * 该字段在序列化时将被忽略,不返回给客户端。
// */
@JsonIgnore
@ApiModelProperty(value = "当前用户的ID")
private String userId;
// * 将当前分页请求参数转换成MyBatis的简单分页对象。
// * @return Page 对象,包含当前页码和每页数量
// */
public Page toPage() {
Page page = new Page();
page.setCurrent(this.current);
page.setSize(this.size);
return page;
}
}

@ -1,28 +0,0 @@
package com.yf.exam.core.api.dto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
///**
// * 分页响应类继承自MyBatis Plus的Page类用于封装分页查询结果
// * @param <T> 分页数据的类型
// * @author bool
// * @date 2019-07-20 15:17
// */
public class PagingRespDTO<T> extends Page<T> {
// /**
// * 获取页面总数量
// * @return 页面总数量
// */
@Override
public long getPages() {
if (this.getSize() == 0L) {
return 0L;
} else {
long pages = this.getTotal() / this.getSize();
if (this.getTotal() % this.getSize() != 0L) {
++pages;
}
return pages;
}
}
}

@ -1,47 +0,0 @@
package com.yf.exam.core.api.utils;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
///**
// * JSON数据转换器用于转换返回消息的格式
// * @author dav
// * @date 2018/9/11 19:30
// */
public class JsonConverter {
// /**
// * FastJson消息转换器
// * @return
// */
public static HttpMessageConverter fastConverter() {
// 定义一个convert转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 添加FastJson的配置信息
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 默认转换器
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
SerializerFeature.WriteNullNumberAsZero,
SerializerFeature.MapSortField,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteDateUseDateFormat,
SerializerFeature.WriteNullListAsEmpty);
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
// 处理中文乱码问题
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastConverter.setSupportedMediaTypes(fastMediaTypes);
// 在convert中添加配置信息
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}
}

@ -1,19 +0,0 @@
package com.yf.exam.core.enums;
///**
// * 通用的状态枚举信息
// *
// * @author bool
// * @date 2019-09-17 17:57
// */
public interface CommonState {
// /**
// * 普通状态,正常的
// */
Integer NORMAL = 0;
// /**
// * 非正常状态,禁用,下架等
// */
Integer ABNORMAL = 1;
}

@ -1,18 +0,0 @@
package com.yf.exam.core.enums;
///**
// * 开放方式
// * @author bool
// */
public interface OpenType {
// /**
// * 完全开放
// */
Integer OPEN = 1;
// /**
// * 部门开放
// */
Integer DEPT_OPEN = 2;
}

@ -1,51 +0,0 @@
package com.yf.exam.core.exception;
import com.yf.exam.core.api.ApiError;
import com.yf.exam.core.api.ApiRest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends RuntimeException{
// /**
// * 错误码
// */
private Integer code;
// /**
// * 错误消息
// */
private String msg;
// /**
// * 从结果初始化
// * @param apiRest
// */
public ServiceException(ApiRest apiRest){
this.code = apiRest.getCode();
this.msg = apiRest.getMsg();
}
// /**
// * 从枚举中获取参数
// * @param apiError
// */
public ServiceException(ApiError apiError){
this.code = apiError.getCode();
this.msg = apiError.msg;
}
/**
*
* @param msg
*/
public ServiceException(String msg){
this.code = 1;
this.msg = msg;
}
}

@ -1,46 +0,0 @@
package com.yf.exam.core.exception;
import com.yf.exam.core.api.ApiRest;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
///**
// * 统一异常处理类
// * @author bool
// * @date 2019-06-21 19:27
// */
@RestControllerAdvice
public class ServiceExceptionHandler {
// /**
// * 应用到所有@RequestMapping注解方法在其执行之前初始化数据绑定器
// * @param binder
// */
@InitBinder
public void initWebBinder(WebDataBinder binder){
}
// /**
// * 把值绑定到Model中使全局@RequestMapping可以获取到该值
// * @param model
// */
@ModelAttribute
public void addAttribute(Model model) {
}
// /**
// * 捕获ServiceException
// * @param e
// * @return
// */
@ExceptionHandler({com.yf.exam.core.exception.ServiceException.class})
@ResponseStatus(HttpStatus.OK)
public ApiRest serviceExceptionHandler(ServiceException e) {
return new ApiRest(e);
}
}

@ -1,59 +0,0 @@
package com.yf.exam.core.utils;
import org.dozer.DozerBeanMapper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
///**
// * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现:
// *
// * 1. 持有Mapper的单例.
// * 2. 返回值类型转换.
// * 3. 批量转换Collection中的所有对象.
// * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数.
// *
// */
public class BeanMapper {
// /**
// * 持有Dozer单例, 避免重复创建DozerMapper消耗资源.
// */
private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
// /**
// * 基于Dozer转换对象的类型.
// */
public static <T> T map(Object source, Class<T> destinationClass) {
return dozerBeanMapper.map(source, destinationClass);
}
// /**
// * 基于Dozer转换Collection中对象的类型.
// */
public static <T> List<T> mapList(Iterable<?> sourceList, Class<T> destinationClass) {
List<T> destinationList = new ArrayList();
for (Object sourceObject : sourceList) {
T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass);
destinationList.add(destinationObject);
}
return destinationList;
}
// /**
// * 基于Dozer将对象A的值拷贝到对象B中.
// */
public static void copy(Object source, Object destinationObject) {
if(source!=null) {
dozerBeanMapper.map(source, destinationObject);
}
}
public static <T, S> List<T> mapList(Collection<S> source, Function<? super S, ? extends T> mapper) {
return source.stream().map(mapper).collect(Collectors.toList());
}
}

@ -1,31 +0,0 @@
package com.yf.exam.core.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
///**
// * 时间转换quartz表达式
// * @author bool
// * @date 2020/11/29 下午3:00
// */
public class CronUtils {
// /**
// * 格式化数据
// */
private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy";
// /**
// * 准确的时间点到表达式
// * @param date
// * @return
// */
public static String dateToCron(final Date date){
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT);
String formatTimeStr = "";
if (date != null) {
formatTimeStr = fmt.format(date);
}
return formatTimeStr;
}
}

@ -1,103 +0,0 @@
package com.yf.exam.core.utils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
///**
// * 日期处理工具类
// * ClassName: DateUtils <br/>
// * date: 2018年12月13日 下午6:34:02 <br/>
// *
// * @author Bool
// * @version
// */
public class DateUtils {
// /**
// *
// * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数 <br/>
// * @author Bool
// * @param userCreateTime
// * @return
// * @since JDK 1.6
// */
public static int calcExpDays(Date userCreateTime){
Calendar start = Calendar.getInstance();
start.setTime(userCreateTime);
Calendar now = Calendar.getInstance();
now.setTime(new Date());
long l = now.getTimeInMillis() - start.getTimeInMillis();
int days = new Long(l / (1000 * 60 * 60 * 24)).intValue();
return days;
}
// /**
// *
// * dateNow:获取当前时间的字符串格式,根据传入的格式化来展示. <br/>
// * @author Bool
// * @param format 日期格式化
// * @return
// */
public static String dateNow(String format) {
SimpleDateFormat fmt = new SimpleDateFormat(format);
Calendar c = new GregorianCalendar();
return fmt.format(c.getTime());
}
// /**
// * formatDate:格式化日期,返回指定的格式 <br/>
// * @author Bool
// * @param time
// * @param format
// * @return
// */
public static String formatDate(Date time, String format) {
SimpleDateFormat fmt = new SimpleDateFormat(format);
return fmt.format(time.getTime());
}
// /**
// * parseDate:将字符串转换成日期使用yyyy-MM-dd HH:mm:ss 来格式化
// * @author Bool
// * @param date
// * @return
// */
public static Date parseDate(String date) {
return parseDate(date, "yyyy-MM-dd HH:mm:ss");
}
// /**
// *
// * parseDate:将字符串转换成日期,使用指定格式化来格式化
// * @author Bool
// * @param date
// * @param pattern
// * @return
// */
public static Date parseDate(String date, String pattern) {
if (pattern==null) {
pattern = "yyyy-MM-dd HH:mm:ss";
}
SimpleDateFormat fmt = new SimpleDateFormat(pattern);
try {
return fmt.parse(date);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}

@ -1,65 +0,0 @@
package com.yf.exam.core.utils;
import javax.servlet.http.HttpServletRequest;
///**
// * IP获取工具类用户获取网络请求过来的真实IP
// * ClassName: IpUtils <br/>
// * date: 2018年2月13日 下午7:27:52 <br/>
// *
// * @author Bool
// * @version
// */
public class IpUtils {
// /**
// *
// * getClientIp:通过请求获取客户端的真实IP地址
// * @author Bool
// * @param request
// * @return
// */
public static String extractClientIp(HttpServletRequest request) {
String ip = null;
//X-Forwarded-ForSquid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IPapache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IPweblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//X-Real-IPnginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
}
//有些网络通过多层代理那么获取到的ip就会有多个一般都是通过逗号,分割开来并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
}
//还是不能获取到最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip;
}
}

@ -1,324 +0,0 @@
/**
* Copyright (c) 2005-2012 springside.org.cn
*/
package com.yf.exam.core.utils;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
///**
// * 反射工具类.
// * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
// * @author calvin
// * @version 2016-01-15
// */
@Log4j2
public class Reflections {
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
private static final String CGLIB_CLASS_SEPARATOR = "$$";
// /**
// * 获取类的所有属性,包括父类
// *
// * @param object
// * @return
// */
public static Field[] getAllFields(Object object) {
Class<?> clazz = object.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
// /**
// * 调用Getter方法.
// * 支持多级,如:对象名.对象名.方法
// */
public static Object invokeGetter(Object obj, String propertyName) {
Object object = obj;
for (String name : StringUtils.split(propertyName, ".")){
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
return object;
}
// /**
// * 调用Setter方法, 仅匹配方法名。
// * 支持多级,如:对象名.对象名.方法
// */
public static void invokeSetter(Object obj, String propertyName, Object value) {
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i=0; i<names.length; i++){
if(i<names.length-1){
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}else{
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
invokeMethodByName(object, setterMethodName, new Object[] { value });
}
}
}
// /**
// * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
// */
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
log.error("不可能抛出的异常{}", e.getMessage());
}
return result;
}
// /**
// * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
// */
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
log.error("不可能抛出的异常:{}", e.getMessage());
}
}
// /**
// * 直接调用对象方法, 无视private/protected修饰符.
// * 用于一次性调用的情况否则应使用getAccessibleMethod()函数获得Method后反复调用.
// * 同时匹配方法名+参数类型,
// */
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
final Object[] args) {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
// /**
// * 直接调用对象方法, 无视private/protected修饰符
// * 用于一次性调用的情况否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
// * 只匹配函数名,如果有多个同名函数调用第一个。
// */
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
Method method = getAccessibleMethodByName(obj, methodName);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
// /**
// * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
// *
// * 如向上转型到Object仍无法找到, 返回null.
// */
public static Field getAccessibleField(final Object obj, final String fieldName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
} catch (NoSuchFieldException e) {//NOSONAR
// Field不在当前类定义,继续向上转型
continue;// new add
}
}
return null;
}
// /**
// * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
// * 如向上转型到Object仍无法找到, 返回null.
// * 匹配函数名+参数类型。
// *
// * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
// */
public static Method getAccessibleMethod(final Object obj, final String methodName,
final Class<?>... parameterTypes) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
try {
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
makeAccessible(method);
return method;
} catch (NoSuchMethodException e) {
// Method不在当前类定义,继续向上转型
continue;// new add
}
}
return null;
}
// /**
// * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
// * 如向上转型到Object仍无法找到, 返回null.
// * 只匹配函数名。
// *
// * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
// */
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
// /**
// * 改变private/protected的方法为public尽量不调用实际改动的语句避免JDK的SecurityManager抱怨。
// */
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
// /**
// * 改变private/protected的成员变量为public尽量不调用实际改动的语句避免JDK的SecurityManager抱怨。
// */
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
// /**
// * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
// * 如无法找到, 返回Object.class.
// * eg.
// * public UserDao extends HibernateDao<User>
// *
// * @param clazz The class to introspect
// * @return the first generic declaration, or Object.class if cannot be determined
// */
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassGenricType(final Class clazz) {
return getClassGenricType(clazz, 0);
}
// /**
// * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
// * 如无法找到, 返回Object.class.
// *
// * 如public UserDao extends HibernateDao<User,Long>
// *
// * @param clazz clazz The class to introspect
// * @param index the Index of the generic ddeclaration,start from 0.
// * @return the index generic declaration, or Object.class if cannot be determined
// */
public static Class getClassGenricType(final Class clazz, final int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
return Object.class;
}
return (Class) params[index];
}
public static Class<?> getUserClass(Object instance) {
Assert.notNull(instance, "Instance must not be null");
Class clazz = instance.getClass();
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass)) {
return superClass;
}
}
return clazz;
}
// /**
// * 将反射时的checked exception转换为unchecked exception.
// */
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|| e instanceof NoSuchMethodException) {
return new IllegalArgumentException(e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException(((InvocationTargetException) e).getTargetException());
} else if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException("Unexpected Checked Exception.", e);
}
}

@ -1,53 +0,0 @@
package com.yf.exam.core.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
///**
// * Spring获取工具类用于在非Spring管理的类中获取Spring管理的Bean。
// *
// * @author bool
// * @date 2019-12-09 15:55
// */
@Component
public class SpringUtils implements ApplicationContextAware {
// /**
// * 静态变量用于存储Spring的ApplicationContext对象。
// */
private static ApplicationContext applicationContext;
// /**
// * 实现ApplicationContextAware接口的方法用于设置ApplicationContext。
// *
// * @param context Spring的ApplicationContext对象
// * @throws BeansException 如果发生Bean相关的异常
// */
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
// /**
// * 根据Bean的类型获取Spring管理的Bean。
// *
// * @param tClass Bean的类型
// * @return 指定类型的Bean实例
// */
public static <T> T getBean(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
// /**
// * 根据Bean的名称和类型获取Spring管理的Bean。
// *
// * @param name Bean的名称
// * @param type Bean的类型
// * @return 指定名称和类型的Bean实例
// */
public static <T> T getBean(String name, Class<T> type) {
return applicationContext.getBean(name, type);
}
}

@ -1,42 +0,0 @@
package com.yf.exam.core.utils;
import java.util.Map;
//
///**
// * 字符串常用工具类,提供一些字符串处理的静态方法。
// *
// * @author bool
// * @date 2019-05-15 11:40
// */
public class StringUtils {
// /**
// * 判断给定的字符串是否为空或空白。
// * 空白字符串是指长度为0的字符串或仅包含空白字符的字符串。
// *
// * @param str 要判断的字符串
// * @return 如果字符串为空或空白返回true否则返回false
// */
public static boolean isBlank(String str) {
return str == null || "".equals(str);
}
// /**
// * 将给定的Map对象转换成一个XML格式的字符串。
// * 格式为<xml><key>value</key>...</xml>。
// *
// * @param params 要转换的Map对象键和值都是字符串类型
// * @return 转换成的XML格式字符串
// */
public static String mapToXml(Map<String, String> params) {
StringBuffer sb = new StringBuffer("<xml>");
for (String key : params.keySet()) {
sb.append("<")
.append(key).append(">")
.append(params.get(key))
.append("</").append(key).append(">");
}
sb.append("</xml>");
return sb.toString();
}
}

@ -1,402 +0,0 @@
/**
* Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
*/
package com.yf.exam.core.utils.excel;
import com.google.common.collect.Lists;
import com.yf.exam.core.utils.Reflections;
import com.yf.exam.core.utils.excel.annotation.ExcelField;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
///**
// * 导出Excel文件导出“XLSX”格式支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion
// * @author jeeplus
// * @version 2016-04-21
// */
public class ExportExcel {
private static Logger log = LoggerFactory.getLogger(ExportExcel.class);
// /**
// * 工作薄对象
// */
private SXSSFWorkbook wb;
// /**
// * 工作表对象
// */
private Sheet sheet;
// /**
// * 样式列表
// */
private Map<String, CellStyle> styles;
// /**
// * 当前行号
// */
private int rownum;
// /**
// * 注解列表Object[]{ ExcelField, Field/Method }
// */
List<Object[]> annotationList = Lists.newArrayList();
// /**
// * 构造函数
// * @param title 表格标题,传“空值”,表示无标题
// * @param cls 实体对象通过annotation.ExportField获取标题
// */
public ExportExcel(String title, Class<?> cls){
this(title, cls, 1);
}
// /**
// * 构造函数
// * @param title 表格标题,传“空值”,表示无标题
// * @param cls 实体对象通过annotation.ExportField获取标题
// * @param type 导出类型1:导出数据2导出模板
// * @param groups 导入分组
// */
public ExportExcel(String title, Class<?> cls, int type, int... groups){
// Get annotation field
Field[] fs = cls.getDeclaredFields();
for (Field f : fs){
ExcelField ef = f.getAnnotation(ExcelField.class);
if (ef != null && (ef.type()==0 || ef.type()==type)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, f});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, f});
}
}
}
// Get annotation method
Method[] ms = cls.getDeclaredMethods();
for (Method m : ms){
ExcelField ef = m.getAnnotation(ExcelField.class);
if (ef != null && (ef.type()==0 || ef.type()==type)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, m});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, m});
}
}
}
// Field sorting
Collections.sort(annotationList, new Comparator<Object[]>() {
@Override
public int compare(Object[] o1, Object[] o2) {
return new Integer(((ExcelField)o1[0]).sort()).compareTo(
new Integer(((ExcelField)o2[0]).sort()));
}
});
// Initialize
List<String> headerList = Lists.newArrayList();
for (Object[] os : annotationList){
String t = ((ExcelField)os[0]).title();
// 如果是导出,则去掉注释
if (type==1){
String[] ss = StringUtils.split(t, "**", 2);
if (ss.length==2){
t = ss[0];
}
}
headerList.add(t);
}
initialize(title, headerList);
}
// /**
// * 初始化函数
// * @param title 表格标题,传“空值”,表示无标题
// * @param headerList 表头列表
// */
private void initialize(String title, List<String> headerList) {
this.wb = new SXSSFWorkbook(500);
this.sheet = wb.createSheet("Export");
this.styles = createStyles(wb);
// Create title
if (StringUtils.isNotBlank(title)){
Row titleRow = sheet.createRow(rownum++);
titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get("title"));
titleCell.setCellValue(title);
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(),
titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1));
}
// Create header
if (headerList == null){
throw new RuntimeException("headerList not null!");
}
Row headerRow = sheet.createRow(rownum++);
headerRow.setHeightInPoints(16);
for (int i = 0; i < headerList.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellStyle(styles.get("header"));
String[] ss = StringUtils.split(headerList.get(i), "**", 2);
if (ss.length==2){
cell.setCellValue(ss[0]);
Comment comment = this.sheet.createDrawingPatriarch().createCellComment(
new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6));
comment.setString(new XSSFRichTextString(ss[1]));
cell.setCellComment(comment);
}else{
cell.setCellValue(headerList.get(i));
}
sheet.autoSizeColumn(i);
}
for (int i = 0; i < headerList.size(); i++) {
int colWidth = sheet.getColumnWidth(i)*2;
sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth);
}
log.debug("Initialize success.");
}
/**
*
* @param wb
* @return
*/
private Map<String, CellStyle> createStyles(Workbook wb) {
Map<String, CellStyle> styles = new HashMap<>(16);
CellStyle style = wb.createCellStyle();
style.setAlignment(CellStyle.ALIGN_CENTER);
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
Font titleFont = wb.createFont();
titleFont.setFontName("Arial");
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
style.setFont(titleFont);
styles.put("title", style);
style = wb.createCellStyle();
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
style.setBorderRight(CellStyle.BORDER_THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(CellStyle.BORDER_THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(CellStyle.BORDER_THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(CellStyle.BORDER_THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
style.setFont(dataFont);
styles.put("data", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(CellStyle.ALIGN_LEFT);
styles.put("data1", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(CellStyle.ALIGN_CENTER);
styles.put("data2", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(CellStyle.ALIGN_RIGHT);
styles.put("data3", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
// style.setWrapText(true);
style.setAlignment(CellStyle.ALIGN_CENTER);
style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(CellStyle.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
headerFont.setColor(IndexedColors.WHITE.getIndex());
style.setFont(headerFont);
styles.put("header", style);
return styles;
}
// /**
// * 添加一行
// * @return 行对象
// */
public Row addRow(){
return sheet.createRow(rownum++);
}
// /**
// * 添加一个单元格
// * @param row 添加的行
// * @param column 添加列号
// * @param val 添加值
// * @return 单元格对象
// */
public Cell addCell(Row row, int column, Object val){
return this.addCell(row, column, val, 0, Class.class);
}
// /**
// * 添加一个单元格
// * @param row 添加的行
// * @param column 添加列号
// * @param val 添加值
// * @param align 对齐方式1靠左2居中3靠右
// * @return 单元格对象
// */
public Cell addCell(Row row, int column, Object val, int align, Class<?> fieldType){
Cell cell = row.createCell(column);
CellStyle style = styles.get("data"+(align>=1&&align<=3?align:""));
try {
if (val == null){
cell.setCellValue("");
} else if (val instanceof String) {
cell.setCellValue((String) val);
} else if (val instanceof Integer) {
cell.setCellValue((Integer) val);
} else if (val instanceof Long) {
cell.setCellValue((Long) val);
} else if (val instanceof Double) {
cell.setCellValue((Double) val);
} else if (val instanceof Float) {
cell.setCellValue((Float) val);
} else if (val instanceof Date) {
DataFormat format = wb.createDataFormat();
style.setDataFormat(format.getFormat("yyyy-MM-dd"));
cell.setCellValue((Date) val);
} else {
if (fieldType != Class.class){
cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val));
}else{
cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
"fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val));
}
}
} catch (Exception ex) {
log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString());
cell.setCellValue(val.toString());
}
cell.setCellStyle(style);
return cell;
}
// /**
// * 添加数据通过annotation.ExportField添加数据
// * @return list 数据列表
// */
public <E> ExportExcel setDataList(List<E> list){
for (E e : list){
int colunm = 0;
Row row = this.addRow();
StringBuilder sb = new StringBuilder();
for (Object[] os : annotationList){
ExcelField ef = (ExcelField)os[0];
Object val = null;
try{
if (StringUtils.isNotBlank(ef.value())){
val = Reflections.invokeGetter(e, ef.value());
}else{
if (os[1] instanceof Field){
val = Reflections.invokeGetter(e, ((Field)os[1]).getName());
}else if (os[1] instanceof Method){
val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {});
}
}
}catch(Exception ex) {
log.info(ex.toString());
val = "";
}
this.addCell(row, colunm++, val, ef.align(), ef.fieldType());
sb.append(val + ", ");
}
log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString());
}
return this;
}
// /**
// * 输出数据流
// * @param os 输出数据流
// */
public ExportExcel write(OutputStream os) throws IOException{
wb.write(os);
return this;
}
// /**
// * 输出到客户端
// * @param fileName 输出文件名
// */
public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{
response.reset();
response.setHeader("Access-Control-Allow-Origin", "*");
response.setContentType("application/octet-stream; charset=utf-8");
response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "utf-8"));
write(response.getOutputStream());
return this;
}
// /**
// * 清理临时文件
// */
public ExportExcel dispose(){
wb.dispose();
return this;
}
}

@ -1,303 +0,0 @@
/**
* Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
*/
package com.yf.exam.core.utils.excel;
import com.google.common.collect.Lists;
import com.yf.exam.core.utils.Reflections;
import com.yf.exam.core.utils.excel.annotation.ExcelField;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
///**
// * 导入Excel文件支持“XLS”和“XLSX”格式
// * @author jeeplus
// * @version 2016-03-10
// */
public class ImportExcel {
private static Logger log = LoggerFactory.getLogger(ImportExcel.class);
// /**
// * 工作薄对象
// */
private Workbook wb;
// /**
// * 工作表对象
// */
private Sheet sheet;
//
// /**
// * 标题行号
// */
private int headerNum;
// /**
// * 构造函数
// * @param multipartFile 导入文件对象
// * @param headerNum 标题行号,数据行号=标题行号+1
// * @param sheetIndex 工作表编号
// * @throws InvalidFormatException
// * @throws IOException
// */
public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex)
throws InvalidFormatException, IOException {
this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex);
}
// /**
// * 构造函数
// * @param is 导入文件对象
// * @param headerNum 标题行号,数据行号=标题行号+1
// * @param sheetIndex 工作表编号
// * @throws InvalidFormatException
// * @throws IOException
// */
public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex)
throws IOException {
if (StringUtils.isBlank(fileName)){
throw new RuntimeException("导入文档为空!");
}else if(fileName.toLowerCase().endsWith("xls")){
this.wb = new HSSFWorkbook(is);
}else if(fileName.toLowerCase().endsWith("xlsx")){
this.wb = new XSSFWorkbook(is);
}else{
throw new RuntimeException("文档格式不正确!");
}
if (this.wb.getNumberOfSheets()<sheetIndex){
throw new RuntimeException("文档中没有工作表!");
}
this.sheet = this.wb.getSheetAt(sheetIndex);
this.headerNum = headerNum;
log.debug("Initialize success.");
}
// /**
// * 获取行对象
// * @param rownum
// * @return
// */
public Row getRow(int rownum){
return this.sheet.getRow(rownum);
}
// /**
// * 获取数据行号
// * @return
// */
public int getDataRowNum(){
return headerNum+1;
}
// /**
// * 获取最后一个数据行号
// * @return
// */
public int getLastDataRowNum(){
return this.sheet.getLastRowNum()+headerNum;
}
// /**
// * 获取单元格值
// * @param row 获取的行
// * @param column 获取单元格列号
// * @return 单元格值
// */
public Object getCellValue(Row row, int column) {
Object val = "";
try {
Cell cell = row.getCell(column);
if (cell != null) {
if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
// 当excel 中的数据为数值或日期是需要特殊处理
if (HSSFDateUtil.isCellDateFormatted(cell)) {
double d = cell.getNumericCellValue();
Date date = HSSFDateUtil.getJavaDate(d);
SimpleDateFormat dformat = new SimpleDateFormat(
"yyyy-MM-dd");
val = dformat.format(date);
} else {
NumberFormat nf = NumberFormat.getInstance();
nf.setGroupingUsed(false);// true时的格式1,234,567,890
val = nf.format(cell.getNumericCellValue());// 数值类型的数据为double所以需要转换一下
}
} else if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
val = cell.getStringCellValue();
} else if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
val = cell.getCellFormula();
} else if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
val = cell.getBooleanCellValue();
} else if (cell.getCellType() == Cell.CELL_TYPE_ERROR) {
val = cell.getErrorCellValue();
}
}
} catch (Exception e) {
return val;
}
return val;
}
// /**
// * 获取导入数据列表
// * @param cls 导入对象类型
// * @param groups 导入分组
// */
public <E> List<E> getDataList(Class<E> cls, int... groups) throws InstantiationException, IllegalAccessException{
List<Object[]> annotationList = Lists.newArrayList();
// Get annotation field
Field[] fs = cls.getDeclaredFields();
for (Field f : fs){
ExcelField ef = f.getAnnotation(ExcelField.class);
if (ef != null && (ef.type()==0 || ef.type()==2)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, f});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, f});
}
}
}
// Get annotation method
Method[] ms = cls.getDeclaredMethods();
for (Method m : ms){
ExcelField ef = m.getAnnotation(ExcelField.class);
if (ef != null && (ef.type()==0 || ef.type()==2)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, m});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, m});
}
}
}
// Field sorting
Collections.sort(annotationList, new Comparator<Object[]>() {
@Override
public int compare(Object[] o1, Object[] o2) {
return new Integer(((ExcelField)o1[0]).sort()).compareTo(
new Integer(((ExcelField)o2[0]).sort()));
}
});
// Get excel data
List<E> dataList = Lists.newArrayList();
for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) {
E e = (E)cls.newInstance();
int column = 0;
Row row = this.getRow(i);
StringBuilder sb = new StringBuilder();
for (Object[] os : annotationList){
Object val = this.getCellValue(row, column++);
if (val != null){
ExcelField ef = (ExcelField)os[0];
// Get param type and type cast
Class<?> valType = Class.class;
if (os[1] instanceof Field){
valType = ((Field)os[1]).getType();
}else if (os[1] instanceof Method){
Method method = ((Method)os[1]);
if ("get".equals(method.getName().substring(0, 3))){
valType = method.getReturnType();
}else if("set".equals(method.getName().substring(0, 3))){
valType = ((Method)os[1]).getParameterTypes()[0];
}
}
//log.debug("Import value type: ["+i+","+column+"] " + valType);
try {
//如果导入的java对象需要在这里自己进行变换。
if (valType == String.class){
String s = String.valueOf(val.toString());
if(StringUtils.endsWith(s, ".0")){
val = StringUtils.substringBefore(s, ".0");
}else{
val = String.valueOf(val.toString());
}
}else if (valType == Integer.class){
val = Double.valueOf(val.toString()).intValue();
}else if (valType == Long.class){
val = Double.valueOf(val.toString()).longValue();
}else if (valType == Double.class){
val = Double.valueOf(val.toString());
}else if (valType == Float.class){
val = Float.valueOf(val.toString());
}else if (valType == Date.class){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
val=sdf.parse(val.toString());
}else{
if (ef.fieldType() != Class.class){
val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString());
}else{
val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
"fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString());
}
}
} catch (Exception ex) {
log.info("Get cell value ["+i+","+column+"] error: " + ex.toString());
val = null;
}
// set entity value
if (os[1] instanceof Field){
Reflections.invokeSetter(e, ((Field)os[1]).getName(), val);
}else if (os[1] instanceof Method){
String mthodName = ((Method)os[1]).getName();
if ("get".equals(mthodName.substring(0, 3))){
mthodName = "set"+StringUtils.substringAfter(mthodName, "get");
}
Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val});
}
}
sb.append(val+", ");
}
dataList.add(e);
log.debug("Read success: ["+i+"] "+sb.toString());
}
return dataList;
}
}

@ -1,59 +0,0 @@
/**
* Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
*/
package com.yf.exam.core.utils.excel.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
///**
// * Excel注解定义
// * @author jeeplus
// * @version 2016-03-10
// */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
// /**
// * 导出字段名默认调用当前字段的“get”方法如指定导出字段为对象请填写“对象名.对象属性”“area.name”、“office.name”
// */
String value() default "";
// /**
// * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效)
// */
String title();
// /**
// * 字段类型0导出导入1仅导出2仅导入
// */
int type() default 0;
// /**
// * 导出字段对齐方式0自动1靠左2居中3靠右
// */
int align() default 0;
// /**
// * 导出字段字段排序(升序)
// */
int sort() default 0;
// /**
// * 如果是字典类型请设置字典的type值
// */
String dictType() default "";
// /**
// * 反射类型
// */
Class<?> fieldType() default Class.class;
// /**
// * 字段归属组(根据分组导出导入)
// */
int[] groups() default {};
}

@ -1,62 +0,0 @@
/**
* Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
*/
package com.yf.exam.core.utils.excel.fieldtype;
import com.google.common.collect.Lists;
import com.yf.exam.core.utils.StringUtils;
import java.util.List;
///**
// * 字段类型转换工具类用于处理Excel导入导出时的List类型字段转换。
// *
// * @author jeeplus
// * @version 2016-5-29
// */
public class ListType {
// /**
// * 从字符串中获取对象值(导入)。
// * 该方法将输入的字符串按逗号分割并将每个分割后的子字符串添加到List中。
// *
// * @param val 输入的字符串格式为“item1,item2,item3,...”
// * @return 包含分割后子字符串的List对象
// */
public static Object getValue(String val) {
List<String> list = Lists.newArrayList();
if (!StringUtils.isBlank(val)) {
for (String s : val.split(",")) {
list.add(s);
}
}
return list;
}
// /**
// * 将对象值设置为字符串(导出)。
// * 该方法将输入的List对象中的每个元素按逗号拼接成一个字符串。
// *
// * @param val 输入的List对象
// * @return 拼接后的字符串格式为“item1,item2,item3,...”
// */
public static String setValue(Object val) {
if (val != null) {
List<String> list = (List<String>) val;
StringBuffer sb = null;
for (String item : list) {
if (StringUtils.isBlank(item)) {
continue;
}
if (sb == null) {
sb = new StringBuffer(item);
} else {
sb.append(",").append(item);
}
}
if (sb != null) {
return sb.toString().replace("[]", "");
}
}
return "";
}
}

@ -1,33 +0,0 @@
package com.yf.exam.core.utils.file;
import java.security.MessageDigest;
///**
// * MD5工具类用于生成字符串的MD5哈希值。
// *
// * @author Bool
// * @version 2018年1月13日 下午6:54:53
// */
public class Md5Util {
// /**
// * 生成给定字符串的简单MD5哈希值。
// * 该方法将输入字符串编码为UTF-8字节数组然后使用MD5算法进行哈希计算最后将哈希结果转换为十六进制字符串。
// *
// * @param str 输入的字符串
// * @return 字符串的MD5哈希值如果发生异常则返回null
// */
public static String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(str.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
}

@ -1,57 +0,0 @@
package com.yf.exam.core.utils.passwd;
import com.yf.exam.core.utils.file.Md5Util;
import org.apache.commons.lang3.RandomStringUtils;
///**
// * 通用的密码处理类,用于生成密码和校验密码
// * ClassName: PassGenerator <br/>
// * date: 2017年12月13日 下午7:13:03 <br/>
// *
// * @author Bool
// * @version
// */
public class PassHandler {
// /**
// * checkPass:校验密码是否一致
// * @author Bool
// * @param inputPass 用户传入的密码
// * @param salt 数据库保存的密码随机码
// * @param pass 数据库保存的密码MD5
// * @return
// */
public static boolean checkPass(String inputPass , String salt , String pass){
String pwdMd5 = Md5Util.md5(inputPass);
return Md5Util.md5(pwdMd5 + salt).equals(pass);
}
// /**
// *
// * buildPassword:用于用户注册时产生一个密码
// * @author Bool
// * @param inputPass 输入的密码
// * @return PassInfo 返回一个密码对象,记得保存
// */
public static PassInfo buildPassword(String inputPass) {
//产生一个6位数的随机码
String salt = RandomStringUtils.randomAlphabetic(6);
//加密后的密码
String encryptPassword = Md5Util.md5(Md5Util.md5(inputPass)+salt);
//返回对象
return new PassInfo(salt,encryptPassword);
}
public static void main(String[] args) {
PassInfo info = buildPassword("190601");
System.out.println(info.getPassword());
System.out.println(info.getSalt());
}
}

@ -1,70 +0,0 @@
package com.yf.exam.core.utils.passwd;
///**
// * 密码实体类,用于存储和管理密码及其随机盐值。
// *
// * @author Bool
// * @version 2018年2月13日 下午7:13:50
// */
public class PassInfo {
// /**
// * 密码的随机盐值。
// * 用于增强密码的安全性。
// */
private String salt;
// /**
// * 经过MD5哈希处理后的密码。
// * 存储最终用于验证的密码字符串。
// */
private String password;
// /**
// * 构造方法,用于初始化密码实体对象。
// *
// * @param salt 密码的随机盐值
// * @param password 经过MD5哈希处理后的密码
// */
public PassInfo(String salt, String password) {
super();
this.salt = salt;
this.password = password;
}
// /**
// * 获取密码的随机盐值。
// *
// * @return 密码的随机盐值
// */
public String getSalt() {
return salt;
}
// /**
// * 设置密码的随机盐值。
// *
// * @param salt 密码的随机盐值
// */
public void setSalt(String salt) {
this.salt = salt;
}
// /**
// * 获取经过MD5哈希处理后的密码。
// *
// * @return 经过MD5哈希处理后的密码
// */
public String getPassword() {
return password;
}
// /**
// * 设置经过MD5哈希处理后的密码。
// *
// * @param password 经过MD5哈希处理后的密码
// */
public void setPassword(String password) {
this.password = password;
}
}

@ -1,151 +0,0 @@
package com.yf.exam.modules.exam.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.BaseStateReqDTO;
import com.yf.exam.core.api.dto.PagingReqDTO;
import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.entity.Exam;
import com.yf.exam.modules.exam.service.ExamService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresRoles;
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;
import java.util.Date;
///**
//* <p>
//* 考试控制器
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Api(tags={"考试"})
@RestController
@RequestMapping("/exam/api/exam/exam")
public class ExamController extends BaseController {
@Autowired
private ExamService baseService;
// /**
// * 添加或修改
// * @param reqDTO
// * @return
// */
@RequiresRoles("sa")
@ApiOperation(value = "添加或修改")
@RequestMapping(value = "/save", method = { RequestMethod.POST})
public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) {
//复制参数
baseService.save(reqDTO);
return super.success();
}
// /**
// * 批量删除
// * @param reqDTO
// * @return
// */
@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
// * @return
// */
@ApiOperation(value = "查找详情")
@RequestMapping(value = "/detail", method = { RequestMethod.POST})
public ApiRest<ExamSaveReqDTO> find(@RequestBody BaseIdReqDTO reqDTO) {
ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId());
return super.success(dto);
}
// /**
// * 查找详情
// * @param reqDTO
// * @return
// */
@RequiresRoles("sa")
@ApiOperation(value = "查找详情")
@RequestMapping(value = "/state", method = { RequestMethod.POST})
public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) {
QueryWrapper<Exam> wrapper = new QueryWrapper<>();
wrapper.lambda().in(Exam::getId, reqDTO.getIds());
Exam exam = new Exam();
exam.setState(reqDTO.getState());
exam.setUpdateTime(new Date());
baseService.update(exam, wrapper);
return super.success();
}
// /**
// * 分页查找
// * @param reqDTO
// * @return
// */
@ApiOperation(value = "考试视角")
@RequestMapping(value = "/online-paging", method = { RequestMethod.POST})
public ApiRest<IPage<ExamOnlineRespDTO>> myPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
//分页查询并转换
IPage<ExamOnlineRespDTO> page = baseService.onlinePaging(reqDTO);
return super.success(page);
}
// /**
// * 分页查找
// * @param reqDTO
// * @return
// */
@RequiresRoles("sa")
@ApiOperation(value = "分页查找")
@RequestMapping(value = "/paging", method = { RequestMethod.POST})
public ApiRest<IPage<ExamDTO>> paging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
//分页查询并转换
IPage<ExamDTO> page = baseService.paging(reqDTO);
return super.success(page);
}
// /**
// * 分页查找
// * @param reqDTO
// * @return
// */
@RequiresRoles("sa")
@ApiOperation(value = "待阅试卷")
@RequestMapping(value = "/review-paging", method = { RequestMethod.POST})
public ApiRest<IPage<ExamReviewRespDTO>> reviewPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
//分页查询并转换
IPage<ExamReviewRespDTO> page = baseService.reviewPaging(reqDTO);
return super.success(page);
}
}

@ -1,101 +0,0 @@
package com.yf.exam.modules.exam.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.yf.exam.modules.paper.enums.ExamState;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
///**
//* <p>
//* 考试数据传输类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Data
@ApiModel(value="考试", description="考试")
public class ExamDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID", required=true)
private String id;
@ApiModelProperty(value = "考试名称", required=true)
private String title;
@ApiModelProperty(value = "考试描述", required=true)
private String content;
@ApiModelProperty(value = "1公开2部门3定员", required=true)
private Integer openType;
@ApiModelProperty(value = "考试状态", required=true)
private Integer state;
@ApiModelProperty(value = "是否限时", required=true)
private Boolean timeLimit;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty(value = "开始时间", required=true)
private Date startTime;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty(value = "结束时间", required=true)
private Date endTime;
@ApiModelProperty(value = "创建时间", required=true)
private Date createTime;
@ApiModelProperty(value = "更新时间", required=true)
private Date updateTime;
@ApiModelProperty(value = "总分数", required=true)
private Integer totalScore;
@ApiModelProperty(value = "总时长(分钟)", required=true)
private Integer totalTime;
@ApiModelProperty(value = "及格分数", required=true)
private Integer qualifyScore;
/**
*
* @return
*/
public Integer getState(){
if(this.timeLimit!=null && this.timeLimit){
if(System.currentTimeMillis() < startTime.getTime() ){
return ExamState.READY_START;
}
if(System.currentTimeMillis() > endTime.getTime()){
return ExamState.OVERDUE;
}
if(System.currentTimeMillis() > startTime.getTime()
&& System.currentTimeMillis() < endTime.getTime()
&& !ExamState.DISABLED.equals(this.state)){
return ExamState.ENABLE;
}
}
return this.state;
}
}

@ -1,33 +0,0 @@
package com.yf.exam.modules.exam.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
///**
//* <p>
//* 考试部门数据传输类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-03 17:24
//*/
@Data
@ApiModel(value="考试部门", description="考试部门")
public class ExamDepartDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID", required=true)
private String id;
@ApiModelProperty(value = "考试ID", required=true)
private String examId;
@ApiModelProperty(value = "部门ID", required=true)
private String departId;
}

@ -1,51 +0,0 @@
package com.yf.exam.modules.exam.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
///**
//* <p>
//* 考试题库数据传输类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-05 11:14
//*/
@Data
@ApiModel(value="考试题库", description="考试题库")
public class ExamRepoDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "ID", required=true)
private String id;
@ApiModelProperty(value = "考试ID", required=true)
private String examId;
@ApiModelProperty(value = "题库ID", required=true)
private String repoId;
@ApiModelProperty(value = "单选题数量", required=true)
private Integer radioCount;
@ApiModelProperty(value = "单选题分数", required=true)
private Integer radioScore;
@ApiModelProperty(value = "多选题数量", required=true)
private Integer multiCount;
@ApiModelProperty(value = "多选题分数", required=true)
private Integer multiScore;
@ApiModelProperty(value = "判断题数量", required=true)
private Integer judgeCount;
@ApiModelProperty(value = "判断题分数", required=true)
private Integer judgeScore;
}

@ -1,32 +0,0 @@
package com.yf.exam.modules.exam.dto.ext;
import com.yf.exam.modules.exam.dto.ExamRepoDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
///**
//* <p>
//* 考试题库数据传输类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-05 11:14
//*/
@Data
@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类")
public class ExamRepoExtDTO extends ExamRepoDTO {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "单选题总量", required=true)
private Integer totalRadio;
@ApiModelProperty(value = "多选题总量", required=true)
private Integer totalMulti;
@ApiModelProperty(value = "判断题总量", required=true)
private Integer totalJudge;
}

@ -1,32 +0,0 @@
package com.yf.exam.modules.exam.dto.request;
import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
///**
//* <p>
//* 考试保存请求类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Data
@ApiModel(value="考试保存请求类", description="考试保存请求类")
public class ExamSaveReqDTO extends ExamDTO {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "题库列表", required=true)
private List<ExamRepoExtDTO> repoList;
@ApiModelProperty(value = "考试部门列表", required=true)
private List<String> departIds;
}

@ -1,22 +0,0 @@
package com.yf.exam.modules.exam.dto.response;
import com.yf.exam.modules.exam.dto.ExamDTO;
import io.swagger.annotations.ApiModel;
import lombok.Data;
//* <p>
//* 考试分页响应类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Data
@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类")
public class ExamOnlineRespDTO extends ExamDTO {
private static final long serialVersionUID = 1L;
}

@ -1,31 +0,0 @@
package com.yf.exam.modules.exam.dto.response;
import com.yf.exam.modules.exam.dto.ExamDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
///**
//* <p>
//* 考试分页响应类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Data
@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类")
public class ExamReviewRespDTO extends ExamDTO {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "考试人数", required=true)
private Integer examUser;
@ApiModelProperty(value = "待阅试卷", required=true)
private Integer unreadPaper;
}

@ -1,100 +0,0 @@
package com.yf.exam.modules.exam.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;
///**
//* <p>
//* 考试实体类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Data
@TableName("el_exam")
public class Exam extends Model<Exam> {
private static final long serialVersionUID = 1L;
// /**
// * ID
// */
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
// /**
// * 考试名称
// */
private String title;
// /**
// * 考试描述
// */
private String content;
// /**
// * 1公开2部门3定员
// */
@TableField("open_type")
private Integer openType;
// /**
// * 考试状态
// */
private Integer state;
// /**
// * 是否限时
// */
@TableField("time_limit")
private Boolean timeLimit;
// /**
// * 开始时间
// */
@TableField("start_time")
private Date startTime;
// /**
// * 结束时间
// */
@TableField("end_time")
private Date endTime;
// /**
// * 创建时间
// */
@TableField("create_time")
private Date createTime;
// /**
// * 更新时间
// */
@TableField("update_time")
private Date updateTime;
// /**
// * 总分数
// */
@TableField("total_score")
private Integer totalScore;
// /**
// * 总时长(分钟)
// */
@TableField("total_time")
private Integer totalTime;
// /**
// * 及格分数
// */
@TableField("qualify_score")
private Integer qualifyScore;
}

@ -1,42 +0,0 @@
package com.yf.exam.modules.exam.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;
///**
//* <p>
//* 考试部门实体类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-03 17:24
//*/
@Data
@TableName("el_exam_depart")
public class ExamDepart extends Model<ExamDepart> {
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("depart_id")
private String departId;
}

@ -1,78 +0,0 @@
package com.yf.exam.modules.exam.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;
///**
//* <p>
//* 考试题库实体类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-05 11:14
//*/
@Data
@TableName("el_exam_repo")
public class ExamRepo extends Model<ExamRepo> {
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("repo_id")
private String repoId;
// /**
// * 单选题数量
// */
@TableField("radio_count")
private Integer radioCount;
// /**
// * 单选题分数
// */
@TableField("radio_score")
private Integer radioScore;
// /**
// * 多选题数量
// */
@TableField("multi_count")
private Integer multiCount;
// /**
// * 多选题分数
// */
@TableField("multi_score")
private Integer multiScore;
// /**
// * 判断题数量
// */
@TableField("judge_count")
private Integer judgeCount;
// /**
// * 判断题分数
// */
@TableField("judge_score")
private Integer judgeScore;
}

@ -1,15 +0,0 @@
package com.yf.exam.modules.exam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yf.exam.modules.exam.entity.ExamDepart;
///**
//* <p>
//* 考试部门Mapper
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-03 17:24
//*/
public interface ExamDepartMapper extends BaseMapper<ExamDepart> {
}

@ -1,45 +0,0 @@
package com.yf.exam.modules.exam.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.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.entity.Exam;
import org.apache.ibatis.annotations.Param;
///**
//* <p>
//* 考试Mapper
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
public interface ExamMapper extends BaseMapper<Exam> {
// /**
// * 查找分页内容
// * @param page
// * @param query
// * @return
// */
IPage<ExamDTO> paging(Page page, @Param("query") ExamDTO query);
// /**
// * 查找分页内容
// * @param page
// * @param query
// * @return
// */
IPage<ExamReviewRespDTO> reviewPaging(Page page, @Param("query") ExamDTO query);
// /**
// * 在线考试分页响应类-考生视角
// * @param page
// * @param query
// * @return
// */
IPage<ExamOnlineRespDTO> online(Page page, @Param("query") ExamDTO query);
}

@ -1,26 +0,0 @@
package com.yf.exam.modules.exam.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.entity.ExamRepo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
///**
//* <p>
//* 考试题库Mapper
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-05 11:14
//*/
public interface ExamRepoMapper extends BaseMapper<ExamRepo> {
// /**
// * 查找考试题库列表
// * @param examId
// * @return
// */
List<ExamRepoExtDTO> listByExam(@Param("examId") String examId);
}

@ -1,32 +0,0 @@
package com.yf.exam.modules.exam.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yf.exam.modules.exam.entity.ExamDepart;
import java.util.List;
///**
//* <p>
//* 考试部门业务类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-03 17:24
//*/
public interface ExamDepartService extends IService<ExamDepart> {
// /**
// * 保存全部
// * @param examId
// * @param departs
// */
void saveAll(String examId, List<String> departs);
// /**
// * 根据考试查找对应的部门
// * @param examId
// * @return
// */
List<String> listByExam(String examId);
}

@ -1,40 +0,0 @@
package com.yf.exam.modules.exam.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.entity.ExamRepo;
import java.util.List;
///**
//* <p>
//* 考试题库业务类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-05 11:14
//*/
public interface ExamRepoService extends IService<ExamRepo> {
// /**
// * 保存全部
// * @param examId
// * @param list
// */
void saveAll(String examId, List<ExamRepoExtDTO> list);
//
// /**
// * 查找考试题库列表
// * @param examId
// * @return
// */
List<ExamRepoExtDTO> listByExam(String examId);
// /**
// * 清理脏数据
// * @param examId
// */
void clear(String examId);
}

@ -1,63 +0,0 @@
package com.yf.exam.modules.exam.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.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.entity.Exam;
///**
//* <p>
//* 考试业务类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
public interface ExamService extends IService<Exam> {
// /**
// * 保存考试信息
// * @param reqDTO
// */
void save(ExamSaveReqDTO reqDTO);
// /**
// * 查找考试详情
// * @param id
// * @return
// */
ExamSaveReqDTO findDetail(String id);
// /**
// * 查找考试详情--简要信息
// * @param id
// * @return
// */
ExamDTO findById(String id);
// /**
// * 分页查询数据
// * @param reqDTO
// * @return
// */
IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO);
// /**
// * 在线考试分页响应类-考生视角
// * @param reqDTO
// * @return
// */
IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO);
// * 待阅试卷列表
// * @param reqDTO
// * @return
// */
IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO);
}

@ -1,66 +0,0 @@
package com.yf.exam.modules.exam.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.modules.exam.entity.ExamDepart;
import com.yf.exam.modules.exam.mapper.ExamDepartMapper;
import com.yf.exam.modules.exam.service.ExamDepartService;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
///**
//* <p>
//* 考试部门业务实现类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-03 17:24
//*/
@Service
public class ExamDepartServiceImpl extends ServiceImpl<ExamDepartMapper, ExamDepart> implements ExamDepartService {
@Override
public void saveAll(String examId, List<String> departs) {
// 先删除
QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamDepart::getExamId, examId);
this.remove(wrapper);
// 再增加
if(CollectionUtils.isEmpty(departs)){
throw new ServiceException(1, "请至少选择选择一个部门!!");
}
List<ExamDepart> list = new ArrayList<>();
for(String id: departs){
ExamDepart depart = new ExamDepart();
depart.setDepartId(id);
depart.setExamId(examId);
list.add(depart);
}
this.saveBatch(list);
}
@Override
public List<String> listByExam(String examId) {
// 先删除
QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamDepart::getExamId, examId);
List<ExamDepart> list = this.list(wrapper);
List<String> ids = new ArrayList<>();
if(!CollectionUtils.isEmpty(list)){
for(ExamDepart item: list){
ids.add(item.getDepartId());
}
}
return ids;
}
}

@ -1,67 +0,0 @@
package com.yf.exam.modules.exam.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.core.utils.BeanMapper;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.entity.ExamRepo;
import com.yf.exam.modules.exam.mapper.ExamRepoMapper;
import com.yf.exam.modules.exam.service.ExamRepoService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.List;
///**
//* <p>
//* 考试题库业务实现类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-09-05 11:14
//*/
@Service
public class ExamRepoServiceImpl extends ServiceImpl<ExamRepoMapper, ExamRepo> implements ExamRepoService {
@Transactional(rollbackFor = Exception.class)
@Override
public void saveAll(String examId, List<ExamRepoExtDTO> list) {
// 先删除
QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamRepo::getExamId, examId);
this.remove(wrapper);
// 再增加
if(CollectionUtils.isEmpty(list)){
throw new ServiceException(1, "必须选择题库!");
}
List<ExamRepo> repos = BeanMapper.mapList(list, ExamRepo.class);
for(ExamRepo item: repos){
item.setExamId(examId);
item.setId(IdWorker.getIdStr());
}
this.saveBatch(repos);
}
@Override
public List<ExamRepoExtDTO> listByExam(String examId) {
return baseMapper.listByExam(examId);
}
@Override
public void clear(String examId) {
// 先删除
QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(ExamRepo::getExamId, examId);
this.remove(wrapper);
}
}

@ -1,194 +0,0 @@
package com.yf.exam.modules.exam.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
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.core.enums.OpenType;
import com.yf.exam.core.exception.ServiceException;
import com.yf.exam.core.utils.BeanMapper;
import com.yf.exam.modules.exam.dto.ExamDTO;
import com.yf.exam.modules.exam.dto.ExamRepoDTO;
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
import com.yf.exam.modules.exam.entity.Exam;
import com.yf.exam.modules.exam.mapper.ExamMapper;
import com.yf.exam.modules.exam.service.ExamDepartService;
import com.yf.exam.modules.exam.service.ExamRepoService;
import com.yf.exam.modules.exam.service.ExamService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import java.util.List;
///**
//* <p>
//* 考试业务实现类
//* </p>
//*
//* @author 聪明笨狗
//* @since 2020-07-25 16:18
//*/
@Service
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService {
@Autowired
private ExamRepoService examRepoService;
@Autowired
private ExamDepartService examDepartService;
@Override
public void save(ExamSaveReqDTO reqDTO) {
// ID
String id = reqDTO.getId();
if(StringUtils.isBlank(id)){
id = IdWorker.getIdStr();
}
//复制参数
Exam entity = new Exam();
// 计算分值
this.calcScore(reqDTO);
// 复制基本数据
BeanMapper.copy(reqDTO, entity);
entity.setId(id);
// 修复状态
if (reqDTO.getTimeLimit()!=null
&& !reqDTO.getTimeLimit()
&& reqDTO.getState()!=null
&& reqDTO.getState() == 2) {
entity.setState(0);
} else {
entity.setState(reqDTO.getState());
}
// 题库组卷
try {
examRepoService.saveAll(id, reqDTO.getRepoList());
}catch (DuplicateKeyException e){
throw new ServiceException(1, "不能选择重复的题库!");
}
// 开放的部门
if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){
examDepartService.saveAll(id, reqDTO.getDepartIds());
}
this.saveOrUpdate(entity);
}
@Override
public ExamSaveReqDTO findDetail(String id) {
ExamSaveReqDTO respDTO = new ExamSaveReqDTO();
Exam exam = this.getById(id);
BeanMapper.copy(exam, respDTO);
// 考试部门
List<String> departIds = examDepartService.listByExam(id);
respDTO.setDepartIds(departIds);
// 题库
List<ExamRepoExtDTO> repos = examRepoService.listByExam(id);
respDTO.setRepoList(repos);
return respDTO;
}
@Override
public ExamDTO findById(String id) {
ExamDTO respDTO = new ExamDTO();
Exam exam = this.getById(id);
BeanMapper.copy(exam, respDTO);
return respDTO;
}
@Override
public IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO) {
//创建分页对象
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
//转换结果
IPage<ExamDTO> pageData = baseMapper.paging(page, reqDTO.getParams());
return pageData;
}
@Override
public IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO) {
// 创建分页对象
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
// 查找分页
IPage<ExamOnlineRespDTO> pageData = baseMapper.online(page, reqDTO.getParams());
return pageData;
}
@Override
public IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO) {
// 创建分页对象
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
// 查找分页
IPage<ExamReviewRespDTO> pageData = baseMapper.reviewPaging(page, reqDTO.getParams());
return pageData;
}
// /**
// * 计算分值
// * @param reqDTO
// */
private void calcScore(ExamSaveReqDTO reqDTO){
// 主观题分数
int objScore = 0;
// 题库组卷
List<ExamRepoExtDTO> repoList = reqDTO.getRepoList();
for(ExamRepoDTO item: repoList){
if(item.getRadioCount()!=null
&& item.getRadioCount()>0
&& item.getRadioScore()!=null
&& item.getRadioScore()>0){
objScore+=item.getRadioCount()*item.getRadioScore();
}
if(item.getMultiCount()!=null
&& item.getMultiCount()>0
&& item.getMultiScore()!=null
&& item.getMultiScore()>0){
objScore+=item.getMultiCount()*item.getMultiScore();
}
if(item.getJudgeCount()!=null
&& item.getJudgeCount()>0
&& item.getJudgeScore()!=null
&& item.getJudgeScore()>0){
objScore+=item.getJudgeCount()*item.getJudgeScore();
}
}
reqDTO.setTotalScore(objScore);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save