2021山东大学软件学院JAVA高程课设-考试平台的设计与开发(三)

系列目录

2021山东大学软件学院JAVA高程课设-考试平台的设计与开发(一)
2021山东大学软件学院JAVA高程课设-考试平台的设计与开发(二)
2021山东大学软件学院JAVA高程课设-考试平台的设计与开发(三)



系统的主要界面

登录界面
注册界面
教师主界面
个人信息界面
对于题库维护的增删改查界面
教师自主出卷界面
系统随机出卷界面
预览试卷界面
教师批阅试卷界面
查看学生成绩柱状图
查看学生成绩排名表修改密码界面

学生主界面

学生考试界面

系统模块架构

1、 系统总体框架和功能模块设计

总体分为教师和学生两个模块,其中登陆注册、个人信息是两个模块所共有的,除此之外学生模块主要还有考试功能模块;教师主要还有题库维护功能模块、创建试卷功能模块、批阅试卷功能模块和查看成绩功能模块
在这里插入图片描述

2、各个功能模块详细设计

功能模块主要分为登陆注册、个人信息、题库维护、教师组卷、批阅试卷、查看成绩、学生答卷七个部分。

(1)登录注册
这部分是教师和学生所共有的,也是整个程序的入口,具体的流程如下:
在这里插入图片描述

(2)个人信息
这个模块也是教师和学生所共有的,主要实现对个人信息的查看和修改、修改密码等功能,流程图如下
在这里插入图片描述

(3)题库维护
教师系统的功能模块,主要实现的功能是按题目难度或类型浏览题库的所有试题,以及通过题目ID对题库中的题目进行添加、删除、修改和查看。流程图如下
在这里插入图片描述

(4)教师组卷
教师系统的功能模块,主要实现的功能是教师自主选择题目组卷,或者教师选择题目难度和不同题目的数量由系统自动组卷,然后可对生成的随机试卷的试题进行添加和删除。为保证用户在选择题目时可以查看题目的详细信息,上述两个功能中都增添了根据题目难度或类型浏览题库的所有试题的功能。流程图如下
在这里插入图片描述

(5)批阅试卷
教师系统的功能模块,主要实现的功能是教师通过输入试卷名和学生名对学生答卷进行批改,其中客观题由系统自动批阅,主观题分数由教师给出;为保证用户在选择试卷和学生时可以查看自己发布的所有试卷,同时也可以看见指定试卷的所有答题学生,这个模块增添了这两个查看功能。流程图如下:
在这里插入图片描述

(6)查看成绩
教师系统的功能模块,主要实现的功能是教师通过输入试卷名查看该卷的考试情况,分为成绩表和柱状图两种:成绩表是指定试卷所有学生的成绩的排名表,按从高到低显示;柱状图是统计该答卷的所有答题学生的各个分数段的人数。这个模块添加了查看所有已批阅试卷的功能。流程图如下:
在这里插入图片描述

(7)学生答卷
学生系统的功能模块,主要实现的功能是学生选择已发布的试卷进行考试,在答题过程中学生可以以任意的顺序答题,而不必按照试卷题目的顺序答题;对于试卷的提交有两种方式,一种是考试时间结束系统自动交卷,另一种是考试时间未到学生主动交卷;这个模块添加了学生查看所有已发布试卷的功能。流程图如下:
在这里插入图片描述

3、实体类以及对应的数据库表格的设计

(1)SerializableObject类
这个类用于在服务端和客户端传输数据时,将所要传输的数据序列化,每一个数据对象都会继承这个类,无论是传递对象还是直接传递功能都要使用这个类
属性:
private String function;//对于客户端来说:传递该对象时所要实现的功能,比如要实现登陆功能,则该//变量为“登陆”;对于服务端来说:传递一个结果反馈,表示是否操作成功,//比如“登陆成功”
private String TYPE;//传递的对象的类型

