首頁 > php框架 > ThinkPHP > ThinkPHP框架執行流程(附腦圖)

ThinkPHP框架執行流程(附腦圖)

咔咔
發布: 2020-11-06 16:21:33
原創
5274 人瀏覽過

本文主要介紹框架的執行流程

##前言

如果不清楚框架是怎麼執行的,那麼看在多的程式碼都是只是認識程式碼而已,閱讀原始碼是為了學習其框架的設計想法和程式碼模式。

而執行流程則是將我們學習的東西串連在一起,以便更好地理解。咔咔也會給大家把執行流程用心智圖的方式畫出來。

只要大家在本文學到一點點的知識點,咔咔也是心滿意足的。

這個流程圖只是針對initialize的執行過程,其餘的執行過程後期會進行補充,都是以腦圖的形式呈現給大家的。

ThinkPHP框架執行流程(附腦圖)

執行流程圖

#一、框架執行流程之初始化應用的資料設置

這裡的內容跟容器的內容有點重複,因為執行流程是從入口檔案開始的,最後也是透過容器執行的。

ThinkPHP框架執行流程(附腦圖)

入口檔案

然後就會進入到檔案thinkphp/library/think/App.php的run方法,在這個方法主要就是下圖框出來的地方,執行的initialize方法。

ThinkPHP框架執行流程(附腦圖)

來到initialize這個方法,先看上半部。

  • microtime(true);傳回的是unix的微秒數
  • memory_get_usage回傳的是分配給PHP的記憶體量,單位為位元組
  • 在接下來就是對框架的幾個路徑進行設定
  • static::setInstance($this);這裡是將app這個實例設定為容器實例
  • #$this->instance('app' , $this);這個在之前容器章節就提到了,就是為了把app這個類別綁定到容器裡邊去,也就是註冊樹模式。


ThinkPHP框架執行流程(附腦圖)

這裡有一個小小的問題點可以給大家提出來,在初始化應用的這個方法裡邊存在這樣一行程式碼。

有沒有小夥伴對這個$this->env和下邊的$this->config這兩個呼叫有疑惑。

如果你有疑惑那就跟著喀喀一起來看,沒疑惑的就可以繼續往下看了。

App這個類別是繼承的容器類,那麼這個env和config不論是在app還是container類別中都是沒有這兩個屬性的。

那麼怎麼就可以直接呼叫呢!而且程式碼追蹤都會追蹤到env類別和container類別中。

要知道這個源頭就需要我們去在大致的看一遍container類別的程式碼。

ThinkPHP框架執行流程(附腦圖)

解決疑惑,為什麼可以這樣使用

#經過一番苦讀之後,可以看到下圖的幾行程式碼。這幾行程式碼全部使用的是魔術方法。

當存取env類別不存在的時候就會去執行make方法。

make這個方法在容器那一章進行的細的不能再細的解讀了。

這個make方法最後會回傳一個類別的實例,也會儲存到容器裡邊。

ThinkPHP框架執行流程(附腦圖)

這裡只放一個make方法的程式碼,如果有不會的可以去看之前的文章。

ThinkPHP框架執行流程(附腦圖)

最後就是載入一系列的數據,載入詳情請看前言的心智圖。

ThinkPHP框架執行流程(附腦圖)

執行載入

#二、如何檢視一個方法都在哪裡執行了

在閱讀原始碼的過程中,有一個很難把控的問題就是一個方法在不同的地方進行了調用,但是咱們確一時半會根本不知道都在哪裡調用了。

這裡用init方法來做一個示範。

init方法是初始化應用程式或模組的方法,但這裡的module參數確實一個空值。

ThinkPHP框架執行流程(附腦圖)先做一個斷點查看一下相關的資料資訊。

列印的結果就是空,這就是一些新學習的伙伴會犯的一個錯誤,因為這個方法不可能只呼叫一次的。

如果初始化模組都是空那麼這個方法就沒有存在的必要了。

ThinkPHP框架執行流程(附腦圖)1ThinkPHP框架執行流程(附腦圖)那麼正確的斷點方式應該是這個樣子的。

1ThinkPHP框架執行流程(附腦圖)1ThinkPHP框架執行流程(附腦圖)此時就會有一個問題,這個init方法明顯是被呼叫了兩個次的,那麼另一個呼叫的地方是在哪裡呢!

如果在不知道新的技巧之前,就會進行一系列的斷點列印,看在哪裡進行了執行,例如在這個init的上層去列印。

也就是在initialize那個方法裡邊去打印做斷點,但是這樣很是麻煩的,而且很有可能浪費了大量的時間還是找不到正確的地方。

