boycot 搜索
avatar

boycot

考试系统设计总结

  • 该系统成品已部署与线上,在线访问:ExamSystem
  • 系统线上环境部分功能已锁定
  • 学生端登录
    • 测试账号 1: user1 密码: 7gzt9i0lkh
    • 测试账号 2: user2 密码: lyufmklxy
  • 教师端登录
    • 测试账号: admin1 密码: qrmeke4p75

该系统为本人毕业设计,由于时间仓促,部分功能设计不太优雅,请见谅。
以下截取于部分论文相关内容。

目录

  1. 系统架构
  2. 技术选型
  3. Axios 拦截器
  4. Laravel 相关
  5. 前端界面截图

系统架构

系统功能架构

系统分为学生系统与教师系统,教师用户在登录页登录后自动跳转到教师系统首页,然后教师可以进行创建考试,创建的过程中可以对考试进行临时保存。把题目与考试班级等考试信息填写完毕后可以进行发布考试。这时学生用户登录到学生系统后,学生主页将会显示自己需要进行的考试列表或自己已经完成的考试列表。学生点击进入考试则可进行考试,在规定时间内完成考试后进行交卷。提交试卷后,将会弹出自动阅卷结果,学生可以进入查看自己的答卷与分数。与此同时,教师系统可获取到学生完成考试的列表,教师可以查看学生的成绩与答卷,并对需要进行阅卷的考试进行阅卷。教师系统提供一个公共功能,教师可以发布、编辑、删除公告,发布的公告将展示在学生系统上。

系统功能架构图

系统技术架构

系统技术架构图

技术选型

本研究中,总体采用前后端分离的技术进行开发。前端使用近年流行的 MVVM 框架 Vue,同时使用其附属的 Vue-router 路由管理模块和 Vuex 状态管理模块。UI 将采用 Element-ui 框架,系统将采用部分响应式布局设计。

前端使用 axios 工具,其基于 Ajax 技术开发,前端可通过它把所有需要的动态数据以 JSON 格式请求后端,同时获取后端返回到的 JSON 数据。再将动态数据经过逻辑处理渲染到页面中。

后端总体采用 PHP 技术开发,使用 PHP 的 MVC 框架 Laravel,其具有路由、中间件、查询构造器等功能。本次后端仅作为 API 服务器,并不会直接使用后端的 View 视图层。

数据库方面采用 Mysql 数据库,基于其能与 PHP 语言友好结合,运行速度快,提供事务处理功能的特点。同时,关系型数据库,能方便的处理在线考试系统模型关联问题。

Axios 拦截器

配置 Axios 拦截器,可以在请求之前和请求之后作统一处理。每一个后端请求都需要默认传送一个 token 作为身份认证。所以前端在请求前,可以统一配置这个 token,默认为每个请求自动拼上 token 字段。

后端每个请求都会返回错误码与请求信息,前端 Axios 可以对每个响应进行统一处理。例如,字段码为 200 则说明请求成功,其他字段码均为错误请求,可以为错误请求统一弹出错误信息。

同时本次用拦截器加入了一个取消重复请求与切换路由自动取消之前路由未完成请求的功能。

axios.js

const baseURL = process.env.NODE_ENV === "production" ? "./api" : "./api/api";
// 创建实例
const instance = axios.create({
  baseURL,
  timeout: 90000,
  headers: {
    "Content-Type": "application/json",
  },
});
let axiosPendingList = []; // 记录请求
// 请求拦截
instance.interceptors.request.use((config) => {
  let mark = config.url;
  let markIndex = axiosPendingList.findIndex((item) => item.name == mark);
  if (markIndex > -1) {
    axiosPendingList[markIndex].cancel("重复请求");
    axiosPendingList.splice(markIndex, 1);
  }
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();
  config.cancelToken = source.token;
  config._mark = mark;
  axiosPendingList.push({
    name: mark,
    cancel: source.cancel,
  });
  let token = sessionStorage.getItem("token") || "";
  if (token) {
    if (config.data) {
      config.data.token = token;
    } else {
      config.data = {};
      config.data.token = token;
    }
  }
  if (
    config.method === "post" &&
    config.headers["Content-Type"] !== "application/json"
  ) {
    config.data = qs.stringify(config.data);
  }
  return config;
});
// 响应拦截
instance.interceptors.response.use(
  (response) => {
    let markIndex = axiosPendingList.findIndex(
      (item) => item.name == response.config._mark
    );
    if (markIndex > -1) {
      axiosPendingList.splice(markIndex, 1);
    }
    let data = response.data;
    if (data.code === 200) {
      return data.data;
    } else {
      if (data.code === 300) {
        Message({
          showClose: true,
          message: data.message,
          type: "error",
          duration: 2000,
        });
        router.push("/login");
      } else if (data.message) {
        Message({
          showClose: true,
          message: data.message,
          type: "error",
          duration: 2000,
        });
      }
      return Promise.reject(response);
    }
  },
  (err) => {
    if (!err.message === "重复请求") {
      Message({
        showClose: true,
        message: "Api访问失败,请检查网络..",
        type: "error",
        duration: 1500,
      });
      return Promise.reject(err);
    }
  }
);
export { baseURL, axiosPendingList };
export default instance;