(2)UserInfo类
这个类代表一个用户,其属性是用户的所有信息
属性:
private String username;//汉字不超过五个字
private String telephonenum;//电话号码同时作为账号,11位数字的字符串
private String school;//汉字不超过8个字
private String identity;//只有“学生”和“老师”两种
private String password;//8位数字
对应数据库中的userinfo表格
在这里插入图片描述
(3)Question类
这个类代表一个试题,共分为选择题、阅读题、编程题三种
程序阅读题和编程题无选项,所以选项值默认为null
属性:
private int Id;//题目的ID用于标识一道题,不可为空,不可重复
private double Complexity;//题目难度,范围为1.0到5.0的double,方便后续对于试卷综合难度的计算
private String Type=null;//题目类型,只有三种
private String Content=null;//题目内容
private String Optiona=null;//选项 A的标号及内容
private String Optionb=null; //选项B的标号及内容
private String Optionc=null; //选项C的标号及内容
private String Optiond=null; //选项D的标号及内容
private String Answer=null;//题目的答案,选择题为A/B/C/D
对应数据库中的questionbank表格
在这里插入图片描述
(4)TestPaper类
这个类代表教师发布的学生还未做过的试卷
属性:
private String papername;//试卷名称,也对应着数据库中的试卷表格名,必须是英文的
private String degreedif;//试卷难度,分为容易,中等,较难,困难四个等级
private int questionsum;//总题目个数,也对应着数据库中的表格行数,以下三种类型题目不一定都有
//也就是说其中的一种或两种可为0
private int selectionnum;//选择题个数
private int readingnum;//读程序题个数
private int programingnum;//编程题个数
private String createperson=null;//创建人,即创建这张试卷教师的用户名
private int totalscore;//试卷的总分数
private String allquestionid=null;//将试卷包含的所有题目的id存为字符串的形式,以空格作为分隔

对应数据库中的totalpaper表格
在这里插入图片描述
(5)PaperName类
这个类代表已经发布的由学生做过的试卷,分为批阅和未批阅两种,对应的数据库表格名称就是试卷的名称
属性:
private String state;//只有“已批阅”和“未批阅”两种
private String studentname;//做过这张试卷的学生的用户名
private String studentpaper;/对应学生的答题情况(这又是另一张表,存储了该学生在该试卷上每一道题的答案),必须是英文,格式是“试卷名称+学生账号”/
private int studentscore;//对应学生的得分

对应数据库中的papername(这个代表试卷名,由用户决定,每创建一张试卷,就在数据库中建一个这样的表格)表格
在这里插入图片描述
(6)StudentPaper类
这个类代表着特定学生对于特定试卷的答题情况
属性:
private int id;//每一个题目的ID,只有一个且一定不会重复,对应的是questionbank中的每一道题
private String state;//只有“已批阅”和“未批阅”两种
private String studentanswer;//该生的作答答案
private int questionscore;//该学生本题的得分,选择题只有0或5,阅读题可以是0-10之间的任意整
//数,编程题可以是0-15之间的任意整数
对应数据库中的studentpaper(这个代表指定试卷的指定学生的答卷名,格式是“试卷名称+学生账号”,每有一个学生答卷,就在数据库中创建一个这样的表格)表格
在这里插入图片描述

4、完成系统需要的包、类及接口