小技巧之debug_backtrace()

這個方法會產生一條回溯追踪,會顯示出一個方法所有的呼叫位置。

使用方式就是如下圖,只要把debug_backtrace這個方法印出來即可。

1ThinkPHP框架執行流程(附腦圖)1ThinkPHP框架執行流程(附腦圖)1ThinkPHP框架執行流程(附腦圖)

根據所得到的資料訊息,就可以非常快的進行定位。

第一次就是在app類別的215行。

1ThinkPHP框架執行流程(附腦圖)


第一次呼叫init的地方

第二次是在thinkphp/library/think/route/dispatch/Module.php類的60行

1ThinkPHP框架執行流程(附腦圖)

第二次呼叫地方

可以在這裡做一個列印,看看這個module是否為index

1ThinkPHP框架執行流程(附腦圖)ThinkPHP框架執行流程(附腦圖)所以說有了這個方法就可以非常快速地定位呼叫位置。

三、框架執行流程之初始化應用init分析

#上文給大家提供了一個小技巧debug_backtrace實戰示範如何檢視一個方法都在哪裡執行的。

而案例也是使用的init這個方法來示範的,因為接下來就是要對init這個方法進行深入的了解。

在init方法裡邊主要做的事情在上邊的腦圖已經描述的很清楚了。

  • 從一開始就對模組的定位,就是在第二節中的對init方法的調用,會傳入對應的模組
  • 加載app目錄下的tags文件,在tags文件裡邊就是行為擴充定義的文件。在先前門面的文章中定義鉤子執行就在這個檔案中設定的。
  • 載入common文件,也就是公開文件,所以說公共文件就是在這裡進行載入的。
  • 載入助手函式檔案helper,在助手函式裡邊有一個大家特別熟悉的一個方法,那就是dump。這就是為什麼在有的地方使用dump會報錯的原因。
  • 載入中間件文件,這裡的直接給的是直接載入app目錄下的中間件文件,但是在框架中我們需要在定義一個目錄為http,在這個目錄下定義中間件檔案。
  • 註冊服務的容器物件實例,這裡註冊就使用的是容器類別中的bindTo方法進行綁定註冊的。
  • 讀取設定文件,這段在設定檔載入那一節中已經進行深入的說明了, 這裡就不提了。設定檔會讀取兩個地方一個是第一步模組下的config文件,另一個就是config目錄下的設定檔。
  • 設定模組路徑,會把第一步取得到的模組進行env環境變數配置裡邊
  • 最後一步就是對容器中的物件實例進行設定更新,具體更新了什麼在後文中給大家詳細說來。
