## 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
```
可以看到:
![a pic](http://7qnay5.com1.z0.glb.clouddn.com/20150809.png)
可以看出,这与优酷平台上的接口文档是非常相近的。
最后,我们可以简单模拟实现:
```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:接口文档参考模板