Give it some time, everything will be okay. — 《那些年我们疯狂的青春/青春洋溢色彩/This Youth is Crazy》

4.3.1 模拟的业务场景

假设我们需要为“奔跑吧兄弟”综艺节目开发一套手机App的投票接口,以供用户在观看电视的同时,可以进行投票活动参与互动。

下面将以此模拟的业务场景,提供一个接口开发实战的过程。

4.3.2 源代码下载

PhalApi-Demo-Vote

4.3.3 接口总列表

以下是我们根据业务需求整理出来的接口:

  • 1、用户可以通过微信、QQ、新浪微博等渠道进行第三方登录
  • 2、用户可以创建团队进行参赛,但队名不能重复
  • 3、用户可以对已参赛的团队进行投票,且每个用户每天投票最多不能超过3次,支持可配置
  • 4、获取已参赛团队的得票排行榜

    4.3.4 主要涉及技术功能点

  • 1、使用User扩展类库实现第三方登录操作

  • 2、使用缓存存放用户每天投票的次数(为方便起见,使用文件缓存,不落地)
  • 3、对接口进行签名验证(为方便起见,固定sign签名)
  • 4、数据库的基本操作
  • 5、自动化脚本的使用

    4.3.4 快速开发流程

(题外音:整个示例的开发,我个人在单元测试驱动开发下,只用了两个多小时,其中还包括对模板场景的业务构思、建表、编写单元测试代码等)。

(1)创建项目和部署环境

把PhalApi最新的框架代码下载后,并将User扩展类库按文档说明配置后,将项目部署到了以下接口域名:

  1. http://api.vote.phalapi.com

测试一下:

  1. http://api.vote.phalapi.com/vote/?sign=phalapi
  2. //返回
  3. {
  4. "ret": 200,
  5. "data": {
  6. "title": "Hello World!",
  7. "content": "PHPer您好,欢迎使用PhalApi!",
  8. "version": "1.1.4",
  9. "time": 1431796924
  10. },
  11. "msg": ""
  12. }

Good! 下面是简明的开发过程。

(2)单元测试驱动开发

在定好接口后:

  1. <?php
  2. class Api_Act extends PhalApi_Api {
  3. public function joinIn() {
  4. }
  5. public function showList() {
  6. }
  7. public function vote() {
  8. }
  9. }

便可使用脚本,快速生成单元测试的骨架代码:

  1. $ cd ./Vote/Tests/Api
  2. $ phalapi_buildtests ../../Api/Act.php Api_Act ../test_env.php > Api_Act_Test.php

(3)快速开发

开发过程此处略,但在单元测试驱动的引导下,很快就产出了以下高质量的代码:

  1. .
  2. ├── Api
  3. ├── Act.php
  4. └── Default.php
  5. ├── Common
  6. └── SignFilter.php
  7. ├── Domain
  8. ├── Team.php
  9. └── Vote.php
  10. ├── Model
  11. ├── Team.php
  12. ├── UserVoteRecord.php
  13. └── Vote.php
  14. └── Tests
  15. ├── Api
  16. ├── Api_Act_Test.php
  17. └── Api_Default_Test.php
  18. ├── Domain
  19. ├── Model
  20. ├── phpunit.xml
  21. └── test_env.php

(4)单元测试全部通过了!