总体分为了三个,分别是DataObject包、Client包和Server包,以下是详细介绍
(1)DataObject包
(详细介绍见3、实体类以及对应的数据库表格的设计)
SerializableObject类
UserInfo类
Question类
TestPaper类
PaperName类
StudentPaper类
(2)Client包
主要是GUI以及socket客户端等的一些相关类
LogInterface类
用户登录的界面,与Enroll(注册)界面相关联,客户端的入口程序
Enroll类
用户注册的界面
StudentPanel类
学生登录后的界面,界面上显示出学生的功能
StudentChooseText类
学生点击考试后弹出的界面,此界面上可以学生可以查询未做过的试卷后输入某张试卷名字进行考试
PaperView
类学生在点击查看试卷后出来的界面,以表格的形式将所有试卷罗列出来
StudentText类
学生点击开始考试后出现的考试界面,学生在此界面上答题、交卷
TeacherPanel类
老师登录后的界面,界面上显示出老师能够使用的功能
DefendQuestion类
老师维护题库的页面,有三个选项可选
SelectquesText类
试卷的增删改查
QuestionType类
按题型查看题目
TypeSumTable类
老师选择查询某一题型的题后弹出的界面,以表格形式将此题型的题罗列出来
QuestionCom类
按难度查看题目
ComSumTable类
老师选择查询某一难度的题后弹出来的界面,以表格形式将此难度的题罗列出来
GiveTest类
老师在TeacherPanel界面点击出试卷后弹出的界面,老师选择出题方式
AddTest类
系统随机出试卷(输入试卷名、难度、题目个数等等信息)
ChooseText类
自主选择出试卷
ViewTest类
用于试卷的预览 包括试卷的增删 下一题上一题预览
ViewTest01类
在试卷预览界面上点击添加后弹出的界面,用于在试卷中添加新的题目
LookPaper类
老师点击批阅试卷后弹出的界面,此界面上老师可以查看自己出过的试卷,输入某张试卷的名称,弹出ChoosePaper界面
ViewTeacherPaper类
以表格形式罗列出老师出过的试卷
ChoosePaper类
在此界面上老师可以查看输入的那张试卷的未批改的学生试卷,然后输入某个学生的名称,进行批改此学生的试卷
CorrectText类
老师批改试卷
ViewStudentPaper 类
将做过此试卷的、未批改的学生信息以表格罗列出来
LookGrades类
老师点击查询成绩后弹出的界面,此界面上老师选择两种不同的查询成绩的方式
StuBarChart类
柱状图显示学生成绩
GradesType类
学生成绩排名表
(3)Server包
DbConnect类
用于连接数据库
Landing类
用于实现登陆的具体操作
Enroll类
实现注册功能
ChangePassword类
用于实现修改个人信息和密码的功能
DoQuestionBank类
实现对题库的增删改查,简单维护
DoTestPaper类
实现创建试卷,存储试卷,获取试卷的功能
DoPaperName类
对DoTestPaper的方法createPaperName(String papername)所创表格内的数据的一系列操作
DoStudentPaper类
用于对于studentpaper这类表的一些操作
ServerHandleThread类
服务器端多线程处理
MasterSever类
服务器端建立socket端口,也是服务端的入口程序

主要技术难点

1、从数据库中获得批量数据

在编写与数据库的数据进行交互的的类中,很多方法都需要从数据库中批量获得多个对象的数据,对于这些获得的数据,不仅要求每一个对象的属性都要一一与之对应,并且还需要能方便地调用每一个对象的不同属性。
对此解决的方法是新建一个对象向数组,数组的每一个元素都指向实体类的一个不同的对象,在从数据库查询数据时,只需循环调用实体类的set方法,就可以将每个对象的对应属性存储起来;在调用时,只需对该数组的某个元素(对应着一个实体类对象)使用对应的get方法,即可获得该对象的对应属性。
具体实现的代码如下(以从题库中获得对应难度的所有题目的方法为例):
//根据题目难度获取题目,并将查询结果存储到数组中(如果是阅读题和编程题,则选项值为null),返回值是一个Question[]
首先使用工具类DbConnect与数据库建立连接,这里的SQL语句获取的是传入参数难度的所有对应题目的所有信息
在这里插入图片描述将resultset的参数设置为TYPE_SCROLL_SENSITIVE,以便游标可以在结果集中上下移动;
首先将游标指向结果集的末尾,将结果遍历一遍,获取结果集的行数,以便确定Question[]数组的长度,然后再调用rs.beforeFirst();方法返回到开始
在这里插入图片描述使用while循环对返回的数据进行处理封装,循环直到遍历完resultset的最后一行停止;
每一次循环就new一个新的实体类对象,使用question.set方法和rs.get方法将对应的数据赋值给对象的对应属性;
最后将数组对应下标的元素指向这个实体类对象,然后数组下标指向下一位
在这里插入图片描述最后将questions这个数组返回。