router.js

...
// 路由守卫拦截,切换路由取消未完成请求
router.beforeEach((to, from, next) => {
  axiosPendingList.map(item => {
    if (!item.notAllowCancel) {
      item.cancel()
    }
  })
  next()
})
...

Laravel 相关

中间件

本项目采用前后端分离方式开发,以 Token 形式保存当前的用户信息。Token 可分为学生 Token 和教师 Token,每一个请求都会带有 Token 以识别当前的用户。所以我们可以设置一些请求中间件,在每次请求前统一对 Token 进行处理。 CheckToken.php

...
class CheckToken
{
  /**
  * Handle an incoming request.
  *
  * @param  \Illuminate\Http\Request  $request
  * @param  \Closure  $next
  * @return mixed
  */
  public function handle($request, Closure $next)
  {
    $token = $request -> input('token');
    $now = date('Y-m-d H:i:s');
    $token_row = Token::where([['token', '=', $token],['overdue_after', '>', $now]]);
    if ($token_row->exists()) {
      // 更新Token过期时间
      $update_overdue = date('Y-m-d H:i:s', strtotime("+2 hour"));
      $token_row->update(['overdue_after' => $update_overdue]);
      return $next($request);
    } else {
      return response(Response::loginError());
    }
  }
}
...

查询构造器

使用 Laravel 提供的查询构造器功能,能够方便的让我们的 SQL 语句变得简单,查询数据库数据更加快速。它使用 PDO 连接方式连接数据库,并且通过封装可以有效的防止 SQL 注入攻击。

查询构造器语法什么简洁,并且采用拼接的方式组成。例如:table(‘user’)->get(),它将别转换为 select * from user 的 SQL 命令。在可以运行原生 SQL 的同时,对 SQL 的所有命令都有对其进行进一层封装,例如条件语句、连接查询、排序、分组、Limit 等,同时它扩展了同时多个插入更新、Exists、悲观锁等功能。

使用查询构造器查询出来的数据记录为 Laravel 特有的集合类型,Larvel 为集合类型封装了很多简便快捷的数据处理方法 API,例如 map(遍历)、filter(过滤)、diff(比较)、forPage(快速分页)、groupBy(快速分组)等等。

事务处理

Laravel 框架对 Mysql 的事务处理进行了有效方便的封装,可以让我们对数据库进行开启事务处理功能。开启事务,可以有效地防止数据库方式死锁。在本在线考试系统中,教师可以对正在创建的考试进行临时保存,在进行重新编辑并重新保存时,需要先删除当前该试卷已保存的试题记录,然后再插入修改后的试题记录。这时候在删除和插入的过程中,有可能会发生数据库数据未能正常删除,然后插入了相同 ID 记录,形成数据表错误或发生死锁。所有,我们可以对删除和插入构成同一个事务,只有当删除和插入都没异常时再进行事务提交,若有异常则进行回滚。

使用 DB::beginTransaction()开启事务,然后把所有数据库操作放在一个 try 模块下,在 try 模块最后使用 DB::commit()进行事务提交,然后 catch 模块写入回滚代码 DB::rollBack()。

本次研究中,项目中在学生提交考试、教师编辑临时保存考试、教师发布考试、教师编辑班级等模块操作中都有使用相应事务处理功能。

响应体封装

Response.php

