本章節(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
新增加一份試卷,進(jìn)行自動組卷:

-
考試管理
image.png
新增加一次考試:

-
學(xué)生成績管理
image.png
還可以導(dǎo)出考試考的成績列表:

超級管理員角色
具有所有的上面管理員選線,額外再增加以下的功能:
-
管理所有管理員
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)限,這里就沒必要一一列舉了:

學(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í)




























