Node.js在线考试系统
1. 系统结构
系统考虑使用Nodejs和SocketIo实现服务器端逻辑,前端使用HTML5,数据库使用MongoDB
2.数据结构
用户表
var userSchema = new Schema({
userId:String, //用户ID
username: String, //用户名称
password: String, //账户密码,初始化为a
status:String, //用户状态,分为四个状态,在线landing,离线offline,提交完毕submited,考试中testing
identity:String, //用户身份,分别为student和teacher
});
科目表
var subjectsSchema = new Schema({
course:String, //考试科目名称
startTime:Date, //考试开始时间
lastTime:Number, //考试持续时间,单位为分
numOfQuestions:Number,//问题总数
questions:[{ //问题
index:Number, //当前问题序号
question:String,//问题内容
score:Number //问题分值
}]
});
答题表
var answersSchema = new Schema({
user:{type:Schema.Types.ObjectId, ref:'User' },//用户表引用
subject:{type:Schema.Types.ObjectId,ref:'Subjects'},//科目表引用
questions:[{
index:Number,//当前问题序号
content:String,//答案
score:Number //后期打分分值
}]
});
3.系统模块设计
3.1前端
3.1.1学生模块
- 绘制倒计时界面
* 提示学生还有多少时间开始考试
* 如果考试时间未到,学生点击开始考试会弹出"未到考试时间"提示框
* 开始考试,学生点击开始考试按钮,进入考试界面并且发送消息给服务器,服务器转发给教师端,更新教师端当前学生状态
- 绘制答题界面
- 绘制题目下标、题目标题、题目内容
- 绘制答题框
- 绑定上一题、提交答案、下一题三个按钮点击事件
-
存储数据
- 提交答案
- 发送学生ID、题目序号、题目答案到服务器,服务器存入数据库
3.1.2教师模块
- 绘制学生状态界面
* 将每一个学生的名字与学生ID作为一个单位,有序的排列在界面上
* 绑定点击学生信息单元,进入对应学生的打分界面
* 通过socket实时更新学生状态
- 绘制打分界面
- 主要调用学生模块的绘制答题界面函数
- 添加打分栏
-
更改提交答案按钮为打分按钮=
- 存储数据
3.1.3客户端socket模块
- 发送数据模块
* 向服务器提交学生考试答案(学生端)
* 向服务器提交教师所改学生答卷题目分数(教师端)
* 向服务器提交开始考试信息(学生端)
* 向服务器提交结束考试信息(学生端)
- 接受数据模块
- 接受来自服务器发送的确认登录消息(学生端、教师端)
- 接受来自服务器发送的状态改变消息(教师端)
3.2后台
3.2.1数据库操作模块
- 向登录的学生发送数据(考试开始时间,考试内容)
- 向登录的教师发送数据(学生列表,学生考试答案)
- 更改用户状态(在线、进行考试、结束考试、离线)
- 存储学生考卷答案
- 存储学生考卷题目得分
3.2.2服务器socket模块
- 接收来自客户端所提交的学生考试答案,并调用数据库模块存储学生考卷答案
- 接收来自客户端提交的教师所改学生答卷题目分数,并调用数据库模块存储学生考卷得分
。。。。。。
服务器socket模块实现的功能与客户端socket模块相对应
4.功能分析
学生模块与教师模块中的界面绘制之前已有文章提及,这里略去
学生模块的本地数据存储功能
学生登录->
后台路由发送数据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
});
})
});
因为服务器应用express
模块,data
数据用于渲染前端页面,但无法被js
文件获取到,所以需要经过处理,data
数据被转换为json
格式,并存入window.scriptData
->
<script>
window.scriptData = JSON.stringify({{{scriptData}}});
window.scriptData=eval("("+window.scriptData+")");//转换为json对象
</script>
学生成功登录后,进入准备开始考试界面,运行modalBox.init()
函数,并将modalBox
对象并赋值给STUDENT
,default
存储本地数据,window.scriptData.subject.questions
即为服务器所发考试相关数据中的题目对象->
var STUDENT;
new modal_student();
var modal_student = function () {
var modalBox = {
default:{
questions:[],//题目对象
answers:[],//学生所写答案
index:0//当前题目编号,
},
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();
}
今后学生切换题目时只需要从本地获取数据,而不需要多次与服务器进行请求
教师模块的本地数据存储功能
教师登录->
后台发送数据data
->
router.get('/teacher', function(req, res, next) {
dbHelper.findStudentsInfo(req, function (success, data) {
//console.log(data.friends);
//data.students学生信息,data.subject课程信息
res.render('teacher', {
students:data.students,
scriptData:JSON.stringify(data)
});
})
});
与上同->
<script>
window.scriptData = JSON.stringify({{{scriptData}}});
window.scriptData=eval("("+window.scriptData+")");//转换为json对象
</script>
教师成功登录后,进入浏览学生名单界面,运行modalBox.init()
函数,并将modalBox
对象并赋值给TEACHER
->
var TEACHER;
new modal_teacher();
var modal_teacher = function () {
var ptrThis;
var modalBox = {
default:{
studentIndex:0,//当前正在进行批改的学生的下标
studentsList:[]
},
init:function () {
ptrThis = this;
ptrThis.default.studentsList = window.scriptData.students;
}
}
TEACHER = modalBox;
return modalBox.init();
}
studentsList
存储学生信息列表,包含学生姓名、学号、状态,以及学生每道题目的答案
studentsList
格式
var studentList = [{
user:{
username:String,//学生姓名
userId:String,//学生ID
status:String,//学生状态
},
questions:[{
content:String,//答案
index:Number//当前题目下标
}]
}]
学生交卷
modalBox.bindEnd()
绑定了结束考试button
的点击事件->
bindEnd:function () {
var end = document.getElementById("end");
end.addEventListener("click",function () {
ptrThis.saveAnswer();//将当前答案保存到本地文件
socketFun.end();//调用客户端socket模块的end()函数
});
},
点击事件触发,调用客户端socket模块的end()函数->
var socket = io.connect('http://localhost:3000');
var X = window.scriptData; //截取服务器发送过来的数据
var socketFun = {
//省略
end:function () {
var obj = {
userId:X.userId,
indentify:X.identify
}
obj ["answers"] = STUDENT.default.answers;
if(X.status=="submited"){
alert("你已经提交过答案!");
}else {
socket.emit("end", obj );
}
}
}
客户端socket模块将学生学号userId
、学生身份identify
、学生答案answers
发送到服务器->
socket.on("end",function (result) {
User.update({userId:result.userId},{status:"submited"}).exec(function (err,doc) {//更新数据库中的学生状态为submited(提交答案)
User.findOne({userId:result.userId}).exec(function (err,doc) {//根据学生ID查询该条学生记录
var change = {
studentId:result.userId,
status:"submited"
};
if(teacherOnline){//如果教师在线,则向其发送学生状态改变的消息
teacher.emit("statusChange",change);
}
Answers.findOne({user:doc._id}).exec(function (err,answer) {//答题表根据学生记录的_id查询到对应记录,更新答案
answer.questions = result.answers;
answer.save(function (err) {})
})
});
})
}),
教师模块通过socket实时更新学生状态
客户端socket模块接收到来自服务器的学生状态改变信息->
var socket = io.connect('http://localhost:3000');
socket.on("statusChange",function (change) {
TEACHER.changeStatus(change);
})
调用教师模块的changeStatus
函数,以change
为参数->
changeStatus:function (change) {
console.log(ptrThis.default.studentsList);
for(var i = 0;i<ptrThis.default.studentsList.length;++i){//遍历当前学生列表
console.log(ptrThis.default.studentsList[i].user.userId+"与"+change.studentId);
if(ptrThis.default.studentsList[i].user.userId==change.studentId){//重新设置status
ptrThis.default.studentsList[i].user.status = change.status;
var studentInfo = document.getElementsByClassName("studentInfo");
studentInfo[i].setAttribute("class","studentInfo "+change.status);//修改界面上学生列表中对应的学生的状态(改变颜色以提示教师有学生状态改变)
break;
}
}
},
5.具体代码见
github下的onlineTest
项目