在线考试系统之模块分析

工具:Node.js + MongoDB + Socket.IO

完成进度
教师端:
  1. 学生的添加删除等操作
  2. 考题和分数的添加删除编辑修改等操作
  3. 在查看考试情况页面显示所有考生的姓名和学号,以及状态信息(红色:已登录;灰色:未登录;蓝色:考试中;绿色:已交卷)
  4. 点击已提交的考生对象,进入该考生的阅卷界面,显示该考生提交考卷和答案的信息,并且可以批阅该考卷的得分;
  5. 实时显示考生的状态
学生端:
  1. 考生登入系统后,若时间未到,显示倒计时,点击题号弹出警告框;若时间到,可进行答题
  2. 点击题号后进入答题状态,同时教师端会实时显示考生的状态;
  3. 点击其他题号答题自动保存;
  4. 考试时间到自动提交等;
预览
教师端实时显示考生状态
教师端界面
学生端界面

1. 数据结构定义

1.用户表

用户学号userId,姓名username, 密码password, 类型category(学生/老师), 状态status(初始,登录,答题,提交)

var userSchema = new Schema({
    userId: String,
    username: String,
    password: String,
    category: String,   //分类-学生
    status: String, //状态
    meta: {
        updateAt: {type:Date, default: Date.now()},
        createAt: {type:Date, default: Date.now()}
    }
});
2.答题表

题目内容content,分数score

var questionSchema = new Schema({
    content: String,
    score: Number,
    meta: {
        updateAt: {type:Date, default: Date.now()},
        createAt: {type:Date, default: Date.now()}
    }
});
3. 学生答题内容表

学生IDuserId,问题IDquestionId,回答内容answerCtn, 批阅后得到的分数score

var answerSchema = new Schema({
    userId: {type: ObjectId, ref: 'User'},
    questionId: {type: ObjectId, ref: 'Question'},
    answerCtn: String,
    score: Number,
    meta: {
        updateAt: {type:Date, default: Date.now()},
        createAt: {type:Date, default: Date.now()}
    }
});

2. 教师端模块分解

2.1 学生管理
  • 学生列表:查看已添加的学生学号和姓名
  • 添加学生:添加新学生
2.2 题目管理
  • 查看题目列表:点击题号显示保存的题目内容和分数,点击文本框修改内容
  • 添加题目:添加新题目和分数
2.3 考试情况
  • 学生考试状态:
  1. 实时查看学生的各种状态信息(红色:已登录;灰色:未登录;蓝色:考试中;绿色:已交卷)
  2. 可点击已交卷的学生块,进行对该学生的阅卷操作
  • 学生考试成绩:查看学生的考试成绩信息

3. 学生端模块分解

3.1 倒计时模块
倒计时模块
  • 未到达开考时间显示 “距离考试开始” 的倒计时;
  • 到达开考时间显示 “距离考试结束” 的倒计时,直到考试结束倒计时停止;
3.2 答题模块
  • 若考试时间未到点击题号,弹出警告框(考试时间未到);
  • 考试时间到学生点击题号进入答题状态,教师端更新学生状态;
  • 考试未结束考生点击提交或者考试时间到,考生转换成提交状态,教师端更新学生状态,提交状态的考生无法继续答题;

4. 模块代码分析

4.1 登录检测

用户类型分为考生和教师,在登录时检测用户的类型,如果是教师则登入教师端页面,如果是考生则进入考生页面。

// result为登录成功返回的用户信息
if (result.data.category === "TEACHER") {
    location.href = "/p/index";
} else {
    location.href = "/p/indexStudent";
}
4.2 添加学生(添加题目方法类似)
添加学生页面元素
//postData()为之后的post提供函数
function postData(url, data, cb) {
    var promise = $.ajax({
        type: "post",
        url: url,
        dataType: "json",
        contentType: "application/json",
        data:data
    });
    promise.done(cb);
}

//传递JSON
function doAddStudent() {
    var jsonData = JSON.stringify({
        'usrId': usrId,
        'pwd': pwd,
        'username': username
    });
    postData(urlAddStudent, jsonData, cbAddStudent);
}

//返回结果
function cbAddStudent(result) {
    if (result.code == 99) {
        alert(result.msg);
    } else {
        alert("添加成功!");
        location.href = '/p/index';
    }
}
4.3 阅卷(查看考题信息和学生答题模块方法类似)

进入页面通过POST从数据库获得题目列表,渲染出题号列表,每个题号给予一个data-id

