学生客户端的技术选型则有点变化。为了防作弊和整合编程题调试功能,学生客户端必须为 PC 端软件, 而不能向教师客户端一样采用浏览器平台。鉴于编程题为 Python 编程,PC 端实现最好也采用 Python 的技术栈。 因而基础框架选择的是 PyQt5,本来计划整个客户端都使用 PyQt 构建,但是中途发觉 Python 语言实在是不适合描述 UI。 Qt 还有一个 DSL 叫 QML,更适合描述 UI,但是这样一来只会让设计变得更加复杂。 最终方案是只使用 PyQt 来作为 PC 端平台,然后在其上用 Qt WebEngine 搭建浏览器平台, 这样就能使用已有的 vue-admin-template 构建主体应用。 而 Python 编程题的在线调试功能不能一并交给 Vue 应用,因为我们没有找到能在浏览器运行的 Javascript 实现的 Python 解释器, 所以只好让 Python 平台程序另外启动一个本地服务端,以处理调试请求。 我们没有采用 Qt WebChannel 的通信方案, 是因为它没有本地 Client/Server 的方案常用。 而为了避免直接向用户暴露 Python 源文件,更为了方便没有安装 Python 解释器的用户能直接使用客户端, 项目最终使用 PyInstaller 来作打包。

学生客户端的源码文件夹结构如下:

.
├── pyqt                Python 实现的程序平台
│   ├── Pipfile           pipenv 的依赖文件
│   ├── Pipfile.lock      自动生成的 pipenv 依赖文件
│   ├── code_runner.py    用于编程题调试的 Python Code Runner
│   ├── config.py         配置文件
│   ├── local_server.py   用于编程题调试的本地服务器
│   ├── main.py           程序入口
│   └── main.spec         PyInstaller 的配置文件
└── vue                 Vue 部分
    ├── LICENSE
    ├── babel.config.js
    ├── jest.config.js
    ├── jsconfig.json
    ├── package.json      Vue 的项目依赖
    ├── postcss.config.js
    ├── public            打包需要的静态文件
    │   ├── favicon.ico     网页图标
    │   └── index.html      单页 html
    ├── src               真正的源代码
    │   ├── App.vue
    │   ├── main.js
    │   ├── permission.js   权限管理 
    │   ├── settings.js
    │   ├── api             单纯的 JS API 请求库
    │   │   └── index.js
    │   ├── assets          应用中引用的静态文件
    │   │   ├── 404_images
    │   │   │   ├── 404.png
    │   │   │   └── 404_cloud.png
    │   │   ├── logo.png
    │   │   └── tip_zoom.png
    │   ├── components      通用组件
    │   │   ├── BackToTop
    │   │   │   └── index.vue
    │   │   └── SvgIcon
    │   │       └── index.vue
    │   ├── icons           应用中引用的图标文件
    │   │   ├── avatar.gif
    │   │   ├── index.js
    │   │   ├── svg
    │   │   │   ├── exam.svg
    │   │   │   ├── id.svg
    │   │   │   ├── server.svg
    │   │   │   └── user.svg
    │   │   └── svgo.yml
    │   ├── layout          界面的整体布局
    │   │   ├── components
    │   │   │   ├── AppMain.vue
    │   │   │   └── index.js
    │   │   ├── index.vue
    │   │   └── mixin
    │   │       └── ResizeHandler.js
    │   ├── router          页面路由
    │   │   └── index.js
    │   ├── store           Vuex
    │   │   ├── getters.js
    │   │   ├── index.js
    │   │   └── modules
    │   │       ├── settings.js
    │   │       └── user.js
    │   ├── styles          通用的样式文件
    │   │   ├── btn.scss
    │   │   ├── element-ui.scss
    │   │   ├── element-variables.scss
    │   │   ├── index.scss
    │   │   ├── mixin.scss
    │   │   ├── sidebar.scss
    │   │   ├── transition.scss
    │   │   └── variables.scss
    │   ├── utils           辅助工具库
    │   │   ├── cookie.js
    │   │   ├── get-page-title.js
    │   │   ├── index.js
    │   │   ├── random.js
    │   │   ├── request.js
    │   │   ├── scroll-to.js
    │   │   ├── time.js
    │   │   └── validate.js
    │   └── views           各个页面的界面主体
    │       ├── 404.vue
    │       ├── home          主界面
    │       │   └── index.vue
    │       └── login         登录界面
    │           └── index.vue
    └── vue.config.js

学生客户端的 Vue 应用相对教师端更简单,只有登录界面与答题的主界面。 主界面使用一个 El-Tabs 承载所有的题目, 不同的题型被放在不同的 Tab。主界面在被创建时获取题目至 questions 变量, 而学生的作答对应的 model 为 answers 变量。应用每隔几分钟就将作答保存到服务端,这包括两个 API 调用: PUT exams/my_answers 会将作答提交至数据库,而 PUT cache 会将 answers 变量直接序列化成字符串保存到服务端的内存 Cache 中去; 后者是方便学生中途退出程序重进时,通过 GET cache 快速还原 answers 变量的状态。 设计的检查进度功能是通过一个按钮触发的,之所以不设计成一个常驻的实时展示进度的组件,是因为 answers 变量结构较复杂, 不适合通过 model 监听。

Python 部分主要是启动 Qt WebEngine 和本地服务端。 本地服务端选型是 Flask,在 http://localhost:2998 监听, 处理 PUT code 请求与静态文件请求。Flask 在单独的进程运行,与 PyQt 并行执行。 在处理 PUT code 请求时,程序使用 PyInstaller 打包好的单独的 Python 解释器执行用户代码,并按题目要求处理好输入输出与异常。 值得一提的是,编程题的对错就是在此时判定的,客户端判分必然是脆弱的、不安全的,但这也是受限于实际情境中服务器不足的性能的结果, 在重构时也许会考虑作出改变。 在 v0.5.3 之后的版本中,客户端仅仅计算程序的输出,而答案的比较、判分都移动到了服务端去做。

CC BY 4.0 Licensed | Copyright © 2022-present Euan Mactavish            Last Updated 2022-07-12 17:24

results matching ""

    No results matching ""