构建 Twitter OAuth 应用程序
一开始,OAuth 可能是一个难以理解的概念,但随着 Twitter API 现在需要使用它,您在创建 Twitter 应用程序之前需要了解它。本教程将向您介绍 OAuth,并引导您完成创建基本应用程序的过程。
简介
在本教程中,我们将构建一个简单的应用程序,允许用户对其 Twitter 头像应用不同的效果。为了使用 Twitter API,我们必须使用 OAuth 来授权我们的应用代表用户发出请求。
我们的应用程序流程将是这样的:
- 系统会要求用户连接 Twitter。
- 系统会向用户显示预览头像列表以供选择。
- 选择后,用户会看到一个确认屏幕,其中显示原始头像和新头像以进行比较。用户还可以选择发送推文。
- 用户确认后,应用会创建修改后的头像并将其上传到 Twitter,并显示成功页面。
设置
首先,我们应该设置源目录。我们需要一个 lib 目录来存放我们的 PHP 库(类)文件,一个 tmp 目录来保存临时文件(这需要服务器可写),一个 css 目录来存放我们的样式表,以及一个 img 目录任何图像的目录。
您的目录树应如下所示:
- 教程
- CSS
- 图片
- lib
- tmp(可写)
注册您的应用程序
为了使用 OAuth,您需要所谓的消费者密钥和秘密来识别您的应用程序。要获取这些信息,您必须按照以下步骤向 Twitter 注册应用程序。
转到注册页面,如有必要请登录。您将看到如下所示的表格:


填写表单,其中包含与您的应用相关的详细信息。在我们的例子中,应用程序类型是浏览器,我们需要设置默认回调 URL。该 URL 可以是任何内容,只要格式有效即可。我们将重写代码中的回调,因此它是否是真实的 URL 并不重要。为了方便起见,默认访问类型应为读取和写入。
注册并接受条款后,您将看到有关新申请的信息。我们需要的重要详细信息是消费者密钥和消费者秘密,它们应该如下所示:


