[toc]


开发规划:

  1. 实现无界面的系统,编写可以独立完成所有功能的后端代码
    1. 提高代码的复用性,减少重复的字段,解耦合。
      将功能封装为函数,函数值完成执行,获取值,返回值,不进行打印等额外功能,将函数功能化。调用函数的代码负责对函数返回值进行处理。提高易用性。
    2. 抽象化,将同类功能函数抽象为同一类,并加入必要的成员变量,隐藏信息、保护数据、便于代码移植。
  2. 实现有界面的系统,采用B/S 浏览器/服务器 结构
    1. 前端通过浏览器展示,采用html展示页面结构,css进行html美化,js用于控制html上组件的行为,并内联ajax,将某些组件的响应作为请求,与参数一起打包发送,并等待后端响应,获取参数,然后执行相应的操作
    2. 后端,先移植后端代码与数据库到Linux云服务器。升级cmake和gcc。安装httplib c++库,用来监听指定端口下的网络请求,执行后端代码,将结果作为参数传回前端。
    3. 将移植后的后端代码进行更改,将接口的参数和返回值,进行更改,比如将变量进行JSON序列话和反序列化,用于网络通信时参数传递。

技术栈:

后端:C/C++ C++11STL准标准库JsonCpp准标准库cpp-httplib准标准库mysql

前端:前端三大件(HTML5,CSS,JS),此处使用jQuery替代JS,学习成本较低Ajax,在JS中插入,用于向服务器发送请求,实现前后端交互

项目环境:

Centos7 云服务器vim/gcc(g++)/MakefileClionvscodeMySQL80

开发流程:

后端开发:

1.本地开发,基本功能实现

开发环境:CLion,本地MySQL

使用CLion进行开发,使用Cpp通过MySQL原生自带API连接MySQL中相关数据库

需求分析

监考管理查询系统

系统分为管理员和教师两种角色。

管理员:
导入监考信息(考试科目、监考人员(可多人监考一门考试)、时间、地点)。
监考信息管理(增删改查)。
教师管理(增删改查)

教师:
查看监考信息。

设计数据库:

初步设计:

设计三个表,管理员信息(root)、教师信息(teacher)、监考信息(proctoring_information)

root (ID, passwd),其中ID为主键,ID与passwd均不能为空

teacher (ID, name),其中ID为主键,ID与name均不能为空

proctoring_information (proctoringID, proctoringName, teacherID, teacherName, startTime, endTime, allTime, access),其中proctoringID和teacherID为主键

数据类型均为varchar

此数据库各表都只遵循了第二范式
虽然满足了“一个考试可以允许有多个监考老师”的要求,但是数据原子性太强,数据中的依赖性强,数据冗余过大,牵一发而动全身,一个数据更改之后,其余数据必须更改,麻烦费力

完善设计:
将proctoring_Information拆分为proctoring_Information和exam_Information

proctoring_Information (proctoringID, teacherID),其中proctroingID和teacherID均为主键

exam_Information (proctoringID, proctoringName, startTime, endTime, allTime, access),proctoringID为主键

proctoring_Information(proctoringID, teacherID),proctoringID和teacherID为主键

数据类型均为varchar

满足第三范式

