uoj概述

ighshssjjsjs發表於2024-05-30

一、基本工作原理
UOJ 主要由兩部分組成:網頁端和測評端。顧名思義,網頁端就是用來透過網頁與使用者互動的,測評端則是在使用者發出測評請求時負責測評並將結果傳送給網頁端的。

網頁端
什麼是網頁端?顧名思義,就是管網頁的部分。網頁端又分為網頁前端和網頁後端。所謂網頁前端,就是你開啟瀏覽器能看到的那部分。不過瀏覽器是如何知道自己要顯示什麼東西的呢?這就是因為伺服器上還有網頁後端,會以 HTML 程式碼的形式告訴瀏覽器它要顯示些什麼。
下面我們來理一下你從輸入網址到看到網頁的全過程。首先,你輸入了 UOJ 的網址,然後瀏覽器就會根據你的網址去找到對應的伺服器,併發一個訊息給該伺服器。這種訊息稱為 HTTP 請求(HTTP Request)。當然了,這取決於你的網址是以什麼開頭的。如果是 https 開頭那麼就是個加了密的 HTTP 請求,稱為 HTTPS 請求。一個 HTTP 請求報文會包含很多很多資訊,例如你輸入的網址啦,例如你的 IP 啦。

伺服器收到 HTTP 請求之後,會根據請求的內容進行相應的處理,並生成一個 HTTP 響應(HTTP Response),然後發回給你的瀏覽器。HTTP 響應報文也會包括很多資訊。比如會包括狀態碼,正常情況下是 200,但如果對應的網址上沒有東西可顯示,那麼會返回一個喜聞樂見的 404。當然最重要的是 HTTP 響應報文裡面會包含一份 HTML 程式碼,這份程式碼會指示你的瀏覽器如何渲染出一個完整的網頁。只要你在瀏覽器裡滑鼠右鍵,點選審查元素,就能看到這份 HTML 程式碼了。實際上,HTML 程式碼中可能還會內嵌有其他的元素,例如內嵌一些 JavaScript 程式碼就可以做一些動態的頁面效果,內嵌一些 CSS 程式碼就可以更好的控制網頁的排版。

總而言之,想要理解網頁端,就得理解兩部分:第一,瀏覽器是如何把 HTML 程式碼變成你所看到的網頁的;第二,伺服器是如何根據 HTTP 請求報文生成 HTML 程式碼的。前者屬於網頁前端的範疇,而後者屬於網頁後端的範疇。

測評端
光有網頁端是不夠的,因為如果使用者提交了一份程式碼,UOJ 就得負責評測,而這往往不是幾秒鐘內能完成的,所以不能用 HTTP 響應的方式快速返回結果。因此,UOJ 的網頁端伺服器得把該測評請求發給另一個被我們稱之為測評端的伺服器,讓它測評之後將測評結果發回,再透過網頁端告知使用者。
UOJ 官網的網頁端和測評端伺服器是分別部署在兩臺機器上的,不過為了方便起見,在我們釋出的這版 UOJ 裡網頁端和測評端是放在同一臺機器上的。

測評端又分為負責通訊的部分和(真正)負責測評的部分。顧名思義,負責通訊的程式碼控制的是和網頁端伺服器的互動,而負責測評的程式碼則是給選手交過來的檔案打分的,最後負責通訊的部分會將測評結果發回網頁端。

二、網頁端程式碼簡介
下面我們稍微展開講一講 UOJ 網頁前端和網頁後端分別是如何實現的。

網頁後端
UOJ 使用了一款開源的網頁伺服器軟體,叫作 Apache。Apache 支援使用 PHP 語言來實現網頁後端。進入 UOJ 的 docker,網頁後端的程式碼就位於 /opt/uoj/web/ 目錄下。Apache 收到一個 HTTP 請求之後,會執行 index.php(這一行為其實是由同目錄下的 .htaccess 指定的)。index.php 會載入所需的函式庫和類庫,然後根據路由檔案(route file) 去給請求中的網址匹配用於生成響應報文的 PHP 程式碼。所謂路由檔案其實也是 PHP 程式碼,只不過這種檔案記錄了一個從網址到程式碼檔案路徑的對映。控制主站路由的是 app/route.php,控制部落格路由的是 app/controllers/subdomain/blog/route.php。
我們稱路由檔案中被對映到的程式碼為控制器(controller),位於 app/controllers 目錄下。index.php 給請求中的網址匹配到對應的控制器後,控制器會根據使用者的 HTTP 請求報文的具體內容生成一個 HTTP 響應報文,交由 Apache 負責發回給作出 HTTP 請求的瀏覽器。

