在線考試系統之模塊分析

工具: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參考代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容