DeerOJ的前端框架介绍
Web文件夹下的结构
DeerOJ的前端框架参考了部分 Lavarel
框架,做到兼顾代码的可维护性和可阅读性。具体的维护目录文件结构如下:
注意到web文件夹下的 index.php
这是整个前端程序的 main
程序 ,当服务段收到请求后, 根据 .htaccess
文件指定使用 index.php
文件来生成网页,并把网页数据返回到前端进行渲染呈现。
App文件夹下的结构
网页的生成构造需要一些基本要素,web文件夹下的app文件夹中保存着大量的基本要素。app文件夹下的文件结构如下:
文件夹下包含一些子文件夹和文件,这里选取一些重要的文件来进行解析:
route.php文件
route.php
是整个前端程序的路由, index.php
会把前端发送的路由信息传递给route.php
,文件会根据发送的路由信息,结合 model
文件夹下的 Route.php
调用对应的php程序,从而来实现生成网页(HTML文件)的功能。具体解析如下:
路由模式定义
给出如下代码
这些模式定义了 URL 参数的格式。比如,username
只能包含字母、数字和下划线,长度在 1 到 20 之间;id
和 contest_id
是数字,长度在 1 到 10 之间;rand_str_id
是一个长度为 20 的字符串,由数字和大小写字母组成。
路由组定义
这个路由组限定了所有路由都在指定域名下可用。UOJConfig::$data['web']['main']['host']
获取的是配置中的主机名。
具体路由配置
这些路由使用 Route::any
方法配置,表示无论是 GET
还是 POST
请求,都会路由到对应的 PHP 文件。例如,请求根路径 /
会被路由到 index.php
处理,请求 /problems
会被路由到 problem_set.php
处理。
用户相关路由
这些路由处理用户登录、注册、忘记密码、重置密码,以及查看和修改用户信息等操作。
评测相关路由
这些路由处理与评测系统相关的操作,例如提交评测任务、同步评测客户端、下载提交记录、临时文件、题目和评测器等。
config.php文件
config.php
文件是 PHP 应用程序的配置文件,具体用于 Deer Online Judge (DeerOJ) 系统的设置。它定义了应用程序的基本信息、数据库连接、Web 服务器设置、安全配置、邮件配置、评测机配置以及一些开关设置。
基本信息 (profile)
部分参数解释如下:
oj-name
: 在线评测系统的全称。oj-name-short
: 在线评测系统的简称。administrator
: 系统管理员的用户名。admin-email
: 系统管理员的电子邮件地址。QQ-group
: QQ 群的联系方式,当前为空。ICP-license
: ICP 备案号,当前为空。
数据库配置 (database)
部分参数解释如下:
database
: 数据库名称。username
: 数据库用户名。password
: 数据库用户的密码。host
: 数据库服务器的地址。
Web 服务器配置 (web)
部分参数解释如下:
domain
: 网站的域名,当前为NULL
。main
: 主站点的协议、主机名和端口。blog
: 博客站点的协议、主机名和端口。主机名是通过UOJContext::httpHost()
获取的。
安全配置 (security)
部分参数解释如下:
user.client_salt
: 用户密码的盐值,用于增强密码的安全性。cookie.checksum_salt
: 用于校验 Cookie 的盐值数组。
邮件配置 (mail)
部分参数解释如下:
noreply
: 配置 no-reply 邮箱的用户名、密码、SMTP 服务器地址、加密方式和端口。
评测机配置 (judger)和开关设置 (switch)
部分参数解释如下:
socket.port
: 评测机连接的端口号。socket.password
: 评测机连接的密码。web-analytics
: 是否启用网站分析,当前为false
(不启用)。blog-domain-mode
: 博客域名模式,当前为3
。
model文件夹
model
文件夹下存储的是一些相关类的php文件,在HTML文件生成的时候,利用这些类能够高效地辅助文件与文件之间的调度转换。文件夹下的内容如下:
这里列举一些重要的类文件:
Route.php文件
前文中在实现 route.php
的路由调度过程中有出现使用类Route
的情况,实际上就是调用这里的类Route
的相关方法去实现的。这个 Route
类是一个简单的路由器,它可以用于处理基于 URI 和 HTTP 方法的请求路由。它支持添加、匹配和分组路由,以及定义参数模式。下面介绍一下部分重要属性和方法:
Route.php文件的部分属性
protected static $routes
: 存储所有注册的路由。protected static $patterns
: 存储所有自定义的参数模式。protected static $groupStack
: 存储当前的路由组属性。
match 方法
代码截图如下:
match
方法将指定的 HTTP 方法、URI 和处理动作添加到路由中。其中,array_map('strtoupper', (array)$methods)
将方法名转为大写,便于规范匹配。
any 方法
代码截图如下:
any
方法支持任意 HTTP 方法的路由,保证前端调用的时候能够保证所有的基本请求方式都能使用。
各种 HTTP 方法的快捷方式
代码截图如下:
将不同的 HTTP 方法打包在 Route
类中,方便实现调用。
group 方法和 getGroup 方法
代码截图如下:
在 group
方法中,定义一个路由组,将传入的属性和当前组的属性合并,然后执行回调函数。其中,array_pop(self::$groupStack)
在执行完回调函数后恢复上一个路由组的属性。
getGroup
方法则是用于获取当前的路由组属性。
addRoute 方法
代码截图如下:
该方法将一个新路由添加到 $routes 数组中,便于后续对路由的检查。
checkRoute 方法
代码截图如下:
该方法检查当前请求是否匹配某个路由,并匹配请求方法。同时,使用定义的参数模式替换 URI 模式中的占位符。然后匹配 URI 和域名,如果匹配成功,将匹配的参数存储到 $_GET
中。
DB.php 文件
在编写代码的过程中,如果直接调用php中与数据库相关的语句,可能会导致代码可阅读性差,结构混乱从而加大了系统的维护成本。故使用 DB.php
来统一管理与数据库相关的交互操作。DB
类是一个封装了 MySQL 数据库操作的静态类,用于简化数据库操作并提高代码的可读性和可维护性。它使用 PHP 的 mysqli
扩展来与 MySQL 数据库进行交互。下面介绍一下部分重要属性和方法:
初始化数据库连接
初始化Init
方法的具体代码如下:
该方法初始化与 MySQL 数据库的连接,并存储在全局变量 $uojMySQL
中。并且,使用 mysqli_connect
函数连接数据库,如果连接失败,输出错误信息并终止程序。
SQL 注入防护
escape
方法的具体代码如下:
该方法使用 mysqli_real_escape_string
函数对输入的字符串进行转义,以防止 SQL 注入攻击。
数据提取方法
fench
方法的具体代码如下:
该方法从查询结果集中获取一行作为关联数组或数字数组。
基本数据库操作
基本的增删改的实现,这里仅给出实现代码,代码如下:
数据选择方法
代码如下:
select
方法执行查询并返回结果。selectAll
方法返回查询结果中的所有行。selectFirst
方法返回查询结果中的第一行。selectCount
方法返回查询结果的行数。
检查表是否存在
checkTableExists
方法的具体代码如下:
该方法通过尝试查询表中的一行数据来检查表是否存在。
行数和受影响行数
num_rows
和 affected_rows
方法的具体代码实现如下:
其中,num_rows
方法返回结果集中行的数量,affected_rows
方法返回上一次查询中受影响的行数。
libs文件夹
在 index.php
文件初始化的过程中,需要提前准备好一些类和方法,这些类和方法的初始化是调用 libs
文件夹下的大多数 php 文件来实现的。libs文件夹下的文件如下:
其中,uoj-lib.php
文件是进行所有相关初始化的主文件,上级的 index.php
文件会直接调用该文件展开初始化的工作。libs
文件夹下的其他文件和 model
文件夹下的所有类都是通过 uoj-lib.php
文件来完成初始化工作的。
controller文件夹
该文件夹是用来存放控制器文件的,也就是构建HTML文件的主要文件。controller文件夹下的文件如下:
其中, judge
文件夹下的文件负责处理OJ的判题逻辑。比如,评判提交的代码是否AC/WA/TLE/MLE等。
这里以当前文件夹下的 index.php
为例来描述主页的生成。
生成主页的 index.php
DeerOJ的工作原理
服务端收到请求后,会运行 web
文件夹下的 index.php
文件(由同目录下的.htaccess
决定)
index.php
文件的内容截图如下:
index.php
会加载所需的函数库和类库,具体如下:
require $_SERVER['DOCUMENT_ROOT'] . '/app/libs/uoj-lib.php';
该句是调用 /app/libs/
下的php文件,用来调用一些类和方法。
uoj-lib.php
的完整代码如下:
注意到 uoj-lib.php
下有如下代码段:
function requirePHPLib($name) { // uoj php lib
require $_SERVER['DOCUMENT_ROOT'].'/app/libs/uoj-'.$name.'-lib.php';
}
requirePHPLib('validate');
requirePHPLib('query');
requirePHPLib('rand');
requirePHPLib('utility');
requirePHPLib('security');
requirePHPLib('contest');
requirePHPLib('html');
可知在代码中调用了同类文件夹的其他 lib.php
文件,这些文件的具体功能大致如下:
validate
:验证lib,用来验证某些信息是否合法,比如账号密码等query
:查询lib,用来调用后台数据库查询相关信息,结合model
文件夹下的DB.php
类使用rand
:随机lib,用来生成随机数据(随机数、随机文件名等)utility
:utility—lib,用来实现一些在web上的功能,比如配置合并,字符串处理等security
:安全lib,用来维护账号的安全,比如密码处理,token等contest
:比赛lib,用来处理比赛相关的方法集,比如计算rating,更新人数等html
:HTMLlib,用来接收网页相关信息、输出html代码等
注意到 uoj-lib.php
下有如下代码段:
Session::init();
UOJTime::init();
DB::init();
Auth::init();
if (isset($_GET['locale'])) {
UOJLocale::setLocale($_GET['locale']);
}
UOJLocale::requireModule('basic');
此段均是对 /app/models/
下的部分类进行初始化,其中:
Session
:初始化会话层相关的信息UOJTime
:初始化时间信息DB
:初始化和数据库的连接Auth
:初始化登录用户的信息UOJLocale
:中英文相关的配置信息
回到web
文件夹下的 index.php
文件,注意到如下代码:
require UOJContext::documentRoot().'/app/route.php';
其中,UOJContext
类是用来管理和获取与当前Web请求相关的上下文信息,例如获取服务器环境信息,blog处理等。该句是调用路由文件。路由文件(route file) 去给请求中的网址匹配用于生成响应报文的 PHP 代码,结合 models
下的 Route
使用。 Route
定义了一个简单的路由系统,允许通过静态方法定义和管理路由。通过 match、get、post 等方法,可以方便地为不同的HTTP请求方法定义路由。通过 group 方法,可以对一组路由应用共同的属性。dispatch 方法用于遍历和匹配请求的路由,并执行相应的操作。整个路由系统利用静态属性和方法来管理路由,使其在应用中便于使用和扩展。
index.php
文件中,注意到如下代码:
include UOJContext::documentRoot().'/app/controllers'.call_user_func(function() {
$route = Route::dispatch();
$q_pos = strpos($route['action'], '?');
if ($q_pos === false) {
$path = $route['action'];
} else {
parse_str(substr($route['action'], $q_pos + 1), $vars);
$_GET += $vars;
$path = substr($route['action'], 0, $q_pos);
}
if (isset($route['onload'])) {
call_user_func($route['onload']);
}
return $path;
});
这里是动态调用库文件,主要是根据用户的网页请求来调用。比如申请主页,会调用 controllers
的 index.php
文件。