项目编号:BS-GX-041
前言:
在生活中,我们通常使用传统的纸质化考试;这就意味着花费大量的人力、物力、财力、时间和纸张,日复一日,年复一年,这种考试方式在一定程度上影响了学校的工作效率。基于这一原因,本次设计开发一个在线考试系统,解决传统纸质化考试带来的不便因素。在线考试系统是以现代信息技术为手段,通过组卷生成无纸化考试试卷进行考试,并及时生成考试成绩,具有提高管理工作效率、节约考试资源、规范考试管理、方便考生应考等传统考试不可替代的优势。
考试系统采用前后端分离,前端采用了vue+elementUI,后端采用Java语言,系统应用了Springboot+MyBatis的框架技术,在IDEA环境下开发完成。前台功能主要有用户注册登录,试题练习,会员中心,个人设置等功能模块。后台功能主要有试题、试卷、会员、题库管理,网站设置等功能模块。
本系统按照软件工程原理、方法进行开发,为学生课程练习及考试提供了灵活、方便、科学的检测手段。经测试,试用,目前本系统基本达到了设计任务书的设计目标,能够用于学校的机考工作。
一,项目简介
本项目基于Springboot+Vue开发实现了一套前后端分离的智能化考试管理系统,系统拥有完善的权限管理系统,可以根据不同的用户角色分配不同的权限,不同角色的用户登陆系统后可以进行不同的功能操作。目前根据业务划分为三个角色:系统管理员、老师、学生
管理登陆系统后可以操作的功能模块:
- 用户管理:管理学生和教师
- 试题管理:添加和管理试题,可以添加主观题与客观题等
- 试卷管理:实现自动组卷功能
- 角色管理:管理不同的角色及权限分配
- 菜单管理:也即权限管理,对显示的菜单功能进行管理
- 考试科目管理:管理考试的科目
- 班级管理:管理班级信息
- 成绩查询:查询各班学生的考试成绩
- 成绩统计:以图形报表方式统计不同分数段的考试情况
教师登陆系统:
- 个人密码修改
- 试题管理
- 试卷管理
- 成绩查询
- 成绩统计
学生登陆系统
- 个人密码修改
- 参与考试:选择要参与的试卷进行在线答题并自动算分
- 成绩查询
二,环境介绍
语言环境:Java: jdk1.8
数据库:Mysql: mysql5.7
应用服务器:Tomcat: tomcat8.5.31
开发工具:IDEA或eclipse
后台开发技术:springboot+mybatis
前台开发技术:vue+nodejs+elementUI
语言环境:Java: jdk1.8
数据库:Mysql: mysql5.7
应用服务器:Tomcat: tomcat8.5.31
开发工具:IDEA或eclipse
三,系统展示
管理员登陆
用户注册
用户管理
角色管理及权限分配
菜单权限管理
试题管理
科目管理
试卷管理
班级管理
成绩查询
成绩统计
四,核心代码展示
package cn.exam.controller; import cn.exam.config.BaseController; import cn.exam.config.UserUtil; import cn.exam.dao.mapper.zj.ZjPaperTestMapper; import cn.exam.dao.mapper.zj.ZjSubjectUserLinkMapper; import cn.exam.dao.mapper.zj.ZjTitleInfoMapper; import cn.exam.domain.zj.*; import cn.exam.query.PaperByUserIdQuery; import cn.exam.query.PaperQuery; import cn.exam.query.TitlePageQuery; import cn.exam.service.ExaminationService; import cn.exam.util.DateUtil; import cn.exam.util.PageResult; import cn.exam.util.ResultDTO; import cn.exam.util.SystemCode; import cn.exam.vo.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; /** * * @version 1.0 * @date 2022-02-26 14:30 */ @Controller @RequestMapping("title") public class ExaminationController extends BaseController { @Autowired private ExaminationService examinationService; @Autowired private UserUtil userUtil; @RequestMapping("queryTitlePage.htm") public void queryTitlePage(HttpServletResponse response, TitlePageQuery query) { ResultDTO<PageResult<List<TitleVO>>> resultDTO = new ResultDTO<>(); PageResult<List<TitleVO>> listPageResult = examinationService.queryPage(query); resultDTO.setResult(listPageResult); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccessPage(resultDTO, response); } //试卷分页 @RequestMapping("queryPaperPage.htm") public void queryTitlePage(HttpServletResponse response, PaperQuery query) { ResultDTO<PageResult<List<PaperPageVO>>> resultDTO = new ResultDTO<>(); PageResult<List<PaperPageVO>> listPageResult = examinationService.queryManagerPage(query); resultDTO.setResult(listPageResult); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccessPage(resultDTO, response); } @RequestMapping("insertByTitle.htm") public void insertByTitle(ZjTitleInfo info, HttpServletResponse response) { UserVO user = userUtil.getUser(); examinationService.insertTitle(info, user); sendJsonSuccess(response); } @RequestMapping("queryTitleInfo.htm") public void queryTitleInfo(HttpServletResponse response, Integer titleId) { ResultDTO<TitleVO> resultDTO = new ResultDTO<>(); resultDTO.setResult(examinationService.queryTitleInfo(titleId)); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccess(resultDTO, response); } @RequestMapping("deleteTileInfo.htm") public void deleteTileInfo(HttpServletResponse response, Integer titleId) { examinationService.deleteTitle(titleId); sendJsonSuccess(response); } @RequestMapping("updateTitle.htm") public void updateTitle(HttpServletResponse response,ZjTitleInfo info){ examinationService.updateTitle(info); sendJsonSuccess(response); } //试卷页面 @RequestMapping("queryPaper.htm") public void queryPaper(Integer paperId,HttpServletResponse response){ ResultDTO<PaperTestLevel> resultDTO = new ResultDTO<>(); resultDTO.setResult(examinationService.queryPaper(paperId)); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccess(resultDTO, response); } //考生查看考完的试卷 @RequestMapping("queryPaperCompleted.htm") public void queryPaperCompleted(Integer paperId,HttpServletResponse response) { ResultDTO<PaperTestLevel> resultDTO = new ResultDTO<>(); UserVO user = userUtil.getUser(); resultDTO.setResult(examinationService.queryPaperCompleted(paperId,user.getUserId())); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccess(resultDTO, response); } //组卷功能 @RequestMapping("audioExam.htm") public void audioExam(ZjPaperInfo paperInfo,HttpServletResponse response){ UserVO user = userUtil.getUser(); paperInfo.setTeachId(user.getUserId()); paperInfo.setTeachName(user.getUserName()); examinationService.audioPaper(paperInfo); sendJsonSuccess( response); } @RequestMapping("updateTitleByList.htm") public void updateTitleByList(String titleString,HttpServletResponse response){ examinationService.updateTitle(titleString); sendJsonSuccess(response); } /** * 学生已考试卷查询分页 */ @RequestMapping("queryPaperByUserId.htm") public void queryPaperByUserId(PaperByUserIdQuery query, HttpServletResponse response){ ResultDTO<PageResult<List<PaperByUserIdVO>>> resultDTO = new ResultDTO<>(); UserVO user = userUtil.getUser(); query.setUserId(user.getUserId()); PageResult<List<PaperByUserIdVO>> listPageResult = examinationService.queryPaperByUserId(query); resultDTO.setResult(listPageResult); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccessPage(resultDTO, response); } }
package cn.exam.controller; import cn.exam.config.BaseController; import cn.exam.config.RedisUtil; import cn.exam.dao.mapper.zj.ZjUserInfoMapper; import cn.exam.dao.mapper.zj.ZjUserRoleMapper; import cn.exam.domain.zj.ZjUserInfo; import cn.exam.domain.zj.ZjUserRole; import cn.exam.query.UserQuery; import cn.exam.redis.RedisKeyEnum; import cn.exam.service.UserInfoService; import cn.exam.service.ZjRoleMenuService; import cn.exam.util.*; import cn.exam.vo.MenuInfoVO; import cn.exam.vo.UserPageVO; import cn.exam.vo.UserVO; import cn.zq.exam.so.UserInfoSO; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.http.HttpServletResponse; import java.util.*; import java.util.stream.Collectors; /** * @author YS * @version 1.0 * @date 2021-02-02 14:49 */ @Controller @RequestMapping("login") public class LoginController extends BaseController { @Autowired private UserInfoService userInfoService; @Autowired private ZjUserInfoMapper userInfoMapper; @Autowired private RedisUtil redisUtil; @Autowired private ZjRoleMenuService roleMenuService; @Autowired private ZjUserRoleMapper userRoleMapper; /** * 登录 */ @RequestMapping("login.htm") public void login(ZjUserInfo userInfo, HttpServletResponse response) { ResultDTO<UserVO> resultDTO = new ResultDTO<>(); //注册shiro UserVO tblUser = userInfoService.queryUserInfoByName(userInfo.getUserId()); if (ObjectUtils.isEmpty(tblUser)) { throw new ExpressException(SystemCode.SERVICE_FAILD_CODE, "账号输入有误"); } String s = MD5Utils.md5(userInfo.getPassword()); if (!s.equals(tblUser.getPassword())) { throw new ExpressException(SystemCode.SERVICE_FAILD_CODE, "密码错误"); } UsernamePasswordToken shiroToken = new UsernamePasswordToken(userInfo.getUserId(), MD5Utils.md5(userInfo.getPassword())); Subject currentUser = SecurityUtils.getSubject(); currentUser.login(shiroToken); //获取用户信息 Object principal = SecurityUtils.getSubject().getPrincipal(); UserVO user1 = (UserVO) principal; try { resultDTO.setDescription(SystemCode.RET_MSG_SUCC); String token = UUID.randomUUID().toString(); user1.setToken(token); resultDTO.setCode(SystemCode.RET_CODE_SUCC); resultDTO.setResult(user1);//LOGIN_USER_INFO:UUID.randomUUID().toString() redisUtil.setKeyTime(RedisKeyEnum.USER.getCode() + ":" + token, JSON.toJSONString(user1), Constant.KEY_IN_REDIS_TIME); } catch (IncorrectCredentialsException e) { resultDTO.setCode(SystemCode.USER_LOGIN_ERROR_CODE); resultDTO.setDescription("密码错误"); } catch (LockedAccountException e) { resultDTO.setCode(SystemCode.USER_LOGIN_ERROR_CODE); resultDTO.setDescription("登录失败,该用户已被冻结"); } catch (IllegalStateException e) { resultDTO.setCode(SystemCode.SYS_OVERDUE_CODE); resultDTO.setDescription(SystemCode.SYS_OVERDUE_CODE_DESC); } catch (Exception e) { resultDTO.setCode(SystemCode.USER_LOGIN_ERROR_CODE); resultDTO.setDescription(SystemCode.USER_LOGIN_ERROR_MSG); } sendJsonResult(resultDTO, response); } /** * 角色菜单查询 * * @param roleId 角色id * @param response 响应体 */ @RequestMapping("queryMenuList.htm") public void queryMenuList(String roleId, HttpServletResponse response) { JSONArray array = JSON.parseArray(roleId); List<String> roleIdList = new ArrayList<>(); for (Object json : array) { JSONObject jsonObject = JSON.parseObject(json.toString()); Object roleId1 = jsonObject.get("roleId"); roleIdList.add(roleId1.toString()); } ResultDTO<List<MenuInfoVO>> resultDTO = new ResultDTO<>(); List<MenuInfoVO> menuVOS = roleMenuService.queryMenuList(roleIdList); //jdk8新特性 stream流处理去重 ArrayList<MenuInfoVO> infos2 = menuVOS.stream() .collect(Collectors.collectingAndThen(Collectors .toCollection(() -> new TreeSet<>(Comparator .comparing(MenuInfoVO::getMenuId))), ArrayList::new)); resultDTO.setResult(infos2); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJson(resultDTO, response); } /** * 退出 */ @RequestMapping(method = RequestMethod.POST, value = "logout.htm") public void logout(HttpServletResponse response) { Subject currentUser = SecurityUtils.getSubject(); currentUser.logout(); sendJsonSuccess(response); } /** * 注册 */ @RequestMapping("registerLogin.htm") public void registerLogin(UserInfoSO so, HttpServletResponse response) { ResultDTO<ZjUserInfo> resultDTO = new ResultDTO<>(); if (ObjectUtils.isEmpty(so.getUserId()) || ObjectUtils.isEmpty(so.getPassword()) || ObjectUtils.isEmpty(so.getUserName())) { throw new ExpressException(SystemCode.SERVICE_FAILD_CODE, "必填字段不能为空"); } if (!so.getPassword().equals(so.getConfirmPassword())) { throw new ExpressException(SystemCode.SERVICE_FAILD_CODE, "密码不一致"); } UserVO user = userInfoMapper.queryShiroUserInfoByUserName(so.getUserId()); if (!ObjectUtils.isEmpty(user)) { throw new ExpressException(SystemCode.SERVICE_FAILD_CODE, "账号已存在,请勿重复注册"); } ZjUserInfo userInfo = new ZjUserInfo(); String currentTime = DateUtils.getCurrentTime(); userInfo.setUserId(so.getUserId()); userInfo.setUserName(so.getUserName()); userInfo.setTypeId(so.getTypeId()); if (so.getTypeId()==0){ userInfo.setClassId(so.getClassId()); } userInfo.setPassword(MD5Utils.md5(so.getPassword())); userInfo.setCreateTime(currentTime); userInfo.setUpdateTime(currentTime); userInfoMapper.insertSelective(userInfo); ZjUserRole userRole = new ZjUserRole(); if (so.getTypeId()==0){ userRole.setRoleId("student"); }else if (so.getTypeId()==1){ userRole.setRoleId("teacher"); } userRole.setUserId(userInfo.getUserId()); userRoleMapper.insertSelective(userRole); resultDTO.setDescription(SystemCode.RET_MSG_SUCC); resultDTO.setCode(SystemCode.RET_CODE_SUCC); sendJsonSuccess(resultDTO, response); } /** * 用户查询页面 */ @RequestMapping("queryUserInfo.htm") public void queryPage(HttpServletResponse response, UserQuery query) { ResultDTO<PageResult<List<UserPageVO>>> resultDTO = new ResultDTO<>(); PageResult<List<UserPageVO>> listPageResult = userInfoService.queryPage(query); resultDTO.setResult(listPageResult); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC, SystemCode.RET_MSG_SUCC); sendJsonSuccessPage(resultDTO, response); } /** * 用户信息修改 */ @RequestMapping("updateUserInfo.htm") public void updateUserInfo(HttpServletResponse response, UserInfoSO so) { ZjUserInfo zjUserInfo = userInfoMapper.selectByPrimaryKey(so.getUserId()); if (ObjectUtils.isEmpty(zjUserInfo)) { throw new ExpressException(SystemCode.SERVICE_FAILD_CODE, "用户信息异常"); } ZjUserInfo userInfo = new ZjUserInfo(); //如果密码修改,则和原密码对比 不一致新密码重新md5存入数据库 if (!so.getPassword().equals(zjUserInfo.getPassword())) { String s = MD5Utils.md5(so.getPassword()); userInfo.setPassword(s); } userInfo.setTypeId(Integer.valueOf(so.getTypeId())); userInfo.setIsDelete(Integer.valueOf(so.getIsDelete())); userInfo.setUserId(so.getUserId()); userInfo.setUserName(so.getUserName()); userInfo.setUpdateTime(DateUtils.getCurrentTime()); userInfoMapper.updateByPrimaryKeySelective(userInfo); sendJsonSuccess(response); } }
package cn.exam.controller; import cn.exam.config.BaseController; import cn.exam.domain.zj.ZjMenuInfo; import cn.exam.query.ZjMenuQuery; import cn.exam.service.ZjMenuInfoService; import cn.exam.so.RoleMenuIdSO; import cn.exam.util.*; import cn.exam.vo.RoleMenuVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletResponse; import java.util.List; /** * @author YS * @version 1.0 * @date 2021-02-22 9:12 */ @Controller @RequestMapping("menu") public class MenuInfoController extends BaseController { @Autowired private ZjMenuInfoService menuInfoService; /** * 分页 * @param response 响应体 * @param query 菜单名通用查询对象 */ @RequestMapping("queryPage.htm") public void queryPage(HttpServletResponse response, ZjMenuQuery query){ ResultDTO<PageResult<List<ZjMenuInfo>>> resultDTO = new ResultDTO<>(); PageResult<List<ZjMenuInfo>> listPageResult = menuInfoService.queryMenuInfoPage(query); resultDTO.setResult(listPageResult); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC,SystemCode.RET_MSG_SUCC); sendJsonSuccessPage(resultDTO,response); } /** * * @param roleId 角色id * @param response 响应体 */ @RequestMapping("queryMenuIdListByRoleId.htm") public void queryMenuIdListByRoleId(String roleId, HttpServletResponse response){ ResultDTO<List<Integer>> resultDTO = new ResultDTO<>(); List<Integer> integers = menuInfoService.queryMenuIdListByRoleId(roleId); resultDTO.setResult(integers); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC,SystemCode.RET_MSG_SUCC); sendJsonSuccess(resultDTO,response); } /** * * @param roleId 角色id * @param response 响应体 */ @RequestMapping("queryMenuListByRoleId.htm") public void queryMenuListByRoleId(String roleId, HttpServletResponse response){ ResultDTO<List<RoleMenuVO>> resultDTO = new ResultDTO<>(); List<RoleMenuVO> menuVOS = menuInfoService.queryMenuTreeByRoleId(roleId); System.out.println(menuVOS); resultDTO.setResult(menuVOS); resultDTO.buildReturnCode(SystemCode.RET_CODE_SUCC,SystemCode.RET_MSG_SUCC); sendJson(resultDTO,response); } @RequestMapping("updateRoleMenuInfo.htm") public void updateRoleMenuInfo(HttpServletResponse response, RoleMenuIdSO so){ menuInfoService.updateRoleMenuInfo(so); sendJsonSuccess(response); } @RequestMapping("insertMenuInfo.htm") public void insertMenuInfo(ZjMenuInfo info ,HttpServletResponse response){ String currentDateTime = DateUtil.getCurrentDateTime(); info.setMenuStatus(1); info.setCreateTime(currentDateTime); info.setUpdateTime(currentDateTime); menuInfoService.insertMenuInfo(info); sendJsonSuccess(response); } }
五,项目总结
多年来,国内外网络信息发展迅猛,因而传统的考试势必被信息化的产品所替代。网络信息时代将传统教育代逐推向信息教育,网络考试已经渐渐地替代了传统纸质考试是现代化技术教育的方向之一,且它的考试具有着不受地点限制,节约资源,快速方便等特点,深受各大院校师生的喜爱。
一般地,学校与社会中,不论多大规模的考试大都采用传统的纸质考试模式。在这一情况下,组织一次考试至少要经过教师手动出卷子、学生按教室地点指定时间参加考试、教师参加评阅试卷、学生成绩进行登记、考试试卷进行分析等步骤。这样一来,当考试种类渐渐地增加,考试的需求逐步增强,学校各学院老师们的工作量也变得很多,老师的压力也是巨大的。因此,传统的考试模式已经不能再满足各校学院教师学生的需求了。
况且学生面临很多学习压力,面对繁重的课业负担,教师则面临着繁琐的工作。如何让学生采取方便有效的复习方法和进行自我学习的检测,让教师从繁琐的工作中减轻压力成为本次研究对象。因此本系统能够符合教师学生们的需求,帮助学生解决知识复习,自我测评等问题,让学生在考试之前可以进行知识点的练习复习巩固,以更有效的方式参加考试取得优异的成绩。同时帮助老师更方便的出试卷,管理题库试题,以及不用手动批改试卷,大大增强老师工作效率,使得老师办公更得力!