2、根据给定的难度和题型数随机组卷

试卷难度的计算公式是试卷难度=生成试卷的每个题目难度之和/试卷题目个数(结果为double型,保留两位小数),然后通过if-else语句将数值与四个难度对应起来(四个难度分别是简单、中等、较难、困难)
具体实现的代码如下:
//根据题目数产生对应数量的不重复随机数,并将其存储在数组中,内部方法
在这里插入图片描述//根据三种类型的题目个数随机抽题创建符合要求的试卷
//根据这三种类型的题目的个数分别是否为零可分为7种情况
//这里展示的是三种题型都不为零的情况
//返回一个Question数组,存储了系统随机抽的题,存储顺序一定是按照选择题,阅读题,编程题
在这里插入图片描述
/调用doQuestionBank.queryQuestionType(String type);方法得到题库中对应题型的所有题目,存储在三个数组中;在调用上面的randomID(int num, int range)方法,分别产生符合三种题型数目的随机数组/
在这里插入图片描述//将三种题型选出的题加入到总数组里
在这里插入图片描述//此方法最终返回的题目数组只是符合了题型数目要求,同时满足难度要求的方法是下面的这个
//根据初始要求随机抽题创建符合要求的试卷(试卷难度和三种类型题个数)
//这个方法根据难度不同分为四种情况,这里展示的是试卷难度为“容易”的情况
在这里插入图片描述

3、存储试卷的考题

考虑到教师所出的每一张试卷都可以看做是一个满足特殊条件的题组,题组中的每一道题都是一个Question实体类对象,但是数据库无法直接存储数组,而且再另建表格存储考题的方式也很麻烦。最终想到的解决方法是只存储试卷的所有考题的ID(每一道题的ID都不会重复,并且通过ID一定能够在数据库中定位到一道题),然后在需要考题的其他数据时,通过ID在questionbank(存储题库的表格)这张表格中进行查询。
具体实现的代码如下:
//内部方法,将一个带空格的字符串以空格为分隔符转换为一组整数,并存入一个数组
/这里所指的字符串就是存储一张试卷的所有题目ID的字符串(在实体类Testpaper中的属性名和数据库表格totalpaper中的列名都是allquestionid属性),以空格为分隔;例如,一张考卷的所有题目的对应ID为:1,4,9,23,41,12;则其对应的字符串就是“1 4 9 23 41 12 ”/
在这里插入图片描述
//根据试卷的allquestionid属性得到试卷的所有试题,返回一个Question类数组
这个方法中调用了上面的transform(String str)方法和DoQuestionBank中根据ID获取题目的queryQuestionID(int id)方法
在这里插入图片描述

4、服务端处理客户端请求

