本文將以本站IT快訊網整合Discuz X2結合Discuz X2用戶登陸流程源碼來分析整個Ucenter的通信流程原理步驟。
1. 當會員從登陸界面輸入正確的用戶名和密碼單擊登陸按鈕後,我們看到打開源碼discuzx/source/class/class_member.php 找到第155行代碼.
- $ucsynlogin = $this->setting['allowsynlogin'] ? uc_user_synlogin($_G['uid']) : '';
從上面的代碼可以看到,當會員登陸驗證通過後, 當你在ucenter管理面板裏面設置開啓同步登陸後,X2將會調用uc_user_synlogin($_G['uid']) 方法發出同步登陸的通知。
2. 調用uc_user_synlogin($_G['uid']) 方法後,實際調用的是discuzx/uc_client/client.php 中的 uc_user_synlogin函數,我們打開源碼看方法簽名,在310 - 320 之間:
- function uc_user_synlogin($uid) {
- $uid = intval($uid);
- if(@include UC_ROOT.'./data/cache/apps.php') {
- if(count($_CACHE['apps']) > 1) {
- $return = uc_api_post('user', 'synlogin', array('uid'=>$uid));
- } else {
- $return = '';
- }
- }
- return $return;
- }
跟着這個方法流程,我們繼續看 uc_api_post 方法簽名, 仍然是在discuzx/uc_client/client.php文件,找到54-74行
- function uc_api_post($module, $action, $arg = array()) {
- $s = $sep = '';
- foreach($arg as $k => $v) {
- $k = urlencode($k);
- if(is_array($v)) {
- $s2 = $sep2 = '';
- foreach($v as $k2 => $v2) {
- $k2 = urlencode($k2);
- $s2 .= "$sep2{$k}[$k2]=".urlencode(uc_stripslashes($v2));
- $sep2 = '&';
- }
- $s .= $sep.$s2;
- } else {
- $s .= "$sep$k=".urlencode(uc_stripslashes($v));
- }
- $sep = '&';
- }
- $postdata = uc_api_requestdata($module, $action, $s);
- return uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20);
- }
可以看到這個方法主要調用
uc_api_requestdata
來組裝一個post請求需要發送的數據,看這個方法的簽名
- function uc_api_requestdata($module, $action, $arg='', $extra='') {
- $input = uc_api_input($arg);
- $post = "m=$module&a=$action&inajax=2&release=".UC_CLIENT_RELEASE."&input=$input&appid=".UC_APPID.$extra;
- return $post;
- }
注意看uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20)函數中的 UC_API和 UC_APPID常量實際就是我們在 discuzx/config/config_ucenter.php 文件中配置的預定的常量,相信到這裏大家已經明白了這幾個常量的用意,UC_API就是你定義的uc_server的URL.
return uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20);
- define('UC_API', 'http://www.itkuaixun.com/xxxx'); //uc_server地址
- define('UC_APPID', '1');
3. 接着看uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20) 方法簽名和調用原理
- function uc_fopen2($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE) {
- $__times__ = isset($_GET['__times__']) ? intval($_GET['__times__']) + 1 : 1;
- if($__times__ > 2) {
- return '';
- }
- $url .= (strpos($url, '?') === FALSE ? '?' : '&')."__times__=$__times__";
- return uc_fopen($url, $limit, $post, $cookie, $bysocket, $ip, $timeout, $block);
- }
最終由uc_fopen函數調用PHP函數fsockopen 或者 pfsockopen 打開一個socket 連接將數據用流的形式發送通知數據到uc_server
- function uc_fopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE) {
- $return = '';
- $matches = parse_url($url);
- !isset($matches['host']) && $matches['host'] = '';
- !isset($matches['path']) && $matches['path'] = '';
- !isset($matches['query']) && $matches['query'] = '';
- !isset($matches['port']) && $matches['port'] = '';
- $host = $matches['host'];
- $path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
- $port = !emptyempty($matches['port']) ? $matches['port'] : 80;
- if($post) {
- $out = "POST $path HTTP/1.0\r\n";
- $out .= "Accept: */*\r\n";
- //$out .= "Referer: $boardurl\r\n";
- $out .= "Accept-Language: zh-cn\r\n";
- $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
- $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
- $out .= "Host: $host\r\n";
- $out .= 'Content-Length: '.strlen($post)."\r\n";
- $out .= "Connection: Close\r\n";
- $out .= "Cache-Control: no-cache\r\n";
- $out .= "Cookie: $cookie\r\n\r\n";
- $out .= $post;
- } else {
- $out = "GET $path HTTP/1.0\r\n";
- $out .= "Accept: */*\r\n";
- //$out .= "Referer: $boardurl\r\n";
- $out .= "Accept-Language: zh-cn\r\n";
- $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
- $out .= "Host: $host\r\n";
- $out .= "Connection: Close\r\n";
- $out .= "Cookie: $cookie\r\n\r\n";
- }
- if(function_exists('fsockopen')) {
- $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
- } elseif (function_exists('pfsockopen')) {
- $fp = @pfsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
- } else {
- $fp = false;
- }
- if(!$fp) {
- return '';
- } else {
- stream_set_blocking($fp, $block);
- stream_set_timeout($fp, $timeout);
- @fwrite($fp, $out);
- $status = stream_get_meta_data($fp);
- if(!$status['timed_out']) {
- while (!feof($fp)) {
- if(($header = @fgets($fp)) && ($header == "\r\n" || $header == "\n")) {
- break;
- }
- }
- $stop = false;
- while(!feof($fp) && !$stop) {
- $data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit));
- $return .= $data;
- if($limit) {
- $limit -= strlen($data);
- $stop = $limit <= 0;
- }
- }
- }
- @fclose($fp);
- return $return;
- }
- }
至此,uc_client的調用流程結束,轉入uc_server部份.
4. 繼續打開
discuzx/uc_server/index.php
部份,找到52行,我們就可以完全理解ucenter的內部通知機制,當接收到
uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20) 方法發送過來的請求後,
- if(in_array($m, array('app', 'frame', 'user', 'pm', 'pm_client', 'tag', 'feed', 'friend', 'domain', 'credit', 'mail', 'version'))) {
- if(file_exists(UC_ROOT.RELEASE_ROOT."control/$m.php")) {
- include UC_ROOT.RELEASE_ROOT."control/$m.php";
- } else {
- include UC_ROOT."control/$m.php";
- }
- $classname = $m.'control';
- $control = new $classname();
- $method = 'on'.$a;
- if(method_exists($control, $method) && $a{0} != '_') {
- $data = $control->$method();
- echo is_array($data) ? $control->serialize($data, 1) : $data;
- exit;
- } elseif(method_exists($control, '_call')) {
- $data = $control->_call('on'.$a, '');
- echo is_array($data) ? $control->serialize($data, 1) : $data;
- exit;
- } else {
- exit('Action not found!');
- }
- } else {
- exit('Module not found!');
- }
在這個方法裏面查找對應的通知模塊,這裏我們以用戶同步登陸爲例,其它原理都是一樣的,所以這裏實際調用的是user我們接着打開 discuzx/uc_server/control/user.php 文件源碼, 在第32行開始,可以看到最終它調用onsynlogin方法,查詢緩存的所有開啓同步通知的應用,
- // -1 未開啓
- function onsynlogin() {
- $this->init_input();
- $uid = $this->input('uid');
- if($this->app['synlogin']) {
- if($this->user = $_ENV['user']->get_user_by_uid($uid)) {
- $synstr = '';
- //這裏循環從緩存中讀取所有需要發送通知的應用
- foreach($this->cache['apps'] as $appid => $app) {
- if($app['synlogin']) {
- $synstr .= '<script type="text/javascript" src="'.$app['url'].'/api/'.$app['apifilename'].'?time='.$this->time.'&code='.urlencode($this->authcode('action=synlogin&username='.$this->user['username'].'&uid='.$this->user['uid'].'&password='.$this->user['password']."&time=".$this->time, 'ENCODE', $app['authkey'])).'" reload="1"></script>';
- if(is_array($app['extra']['extraurl'])) foreach($app['extra']['extraurl'] as $extraurl) {
- $synstr .= '<script type="text/javascript" src="'.$extraurl.'/api/'.$app['apifilename'].'?time='.$this->time.'&code='.urlencode($this->authcode('action=synlogin&username='.$this->user['username'].'&uid='.$this->user['uid'].'&password='.$this->user['password']."&time=".$this->time, 'ENCODE', $app['authkey'])).'" reload="1"></script>';
- }
- }
- }
- return $synstr;
- }
- }
- return '';
- }
使用Firefox安裝好firebug可以看到,這就是爲什麼返回我們看到返回的一段javascript代碼,如果不成功將返回-1(false) ,
$app['url'].'/api
看到這段代碼,這也是爲什麼ucenter API文檔中定義我們必須在根目錄下面創建一個api文件夾, 使用P3P協議來解決cookie發送的問題.到這裏相信你已經明白整個UCenter的運行流程和原理,趕緊動手去整合一個。
Yii 和 Discuz X2完美整合演式地址:
IT快訊網
有問題歡迎回復,時間允許範圍內將會第一時間回覆。
轉載請說明出自: IT快訊網| 原文地址: Ucenter 1.6和Discuz X2整合通信流程原理詳細分析