前日,同事给我说,有一个客户充了10块钱,但是在自己的账户里面找不到,不知道钱哪里去了,在后台留言询问我们,以为我们是钓鱼网站。
凡是涉及到金钱的事情都是大事情,马上检查时什么原因。首先让同事检查了,是否有这笔钱,经过财务查看,在当前确实有一笔10块钱的进入。同进入后台查看了留言客户的信息,却确实没有余额。
让人有一点小紧张,马上去排查到底是怎么一个情况。
经过反复几轮的测试,我们发现这个用户是通过手机端登陆的,也就是ectouch的QQ登陆!
经过蛮久的代码检查和搜索,总算是明白ectouch这个qq登陆的一个流程和思路。跟我在《从代码深探ectouch商品销售为0的原因及详解索源追码思路》和《解决ectouch中快递费用为零的问题及介绍增加配送方式的方法》中提到的一样,ectouch与ecshop整合的思路是纠结的。
ectouch控制qq第三方登录的代码控制在\mobile\include\apps\default\controller\UserController.class.php
中的public function third_login
函数。既然能够登陆成功,那么说明问题出在登陆之后,其控制登陆后的代码如下
// 授权成功 返回登录 if ($obj->call_back($info, $url, $_GET['code'])) { if ($_SESSION['access_token']) { $res = new $type($info, $_SESSION['access_token']); $openid = $res->get_openid(); // 获取用户信息 $userinfo = $res->get_user_info($openid); // 处理数据 $userinfo['aite_id'] = $type . '_' . $openid; // 添加登录标示 if ($userinfo['user_name'] = model('Users')->get_one_user($userinfo['aite_id'])) { // 已有记录 self::$user->set_session($userinfo['user_name']); self::$user->set_cookie($userinfo['user_name']); model('Users')->update_user_info(); model('Users')->recalculate_price(); $jump_url = empty($this->back_act) ? url('index') : $this->back_act; $this->redirect($jump_url); } $userinfo['user_name'] = substr($openid, -6); if(self::$user->check_user($userinfo['user_name'])) { $userinfo['user_name'] = $userinfo['user_name'].rand(1000, 9999); // 重名处理 } $userinfo['email'] = empty($userinfo['email']) ? $userinfo['user_name'] . '@' . get_top_domain() : $userinfo['email']; // 插入数据库 model('Users')->third_reg($userinfo); self::$user->set_session($userinfo['user_name']); self::$user->set_cookie($userinfo['user_name']); model('Users')->update_user_info(); model('Users')->recalculate_price(); $jump_url = empty($this->back_act) ? url('index') : $this->back_act; $this->redirect($jump_url); } } else { show_message(L('process_false'), L('relogin_lnk'), url('login', array( 'referer' => urlencode($this->back_act) )), 'error'); }
在这里,因为一个非常不应该出现的错误,耽误了我相当长的时间。看到这一段代码,很多人应当都有一个疑问,$userinfo['user_name']
的赋值在哪里?
为了测试,我一直想获取$userinfo['user_name']
的值,但是每次都是获取空值,无奈之下,自己随便赋值一个测试一下$userinfo['user_name']='test';
,用一个网站系统已经已经存在的用户名做测试,但是逻辑过程并没有进入if处理过程,更为奇妙的是我在$userinfo['user_name'] = substr($openid, -6);
前面截取$userinfo['user_name']
的值竟然还是空值!!在这里我追查了get_one_user($userinfo[‘aite_id’])的结果,也是空值,跑去看了这个函数的代码。
这个函数位于\mobile\include\apps\default\model\UsersModel.class.php
,代码如下
/** * 检查该用户是否启动过第三方登录 * @param type $aite_id * @return type */ function get_one_user($aite_id) { $sql = 'SELECT u.user_name FROM ' . $this->pre . 'users u LEFT JOIN ' . $this->pre . 'touch_user_info t ON t.user_id = u.user_id WHERE t.aite_id = "' . $aite_id . '" '; $res = $this->row($sql); return $res['user_name']; }
可以看到这个函数是在users和touch_user_info中联合查询相同user_id对应的user_name,跑去数据库一看,touch_user_info的user_id全是0,这样查询一辈子也不会相等了,也就不会有user_name了。
纠结这个事情的时候,更为纠结的代码来了, if ($userinfo['user_name'] = model('Users')->get_one_user($userinfo['aite_id'])) {
,玩过php代码的程序员应当都知道,这个判断成立的条件是只要get_one_user有返回值不为假就成立!在php中=
是赋值,并不是判断两边是否相等!也算找到为何在$userinfo['user_name'] = substr($openid, -6);
前面截取user_name也是空值了!
既然 $userinfo = $res->get_user_info($openid);
,那么这个$userinfo 是从这个函数继承的。很容易追查到这个函数来自于ectouch的qq插件文件\mobile\plugins\connect\qq.php
,经过无数次测试,我确认这个插件是perfect的,非常精简有效!同时也摸清了qq第三方登录的一个流程。相关资料在http://wiki.open.qq.com/wiki/website/API%E5%88%97%E8%A1%A8和http://wiki.open.qq.com/wiki/website/get_user_info
通过官方文档可以看到,qq返回的json数据如下:
Content-type: text/html; charset=utf-8 { "ret":0, "msg":"", "nickname":"Peter", "figureurl":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/30", "figureurl_1":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/50", "figureurl_2":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/100", "figureurl_qq_1":"http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/40", "figureurl_qq_2":"http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/100", "gender":"男", "is_yellow_vip":"1", "vip":"1", "yellow_vip_level":"7", "level":"7", "is_yellow_year_vip":"1" }
ret,msg,nickname等等就是变量名。可以看到,这里没有user_name
这样一个变量,那么回到third_login
函数,看原来的代码,就可以知道为什么每次用同一个qq登陆网站,但是用户名都不同。每一次登陆, if ($userinfo['user_name'] = model('Users')->get_one_user($userinfo['aite_id'])) {
if判断都不成立,于是在 $userinfo['user_name'] = substr($openid, -6);
进行了随机命名,在
if(self::$user->check_user($userinfo['user_name'])) { $userinfo['user_name'] = $userinfo['user_name'].rand(1000, 9999); // 重名处理 }
进行了重名检查。跑去网站后台一看,
接着往下走 model('Users')->third_reg($userinfo);
,这个就是第三方登录的处理代码了。
这个函数位于\mobile\include\apps\default\model\UsersModel.class.php
,和get_one_user
紧挨在一起。
代码如下
function third_reg($info) { $username = $info['user_name']; $password = time(); $email = $info['email']; if ($this->register($username, $password, $email) !== false) { // 更新附表 $this->table = "touch_user_info"; $touch_data['user_id'] = $uid; $touch_data['aite_id'] = $info['aite_id']; $this->insert($touch_data); return true; } else { return false; } }
register函数是去添加ecshop的用户,下面的代码是添加ectouch的用户。$touch_data['user_id'] = $uid;
这个代码就是为何ectouch的touch_user_info的user_id为何全是0的原因,因为 $uid根本没有赋值!搜索了全站关于 $uid的赋值语句,没有找到与用户登录有关的函数。所以这里有一个简单办法解决这个qq登陆的问题。就像我前面说的 if ($userinfo['user_name'] = model('Users')->get_one_user($userinfo['aite_id'])) {
这个代码中get_one_user返回的数据为空就是因为这个ectouche的user_id为0,而ecshop是有正确user_id的,因为不相等,所以拿不到结果,如果我们修正ectouch这里的user_id值,使得get_one_user有返回,那么这个if就可以成立了。
后面的代码
self::$user->set_session($userinfo['user_name']); self::$user->set_cookie($userinfo['user_name']); model('Users')->update_user_info(); model('Users')->recalculate_price(); $jump_url = empty($this->back_act) ? url('index') : $this->back_act; $this->redirect($jump_url);
就是程序处理登陆成功的代码。
跟踪一下注册程序,在mobile\include\apps\default\model\UsersModel.class.php
的function third_reg
中注册用户的代码是由register($username, $password, $email)
来执行的,在register函数中我们可以看到这样的代码
if (!ECTouch::user()->add_user($username, $password, $email)) { if (ECTouch::user()->error == ERR_INVALID_USERNAME) { ECTouch::err()->add(sprintf(L('username_invalid'), $username)); } elseif (ECTouch::user()->error == ERR_USERNAME_NOT_ALLOW) { ECTouch::err()->add(sprintf(L('username_not_allow'), $username)); } elseif (ECTouch::user()->error == ERR_USERNAME_EXISTS) { ECTouch::err()->add(sprintf(L('username_exist'), $username)); } elseif (ECTouch::user()->error == ERR_INVALID_EMAIL) { ECTouch::err()->add(sprintf(L('email_invalid'), $email)); } elseif (ECTouch::user()->error == ERR_EMAIL_NOT_ALLOW) { ECTouch::err()->add(sprintf(L('email_not_allow'), $email)); } elseif (ECTouch::user()->error == ERR_EMAIL_EXISTS) { ECTouch::err()->add(sprintf(L('email_exist'), $email)); } else { ECTouch::err()->add('UNKNOWN ERROR!'); } //注册失败 return false; } else { //注册成功 /* 设置成登录状态 */ ECTouch::user()->set_session($username); ECTouch::user()->set_cookie($username);
即如果注册成功之后将设置session和cookie,这个代码中的add_user函数和set_session函数位于mobile\plugins\integrates\integrate.php
,
function set_session($username = '') { if (empty($username)) { ECTouch::sess()->destroy_session(); } else { $sql = "SELECT user_id, password, email FROM " . $this->db->pre . 'users ' . " WHERE user_name='$username' LIMIT 1"; $row = $this->db->getRow($sql); if ($row) { $_SESSION['user_id'] = $row['user_id']; $_SESSION['user_name'] = $username; $_SESSION['email'] = $row['email']; } } }
可以看到在这里,程序将user_id赋值给了$_SESSION[‘user_id’] ,那么修正我们这个问题也就简单了。在function third_reg($info)函数中将$_SESSION[“user_id”]赋值给$touch_data[‘user_id’] 就能获取正确的user_id了,即$touch_data['user_id'] = $_SESSION["user_id"];
。
来到这里,我突然想,或许 if ($userinfo['user_name'] = model('Users')->get_one_user($userinfo['aite_id']))
这样的判断是作者的本意,他本来就是想这样设计的,判断是否已经存在用户同时赋值给$userinfo[‘user_name’]。
所有的麻烦均来自$touch_data[‘user_id’] = $uid;这一个错误,如果当初这个值是正确的,那么touch_user_info表存放ectouch的aite_id值,users存放电脑的aite_id值,get_one_user联合查询两个表的情况,现在因为user_id错误,联合查询没有结果,这样将导致touch_user_info和users表中的数据不同步,用户查询判断失误,导致在users表中不断添加新用户。有两个方案可以修正这个问题,一个方案是根据users表中aite_id对应的user_id的值来修复touch_user_info的user_id,aite_id相等就给touch_user_info的user_id赋值;另外一个方案是以users表的aite_id为准来查询,将ectouch新登陆的aite_id值也写入users表,也可以将touch_user_info的aite_id迁移到users中来,就看有没有那个必要了。我采用了第二种方案。
今天太晚了,下次才能介绍怎样将手机登录的aite_id写入users表了,同时在手机的网站用户界面显示当前登录qq用户的昵称和头像,是不是会使网站显的更加亲和?欲知后事如何,请看百蔬君下回分解《同步ecshop与ectouch第三方QQ登陆并显示用户QQ昵称和头像》。