##2.20.1 前言
我們一直強調推薦讓后臺接口開發更簡單。
所以,我們提供了PhalApi開發框架和WIKI文檔。然后,這僅僅是個開始。
因為,真正發揮作用,實現價值的還是來自項目實際開發中的源代碼。
但縱使我們提供了好的框架作為基礎,但不得不承認的一個事實是:架構模型和項目代碼隨著各自的演進逐漸產生分歧。而這種分歧,如果沒有注意、管理和約束,則會產生越來越混亂的代碼。
在說明如何編寫簡潔的項目代碼之前,讓我們先了解一下PhalApi的架構思想以及主要的設計意圖。
##2.20.2 開發-配置-使用 模式 (develop-config-use pattern)##
開發-配置-使用 模式即:開發實現-配置注冊-客戶使用 模式。
現分說如下。
###(1)開發實現
開發實現的主要內容是組件、公共服務或者基礎設施的功能實現,此部分主要針對高級開發工程師,或者有經驗的PHP同學。
例如對項目的接口簽名的驗證攔截、一個完成了對七牛云存儲接口調用的擴展、又或者是項目內部加密的方案等,這些以包或者接口提供,為外部使用提供了配置說明、使用示例和文檔說明。更為重要的是,應該提供了配套的單元測試,并有著很高的代碼覆蓋率。
此類實現應該是穩定的,即沒有明顯或者隱藏的BUG。即使有,原作者也可以快速進行定位和解決,以及后期的擴展和升級。
###(2)配置注冊
一旦上面的接口被實現后,不同的項目都可以輕松引入和使用。這塊通常由項目的負責人,或者主程來操作,因為在對項目進行構建部署、組件和環境裝配時,需要考慮到哪些組件需要被用到,以何種方式進行初始化和裝載。
但使用的方式,應該是簡明的。如簡明的安裝,簡明的配置。所以,這里自然而言,就涉及到了 **依賴注入** ,DI。
通過DI,項目的負責人,可以輕松地將已通過嚴格測試的組件/服務注冊進來。完成此步驟后,一切都整裝待發,剩下的就是使用的問題了。
###(3)客戶使用
項目會不斷有新的需求出來,而團隊也會因此同步增加吸納新開發同學進來負責新模塊新功能的開發。而新的同學,往往會是一些開發新手,他們需要使用已有的功能,快速實現一些具體的業務邏輯、規則和功能。
但如果他們還需要實現一些基礎重要的功能,又要考慮如何與現在項目整合,會分散他們的關注點。而且,即使放手給他們去做,他們也會常常因為考慮不周或者編程風格各異而產出一些與項目期望不符的代碼。
若換一種工作的方式,即如果新手使用已有的組件進行一些特定領域業務的開發,會是怎樣?
我想,會有很大的改觀。
比如,我們對新來的同學說,你使用DI()->logger就可以寫一條日志了,如:
```javascript
DI()->logger->debug('app enter');
```
新手可能很喜歡追問一些問題,他可能會問及到,那怎么將一些參數(當時日志的上下文)也進行紀錄呢?你可以很驕傲地說:也是可以的,你只需要這樣寫就可以了:
```javascript
DI()->logger->debug('app enter', array('device' => 'iOS', 'version' => '1.1.0'));
```
###(4)創建和使用分離
**開發-配置-使用 模式** 也符合了創建和使用分離的思想。
不同的項目,不同的應用,需要的初始化服務不一樣;不同的規模,對不同的技術解決方案也不一樣;不同的環境,配置也不一樣。
但即使是這樣,新手還是可以一如既往地使用之前注冊的服務(也就是不需要修改任何調用代碼)。也就是上層的調整或者環境變更這些,對新手的使用都是透明的。為了更好地理解這些概念,這里補充一些案例場景。
####以日志為例
假設我們有個項目A,分別部署到內網測試環境和外網生產環境,顯然內外網環境的配置是不一樣的。我們希望在內網環境為日志開啟debug模式以方便開發人員進行調試,在外網則希望將其關閉以減少系統的性能開銷。在一開始使用文件作為日志存儲方案時,對應的內網環境初始化代碼如下:
```javascript
//日志紀錄
DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime',
PhalApi_Logger::LOG_LEVEL_DEBUG | PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
```
在外網,我們只需要去掉PhalApi_Logger::LOG_LEVEL_DEBUG即可:
```javascript
//日志紀錄
DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime',
PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
```
隨著項目的不斷發展,我們有了一批又一批的新用戶。產品經理為此很開心,也請我們開發吃了好幾頓大餐。但謹慎的我們發現了現在文件日志的一些限制。如即時文件讀寫帶來了I/O瓶頸,而且不能將分布式的日志文件自動收集起來。所以,我們決定對logger進行更深層次的探索。。。
至于最后是使用了Hive還是Hadoop,還是MC異步后臺隊列的方式實現,我們這里不具體指定。假設新的logger研發成功后,我們便可以輕松對原有的文件日志組件進行升級,實現完美切換:
```javascript
//升級后的日志紀錄
DI()->logger = new My_Logger(PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
```
這不僅是幾行代碼上的區別,而是針對不同問題不同技術解決方案的抉擇。這也是有經驗的開發和新手之間的區別,因為你選擇的技術解決方案要和面臨的風險相匹配。例如用牛刀來殺雞,就是一個不匹配的做法,就如同使用高級的Hive來實現單一小項目的日志存儲一樣。
這是令人值得興奮的。在很多舊的項目里面,當遇到瓶頸時,會請一些外部的專家來指導或優化。但即使擁有著各種“法寶”以及知道何時該使用哪種方案的專家,對于這種殘留的代碼也會步履維艱基于束手無策。因為,各種初始化和調用的代碼,分遍在項目的“全國各地,四面八方”。即使你優化了,你會發現還要手動一個個地進行切換升級。更重要的是,很多時候不是你想優化就能優化的。
我曾經遇到過這樣一個舊系統。它是在UcHome基礎上做二次開發,但對于它的數據庫使用,開發人員沒有過多地優化,如:沒有使用緩存,沒有進行批量合并查詢優化,重復查詢相同的數據,沒有建立索引等等,等等。這樣的后果就是,請求一次接口,會觸發150條到500條SQL語句不等。我后來,在底層添加了在線查看調試SQL語句的功能,嘗試進行了一些合并查詢,但當我想為數據庫的表添加索引時,發現它用的卻是虛擬表 -- 視圖!
###(5)擴展類庫對“開發-配置-使用”模式的應用
如果說DI服務是微觀上對“開發-配置-使用”模式的使用,那么PhalApi的擴展類庫則是宏觀上的應用。
擴展類庫也是由第三方(可能是PhalApi開發團隊、他人或者你)開發實現的,然后再通過簡單配置(或者免配置),就可以使用擴展類庫的功能了。如郵件發送、phprpc協議。
之所以提供擴展類庫的形式,是因為DI服務更適合于單個類以及幾個操作接口,而擴展類庫則提供更豐富的功能操作和一系列的接口。
這樣以后,項目就可以簡單快速共享各種擴展類庫了。難道這不是一件令人興奮的事情嗎?
因為“哈啊!我又找到了一個可以直接用的代碼類庫”,而不是“唉,又要寫一堆代碼,還要測試、聯調。。。”。
###(6)回顧Yii框架的發現
程序、系統和框架,其作用太多數都體現在動態的功能上,而不是靜態有限的功能。而動態的功能則很大程序上依賴于各種配置,如Tomcat下各層級xml配置。有些框架對配置這塊提供了豐富的支持,但為此的代碼是,配置難以掌控。
就拿Yii框架為例(Yii確實是一個很了不起的框架,這里只是以事論事),當你需要在視圖渲染一個數據表格時,你可以使用 [CGridView](http://www.yiichina.com/doc/api/1.1/CGridView) ,并類似這樣配置:
```javascript
$columns = array(
array('name' => 'mId', 'header' => '序號'),
array('name'=>'id', 'header'=>'事件ID'),
array('name'=>'title', 'header'=>'標題'),
array('name'=>'content', 'header'=>'內容', 'type' => 'html'),
);
$this->widget('bootstrap.widgets.TbGridView', array(
'type'=>'striped bordered condensed',
'dataProvider'=>$dataProvider,
'columns'=> $columns,
));
```
更為復雜的情況可以是:
```javascript
$columns = array(
// ...
array('class' => 'CDataColumn', 'header' => '內容', 'type' => 'html', 'name' => 'content', 'htmlOptions' => array('width' => '200px')),
array(
'class'=>'CButtonColumn',
'template'=>'{showEvent}<br/><br/>{deleteEvent}',
'header'=>'操作',
'buttons'=>array
(
'showEvent' => array(
'label' => '查看',
'url' => '"?r=DailyOperations/eventManagerShow&user_iduser_id=' . $userId . '&eventId=". $data["id"];',
'options' => array('target' => '_blank'),
),
'deleteEvent' => array(
'label'=>'刪除',
'url'=>'"javascript:void(0)"',
'imageUrl'=>'/images/delete_24.png',
'deleteConfirmation'=>"js:'Record with ID '+$(this).parent().parent().children(':first-child').text()+' will be deleted! Continue?'",
'click'=>'js:function(){if (confirm("此操作將刪除:ID = " + $(this).parent().parent().children(\':first-child\').text() + " \n是否確定?")) {deleteEvent($(
this).parent().parent().children(\':first-child\').text());};}',
),
),
),
);
// ...
```
然后,對于我這么笨的人來說,不管是簡單的配置,還是復雜的配置,每次當我需要使用時,我都非常害怕且需要從以下三方便獲取幫助:
+ 找曾經寫過類似的代碼并拷貝過來修改;
+ “耐心”(耐著心)查看官方的文檔;
+ 網上搜索相關的例子
因為,每次我都記不住這些配置,但我又不得不承認它的效果很好。然后我覺得其缺點至少有兩點:
+ 缺點1:盡管是很簡單的功能也需要用配置來實現,從而導致配置羞澀難懂;
+ 缺點2:配置太復雜,對人的記憶要求太高;
這是我對Yii框架配置的體會。
#####自己工作的回顧
最初,感受到配置式的開發,是在大學的時候做一個OutLook的插件。這個插件需要同步本地和遠程服務器的聯系人,其中當有沖突時,就有這么幾種策略:沖突時以本地為準、沖突時以遠程為準、沖突時提醒我、忽略沖突。
這是當時寫的博客,感興趣可以看看 [配置編程: 讓項目開發從多樣到統一 ](http://my.oschina.net/dogstar/blog/95314)
這有點像我們常用的SVN的處理方式。然而當我在嘗試開發實現時,我發現過程很復雜,但處理又是如此相似。這里的區別很微妙,特別這些策略又是由外部用戶指定時。最后,我驚訝地發現,如果我使用配置來做的話,會非常簡單且明了!
但那時,只是初體會。
現在,經過了幾年的開發,我才慢慢發現,可以把這種開發模式總結為:開發-配置-使用模式。
不知是否有其他模式和此新發現的模式類似?
##2.20.3 框架外延
框架外延是指跨項目,與業務無關的抽象特性與代碼實現。
這些從簡單到復雜,有:通用功能,定制,擴展類庫和產品簇框架。
###(1)通用功能
有些功能是常用的,也是通用的。一般以函數或者工具類的形式提供,如一個生成隨機數的方法。這些通用的功能,開發人員都可以在項目間流通使用。
###(2)定制
當發現PhalApi現在的框架不支持某些接口下具體方案的實現時,或者支持但不滿足當前項目的開發需要時,可以進行一些定制化。
之前有一個項目的開發同學,給我提供了一個很好的建議。他說他的項目為了能夠獲得更高的安全性,對客戶端傳遞的參數進行了整包加密,然后在服務商進行整包解密再通過getRules()獲取。所以對應的定制大概實現可以是這樣的:
```javascript
<?php
class My_Request extends PhalApi_Request {
protected function genData($data){
$needData = array();
if (!isset($data) || !is_array($data)) {
$data = $_POST;
}
$needData['service'] = isset($data['service']) ? $data['service'] : '';
//整包加密后的數據包
$params = isset($data['params']) ? $data['params'] : '';
//TODO: 對稱解密 ...
$needData = array_merge($params, $needData);
return $needData;
}
}
```
###(3)擴展類庫
有時候,我們在項目中需要使用到一組功能操作,這時可以結合外觀模式,將這些需要用到的接口以包的形式都封裝到一個擴展里面。因為擴展這個概念是容易理解的,也是開發人員所喜愛的。
在PhalApi的Issues里面,有位同學就提到了對新浪或者百度云空間的支持,就是對應的一個場景。
現在PhalApi提供了部分的擴展類庫,但由于個人時間有限,而且也不可能知悉全部項目需要用到的全部擴展。所以當發現需要的擴展類庫還沒有提供時,簡單的方法:自動寫一個。
這也是對框架進行外延的一種途徑。伴隨著這種途徑經歷的次數越多,你會突然有一天發現你所搭建的這一切結合起來后,已經可以很好地應對了公司內的目前全部項目的開發。
###(4)產品簇框架
這個時候,就來了產品簇框架這一階段。
PhalApi只是提供了一個基礎的框架,一個項目實際開發的基礎框架,更是一個產品簇框架的鋪墊。如果說我們關注項目的快速交付,不如說我們更關注 **如何提供一個底層框架以支持不同項目的產品簇框架開發,從而最終支持項目的快速交付** 。
也只有這樣,你辛苦付出的代碼,你才會更加珍惜和不斷為之冥想、維護和改進。
也只有這樣,你公司里面的其他項目才會更愿意和信賴使用,因為框架是你直接提供的。
也只有這樣,代碼才得以永恒,因為這種思想在你、我、各個項目團隊間不斷傳遞,共存在每個開發人員的腦里和心中,而不是為某個自私的人把持著。
##2.20.4 項目內涵
**框架外延** 是針對特定領域項目以外的問題。然而,在使用PhalApi進行項目開發時,我們如果只關注這些是無用的,我們還要關注項目的實際開發,如何編寫代碼,即:項目內涵。
項目內涵是指完成特定領域功能所需要的前置條件、基礎設施和工作流程。
這里面著重講解復雜的領域和更廣義的數據源這兩方面。不是說其他的方面不重要,而是這兩方既重要但又常常為人們所誤解誤用。
###(1)復雜的領域業務層
在一個項目架構里面,有三個主要模型:設計模型、領域模型和代碼模型。設計模型在選擇PhalApi時已大體確定,領域模式則需要項目干系人員消化、理解并表達出來。對于開發人員,代碼模型則是他們表達的媒介。
這一層,主要關注的是領域業務規則的處理。所以,我們拋開外界客戶端接口調用的簽名驗證、參數獲取、安全性等問題,也不考慮數據從何而來、存放于何處,而是著重關注對領域業務數據的處理上。
根據這么年來的工作、項目開發和學習,這里有一些建議。
####規則出現且出現一次####
領域之所以復雜,在于規則眾多。如果不能很好地把控這些規則,當規則發生變化時,就會出現很大的問題。在開發過程中,要注意對規則進行提煉并且放置在一個指定的位置。如對游戲玩家的經驗計算等級時,這樣一個規則就要統一好。不要到處都有類型相同的計算接口。
####釋意接口####
領域的邏輯是對現實業務場景的再解釋。現實的因素充滿變數并且由人為指定,所以不能簡單的在計算機中“推導”出領域邏輯。在開發過程中,要特別對這些領域邏輯準確并很好的解釋,以便后面接手的同學可以更容易理解和明白這些流程、限制和規則。
其中一個有力的指導就是釋意接口。對接口簽名甚至是對變量命名的仔細推敲都是很有益處的,因為名字能正名份,不至于混淆或者含糊不清。
####代碼保持在同一高度####
領域層關注的是流程、規則,所以當你進行用戶個性化分流和排序時,不應該把底層網絡接口請求的細節也放到這里流程里面。把底層技術實現的細節和業務規則的處理分開是很有好處的,這樣便于更清晰領域邏輯的表達,也助于單元測試時的測試樁模擬。
###(2)更廣義的數據源層
領域層固然重要,但如果沒有數據源層,領域層就是一個空中樓閣。
但不應把數據源就理所當然地對等成數據庫。因為這種觀念很常見但也很狹隘。首先,很多項目在對數據存儲時,不一定會落地存儲,即使落地也不一定使用數據庫。我曾經在一家游戲公司任職時,就看到他們使用了文件來存放。相信,你也看到過。其次,在現在多客戶端多系統的交互背景下,很多系統都需要進行數據共享和通信,為了提高服務器的性能也會使用到緩存。這些場景下,會導致數據是通過接口來獲取,或者來源于緩存。可以看出,如果把數據源就看作是MySql,是非常局限的。
我們在PhalApi中繼續使用了Model層,因為受MVC模式的影響,大家都對Model層非常熟悉。但我們卻為它賦予了新的詮釋和活力。
Model導獲取的數據,可以是來自數據庫的讀取,也可以是通過開放平臺接口獲取的數據,也可以是不落地直接存放于緩存的數據。
##2.20.5 測試先行和真實的測試
框架外延和項目內涵,分別是對 **開發-配置-使用 模式** 前半部分和后半部分的詮釋。
剩下的問題就在于,如何評判我們編寫的代碼是好的,是美的,并且能夠按期望地工作?
如果只是主觀地判斷,顯然是不可靠的也不是完全可信的。例如“我覺得我這樣沒問題啊!”,即使代碼能夠正常運行,也不并代表在其他臨界或者極端的情況也能如期運行,也不代表這些代碼就具備了一些好的品質,更不代表這些代碼遵循了我們的架構明顯的編程風格:編寫人容易理解的代碼。
既然主觀不可靠,就應該轉到客觀上的標準。
靜態的代碼分析,是很有用的,但對于最終需要動態執行的代碼,還是需要單元測試才能更好的探知代碼內部每個角落的狀況。
這里,我們強烈推薦測試先行,也就是測試驅動開發。可能你已對此耳熟能詳,也可能你略知一二。
但請相信,測試先行是值得的。關于真實的測試,即編寫PHPUnit單元測試,關于PHPUnit的使用,請查看: [PHPUnit Manual – 第 1 章 自動化測試](https://phpunit.de/manual/3.7/zh_cn/automating-tests.html) 。
##2.20.6 PhalApi架構的設計意圖
###(1)設計意圖
PhalApi是開放式的框架,這里不僅僅體現在源代碼開放,產品開放,還表現在思想開放。
這樣說,可能有些抽象。若落實到代碼層次,你會發現PhalApi提供了接口上的約束,并為每塊接口提供了很好的擴展機制。也就是說,一旦你發現已提供的功能不滿足你項目的需要,你可以輕松定制、擴展和升級。
這就是我們的設計意圖。
對于新手,你可以快速使用這個框架;對于老手,你則可以定制它。
###(2)協同合作和關注點
**開發-配置-使用 模式** 并不是為了傳遞 “君君臣臣、父父子子”這樣的封建等級觀念。而是為了資源更好的調配,最終更好地完成項目的開發。
架構系統有分層的思想,這里也一樣。通過高級開發、開發工程師和新手各盡其才、各施其職,能夠更好的提高各自的關注點,并發揮他們應有的能力和價值。
比如對于高級開發,我們希望他們能夠抽離業務,并且產出一些公司內可以使用的通用組件、核心技術甚至是產品簇開發框架。而對于他們來說,也許這也正是他們所喜愛的,因為他們在攻關一些技術難點,又或者在解決一些他們覺得有挑戰的工作。
對于項目負責人,也就是開發工程師,他們更關注的是整個項目的運行和所能提供的功能。而這一些功能需要各業務規則在代碼上的體現,最終又會直接或間接落到某些基礎設施的支持上,一如數據庫的查詢操作。顯然,他們不希望每次都為這樣通用的技術支持重復開發。如果有已經能夠直接拿來使用的代碼,那該多好。網上雖然資源眾多,但符合公司項目使用的,少之甚少,又或許會有這樣那樣的限制。如果有公司內部可重用的組件庫,這種情況會大為改觀。
對于新手,我們要求明顯會低很多。簡單來說,他們會使用就可以了。當然,我們也推薦新手在熟練使用的情況下,再深化到底層,慢慢過渡到大工。
###(3)這只是一個開始
盡管 我們提供了這樣架構明顯的編程風格,但仍然需要你以及你的團隊來遵循。
正如前面所說的,項目的每一行代碼和每一個命名都來自你的思考和雙手的輸入。也正如此,你的付出讓你更深刻體會到編程的樂趣,特別是項目發揮和實現了有價值的業務功能時。這時,你會發現維護別人的代碼不再是一件痛苦的事件,與團隊的合作也變得更加融洽,因為和你一起奮斗努力的是一支精英團隊。
請記住,這只是一個開始,一個起點。
- 歡迎使用PhalApi!
- 接口,從簡單開始!
- [1.1]-下載與安裝
- [1.2]-創建一個自己的項目
- [1.3]-在線體驗
- [1.4]-文檔、幫助和官網
- [1.10]-對PhalApi框架的抉擇
- [1.11]-快速入門(backup)
- [1.12]-參數規則:接口參數規則配置
- [1.13]-統一的接口請求方式:_sevice=XXX.XXX
- [1.14]-統一的返回格式和結構:ret-data-msg
- [1.15]-數據庫操作:基于NotORM的使用及優化
- [1.16]-配置讀取:內外網環境配置的完美切換
- [1.17]-日記紀錄:簡化版的日記接口
- [1.18]-快速函數:人性化的關懷
- [1.19]-DI服務速查:各資源服務一覽表
- [1.20]-DB操作:數據庫基本操作速查
- [1.21]-類的自動加載:遵循PEAR包的命名規范
- [1.22]-簽名驗證:自定義簽名規則
- [1.23]-請求和響應:GET和POST兩者皆可得及超越JSON格式返回
- [1.24]-緩存策略:更靈活地可配置化的多級緩存
- [1.25]-國際化翻譯:為走向國際化提前做好翻譯準備
- [1.26]-數據安全:數據對稱加密方案
- [1.27]-精益開發:更富表現力的Model層和重量級數據獲取的應對方案
- [1.28]-COOKIE:對COOKIE原生態的支持及記憶加密升級版
- [1.29]-開放與封閉:多入口和統一初始化
- [1.30]-保持的力量:接口開發最佳實踐
- [1.31]-新型計劃任務:以接口形式實現的計劃任務
- [2.11]-核心思想:DI依賴注入-讓資源更可控
- [2.12]-海量數據:可配置的分庫分表
- [2.13]-接口調試:在線SQL語句查看與性能優化
- [2.14]-測試驅動開發:意圖導向編程下的接口開發
- [2.15]-演進:新型計劃任務續篇
- [2.16]-領域驅動設計:應對復雜領域業務的Domain層
- [2.17]-微服務:Api接口服務層
- [2.18]-定制化:資源服務的再實現
- [2.19]-擴展庫:可重用的擴展類庫
- [2.20]-約定編程:架構明顯的編程風格
- [2.21]-服務器統一部署方案簡明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益項目和團隊建設
- [3.1]-擴展類庫:微信開發
- [3.2]-擴展類庫:代理模式下phprpc協議的輕松支持
- [3.3]-擴展類庫:基于PHPMailer的郵件發送
- [3.4]-擴展類庫:優酷開放平臺接口調用
- [3.5]-擴展類庫:七牛云存儲接口調用
- [3.6]-擴展類庫:新型計劃任務
- [3.8]-擴展類庫:用戶、會話和第三方登錄集成
- [3.9]-擴展類庫:swoole支持下的長鏈接和異步任務實現
- [3.11]-擴展類庫:基于FastRoute的快速路由
- [4.2]-開發實戰2:模擬優酷開放平臺接口項目開發
- [4.3]-開發實戰3:一個簡單的小型項目開發(奔跑吧兄弟投票活動)
- [5.1]-架構與思想:PhalApi核心設計和思想解讀
- [5.2]-雜談:扯一些PhalApi的前世和今生
- [5.3]-框架總結:術語表和PHP開發建議
- [5.4]-許可
- [5.5]-聯系和加入我們
- [5.6]-更新日記
- [5.8]-致框架貢獻者:加入PhalApi開源指南
- [6.1]-基于接口查詢語言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi視頻教程
- 附錄1:接口文檔參考模板