基于springboot+vue实现的微在线考试系统
1.项目简介
1.1 开发目的
面向组织、公司、高校的一款通用在线考试系统,节约人力财力,轻松完成在线考核。系统拥有自行添加题库,考试随机抽题,线上监考,自动阅卷评分,实现了无纸化考试,自动化阅卷等功能。
1.2 涉及技术
-
基本平台 :JDK1.8+MySql8.0
-
前端 :Vue2+Axios+ElementUI+echart
-
后端 :SpringBoot+MyBatis+SpringSecurity+easyPoi+Jwt
-
基于RBAC权限模型 (用户、角色、权限)
1.3 项目架构
-
bin :完整项目成品(一键部署)
-
image :项目运行截图
-
sql :数据库文件
-
voes-admin :Springboot核心模块
-
voes-common :公共模块
-
voes-examination :考试模块
-
voes-framework :框架模块
-
voes-system :系统模块
-
voes-ui :前端界面
1.4 部署步骤
源码安装
-
将sql目录下的sql文件导入mysql数据库
-
将项目导入IDEA开发工具,执行voes-admin模块目录下的main方法,启动SpringBoot服务
-
进入voes-ui目录下执行
npm run dev
即可启动前端UI服务
一键安装
-
将sql目录下的sql文件导入mysql数据库
-
将bin目录下的文件移动到你的服务器上,点击start.bat即可启动服务
2.数据库设计
2.1 表结构
用户表
角色表
用户-角色表
成绩表
科目表
权限表
题目关键字表
答案表
题目解析表
考试表
字典表
考场信息表
2.2 E-R图
3.系统设计
项目模块结构图
- 考生模块
- 在线考试:考生在规定时间里选择考试科目,进行考试
- 历史考试:考生可以查看历史考试的信息信息
- 修改密码:考生可以修改自己的登录密码
-
账户信息:考生可以修改的真实姓名和手机号码
-
教师模块
- 系统设置: 教师可以进行菜单管理添加、编辑、删除,角色管理添加、编辑、删除,修改密码
- 用户管理:教师可以进行用户列表的添加、编辑、删除
- 系统日志:教师可以进行日志列表的添加、删除
- 学科管理: 教师可以进行学科列表的添加、编辑、删除
- 考生管理:教师可以进行考生列表的添加、编辑、删除
- 试题管理:教师可以进行试题列表的添加、编辑、删除,批量导入试题
- 考试管理:教师可以进行考试列表的添加、编辑、删除
- 试卷管理:教师可以进行试卷列表的编辑、删除
- 答题管理:教师可以进行学生答题情况的查询
- 成绩统计:教师可以进行查看考试的成绩统计图表
分布式架构
项目结构图
3.1 业务代码
功能模块
```java /* * 通过ID查询单条数据 * * @param id 主键 * @return 实例对象 / @Override public QuestionBank queryById(Integer id, boolean answer) { Integer type = questionBankMapper.queryTypeById(id); //单选题 if (QuestionType.SINGLE_CHOICE.equals(type)) { log.info("单选题"); QuestionBank questionBank = questionBankMapper.queryByIdWithOption(id); QuestionOption questionOption = questionBank.getQuestionOption(); if (!answer) { questionOption.setRightKey(null); questionOption.setExplain(null); } return questionBank; } //多选题 if (QuestionType.MULTIPLE_CHOICE.equals(type)) { log.info("多选题"); QuestionBank questionBank = questionBankMapper.queryByIdWithOption(id); QuestionOption questionOption = questionBank.getQuestionOption(); if (!answer) { questionOption.setRightKey(null); questionOption.setExplain(null); } return questionBank;
}
//填空题
if (QuestionType.FILL_IN_THE_BLANKS.equals(type)) {
log.info("填空题");
QuestionBank questionBank = questionBankMapper.queryByIdWithFill(id);
QuestionFillBlanks questionFillBlank = questionBank.getQuestionFillBlank();
if (!answer) {
questionFillBlank.setAnswer(null);
}
return questionBank;
}
//判断题
if (QuestionType.JUDGE.equals(type)) {
log.info("判断题");
QuestionBank questionBank = questionBankMapper.queryByIdWithJudgment(id);
if (!answer) {
QuestionJudgment questionJudgment = questionBank.getQuestionJudgment();
questionJudgment.setRightKey(null);
questionJudgment.setExplain(null);
}
return questionBank;
}
//主观题目
//与填空题保持一致
if (QuestionType.SUBJECTIVE.equals(type)) {
log.info("主观题");
QuestionBank questionBank = questionBankMapper.queryByIdWithFill(id);
QuestionFillBlanks questionFillBlank = questionBank.getQuestionFillBlank();
if (!answer) {
questionFillBlank.setAnswer(null);
}
return questionBank;
}
return null;
}
@Override
public QuestionBank queryById(Integer id) {
return queryById(id, true);
}
/**
* 查询多条数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
@Override
public List<QuestionBank> queryAllByLimit(int offset, int limit) {
return this.questionBankMapper.queryAllByLimit(offset, limit);
}
/**
* 查询多条数据
*
* @return 对象列表
*/
@Override
public PageInfo<QuestionBank> queryAll(QuestionBank questionBank, int currentPage, int pageSize) {
PageHelper.startPage(currentPage, pageSize);
List<QuestionBank> questionBanks = questionBankMapper.queryAll(questionBank);
return new PageInfo<QuestionBank>(questionBanks);
}
/**
* 新增数据
*
* @param questionBank 实例对象
* @return 实例对象
*/
@Override
public QuestionBank insert(QuestionBank questionBank) {
Integer type = questionBank.getType();
//插入题库
questionBankMapper.insert(questionBank);
Integer primary = questionBank.getId();
//插入答案
//单选题
if (QuestionType.SINGLE_CHOICE.equals(type)) {
QuestionOption questionOption = questionBank.getQuestionOption();
questionOption.setQuestionBankId(primary);
questionOptionMapper.insert(questionOption);
}
//多选题
if (QuestionType.MULTIPLE_CHOICE.equals(type)) {
QuestionOption questionOption = questionBank.getQuestionOption();
questionOption.setQuestionBankId(primary);
questionOptionMapper.insert(questionOption);
}
//填空题
if (QuestionType.FILL_IN_THE_BLANKS.equals(type)) {
QuestionFillBlanks questionFillBlank = questionBank.getQuestionFillBlank();
questionFillBlank.setQuestionBankId(primary);
questionFillBlanksMapper.insert(questionFillBlank);
}
//判断题
if (QuestionType.JUDGE.equals(type)) {
QuestionJudgment questionJudgment = questionBank.getQuestionJudgment();
questionJudgment.setQuestionBankId(primary);
questionJudgmentMapper.insert(questionJudgment);
}
//主观题
//等于填空题
if (QuestionType.SUBJECTIVE.equals(type)) {
QuestionFillBlanks questionFillBlank = questionBank.getQuestionFillBlank();
questionFillBlank.setQuestionBankId(primary);
questionFillBlanksMapper.insert(questionFillBlank);
}
return questionBank;
}
/**
* 修改数据
*
* @param questionBank 实例对象
* @return 实例对象
*/
@Override
public QuestionBank update(QuestionBank questionBank) {
//题编号
Integer id = questionBank.getId();
//题型
Integer type = questionBank.getType();
//单选题
if (QuestionType.SINGLE_CHOICE.equals(type)) {
QuestionOption questionOption = questionBank.getQuestionOption();
questionOption.setQuestionBankId(id);
questionOptionMapper.update(questionOption);
}
//多选题
if (QuestionType.MULTIPLE_CHOICE.equals(type)) {
QuestionOption questionOption = questionBank.getQuestionOption();
questionOption.setQuestionBankId(id);
questionOptionMapper.update(questionOption);
}
//填空题
if (QuestionType.FILL_IN_THE_BLANKS.equals(type)) {
QuestionFillBlanks questionFillBlank = questionBank.getQuestionFillBlank();
questionFillBlank.setQuestionBankId(id);
questionFillBlanksMapper.update(questionFillBlank);
}
//判断题
if (QuestionType.JUDGE.equals(type)) {
QuestionJudgment questionJudgment = questionBank.getQuestionJudgment();
questionJudgment.setQuestionBankId(id);
questionJudgmentMapper.update(questionJudgment);
}
//主观题
//等于填空题
if (QuestionType.SUBJECTIVE.equals(type)) {
QuestionFillBlanks questionFillBlank = questionBank.getQuestionFillBlank();
questionFillBlank.setQuestionBankId(id);
questionFillBlanksMapper.update(questionFillBlank);
}
questionBankMapper.update(questionBank);
return this.queryById(questionBank.getId());
}
/**
* 通过主键删除数据
*
* @param id 主键
* @return 是否成功
*/
@Override
public boolean deleteById(Integer id) {
//删除答卷上面的题
testPaperMapper.deleteByQuestionBankId(id);
scoreMapper.deleteByQuestionBankId(id);
//先删除答案
//再删除题
QuestionBank questionBank = questionBankMapper.queryById(id);
Integer type = questionBank.getType();
//单选题
if (QuestionType.SINGLE_CHOICE.equals(type)) {
questionOptionMapper.deleteByQuestionBankId(id);
}
//多选题
if (QuestionType.MULTIPLE_CHOICE.equals(type)) {
questionOptionMapper.deleteByQuestionBankId(id);
}
//填空题
if (QuestionType.FILL_IN_THE_BLANKS.equals(type)) {
questionFillBlanksMapper.deleteByQuestionBankId(id);
}
//判断题
if (QuestionType.JUDGE.equals(type)) {
questionJudgmentMapper.deleteByQuestionBankId(id);
}
//主观题
if (QuestionType.SUBJECTIVE.equals(type)) {
questionFillBlanksMapper.deleteByQuestionBankId(id);
}
return questionBankMapper.deleteById(id) > 0;
}
@Override
public boolean deleteByIds(List<Integer> ids) {
if (ids != null && ids.size() > 0) {
ids.forEach(id -> {
boolean b = this.deleteById(id);
});
}
return true;
}
```
3.2 控制层代码
```java @Autowired QuestionBankService questionBankService;
@RequestMapping("/questionBanks")
public AjaxResult list(
QuestionBank questionBank,
@RequestParam(defaultValue = "1") int currentPage,
@RequestParam(defaultValue = "10") int pageSize) {
PageInfo
@RequestMapping("/get/{id}") public AjaxResult list(@PathVariable int id) { QuestionBank questionBank = questionBankService.queryById(id); return AjaxResult.success(questionBank); }
// 下面的记得添加权限 @RequestMapping("/add") public AjaxResult add(@Valid @RequestBody QuestionBank questionBank) { QuestionBank insert = questionBankService.insert(questionBank); return AjaxResult.success(insert); }
@RequestMapping("/update") public AjaxResult update(@Valid @RequestBody QuestionBank questionBank) { QuestionBank insert = questionBankService.update(questionBank); return AjaxResult.success(insert); }
@RequestMapping("/delete") public AjaxResult delete(Integer id) { boolean flag = questionBankService.deleteById(id); return AjaxResult.success(flag); }
@RequestMapping("/deletes")
public AjaxResult deletes(IdsVo idsVo) {
List
@RequestMapping("/searchCount")
public AjaxResult searchCount(String keyWord) {
Map
3.3 前端代码
```html
<el-form-item label="考场"> <el-input placeholder="请输入考试位置" v-model="searchData.examLocation" size="small" clearable> </el-input> </el-form-item> <el-form-item label="考试状态"> <el-select v-model="searchData.examState" placeholder="请选择考试状态" clearable size="small" > <el-option v-for="item in examState" :key="item.valueId" :label="item.valueName" :value="item.valueId"> </el-option> </el-select> </el-form-item> <el-form-item> <el-button icon="el-icon-search" size="mini" type="primary" @click="onSearch">搜索</el-button> <el-button icon="el-icon-refresh" size="mini" @click="onReset">重置</el-button> </el-form-item> </el-form> </div> </el-col> </el-row> <!--工具栏--> <el-row> <el-col :span="24"> <div > <el-button @click="handleAddBtn" size="mini" icon="el-icon-plus" type="primary">新增</el-button> <el-button :disabled="editBtnDisable" size="mini" icon="el-icon-edit" @click="handleEditBtn(null,{id:selected[0]})" type="success">修改 </el-button> <el-button :disabled="deleteBtnDisable" size="mini" icon="el-icon-delete" @click="handleDeleteByIds(selected)" type="danger">删除 </el-button> <el-button :loading="exportBtnLoading" :disabled="exportBtnDisable" size="mini" icon="el-icon-download" type="warning" @click="exportRole">导出 </el-button> <el-button :disabled="false" size="mini" icon="el-icon-refresh" @click="refresh">刷新 </el-button> </div> </el-col> </el-row> <!--表格--> <el-row> <el-col :span="24"> <div > <el-table :data="tableData" row-key="id" :tree-props="{children: 'children', hasChildren: 'hasChildren'}" @selection-change="selectionChange" stripe border > <el-table-column type="selection" align="center" width="50"> </el-table-column> <el-table-column label="展开" align="center" type="expand" width="50"> <template #default="props"> <el-form label-position="left" label-width="100px" inline > <el-form-item label="考试编号:"> <span>{{ props.row.id }}</span> </el-form-item> <el-form-item label="考试名称:"> <span>{{ props.row.examName }}</span> </el-form-item> <el-form-item label="考试科目:"> <span v-for="item in examSubject" :key="item.id" :label="item.subject" :value="item.id" v-if="props.row.examSubject===item.id"> {{item.subject}} </span> </el-form-item> <el-form-item label="出卷人:"> <span>{{ props.row.examProviderUser.name }}</span> </el-form-item> <el-form-item label="考试时间:"> <span> {{ props.row.examBeginTime }} 至 {{ props.row.examOverTime }} </span> </el-form-item> <el-form-item label="考试时长:"> <span> {{(new Date(props.row.examOverTime).getTime()-new Date(props.row.examBeginTime).getTime())/60000+'分钟' }} </span> </el-form-item> <el-form-item label="考试地点:"> <span>{{ props.row.examLocation }}</span> </el-form-item> <el-form-item label="考试状态:"> <span v-for="item in examState" :key="item.valueId" :label="item.valueName" :value="item.valueId" v-if="props.row.examState===item.valueId"> {{item.valueName}} </span> </el-form-item> <el-form-item label="考生数:"> <span>{{ props.row.examJoinNum }}</span> </el-form-item> </el-form> </template> </el-table-column> <el-table-column prop="examName" align="left" label="考试名称"> </el-table-column> <el-table-column align="left" label="考试科目"> <template slot-scope="scope"> <span v-for="item in examSubject" :key="item.id" :label="item.subject" :value="item.id" v-if="scope.row.examSubject===item.id"> {{item.subject}} </span> </template> </el-table-column> <el-table-column prop="examProviderUser.name" align="left" label="出卷人"> </el-table-column> <el-table-column prop="examBeginTime" align="left" label="考试时间"> </el-table-column> <el-table-column label="考试状态" width="100" align="center"> <template slot-scope="scope"> <span v-for="item in examState" :key="item.valueId" :label="item.valueName" :value="item.valueId" v-if="scope.row.examState===item.valueId"> <el-tag v-if="item.valueId===1">{{item.valueName}}</el-tag> <el-tag v-if="item.valueId===2" type="info">{{item.valueName}}</el-tag> <el-tag v-if="item.valueId===3" type="danger">{{item.valueName}}</el-tag> <el-tag v-if="item.valueId===4" type="warning">{{item.valueName}}</el-tag> <el-tag v-if="item.valueId===5" type="success">{{item.valueName}}</el-tag> </span> </template> </el-table-column> <el-table-column prop="examJoinNum" width="100" align="center" label="考生数"> </el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditBtn(scope.$index, scope.row)">编辑 </el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDeleteBtn(scope.$index, scope.row)">删除 </el-button> <el-dropdown size="mini"> <el-button size="mini" type="text" >更多<i ></i> </el-button> <el-dropdown-menu size="medium" slot="dropdown"> <el-dropdown-item @click.native="randomQuestions(scope.$index, scope.row)"> <i ></i>随机抽题 </el-dropdown-item> <el-dropdown-item @click.native="handleGetTestPaper(scope.$index, scope.row)"> <i ></i>查看试卷 </el-dropdown-item> <el-dropdown-item @click.native="handleInvigilate(scope.$index, scope.row)"> <i ></i>智慧监考 </el-dropdown-item> <el-dropdown-item @click.native="markingExamPapers(scope.$index, scope.row)"> <i ></i>考试阅卷 </el-dropdown-item> <el-dropdown-item @click.native="announceResults(scope.$index, scope.row)"> <i ></i>公布成绩 </el-dropdown-item> </el-dropdown-menu> </el-dropdown> </template> </el-table-column> </el-table> <div > <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10,20,50,100, 200, 300, 400]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> </div> </div> </el-col> </el-row> <el-dialog :close-on-click-modal="false" title="新增" width="600px" :visible.sync="addDialogVisible"> <el-form :inline="false" :model="add" label-position="right" label-width="90px"> <el-form-item label="考试名称"> <el-input placeholder="请输入考试名称" v-model="add.examName" size="small" clearable> </el-input> </el-form-item> <el-form-item label="考试科目"> <el-select v-model="add.examSubject" placeholder="请选择考试科目" size="small" > <el-option v-for="item in examSubject" :key="item.id" :label="item.subject" :value="item.id"> </el-option> </el-select> </el-form-item> <el-form-item label="考试时间"> <el-date-picker @change="timeChange" v-model="time" size="small" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"> </el-date-picker> </el-form-item> <el-form-item label="考试地点"> <el-input placeholder="请输入考试地点" v-model="add.examLocation" size="small" clearable> </el-input> </el-form-item> </el-form> <span slot="footer" > <el-button @click="addDialogVisible = false">取 消</el-button> <el-button type="primary" @click="handleAdd">确 定</el-button>
参考文献
- 基于分布式爬虫的在线考试系统设计与实现(厦门大学·李捷)
- 基于Web的在线考试系统的设计与实现(电子科技大学·廖欧)
- 基于微服务的在线教育系统的设计与实现(华中科技大学·毛颖志)
- 面向远程实验的在线智能考试系统研究和开发(北京邮电大学·龚潘晶)
- 基于微服务的在线教育系统的设计与实现(华中科技大学·毛颖志)
- 面向远程实验的在线智能考试系统研究和开发(北京邮电大学·龚潘晶)
- 基于微服务的在线教育系统的设计与实现(华中科技大学·毛颖志)
- 基于Java EE架构的在线考试系统设计与实现(西安电子科技大学·龚尚映)
- 基于J2EE的高职院校在线考试系统(内蒙古大学·赵源)
- 基于ASP.NET的在线考试系统设计与实现(吉林大学·吴树德)
- 基于微服务的在线教育系统的设计与实现(华中科技大学·毛颖志)
- 基于微服务的在线教育系统的设计与实现(华中科技大学·毛颖志)
- 基于微服务的在线教育系统的设计与实现(华中科技大学·毛颖志)
- 基于Web的在线考试系统的设计与实现(南昌大学·胡显春)
- 基于J2EE的高职院校在线考试系统(内蒙古大学·赵源)
本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:毕设工坊 ,原文地址:https://m.bishedaima.com/yuanma/35574.html