29基于java的在线考试系统设计与实现

本章节来介绍一个基于java的在线考试系统的实现

系统概要

近年来,随着世界各国需要参加考核的人员与日俱增,单纯依靠传统的人工安排考场和监考人员的纸质化考试逐渐显示出了效率低,易发生冲突的缺陷,这时,在线考试系统便应运而生,此种考试方式以方便快捷高效等优点将越来越适用于如今的各项考试、考核。此外,无纸化在线考试对考试人员和审阅人员均提供了便捷。因此,本文将主要以JAVA为开发基础,实现一个在线考试系统。

它的用户由学生、教师,管理员和超级管理员组成。学生登陆系统可以进行在线测试和成绩查询。当学生登陆时,系统会随机地为学生选取试题组成考卷。当学生提交考卷后,系统会自动批改客观题,并将试卷提供给教师查看和提醒教师对试卷主观题进行修改。待教师修改完试卷后,系统会自动生成考生成绩和分数段统计信息。学生可以查询自己的成绩信息和试卷,以便更好地了解自己的学习情况。教师也可以通过分数段统计信息更好地了解学生的学生情况。后台管理员可以对考题,考试设置信息,用户信息进行维护。学生,教师,管理员和超级管理员都可以对个人信息进行维护。

  • 基础功能
  • 登录、注册
  • 首页公告栏
  • 操作日志
    ....等等
  • 学生角色
  • 查询考试列表
  • 参加考试
  • 查看错题集
  • 成绩分析
    .....等等
  • 教师角色
  • 审批管理:管理请求绑定自己班级的审批
  • 学生管理:管理自己班级下的所有学生
  • 班级管理:管理自己的班级
  • 考试管理:发布考试(自动生成试卷)、修改考试信息、取消考试
  • 成绩统计
    ....等等
  • 管理员角色
  • 用户管理:管理普通用户(学生、教师角色)
  • 审批管理:管理学生绑定教师某个班级的审批(增、同意/拒绝审批、删、查)
  • 班级管理:管理系统中所有班级
  • 查询成绩、做题记录
  • 题目管理:包括单选、多选、判断
  • 题库管理
  • 公告管理
  • 试卷管理:管理相应试卷(对已结束考试锁定,不可修改)
  • 考试管理:管理考试相关信息
    ....等等
  • 超级管理角色
    拥有全部角色权限,且在此基础上添加功能:
  • 管理员管理:管理管理员角色用户
  • 系统公告管理
  • 系统所有的操作日志留痕

详细功能在下面会介绍到。

系统使用的架构

采用B/S的架构实现,整体遵循MVC的设计思想。

> 后端:java,spring,springmvc,mybatis,springboot等
> 数据库:mysql
> 开发工具:idea或者eclipse
> 前端:html,css,javascript,jquery,layui等
> 文件储存:采用的七牛云储存
> 详细系统介绍:http://projecthelp.top

项目实现

  • 用户 UserController 的实现
@Controller
@Slf4j
@Api("用户controller")
public class UserController {

    @Autowired
    private UserService userService;
    @Autowired
    private UserClazzService userClazzService;
    @Autowired
    private Kaptcha kaptcha;

    @PostMapping("/login")
    @ResponseBody
    @ApiOperation("登录")
    @OperationLog("登录")
    public ResponseParam login(String username, String password, String code) {
        String msg = "";
        try {
            if (kaptcha.validate(code)){
                //获取主体对象
                Subject subject = SecurityUtils.getSubject();
                subject.login(new UsernamePasswordToken(username, password));
                log.info("{}登录成功",subject.getPrincipal());
                return new ResponseParam("登录成功");
            }
        } catch (KaptchaNotFoundException e) {
            msg="验证码已失效!";
        } catch (KaptchaIncorrectException e) {
            msg="验证码错误!";
        } catch (UnknownAccountException e) {
            msg="用户名错误!";
        } catch (IncorrectCredentialsException e) {
            msg="密码错误!";
        } catch (Exception e){
           throw e;
        }
        log.info("{}登录失败,{}",username,msg);
        return ResponseUtil.getErrorResponseParam(msg);
    }