早期 UOJ 的網頁後端是我們一點一點自己寫的,架構上沒有參考任何的現有開源專案。後來我們發現有些架構的設計思路理不太清楚,於是去學習了一下著名的 Laravel 框架。所以程式碼架構實則是介於早期 UOJ 架構和 Laravel 之間的一種架構,沒有像早期架構那麼亂,也沒用像專業的 Laravel 那樣包裝得很深,希望這樣能兼顧程式碼的可維護性和可閱讀性吧。

資料庫
資料庫是網頁後端的一部分,但又是較為獨立的一部分。通常我們在 OI 中會使用陣列來儲存資料,但一旦你把程式掐了,或者電腦關機了,陣列裡的內容就丟失了。這是因為陣列通常是儲存在記憶體裡的,而非機械硬碟、固態硬碟等可持久儲存的介質上。那麼如何把資料儲存在硬碟上呢?你可能已經開始摩拳擦掌準備寫個 B 樹了,但是且慢 —— 如今有很多資料庫軟體已經幫你寫好了。
UOJ 使用的是 MySQL 資料庫。在 UOJ 的後端 PHP 程式碼裡,你可以透過訪問 DB 這個靜態類來跟資料庫進行互動。互動使用的是 SQL 語言,一種專門為資料庫中資料的查詢、修改、插入、刪除而設計的語言,你可能需要稍微學習一番這種特殊的語言。DB 是對 SQL 的一種簡單包裝,請參照現有程式碼呼叫 DB 的方式來更好的理解其用法。

網頁前端
大部分的網頁前端程式碼實際上是由 PHP 一行一行輸出出來的。負責這種輸出的程式碼一部分位於各個控制器程式碼中,一部分 app/views 目錄下。理想情況下,如果一段 PHP 程式碼能生成一長段 HTML 程式碼,且生成的過程不包含任何控制邏輯或者資料庫查詢,那麼該 PHP 程式碼就應該放在 app/views 裡作為一種檢視(view);否則就應該放在 app/controllers 下作為一種控制器。例如,每個頁面都會包含的頁頭和頁尾就寫在了 app/views/page-header.php 和 app/views/page-footer.php 這兩個檢視檔案裡。當然了,嚴格遵守這樣的規則會讓一些簡單的程式碼變得結構很複雜,所以目前的 UOJ 程式碼並沒有嚴格遵守這一點。
其實,並非所有前端程式碼都是 PHP 一行行輸出出來的。HTML 程式碼中通常會內嵌一些 CSS 和 JavaScript 來控制頁面排版和製造一些動態頁面效果,而這些部分往往是跟頁面具體內容獨立的。所以有很多優秀的前端框架,例如 Bootstrap。UOJ 目前使用的是 Bootstrap 3。所以,要想完全理解前端的程式碼,你不僅需要閱讀對應的控制器和檢視的內容,還要學習一下 Bootstrap 的使用方式。如果你尚未接觸過前端開發,不妨先找點前端開發教程,試著做一點不需要 PHP 的靜態小網站,然後再回過頭來閱讀 UOJ 的程式碼。

重要檔案目錄
網頁端的程式碼和相關檔案主要在 /opt/uoj/web/ 目錄下。下面列出了一些重要的檔案和子目錄:
app/:UOJ 主要的後端 PHP 程式碼目錄
controllers/:存放控制器檔案的目錄
locale/:存放頁面上的文字在不同語言下的翻譯的目錄
models/:UOJ 執行所需的一些 PHP 類
storage/:儲存一些檔案資料的目錄
submissions/:存放使用者提交的測評請求中附帶的檔案的目錄
tmp/:存放臨時檔案的目錄
views/:存放檢視檔案的目錄
route.php:主站路由檔案
uoj-*-lib.php:一些 UOJ 執行所需的函式庫檔案
vendor/:一些 UOJ 使用的第三方 PHP 程式碼庫,由 Composer 管理。
public/:一些可以不經過 PHP 直接訪問的資源
css/:存放部分 CSS 程式碼檔案的目錄
fonts/:存放字型檔案的目錄
js/:存放部分 JavaScript 程式碼檔案的目錄
libs/:一些 UOJ 使用的前端庫,例如 Bootstrap,jQuery 等等
pictures/:存放部分圖片檔案的目錄