Node.js在線考試系統(tǒng)——服務(wù)器與客戶端的交互

Node.js在線考試系統(tǒng)

1. 系統(tǒng)結(jié)構(gòu)

系統(tǒng)考慮使用Nodejs和SocketIo實(shí)現(xiàn)服務(wù)器端邏輯,前端使用HTML5,數(shù)據(jù)庫使用MongoDB

2.數(shù)據(jù)結(jié)構(gòu)

用戶表

var userSchema = new Schema({
    userId:String,          //用戶ID
    username: String,       //用戶名稱
    password: String,       //賬戶密碼,初始化為a
    status:String,          //用戶狀態(tài),分為四個(gè)狀態(tài),在線landing,離線offline,提交完畢submited,考試中testing
    identity:String,        //用戶身份,分別為student和teacher
});

科目表

var subjectsSchema = new Schema({
    course:String,      //考試科目名稱
    startTime:Date,     //考試開始時(shí)間
    lastTime:Number,    //考試持續(xù)時(shí)間,單位為分
    numOfQuestions:Number,//問題總數(shù)
    questions:[{        //問題
        index:Number,   //當(dāng)前問題序號(hào)
        question:String,//問題內(nèi)容
        score:Number    //問題分值
    }]
});

答題表

var answersSchema = new Schema({
    user:{type:Schema.Types.ObjectId, ref:'User' },//用戶表引用
    subject:{type:Schema.Types.ObjectId,ref:'Subjects'},//科目表引用
    questions:[{
        index:Number,//當(dāng)前問題序號(hào)
        content:String,//答案
        score:Number //后期打分分值
    }]
});

3.系統(tǒng)模塊設(shè)計(jì)

3.1前端
3.1.1學(xué)生模塊

  • 繪制倒計(jì)時(shí)界面
* 提示學(xué)生還有多少時(shí)間開始考試
* 如果考試時(shí)間未到,學(xué)生點(diǎn)擊開始考試會(huì)彈出"未到考試時(shí)間"提示框
* 開始考試,學(xué)生點(diǎn)擊開始考試按鈕,進(jìn)入考試界面并且發(fā)送消息給服務(wù)器,服務(wù)器轉(zhuǎn)發(fā)給教師端,更新教師端當(dāng)前學(xué)生狀態(tài)
準(zhǔn)備界面.jpg
  • 繪制答題界面
    • 繪制題目下標(biāo)、題目標(biāo)題、題目內(nèi)容
    • 繪制答題框
    • 綁定上一題、提交答案、下一題三個(gè)按鈕點(diǎn)擊事件
  • 存儲(chǔ)數(shù)據(jù)


    答題界面.jpg
  • 提交答案
    • 發(fā)送學(xué)生ID、題目序號(hào)、題目答案到服務(wù)器,服務(wù)器存入數(shù)據(jù)庫

3.1.2教師模塊

  • 繪制學(xué)生狀態(tài)界面
* 將每一個(gè)學(xué)生的名字與學(xué)生ID作為一個(gè)單位,有序的排列在界面上
* 綁定點(diǎn)擊學(xué)生信息單元,進(jìn)入對(duì)應(yīng)學(xué)生的打分界面
* 通過socket實(shí)時(shí)更新學(xué)生狀態(tài)
學(xué)生狀態(tài)界面
  • 繪制打分界面
    • 主要調(diào)用學(xué)生模塊的繪制答題界面函數(shù)
    • 添加打分欄
    • 更改提交答案按鈕為打分按鈕=


      改成績界面
  • 存儲(chǔ)數(shù)據(jù)
    3.1.3客戶端socket模塊
  • 發(fā)送數(shù)據(jù)模塊
* 向服務(wù)器提交學(xué)生考試答案(學(xué)生端)
* 向服務(wù)器提交教師所改學(xué)生答卷題目分?jǐn)?shù)(教師端)
* 向服務(wù)器提交開始考試信息(學(xué)生端)
* 向服務(wù)器提交結(jié)束考試信息(學(xué)生端)
  • 接受數(shù)據(jù)模塊
    • 接受來自服務(wù)器發(fā)送的確認(rèn)登錄消息(學(xué)生端、教師端)
    • 接受來自服務(wù)器發(fā)送的狀態(tài)改變消息(教師端)

3.2后臺(tái)
3.2.1數(shù)據(jù)庫操作模塊

  • 向登錄的學(xué)生發(fā)送數(shù)據(jù)(考試開始時(shí)間,考試內(nèi)容)
  • 向登錄的教師發(fā)送數(shù)據(jù)(學(xué)生列表,學(xué)生考試答案)
  • 更改用戶狀態(tài)(在線、進(jìn)行考試、結(jié)束考試、離線)
  • 存儲(chǔ)學(xué)生考卷答案
  • 存儲(chǔ)學(xué)生考卷題目得分