    @GetMapping("logout")
    @ApiOperation("注销")
    @OperationLog("注销")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }

    @PostMapping("/register")
    @ResponseBody
    @ApiOperation("注册")
    @OperationLog("注册")
    public ResponseParam registerTeacher(@Validated @RequestBody RequestDataParam<RegisterUserParam> req) throws Exception {
        RegisterUserParam data = req.getData();
        if(ObjectUtil.notEqual(data.getRole(), RoleEnum.STUDENT.getCode()) && ObjectUtil.notEqual(data.getRole(), RoleEnum.TEACHER.getCode())){
            throw new ServiceException("注册失败");
        }
        UserRequestParam user = new UserRequestParam();
        BeanUtil.copyProperties(data, user);
        RequestDataParam<UserRequestParam> param = new RequestDataParam<>();
        param.setData(user);
        return save(param);
    }

    @RequiresUser
    @ResponseBody
    @PostMapping({"/user/information"})
    @ApiOperation("个人信息修改")
    @OperationLog("个人信息修改")
    public ResponseParam information(@Validated @RequestBody RequestDataParam<UserRequestParam> req){
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        UserRequestParam data = req.getData();
        data.setUsername(null);
        data.setRole(null);
        data.setId(user.getId());
        return update(data);
    }

    /*----------------------------------管理员------------------------------*/

    @PostMapping("/user/updateUser/{id}")
    @ResponseBody
    @ApiOperation("用户管理:修改用户")
    @OperationLog("用户管理:修改用户")
    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    public ResponseParam updateUser(@PathVariable("id")Long id, @Validated @RequestBody RequestDataParam<UserRequestParam> req) throws Exception {
        Integer role = req.getData().getRole();
        if(!(RoleEnum.STUDENT.getCode().equals(role) || RoleEnum.TEACHER.getCode().equals(role))){
            return ResponseUtil.getErrorResponseParam("您没有权限!");
        }
        req.getData().setId(id);
        return update(req.getData());
    }

    @PostMapping("/user/saveUser")
    @ResponseBody
    @ApiOperation("用户管理:新增用户")
    @OperationLog("用户管理:新增用户")
    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    public ResponseParam saveUser(@Validated @RequestBody RequestDataParam<UserRequestParam> req) throws Exception {
        Integer role = req.getData().getRole();
        if(!(RoleEnum.STUDENT.getCode().equals(role) || RoleEnum.TEACHER.getCode().equals(role))){
            return ResponseUtil.getErrorResponseParam("您没有权限!");
        }
        return save(req);
    }

    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    @PostMapping("/user/listUserForPage")
    @ResponseBody
    @ApiOperation("用户管理:查询用户")
    @OperationLog("用户管理:查询用户")
    public ResponseParam listUserForPage(@Validated @RequestBody PageRequestParam<UserPageRequestParam> req) throws Exception {
        UserPageRequestParam data = req.getData();

        Page<User> page = new Page<>(req.getPage(),req.getLimit());
        page = userService.listForPage(page,data,false);

        return new ResponseParam(page);
    }

    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    @PostMapping("/user/deleteUser/{id}")
    @ResponseBody
    @ApiOperation("用户管理:删除用户")
    @OperationLog("用户管理:删除用户")
    public ResponseParam deleteUser(@PathVariable("id")Long id) {
        User user = userService.getById(id);
        if(ObjectUtil.isNull(user) || !(RoleEnum.STUDENT.getCode().equals(user.getRole()) || RoleEnum.TEACHER.getCode().equals(user.getRole()))){
            return ResponseUtil.getErrorResponseParam("您没有权限!");
        }
        return deleteById(id);
    }

    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    @PostMapping("/user/lockUser/{id}")
    @ResponseBody
    @ApiOperation("用户管理:封禁/解封用户")
    @OperationLog("用户管理:封禁/解封用户")
    public ResponseParam lockUser(@PathVariable("id")Long id) {
        User user = userService.getById(id);
        if(ObjectUtil.isNull(user) || !(RoleEnum.STUDENT.getCode().equals(user.getRole()) || RoleEnum.TEACHER.getCode().equals(user.getRole()))){
            return ResponseUtil.getErrorResponseParam("您没有权限!");
        }
        return lockById(id);
    }

    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    @GetMapping("/user/listStudent")
    @ResponseBody
    @ApiOperation("用户管理:学生列表")
    @OperationLog("用户管理:学生列表")
    public ResponseParam listStudent() {
        List<User> userList = userService.listUser(RoleEnum.STUDENT.getCode());
        return new ResponseParam(userList);
    }

    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    @GetMapping("/user/listTeacher")
    @ResponseBody
    @ApiOperation("用户管理:教师列表")
    @OperationLog("用户管理:教师列表")
    public ResponseParam listTeacher() {
        List<User> userList = userService.listUser(RoleEnum.TEACHER.getCode());
        return new ResponseParam(userList);
    }

    @RequiresRoles(value = "superadmin")
    @PostMapping("/user/deleteAdmin/{id}")
    @ResponseBody
    @ApiOperation("用户管理:删除管理员")
    @OperationLog("用户管理:删除管理员")
    public ResponseParam deleteAdmin(@PathVariable("id")Long id) {
        return deleteById(id);
    }

    @RequiresRoles(value = "superadmin")
    @PostMapping("/user/lockAdmin/{id}")
    @ResponseBody
    @ApiOperation("用户管理:封禁/解封管理员")
    @OperationLog("用户管理:封禁/解封管理员")
    public ResponseParam lockAdmin(@PathVariable("id")Long id) {
        return lockById(id);
    }

    @RequiresRoles(value = "superadmin")
    @PostMapping("/user/listAdminForPage")
    @ResponseBody
    @ApiOperation("用户管理:查询管理员")
    @OperationLog("用户管理:查询管理员")
    public ResponseParam listAdminForPage(@Validated @RequestBody PageRequestParam<UserPageRequestParam> req) {
        UserPageRequestParam data = req.getData();

        Page<User> page = new Page<>(req.getPage(),req.getLimit());
        page = userService.listForPage(page,data,true);

        return new ResponseParam(page);
    }

    @PostMapping("/user/saveAdmin")
    @ResponseBody
    @ApiOperation("用户管理:新增管理员")
    @OperationLog("用户管理:新增管理员")
    @RequiresRoles(value = "superadmin")
    public ResponseParam saveAdmin(@Validated @RequestBody RequestDataParam<UserRequestParam> req) throws Exception {
        return save(req);
    }

    @PostMapping("/user/updateAdmin/{id}")
    @ResponseBody
    @ApiOperation("用户管理:修改管理员")
    @OperationLog("用户管理:修改管理员")
    @RequiresRoles(value = "superadmin")
    public ResponseParam updateAdmin(@PathVariable("id")Long id, @Validated @RequestBody RequestDataParam<UserRequestParam> req) {
        req.getData().setId(id);
        return update(req.getData());
    }

    @RequiresRoles(value = {"teacher","superadmin"}, logical = Logical.OR)
    @PostMapping("/user/listStudentForPage")
    @ResponseBody
    @ApiOperation("用户管理:查询学生")
    @OperationLog("用户管理:查询学生")
    public ResponseParam listStudentForPage(@Validated @RequestBody PageRequestParam<StudentPageRequestParam> req) throws Exception {
        StudentPageRequestParam data = req.getData();

        User user = (User) SecurityUtils.getSubject().getPrincipal();
        UserClazz userClazz = UserClazz.builder().userId(user.getId()).build();
        List<Long> clazzIds = userClazzService.list(userClazz).stream().map(UserClazz::getClazzId).collect(Collectors.toList());
        List<Long> studentIds = userClazzService.listByClazzIds(clazzIds).stream().map(UserClazz::getUserId).collect(Collectors.toList());

        data.setStudentIds(studentIds);
        Page<User> page = new Page<>(req.getPage(),req.getLimit());
        page = userService.listStudentForPage(page,data);

        return new ResponseParam(page);
    }
}
  • QiniuUtil七牛云工具类
