29基于java的在線考試系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)

本章節(jié)來介紹一個基于java的在線考試系統(tǒng)的實(shí)現(xiàn)

系統(tǒng)概要

近年來,隨著世界各國需要參加考核的人員與日俱增,單純依靠傳統(tǒng)的人工安排考場和監(jiān)考人員的紙質(zhì)化考試逐漸顯示出了效率低,易發(fā)生沖突的缺陷,這時,在線考試系統(tǒng)便應(yīng)運(yùn)而生,此種考試方式以方便快捷高效等優(yōu)點(diǎn)將越來越適用于如今的各項(xiàng)考試、考核。此外,無紙化在線考試對考試人員和審閱人員均提供了便捷。因此,本文將主要以JAVA為開發(fā)基礎(chǔ),實(shí)現(xiàn)一個在線考試系統(tǒng)。

它的用戶由學(xué)生、教師,管理員和超級管理員組成。學(xué)生登陸系統(tǒng)可以進(jìn)行在線測試和成績查詢。當(dāng)學(xué)生登陸時,系統(tǒng)會隨機(jī)地為學(xué)生選取試題組成考卷。當(dāng)學(xué)生提交考卷后,系統(tǒng)會自動批改客觀題,并將試卷提供給教師查看和提醒教師對試卷主觀題進(jìn)行修改。待教師修改完試卷后,系統(tǒng)會自動生成考生成績和分?jǐn)?shù)段統(tǒng)計(jì)信息。學(xué)生可以查詢自己的成績信息和試卷,以便更好地了解自己的學(xué)習(xí)情況。教師也可以通過分?jǐn)?shù)段統(tǒng)計(jì)信息更好地了解學(xué)生的學(xué)生情況。后臺管理員可以對考題,考試設(shè)置信息,用戶信息進(jìn)行維護(hù)。學(xué)生,教師,管理員和超級管理員都可以對個人信息進(jìn)行維護(hù)。

  • 基礎(chǔ)功能
  • 登錄、注冊
  • 首頁公告欄
  • 操作日志
    ....等等
  • 學(xué)生角色
  • 查詢考試列表
  • 參加考試
  • 查看錯題集
  • 成績分析
    .....等等
  • 教師角色
  • 審批管理:管理請求綁定自己班級的審批
  • 學(xué)生管理:管理自己班級下的所有學(xué)生
  • 班級管理:管理自己的班級
  • 考試管理:發(fā)布考試(自動生成試卷)、修改考試信息、取消考試
  • 成績統(tǒng)計(jì)
    ....等等
  • 管理員角色
  • 用戶管理:管理普通用戶(學(xué)生、教師角色)
  • 審批管理:管理學(xué)生綁定教師某個班級的審批(增、同意/拒絕審批、刪、查)
  • 班級管理:管理系統(tǒng)中所有班級
  • 查詢成績、做題記錄
  • 題目管理:包括單選、多選、判斷
  • 題庫管理
  • 公告管理
  • 試卷管理:管理相應(yīng)試卷(對已結(jié)束考試鎖定,不可修改)
  • 考試管理:管理考試相關(guān)信息
    ....等等
  • 超級管理角色
    擁有全部角色權(quán)限,且在此基礎(chǔ)上添加功能:
  • 管理員管理:管理管理員角色用戶
  • 系統(tǒng)公告管理
  • 系統(tǒng)所有的操作日志留痕

詳細(xì)功能在下面會介紹到。

系統(tǒng)使用的架構(gòu)

采用B/S的架構(gòu)實(shí)現(xiàn),整體遵循MVC的設(shè)計(jì)思想。

> 后端:java,spring,springmvc,mybatis,springboot等
> 數(shù)據(jù)庫:mysql
> 開發(fā)工具:idea或者eclipse
> 前端:html,css,javascript,jquery,layui等
> 文件儲存:采用的七牛云儲存
> 詳細(xì)系統(tǒng)介紹:http://projecthelp.top

項(xiàng)目實(shí)現(xiàn)

  • 用戶 UserController 的實(shí)現(xiàn)
@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="驗(yàn)證碼已失效!";
        } catch (KaptchaIncorrectException e) {
            msg="驗(yàn)證碼錯誤!";
        } 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("您沒有權(quán)限!");
        }
        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("您沒有權(quán)限!");
        }
        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("您沒有權(quán)限!");
        }
        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("您沒有權(quán)限!");
        }
        return lockById(id);
    }

    @RequiresRoles(value = {"admin","superadmin"}, logical = Logical.OR)
    @GetMapping("/user/listStudent")
    @ResponseBody
    @ApiOperation("用戶管理:學(xué)生列表")
    @OperationLog("用戶管理:學(xué)生列表")
    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("用戶管理:查詢學(xué)生")
    @OperationLog("用戶管理:查詢學(xué)生")
    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是華南區(qū)域
        Configuration cfg = new Configuration(Region.autoRegion());
        UploadManager uploadManager = new UploadManager(cfg);
        // 生成上傳憑證,然后準(zhǔn)備上傳
        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));
            // 解析上傳成功的結(jié)果
            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;
    }

    //刪除文件 參數(shù):存儲的圖片文件名
    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()));
        }
    }
}
  • 成績導(dǎo)出工具類