<span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">    <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>     * 初始化应用或模块<br>     * <span class="hljs-doctag" style="color: #c678dd; line-height: 26px;">@access</span> public<br>     * <span class="hljs-doctag" style="color: #c678dd; line-height: 26px;">@param</span>  string $module 模块名<br>     * <span class="hljs-doctag" style="color: #c678dd; line-height: 26px;">@return</span> void<br>     */</span><br>    <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">public</span> <span class="hljs-function" style="line-height: 26px;"><span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">function</span> <span class="hljs-title" style="color: #61aeee; line-height: 26px;">init</span><span class="hljs-params" style="line-height: 26px;">($module = <span class="hljs-string" style="color: #98c379; line-height: 26px;">''</span>)</span><br>    </span>{<br>        <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 定位模块目录</span><br>        $module = $module ? $module . DIRECTORY_SEPARATOR : <span class="hljs-string" style="color: #98c379; line-height: 26px;">''</span>;<br>        <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>         * 第一次:D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\<br>         * 第二次:D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\index\<br>         */</span><br>        $path   = <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->appPath . $module;<br><br>        <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 加载初始化文件</span><br>        <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_file($path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>)) {<br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span> $path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>;<br>        } <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">elseif</span> (is_file(<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->runtimePath . $module . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>)) {<br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span> <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->runtimePath . $module . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>;<br>        } <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">else</span> {<br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 加载行为扩展文件</span><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_file($path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'tags.php'</span>)) {<br>                $tags = <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span> $path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'tags.php'</span>;<br>                <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_array($tags)) {<br>                    <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->hook->import($tags);<br>                }<br>            }<br><br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 加载公共文件</span><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_file($path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'common.php'</span>)) {<br>                <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include_once</span> $path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'common.php'</span>;<br>            }<br><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (<span class="hljs-string" style="color: #98c379; line-height: 26px;">''</span> == $module) {<br>                <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 加载系统助手函数</span><br>                <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span> <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->thinkPath . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'helper.php'</span>;<br>            }<br><br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 加载中间件</span><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_file($path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'middleware.php'</span>)) {<br>                $middleware = <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span> $path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'middleware.php'</span>;<br>                <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_array($middleware)) {<br>                    <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->middleware->import($middleware);<br>                }<br>            }<br><br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 注册服务的容器对象实例</span><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_file($path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'provider.php'</span>)) {<br>                $provider = <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span> $path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'provider.php'</span>;<br>                <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_array($provider)) {<br>                    <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->bindTo($provider);<br>                }<br>            }<br><br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>             * $path : "D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\"<br>             *          "D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\index\"<br>             */</span><br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 自动读取配置文件</span><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (is_dir($path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'config'</span>)) {<br>                $dir = $path . <span class="hljs-string" style="color: #98c379; line-height: 26px;">'config'</span> . DIRECTORY_SEPARATOR;<br>            } <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">elseif</span> (is_dir(<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->configPath . $module)) {<br>                <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\config\</span><br>                $dir = <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->configPath . $module;<br>            }<br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// scandir:以升序的方式读取目录中的文件</span><br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 返回就是config目录中的所有文件</span><br>            $files = <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">isset</span>($dir) ? scandir($dir) : [];<br><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">foreach</span> ($files <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">as</span> $file) {<br>                <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>                 * $this->configExt:配置文件的后缀<br>                 * pathinfo返回的是文件后缀,关于pathinfo共有三个可选的参数PATHINFO_DIRNAME、PATHINFO_BASENAME、PATHINFO_EXTENSION,分别为只返回文件名,文件目录名,文件扩展<br>                 */</span><br>                <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> (<span class="hljs-string" style="color: #98c379; line-height: 26px;">'.'</span> . pathinfo($file, PATHINFO_EXTENSION) === <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->configExt) {<br>                    <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>                     * 俩个参数分别为<br>                     * 1.目录+config目录下的文件<br>                     * 2.config目录下文件名<br>                     */</span><br>                    <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME));<br>                }<br>            }<br>        }<br><br>        <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->setModulePath($path);<br><br>        <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span> ($module) {<br>            <span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">// 对容器中的对象实例进行配置更新</span><br>            <span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->containerConfigUpdate($module);<br>        }<br>    }<br></code>
登入後複製

這裡附帶一份程式碼,可以對著程式碼看上邊的執行流程,對每一步都做了簡單的說明。

喀喀噠個人見解對原始碼進行最佳化

在設定模組的這步驟程式碼喀喀感覺不是很嚴謹,因為init方法會在兩個地方進行執行。

第一次的模組為空,這塊程式碼執行是沒有任何意義的。

下面在容器的物件實例進行配置更新時進行了一次判斷,判斷模組的這個參數是否為空,如果不為空才會執行。

那麼同樣的道理,咔咔感覺在設定模組路徑這塊也應該在這個判斷裡邊。

雖然說第二次執行會把第一次的結果覆蓋掉,但是咔咔感覺下圖這樣使用才會更好。

2ThinkPHP框架執行流程(附腦圖)


咔咔對原始碼修改建議

#四、對容器中的物件實例進行更新配置

在上一節這裡就是最後的內容,那這個對實例進行更新配置,到底更新了什麼,怎麼更新沒有說明。

在這一小節就會做出說明,同樣可以配合著前言的心智圖看。

  • 先會把config目錄下的所有設定資訊全部取得出來
  • 從app設定檔中將註冊異常處理類別
  • 第三大塊是把第一步取得出來的所有設定資訊給對應的類別進行註冊配置。
  • 第四步就是在把模組確定下來之後載入對應的語言包,語言包功能就可以實現多語言功能,之前咔咔寫過一篇文章實現多語言功能,如果有興趣的可以去查看。
  • 最後一步就是根據app設定檔中的三個屬性進行快取的處理

在這一節中咔咔感覺最重要的就是下圖的內容了。

2ThinkPHP框架執行流程(附腦圖)我們可以隨意追蹤一到兩個方法查看一下那邊到底執行了什麼方法。

追蹤方法Db::init()

追蹤方法過來後可以看到就是對Db類別中的config屬性進行賦值,把database中的值賦值給Db類別中的config屬性。

2ThinkPHP框架執行流程(附腦圖)追蹤方法$this->middleware->setConfig()

來到中間件這個類別裡邊,可以看到就是把本類的配置和傳遞過來的參數類別進行合併,同樣也是進行config屬性的賦值。