下载 tmhOAuth 库
我们将利用一个库来处理 OAuth 请求背后的所有细节。在本教程中,我们将使用 @themattharris 的 tmhOAuth 库,它支持文件上传。
- 从 GitHub 下载 tmhOAuth
- 将tmhOAuth.php解压到我们之前创建的lib目录
身份验证
使用 OAuth 进行身份验证基本上是一个三步过程。有关更深入的解释,请参阅 Twitter 上有关身份验证的页面,但这里有一个摘要:
- 应用获取请求令牌:第一步是我们的应用向 Twitter 表明自身身份(使用其消费者密钥)并获取请求令牌。我们需要保存此请求令牌以供稍后使用。
- 用户在 Twitter 上授权应用程序:现在需要将用户发送到 Twitter 才能授予我们的应用程序访问其帐户的权限。之后,用户将被发送回应用指定的回调 URL。
- 应用程序将请求令牌交换为访问令牌:现在我们的应用程序已获得批准,它可以将第 1 步中的请求令牌交换为访问令牌。获得访问令牌后,我们的应用就可以代表用户自由地与 Twitter API 进行交互。
那么让我们开始编写一些代码。我们将在名为 TwitterApp 的类中处理所有身份验证任务。在名为 lib/TwitterApp.php 的新文件中开始以下代码:
<?php
class TwitterApp {
/**
* This variable holds the tmhOAuth object used throughout the class
*
* @var tmhOAuth An object of the tmhOAuth class
*/
public $tmhOAuth;
/**
* User's Twitter account data
*
* @var array Information on the current authenticated user
*/
public $userdata;
/**
* Authentication state
*
* Values:
* - 0: not authed
* - 1: Request token obtained
* - 2: Access token obtained (authed)
*
* @var int The current state of authentication
*/
protected $state;
/**
* Initialize a new TwitterApp object
*
* @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
*/
public function __construct(tmhOAuth $tmhOAuth) {
// save the tmhOAuth object
$this->tmhOAuth = $tmhOAuth;
}
}
这里我们创建了三个属性和一个简单的构造函数。 $tmhOAuth 属性将是一个 tmhOAuth 对象,它将在整个类中使用。 $userdata 属性将保存一个包含用户信息的对象,例如他们的 Twitter 用户名和状态。 $state 属性跟踪当前的身份验证状态。
构造函数仅接受 tmhOAuth 对象并将其分配给 $tmhOAuth 属性。
第 1 步: 获取请求令牌
以下是获取请求令牌的方法:
/**
* Obtain a request token from Twitter
*
* @return bool False if request failed
*/
private function getRequestToken() {
// send request for a request token
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("oauth/request_token", ""), array(
// pass a variable to set the callback
'oauth_callback' => $this->tmhOAuth->php_self()
));
if($this->tmhOAuth->response["code"] == 200) {
// get and store the request token
$response = $this->tmhOAuth->extract_params($this->tmhOAuth->response["response"]);
$_SESSION["authtoken"] = $response["oauth_token"];
$_SESSION["authsecret"] = $response["oauth_token_secret"];
// state is now 1
$_SESSION["authstate"] = 1;
// redirect the user to Twitter to authorize
$url = $this->tmhOAuth->url("oauth/authorize", "") . '?oauth_token=' . $response["oauth_token"];
header("Location: ' . $url);
exit;
}
return false;
}
要理解第一部分,您需要了解 tmhOAuth::request() 方法。该方法允许我们发出启用 OAuth 的 HTTP 请求,其用法如下:
tmhOAuth::request($method, $url[, $params[, $useauth[, $multipart]]])
-
string $method- 要使用的请求方法(GET、POST 等) -
string $url- 要访问的 URL -
array $params(可选) - 要包含在请求中的参数的关联数组 -
bool $useauth(可选,默认 true) - 是否需要身份验证? -
bool $multipart(可选,默认 false)- 文件上传设置为 true
对于 $url 参数,我们使用 tmhOAuth::url() 方法根据我们调用的 API 方法创建 URL:
tmhOAuth::url($request[, $format])
-
string $request- api 方法(不带扩展名) -
string $format(可选, 默认 'json") - 所需的响应格式(JSON、XML 等)
现在您已经熟悉了这些方法,我们必须向 oauth/request_token API 方法发出 POST 请求。这将以特殊格式返回 OAuth 数据,因此当我们使用 tmhOAuth::url() 方法时,需要将格式设置为空白。我们还需要传递一个名为 oauth_callback 的变量,这是用户在 Twitter 授权后将返回的位置。我们将使用 tmhOAuth::php_self() 方法来引用当前页面。这是该代码:
// send request for a request token
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("oauth/request_token", ""), array(
// pass a variable to set the callback
'oauth_callback' => $this->tmhOAuth->php_self()
));
一旦我们发出请求,响应就会以数组的形式存储在 tmhOAuth::response 属性中,其中包含以下关键数据:
-
code- HTTP 响应代码 -
response- 实际返回的数据 -
headers- 响应标头
因此,我们代码的下一部分检查响应代码(200 表示成功),然后将我们收到的 oauth_token 和 oauth_token_secret 放入会话变量中,因为稍后我们将需要它们。这些是使用 tmhOAuth::extract_params() 方法从响应数据中提取的,该方法返回响应中包含的数据数组。我们还设置了 authstate 会话变量来表明我们正处于身份验证的下一阶段。这是代码:
if($this->tmhOAuth->response["code"] == 200) {
// get and store the request token
$response = $this->tmhOAuth->extract_params($this->tmhOAuth->response["response"]);
$_SESSION["authtoken"] = $response["oauth_token"];
$_SESSION["authsecret"] = $response["oauth_token_secret"];
// state is now 1
$_SESSION["authstate"] = 1;
}
完成后,我们现在必须将用户重定向到 oauth/authorize URL,包括 GET 参数中的 oauth_token。这是该代码:
// redirect the user to Twitter to authorize
$url = $this->tmhOAuth->url("oauth/authorize", "") . '?oauth_token=' . $response["oauth_token"];
header("Location: ' . $url);
exit;
第 2 步: 获取访问令牌
以下是将请求令牌交换为访问令牌的方法:
/**
* Obtain an access token from Twitter
*
* @return bool False if request failed
*/
private function getAccessToken() {
// set the request token and secret we have stored
$this->tmhOAuth->config["user_token"] = $_SESSION["authtoken"];
$this->tmhOAuth->config["user_secret"] = $_SESSION["authsecret"];
// send request for an access token
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("oauth/access_token", ""), array(
// pass the oauth_verifier received from Twitter
'oauth_verifier' => $_GET["oauth_verifier"]
));
if($this->tmhOAuth->response["code"] == 200) {
// get the access token and store it in a cookie
$response = $this->tmhOAuth->extract_params($this->tmhOAuth->response["response"]);
setcookie("access_token", $response["oauth_token"], time()+3600*24*30);
setcookie("access_token_secret", $response["oauth_token_secret"], time()+3600*24*30);
// state is now 2
$_SESSION["authstate"] = 2;
// redirect user to clear leftover GET variables
header("Location: ' . $this->tmhOAuth->php_self());
exit;
}
return false;
}
我们要做的第一件事就是将 tmhOAuth::config 数组中的 user_token 和 user_secret 设置为我们之前获取的请求令牌。
// set the request token and secret we have stored $this->tmhOAuth->config["user_token"] = $_SESSION["authtoken"]; $this->tmhOAuth->config["user_secret"] = $_SESSION["authsecret"];
下一部分是我们向 oauth/access_token 发出 POST 请求的地方。我们将在 GET 变量中收到的 oauth_verifier 作为此请求中的参数传递。
// send request for an access token
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("oauth/access_token", ""), array(
// pass the oauth_verifier received from Twitter
'oauth_verifier' => $_GET["oauth_verifier"]
));
Twitter 将使用访问令牌和机密进行响应,我们需要保存这些令牌以供将来的请求使用。因此,下一段代码采用这些并将每个内容保存在 cookie 中,然后将状态设置为 2。
if($this->tmhOAuth->response["code"] == 200) {
// get the access token and store it in a cookie
$response = $this->tmhOAuth->extract_params($this->tmhOAuth->response["response"]);
setcookie("access_token", $response["oauth_token"], time()+3600*24*30);
setcookie("access_token_secret", $response["oauth_token_secret"], time()+3600*24*30);
// state is now 2
$_SESSION["authstate"] = 2;
// redirect user to clear leftover GET variables
header("Location: ' . $this->tmhOAuth->php_self());
exit;
}
最后的重定向是为了清除Twitter留下的URL参数,让cookie生效。
第 3 步:验证访问令牌
获得访问令牌后,我们应该检查以确保其有效。这是执行此操作的方法:
/**
* Verify the validity of our access token
*
* @return bool Access token verified
*/
private function verifyAccessToken() {
$this->tmhOAuth->config["user_token"] = $_COOKIE["access_token"];
$this->tmhOAuth->config["user_secret"] = $_COOKIE["access_token_secret"];
// send verification request to test access key
$this->tmhOAuth->request("GET", $this->tmhOAuth->url("1/account/verify_credentials"));
// store the user data returned from the API
$this->userdata = json_decode($this->tmhOAuth->response["response"]);
// HTTP 200 means we were successful
return ($this->tmhOAuth->response["code"] == 200);
}
现在这段代码看起来应该很熟悉了。我们在这里所做的就是设置 user_token 和 user_secret 并向 1/account/verify_credentials 发出 GET 请求。如果 Twitter 响应 200 代码,则访问令牌有效。
另一个需要注意的细节是,我们在这里使用此 Twitter 请求返回的数据填充 $userdata 属性。数据是JSON格式的,所以我们使用json_decode()将其转换为PHP对象。这又是那一行:
// store the user data returned from the API $this->userdata = json_decode($this->tmhOAuth->response["response"]);
第 4 步: 将所有内容捆绑在一起
我们的 OAuth 组件就位后,是时候将所有内容整合在一起了。我们需要一个面向公众的方法来允许我们的客户端代码启动身份验证过程,如下所示:
/**
* Authenticate user with Twitter
*
* @return bool Authentication successful
*/
public function auth() {
// state 1 requires a GET variable to exist
if($this->state == 1 && !isset($_GET["oauth_verifier"])) {
$this->state = 0;
}
// Step 1: Get a request token
if($this->state == 0) {
return $this->getRequestToken();
}
// Step 2: Get an access token
elseif($this->state == 1) {
return $this->getAccessToken();
}
// Step 3: Verify the access token
return $this->verifyAccessToken();
}
大多数 auth() 方法应该是不言自明的。根据状态,它执行该阶段的身份验证的适当方法。如果状态为 1,则 oauth_verifier GET 变量应该存在,因此该方法也会检查该变量。
我们现在应该创建一个公共方法来确定我们是否通过了身份验证。如果状态为 2,则 isAuthed() 方法返回 true:
/**
* Check the current state of authentication
*
* @return bool True if state is 2 (authenticated)
*/
public function isAuthed() {
return $this->state == 2;
}
我们还可以使用一种方法来删除用户的身份验证。此 endSession() 方法将状态设置为 0 并删除包含访问令牌的 cookie:
/**
* Remove user's access token cookies
*/
public function endSession() {
$this->state = 0;
$_SESSION["authstate"] = 0;
setcookie("access_token", "", 0);
setcookie("access_token_secret", "", 0);
}
初始化
现在我们需要向 __construct() 方法添加一些内容,以确定应用程序在初始化时处于哪种身份验证状态。另外,由于我们的代码使用会话变量,因此我们应该确保会话是使用以下代码启动的:
// start a session if one does not exist
if(!session_id()) {
session_start();
}
下一部分是我们确定状态的地方。状态从0开始;如果设置了包含访问令牌的 cookie,则状态假定为 2;如果失败,状态将设置为 authstate 会话变量(如果存在)。这是代码:
// determine the authentication status
// default to 0
$this->state = 0;
// 2 (authenticated) if the cookies are set
if(isset($_COOKIE["access_token"], $_COOKIE["access_token_secret"])) {
$this->state = 2;
}
// otherwise use value stored in session
elseif(isset($_SESSION["authstate"])) {
$this->state = (int)$_SESSION["authstate"];
}
如果状态为 1,则表示我们正在进行身份验证。所以我们现在可以继续这个过程:
// if we are in the process of authentication we continue
if($this->state == 1) {
$this->auth();
}
如果状态为2,我们应该验证访问令牌。如果身份验证失败,此代码将清除 cookie 并重置状态:
// verify authentication, clearing cookies if it fails
elseif($this->state == 2 && !$this->auth()) {
$this->endSession();
}
这是进行了这些更改的新构造函数:
/**
* Initialize a new TwitterApp object
*
* @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
*/
public function __construct(tmhOAuth $tmhOAuth) {
// save the tmhOAuth object
$this->tmhOAuth = $tmhOAuth;
// start a session if one does not exist
if(!session_id()) {
session_start();
}
// determine the authentication status
// default to 0
$this->state = 0;
// 2 (authenticated) if the cookies are set
if(isset($_COOKIE["access_token"], $_COOKIE["access_token_secret"])) {
$this->state = 2;
}
// otherwise use value stored in session
elseif(isset($_SESSION["authstate"])) {
$this->state = (int)$_SESSION["authstate"];
}
// if we are in the process of authentication we continue
if($this->state == 1) {
$this->auth();
}
// verify authentication, clearing cookies if it fails
elseif($this->state == 2 && !$this->auth()) {
$this->endSession();
}
}
发送推文
现在所有授权代码都已完成,我们可以向我们的类添加一些常用功能。以下是通过 Twitter API 发送推文的方法:
/**
* Send a tweet on the user's behalf
*
* @param string $text Text to tweet
* @return bool Tweet successfully sent
*/
public function sendTweet($text) {
// limit the string to 140 characters
$text = substr($text, 0, 140);
// POST the text to the statuses/update method
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/statuses/update"), array(
'status' => $text
));
return ($this->tmhOAuth->response["code"] == 200);
}
sendTweet() 方法接受一个字符串,将其限制为 140 个字符,然后在 POST 请求中将其发送到 1/statuses/update。这种模式现在应该非常熟悉了。
完整的 TwitterApp 类
<?php
class TwitterApp {
/**
* This variable holds the tmhOAuth object used throughout the class
*
* @var tmhOAuth An object of the tmhOAuth class
*/
public $tmhOAuth;
/**
* User's Twitter account data
*
* @var array Information on the current authenticated user
*/
public $userdata;
/**
* Authentication state
*
* Values:
* - 0: not authed
* - 1: Request token obtained
* - 2: Access token obtained (authed)
*
* @var int The current state of authentication
*/
protected $state;
/**
* Initialize a new TwitterApp object
*
* @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
*/
public function __construct(tmhOAuth $tmhOAuth) {
// save the tmhOAuth object
$this->tmhOAuth = $tmhOAuth;
// start a session if one does not exist
if(!session_id()) {
session_start();
}
// determine the authentication status
// default to 0
$this->state = 0;
// 2 (authenticated) if the cookies are set
if(isset($_COOKIE["access_token"], $_COOKIE["access_token_secret"])) {
$this->state = 2;
}
// otherwise use value stored in session
elseif(isset($_SESSION["authstate"])) {
$this->state = (int)$_SESSION["authstate"];
}
// if we are in the process of authentication we continue
if($this->state == 1) {
$this->auth();
}
// verify authentication, clearing cookies if it fails
elseif($this->state == 2 && !$this->auth()) {
$this->endSession();
}
}
/**
* Authenticate user with Twitter
*
* @return bool Authentication successful
*/
public function auth() {
// state 1 requires a GET variable to exist
if($this->state == 1 && !isset($_GET["oauth_verifier"])) {
$this->state = 0;
}
// Step 1: Get a request token
if($this->state == 0) {
return $this->getRequestToken();
}
// Step 2: Get an access token
elseif($this->state == 1) {
return $this->getAccessToken();
}
// Step 3: Verify the access token
return $this->verifyAccessToken();
}
/**
* Obtain a request token from Twitter
*
* @return bool False if request failed
*/
private function getRequestToken() {
// send request for a request token
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("oauth/request_token", ""), array(
// pass a variable to set the callback
'oauth_callback' => $this->tmhOAuth->php_self()
));
if($this->tmhOAuth->response["code"] == 200) {
// get and store the request token
$response = $this->tmhOAuth->extract_params($this->tmhOAuth->response["response"]);
$_SESSION["authtoken"] = $response["oauth_token"];
$_SESSION["authsecret"] = $response["oauth_token_secret"];
// state is now 1
$_SESSION["authstate"] = 1;
// redirect the user to Twitter to authorize
$url = $this->tmhOAuth->url("oauth/authorize", "") . '?oauth_token=' . $response["oauth_token"];
header("Location: ' . $url);
exit;
}
return false;
}
/**
* Obtain an access token from Twitter
*
* @return bool False if request failed
*/
private function getAccessToken() {
// set the request token and secret we have stored
$this->tmhOAuth->config["user_token"] = $_SESSION["authtoken"];
$this->tmhOAuth->config["user_secret"] = $_SESSION["authsecret"];
// send request for an access token
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("oauth/access_token", ""), array(
// pass the oauth_verifier received from Twitter
'oauth_verifier' => $_GET["oauth_verifier"]
));
if($this->tmhOAuth->response["code"] == 200) {
// get the access token and store it in a cookie
$response = $this->tmhOAuth->extract_params($this->tmhOAuth->response["response"]);
setcookie("access_token", $response["oauth_token"], time()+3600*24*30);
setcookie("access_token_secret", $response["oauth_token_secret"], time()+3600*24*30);
// state is now 2
$_SESSION["authstate"] = 2;
// redirect user to clear leftover GET variables
header("Location: ' . $this->tmhOAuth->php_self());
exit;
}
return false;
}
/**
* Verify the validity of our access token
*
* @return bool Access token verified
*/
private function verifyAccessToken() {
$this->tmhOAuth->config["user_token"] = $_COOKIE["access_token"];
$this->tmhOAuth->config["user_secret"] = $_COOKIE["access_token_secret"];
// send verification request to test access key
$this->tmhOAuth->request("GET", $this->tmhOAuth->url("1/account/verify_credentials"));
// store the user data returned from the API
$this->userdata = json_decode($this->tmhOAuth->response["response"]);
// HTTP 200 means we were successful
return ($this->tmhOAuth->response["code"] == 200);
}
/**
* Check the current state of authentication
*
* @return bool True if state is 2 (authenticated)
*/
public function isAuthed() {
return $this->state == 2;
}
/**
* Remove user's access token cookies
*/
public function endSession() {
$this->state = 0;
$_SESSION["authstate"] = 0;
setcookie("access_token", "", 0);
setcookie("access_token_secret", "", 0);
}
/**
* Send a tweet on the user's behalf
*
* @param string $text Text to tweet
* @return bool Tweet successfully sent
*/
public function sendTweet($text) {
// limit the string to 140 characters
$text = substr($text, 0, 140);
// POST the text to the statuses/update method
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/statuses/update"), array(
'status' => $text
));
return ($this->tmhOAuth->response["code"] == 200);
}
}
我们的应用
现在我们有了一个处理所有 OAuth 任务的类,我们现在可以使用特定于我们的应用程序的功能来扩展它。这包括获取、更改和设置用户头像的能力。
我们将使用 TwitterAvatars 类扩展 TwitterApp 类。在名为 lib/TwitterAvatars.php 的新文件中开始以下代码:
<?php
class TwitterAvatars extends TwitterApp {
/**
* The path to our temporary files directory
*
* @var string Path to store image files
*/
public $path;
/**
* These are all the GD image filters available in this class
*
* @var array Associative array of image filters
*/
protected $filters = array(
'grayscale' => IMG_FILTER_GRAYSCALE,
'negative' => IMG_FILTER_NEGATE,
'edgedetect' => IMG_FILTER_EDGEDETECT,
'embossed' => IMG_FILTER_EMBOSS,
'blurry' => IMG_FILTER_GAUSSIAN_BLUR,
'sketchy' => IMG_FILTER_MEAN_REMOVAL
);
/**
* Initialize a new TwitterAvatars object
*
* @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
* @param string $path Path to store image files (default 'tmp")
*/
public function __construct(tmhOAuth $tmhOAuth, $path = 'tmp") {
// call the parent class' constructor
parent::__construct($tmhOAuth);
// save the path variable
$this->path = $path;
}
}
正如你所看到的,扩展类包括一个 $path 属性,用于指向临时图像文件的位置,一个 $filters 属性,保存图像过滤器数组,以及一个带有要设置的参数的扩展构造函数路径。由于我们要重写原始构造函数,因此必须使用 parent::__construct() 显式调用父级构造函数。
现在我们可以开始添加我们的方法了。
下载
显然,我们需要能够下载图像才能操作它们。这是一个通用的 download() 方法,它接受 URL 并返回该位置的数据。该方法发出基本的 cURL 请求。
/**
* Download data from specified URL
*
* @param string $url URL to download
* @return string Downloaded data
*/
protected function download($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
查找 URL
现在我们可以下载文件了,我们需要找到所需文件的位置。我们感兴趣的是两种不同的图像,标准尺寸的缩略图和原始的全尺寸图像。因此,我们将创建一个方法来获取每个 URL。
为了获取标准尺寸的缩略图,我们将调用 users/profile_image/:screen_name API 方法,该方法通过 302 重定向响应指定用户的头像图像。这意味着 URL 将在 Location 标头中找到。这是该方法:
/**
* Get the URL to the standard sized avatar
*
* @return string The URL to the image file
*/
protected function getImageURL() {
// request user's 'bigger' profile image
$this->tmhOAuth->request("GET", $this->tmhOAuth->url("1/users/profile_image/" . $this->userdata->screen_name), array(
'screen_name' => $this->userdata->screen_name,
'size' => 'bigger'
));
if($this->tmhOAuth->response["code"] == 302) {
// the direct URL is in the Location header
return $this->tmhOAuth->response["headers"]["location"];
}
throw new Exception("Error locating image");
}
请注意,我们正在使用 tmhOAuth 发出 GET 请求,传递 screen_name 和 size 参数,然后返回 Location 标头的内容。
没有 API 方法可以获取完整尺寸的图像,因此对于下一个方法,我们将稍微作弊并编辑 URL。用户数据包含一个 profile_image_url 字段,该字段指向 avatar_normal.jpg 之类的内容,并且可以在 avatar.jpg 中找到不带后缀的原始图像。所以这个方法获取URL,去掉尺寸后缀并返回修改后的URL:
/**
* Get the URL to the full sized avatar
*
* @return string The URL to the image file
*/
protected function getOriginalImageURL() {
// get the regular sized avatar
$url = $this->userdata->profile_image_url;
// save the extension for later
$ext = strrchr($url, '.");
// strip the "_normal' suffix and add back the extension
return substr($url, 0, strrpos($url, "_")) . $ext;
}
读取图像
现在我们可以找到并下载图像,我们需要一种方法来读取它们。我们将使用 GD 库来操作图像,因此此方法会将原始图像数据转换为 GD 图像资源。
/**
* Convert raw image data to a GD resource
*
* @param string $data Binary image data to parse
* @return resource A GD image resource identifier
*/
protected function readImage($data) {
// read in the original image
$src = imagecreatefromstring($data);
if(!$src) {
throw new Exception("Error reading image");
}
// get the dimensions
$width = imagesx($src);
$height = imagesy($src);
// create a blank true color image of the same size
$img = imagecreatetruecolor($width, $height);
// copy the original image to this new canvas
imagecopy($img, $src, 0, 0, 0, 0, $width, $height);
// discard the source image
imagedestroy($src);
return $img;
}
描述上面发生的事情:
- 使用
imagecreatefromstring()函数将图像数据转换为 GD 资源。 - 使用
imagesx()和imagesy()记录图像尺寸。 - 使用
imagecreatetruecolor()创建具有相同尺寸的新空白真彩色图像。 - 使用
imagecopy()函数将原始图像复制到新图像中。无论原始颜色模式如何,这都会产生原始图像的真彩色版本。 - 使用
imagedestroy()销毁原始图像资源,并返回新图像的句柄。
保存图像
现在我们可以下载图像并创建 GD 资源,我们需要一种将图像保存到文件系统的方法。以下是使用 imagepng() 将提供的图像保存为具有指定名称的 PNG 文件的方法:
/**
* Save a GD image resource to a PNG file
*
* @param resource $img GD image resource identifier
* @param string $name Name of the image
* @return string Path to the saved image
*/
protected function saveImage($img, $name) {
$path = $this->path . "/' . $name . '.png';
imagepng($img, $path);
imagedestroy($img);
return $path;
}
生成预览
现在我们已经拥有了为我们的应用程序提供支持的所有部分,我们可以开始将它们组合在一起。在我们的应用程序流程中,我们将为用户提供可供选择的预览选项。以下是生成这些预览的方法:
/**
* Generate previews for each image filter
*
* @return array Associative array of image previews
*/
public function generatePreviews() {
// we need valid user info to know whose avatar to handle
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
$username = $this->userdata->screen_name;
// cache the raw data to use
$data = $this->download($this->getImageURL());
// copy the original image
$img = $this->readImage($data);
$this->saveImage($img, $username . "_orig");
// array to hold the list of previews
$images = array();
// loop through each filter to generate previews
foreach($this->filters as $filter_name => $filter) {
$img = $this->readImage($data);
imagefilter($img, $filter);
$images[$filter_name] = $this->saveImage($img, $username . "_' . $filter_name);
}
return $images;
}
我们要做的第一件事是检查用户是否已通过身份验证,然后获取用户名以便稍后在文件名中使用。
// we need valid user info to know whose avatar to handle
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
$username = $this->userdata->screen_name;
然后我们使用我们创建的 getImageURL() 和 download() 方法下载用户的图像。该数据将在每次预览中重复使用,因此我们将其保存在 $data 变量中。
// cache the raw data to use $data = $this->download($this->getImageURL());
接下来,我们使用 _orig 后缀保存未修改的副本。这是为了稍后进行视觉比较。
// copy the original image $img = $this->readImage($data); $this->saveImage($img, $username . "_orig");
该方法的最后一部分是我们循环遍历 $filters 属性中列出的图像过滤器,为每个过滤器生成一个图像。在每次迭代中,我们创建一个图像并应用 imagefilter() 函数,该函数接受我们在 $filters 数组中列出的常量之一。然后,对于我们保存的每个图像,我们将其路径添加到该方法最后返回的关联数组(使用过滤器名称作为键)。
// array to hold the list of previews
$images = array();
// loop through each filter to generate previews
foreach($this->filters as $filter_name => $filter) {
$img = $this->readImage($data);
imagefilter($img, $filter);
$images[$filter_name] = $this->saveImage($img, $username . "_' . $filter_name);
}
return $images;
我们的应用程序流程的下一部分要求用户确认他们的选择,因此我们需要一种方法来查找特定的预览。下面是根据作为参数传递的选项获取图像路径的简单方法,默认为原始图像:
/**
* Get the path to a previously generated preview
*
* @param string $filter The image filter to get the preview for
* @return string The path to the preview file or null if not found
*/
public function getPreview($filter = 'orig") {
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
$path = $this->path . "/' . $this->userdata->screen_name . "_' . $filter . '.png';
if(file_exists($path)) {
return $path;
}
return null;
}
更改头像
我们的应用程序流程的最后阶段是实际更改用户的头像。首先,我们需要一种方法来获取全尺寸图像并对其应用特定的滤镜。这是:
/**
* Process the user's full avatar using one of the filters
*
* @param string $filter The filter to apply to the image
* @return string Path to the output file
*/
protected function processImage($filter = "grayscale") {
// make sure the filter exists
$filter = strtolower($filter);
if(!array_key_exists($filter, $this->filters)) {
throw new Exception("Unsupported image filter");
}
$username = $this->userdata->screen_name;
// get the full sized avatar
$data = $this->download($this->getOriginalImageURL());
$img = $this->readImage($data);
// apply the filter to the image
imagefilter($img, $this->filters[$filter]);
// save the image and return the path
return $this->saveImage($img, $username . "_' . $filter . "_full");
}
这应该很容易理解,因为它与 generatePreviews() 方法非常相似。它接受一个参数来指定图像过滤器并检查它是否存在。然后它下载原始图像并对其应用过滤器,将生成图像的路径作为返回值传回。
现在我们需要将生成的图像实际发送到 Twitter 的方法,以更新用户的头像。该方法调用 processImage() 方法创建图像并通过 1/account/update_profile_image API 方法上传到 Twitter:
/**
* Update user's avatar with a filtered version
*
* @param string $filter The filter to use
* @return bool Operation successful
*/
public function commitAvatar($filter) {
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
// generate the image and get the path
$path = $this->processImage($filter);
if(file_exists($path)) {
// send a multipart POST request with the image file data
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/account/update_profile_image"), array(
// format: @local/path.png;type=mime/type;filename=file_name.png
'image' => '@' . $path . ';type=image/png;filename=' . basename($path)
), true, true);
return ($this->tmhOAuth->response["code"] == 200);
}
return false;
}
这里棘手的部分是实际的 tmhOAuth POST 请求,它是一个包含原始图像数据的多部分请求。为此,我们必须将 tmhOAuth::request() 方法的最后一个参数设置为 true,并以特殊格式传递 image 变量:
例如,如果我们要上传 tmp/username_grayscale_full.png,则值为 @tmp/username_grayscale_full.png;type=image/png;filename=username_grayscale_full.png。
这又是那部分代码:
// send a multipart POST request with the image file data
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/account/update_profile_image"), array(
// format: @local/path.png;type=mime/type;filename=file_name.png
'image' => '@' . $path . ';type=image/png;filename=' . basename($path)
), true, true);
清理
所有这些文件操作的副作用是留下大量临时文件。下面是清理临时目录的方法:
/**
* Delete leftover image files
*/
public function cleanupFiles() {
// file to track when we last checked
$flag = $this->path . "/.last_check';
$time = time();
// have we checked within the last hour?
if(!file_exists($flag) || $time - filemtime($flag) > 3600) {
// get an array of PNG files in the directory
$files = glob($this->path . "/*.png");
// loop through files, deleting old files (12+ hours)
foreach($files as $file) {
if($time - filemtime($file) > 60*60*12) {
unlink($file);
}
}
// update the timestamp of our flag file
touch($flag);
}
}
这只是循环遍历 PNG 文件,删除那些超过 12 小时的文件。它还检查自我们使用 .last_check 文件上的时间戳进行检查以来已经过去了多长时间,从而允许我们将检查限制为每小时一次。这样我们就可以在每个请求上调用这个方法,而不会浪费资源。
注意:我们在 PHP 中使用 glob() 函数,这是获取与模式匹配的文件数组的简单方法。
完整的 TwitterAvatars 类
&?php
class TwitterAvatars extends TwitterApp {
/**
* The path to our temporary files directory
*
* @var string Path to store image files
*/
public $path;
/**
* These are all the GD image filters available in this class
*
* @var array Associative array of image filters
*/
protected $filters = array(
'grayscale' => IMG_FILTER_GRAYSCALE,
'negative' => IMG_FILTER_NEGATE,
'edgedetect' => IMG_FILTER_EDGEDETECT,
'embossed' => IMG_FILTER_EMBOSS,
'blurry' => IMG_FILTER_GAUSSIAN_BLUR,
'sketchy' => IMG_FILTER_MEAN_REMOVAL
);
/**
* Initialize a new TwitterAvatars object
*
* @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
* @param string $path Path to store image files (default 'tmp")
*/
public function __construct(tmhOAuth $tmhOAuth, $path = 'tmp") {
// call the parent class' constructor
parent::__construct($tmhOAuth);
// save the path variable
$this->path = $path;
}
/**
* Download data from specified URL
*
* @param string $url URL to download
* @return string Downloaded data
*/
protected function download($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
/**
* Get the URL to the standard sized avatar
*
* @return string The URL to the image file
*/
protected function getImageURL() {
// request user's 'bigger' profile image
$this->tmhOAuth->request("GET", $this->tmhOAuth->url("1/users/profile_image/' . $this->userdata->screen_name), array(
'screen_name' => $this->userdata->screen_name,
'size' => 'bigger'
));
if($this->tmhOAuth->response["code"] == 302) {
// the direct URL is in the Location header
return $this->tmhOAuth->response["headers"]["location"];
}
throw new Exception("Error locating image");
}
/**
* Get the URL to the full sized avatar
*
* @return string The URL to the image file
*/
protected function getOriginalImageURL() {
// get the regular sized avatar
$url = $this->userdata->profile_image_url;
// save the extension for later
$ext = strrchr($url, '.");
// strip the "_normal' suffix and add back the extension
return substr($url, 0, strrpos($url, "_")) . $ext;
}
/**
* Convert raw image data to a GD resource
*
* @param string $data Binary image data to parse
* @return resource A GD image resource identifier
*/
protected function readImage($data) {
// read in the original image
$src = imagecreatefromstring($data);
if(!$src) {
throw new Exception("Error reading image");
}
// get the dimensions
$width = imagesx($src);
$height = imagesy($src);
// create a blank true color image of the same size
$img = imagecreatetruecolor($width, $height);
// copy the original image to this new canvas
imagecopy($img, $src, 0, 0, 0, 0, $width, $height);
// discard the source image
imagedestroy($src);
return $img;
}
/**
* Save a GD image resource to a PNG file
*
* @param resource $img GD image resource identifier
* @param string $name Name of the image
* @return string Path to the saved image
*/
protected function saveImage($img, $name) {
$path = $this->path . "/' . $name . '.png';
imagepng($img, $path);
imagedestroy($img);
return $path;
}
/**
* Generate previews for each image filter
*
* @return array Associative array of image previews
*/
public function generatePreviews() {
// we need valid user info to know whose avatar to handle
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
$username = $this->userdata->screen_name;
// cache the raw data to use
$data = $this->download($this->getImageURL());
// copy the original image
$img = $this->readImage($data);
$this->saveImage($img, $username . "_orig");
// array to hold the list of previews
$images = array();
// loop through each filter to generate previews
foreach($this->filters as $filter_name => $filter) {
$img = $this->readImage($data);
imagefilter($img, $filter);
$images[$filter_name] = $this->saveImage($img, $username . "_' . $filter_name);
}
return $images;
}
/**
* Get the path to a previously generated preview
*
* @param string $filter The image filter to get the preview for
* @return string The path to the preview file or null if not found
*/
public function getPreview($filter = 'orig") {
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
$path = $this->path . "/' . $this->userdata->screen_name . "_' . $filter . '.png';
if(file_exists($path)) {
return $path;
}
return null;
}
/**
* Process the user's full avatar using one of the filters
*
* @param string $filter The filter to apply to the image
* @return string Path to the output file
*/
protected function processImage($filter = 'grayscale") {
// make sure the filter exists
$filter = strtolower($filter);
if(!array_key_exists($filter, $this->filters)) {
throw new Exception("Unsupported image filter");
}
$username = $this->userdata->screen_name;
// get the full sized avatar
$data = $this->download($this->getOriginalImageURL());
$img = $this->readImage($data);
// apply the filter to the image
imagefilter($img, $this->filters[$filter]);
// save the image and return the path
return $this->saveImage($img, $username . "_' . $filter . "_full");
}
/**
* Update user's avatar with a filtered version
*
* @param string $filter The filter to use
* @return bool Operation successful
*/
public function commitAvatar($filter) {
if(!$this->isAuthed()) {
throw new Exception("Requires oauth authorization");
}
// generate the image and get the path
$path = $this->processImage($filter);
if(file_exists($path)) {
// send a multipart POST request with the image file data
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/account/update_profile_image"), array(
// format: @local/path.png;type=mime/type;filename=file_name.png
'image' => '@' . $path . ';type=image/png;filename=' . basename($path)
), true, true);
return ($this->tmhOAuth->response["code"] == 200);
}
return false;
}
/**
* Delete leftover image files
*/
public function cleanupFiles() {
// file to track when we last checked
$flag = $this->path . "/.last_check';
$time = time();
// have we checked within the last hour?
if(!file_exists($flag) || $time - filemtime($flag) > 3600) {
// get an array of PNG files in the directory
$files = glob($this->path . "/*.png");
// loop through files, deleting old files (12+ hours)
foreach($files as $file) {
if($time - filemtime($file) > 60*60*12) {
unlink($file);
}
}
// update the timestamp of our flag file
touch($flag);
}
}
}
前端
我们将应用程序的所有组件放在一起,所以现在我们需要的只是用户界面。这里的所有代码都将进入根目录中的index.php文件。我们将首先包含库并设置配置:
<?php
// include our libraries
include 'lib/tmhOAuth.php';
include 'lib/TwitterApp.php';
include 'lib/TwitterAvatars.php';
// set the consumer key and secret
define("CONSUMER_KEY", 'qSkJum23MqlG6greF8Z76A");
define("CONSUMER_SECRET", 'Bs738r5UY2R7e5mwp1ilU0voe8OtXAtifgtZe9EhXw");
?>
注意:请务必将 CONSUMER_KEY 和 CONSUMER_SECRET 替换为您自己的。
我们将把代码放在 try-catch 块中,这样我们就可以优雅地处理任何错误,将它们的消息分配给 $error 变量。
try {
} catch(Exception $e) {
// catch any errors that may occur
$error = $e;
}
在 try 块中,我们可以开始编写代码,首先使用配置的 tmhOAuth 对象初始化名为 $ta 的 TwitterAvatars 对象:
// our tmhOAuth settings
$config = array(
'consumer_key' => CONSUMER_KEY,
'consumer_secret' => CONSUMER_SECRET
);
// create a new TwitterAvatars object
$ta = new TwitterAvatars(new tmhOAuth($config));
此时我们可以清除所有旧的临时文件:
// check for stale files
$ta->cleanupFiles();
接下来,我们检查用户是否已通过身份验证,或者如果用户未通过身份验证,则检查用户是否已请求身份验证,在这种情况下,我们通过调用 auth() 方法来启动该过程:
// check our authentication status
if($ta->isAuthed()) {
}
// did the user request authorization?
elseif(isset($_POST["auth"])) {
// start authentication process
$ta->auth();
}
如果用户已通过身份验证,我们需要检查是否已选择某个选项,否则我们将生成预览:
// check our authentication status
if($ta->isAuthed()) {
// has the user selected an option?
if(isset($_POST["filter"])) {
}
// generate previews if the user has not chosen
else {
// $previews will be a list of images
$previews = $ta->generatePreviews();
}
}
如果选择了某个选项,我们需要获取要显示的旧图像和新图像的路径:
// has the user selected an option?
if(isset($_POST["filter"])) {
// get the image paths for display
$original = $ta->getPreview();
$newimage = $ta->getPreview($_POST["filter"]);
}
最后,我们检查用户是否确认了他们的选择并应用更改。如果需要,我们还会发送一条推文,并将 $success 变量设置为 true:
// has the user selected an option?
if(isset($_POST["filter"])) {
// is the user sure?
if(isset($_POST["confirm"])) {
// change the user's avatar
$ta->commitAvatar($_POST["filter"]);
// tweet if the user chose to
if(isset($_POST["tweet"])) {
$ta->sendTweet("I just updated my avatar using Avatar Effects...");
}
$success = true;
}
// get the image paths for display
$original = $ta->getPreview();
$newimage = $ta->getPreview($_POST["filter"]);
}
这是我们迄今为止所拥有的:
<?php
// include our libraries
include 'lib/tmhOAuth.php';
include 'lib/TwitterApp.php';
include 'lib/TwitterAvatars.php';
// set the consumer key and secret
define("CONSUMER_KEY", 'qSkJum23MqlG6greF8Z76A");
define("CONSUMER_SECRET", 'Bs738r5UY2R7e5mwp1ilU0voe8OtXAtifgtZe9EhXw");
try {
// our tmhOAuth settings
$config = array(
'consumer_key' => CONSUMER_KEY,
'consumer_secret' => CONSUMER_SECRET
);
// create a new TwitterAvatars object
$ta = new TwitterAvatars(new tmhOAuth($config));
// check for stale files
$ta->cleanupFiles();
// check our authentication status
if($ta->isAuthed()) {
// has the user selected an option?
if(isset($_POST["filter"])) {
// is the user sure?
if(isset($_POST["confirm"])) {
// change the user's avatar
$ta->commitAvatar($_POST["filter"]);
// tweet if the user chose to
if(isset($_POST["tweet"])) {
$ta->sendTweet("I just updated my avatar using Avatar Effects...");
}
$success = true;
}
// get the image paths for display
$original = $ta->getPreview();
$newimage = $ta->getPreview($_POST["filter"]);
}
// generate previews if the user has not chosen
else {
// $previews will be a list of images
$previews = $ta->generatePreviews();
}
}
// did the user request authorization?
elseif(isset($_POST["auth"])) {
// start authentication process
$ta->auth();
}
} catch(Exception $e) {
// catch any errors that may occur
$error = $e;
}
?>
HTML
在 PHP 代码之后,我们将输出适当的 HTML,从这个模板开始,它设置标题和主标题:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Twitter Avatar Effects</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h1>Twitter Avatar Effects</h1>
</body>
</html>
这里是我们显示带有每个预览的图像输入的表单的地方:
<?php if(isset($previews)): ?>
<h2>Choose your weapon...</h2>
<form action="index.php" method="post">
<?php foreach($previews as $filter => $path): ?>
<input type="image" src="<?php echo $path; ?>"
alt="<?php echo ucfirst($filter); ?>"
width="73" height="73"
name="filter" value="<?php echo $filter; ?>">
<?php endforeach; ?>
</form>
<p>Select one of the images above to change your Twitter avatar.</p>
这是成功页面:
<?php elseif(isset($success)): ?>
<h2>Success! Your Twitter avatar is now:</h2>
<img src="<?php echo $newimage; ?>" alt="Your Avatar" width="73" height="73">
<p><a href="http://twitter.com/<?php echo $ta->userdata->screen_name; ?>">Go see it</a></p>
这是确认页面,我们在其中显示比较并提供取消的机会:
<?php elseif(isset($newimage)): ?>
<h2>Are you sure?</h2>
<img src="<?php echo $original; ?>" alt="Original" width="73" height="73">
<span class="arrow">⇒</span>
<img src="<?php echo $newimage; ?>" alt="<?php echo ucfirst($_POST["filter"]); ?>">
<form action="index.php" method="post">
<input type="hidden" name="filter" value="<?php echo $_POST["filter"]; ?>">
<input type="submit" name="confirm" value="Confirm">
<a href="index.php">Cancel</a>
<p><label>Tweet about your new avatar?
<input type="checkbox" name="tweet" value="true"></label></p>
</form>
请注意,确认表单在隐藏字段中包含所选的过滤器。
如果出现错误,我们会显示:
<?php elseif(isset($error)): ?>
<p>Error. <a href="index.php">Try again?</a></p>
默认显示的是“连接到 Twitter”按钮作为图像输入(从本页底部下载一张图像到 img 目录):
<?php else: ?>
<form action="index.php" method="post">
<input type="image" src="img/sign-in-with-twitter-l.png"
alt="Connect to Twitter" name="auth" value="1">
</form>
<p>Connect to Twitter to use this app.</p>
<?php endif; ?>
这是完整的 HTML 部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Twitter Avatar Effects</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h1>Twitter Avatar Effects</h1>
<?php if(isset($previews)): ?>
<h2>Choose your weapon...</h2>
<form action="index.php" method="post">
<?php foreach($previews as $filter => $path): ?>
<input type="image" src="<?php echo $path; ?>"
alt="<?php echo ucfirst($filter); ?>"
width="73" height="73"
name="filter" value="<?php echo $filter; ?>">
<?php endforeach; ?>
</form>
<p>Select one of the images above to change your Twitter avatar.</p>
<?php elseif(isset($success)): ?>
<h2>Success! Your Twitter avatar is now:</h2>
<img src="<?php echo $newimage; ?>" alt="Your Avatar" width="73" height="73">
<p><a href="http://twitter.com/<?php echo $ta->userdata->screen_name; ?>">Go see it</a></p>
<?php elseif(isset($newimage)): ?>
<h2>Are you sure?</h2>
<img src="<?php echo $original; ?>" alt="Original" width="73" height="73">
<span class="arrow">⇒</span>
<img src="<?php echo $newimage; ?>" alt="<?php echo ucfirst($_POST["filter"]); ?>">
<form action="index.php" method="post">
<input type="hidden" name="filter" value="<?php echo $_POST["filter"]; ?>">
<input type="submit" name="confirm" value="Confirm">
<a href="index.php">Cancel</a>
<p><label>Tweet about your new avatar?
<input type="checkbox" name="tweet" value="true"></label></p>
</form>
<?php elseif(isset($error)): ?>
<p>Error. <a href="index.php">Try again?</a></p>
<?php else: ?>
<form action="index.php" method="post">
<input type="image" src="img/sign-in-with-twitter-l.png"
alt="Connect to Twitter" name="auth" value="1">
</form>
<p>Connect to Twitter to use this app.</p>
<?php endif; ?>
</body>
</html>
CSS
这里是一些使界面看起来更漂亮的基本 CSS,保存在 css/style.css 中:
html {
background-color: #eee;
text-align: center;
font-family: "Lucida Grande",Verdana, sans-serif;
font-size: 16px;
color: #224;
}
body {
width: 700px;
margin: 30px auto;
background-color: #acf;
padding: 10px;
border-radius: 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
}
p {
font-size: 1em;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.6em;
}
.arrow {
font-size: 4em;
font-weight: bold;
}
结果
这是一个视频,详细介绍了我们完成的应用程序的外观:
结论
如果您完全按照本教程进行操作,您应该对 OAuth 以及创建简单 Twitter Web 应用程序所需的内容有很好的了解。一旦您了解了基本概念,使用 Twitter API 就会很容易 - 特别是如果您使用像 tmhOAuth 这样的库来处理次要细节。
我们在本教程中创建的简单示例可以轻松修改或扩展以执行任何操作。因此,如果您对一款很酷的新 Twitter 应用程序有好主意,请随意使用它作为基础。
感谢您的阅读。如果您对本教程有任何疑问或意见,请留言!
以上就是构建 Twitter OAuth 应用程序的详细内容,更多请关注其它相关文章!
Php