## 4.2.1 模擬的業務場景
在這個開發實戰中,我們將模擬實現優酷的開放平臺接口,即:[http://open.youku.com/docs](http://open.youku.com/docs)
但這里不涉及具體的內部開發(我們也確實不得而知),而是只從外部的角度,通過PhalApi框架搭建類似的平臺接口架構。
## 4.2.2 優酷URL分析與路由Rewrite
以下是優酷平臺的部分接口URL:
```javascript
#單條視頻基本信息(videos/show_basic)
https://openapi.youku.com/v2/videos/show_basic.json
#我的詳細信息(users/myinfo)
https://openapi.youku.com/v2/users/myinfo.json
#評論創建(comments/create)
https://openapi.youku.com/v2/comments/create.json
#搜索節目通過關鍵詞(searches/show/by_keyword)
https://openapi.youku.com/v2/searches/show/by_keyword.json
```
從上面的接口URL,我們可以明顯發現一些規律。即:
```
URL = 接口域名 + 版本 + 相對路徑.json
```
###與PhalApi接口規則的沖突
但在PhalApi框架中,我們是通過&service=XXX參數來指定需要的服務的。為此,我們需要在服務端配置一些Rewrite規則以支持這些URL。
簡單地,我們可以這樣在nginx配置:
```javascript
if ( !-f $request_filename )
{
rewrite ^/v2/(.*)/(.*).json /v2/?service=$1.$2;
}
```
并為了更兼容PhalApi的風格,我們在入口文件將接收到的service首字母強制為大寫,即:
```javascript
//$ vim ./Public/v2/index.php
if (isset($_REQUEST['service'])) {
$_REQUEST['service'] = ucwords($_REQUEST['service']);
}
```
重啟一下nginx后,我們可以有瀏覽器,試著訪問:
```
https://openapi.youku.com/v2/videos/show_basic.json
```
我們將會看到:
```javascript
{
"ret": 400,
"data": [
],
"msg": "非法請求:接口服務Videos.show_basic不存在"
}
```
即表明Rewrite規則已生效,并能正常工作了,哈哈!
以下是更完整的nginx配置:
```javascript
server {
root /path/to/openapi.youku.com/Public;
index index.html index.htm index.php;
error_log /var/log/nginx/.error_log;
access_log /var/log/nginx/openapi.youku.com.access_log;
server_name openapi.youku.com;
location / {
try_files $uri $uri/ /index.html;
}
if ( !-f $request_filename )
{
rewrite ^/v2/(.*)/(.*).json /v2/?service=$1.$2;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
}
}
```
## 4.2.3 項目的主要目錄結構
首先,我們建立了一個Youku的項目目錄,并按不同的版本劃分了不同的模塊,如:
```javascript
$ tree ./Youku/
./Youku/
└── V2
├── Api
│?? └── Default.php
├── Domain
├── Model
└── Tests
├── Api
├── Domain
├── Model
├── phpunit_user_getbaseinfo.xml
├── phpunit.xml
└── test_env.php
8 directories, 4 files
```
然后,為不同的版本,提供不同的入口,如:
```javascript
$ tree ./Public/
./Public/
├── checkApiParams.php
├── index.php
├── init.php
└── v2
├── checkApiParams.php
├── index.php
└── listAllApis.php
1 directory, 6 files
```
## 4.2.4 簡單的模擬實現
現在,到了接口具體開發的環節,我們將模擬開發 [單條視頻基本信息(videos/show_basic)](http://open.youku.com/docs/docs?id=44)
首先,我們可以定義接口:
```javascript
//$ vim ./Youku/V2/Api/Videos.php
<?php
class Api_Videos extends PhalApi_Api {
public function getRules() {
return array(
'show_basic' => array(
'clientId' => array('name' => 'client_id', 'require' => true, 'desc' => '應用Key'),
'videoId' => array('name' => 'video_id', 'desc' => '視頻ID'),
'videoUrl' => array('name' => 'video_url', 'desc' => '視頻播放頁URL'),
),
);
}
/**
* 單條視頻基本信息(videos/show_basic)
*
* @return string id 視頻唯一ID
* @return string title 視頻標題
* @return string link 視頻播放鏈接
* @return string thumbnail 視頻截圖
* @return int duration 視頻時長,單位:秒
* @return ... ...
*/
public function show_basic() {
}
}
```
然后,使用瀏覽器在線訪問接口文檔,訪問:
```
http://openapi.youku.com/v2/checkApiParams.php?service=Videos.show_basic
```
可以看到:

可以看出,這與優酷平臺上的接口文檔是非常相近的。
最后,我們可以簡單模擬實現:
```javascript
public function show_basic() {
$rs = '{
"id" : "XMjg1MTcyNDQ0",
"title" : "泡芙小姐的燈泡 11",
"link" : "http://v.youku.com/v_show/id_XMjg1MTcyNDQ0.html",
"thumbnail" : "http://g4.ykimg.com/0100641F464E1FC...",
"duration" : "910",
"category" : "原創",
"state" : "normal",
"published" : "2011-07-15 09:00:42",
"description" : "當一個人在一座城市搬11次家。就意味著準備在這個城市買房了。",
"player" : "http://player.youku.com/player.php/sid/XMjg1MTcyNDQ0/v.swf",
"public_type" : "all",
"copyright_type" : "original",
"user" :
{
"id" : 58921428,
"name" : "泡芙小姐",
"link" : "http://u.youku.com/user_show/id_UMjM1Njg1NzEy.html"
},
"operation_limit": ["COMMENT_DISABLED"],
"streamtypes" : ["flv","flvhd","hd"]
}';
return json_decode($rs, true);
}
```
## 4.2.5 接口調用效果
雖然是模擬返回(其實是直接強制返回優酷開放平臺上的示例數據),但我們還是可以來看下在模擬實現后的接口調用效果。
首先,是缺少client_id時的非法請求:
```javascript
#請求
http://openapi.youku.com/v2/videos/show_basic.json
#返回
{
"ret": 400,
"data": [
],
"msg": "非法請求:缺少必要參數client_id"
}
```
然后,嘗試一個合法的請求:
```javascript
#請求
http://openapi.youku.com/v2/videos/show_basic.json?client_id=test
#返回
{
"ret": 200,
"data": {
"id": "XMjg1MTcyNDQ0",
"title": "泡芙小姐的燈泡 11",
"link": "http://v.youku.com/v_show/id_XMjg1MTcyNDQ0.html",
"thumbnail": "http://g4.ykimg.com/0100641F464E1FC...",
"duration": "910",
"category": "原創",
"state": "normal",
"published": "2011-07-15 09:00:42",
"description": "當一個人在一座城市搬11次家。就意味著準備在這個城市買房了。",
"player": "http://player.youku.com/player.php/sid/XMjg1MTcyNDQ0/v.swf",
"public_type": "all",
"copyright_type": "original",
"user": {
"id": 58921428,
"name": "泡芙小姐",
"link": "http://u.youku.com/user_show/id_UMjM1Njg1NzEy.html"
},
"operation_limit": [
"COMMENT_DISABLED"
],
"streamtypes": [
"flv",
"flvhd",
"hd"
]
},
"msg": ""
}
```
很好,目前運行效果相當流暢。
雖然如此,但我們明顯看到了問題所在。
## 4.2.6 返回格式的自行調整
在上一節中,我們很明顯看到了返回格式與優酷現有的不一樣,因為PhalApi框架多了一層。
其實,這些調整對于不同的項目來說,都是非常簡單。
當項目需要返回的格式有定制化需求時,可以先自實現response服務,再注冊。
在此場景,即:
### 先自定義response服務
我們先創建一個公共的目錄./Youku/Common,再創建項目需要的特定響應類:
```javascript
//$ vim ./Youku/Common/Response.php
<?php
class Common_Response extends PhalApi_Response_Json {
public function getResult() {
return $this->data;
}
}
```
### 注冊response服務
接著,我們對response進行注冊:
```javascript
//$ vim ./Public/v2/index.php
//裝載你的接口
DI()->loader->addDirs(array('Youku', 'Youku/V2'));
DI()->response = 'Common_Response';
```
這里需要稍微注意一下,我們要在裝載Youku目錄后,才能注冊DI()->response。
### 再次返回
回到剛才那個請求鏈接,我們可以發現,當再次調用時,會返回:
```javascript
{
"id": "XMjg1MTcyNDQ0",
"title": "泡芙小姐的燈泡 11",
"link": "http://v.youku.com/v_show/id_XMjg1MTcyNDQ0.html",
"thumbnail": "http://g4.ykimg.com/0100641F464E1FC...",
"duration": "910",
"category": "原創",
"state": "normal",
"published": "2011-07-15 09:00:42",
"description": "當一個人在一座城市搬11次家。就意味著準備在這個城市買房了。",
"player": "http://player.youku.com/player.php/sid/XMjg1MTcyNDQ0/v.swf",
"public_type": "all",
"copyright_type": "original",
"user": {
"id": 58921428,
"name": "泡芙小姐",
"link": "http://u.youku.com/user_show/id_UMjM1Njg1NzEy.html"
},
"operation_limit": [
"COMMENT_DISABLED"
],
"streamtypes": [
"flv",
"flvhd",
"hd"
]
}
```
## 4.2.7 簽名驗證
以上我們調整了返回格式,這使得我們的項目開發,越來越達到優酷開放平臺接口的標準了(當然,是假設)。
但有一點,我們是不能忽視的,在很多項目中同樣是不能忽視的。那就是:接口的簽名驗證。
我們可以先來看下優酷開放平臺是怎么處理接口簽名的。
簡單地,優酷會為每一個接入方提供一個client_id,然后在每次接口請求時,通過都需要傳遞此參數。
為此,我們針對這個client_id編寫一個簡單的客戶端驗證服務。如:
```javascript
//$ vim ./Youku/Common/ClientCheck.php
<?php
class Common_ClientCheck implements PhalApi_Filter {
public function check() {
$clientId = DI()->request->get('client_id');
$allCliendIds = array(
'phalapi',
'oschina'
);
if (!in_array($clientId, $allCliendIds)) {
throw new PhalApi_Exception_BadRequest('illegal client id');
}
}
}
```
然后,在入口處注冊一下:
```javascript
//$ vim ./Public/v2/index.php
DI()->filter = 'Common_ClientCheck';
```
當我們,再次打開剛才那個鏈接:
```
http://openapi.youku.com/v2/videos/show_basic.json?client_id=test
```
我們會看到空的返回:
```javascript
[]
```
這說明,對客戶端的非法請求已攔截成功,但這樣用戶體驗明顯不好,因為沒有任何的錯誤提示輸出。
為此,我們需要回到剛才自定義的響應類,修改一下:
```javascript
//$ vim ./Youku/Common/Response.php
<?php
class Common_Response extends PhalApi_Response_Json {
public function getResult() {
if ($this->ret != 200) {
return array(
'error' => array(
'code' => $this->ret,
'type' => 'SystemException',
'description' => $this->msg,
),
);
}
return $this->data;
}
}
```
再刷新一下,可以看到和優酷平臺接口近似的返回了!
```javascript
{
"error": {
"code": ,
"type": "SystemException",
"description": "非法請求:illegal client id"
}
}
```
如需要能返回數據,我們只需要傳遞正確的client_id(目前是固定的兩個)即可:
```
http://openapi.youku.com/v2/videos/show_basic.json?client_id=phalapi
```
## 4.2.8 尾聲
當然,此次的優酷接口模擬開發,我們都只是很簡單地表面說明。
這樣的目的,不是為了讓大家真的去了解優酷接口的內部實現,而是向大家展示,通過PhalApi框架,我們可以更靈活地實現各種業務需求和非功能性的需求。
希望對大家有幫助,夜已深,安。
### 源代碼請訪問:
```
https://git.oschina.net/dogstar/PhalApi-Demo-Youku.git
```
- 歡迎使用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:接口文檔參考模板