在線考試系統(tǒng)之模塊分析

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

完成進(jìn)度
教師端:
  1. 學(xué)生的添加刪除等操作
  2. 考題和分?jǐn)?shù)的添加刪除編輯修改等操作
  3. 在查看考試情況頁面顯示所有考生的姓名和學(xué)號痹束,以及狀態(tài)信息(紅色:已登錄椿争;灰色:未登錄;藍(lán)色:考試中最域;綠色:已交卷)
  4. 點擊已提交的考生對象逗扒,進(jìn)入該考生的閱卷界面咆贬,顯示該考生提交考卷和答案的信息肄满,并且可以批閱該考卷的得分;
  5. 實時顯示考生的狀態(tài)
學(xué)生端:
  1. 考生登入系統(tǒng)后智政,若時間未到,顯示倒計時箱蝠,點擊題號彈出警告框续捂;若時間到,可進(jìn)行答題
  2. 點擊題號后進(jìn)入答題狀態(tài)宦搬,同時教師端會實時顯示考生的狀態(tài)牙瓢;
  3. 點擊其他題號答題自動保存;
  4. 考試時間到自動提交等间校;
預(yù)覽
教師端實時顯示考生狀態(tài)
教師端界面
學(xué)生端界面

1. 數(shù)據(jù)結(jié)構(gòu)定義

1.用戶表

用戶學(xué)號userId矾克,姓名username, 密碼password憔足, 類型category(學(xué)生/老師)胁附, 狀態(tài)status(初始,登錄滓彰,答題控妻,提交)

var userSchema = new Schema({
    userId: String,
    username: String,
    password: String,
    category: String,   //分類-學(xué)生
    status: String, //狀態(tài)
    meta: {
        updateAt: {type:Date, default: Date.now()},
        createAt: {type:Date, default: Date.now()}
    }
});
2.答題表

題目內(nèi)容content,分?jǐn)?shù)score

var questionSchema = new Schema({
    content: String,
    score: Number,
    meta: {
        updateAt: {type:Date, default: Date.now()},
        createAt: {type:Date, default: Date.now()}
    }
});
3. 學(xué)生答題內(nèi)容表

學(xué)生IDuserId揭绑,問題IDquestionId弓候,回答內(nèi)容answerCtn, 批閱后得到的分?jǐn)?shù)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 學(xué)生管理
  • 學(xué)生列表:查看已添加的學(xué)生學(xué)號和姓名
  • 添加學(xué)生:添加新學(xué)生
2.2 題目管理
  • 查看題目列表:點擊題號顯示保存的題目內(nèi)容和分?jǐn)?shù)他匪,點擊文本框修改內(nèi)容
  • 添加題目:添加新題目和分?jǐn)?shù)
2.3 考試情況
  • 學(xué)生考試狀態(tài):
  1. 實時查看學(xué)生的各種狀態(tài)信息(紅色:已登錄菇存;灰色:未登錄;藍(lán)色:考試中邦蜜;綠色:已交卷)
  2. 可點擊已交卷的學(xué)生塊依鸥,進(jìn)行對該學(xué)生的閱卷操作
  • 學(xué)生考試成績:查看學(xué)生的考試成績信息

3. 學(xué)生端模塊分解

3.1 倒計時模塊
倒計時模塊
  • 未到達(dá)開考時間顯示 “距離考試開始” 的倒計時;
  • 到達(dá)開考時間顯示 “距離考試結(jié)束” 的倒計時畦徘,直到考試結(jié)束倒計時停止毕籽;
3.2 答題模塊
  • 若考試時間未到點擊題號抬闯,彈出警告框(考試時間未到);
  • 考試時間到學(xué)生點擊題號進(jìn)入答題狀態(tài)关筒,教師端更新學(xué)生狀態(tài)溶握;
  • 考試未結(jié)束考生點擊提交或者考試時間到,考生轉(zhuǎn)換成提交狀態(tài)蒸播,教師端更新學(xué)生狀態(tài)睡榆,提交狀態(tài)的考生無法繼續(xù)答題;

4. 模塊代碼分析

4.1 登錄檢測

用戶類型分為考生和教師袍榆,在登錄時檢測用戶的類型胀屿,如果是教師則登入教師端頁面,如果是考生則進(jìn)入考生頁面包雀。

// result為登錄成功返回的用戶信息
if (result.data.category === "TEACHER") {
    location.href = "/p/index";
} else {
    location.href = "/p/indexStudent";
}
4.2 添加學(xué)生(添加題目方法類似)
添加學(xué)生頁面元素
//postData()為之后的post提供函數(shù)
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);
}

//返回結(jié)果
function cbAddStudent(result) {
    if (result.code == 99) {
        alert(result.msg);
    } else {
        alert("添加成功宿崭!");
        location.href = '/p/index';
    }
}
4.3 閱卷(查看考題信息和學(xué)生答題模塊方法類似)

進(jìn)入頁面通過POST從數(shù)據(jù)庫獲得題目列表,渲染出題號列表才写,每個題號給予一個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);

//顯示題目內(nèi)容和學(xué)生答題內(nèi)容
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獲取題目內(nèi)容,返回結(jié)果放到指定div內(nèi)
$("#questionContent").text(result.content);

//獲取題目內(nèi)容
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獲取學(xué)生答題內(nèi)容讹堤,返回的結(jié)果放到指定div內(nèi)
$("#answerCtn").text(result.answerCtn);