post获取题目列表,通过$.format(QUESTION_LIST, list[i]._id, i+1);渲染每一个题号,添加到(".item-number"里;
QUESTION_LIST模板
var QUESTION_LIST = "<div class='question-item' data-toggle='select' data-id='{0}'>{1}</div>";

//获取题目列表
function getQuestionList() {
    var jsonData = JSON.stringify({});
    postData(urlGetQuestionList, jsonData, cbQuestionList);
}
function cbQuestionList(result) {
    var list = result.results;
    for(var i = 0; i < list.length; i++) {
        var html = $.format(QUESTION_LIST, list[i]._id, i+1);
        $(".item-number").append(html);
    }
}

点击题号获得data-id
$("body").on("click", "[data-toggle='select']", showContent);

//显示题目内容和学生答题内容
function showContent(e) {
    $(".answer-wrap").removeClass("hide");
    e.preventDefault();
    var $this = $(this);
    questionId = $this.data('id');
    $("#question-head").text("第" + $(this).text() + "题");
    getQuestionCtn();
    getAnswerOne();
    if(questionId != 0) {
        saveMark();
    }
}

post获取题目内容,返回结果放到指定div内
$("#questionContent").text(result.content);

//获取题目内容
function getQuestionCtn() {
    var jsonData = JSON.stringify({
        "_id": questionId
    });
    postData(urlGetQuestionCtn, jsonData, cbShowQuestionCtn);
}
function cbShowQuestionCtn(result) {
    $("#que-score").text("分值:" + result.score);
    $("#questionContent").text(result.content);
}

post获取学生答题内容,返回的结果放到指定div内
$("#answerCtn").text(result.answerCtn);

//获得学生答案
function getAnswerOne() {
    var jsonData = JSON.stringify({
        "userId": studentId,
        "questionId": questionId
    });
    postData(urlGetAnswerOne, jsonData, cbShowAnswer);
}
function cbShowAnswer(result) {
    if(result != "99") {
        $("#give-score").val(result.score);
        $("#answerCtn").text(result.answerCtn);
    } else {
        $("#answerCtn").text("该学生没有完成该题目");
    }
}
4.4 考生端倒计时
  1. 将教师设定的开考时间和结束时间分别与当前时间比较,得到相差的时间差毫秒seconds。对seconds进行处理得到格式化的字符串表示时间。
  1. 若当前时间小于开考时间,显示距离考试开始倒计时,到时间seconds <= 0,进入答题倒计时,显示距离考试结束倒计时,直到seconds <= 0,停止倒计时并自动提交考卷,考生转换成提交状态SUBMIT
function getTimeDifference(y, n, M, h, m) {
    var now = new Date();
    var startTime =  new Date(y, n, M, h, m);
    var timeDifference = startTime.getTime() - now.getTime();
    var second = parseInt(timeDifference / 1000);
    var time = {
        remain: second,
        second: (second < 60) ? second : second % 60,
        hour: parseInt(second / 3600),
        minute: parseInt((second - parseInt(second / 3600) * 3600) / 60)
    };
    return time;
}
//考试开始时间
function timeBefore() {
    var timer = setInterval(function() {
        var time = getTimeDifference(2016, 10, 24, 18, 56);
        $('#time-title').text("距离考试开始");
        $('#time-ctn').text(time.hour + " : " + time.minute + " : " + time.second);
        if(time.remain <= 0) {
            status = START;
            showExamTime();
            clearInterval(timer);
        }
    }, 1000);
}
//考试结束倒计时
function showExamTime() {
    var timer = setInterval(function() {
        var time = getTimeDifference(2016, 10, 24, 23, 59);
        $('#time-title').text("距离考试结束");
        $('#time-ctn').text(time.hour + " : " + time.minute + " : " + time.second);
        if(time.remain <= 0) {
            status = END;
            doUpdate(SUBMIT);
            clearInterval(timer);
        }
    }, 1000);
}

5. 将考生端的考生状态实时更新到教师端

  1. 考生登录系统发送带有用户IDuserId和用户类型categorylogin 消息给服务器,服务器保存该用户(user[userId] = socket),接着判断该用户是否为教师,若是则保存teacherId;最后在数据库中将该用户状态更新为登录LOGIN状态,向教师端发送reload消息,教师端接收到后重新post获取学生状态;
  1. 开考时间到,考生处于可考试状态WAIT,考生点击题号转换成答题状态EXAM,post到数据库更新状态EXAM,同时向服务器发送状态转换消息update status,服务器接收到后向教师端发送reload消息`;
  2. 考生点击提交按钮,数据库更新状态SUBMIT,同时向服务器发送状态转换消息update status,服务器接收到后向教师端发送reload消息`;
考生端
//socket初始化
function socketInit() {
    var data = {
        userId: userId,
        userCategory: userCategory
    };
    socket.emit("login", data);
    status = LOGIN;
}

function showQuestion(e) {
    //如果为开考状态且用户不处于提交状态
    if(status == START && userStatus != SUBMIT) {
        getQuestionCtn();  //获取题目内容
        getAnswerOne();   //获取保存的答题内容
        doUpdate(EXAM);  //转换为答题状态
    } else if(userStatus == SUBMIT) {
        alert("你已提交答卷,请等候老师批阅。");
    } else if(status != START) {
        alert("考试时间未到!");
    }
}

//获取题目列表
function getQuestionCtn() {}
function cbShowQuestionCtn(result) {}

//保存答题内容
function doSaveAnswer() {}

//获取答题保存的内容
function getAnswerOne() {}
function cbShowAnswer(result) {}

//数据库更新用户状态SUBMIT,向教师端发送reload消息
function doUpdate(status) {
    socket.emit("update status");
}

//转换成SUBMIT状态
function cbUpdateStatus(result) {
     userStatus = SUBMIT;
}
服务器
io.on('connection', function(socket){
  //用户登录
  socket.on('login', function (data) {
    socket.name = data.userId;
    user[data.userId] = socket;
    var data2 = {
      userId: socket.name,
      status: "LOGIN"
    };
    dbHelper.updateStatus(data2, function (success, doc) {});
    //用户类型-老师
    if(data.userCategory === "TEACHER") {
      teacherId = data.userId;
    }
    //向老师的客户端发送重新加载命令
    if(teacherId !== 0) {
      user[teacherId].emit("reload");
    }
  });
  socket.on('update status', function () {
    if(teacherId !== 0) {
      user[teacherId].emit("reload");
    }
  });
  //用户退出
  socket.on('disconnect', function () {
    var data = {
      userId: socket.name,
      status: "INIT"
    };
    dbHelper.updateStatus(data, function (success, doc) {});
    if(socket.name === teacherId) {
      teacherId = 0;
    } else if(teacherId !== 0){
      user[teacherId].emit("reload");
    }
    delete user[socket.name];
  });
});

Github参考代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容