再学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

posted @ 2017-06-09 23:49  dirk_jian  阅读(1737)  评论(9编辑  收藏  举报