算法实现:

  1. 身份选择:
    分为教师登陆和管理员登陆,进入登陆界面后,用户先进行身份选择,选择后会留下身份记录,进入到对应的登陆界面

  2. 登陆:
    教师登陆,只需要输入教师编号(ID)即可
    管理员登陆,输入管理员编号和管理员密码
    根据步骤1中留下的身份记录,若是教师,则将输入的教师编号,去数据库teacher表中比对,若找到相应信息,则登陆成功;若为管理员,则将输入的管理员编号和管理员密码,去数据库root表中比对,若用户名和密码同时符合,则登陆成功。登陆成功,进入对应的服务界面,

  3. 教师服务:
    教师登陆后,程序根据之前教师输入的教师编号,去数据库proctoring_Information表中查找教师ID对应的考试ID,然后根据考试ID去数据库exam_Information表中查找对应的考试信息,将两个表中的查询结果合并起来(SQL语句实现),并将查询结果使用对应数据结构保存起来(在纯后端中使用的是对应的对象,在前后端交互中是JSON序列化后的string),并返回给调用函数的代码段,代码段负责将结果打印或者传递给前端。

  4. 管理员服务:

    1. 考试信息导入
      输入要增加的考试信息的编号、名称、开始时间、结束时间、考试地点。
      获取后,后端会进行检查:

      1. 考试编号是否已存在,若已存在,会返回对应通知
      2. 开始时间、结束时间是否符合正常时间规范,如月份不能超过12,每月天数规范,时分界限等
      3. 考试时间必须设置在当前时间的将来(调用time库文件,与当前时间比对)
      4. 考试的结束时间比如晚于考试的开始时间

      若检查无误,则会将根据开始时间与结束时间计算出考试总时长(将开始时间结束时间由字符串转换成为整型数据,然后相减,再将结果转化为字符串)

    2. 考试信息管理
      在涉及到信息的增删改时,会将所有信息显示在最底侧,供增删改信息时查看比对

      1. 增加监考信息
        需要输入新增的监考信息的考试编号和教师编号,会进行监考是否重复存在、考试是否存在、教师是否存在的检查
      2. 删除考试信息
        需要输入要删除的考试信息的考试编号,会进行考试是否存在的检查。同时由于考试信息被删除,对应的监考信息也应被删除。在执行之前会告知用户并询问是否继续。将信息传递给后端,后端调用数据库接口,执行对应SQL语句,删除之后会查找应被删除的考试信息,若查找结果为空,说明考试信息删除成功。
      3. 删除监考信息
        需要输入考试编号和教师编号。逻辑同上。
      4. 更改考试信息
        输入要更改的考试信息的考试编号。会进行考试是否存在的检查。需要更改某项信息,则在对应的一栏填写更改后的数据,不需要更改的信息不用填写。若考试信息的考试编号被更改,对应的监考信息也应被更改。在执行之前会告知用户并询问是否继续。将信息传递给后端,后端调用数据库接口,执行对应SQL语句,更改之后会查找应更改后的考试信息,若查找到对应的结果,说明考试信息更改成功。
        在执行更改后查找的过程中,若考试信息的考试编号被更改,会使用更改后的考试编号进行查找。
      5. 更改监考信息
        输入要更改的监考信息的考试编号和教师编号。逻辑同上。
      6. 查看信息
        1. 查看全部考试信息
        2. 查看全部监考信息
        3. 查看全部安排了监考的考试信息
          查询所有的监考信息,并将结果中的考试编号和教师编号分别作为考试信息表和教师信息表的查找条件,将三个表的查询结果合并,然后返回。
        4. 按条件查找考试信息
          给出考试信息各元素的输入框,要根据哪几个信息查询,就在对应的框中输入信息。
        5. 按条件查找监考信息
        6. 按条件查找安排了监考的考试信息
    3. 教师信息管理,逻辑同上

      1. 增加教师信息
      2. 删除教师信息
      3. 更改教师信息
      4. 查看全部教师信息
      5. 按条件查找教师信息

2.代码移植云服务器,并搭建相应环境

环境搭建:

前端开发&前后端交互:

前言:本人不精通前端技术,只是为实现此次程序现学现卖,也只了解到一些基础以及一些开发使用到的技术,若有笔误,请指正

页面结构搭建:html
页面美化:css
页面控制、前后端交互:JS

思路:

在后端程序所在目录中新建一个前端文件夹,存放前端文件,cpp中设置将路径的根目录映射到前端文件目录

截屏2024-01-07 15.50.06

在前端中,设置index.html、TeacherLogin.html、RootLogin.html三个页面,index.html为初始页面,选择登陆身份,选择教师则跳转到TeacherLogin,选择管理员跳转到RootLogin

截屏2024-01-07 16.00.29 截屏2024-01-07 16.00.36

前后端交互实现:

  • 身份选择:在前端中添加JS控制段,获取”教师登陆“与”管理员登陆“按钮被单击的事件,编写函数,实现页面跳转

    截屏2024-01-07 17.46.42
  • 登陆:在前端中添加JS控制段。编写函数,在”登陆”按钮被单击时,获取输入框中的内容,并提交表格。同时,通过AJAX,向指定路径发送网络请求。在后端中,cpp借助httplib库,监听特定端口下制定路径的请求,接受网络请求及传来的参数,进行后端操作,并将结果通过参数的形式响应给前端发送请求的AJAX。

    截屏2024-01-07 17.52.26
    JS前端代码示例
    截屏2024-01-07 17.52.45
    cpp后端代码示例

    此处AJAX传递参数的时候
    可以选择上例中的,在url链接中拼接参数,采用健值对,第一个健值对与链接之间必须加上?,剩余健值对之间通过&
    也可以采用JSON传递,在AJAX中增加一个data项,内容为JSON格式数据,在增加一个dataType项,用来表示数据采用的数据结构为JSON

    截屏2024-01-07 18.01.35

    前一种方式比较简便,但是当传递参数数目过多时,在前端经常报错。而JSON作为专用的网络传递数据结构,在网络传参中有十分优秀的性能,当要传递参数数目多时,要采用第二种方式。

    jQuery - $.ajax() data{} 传参三种常见写法及ajax()方法参数详解

  • 其余各中功能的前后端交互,都是基于上述模式。