@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("成績導(dǎo)出失敗,文件下載時發(fā)生異常",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

管理員,教師和學(xué)生角色統(tǒng)一一個登陸頁面,通過登錄進(jìn)去后臺判斷不同的角色權(quán)限,跳轉(zhuǎn)到不同的頁面系統(tǒng)。

  • 注冊


    image.png

系統(tǒng)目前可以是學(xué)生和老師進(jìn)行一個注冊,然后進(jìn)入系統(tǒng),當(dāng)然如果你的業(yè)務(wù)是不需要用戶進(jìn)行注冊的話,可以把這個注冊給刪除掉,因?yàn)楣芾韱T也是可以在后臺進(jìn)行添加用戶的。

管理員身份

  • 首頁
    首頁有一個輪播圖和系統(tǒng)公告,系統(tǒng)公告管理員可以加入后臺進(jìn)行管理,點(diǎn)擊后臺入口即可進(jìn)入管理頁面。


    image.png
  • 學(xué)生和教師用戶管理


    image.png

管理員可以增加和修改用戶,包括學(xué)生和教師,也可以禁用用戶操作。

  • 班級管理


    image.png
  • 學(xué)生申請加入班級


    image.png

因?yàn)殚_放了學(xué)生自己注冊的功能,所以學(xué)生和教師的關(guān)聯(lián),可以學(xué)生自己通過系統(tǒng)進(jìn)行申請,當(dāng)然如果實(shí)際情況不是注冊的方式的話,管理員可以自己進(jìn)行關(guān)聯(lián)的,該功能可以靈活去掉。

  • 在線考試管理
    考試的一個過程需要在這申明一下:

1.首先先要有一個題庫 ;
2.然后管理員或者老師往這個題庫里面添加題目
3.創(chuàng)建一份試卷,試卷的產(chǎn)生是通過自己定義題目的個數(shù),然后選擇上面創(chuàng)建的題庫,進(jìn)行自動組卷。
4.創(chuàng)建一次考試,需要選擇一份試卷,然后選中考試的班級,然后還有開始的時間。
5.到達(dá)開始時間后,學(xué)生登錄系統(tǒng)中進(jìn)行考試。
6.考試結(jié)束后,提交試卷,為防止考試學(xué)生提前交卷,然后告訴答案給考場上面正在考試的同學(xué),所以考試結(jié)束后才開始系統(tǒng)自動觸發(fā)批改系統(tǒng)job.
7.學(xué)生查看開始分?jǐn)?shù)情況。

  • 題庫管理


    image.png
  • 題目管理


    image.png

新增加題目:


image.png
  • 試卷管理


    image.png

新增加一份試卷,進(jìn)行自動組卷:


image.png
  • 考試管理


    image.png

新增加一次考試:


image.png
  • 學(xué)生成績管理


    image.png

還可以導(dǎo)出考試考的成績列表:


image.png

超級管理員角色

具有所有的上面管理員選線,額外再增加以下的功能:

  • 管理所有管理員


    image.png
  • 系統(tǒng)公告管理


    image.png
  • 系統(tǒng)操作日志管理


    image.png

教師身份角色

  • 首頁


    image.png
  • 教師管理端首頁


    image.png

顯示最近考試的情況統(tǒng)計(jì)。

  • 學(xué)生列表


    image.png

相對于管理員來講,就只能看到學(xué)生用戶,而看不到管理員用戶角色的,并且不可以增加學(xué)生用戶。

  • 班級管理


    image.png

下面具體的教師的權(quán)限信息,可以參照右邊的導(dǎo)航欄和上面管理員的角色看到教師角色所具有的權(quán)限,這里就沒必要一一列舉了:


image.png

學(xué)生角色

  • 學(xué)生首頁


    image.png
  • 關(guān)聯(lián)教師
    因?yàn)閷W(xué)生有可能是自己注冊進(jìn)來的,所以需要自己提交申請加入老師的班級:


    image.png
  • 考試列表


    image.png

這里展示的是自己關(guān)聯(lián)的老師下的班級下已經(jīng)創(chuàng)建的考試列表。

  • 參加考試


    image.png
  • 考試頁面


    image.png
  • 我的成績


    image.png
  • 我的錯題


    image.png
  • 成績分析


  • 個人信息


    image.png
  • 我的班級


    image.png
  • 修改登錄密碼


    image.png

以上是大部分功能展示,所有的功能都是正常運(yùn)行,沒有bug,導(dǎo)入到idea或者eclipse即可運(yùn)行,沒有套路,具體細(xì)節(jié)大家下載后自己慢慢研究,歡迎大家一起交流學(xué)習(xí)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容