为了方便大家查看,已省略了部分的调试内容,但保留了测试过程中全部执行的SQL语句,如下:

  1. $ phpunit ./Api_Act_Test.php
  2. PHPUnit 4.3.4 by Sebastian Bergmann.
  3. [1 - 0.06911s]DELETE FROM phalapi_team WHERE (team_name = 'test team name');<br>
  4. [2 - 0.06487s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  5. [3 - 0.06553s]SELECT COUNT(id) FROM phalapi_team WHERE (team_name = 'test team name');<br>
  6. [4 - 0.0653s]INSERT INTO phalapi_team (team_name) VALUES ('test team name');<br>
  7. [5 - 0.0699s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  8. [6 - 0.06555s]SELECT COUNT(id) FROM phalapi_team WHERE (team_name = 'test team name');<br>
  9. [9 - 0.06778s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  10. [10 - 0.06603s]SELECT COUNT(id) FROM phalapi_team WHERE (id = 3);<br>
  11. [11 - 0.06825s]SELECT vote_num FROM phalapi_vote WHERE (team_id = 3) LIMIT 1;<br>
  12. [12 - 0.07374s]UPDATE phalapi_vote SET team_id = 3, vote_num = 22 WHERE (team_id = 3);<br>
  13. [13 - 0.07012s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  14. [14 - 0.06682s]SELECT COUNT(id) FROM phalapi_team WHERE (id = 3);<br>
  15. [15 - 0.07433s]SELECT vote_num FROM phalapi_vote WHERE (team_id = 3) LIMIT 1;<br>
  16. [16 - 0.07283s]UPDATE phalapi_vote SET team_id = 3, vote_num = 23 WHERE (team_id = 3);<br>
  17. [17 - 0.07307s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  18. [18 - 0.07501s]SELECT COUNT(id) FROM phalapi_team WHERE (id = 3);<br>
  19. [19 - 0.07135s]SELECT vote_num FROM phalapi_vote WHERE (team_id = 3) LIMIT 1;<br>
  20. [20 - 0.07653s]UPDATE phalapi_vote SET team_id = 3, vote_num = 24 WHERE (team_id = 3);<br>
  21. [21 - 0.07215s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  22. [22 - 0.06722s]SELECT COUNT(id) FROM phalapi_team WHERE (id = 3);<br>
  23. [23 - 0.06506s]SELECT expires_time FROM phalapi_user_session_1 WHERE (user_id = '1') AND (token = '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731');<br>
  24. [24 - 0.06732s]SELECT COUNT(id) FROM phalapi_team WHERE (id = 404);<br>
  25. Time: 1.97 seconds, Memory: 7.00Mb
  26. OK (4 tests, 42 assertions)

(5)运行效果 - Part 1

在我们通过第三方登录后,我们就可以这样进行接口操作了。

首先,让我们添加两个参赛团队:

  1. //奔跑吧兄弟(蓝队)
  2. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.JoinIn&team_name=%E5%A5%94%E8%B7%91%E5%90%A7%E5%85%84%E5%BC%9F%EF%BC%88%E8%93%9D%E9%98%9F%EF%BC%89&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  3. //返回
  4. {
  5. "ret": 200,
  6. "data": {
  7. "code": 0,
  8. "team_id": "5"
  9. },
  10. "msg": ""
  11. }
  12. //奔跑吧兄弟(红队)
  13. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.JoinIn&team_name=%E5%A5%94%E8%B7%91%E5%90%A7%E5%85%84%E5%BC%9F%EF%BC%88%E7%BA%A2%E9%98%9F%EF%BC%89&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  14. //返回
  15. {
  16. "ret": 200,
  17. "data": {
  18. "code": 0,
  19. "team_id": "6"
  20. },
  21. "msg": ""
  22. }

然后,让我们进行疯狂地投票:

  1. //第一次投票
  2. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.Vote&team_id=5&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  3. //返回
  4. {"ret":200,"data":{"code":0,"vote_num":1},"msg":""}
  5. //第二次投票
  6. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.Vote&team_id=5&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  7. //返回
  8. {"ret":200,"data":{"code":0,"vote_num":2},"msg":""}
  9. //第三次投票
  10. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.Vote&team_id=5&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  11. //返回
  12. {"ret":200,"data":{"code":0,"vote_num":3},"msg":""}
  13. //第四次投票
  14. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.Vote&team_id=5&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  15. //返回 - 注意此返回!
  16. {"ret":200,"data":{"code":2,"vote_num":0},"msg":""}

最后,看一下排行榜:

  1. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.ShowList&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  2. //返回
  3. {
  4. "ret": 200,
  5. "data": {
  6. "code": 0,
  7. "teams": [
  8. {
  9. "id": 5,
  10. "team_name": "奔跑吧兄弟(蓝队)",
  11. "vote_num": 3
  12. },
  13. {
  14. "id": 6,
  15. "team_name": "奔跑吧兄弟(红队)",
  16. "vote_num": 0
  17. }
  18. ]
  19. },
  20. "msg": ""
  21. }

至此,我们已经可以把接口交付给客户端同学使用啦!当然,我们还需要稍微整理输出WIKI文档~~~部分接口返回的结果可能与你实际看到的不一样,因为数据会在变化而且有单元测试的测试数据。

(6)运行效果 - Part 2

接口服务,不仅仅需要提供正常的业务功能,还需要考虑到各种客户端使用的情况,包括非法的请求,或者不合的调用,比如防刷票。

这一部分,主要展示接口在各种异常情况下的响应能力。

签名失败

  1. http://api.vote.phalapi.com/vote/?sign=XXX&service=Act.JoinIn&team_name=%E5%A5%94%E8%B7%91%E5%90%A7%E5%85%84%E5%BC%9F%EF%BC%88%E7%BA%A2%E9%98%9F%EF%BC%89&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  2. //返回
  3. {
  4. "ret": 400,
  5. "data": [
  6. ],
  7. "msg": "非法请求:wrong sign"
  8. }

无登录态

  1. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.JoinIn&team_name=%E5%A5%94%E8%B7%91%E5%90%A7%E5%85%84%E5%BC%9F%EF%BC%88%E7%BA%A2%E9%98%9F%EF%BC%89&token=XXX&user_id=1
  2. //返回
  3. {
  4. "ret": 401,
  5. "data": [
  6. ],
  7. "msg": "非法请求:user need to login again"
  8. }

重复参赛

  1. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.JoinIn&team_name=%E5%A5%94%E8%B7%91%E5%90%A7%E5%85%84%E5%BC%9F%EF%BC%88%E7%BA%A2%E9%98%9F%EF%BC%89&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  2. //返回
  3. {
  4. "ret": 200,
  5. "data": {
  6. "code": 1,
  7. "team_id": 0
  8. },
  9. "msg": ""
  10. }

当天投票次数已达最大

  1. //第四次投票后
  2. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.Vote&team_id=5&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  3. //返回 - 注意此返回!
  4. {
  5. "ret": 200,
  6. "data": {
  7. "code": 2,
  8. "vote_num": 0
  9. },
  10. "msg": ""
  11. }

投票的团队不存在

  1. http://api.vote.phalapi.com/vote/?sign=phalapi&service=Act.Vote&team_id=404&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731&user_id=1
  2. //返回
  3. {
  4. "ret": 200,
  5. "data": {
  6. "code": 1,
  7. "vote_num": 0
  8. },
  9. "msg": ""
  10. }

在进行上面的非法请求后,我们可以同时关注后台的日记:

  1. $ tailf ./Runtime/log/201505/20150517.log
  2. 2015-05-17 01:35:32|DEBUG|user not login|{"userId":null,"token":null}
  3. 2015-05-17 01:36:01|DEBUG|user can not vote today|{"userId":1,"teamId":5}
  4. 2015-05-17 01:37:52|DEBUG|user can not vote today|{"userId":1,"teamId":5}
  5. 2015-05-17 01:45:25|DEBUG|user need to login again|{"expiresTime":0,"userId":"1","token":"XXX"}
  6. 2015-05-17 01:48:13|DEBUG|user can not vote today|{"userId":1,"teamId":5}

(7)需要的数据库表

以下为关键的表,其他表,可以通过脚本自动生成,然后导入。

  1. -- ----------------------------
  2. -- Table structure for `phalapi_team`
  3. -- ----------------------------
  4. DROP TABLE IF EXISTS `phalapi_team`;
  5. CREATE TABLE `phalapi_team` (
  6. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  7. `team_name` varchar(100) DEFAULT '' COMMENT '队名',
  8. PRIMARY KEY (`id`)
  9. ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
  10. -- ----------------------------
  11. -- Records of phalapi_team
  12. -- ----------------------------
  13. INSERT INTO `phalapi_team` VALUES ('3', 'egg team');
  14. INSERT INTO `phalapi_team` VALUES ('5', '奔跑吧兄弟(蓝队)');
  15. INSERT INTO `phalapi_team` VALUES ('6', '奔跑吧兄弟(红队)');
  16. INSERT INTO `phalapi_team` VALUES ('17', 'test team name');
  17. INSERT INTO `phalapi_user` VALUES ('1', 'wx_edebc877070133c65161d00799e00544', 'weixinName', '******', '4CHqOhe1Jxi3X9HmRfPOXygDnU267eCA', '1431790647', 'phpunit.png');
  18. INSERT INTO `phalapi_user_login_weixin` VALUES ('1', 'wx_122348561111', 'ASDF', '130000000', '1');
  19. INSERT INTO `phalapi_user_session_1` VALUES ('1', '1', '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731', '', '1', '1431790647', '1934382647', null);

4.3.4 脚本的使用

(1)创建项目脚本 - phalapi_buildapp

此脚本可用于快速创建项目,其使用如下:

  1. $ ./PhalApi/phalapi-buildapp
  2. Usage: ./PhalApi/phalapi-buildapp <app>

此脚本会根据Demo示例,快速生成一个新的项目。

(2)自动生成单元测试骨架脚本 - phalapi_buildtest

这是一个用来自动生成单元测试骨架的脚本,其使用如下:

  1. $ phalapi-buildtest
  2. Usage:
  3. php /usr/bin/phalapi-buildtest <file_path> <class_name> [bootstrap] [author = dogstar]
  4. Demo:
  5. php ./build_phpunit_test_tpl.php ./Demo.php Demo > Demo_Test.php

然后,我们就可以使用辅助的PhalApi_Helper_TestRunner::go()进行单元测试:

  1. public function testShowList()
  2. {
  3. //Step 1. 构建请求URL
  4. $url = 'service=Act.ShowList&sign=phalapi&user_id=1&token=193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731';
  5. //Step 2. 执行请求
  6. $rs = PhalApiTestRunner::go($url);
  7. //var_dump($rs);
  8. //Step 3. 验证
  9. $this->assertNotEmpty($rs);
  10. $this->assertArrayHasKey('code', $rs);
  11. $this->assertArrayHasKey('teams', $rs);
  12. $this->assertEquals(0, $rs['code']);
  13. $this->assertNotEmpty($rs['teams']);
  14. foreach ($rs['teams'] as $team) {
  15. $this->assertArrayHasKey('id', $team);
  16. $this->assertArrayHasKey('team_name', $team);
  17. $this->assertArrayHasKey('vote_num', $team);
  18. $this->assertGreaterThanOrEqual(0, $team['vote_num']);
  19. }
  20. }

(3)自动生成分表建表SQL语句 - phalapi_buildsqls

此脚本用于根据dbs.php配置和./Data/*.sql文件生成完整的建表语句,其使用如下:

  1. $ ./PhalApi/phalapi-buildsqls
  2. Usage: ./PhalApi/phalapi-buildsqls <dbs.php> <table> [engine=InnoDB]

假设我们需要对vote进行分表,拆分成3个表,可以这样配置:

  1. //$ vim ./Config/dbs.php
  2. 'vote' => array(
  3. 'prefix' => 'phalapi_',
  4. 'key' => 'id',
  5. 'map' => array(
  6. array('start' => 0, 'end' => 2, 'db' => 'db_vote'),
  7. ),
  8. ),

然后准备基本的建表语句:

  1. //$vim ./Data/vote.sql
  2. `team_id` bigint(20) DEFAULT '0',
  3. `vote_num` int(11) DEFAULT '0',

最后,执行生成脚本:

  1. $ phalapi-buildsqls ./Config/dbs.php vote
  2. /**
  3. * DB: phalapi_vote
  4. */
  5. CREATE TABLE `phalapi_vote_0` (
  6. `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  7. `team_id` bigint(20) DEFAULT '0',
  8. `vote_num` int(11) DEFAULT '0',
  9. `ext_data` text COMMENT 'json data here',
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  12. CREATE TABLE `phalapi_vote_1` (
  13. `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  14. `team_id` bigint(20) DEFAULT '0',
  15. `vote_num` int(11) DEFAULT '0',
  16. `ext_data` text COMMENT 'json data here',
  17. PRIMARY KEY (`id`)
  18. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  19. CREATE TABLE `phalapi_vote_2` (
  20. `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  21. `team_id` bigint(20) DEFAULT '0',
  22. `vote_num` int(11) DEFAULT '0',
  23. `ext_data` text COMMENT 'json data here',
  24. PRIMARY KEY (`id`)
  25. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4.3.5 在线接口参数查看

可以通过以下链接,在线实时查看接口参数:

  1. //参赛接口
  2. http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.JoinIn
  3. //投票接口
  4. http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.Vote
  5. //排行榜接口
  6. http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.ShowList

其中,投票接口的参数如下:a pic

上一章 文档首页 下一章

原文: https://www.phalapi.net/wikis/4-3.html