//獲得學(xué)生答案
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("該學(xué)生沒有完成該題目");
    }
}
4.4 考生端倒計時
  1. 將教師設(shè)定的開考時間和結(jié)束時間分別與當(dāng)前時間比較,得到相差的時間差毫秒seconds厨疙。對seconds進(jìn)行處理得到格式化的字符串表示時間洲守。
  1. 若當(dāng)前時間小于開考時間,顯示距離考試開始倒計時沾凄,到時間seconds <= 0梗醇,進(jìn)入答題倒計時,顯示距離考試結(jié)束倒計時撒蟀,直到seconds <= 0婴削,停止倒計時并自動提交考卷,考生轉(zhuǎn)換成提交狀態(tài)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);
}
//考試結(jié)束倒計時
function showExamTime() {
    var timer = setInterval(function() {
        var time = getTimeDifference(2016, 10, 24, 23, 59);
        $('#time-title').text("距離考試結(jié)束");
        $('#time-ctn').text(time.hour + " : " + time.minute + " : " + time.second);
        if(time.remain <= 0) {
            status = END;
            doUpdate(SUBMIT);
            clearInterval(timer);
        }
    }, 1000);
}

5. 將考生端的考生狀態(tài)實時更新到教師端

  1. 考生登錄系統(tǒng)發(fā)送帶有用戶IDuserId和用戶類型categorylogin 消息給服務(wù)器唉俗,服務(wù)器保存該用戶(user[userId] = socket),接著判斷該用戶是否為教師配椭,若是則保存teacherId虫溜;最后在數(shù)據(jù)庫中將該用戶狀態(tài)更新為登錄LOGIN狀態(tài),向教師端發(fā)送reload消息股缸,教師端接收到后重新post獲取學(xué)生狀態(tài);
  1. 開考時間到衡楞,考生處于可考試狀態(tài)WAIT,考生點擊題號轉(zhuǎn)換成答題狀態(tài)EXAM敦姻,post到數(shù)據(jù)庫更新狀態(tài)EXAM瘾境,同時向服務(wù)器發(fā)送狀態(tài)轉(zhuǎn)換消息update status歧杏,服務(wù)器接收到后向教師端發(fā)送reload消息`;
  2. 考生點擊提交按鈕,數(shù)據(jù)庫更新狀態(tài)SUBMIT迷守,同時向服務(wù)器發(fā)送狀態(tài)轉(zhuǎn)換消息update status犬绒,服務(wù)器接收到后向教師端發(fā)送reload消息`;
考生端
//socket初始化
function socketInit() {
    var data = {
        userId: userId,
        userCategory: userCategory
    };
    socket.emit("login", data);
    status = LOGIN;
}

function showQuestion(e) {
    //如果為開考狀態(tài)且用戶不處于提交狀態(tài)
    if(status == START && userStatus != SUBMIT) {
        getQuestionCtn();  //獲取題目內(nèi)容
        getAnswerOne();   //獲取保存的答題內(nèi)容
        doUpdate(EXAM);  //轉(zhuǎn)換為答題狀態(tài)
    } else if(userStatus == SUBMIT) {
        alert("你已提交答卷,請等候老師批閱兑凿。");
    } else if(status != START) {
        alert("考試時間未到凯力!");
    }
}

//獲取題目列表
function getQuestionCtn() {}
function cbShowQuestionCtn(result) {}

//保存答題內(nèi)容
function doSaveAnswer() {}

//獲取答題保存的內(nèi)容
function getAnswerOne() {}
function cbShowAnswer(result) {}

//數(shù)據(jù)庫更新用戶狀態(tài)SUBMIT,向教師端發(fā)送reload消息
function doUpdate(status) {
    socket.emit("update status");
}

//轉(zhuǎn)換成SUBMIT狀態(tài)
function cbUpdateStatus(result) {
     userStatus = SUBMIT;
}
服務(wù)器
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;
    }
    //向老師的客戶端發(fā)送重新加載命令
    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參考代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末礼华,一起剝皮案震驚了整個濱河市咐鹤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌圣絮,老刑警劉巖祈惶,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扮匠,居然都是意外死亡行瑞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門餐禁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人突照,你說我怎么就攤上這事帮非。” “怎么了讹蘑?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵末盔,是天一觀的道長。 經(jīng)常有香客問我座慰,道長陨舱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任版仔,我火速辦了婚禮游盲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛮粮。我一直安慰自己益缎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布然想。 她就那樣靜靜地躺著莺奔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪变泄。 梳的紋絲不亂的頭發(fā)上令哟,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天恼琼,我揣著相機(jī)與錄音,去河邊找鬼屏富。 笑死晴竞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的役听。 我是一名探鬼主播颓鲜,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼典予!你這毒婦竟也來了甜滨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘤袖,失蹤者是張志新(化名)和其女友劉穎衣摩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捂敌,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡艾扮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了占婉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泡嘴。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逆济,靈堂內(nèi)的尸體忽然破棺而出酌予,到底是詐尸還是另有隱情,我是刑警寧澤奖慌,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布抛虫,位于F島的核電站,受9級特大地震影響简僧,放射性物質(zhì)發(fā)生泄漏建椰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一岛马、第九天 我趴在偏房一處隱蔽的房頂上張望棉姐。 院中可真熱鬧,春花似錦啦逆、人聲如沸谅海。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扭吁。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侥袜,已是汗流浹背蝌诡。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留枫吧,地道東北人浦旱。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像九杂,于是被迫代替她去往敵國和親颁湖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容