3.2.2服務(wù)器socket模塊

  • 接收來自客戶端所提交的學(xué)生考試答案,并調(diào)用數(shù)據(jù)庫模塊存儲(chǔ)學(xué)生考卷答案
  • 接收來自客戶端提交的教師所改學(xué)生答卷題目分?jǐn)?shù),并調(diào)用數(shù)據(jù)庫模塊存儲(chǔ)學(xué)生考卷得分
    。。。。。。
    服務(wù)器socket模塊實(shí)現(xiàn)的功能與客戶端socket模塊相對(duì)應(yīng)

4.功能分析

學(xué)生模塊與教師模塊中的界面繪制之前已有文章提及,這里略去
學(xué)生模塊的本地?cái)?shù)據(jù)存儲(chǔ)功能

學(xué)生登錄->
后臺(tái)路由發(fā)送數(shù)據(jù)data->

router.get('/student', function(req, res, next) {
  dbHelper.findUsrInfo(req, function (success, data) {
    //console.log(data.friends);
    //data.identify身份,data.subject考試題目,data.answer考試答案
    res.render('student', {
      scriptData:JSON.stringify(data),
      data:data
    });
  })
});

因?yàn)榉?wù)器應(yīng)用express模塊,data數(shù)據(jù)用于渲染前端頁面,但無法被js文件獲取到,所以需要經(jīng)過處理,data數(shù)據(jù)被轉(zhuǎn)換為json格式,并存入window.scriptData->

<script>
    window.scriptData = JSON.stringify({{{scriptData}}});
    window.scriptData=eval("("+window.scriptData+")");//轉(zhuǎn)換為json對(duì)象
</script>

學(xué)生成功登錄后,進(jìn)入準(zhǔn)備開始考試界面,運(yùn)行modalBox.init()函數(shù),并將modalBox對(duì)象并賦值給STUDENT,default存儲(chǔ)本地?cái)?shù)據(jù),window.scriptData.subject.questions即為服務(wù)器所發(fā)考試相關(guān)數(shù)據(jù)中的題目對(duì)象->

var STUDENT;
new modal_student();
var modal_student = function () {
     var modalBox = {
        default:{
              questions:[],//題目對(duì)象
              answers:[],//學(xué)生所寫答案
              index:0//當(dāng)前題目編號(hào),
        },
        init:function () {
            ptrThis = this;
            ptrThis.setQuestions();
        },
        setQuestions:function () {
            ptrThis.default.questions = window.scriptData.subject.questions;
            console.log(ptrThis.default.questions);
        },
     }
    STUDENT = modalBox;
    return modalBox.init();
}

今后學(xué)生切換題目時(shí)只需要從本地獲取數(shù)據(jù),而不需要多次與服務(wù)器進(jìn)行請(qǐng)求

教師模塊的本地?cái)?shù)據(jù)存儲(chǔ)功能

教師登錄->
后臺(tái)發(fā)送數(shù)據(jù)data->

router.get('/teacher', function(req, res, next) {
  dbHelper.findStudentsInfo(req, function (success, data) {
    //console.log(data.friends);
    //data.students學(xué)生信息,data.subject課程信息
    res.render('teacher', {
      students:data.students,
      scriptData:JSON.stringify(data)
    });
  })
});

與上同->

<script>
    window.scriptData = JSON.stringify({{{scriptData}}});
    window.scriptData=eval("("+window.scriptData+")");//轉(zhuǎn)換為json對(duì)象
</script>

教師成功登錄后,進(jìn)入瀏覽學(xué)生名單界面,運(yùn)行modalBox.init()函數(shù),并將modalBox對(duì)象并賦值給TEACHER->

var TEACHER;
new modal_teacher();
var modal_teacher = function () {
    var ptrThis;
     var modalBox = {
         default:{
             studentIndex:0,//當(dāng)前正在進(jìn)行批改的學(xué)生的下標(biāo)
             studentsList:[]
         },
        init:function () {
            ptrThis = this;
            ptrThis.default.studentsList = window.scriptData.students;
        }
    }
    TEACHER = modalBox;
    return modalBox.init();
}

studentsList存儲(chǔ)學(xué)生信息列表,包含學(xué)生姓名、學(xué)號(hào)、狀態(tài),以及學(xué)生每道題目的答案
studentsList格式

var studentList = [{
      user:{
          username:String,//學(xué)生姓名
          userId:String,//學(xué)生ID
          status:String,//學(xué)生狀態(tài)
      },
      questions:[{
          content:String,//答案
          index:Number//當(dāng)前題目下標(biāo)
      }]
}]

