寫在前面
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