class Response extends Model
{
  public static function success() {
    $result = array(
      'code' => 200,
      'message' => '操作成功'
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
  public static function successMsg($message = '') {
    $result = array(
      'code' => 200,
      'message' => $message
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
  public static function successData(
    $data = array(),
    $message = '数据获取成功'
  ) {
    $result = array(
      'code' => 200,
      'message' => $message,
      'data' => $data
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
  public static function successPage(
    $items = array(),
    $page = 1,
    $pageSize = 10,
    $total = 0,
    $message='数据获取成功'
  ) {
    $result = array(
      'code' => 200,
      'message' => $message,
      'data' => [
        'items' => $items,
        'page' => $page,
        'pageSize' => $pageSize,
        'total' => $total
      ]
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
  public static function loginError() {
    $result = array(
      'code' => 300,
      'message' => '登录状态异常'
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
  public static function parameterError() {
    $result = array(
      'code' => 301,
      'message' => '参数错误'
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
  public static function error($code = 302 , $message = '') {
    if(!is_numeric($code)) return;
    $result = array(
      'code' => $code,
      'message' => $message
    );
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
  }
}

前端界面截图

登录界面 登录界面

创建考试 创建考试

成绩管理 成绩管理

考试管理 考试管理

考试 考试

考试结果 考试结果

由于时间仓促,本次开发的系统虽然基本功能已经完成,但仍有很大的提升空间。

以上内容未经授权请勿随意转载。

系统在线访问:ExamSystem

乳白
杏仁黄
茉莉黄
麦秆黄
油菜花黄
佛手黄
篾黄
葵扇黄
柠檬黄
金瓜黄
藤黄
酪黄
香水玫瑰黄
淡密黄
大豆黄
素馨黄
向日葵黄
雅梨黄
黄连黄
金盏黄
蛋壳黄
肉色
鹅掌黄
鸡蛋黄
鼬黄
榴萼黄
淡橘橙
枇杷黄
橙皮黄
北瓜黄
杏黄
雄黄
万寿菊黄
菊蕾白
秋葵黄
硫华黄
柚黄
芒果黄
蒿黄
姜黄
香蕉黄
草黄
新禾绿
月灰
淡灰绿
草灰绿
苔绿
碧螺春绿
燕羽灰
蟹壳灰
潭水绿
橄榄绿
蚌肉白
豆汁黄
淡茧黄
乳鸭黄
荔肉白
象牙黄
炒米黄
鹦鹉冠黄
木瓜黄
浅烙黄
莲子白
谷黄
栀子黄
芥黄
银鼠灰
尘灰
枯绿
鲛青
粽叶绿
灰绿
鹤灰
淡松烟
暗海水绿
棕榈绿
米色
淡肉色
麦芽糖黄
琥珀黄
甘草黄
初熟杏黄
浅驼色
沙石黄
虎皮黄
土黄
百灵鸟灰
山鸡黄
龟背黄
苍黄
莱阳梨黄
蜴蜊绿
松鼠灰
橄榄灰
蟹壳绿
古铜绿
焦茶绿
粉白
落英淡粉
瓜瓤粉
蜜黄
金叶黄
金莺黄
鹿角棕
凋叶棕
玳瑁黄
软木黄
风帆黄
桂皮淡棕
猴毛灰
山鸡褐
驼色
茶褐
古铜褐
荷花白
玫瑰粉
橘橙
美人焦橙
润红
淡桃红
海螺橙
桃红
颊红
淡罂粟红
晨曦红
蟹壳红
金莲花橙
草莓红
龙睛鱼红
蜻蜓红
大红
柿红
榴花红
银朱
朱红
鲑鱼红
金黄
鹿皮褐
醉瓜肉
麂棕
淡银灰
淡赭
槟榔综
银灰
海鸥灰
淡咖啡
岩石棕
芒果棕
石板灰
珠母灰
丁香棕
咖啡
筍皮棕
燕颔红
玉粉红
金驼
铁棕
蛛网灰
淡可可棕
中红灰
淡土黄
淡豆沙
椰壳棕
淡铁灰
中灰驼
淡栗棕
可可棕
柞叶棕
野蔷薇红
菠萝红
藕荷
陶瓷红
晓灰
余烬红
火砖红
火泥棕
绀红
橡树棕
海报灰
玫瑰灰
火山棕
豆沙
淡米粉
初桃粉红
介壳淡粉红
淡藏花红
瓜瓤红
芙蓉红
莓酱红
法螺红
落霞红
淡玫瑰灰
蟹蝥红
火岩棕
赭石
暗驼棕
酱棕
栗棕
洋水仙红
谷鞘红
苹果红
铁水红
桂红
极光红
粉红
舌红
曲红
红汞红
淡绯
无花果红
榴子红
胭脂红
合欢红
春梅红
香叶红
珊瑚红
萝卜红
淡茜红
艳红
淡菽红
鱼鳃红
樱桃红
淡蕊香红
石竹红
草茉莉红
茶花红
枸枢红
秋海棠红
丽春红
夕阳红
鹤顶红
鹅血石红
覆盆子红
貂紫
暗玉紫
栗紫
葡萄酱紫
牡丹粉红
山茶红
海棠红
玉红
高粱红
满江红
枣红
葡萄紫
酱紫
淡曙红
唐菖蒲红
鹅冠红
莓红
枫叶红
苋菜红
烟红
暗紫苑红
殷红
猪肝紫
金鱼紫
草珠红
淡绛红
品红
凤仙花红
粉团花红
夹竹桃红
榲桲红
姜红
莲瓣红
水红
报春红
月季红
豇豆红
霞光红
松叶牡丹红
喜蛋红
鼠鼻红
尖晶玉红
山黎豆红
锦葵红
鼠背灰
甘蔗紫
石竹紫
苍蝇灰
卵石紫
李紫
茄皮紫
吊钟花红
兔眼红
紫荆红
菜头紫
鹞冠紫
葡萄酒红
磨石紫
檀紫
火鹅紫
墨紫
晶红
扁豆花红
白芨红
嫩菱红
菠根红
酢酱草红
洋葱紫
海象紫
绀紫
古铜紫
石蕊红
芍药耕红
藏花红
初荷红
马鞭草紫
丁香淡紫
丹紫红
玫瑰红
淡牵牛紫
凤信紫
萝兰紫
玫瑰紫
藤萝紫
槿紫
蕈紫
桔梗紫
魏紫
芝兰紫
菱锰红
龙须红
蓟粉红
电气石红
樱草紫
芦穗灰
隐红灰
苋菜紫
芦灰
暮云灰
斑鸠灰
淡藤萝紫
淡青紫
青蛤壳紫
豆蔻紫
扁豆紫
芥花紫
青莲
芓紫
葛巾紫
牵牛紫
紫灰
龙睛鱼紫
荸荠紫
古鼎灰
乌梅紫
深牵牛紫
银白
芡食白
远山紫
淡蓝紫
山梗紫
螺甸紫
玛瑙灰
野菊紫
满天星紫
锌灰
野葡萄紫
剑锋紫
龙葵紫
暗龙胆紫
晶石紫
暗蓝紫
景泰蓝
尼罗蓝
远天蓝
星蓝
羽扇豆蓝
花青
睛蓝
虹蓝
湖水蓝
秋波蓝
涧石蓝
潮蓝
群青
霁青
碧青
宝石蓝
天蓝
柏林蓝
海青
钴蓝
鸢尾蓝
牵牛花蓝
飞燕草蓝
品蓝
银鱼白
安安蓝
鱼尾灰
鲸鱼灰
海参灰
沙鱼灰
钢蓝
云水蓝
晴山蓝
靛青
大理石灰
海涛蓝
蝶翅蓝
海军蓝
水牛灰
牛角灰
燕颔蓝
云峰白
井天蓝
云山蓝
釉蓝
鸥蓝
搪磁蓝
月影白
星灰
淡蓝灰
鷃蓝
嫩灰
战舰灰
瓦罐灰
青灰
鸽蓝
钢青
暗蓝
月白
海天蓝
清水蓝
瀑布蓝
蔚蓝
孔雀蓝
甸子蓝
石绿
竹篁绿
粉绿
美蝶绿
毛绿
蔻梢绿
麦苗绿
蛙绿
铜绿
竹绿
蓝绿
穹灰
翠蓝
胆矾蓝
樫鸟蓝
闪蓝
冰山蓝
虾壳青
晚波蓝
蜻蜓蓝
玉鈫蓝
垩灰
夏云灰
苍蓝
黄昏灰
灰蓝
深灰蓝
玉簪绿
青矾绿
草原远绿
梧枝绿
浪花绿
海王绿
亚丁绿
镍灰
明灰
淡绿灰
飞泉绿
狼烟灰
绿灰
苍绿
深海绿
长石灰
苷蓝绿
莽丛绿
淡翠绿
明绿
田园绿
翠绿
淡绿
葱绿
孔雀绿
艾绿
蟾绿
宫殿绿
松霜绿
蛋白石绿
薄荷绿
瓦松绿
荷叶绿
田螺绿
白屈菜绿
河豚灰
蒽油绿
槲寄生绿
云杉绿
嫩菊绿
艾背绿
嘉陵水绿
玉髓绿
鲜绿
宝石绿
海沬绿
姚黄
橄榄石绿
水绿
芦苇绿
槐花黄绿
苹果绿
芽绿
蝶黄
橄榄黄绿
鹦鹉绿
油绿
象牙白
汉白玉
雪白
鱼肚白
珍珠灰
浅灰
铅灰
中灰
瓦灰
夜灰
雁灰
深灰