學(xué)生交卷

modalBox.bindEnd()綁定了結(jié)束考試button的點(diǎn)擊事件->

       bindEnd:function () {
            var end = document.getElementById("end");
            end.addEventListener("click",function () {
               ptrThis.saveAnswer();//將當(dāng)前答案保存到本地文件
               socketFun.end();//調(diào)用客戶端socket模塊的end()函數(shù)
            });
        },

點(diǎn)擊事件觸發(fā),調(diào)用客戶端socket模塊的end()函數(shù)->

var socket = io.connect('http://localhost:3000');
var X = window.scriptData;                          //截取服務(wù)器發(fā)送過來的數(shù)據(jù)
var socketFun = {
    //省略
    end:function () {
        var obj = {
            userId:X.userId,
            indentify:X.identify
        }
        obj ["answers"] = STUDENT.default.answers;
        if(X.status=="submited"){
            alert("你已經(jīng)提交過答案!");
        }else {
            socket.emit("end", obj );
        }
    }
}

客戶端socket模塊將學(xué)生學(xué)號(hào)userId、學(xué)生身份identify、學(xué)生答案answers發(fā)送到服務(wù)器->

        socket.on("end",function (result) {
            User.update({userId:result.userId},{status:"submited"}).exec(function (err,doc) {//更新數(shù)據(jù)庫中的學(xué)生狀態(tài)為submited(提交答案)
                User.findOne({userId:result.userId}).exec(function (err,doc) {//根據(jù)學(xué)生ID查詢?cè)摋l學(xué)生記錄
                    var change = {
                        studentId:result.userId,
                        status:"submited"
                    };
                    if(teacherOnline){//如果教師在線,則向其發(fā)送學(xué)生狀態(tài)改變的消息
                        teacher.emit("statusChange",change);
                    }
                    Answers.findOne({user:doc._id}).exec(function (err,answer) {//答題表根據(jù)學(xué)生記錄的_id查詢到對(duì)應(yīng)記錄,更新答案
                        answer.questions = result.answers;
                        answer.save(function (err) {})
                    })
                });
            })
        }),

教師模塊通過socket實(shí)時(shí)更新學(xué)生狀態(tài)

客戶端socket模塊接收到來自服務(wù)器的學(xué)生狀態(tài)改變信息->

var socket = io.connect('http://localhost:3000');
socket.on("statusChange",function (change) {
    TEACHER.changeStatus(change);
})

調(diào)用教師模塊的changeStatus函數(shù),以change為參數(shù)->

         changeStatus:function (change) {
             console.log(ptrThis.default.studentsList);
            for(var i = 0;i<ptrThis.default.studentsList.length;++i){//遍歷當(dāng)前學(xué)生列表
                console.log(ptrThis.default.studentsList[i].user.userId+"與"+change.studentId);
                if(ptrThis.default.studentsList[i].user.userId==change.studentId){//重新設(shè)置status
                    ptrThis.default.studentsList[i].user.status = change.status;
                    var studentInfo = document.getElementsByClassName("studentInfo");
                    studentInfo[i].setAttribute("class","studentInfo "+change.status);//修改界面上學(xué)生列表中對(duì)應(yīng)的學(xué)生的狀態(tài)(改變顏色以提示教師有學(xué)生狀態(tài)改變)
                    break;
                }
            }
         },

5.具體代碼見

github下的onlineTest項(xiàng)目

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

相關(guān)閱讀更多精彩內(nèi)容

  • 《ijs》速成開發(fā)手冊(cè)3.0 官方用戶交流:iApp開發(fā)交流(1) 239547050iApp開發(fā)交流(2) 10...
    葉染柒丶閱讀 5,638評(píng)論 0 7
  • 個(gè)人入門學(xué)習(xí)用筆記、不過多作為參考依據(jù)。如有錯(cuò)誤歡迎斧正 目錄 簡書好像不支持錨點(diǎn)、復(fù)制搜索(反正也是寫給我自己看...
    kirito_song閱讀 2,649評(píng)論 1 37
  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,118評(píng)論 2 17
  • 工具:Node.js + MongoDB + Socket.IO 完成進(jìn)度 教師端: 學(xué)生的添加刪除等操作 考題和...
    淡就加點(diǎn)鹽閱讀 2,836評(píng)論 8 28
  • 一個(gè)人對(duì)另一個(gè)人,一個(gè)人對(duì)一件東西或一件事情,總會(huì)有那么一些或多或少的感覺,這感覺或平淡、或興奮、或難過、或憂傷、...
    知櫻閱讀 359評(píng)論 0 1

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