跟上邊案例的Db類別的init方法實現的效果是一致的。

這裡在提一嘴就是在對容器中的物件實例進行更新配置這一幅圖中可以看到紫色部分是在本類別中沒有引用的。

那麼這是怎麼可以進行執行的呢!是因為App類別繼承了容器類,​​容器類別中有四個魔術方法,其中有一個__get方法,就是在取得不存在的屬性時會執行那個方法。

在魔術方法__get方法中執行了一個make方法,這個make方法說了好多次了,這個方法最終會回傳一個應用的實例,然後用這個實例呼叫對應實例類別的方法。

這一塊一定要理解好,閱讀原始碼就是這個樣子,我們需要對一切未知的進行的解決,只有這樣才能提高我們的程式設計能力和想法。

2ThinkPHP框架執行流程(附腦圖)


中間件的設定配置

五、淺談調試模式以及程式碼冗餘

本節會對偵錯模式做出簡單的說明,並且會對框架程式碼冗餘情況進行簡單的提出。

沒有人寫的程式碼是沒有漏洞的,如果有那就是你還沒達到一定的造詣。

調試模式

在第一節只提到了initialize方法的上半部分,因為在這一節之前聊的都是關於應用初始化init的內容。

接下來會對這一塊的內容做簡單的說明。

  • 從app設定檔中取得到app_debug的設定項目
  • #給環境變數設定debug等級
  • 當框架中的debug是關閉狀態時會執行ini_set這個方法,這個方法是為一個配置選項進行賦值。

接下來的內容估計不是很好理解,都是平常在工作中根本使用不到的。

  • ob_get_level:傳回輸出緩衝機制的巢狀級別,那怎麼去理解呢!其實就是當快取區不起作用時會回傳0。
  • ob_get_clean:這個函數將會回傳輸出緩衝的內容並終止輸出緩衝。如果緩衝區沒有有效內容則傳回false。本質上相當於同時執行了ob_getcontens()和ob_end_clean()。
  • ob_start:開啟輸出控制緩衝

#上邊這三個先暫時認識就行,後期如果有機會會專門出一篇文章做解釋的。

2ThinkPHP框架執行流程(附腦圖)關於框架程式碼冗餘

這裡也僅僅代表咔咔咔個人的觀點。

可以先看看這部分的程式碼,這兩個處程式碼是不是很熟悉,沒錯就是在上文的init方法中容器物件實例配置更新看到過。

2ThinkPHP框架執行流程(附腦圖)如圖

2ThinkPHP框架執行流程(附腦圖)這塊也就是喀喀爾個人提出的見解,由於喀喀爾式針對5.1做的源碼解讀,不太了解新版版是否做出了改動。

六、總結

#本節主要是針對框架執行流程中的初始化應用做了簡單的探討。

至於在app類別的run方法下面還有很多的執行過程在這一節中沒有做過多的解釋。

在閱讀原始碼的過程中給大家提了一個很好得小技巧,那就是如何去查看一個方法都在哪裡進行了執行。

這個方法為debug_backtrace,這個方法需要大家多使用幾次就知道怎麼使用了,因為在列印出來的結果中也存在著很多無用的資訊。

這個方法在偵錯原始碼的過程中是非常有效的,一定要好好利用這個方法。

在就是對初始化應用init方法進行了特別詳細的介紹。

其中咔咔感覺這塊設計最好的就是在容器中的物件實例進行更新配置那一塊,先讀取所有的配置,然後在通過各個類別的方法進行配置的設定。

這種程式碼規劃和設計想法值得我們去學習。

最後聊到了調試模式和框架的程式碼冗餘問題,關於調試模式這裡咔咔給大家提個醒項目在線上的調試模式一定要關閉。

否則你的專案就類似裸奔的存在,沒有一點點的安全可言。

這塊有點不好理解的就是對於緩衝區,關於這塊的內容咔咔認為暫時沒有必要去鑽牛角尖,先認識認識然後在進行深入的研究。

緩衝區的這塊內容估計工作了三四年的也很少有人使用,所以先認識,知道怎麼一回事,咔咔後期學習了之後在給大家進行補充。

直到這裡關於框架的執行流程之初始化應用就結束了,這一節沒有過深需要學習的,主要是其中的程式碼設計模式和實作思路。

最後這張圖大家一定要跟著原始碼看一看哈!

ThinkPHP框架執行流程(附腦圖)

在這裡插入圖片描述
#「

#堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。希望在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。

#

以上是ThinkPHP框架執行流程(附腦圖)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板