再学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
再学ajax--第二天 | 基于php+mysql+ajax的表单注册、登录、注销的更多相关文章
- 第二十二章 Django会话与表单验证
第二十二章 Django会话与表单验证 第一课 模板回顾 1.基本操作 def func(req): return render(req,'index.html',{'val':[1,2,3...]} ...
- ajax+FormData+javascript 实现无刷新表单注册
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- ajax方式提交带文件上传的表单,上传后不跳转
ajax方式提交带文件上传的表单 一般的表单都是通过ajax方式提交,所以碰到带文件上传的表单就比较麻烦.基本原理就是在页面增加一个隐藏iframe,然后通过ajax提交除文件之外的表单数据,在表单数 ...
- 基于Bootstrap+jQuery.validate Form表单验证实践
基于Bootstrap jQuery.validate Form表单验证实践 项目结构 : github 上源码地址:https://github.com/starzou/front-end- ...
- 基于jQuery商品分类选择提交表单代码
分享一款基于jQuery商品分类选择提交表单代码.这是一款基于jQuery实现的商品信息选择列表表单提交代码. 在线预览 源码下载 实现的代码: <div class="yList ...
- Node.js基于Express框架搭建一个简单的注册登录Web功能
这个小应用使用到了node.js bootstrap express 以及数据库的操作 :使用mongoose对象模型来操作 mongodb 如果没了解过的可以先去基本了解一下相关概念~ 首先注 ...
- 基于JQuery的前端form表单操作
Jquery的前端表单操作: jquery提供了良好的方法封装,在一些基本的操作的时候,能节省很多的麻烦,其中,在具体使用时,form表单的数据提交是最频繁也最常见的前后数据交换方式,所以在前 ...
- 文档驱动 —— 表单组件(五):基于Ant Design Vue 的表单控件的demo,再也不需要写代码了。
源码 https://github.com/naturefwvue/nf-vue3-ant 特点 只需要更改meta,既可以切换表单 可以统一修改样式,统一升级,以最小的代价,应对UI的升级.切换,应 ...
- AJAX练习(一):制作可以自动校验的表单(从原理上分析ajax的作用)
继上文(AJAX(一)AJAX的简介和基础)作为联系. 传统网页在注册时检测用户名是否被占用,传统的校验显然缓慢笨拙. 当ajax出现后,这种体验有了很大的改观,因为在用户填写表单时,签名的表单项已经 ...
随机推荐
- 配置 Eureka Server 集群
简介 为了使 Eureka Server 实现高可用,我们需要为它配置集群.这样当有一台 Eureka Server 有故障时,集群中的其他 Server 可以进行代替.Eureka 集群之中的 No ...
- 【USACO13DEC】 最优挤奶 - 线段树
题目描述 FJ最近买了1个新仓库, 内含N 个挤奶机,1 到N 编号并排成一行. 挤奶机i 每天能产出M(i) 单位的奶.不幸的是, 机器装得太近以至于如果一台机器i 在某天被使用, 那与它相邻的两台 ...
- CF 1329B Dreamoon Likes Sequences
传送门 题目: Dreamoon likes sequences very much. So he created a problem about the sequence that you can' ...
- Azure Command Line (一)入门
一,引言 今天我们讲解一个新的 Azure 的知识,叫 “Azure Command Line”,简称 Azure CLI,具体概念是什么,我这里也不多说了,总结下来,Azure CLI 其实就是 用 ...
- 解决QT5移植报错:This application failed to start because no Qt platform plugin could be initialized
今天自己基于Pyqt5开发了一个软件,打包成exe后在自己的电脑上运行正常,在其他机器上提示: This application failed to start because no Qt platf ...
- centos 安装ifconfig
本文通过最简单方式yum
- idea配置opencv
参考:https://blog.csdn.net/sinat_38102206/article/details/81156589 配置运行时参数.通过菜单“Run->Edit Configura ...
- springboot-遇到的错误
1.Field userMapper in com.yanan.outjob.controller.SysUserController required a bean of type 'com.yan ...
- Nordic52840SDK学习之定时器
Nordic 52840 SDK学习之定时器 今天开始学习52840SDK,特在此处记录学习内容,防止以后忘记,或许可以给以后的初学者提供一些帮助.如有错误,请发邮件至843036544@qq.com ...
- 基于官方Drone-CI 的alpine版本asia亚洲时区构建支持. Drone-CI based alpine Timezone Build
基于官方Drone-CI 的alpine版本最简化添加亚洲时区Dockerfile构建支持. iotd@Github: drone-ci-based-alpine-timezone-build 如添加 ...