这里用到了socket网络编程技术和多线程技术,需要解决的问题就是客户端和服务端交互时,服务端要对接受到的数据进行分析处理,决定调用后端的哪个模块的方法;同时客户端在接收到服务端的数据和反馈时,也需要进行判断请求是否顺利实现。
设计的解决方法是定义了一个SerializableObject类,这个类实现了Serializable接口,然后需要在客户端和服务端之间传递的对象的实体类都继承了这个类,以保证可序列化(详细介绍见系统模块架构中的3.(1));
在客户端连接端口时,对于从服务端接受到的数据,就可以使用getFunction()方法获得服务端反馈的结果;而向服务端传递数据时,就使用setFunction(String function)方法setTYPE(String type)和方法对传输的数据进行标记。
以下代码以实现登陆部分为例体现客户端和服务端的交互
//首先是客户端的代码
// LogInterfac类是用户登录的界面,同时其中也有连接服务端的方法(写在actionPerformed(ActionEvent e)方法中)
//得到用户输入的账号和密码,并且封装和标记要传输的数据
在这里插入图片描述
//然后与服务端建立连接,传输对象,接收对象
在这里插入图片描述
//最后对接收到的数据进行判断和处理
在这里插入图片描述
//然后是服务端的代码
/ServerHandleThread是一个服务器用以处理线程的类,其中的上述功能将在run()方法中通过if-else语句来实现/
//先对接收的对象进行强转,转换为SerializableObject类
//使用getFunction()方法得到客户端的请求
在这里插入图片描述//然后使用if语句进行判断
在这里插入图片描述//如果登陆成功,则返回一个function为“登陆成功”的对象,还包含一个用户的所有信息
//同时也会在服务器主程序的控制台窗口打印出“用户XX登陆”的字符串
在这里插入图片描述
//如果登陆失败,则直接向客户端发送一个function为“登陆失败,账号或密码错误”的对象
在这里插入图片描述

关键代码

我负责的主要部分是后端的开发以及网络编程部分,用到的主要技术有:数据库相关技术、socket网络编程技术以及多线程技术。以下是部分关键类或方法的具体代码:

1、 连接SQL数据库的方法

