Chinese (Simplified) (中文(简体)) translation by Soleil (you can also view the original English article)

本教程是Envato Tuts +上使用PHP构建您的启动系列的一部分。 在本系列中,我将引导您使用我的Meeting Planner应用程序作为一个真实的示例,从概念到现实启动创业。 在此过程中的每一步,我都会将会议策划代码作为您可以学习的开源示例发布。 我还将解决与启动相关的业务问题。
介绍
早上好。 今天,我将向您介绍我如何使用Google API将人们的联系人导入会议筹划者。 目标是让人们更快地邀请他们的朋友参加会议。
如果您还没有尝试安排与会议筹办者的会议,请尝试一下。 如果您使用自己的Google帐户进行注册,则可以访问上方的好友页面和导入您的Google通讯录。 在下面的评论中分享您的想法和反馈。
我参与讨论,但你也可以在Twitter上联系我@reifman(最近我的账号已经过验证,所以我必须和Justin Beebert一样酷(注意编辑之神 - 我对这个拼写很有信心。 我认为这是对的。 保留它。)我总是乐于接受会议策划人的新功能想法以及未来系列剧集的建议。
提醒一下,Meeting Planner的所有代码都是作为开源提供的,并在Yii2 Framework for PHP中编写。 如果您想了解有关Yii2的更多信息,请查看我的并行系列Programming With Yii2。
关于Google联系人集成的思考
朋友页面
许多人在其Google帐户中拥有数千个联系人,而且很少有人对他们很重要。 但是,对大多数人来说,没有办法辨别哪些是哪些,哪些不是。
我相信会议计划程序中用户表的大小会影响服务的整体性能。 我不想导入可能永远不会与User表相关的联系人。
这在用户体验和代码中产生了一些复杂性,人们在服务中寻找并访问他们的朋友。
最后我决定为联系人创建一个单独的表,并在用户界面中实际分别显示它。

选择参与者参加会议
所有这些导致的是,只需键入前几个字符,人们就可以更容易地从他们的联系人中添加朋友。 我在下面显示的Add Participants弹出窗口中使用了Typeahead小部件:

在我导入Google联系人后,他们会与我的朋友(我已经邀请参加会议或被邀请的人)合并。
在这种情况下,我开始输入sar,并出现一大堆Sar-前缀名称:

通过Google通讯录查找任何邀请参加会议的人变得非常快速和轻松(直到您添加了很多这些会议,我将在下面进一步提及)。
隐私问题
我也不想滥用成千上万的联系人来滥用人们的信任。 目前,我们甚至不会向人们提供邀请他们所有Google联系人加入Meeting Planner的机会,但我们可能会在将来提供此类联系人。 我们当然不会未经许可批量发送电子邮件。
编写代码
如果您还没有,请查看使用PHP构建您的启动:使用OAuth简化Onramp。 这就是我首次验证用于OAuth登录和注册的Google API的情节。
谷歌对API的安全性非常谨慎。 鉴于雅虎黑客最近发生的事情,我更加欣赏这一点。 但是,这使得他们的API比其他API更难以进行身份验证和使用。
事实上,我发现谷歌联系人API流程是我必须编写的一些最令人困惑,令人沮丧和困难的流程。 谷歌API不赞成PHP程序员 - 我们是最后获得示例代码的人。
我们开始吧。
创建地址表
由于用户手机和Skype地址已经有UserContact表,我决定拨打地址表。 这是创建它的迁移:
<?php use yii\db\Schema; use yii\db\Migration; class m160904_001244_create_table_address extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; } $this->createTable('{{%address}}', [ 'id' => Schema::TYPE_PK, 'user_id' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'firstname' =>Schema::TYPE_STRING.' NOT NULL', 'lastname' =>Schema::TYPE_STRING.' NOT NULL', 'fullname' =>Schema::TYPE_STRING.' NOT NULL', 'email' =>Schema::TYPE_STRING.' NOT NULL', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); $this->addForeignKey('fk_address_user_id', '{{%address}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); } public function down() { $this->dropForeignKey('fk_address_user_id', '{{%address}}'); $this->dropTable('{{%address}}'); } }
当然,我使用Yii的Gii来帮助我控制器,模型和视图。 这已在创业系列的早期版本中介绍过。
扩展Google API身份验证
您可能还记得上面提到的教程中的Google凭据页面:

您可以从Google Developers Console中找到它。
您必须为所有环境(开发,登台,生产等)以及每个控制器和方法添加URL。 这使得使用其Contacts API的工作变得复杂,但也可能更好地保护人们的数据。

导入Google通讯录
这是Google Contacts API v3.0。 当我开始编写代码时,我没有注意到他们现在推荐使用People API进行只读访问。 不幸的是,我的代码使用了读/写API。 好的,所以我不是天才。 很少有企业家 - 比尔盖茨说他很幸运。
一般来说,我发现Google Contacts API是我曾经使用过的更令人困惑和困难的API之一。
如果我在Google API开发方面拥有更高水平的专业知识,或者花更多时间在这方面工作,我可能会找到一种更简单的方法。 但是,据我所知,从一个URL完成API的所有工作非常重要。 在我的情况下https://meetingplanner.io/address/import。
谷歌会返回密钥并重复将您重定向回此网址,因此您需要关注API的状态并解决此问题。
我假设这一切都是为了提高安全性,但它需要内置状态管理才能实现简单的API请求。 状态管理可以节省时间,但前提是文档和示例代码都很好。 在这种情况下,对于PHP,它们不是。
入门
我们来看看AddressController.php actionImport()
:
public function actionImport() { // imports contacts from the google api $address = new Address(); // create session cookies $session = Yii::$app->session; // if we request code reset, then remove the google_code cookie // i.e. if google_code expires if (isset($_GET['reset']) && !$session->has('google_code_reset')) { // prevent loops $session->set('google_code_reset'); // reset the google_code $session->remove('google_code'); $this->redirect(['import']); }
上面,我正在关注Google是否要重置其API令牌。 在这种情况下,我删除了我存储它们的cookie并重定向回方法重新开始。
发出令牌请求
下面,我通过其PHP客户端库向Google发出第一个请求:
// always remove the reset request cookie $session->remove('google_code_reset'); // build the API request $redirect_uri=Url::home(true).'address/import'; $session->open(); $client = new \Google_Client(); $client -> setApplicationName('Meeting Planner'); $client -> setClientid( Yii::$app->components['authClientCollection']['clients']['google']['clientId']); $client -> setClientSecret(Yii::$app->components['authClientCollection']['clients']['google']['clientSecret']); $client -> setRedirectUri($redirect_uri); $client -> setAccessType('online'); $client -> setScopes('https://www.google.com/m8/feeds'); $googleImportUrl = $client -> createAuthUrl();
Google PHP库处于测试阶段。 事实上,PHP通常是谷歌的事后想法。 因此,使用他们的API在PHP中工作并不总是那么容易。
请注意,Google的$redirect_uri
再次使用相同的方法:'address/import'
。
接下来,我们尝试将查询参数中的令牌放在cookie中:
// moves returned code to session variables and returns here if (isset($_GET['code'])) { $auth_code = $_GET['code']; $session->set('google_code',$auth_code); header('Location: '.Url::home(true).'address/import'); // do not remove - breaks the API exit; // do not replace with yii app end // do not remove above exit } else { $session_code = $session->get('google_code'); if (!isset($session_code)) { $this->redirect( $googleImportUrl); } }
如果您确实从Google获取了代码,则必须在Cookie中进行设置并再次重定向到该页面。 这对我来说很奇怪。
快乐的是,我还发现,如果我在代码丢失时没有创建循环 - 再次返回同一页面,它将无法一致地工作。
然后,我们构建并要求Google向用户显示权限对话框,以便会议计划员访问其联系人:
if (isset($session_code)) { $auth_code = $session_code; $fields=array( 'code'=> urlencode($auth_code), 'client_id'=> urlencode(Yii::$app->components['authClientCollection']['clients']['google']['clientId']), 'client_secret'=> urlencode(Yii::$app->components['authClientCollection']['clients']['google']['clientSecret']), 'redirect_uri'=> urlencode($redirect_uri), 'grant_type'=> urlencode('authorization_code'), ); // Requests the access token $post = ''; foreach($fields as $key=>$value) { $post .= $key.'='.$value.'&'; } $post = rtrim($post,'&'); $result = $address->curl('https://accounts.google.com/o/oauth2/token',$post); $response = json_decode($result); if (isset($response->error)) { if ($response->error_description == 'Code was already redeemed.') { $session->remove('google_code'); return $this->redirect(['import']); } if ($response->error_description == 'Invalid code.') { $session->remove('google_code'); return $this->redirect(['import']); } var_dump($response); echo Yii::t('frontend','There was an error. Please contact support.'); } if (isset($response->access_token) || empty($response->access_token)) { $accesstoken = $response->access_token; } else { echo Yii::t('frontend','There was an error. No access token. Please contact support.'); }

我不得不添加大量错误管理来弄清楚它为什么不起作用并使所有这些始终如一地工作。
你猜对了,如果有错误的情况,我会经常重定向到这个相同的控制器方法。
处理返回的数据
当一切正常时,有趣,简单的代码可以处理数据。 目前,我们五次抓取1,000个条目,重复构建分页请求,当然,这些请求会被发送回此URL:
$url = 'https://www.google.com/m8/feeds/contacts/default/full?max-results='.$max_results.'&start-index='.$startIndex.'&alt=json&v=3.0&oauth_token='.$accesstoken; $xmlresponse = $address->curl($url);
翻译Google的XML(这也很复杂,PHP开发人员使用奇怪的变量名称,例如$contact['gd$email'][0]['address']
等密钥,中间有一个美元符号。
下面,我们正在发出每个请求,运行JSON数据,并抓取联系人名称以添加到地址表:
// Requests the data $startIndex = 1; $request_data = true; $max_results = Address::CONTACTS_PAGE_SIZE; $numberPages = 0; while ($request_data && $numberPages <5) { //echo 'calling with startIndex: '.$startIndex.'<br />'; $url = 'https://www.google.com/m8/feeds/contacts/default/full?max-results='.$max_results.'&start-index='.$startIndex.'&alt=json&v=3.0&oauth_token='.$accesstoken; $xmlresponse = $address->curl($url); $contacts = json_decode($xmlresponse,true); if (!isset($contacts['feed']['entry'])) { //var_dump ($url); //var_dump ($xmlresponse); exit; } $resultsCount =count($contacts['feed']['entry']); //echo 'count: '.$resultsCount.'<br />'; //var_dump (count($contacts['feed']['entry'])); // process out contacts without email addresses //$return = array(); if ($resultsCount>0) { foreach($contacts['feed']['entry'] as $contact) { if (isset($contact['gd$email'])) { $temp = array ( 'firstname' => (isset($contact['gd$name']['gd$givenName']['$t'])?$contact['gd$name']['gd$givenName']['$t']:''), 'lastname' => (isset($contact['gd$name']['gd$familyName']['$t'])?$contact['gd$name']['gd$familyName']['$t']:''), 'fullname'=> $contact['title']['$t'], 'email' => $contact['gd$email'][0]['address'], ); //$return[]=$temp; $address->add($temp); } else { continue; } } if ($resultsCount<$max_results) { Yii::$app->getSession()->setFlash('success', Yii::t('backend','Your contacts have been imported.')); return $this->redirect(['/friend','tab'=>'address']); } } //var_dump($return); $numberPages++; $startIndex+=$max_results; }
使用Google Contacts API非常困难,没有详细记录,浪费了很多时间。 虽然我成功地使用了很多API,但我知道我不是这方面的专家。 出于安全方面的考虑,我真的不想在这方面打败谷歌 - 在很多这些案例中,他们可能知道为什么他们以某种方式做事。
但是可以捅一点乐趣,对吗?
首先,Google的每个人都是天才,他们通过收购API.ai再次证明了这一点,这是一个将其注册按钮链接到其登录表单的卓越服务。 真的,他们做到了:
我确信谷歌的尽职调查小组看到的天才仍然超越像我这样的凡人。 他们必须说,“哇,API.ai程序员是我们的AdSense和DFP团队的天才!让我们将它们添加到字母表中!”
由于我可能会在未来与天使和风险投资者谈论会议策划人,我想要谦虚。 但如果我的主页做到这一点并且我的一位潜在投资者注意到,我会感到震惊。
新的Alphabet(Google的新母公司)非常宽容。
扩展添加参与者表单
最后,让我们看一下扩展的Add Participants Form背后的代码。 基本上,我从Friends表中抓取电子邮件,然后从Address表中发送电子邮件:
$friendsEmail=[]; $friendsId=[]; $fq = Friend::find()->where(['user_id'=>Yii::$app->user->getId()])->all(); // to do - add a display name field for right side of input $fa = Address::find() ->select(['id','email']) ->where(['user_id'=>Yii::$app->user->getId()]) ->limit(5000) ->all(); foreach ($fq as $f) { $friendsEmail[]=$f->friend->email; // get constructed name fields $friendsId[]=$f->id; } foreach ($fa as $f) { $friendsEmail[]=$f->email; // get constructed name fields $friendsId[]=$f->id; } if (count($friendsEmail)>0) { ?> <p><strong>Choose From Your Friends</strong></p> <select class="combobox input-large form-control" id="participant-email" name="Participant[email]"> <option value="" selected="selected"><?= Yii::t('frontend','type or click to choose friends') // chg meetingjs if change string ?></option> <?php foreach ($friendsEmail as $email) { ?> <option value="<?= $email;?>"><?= $email;?></option> <?php } ?> <?php } ?> </select>
但是,选项值只是电子邮件,因为指定哪种朋友(来自朋友或来自地址表)会变得更加复杂。
我并不为上面的代码和方法感到超级自豪,但是对于测试版的推出,我做出了妥协以完成它。
在我的朋友下拉列表中有5,000个Google通讯录,性能会变慢。 我可能需要尽快将控件更好地链接到AJAX数据库搜索。
我花了很多时间在试图扩展好友表以包括我邀请的人以及谷歌联系人。 然而,这变成了一堆难以管理的相关数据库查询。 来自Friends表的User表关系开始中断了Contacts行为空的Contacts行,这实际上很难解决。 通过迁移来管理上下移除现有外键也是危险的。
结束思考
这些功能很好地说明了依赖于所选语言的文档较差的API以及在代码体系结构中妥协的挑战,以便在发布计划中启动功能(选择不删除它们)。
参与者添加性能和朋友页面用户体验当然仍然存在需要修复的问题。
老实说,会议策划人的范围已经达到了这样的程度,即一个人这样做是令人生畏的。 拥有更多资源(即团队成员)会很有帮助。
最后,如果您还没有,请立即第一次用Meeting Planner安排会面! 请在下面的评论中告诉我您的想法。 你也可以联系我@reifman。 我总是对未来教程的新功能想法和主题建议持开放态度。
关于众筹的教程也在进行中,因此请关注我们的WeFunder Meeting Planner 页面。
通过查看使用PHP系列构建您的初创公司,请继续关注所有这些以及即将发布的教程。
相关链接
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post