再学ajax--第二天 | 基于php+mysql+ajax的表单注册、登录、注销
写在前面
ajax学习到了第二天,这次是用第一天封装的ajax函数,后端使用了php+mysql实现基本的注册,登录,注销。
php是我前几个月get到的技能,我已经学习到了面向对象,知道各修饰符的含义,继承,接口,构造函数,实例化对象
mysql是跟php一块学的,学习了基本增删改查。
ajax原理其实不难理解,最主要的就是XMLHttpRequest(ActiveXObject("Microsoft.XMLHTTP"));在理解该对象之后,最主要理解的是前后端数据的传递问题,我也是正在学习其中的乐趣。
HTML、CSS
因为这次学习,主要学习前后端数据的传递,所以就不贴出HTML、CSS的代码了,下面是简单示意为主的图
示意为主的注册与登录页面
注册不成功页面
当输入帐号输入栏失去焦点(onblur)时,ajax传入get参数,执行check方法,检测mysql是否有有相同username的用户,有则无刷新提示且提交按钮的disabled为true,无则可以继续注册
正常注册
登录成功
登录成功会设置一个1min的cookie,值为帐号名,js检测cookie不存在,也就是undefined,则隐藏注销栏,当存在,则显示注销栏,有退出选项,点击退出可触发,注销函数,去清除cookie,通过把失效日期设置为过去的日期/时间,删除一个 cookie setcookie('uid', "", time() - 60, '/');,其中uid是mysql做表的时候,auto_increment的编号。用这个代表当前用户。
AJAX
ajax还是第一天的封装好的ajax函数
//ajax函数 function ajax(url,method,data,success){ var xhr = null; try{ xhr = new XMLHttpRequest(); }catch(e){ xhr = new ActiveXObject("Microsoft.XMHTTP"); } url+="?"+data; xhr.open(method,url,true); xhr.onreadystatechange=function(){ if(xhr.readyState===4){ if(xhr.status===200){ success && success(xhr.responseText); }else{ alert(xhr.status); } } } xhr.send() }
ajax(url,method,data,success),一共4个形参,success为一个回调函数,主要作用是将后台的数据传到前台了,这个回调函数很关键。
后端
后端是单入口文件,单入口文件的好处之一是绝对路径的设置是参考该入口文件的,避免因为路径而踩入不必要的坑
index.php
1:$config 一个存放数据库host,port,username,password,database的数组
//数据层 $config=array( 'db_host' => 'localhost', 'db_port' => '3306', 'db_user' => 'root', 'db_password' => '123', 'db_name' => 'talklist', );
2:定义一个获取get/post请求参数值的常量
//控制层 define("module_action",$_REQUEST["a"]);
3:mysql的控制层,有两个类,一个是引入数据库连接库的类,另一个是给模型层的传入send方法,send方法是为了把执行状态(code,message)传给前台,send每次执行完,都要echo 模型层给send传的参数,是为了获取responseText 最后要exit(),退出当前脚本;
// mysql库的控制层 class DB{ public static function factory(){ global $config; //mysql库 require_once("./libs/Class/DB_Mysql.class.php"); return DB_Mysql::instance($config); } }
上述代码中DB_Mysql.class.php是mysql库包含DB_Mysql类,DB_Mysql类包含instance方法,instance方法检测当前类是否实例,如果没有实例,就实例当前类并储存起来,且传入$config,如果有实例,就直接return 当前实例对象,实际上实例就是调用当前类的构造函数。保存$config,且执行DB_Mysql类的数据库连接方法connect(),因为是构造函数,所以实例化对象时就会执行构造函数。
class DB_Mysql { private static $instanceObj; private $config ; //盛放的是数据库连接的信息,host port username password databases private function __construct($config) { $this->config = $config; $this->connect(); } public static function instance($config) { if (!self::$instanceObj) { self::$instanceObj = new DB_Mysql($config); } return self::$instanceObj; } //连接数据库 public function connect() { mysql_connect($this->config['db_host'],$this->config['db_user'],$this->config['db_password']); mysql_select_db($this->config['db_name']); $this->query("set names 'utf8'"); } }
所以我是觉得这段代码是最有趣的。不知道大家是怎样想的。
4:Controller类,是为了为模型层的子级继承父级的Controller类下的send方法,发送数据到前台。
class Controller{ public $db = null; private $ajaxData=array( "code"=>0, "message"=>"", ); public function __construct(){ $this->db = DB::factory(); } public function send($data=array()){ $showdata = array_merge($this->ajaxData,$data); echo json_encode($showdata);//转成json responseText exit(); //输出一个消息并且退出当前脚本 } }
5:加载模型层方法,require_once("./Controller/IndexController.class.php"),get到的参数就是模型层IndexController类的方法
//MVC中的模型层 require_once("./Controller/IndexController.class.php"); //把第一个参数作为回调函数调用,其余参数是回调函数的参数。 call_user_func(array(new IndexController,module_action));
6:在介绍模型层之前,先介绍完数据库文件DB_Mysql.class.php剩下的方法
class DB_Mysql{ //执行sql语句 public function query($sql) { return mysql_query($sql); } public function select($sql) { $query = $this->query($sql); $rs = array(); //将查询的结果以数字1的索引方式存在数组里面 $queryArr = mysql_fetch_array($query, 1); if($queryArr) { $rs[] = $queryArr; } return isset($rs[0])?$rs[0]:false; } }
query方法就不用多讲了,就是执行sql语句 mysql_query(),主要是说了select方法,把select方法单独挑出来就是为了单独执行select sql语句 select * from ...等
单独执行是为了将select语句晒出来的数据fetch到一个数组里面,mysql_fetch_array($query, 1),将查询的结果以数字1的索引方式存在数组里面,最最最关键的是要
判断数据库查到数据了没,查到返回当前查到的数据,没查到就返回一个bool值,为得就是在模型层判断是否查到,return一个状态(code,message),方便前台获取
介绍完这个,就就能很轻松的理解模型层的方法了。
7:模型层IndexController类extends Controller类,并且定义了自己的一些方法和属性
class IndexController extends Controller { /** * @ 用户名验证 传返回值。 * return 0: 表示在数据库没有查到有相同用户名 * return 1: 用户名的长度和类型不合法 * return 2: 表示在数据库查到了相同用户名 * $rs存在: 表示表示用查到了相同的用户名,return 2; */ private function _verifyUserName($username) { if (strlen($username) < 3 || strlen($username) > 10) {return 1;} //查数据库里面的数据 $rs = $this->db->select("SELECT `username` FROM `users` WHERE `username`='{$username}' LIMIT 1"); if ($rs) {return 2;}else{return 0;} } /** * @ interface 用户名验证return * 前台传来的get参数,选择执行IndexController下来action */ public function verifyUserName() { $username = $_REQUEST['username']; $code = $this->_verifyUserName($username); switch ($code) { case 0: $this->send(array('code'=>0,'message'=>'恭喜你,该用户名可以注册!')); break; case 1: $this->send(array('code'=>1,'message'=>'用户名长度不能小于4个或大于10个字符!')); break; case 2: $this->send(array('code'=>2,'message'=>'对不起,该用户名已经被注册了!')); break; default: break; } } }
在_verifyUserName中先要判断长度是否合适,再判断数据库是否有相同的username,记住要limit 1 ;
$this->db->select("SELECT `username` FROM `users` WHERE `username`='{$username}' LIMIT 1");
这句话也很有意思,表单看来是在当前类的db变量下的select方法,别忘了IndexController extends Controller,在当前类找不到db属性,那就是它爸爸那找么,他爸爸身上也是也是没有的
class Controller{ public $db = null; public function __construct(){ $this->db = DB::factory(); } }
所以有趣DB类的factory找,DB::factory()说,我也没有,我给你 return DB_Mysql::instance($config);,那你去DB_Mysql类中去找把,找啊找,终于在DB_Mysql类中找到了select的方法,由此看来找个这个select方法不容易啊,分析一下,我们从模型层中找到了控制层,控制层又去在数据库的控制层找,这样做是为了啥,为的就是模块化管理,数据库的方法就方法数据库类中,互补干扰,修改起来也很容易,这是MVC的魅力,前端MVC大致也如此吧。
好的,那就下一个方法verifyUserName,这个方法主要就是为了send方法,send状态(code,message),让前台获取。用到了流程语句switch case
好的,那验证就结束了,下来就是注册了,注册就是insert into
class IndexController extends Controller{ public function reg() { $username = $_REQUEST['username']; $password = $_REQUEST['password'];
$code = $this->_verifyUserName($username);
//if($code ==0){$this->sendByAjax(array('code'=>1,'message'=>""))}
if ($code !== 0 || strlen($password)<3 || strlen($password) > 15) {
$this->send(array('code'=>1,'message'=>'注册失败!'));
}
//密码加密,插入数据库里面 $password = md5($password); if (false === $this->db->query("insert into users (username, password) values ('{$username}', '{$password}')")) { $this->send(array('code'=>1,'message'=>'注册失败!')); }else { $this->send(array('message'=>'注册成功!')); } } }
插入帐号,插入密码,执行的是query方法,insert错误,就注册是吧,否则注册成功,记着要讲密码md5加密呢。也很好理解
再之后就是登录方法,注册不仅要check帐号密码是否匹配,更重要是设置cookie,就是为以后的注销做打算
class IndexController extend Controller{ /** * @ 用户登陆 * $username 是帐号 * $password 是密码 * $rs 在数据库中选出所有用户名等于$username的所有信息,mysql_fetch_array($sql,1);放在$rs数组里面 * setcookie(cookiename,cookie的值,cookie的有效期,cookie的服务器路径) */ public function login() { $username = $_REQUEST['username']; $password = $_REQUEST['password']; //检测cookie中有没有uid,有则证明已经登录过了。 if (isset($_COOKIE['uid'])) { $this->send(array('code'=>1,'message'=>'你已经登陆过了!')); } $rs= $this->db->select("select * from users where username='{$username}' limit 1"); if ($rs) { if ($rs['password'] != md5($password)) { $this->send(array('code'=>1,'message'=>'密码与帐号不匹配')); } else { //1分钟过期 setcookie('uid', $rs['uid'], time() + 60, '/'); setcookie('username', $rs['username'], time() + 60, '/'); $this->send(array('code'=>0,'message'=>'登陆成功!cookie有效时间为1min')); } } else { $this->send(array('code'=>1,'message'=>'数据库未检测到您的信息')); } } }
首先在登录的时候,有用户已经登录,就不能继续登录,这句话得先判断,isset($_COOKIE['uid']这句话很重要,如何检测是否有用户登录呢,你select * from users
把select到的内容都放入一个数组里面,之前也说了mysql_fetch_array()这个方法了,这是$rs放的就不只有username了,还有password和auto_increment的uid,这就方便了check,首先在数据库的username是否和输入的username一致的情况下再判断password是否一致,如果password一致,那就setcookie了
setcookie('uid', $rs['uid'], time() + 60, '/'); setcookie('username', $rs['username'], time() + 60, '/');
前台检测cookie是否存在,存在就显示注销栏,不存在就不现实注销栏,最后那就是注销了,之前注销也说了,就是清除cookie
class Controller extends Controller{ /** * @ 用户退出 * 通过把失效日期设置为过去的日期/时间,删除一个 cookie * uid不存在的话,则证明就没有登录 */ public function logout() { if (!isset($_COOKIE['uid'])) { $this->send(array('code'=>1,'message'=>'你还没有登陆!')); } else { //通过把失效日期设置为过去的日期/时间,删除一个 cookie: setcookie('uid', "", time() - 60, '/'); $this->send(array('code'=>0,'message'=>'退出成功!')); } } }
setcookie('uid', "", time() - 60, '/');这句话狠抓那个要,通过把失效日期设置为过去的日期/时间,删除一个 cookie:
if (!isset($_COOKIE['uid'])) {
$this->send(array('code'=>1,'message'=>'你还没有登陆!'));
}
这句话可有可无,因为你没有uid的时候,注销栏都隐藏了,所以何谈点击,何谈get请求呢,聪明的你肯定想到了。
说了这么多,还没有说JS大法呢。
JS
理解了后台,再去做前台就会很容易了。getelements我就不写了,就要写函数
检测帐号
//校验帐号 username1.onblur=function(){ ajax("guestbook/index.php","get","m=index&a=verifyUserName&username="+this.value,function(data){ var jsondata = JSON.parse(data) verifyUserNameMsg.innerHTML=jsondata.message; console.log(JSON.parse(data)); if(jsondata.code==1 || jsondata.code==2){ verifyUserNameMsg.style.color="red"; btnReg.disabled=true; }else{ verifyUserNameMsg.style.color="green"; btnReg.disabled=false; } }) }
m=index&a=verifyUserName&username="+this.value,这是你get的参数
回调函数有参数data,data就是responseText,就是状态(code,message),就是send的的echo值
code=1 代表格式不对 code=2 代表重名了 code=0代表ok
注册与登录
//注册帐号 btnReg.onclick=function(){ ajax("guestbook/index.php","get","m=index&a=reg&username="+username1.value+"&password="+password1.value,function(data){ alert("注册成功!跳转页面中..."); location.reload(); }) } //登录帐号 btnLogin.onclick=function(){ ajax("guestbook/index.php","get","m=index&a=login&username="+username2.value+"&password="+password2.value,function(data){ console.log(data); var jsondata = JSON.parse(data); if(jsondata.code===1){ alert(jsondata.message); }else{ alert(jsondata.message); user.style.display="block"; location.reload(); //userinfo.innerHTML=cookiename; } }) }
m=index&a=reg&username="+username1.value+"&password="+password1.value 注册get参数
m=index&a=login&username="+username2.value+"&password="+password2.value 登录get参数
code等于1代表未检测到您的信息
退出
//退出 logout.onclick=function(){ console.log(123); ajax("guestbook/index.php","get","m=index&a=logout",function(data){ var jsondata = JSON.parse(data) console.log(data); if(jsondata.code === 0){ alert("退出成功!"); location.reload(); }else{ } }) }
退出的get参数 m=index&a=logout
code=0退出成功
接下来就是如何前端获取cookie了
//前端获取cookie function getCookie(cookiename){ var strCookie = document.cookie; var arrCookie = strCookie.split(";"); for(var i = 1;i<arrCookie.length;i++){ var arr = arrCookie[i].split("="); if(arr[0]===cookiename){ return arr[1]; } } } var cookiename = getCookie(" username"); console.log(cookiename);
前端如何判断cookie是否存在了
//登录成功后显示 用户名退出栏 if(cookiename===undefined){ // userinfo.innerHTML=""; user.style.display="none"; }else{ userinfo.innerHTML=cookiename; }
cookiename就是username
但是聪明的你又发现了,过多的get请求会导致缓存严重,尤其在chrome下,缓存严重必须要Ctrl+F5了,
而且dom操作过多,导致了页面性能的降低
sql
create database talklist create table `users` ( `uid` int(11) unsigned primary key auto_increment, `username` char(16) `password` char(32) key `username` (`username`) ) engine=myisam default charset=utf8;
收获
通过这两次ajax的复习,对ajax的原理和使用有了深刻的认识,前后端交数据交互,ajax在其中发挥了巨大作用,PHP面向对象与JS面向对象的区别我也有了新的理解,上午还看了一个帖子,讲JS面向对象,一对比果然印象深刻,这次前后端的锻炼,让我收获颇丰,自己继续会撸起袖子加油干。
好了,晚安,期待下一次发贴。
已经把源码放在我的github里面了,有需要可以去下载,如果喜欢帮忙点一个star
https://github.com/dirkhe1051931999/writeBlog/tree/master/php-mysql-ajax-js-login-reg