界面元素显示与隐藏

JS可以控制html元素的显示与隐藏,由此实现在不刷新界面、不跳转到其他界面情况下,页面内容动态更改的效果。

截屏2024-01-07 18.10.24

表格table的动态生成:

每次在后端获取到JSON类型的数据库查询结果后,相应的表格都需要动态刷新(本质是清空原表单、动态生成新表单)。借助JS功能实现。详细请见源码。或见此处JavaScript(JS)网页–动态生成表格_js调用服务器接口,html实现网页表格

设定input输入框输入,提交表单form后,html不刷新:

默认情况下,当在input输入框输入后,点击提交submit,会将输入框所在的表单进行提交,同时html页面会刷新。由于此次采用的JS控制html元素来动态展示页面,因此页面刷新后会回到初始状态,不利于处理。需要更改成为,点击提交submit后不刷新html,同时还可以成功提交表单数据。form表单的submit不重新刷新当前页面

1
<iframe id="Teacher_del_rfFrame" name="Teacher_del_rfFrame" src="about:blank" style="display:none;"></iframe>

在html中,form元素结束位置下,添加如上代码,id、name自定义

截屏2024-01-07 18.21.49
1
$("#root_Teacher_del").attr("target", "Teacher_del_rfFrame");

在JS中,需要进行表单提交操作的函数中,加入上述代码,控制器中的id更换成对应表单的id,attr中第二个参数更改为之前html中添加的iframe元素的id,第一个参数保持不变。

添加html网页中上传excel文件,并读取文件内容,转化为json供后端使用,转化为csv供前端展示:

截屏2024-01-08 15.40.10

使用JavaScript实现纯前端读取excel文件并与后台进行交互_html 读excel?

上述方法中,将excel中内容读取,并转化为json数组(因为excel中结构是:第一行中各列为表头,其余行为表头对应的值,就看作为一个json数组),同时也转化为csv格式
上述方法中包括了将csv格式展现为表格并在页面中显示
关键是在使用AJAX传参时,如何传递JSON数组。一般情况下JSON传参是普通的JSON对象,即写成string类型就是左右一个大括号{},里面为健值对。而此次获取的JSON数组,为[]中包含的一个个普通JSON对象,正常情况下无法通过AJAX传参。需要将JSON数组转化为JSON字符串,并作为AJAX传参的JSON对象健值对中的一个值。Ajax传递数组

截屏2024-01-08 20.31.14

后端接收到前端传递的JSON对象,通过键,找到对应的值,这个值就是JSON字符串化后的excel JSON数组。
注意,此时获取的只是一个字符串,并不是JSON数组类型,因此应先将字符串序列化为JSON类型,然后获取里面的值。Jsoncpp 数组的使用

截屏2024-01-08 20.33.01

makefile编写:

此项目的编译,设计多文件编程、mysql连接使用、httplib库连接使用、cppjson库连接使用

1
2
3
4
5
6
7
8
9
test: test.o teacherProctoringSystem.o
g++ -Wall -o test test.o teacherProctoringSystem.o -L/usr/lib64/mysql -lmysqlclient -lpthread -ldl -lssl -lcrypto -lresolv -lm -lrt -L /usr/local/lib /usr/local/lib/libjsoncpp.a
test.o: test.cpp teacherProctoringSystem.h
g++ -Wall -std=c++11 -c test.cpp -L/usr/lib64/mysql -lmysqlclient -lpthread -ldl -lssl -lcrypto -lresolv -lm -lrt -L /usr/local/lib /usr/local/lib/libjsoncpp.a
teacherProctoringSystem.o: teacherProctoringSystem.cpp teacherProctoringSystem.h
g++ -Wall -std=c++11 -c teacherProctoringSystem.cpp -L/usr/lib64/mysql -lmysqlclient -lpthread -ldl -lssl -lcrypto -lresolv -lm -lrt -L /usr/local/lib /usr/local/lib/libjsoncpp.a
clean:
rm -f test
rm -f *.o