@Component
@Slf4j
public class QiniuUtil {

    @Value("${oss.qiniu.accessKey}")
    private String accessKey;

    @Value("${oss.qiniu.secretKey}")
    private String secretKey;

    @Value("${oss.qiniu.bucketname}")
    private String bucketname;

    public String upload(MultipartFile file) throws IOException, UploadException {
        if(ObjectUtil.isNull(file)) {
            return "";
        }
        // region2是华南区域
        Configuration cfg = new Configuration(Region.autoRegion());
        UploadManager uploadManager = new UploadManager(cfg);
        // 生成上传凭证,然后准备上传
        Auth auth = Auth.create(accessKey, secretKey);
        // 上传
        Response response;
        String fileName;
        String filType;
        DefaultPutRet putRet = null;
        try {
            String originalFileName = file.getOriginalFilename();
            filType = originalFileName.substring(originalFileName.lastIndexOf("."));
            fileName = StrUtil.concat(true, String.valueOf(DateUtil.currentSeconds()), RandomUtil.randomString(6),filType);
            response = uploadManager.put(file.getBytes(), fileName, auth.uploadToken(bucketname));
            // 解析上传成功的结果
            putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
        } catch (QiniuException e) {
            // 请求失败时打印的异常的信息
            log.warn(StrUtil.concat(true,StrUtil.toString(e.code()),e.response.toString()));
            throw new UploadException("上传七牛云失败");
        }
        return fileName;
    }