public class DbConnect {
    private static Connection conn=null;
    private String driver = "com.mysql.cj.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3306/courses?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
    private String username = "";//自己设置的数据库的用户名和密码
    private String password = "";
    public DbConnect() {
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, username, password);
            //System.out.println(conn);//检测是否连接成功
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

2、 调用DbConnect类连接数据库并对其中的数据进行操作的方法

DoQuestionBank类中的根据题ID对题库的题进行删除的方法

public String deleteQuestion(int id) throws SQLException {
    dbcon = new DbConnect();
    conn = dbcon.getConnection();
    String str="删除成功";
    String sql1 = "delete from questionbank where id=?";
    try
    {
        pstmt1 = conn.prepareStatement(sql1);
        pstmt1.setInt(1,id);
        pstmt1.executeUpdate();
    }
    catch (SQLException e)
    {
        e.printStackTrace();
    }
    finally
    {
        dbcon.closeAll(rs,pstmt1,conn);
    }
    return str;
}

DoPaperName类中实现“每当有一个学生答题,就新建一张表格存储该生的答题情况,表名是studentpaper,详细说明在同名类中,每一张试卷对应的每一个学生都有一个”的方法

public String createStudentPaper(String studentpaper){
    dbcon = new DbConnect();
    conn = dbcon.getConnection();
    String str=null;
    String sql1 = "create table "+studentpaper+" (id int,state varchar(1000),studentanswer varchar(1000),questionscore int)";
    try
    {
        pstmt1=conn.prepareStatement(sql1);
        pstmt1.executeUpdate();
        str="创建成功";
    }
    catch (SQLException e1)
    {
        e1.printStackTrace();
    }
    catch (NumberFormatException e)
    {
        e.printStackTrace();
    }
    catch (HeadlessException e)
    {
        e.printStackTrace();
    }
    finally
    {
        dbcon.closeAll(rs,pstmt1,conn);
    }

    return str;
}

3、 启动服务器创建端口的方法

public static void main(String[] args){
    try {
        ServerSocket serverSocket = new ServerSocket(8686);
        System.out.println("服务器启动,等待客户端的连接。。。");
        Socket socket = null;
        while(true){
            socket=serverSocket.accept();// 等待并取出用户连接,并创建套接字
            Thread serverHandleThread=new Thread(new ServerHandleThread(socket));
            serverHandleThread.setPriority(5);//设置线程优先级
            serverHandleThread.start();
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

4、 服务器中多线程处理客户端请求的方法

启动线程的方法

public ServerHandleThread(Socket socket) {
    super();
    this.socket = socket;
}

run()方法中的对教师批阅阅读题和编程题的处理(包括系统自动批阅选择题的部分)

//服务端要传两次对象
if(serializableObject_in.getFunction().equals("获取未批阅的阅读题和编程题")){
    socket.shutdownInput();
    //System.out.println(serializableObject_in.getTYPE(););
    //这是第一次传输的对象,是学生的答案和相应题号
    DoStudentPaper dostudentPaper=new DoStudentPaper();
    StudentPaper[] studentPapers=dostudentPaper.getStudentAnswer(serializableObject_in.getTYPE(),"未批阅");
    serializableObjects_out=studentPapers;
    //相关信息统一存储在数组的第一个元素对象中
    serializableObjects_out[0].setFunction("获取成功");
    serializableObjects_out[0].setTYPE("StudentPaper[]");
    os=socket.getOutputStream();
    oos=new ObjectOutputStream(os);
    oos.writeObject(serializableObjects_out);
    //socket.shutdownOutput();
    //第二次传输对象,传输对应题号的问题的所有相关信息
    DoQuestionBank doQuestionBank=new DoQuestionBank();
    Question[] questions=new Question[studentPapers.length];
    for(int i=0;i<studentPapers.length;i++){
        questions[i]=doQuestionBank.queryQuestionID(studentPapers[i].getId());
    }
    serializableObjects_out=questions;
    //相关信息统一存储在数组的第一个元素对象中
    System.out.println("获取未批阅的阅读题和编程题!");
    serializableObjects_out[0].setFunction("获取成功");
    serializableObjects_out[0].setTYPE("Question[]");
    os=socket.getOutputStream();
    oos=new ObjectOutputStream(os);
    oos.writeObject(serializableObjects_out);
    socket.shutdownOutput();
}

run方法中处理“学生提交答卷之后,存储学生的作答情况”部分的代码

//客户端传递两次数据,第一次是一个对象,第二次传递一个数组对象
if(serializableObject_in.getFunction().equals("存储学生答卷")){
    //第一个对象(这里客户端在发送的时候要将TYPE设置成表格名)
    PaperName paperName=(PaperName) serializableObject_in;
    //继续接收第二个对象
    is = socket.getInputStream();
    ois=new ObjectInputStream(is);
    serializableObjects_in=(SerializableObject[]) ois.readObject();
    socket.shutdownInput();
    //第二个对象
    Question[] questions=(Question[]) serializableObjects_in;
    DoPaperName doPaperName=new DoPaperName();
    DoStudentPaper doStudentPaper=new DoStudentPaper();
    //这里就是服务端第一次传输的对象的所有属性,记得看
    String str1=doPaperName.addStudent(paperName.getTYPE(),paperName.getStudentname(),paperName.getStudentpaper());
    //第二次传输对象的处理(数组每一个元素的对象的属性是ID和答案)
    //新建表格
    String str2=doPaperName.createStudentPaper(paperName.getStudentpaper());
    //逐题储存
    int x=questions.length;
    for(int i=0;i<x;i++){
        doStudentPaper.addStudentAnswer(paperName.getStudentpaper(),questions[i].getId(),questions[i].getAnswer());
    }
    //存储成功
    if(str1.equals("添加成功")&&str2.equals("创建成功")){
        System.out.println("存储学生答卷!");
        serializableObject_out= new SerializableObject();
        serializableObject_out.setFunction("存储学生答卷成功");
        os=socket.getOutputStream();
        oos=new ObjectOutputStream(os);
        oos.writeObject(serializableObject_out);
        socket.shutdownOutput();
    }
    //存储失败
    else {
        serializableObject_out= new SerializableObject();
        serializableObject_out.setFunction("存储学生答卷失败");
        os=socket.getOutputStream();
        oos=new ObjectOutputStream(os);
        oos.writeObject(serializableObject_out);
        socket.shutdownOutput();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值