在编译时要注意和相应的库要建立起动态链接,因此g++命令之后需要添加参数

使用XXX_config指令查看需要添加的参数

例如想要查看makefile时链接mysql时,g++后需要加什么参数,使用mysql_config指令,然后寻找-lib或-libs对应的即可

截屏2024-01-07 18.46.05

CentOS7 下 C++ 连接 Mysql 数据库的环境配置以及常用API测试_centos7下:vs code如何配置c++与mysql数据库连接-CSDN博客

项目部署Linux后台&终止项目运行:

1
nohup ./test &

此条指令,是将当前工作目录下的可执行文件test启动(注意是可执行文件,因此必须要先编译过,生成可执行文件),并在允许在后台运行。关闭会话后,test进程也会一直在后台运行。

同时,会在当前工作目录生成nohub.out文件,用来保存test可执行程序中输出的值,例如cout输出的值,此时就会输入到nohub.out文件中。输入到的文件是可以自己指定的,默认为nohub.out。另外可以结合日志文件,将日志输出进去。

1
ps -axj |grep test

查看当前正在运行的test进程,如下图,可以找到当前正在执行的test进程,第一行就是。第二列中的数字为该进程的PID

截屏2024-01-07 19.03.52
1
kill 13187

使用kill指令杀掉进程,后面的参数为要杀掉的进程的PID

常见问题&解决方案:

SSH连接远程服务器缓慢:

在连接时,输入ssh指令后,没有反应,如要等待很久。或者输入密码之后很久才进入。

有可能是服务器内存、CPU占用过高,无法响应。可以登陆服务器官网如阿里云等,重启服务器实例。

也可能是链接中的问题:[SSH连接缓慢解决方法_ssh连接慢-CSDN博客](https://blog.csdn.net/tainyu/article/details/124317063#:~:text=下面说下如何解决这样的问题,最为常见的原因是因为server的sshd会去DNS查找访问 client IP的hostname,如果DNS不可用或者没有相关记录,就会耗费大量时间。 1、在server上%2Fetc%2Fhosts文件中把你本机的ip和hostname加入配置文件中 [root@oral8 ],%23 cat %2Fetc%2Fhosts 192.168.1.55 oral8 2、在server上%2Fetc%2Fssh%2Fsshd_config文件中修改或加入UseDNS%3Dno,GSSAPIAuthentication no)

g++: internal compiler error: Killed (program cc1plus):

内存不足,在程序make编译时,g++被后台杀死。

原因往往是内存不足,被操作系统杀掉。比如我的云服务器为2G2核,使用vscode远程连接时,vscode远程连接服务会占用很多内存,mysql数据库也会占用很多内存。

解决方法:

执行g++或gcc时抱错找不到指令,或安装了更高版本gcc但使用时还是使用的低版本的:

1
2
3
g++ --version

g++ -V

查看一下gcc或者g++版本

若抱错command not found等,说明gcc服务没有启动

1
scl enable devstoolset-7 bash

启动g++服务(enable后的参数根据个人g++版本等不同)

注意,通过这种方式启动后,只在本次会话中生效,一旦会话关闭,g++又会不可用

解决方法,设置每次打开会话时自动启动:

1
vim ~/.bash_profile

更改shell的配置文件(此处我是用的是bash shell,若使用的zsh shell,则是更改~/.zsh_profile)

打开后将scl enable devtoolset-7 bash添加到最后一行,保存后退出。这样每次启动会话shell都会自动启动g++。

1
source ~/.bash_profile

最后是配置文件生效

启动mysql服务&设置mysql开机自启动:

启动mysql服务:

1
systemctl start mysqld.service

设置mysql服务自启动:

1
2
systemctl enable mysqld
systemctl daemon-reload

附加:

1
stl restart mysqld.service 重启mysql

注:

scl指令就是systemctl指令的缩写,二者是一个功能

全称systemcontrol

源码Gitee链接&个人博客链接&教师监考管理系统网址:

项目源码地址

个人博客:🏴‍☠️浴巾的贼船🏴‍☠️ (chunyujin.top)

教师监考管理系统网站:8.130.72.133:8081