    //删除文件 参数:存储的图片文件名
    public void deleteFileFromQiniu(String fileName){
        Configuration cfg = new Configuration(Region.region2());
        String key = fileName;
        Auth auth = Auth.create(accessKey, secretKey);
        BucketManager bucketManager = new BucketManager(auth, cfg);
        try {
            bucketManager.delete(bucketname, key);
        } catch (QiniuException e) {
            //如果遇到异常,说明删除失败
            log.warn(StrUtil.concat(true,StrUtil.toString(e.code()),e.response.toString()));
        }
    }
}
  • 成绩导出工具类
@Slf4j
public class ExcelUtil {

    public static void download(HttpServletResponse response,String fileName, Class pojoClass, Collection collection) throws IOException, UploadException {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20") + ".xlsx");
            EasyExcel.write(response.getOutputStream(), pojoClass)
                    .autoCloseStream(Boolean.FALSE)
                    .sheet("sheet1")
                    .doWrite(collection);
        } catch (Exception e) {
            log.error("成绩导出失败,文件下载时发生异常",e);
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSONUtil.toJsonStr(map));
        }
    }
}

部分功能展示

  • 登录页面


    image.png

管理员,教师和学生角色统一一个登陆页面,通过登录进去后台判断不同的角色权限,跳转到不同的页面系统。

  • 注册


    image.png

系统目前可以是学生和老师进行一个注册,然后进入系统,当然如果你的业务是不需要用户进行注册的话,可以把这个注册给删除掉,因为管理员也是可以在后台进行添加用户的。

管理员身份

  • 首页
    首页有一个轮播图和系统公告,系统公告管理员可以加入后台进行管理,点击后台入口即可进入管理页面。


    image.png
  • 学生和教师用户管理


    image.png

管理员可以增加和修改用户,包括学生和教师,也可以禁用用户操作。

  • 班级管理


    image.png
  • 学生申请加入班级


    image.png

因为开放了学生自己注册的功能,所以学生和教师的关联,可以学生自己通过系统进行申请,当然如果实际情况不是注册的方式的话,管理员可以自己进行关联的,该功能可以灵活去掉。

  • 在线考试管理
    考试的一个过程需要在这申明一下:

1.首先先要有一个题库 ;
2.然后管理员或者老师往这个题库里面添加题目
3.创建一份试卷,试卷的产生是通过自己定义题目的个数,然后选择上面创建的题库,进行自动组卷。
4.创建一次考试,需要选择一份试卷,然后选中考试的班级,然后还有开始的时间。
5.到达开始时间后,学生登录系统中进行考试。
6.考试结束后,提交试卷,为防止考试学生提前交卷,然后告诉答案给考场上面正在考试的同学,所以考试结束后才开始系统自动触发批改系统job.
7.学生查看开始分数情况。

  • 题库管理


    image.png
  • 题目管理


    image.png

新增加题目:


image.png
  • 试卷管理


    image.png

新增加一份试卷,进行自动组卷:


image.png
  • 考试管理


    image.png

新增加一次考试:


image.png
  • 学生成绩管理


    image.png

还可以导出考试考的成绩列表:


image.png

超级管理员角色

具有所有的上面管理员选线,额外再增加以下的功能:

  • 管理所有管理员


    image.png
  • 系统公告管理


    image.png
  • 系统操作日志管理


    image.png

教师身份角色

  • 首页


    image.png
  • 教师管理端首页


    image.png

显示最近考试的情况统计。

  • 学生列表


    image.png

相对于管理员来讲,就只能看到学生用户,而看不到管理员用户角色的,并且不可以增加学生用户。

  • 班级管理


    image.png

下面具体的教师的权限信息,可以参照右边的导航栏和上面管理员的角色看到教师角色所具有的权限,这里就没必要一一列举了:


image.png

学生角色

  • 学生首页


    image.png
  • 关联教师
    因为学生有可能是自己注册进来的,所以需要自己提交申请加入老师的班级:


    image.png
  • 考试列表


    image.png

这里展示的是自己关联的老师下的班级下已经创建的考试列表。

  • 参加考试


    image.png
  • 考试页面


    image.png
  • 我的成绩


    image.png
  • 我的错题


    image.png
  • 成绩分析


  • 个人信息


    image.png
  • 我的班级


    image.png
  • 修改登录密码


    image.png

以上是大部分功能展示,所有的功能都是正常运行,没有bug,导入到idea或者eclipse即可运行,没有套路,具体细节大家下载后自己慢慢研究,欢迎大家一起交流学习

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351