最近正在完成一个电网的在线考试系统,这个项目的整个业务部分和功能模块部分已经确定。在这个项目中,主*分主要难点是四个部分,分为:设计数据库、考试随机出题、提交试卷评分、页面上题下题提交试卷的JS。
下面就模拟一个小的考试系统,给大家演示:
一、数据库部分
在设计数据库时,
1、先设计题型的表,在这部分每个题型都是一张独立的表,主要是单选表、多选表、判断表、类型表。在表字段的设计上,都是类似的。主要字段和一条虚拟数据如下
单选表:
id | content | a | b | c | d | answer | 所属专业id
radio01 | C语言重要不? | 重要 | 不重要 | 不了解 | 拒绝回答 | a | 1
多选表:
id | content | a | b | c | d | e | answer | 所属专业id
类似
判断表:
id | content | a | b | answer | 所属专业id
类似
专业类型表
id | content
1 | 配电
2、设计试卷参数表,用于设置不同专业出题的类型和数量,主要字段如下。
试卷参数表:
id | 所属专业id | 单选题数量 | 多选题数量 | 判断题数量 | 答题时间 |
1 | 1 | 20 | 20 | 10 | 30
二、考试随机出题部分
在项目中,业务要求每个考生登录考试,即使报考专业相同,得到的题目也是不同的,在这个模块,有两个思路。
1、在数据库试题表中存储一个自增的id作为主键,或者每次添加的时候获取最大的id数目,添加时原有id+1作为主键,不管怎么实现最后在表中要形成不间断的数字,然后创建一个得到不重复数字的随机数工具类,获取的数字作为id进行获取试题。这种方法较第二种方法,效率较高,但当对某些进行删除后,处理相对麻烦,因为不可以获取空的题。
2、第二种思路是在数据库试题表中存储的主键是具有自己填写规则的varchar类型的字符串,比如201701
,在抽取试题时,将所有的单选、多选、判断分别存到三个集合中,然后使用随机数工具类,要求不同题型多少道就获取多少不重复的随机数,然后分别在集合中获取试题。这种方式较笨重,但删除操作不会造成任何影响,适合题量较少、经常修改的考试系统、性能较低。
下面是获取不重复的随机数工具类:
这个是在网上找到的,随机数写的很好:http://www.cnblogs.com/happyday56/p/5163264.html
/**
* 随机指定范围内N个不重复的数
* 在初始化的无重复待选数组中随机产生一个数放入结果中,
* 将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换
* 然后从len-2里随机产生下一个随机数,如此类推
* @param max 指定范围最大值
* @param min 指定范围最小值
* @param n 随机数个数
* @return int[] 随机数结果集
*/
public class randomSet {
public static int[] randomArray(int min,int max,int n){
int len = max-min+1;
if(max < min || n > len){
return null;
}
//初始化给定范围的待选数组
int[] source = new int[len];
for (int i = min; i < min+len; i++){
source[i-min] = i;
}
int[] result = new int[n];
Random rd = new Random();
int index = 0;
for (int i = 0; i < result.length; i++) {
//待选数组0到(len-2)随机一个下标
index = Math.abs(rd.nextInt() % len--);
//将随机到的数放入结果集
result[i] = source[index];
//将待选数组中被随机到的数,用待选数组(len-1)下标对应的数替换
source[index] = source[len];
}
return result;
}
三、提交试卷评分部分
在这个部分,主要分为两个模块,一个是多选部分的checkBox、一个是单选和判断部分的radio
1、checkBox部分
当在登录完成后,后台随机出题时,将随机数保存到session中,将在多选部分是在遍历时候,checkBox的name设置为当前题的id,保证名称是唯一,提交后,在后台取出session中的随机数,然后如果试题id是自增的话,直接可以获取试题的所有信息,按id获取考生每道题的选择,在这里由于是CheckBox,所以有可能是多个值,先将值拼接为字符串,然后和试题的答案进行对比,相同的话得分。
2、radio部分
这部分和checkBox部分类似,最后不需要拼接字符串,就可以比对。
四、上下题、提交JS部分
在页面这个部分,分为三个部分进行遍历,在页面上有一个大的form表单,用于提交数据,单form表单中有三个foreach用于遍历,然后在foreach中有一个div,三个类型的题具有三个不同结构的id,同时使用varStatus属性,实现dan1、dan2、dan3这种效果。然后将所有div隐藏。在最上方的rnum、cnum、tnum分别代表不同类型的试题数目。在form的上方实现显示所有题目号,并通过下面的方式将链接中的class和遍历题的id设为一样的值,用于实现上下题和点击显示题目的效果。
具体代码如下
1、表单和页面设置
<input type="hidden" id="zhi1" value="${rnum}">
<input type="hidden" id="zhi2" value="${cnum}">
<input type="hidden" id="zhi3" value="${tnum}">
<table>
<tr><th>单选题部分</th></tr>
<tr>
<c:forEach begin="${begin}" end="${rend}" var="r">
<td><a onclick="getdanxuan(this)" class="dan${r}">【${r}】</a> </td>
</c:forEach>
</tr>
<tr><th>多选题部分</th></tr>
<br>
<tr>
<c:forEach begin="${begin}" end="${cend}" var="c">
<td><a onclick="getduoxuan(this)" class="duo${c}">【${c}】</a> </td>
</c:forEach>
</tr>
<tr><th>判断题部分</th></tr>
<tr>
<c:forEach begin="${begin}" end="${tend}" var="t">
<td><a onclick="getpanduan(this)" class="pan${t}">【${t}】</a> </td>
</c:forEach>
</tr>
</table>
<br>
<c:forEach items="${rflist}" var="rf" varStatus="state">
<div id="dan${state.count}" class="danxuan" style="display: none">
<tr>【单选题】${state.count}</tr>
</div>
</forEach>
<br>
<c:forEach items="${cflist}" var="cf" varStatus="state1">
<div id="duo${state1.count}" class="duoxuan" style="display: none">
<tr>【多选题】${state1.count}</tr>
</div>
</forEach>
<c:forEach items="${tflist}" var="tf" varStatus="state2">
<div id="pan${state2.count}" class="panduan" style="display: none;">
<tr>【判断题】${state2.count}</tr>
</div>
</forEach>
<input type="button" value="上一题" onclick="shang()">
<input type="button" value="下一题" onclick="xia()">
2、JS部分
在这部分使用JS实现上一题、下一题、和点击题目号显示题目的效果。
(1)在页面显示初始化部分设置单选第一道显示,先获取三个类型的数目,然后获取div的数目,由于只有题目具有div,所以可以通过这种方式进行设置;
(2)在点击题目显示部分,有三个函数,分别对应三类题型,道理相同,首先设置所有div是隐藏的,然后获取目标的class名称,在通过class名称获取指定div,将其设置为block。
(3)点击上下题,显示指定题目,道理是类似的,都是先获取所有div元素,然后判断哪个是显示的,记录下来,将其隐藏,然后将上一个或者下一个进行显示。
<script type="text/javascript">
//页面显示初始化部分
var zhi1=document.getElementById("zhi1").value;
var zhi2=document.getElementById("zhi2").value;
var zhi3=document.getElementById("zhi3").value;
var zhi=parseInt(zhi1)+parseInt(zhi2)+parseInt(zhi3);
var divs=document.getElementsByTagName("div");
divs[0].style.display="block";
//点击单选题目显示
function getdanxuan(obj){
for(var i=0;i<zhi;i++){
divs[i].style.display="none";
}
var a=obj.className;
var div1=document.getElementById(a);
div1.style.display = "block";
}
//点击多选题目显示
function getduoxuan(obj){
for(var i=0;i<zhi;i++){
divs[i].style.display="none";
}
var b=obj.className;
var div2=document.getElementById(b);
div2.style.display="block";
}
//点击判断题目显示
function getpanduan(obj){
for(var i=0;i<zhi;i++){
divs[i].style.display="none";
}
var c=obj.className;
var div3=document.getElementById(c);
div3.style.display="block";
}
//点击上一题
function shang(){
var divs=document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++){
if(divs[i].style.display=='block'){
if(i==0){
alert("已经是第一题了");break;
}else{
divs[i].style.display="none";
divs[i-1].style.display="block";
break;
}
}
}
}
//点击下一题
function xia(){
var divs=document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++){
if(divs[i].style.display=='block'){
if(i==divs.length-1){
alert("已经是最后一题了");break;
}else{
divs[i].style.display="none";
divs[i+1].style.display="block";
break;
}
}
}
}
基本上思路就是这样了,本人能力有限,欢迎指点迷津。