环境搭建: 用nodeJs express 搭建服务器, 用MySQL创建数据库 表格 、SQLyogEnt管理数据库
使用主要技术: bootstrap 、 JS、 jQuery、Ajax、node、 express、 MySQL
登录界面:
项目文件结构:
登录注册模块在 首页的 导航栏 右上角:
html 部分:
<div class="header-right login"> <a ><span class="glyphicon glyphicon-user" aria-hidden="true" id="touXiang">span>a> <div id="loginBox" > <form id="loginForm"> <fieldset id="body"> <fieldset> <label >用户名/手机号label> <input type="text" class="username" id="username"><span id="msg">span> fieldset> <fieldset> <label for="userpaasword">密码label> <input type="password" name="userpaasword" id="userpaasword"> fieldset> <div id="shiBai">div> <p type="submit" id="login" οnclick="checkLogin()">登 录p> <label for="rmbUser"><input type="checkbox" id="rmbUser"><span>记住密码span>label> fieldset> <p>新用户? <a class="sign" href="account.html">注册a> <span>快速登录: <a href="#"><i class="fa fa-qq" aria-hidden="true">i> <i class="fa fa-weixin" aria-hidden="true"> i><i class="fa fa-weibo" aria-hidden="true">i> <i class="fa fa-github" aria-hidden="true">i>a>span>p> form> div> div>css 部分:
/*--Login --*/
/* Login Form */#loginForm { width: 300px; background: #fff; border: 1px solid #d6d6d6;}#loginForm fieldset { margin:0 0 15px 0; display:block; border:0; padding:0;}fieldset#body { border-radius:3px; -webkit-border-radius:3px; -moz-border-radius:3px; -o-border-radius:3px; padding:15px 15px; margin:0;}#loginForm #checkbox { width:auto; margin:3px 6px 0 0; float:left; padding:0; border:0; *margin:-3px 9px 0 0; /* IE7 Fix */}#body label { color:#87807c; margin:10px 0 0 0; display:block; text-align: left; font-size:12px;}#loginForm #body fieldset label{ margin:0 0 6px 0;}#body label { color: #87807c; font-size: .2em; /*outline:1px solid darkorange;*/}#username{ position: relative;}#msg{ position: absolute; width: 140px; height: 20px; top:45px; left:150px; /*outline:1px solid gold;*/}#login{ position:relative;}#shiBai{ width: 140px; height: 30px; color:#F07818; /*outline: 1px solid #4a1aff;*/ position: absolute; left:60px; bottom:160px;}.copyrights{ text-indent:-9999px; height:0; line-height:0; font-size:0; overflow:hidden;}/* Default Input */#loginForm input { width:100%; border:1px solid #DADADA; color: #222; background:#FFF; padding:6px; font-size: 0.4em; -webkit-apperance:none; /*outline:1px solid red;*/}#loginForm input:focus{ box-shadow: inset 0 1px 1px rgba(105, 105, 105, 0.07), 0 0 3px #e87619;}/* Sign In Button */#loginForm #login { background: #F07818; color: #fff; font-size: 1em; padding: 5px 20px; border: 1px solid #F07818; transition: all .5s; -webkit-transition: all .5s; -moz-transition: all .5s; -o-transition: all .5s; font-family: 'Roboto-Regular'; /*outline:1px solid #41b1ff;*/}#loginForm #login:hover { background: #fff; color: #F07818;}/* Forgot your password */#loginForm p { font-size: 0.9em; margin-bottom: 1.2em; color: #999; /*outline:1px solid #7816ff;*/}.header-right #loginForm a.sign { display: inline-block !important; color: #F07818; margin-right: 10px; border-right: 1px solid; padding-right: 10px; /*outline:1px solid #d92dff;*/}.header-right #loginForm a.sign:hover { color: #5D4B33;}#loginForm span a { color: #999; display: inherit;}#loginForm span a:hover{ text-decoration:underline; color:#F07818;}input:focus { outline:none;}.header-right.login:hover #loginBox { display:block;}#msg{ color: white; } .nonee{ display: none; }#loginContainer { position:relative;}#loginContainer a span{ display:block;}/* Login Button */#loginButton { display:inline-block; position:relative; z-index:30; cursor:pointer;}/* Login Box */#loginBox { position: absolute; top: 98%; right:14.5%; /*display:none; /!* ����ٻָ�*!/*/ z-index: 99;}#loginForm:after { content: ''; position: absolute; right: 9%; top: -9px; border-left: 10px solid rgba(0, 0, 0, 0); border-right: 10px solid rgba(0, 0, 0, 0); border-bottom: 10px solid #FFFFFF;
当点击 最终的登录按钮,触发 前台向 服务器发起ajax请求 将用户输入的用户名 和密码 传递到服务器,服务器链接后台数据库,比对数据,如果查询到有这一组数据,说明是正确的,返回 data结果给前台 的ajax 回调函数,然后操作dom 渲染页面(跳转到主页面)
如果前台ajax 回掉函数 里面的 收到的后台返回来的data数据是 自己在路由层设定好的错误信息提示,那么就 操作dom: 弹出一个遮罩层 上面内容提示用户登录失败
另一个ajax请求 是检查登录状态、判断用户是否是第二次登录,二次登录的时候 让浏览器暂时保存用户的名字,提示 欢迎 某某某:
<script> // 点击登录,检查登录成功与否 function checkLogin(){ $.ajax({ type:"post", url:"login.do", data:{uname:$("#username").val(), upwd:$("#userpaasword").val() }, success:function(data){ console.log(data); if(data=="登录失败,请重新登录"){ $("#shiBai").html(data); }else{ $("#mssg").html(data); $("#shiBai").html(""); $("#loginBox").addClass("nonee") //登录成功后 隐藏登陆框 } } }) } // 检查登录状态,第二个ajax请求 发送session到后台 $(function(){ // 第二次打开页面 发起新的ajax请求 检查登录状态 $.ajax({ type:"post", url:"checklogin.do", success:function(data){ // console.log(data); if(data.length>0){ $("#loginBox").hide() $("#mssg").html(data); //显示登录状态 欢迎某某 $("#touXiang").mouseover(function(){ //移入头像 隐藏登陆框 $("#loginBox").hide() }) $("#touXiang").click(function(){ //点击头像 跳转到个人中心页面 window.location.href = '/index.html?u=pagePersenal'; }) } } }) }) //重新获取焦点时,"登录失败" 提示消息不显示 $("#userpaasword").focus(function(){ $("#shiBai").html(""); }) //登录时,手机号码 空值判断、正则判断 $("#username").focus(function(){ //获取焦点时,msg不显示 $("#msg").html(""); $("#shiBai").html(""); console.log("AAA") }); $("#username").blur(function(){ //失去焦点时,发送ajax请求,数据库对比数据, var reg2=/^1[3|4|5|7|8][0-9]\d{8}$/; if(this.value ==""){ console.log("进来了") $("#msg").html("*请输入登录手机号"); $("#msg").css("color","red"); } else{ if(!reg2.test(this.value)){ //如果当前用户输入的值 非真(不符合手机号规则) $("#msg").html("*手机号格式错误,请重新输入") $("#msg").css("color","red"); } } }); script>
以下是:主程序app.js 搭建服务器,并拦截 前台发起的ajax请求, 然后路由express模块 分发到相应的路由功能模块: uerModule.js登录模块、 msgModule.js注册模块
/** * Created by hasee on 2017/4/21. */ const myexpress =require("express"); //加载express的资源 /*使用路由文件*/ const userM =require("./routes/userModule.js"); const msgM = require("./routes/msgModule.js"); const app = myexpress(); //执行express的全局函数,返回一个express的服务器对象 const ejs = require("ejs"); //ejs 模板引擎 /*session cookie*/ //npm install express-session --save-dev //npm install cookie-parser --save-dev const session = require("express-session"); const cookieParser = require("cookie-parser"); app.configure(function(){ /*1.调用cookieParser()*/ app.use(cookieParser()); /*2.Session配置*/ app.use(session({ secret:"1234", //秘钥 随机得到的密码存进去 name:"cookieName128", //cookie名称 cookie:{maxAge:30000}, // 失效过期时间 单位毫秒,30s rolling:true, //从第一次访问该网页时,第一次开始会话开始算 更新session-cookie失效时间 resave:true // 刷新后重新保存一下 })); app.set("views",__dirname+"/public/views"); //配置视图模板在哪个位置 app.set("view engine","ejs"); //__dirname :全局变量,代表的是项目根路径 app.use(myexpress.logger("dev")); //日志 app.use(myexpress.bodyParser()); //把提交的数据,封装到request中的body里 app.use(myexpress.methodOverride()); //非get请求转换为post请求 如:put , head , delete app.use(app.router);//先执行用户设置的,在访问静态页面,这一个配置写在静态资源配置的前面 app.use(myexpress.static(__dirname+"/public")); //设置静态资源路径 //app.use(myexpress.favicon(__dirname+"/public/test.jpg")); //小图标 app.use(myexpress.errorHandler()); //设置错误模块 }); app.set("port",8883); app.listen(app.get("port"),function(){ console.log("服务器已经启动"+app.get("port")); }); app.post("/login.do",userM.login); //拦截前台发来的ajax请求 指向路由模块的login函数 app.post("/checklogin.do",function(req,resp){ // 检查登录状态 let myuser = req.session.myuser; console.log(myuser) resp.send(myuser) }); //app.get("/login",function(req,resp){ // /* resp.rander() 把要 渲染的html和渲染后的数据一起返回给前台*/ // //var uname="张三"; // //resp.render("login",{username:uname}); // resp.render("login.ejs",{}) //}) // app.post("/sendMsg.do",msgM.sendMsg); //拦截 发送短信请求 app.post("/yanZheng.do",msgM.yanZheng);// 拦截 验证短信请求 app.post("/checkPhone.do",msgM.checkPhone); // 拦截 手机号可以注册否
登录路由功能模块:uerModule.js
/** * Created by hasee on 2017/4/21. */ "use strict"; const db = require("./configdb.js"); // 引入 封装后的专门链接数据库的模块 let userModule = { // 点击登录 提交用户名 和密码到后台,后台连接数据库 验证账号 和密码 login: function (req, resp) { console.log("进入了路由层"); let username = req.body.uname; //接收前台传来的值 并打印出来看看 let password = req.body.upwd; console.log(username); console.log(password); // 连上数据库 比对数据,查找 数据库有没有用户输入的数据 db.connection("select * from g_login where n_username=? and n_userpsw=?", [username, password], function (err, data) { //把查到的数据放进 data 里面 //console.log(err); //console.log(data); //if( req.session.myuser){如果有session,那么直接跳转到主页 ////}else{ 如果没有session,那么跳转到登录页面} console.log("查询mysql完毕调用的回调函数"); if (data.length > 0 && data != undefined){ // 当数据库里面有 数据的时候,则 req.session.myuser= data[0].n_username; // 先把data对象里面的第一条的用户名 存到session里面 resp.send(req.session.myuser) } else { resp.send("登录失败,请重新登录"); } }) } } /*把module.exports指向于userModule这个对象*/ module.exports=userModule;
注册页面的效果:
手机号码正则判断、手机发送验证码 、失效时间倒计时 和 动态获取设置的密码强度判断:
注册页面结构 html : account.html:
<div class="account"> <div class="container"> <div class="register"> <form> <div class="register-top-grid registerMain"> <h3>手机号码快速注册h3> <div class="input"> <span class="headText">手机号<label>*label>span> <input type="text" id="regPhone" placeholder="请输入有效的手机号码" ><span id="err_msgPhone">span> div> <div class="input"> <span class="headText">请输入校验码<label>*label>span> <input type="text" class="yanZM" id="yanZM"> <input type="button" class="btn c-333 text-c js-code" value="获取验证码" id="sendYzm"> div> <div class="input"> <span class="headText">密码<label>*label>span> <input name="password" type="text" class="inPsw" id="regPsw" onKeyUp="CheckQiang(this.value)" placeholder="请输入6~16位字符:(大小写字母+数字)组合" > <table border="0" cellpadding="0" cellspacing="0"> <tr align="center"> <td id="pwd_Weak" class="pwd pwd_c"> td> <td id="pwd_Medium" class="pwd pwd_c pwd_f">无td> <td id="pwd_Strong" class="pwd pwd_c pwd_c_r"> td> tr> table> <span id="err_msgPsw">span> div> <div class="clearfix"> div> div> form> <div class="clearfix"> div> <div class="register-but"> <form> <input type="button" id="btnRegister" value="注 册" οnclick="checkCode()"> <div class="clearfix">div> form> div> div> div> div>
注册页面 的样式css:
<style> #regPhone{position: relative;} #err_msgPhone{ position: absolute; left:750px;top:278px; display: inline-block; width: 250px; height: 35px;text-align: center;line-height: 30px; color: #fd811a; /*border: 1px solid #807741;*/ } .input:nth-child(3){ /*outline: 1px solid green;*/ margin-top: 6%; margin-bottom: 8%; } .headText{ font-size:24px; /*font-size: 0.4em;*/ font-weight: 800; } #regPhone:focus,.yanZM:focus,#regPsw:focus{ box-shadow: inset 0 1px 1px rgba(105, 105, 105, 0.07), 0 0 4px #fd811a; } #sendYzm{ background-color: #fd811a; color: white; border-radius: 0px; } #regPsw{position: relative; /*width: 44%;*/ } #err_msgPsw{ position: absolute; left:740px;top:522px; display: inline-block; width: 270px; height: 35px;text-align: center;line-height: 30px; color: #fd811a; font-size: 15px; /*border: 1px solid #801c24;*/ } #msgYanZM{ position:absolute; left:740px;top:374px; display: inline-block; width: 270px; height: 35px;text-align: center;line-height: 30px; color: #fd811a; font-size: 15px; border: 1px solid #4e6b80; } /*设置密码强度检测*/ .pwd{width:40px;height:20px;line-height:14px;padding-top:2px;} .pwd_f{color:#BBBBBB;} .pwd_c{background-color:#F3F3F3;border-top:1px solid #D0D0D0;border-bottom:1px solid #D0D0D0;border-left:1px solid #D0D0D0;} .pwd_Weak_c{background-color:#FF4545;border-top:1px solid #BB2B2B;border-bottom:1px solid #BB2B2B;border-left:1px solid #BB2B2B;} .pwd_Medium_c{background-color:#FFD35E;border-top:1px solid #E9AE10;border-bottom:1px solid #E9AE10;border-left:1px solid #E9AE10;} .pwd_Strong_c{background-color:#3ABB1C;border-top:1px solid #267A12;border-bottom:1px solid #267A12;border-left:1px solid #267A12;} .pwd_c_r{border-right:1px solid #D0D0D0;} .pwd_Weak_c_r{border-right:1px solid #BB2B2B;} .pwd_Medium_c_r{border-right:1px solid #E9AE10;} .pwd_Strong_c_r{border-right:1px solid #267A12;} /*注册成功后的弹框 */ #Tankuang,#Tankuang2{ position: fixed; top:0; left:0; z-index: 333; width: 100%; height: 100%; background-color: rgba(0,0,0,.7); display: none; } .tanMain,.tanMain2{ background-color: #f0e5ff; width: 300px; height: 200px; margin: 20% auto; text-align: center; border-radius: 5px; } .tanMain h3,.tanMain2 h3{ padding-top: 24%; } .tanMain button,.tanMain2 button{ border: none; border-radius: 5px; margin-top: 10%; width: 150px; height: 30px; background-color: #ff9832; } style>
<script src="js/jquery-1.11.3.js">script> <script> // 注册时,手机号 按正则格式输入 , 且检查已存在否 $("#regPhone").focus(function(){ $("#err_msgPhone").html("") }) $("#regPhone").blur(function(){ var reg1=/^1[3|4|5|7|8][0-9]\d{8}$/; if(!reg1.test(this.value)){ //如果当前用户输入的值 非真(不符合手机号规则) $("#err_msgPhone").html("*请输入正确的手机号") }else if(reg1.test(this.value)){ // 发起请求 提交到后台去 比对用户名存在与否.... checkPhone(); function checkPhone(){ $.ajax({ type:"post", url:"/checkPhone.do", data:{userPhone:$("#regPhone").val()}, success:function(data){ $("#err_msgPhone").html(data) } }) } } }) //发送验证码 发起ajax请求,后台拦截这个请求 $("#sendYzm").click( function(){ var phone=$("#regPhone").val(); console.log(phone); $.ajax({ url:"sendMsg.do", type:"post", data:{phone:phone}, success:function(data){ console.log(data); } }) }) //点击 发送验证码 的同时-----倒计时开始 var countdown=60; $("#sendYzm").click(function settime(e){ if(countdown == 0){ e.target.removeAttribute("disabled"); e.target.value="获取校验码"; countdown =60; return; }else{ e.target.setAttribute("disabled",true); e.target.value="重新发送("+countdown+"s)"; countdown--; } setTimeout(function(){settime(e);},1000); }) // 密码强度判断 function CheckQiang(pwd){ var Mcolor,Wcolor,Scolor,Color_Html; var m=0; var Modes=0; for(var i=0; i<pwd.length; i++){ var charType=0; // 字符类型 ASCII类型 var t=pwd.charCodeAt(i); //返回字符串pwd第i个字符的 Unicode 编码 if(t>=48 && t <=57){charType=1;} else if(t>=65 && t <=90){charType=2;} else if(t>=97 && t <=122){charType=4;} else{charType=4;} Modes |= charType; } for(var i=0;i<4;i++){ if(Modes & 1){m++;} Modes>>>=1; } if(pwd.length<=4){m=1;} if(pwd.length<=0){m=0;} switch(m){ case 1 : // 如果输入的密码位数少于5位,那么就判定为弱 Wcolor="pwd pwd_Weak_c"; //如果输入的密码只由数字、小写字母、大写字母或其它特殊符号当中的一种类型组成,则判定为弱 Mcolor="pwd pwd_c"; Scolor="pwd pwd_c pwd_c_r"; Color_Html="弱"; document.getElementById("err_msgPsw").style.display="block"; document.getElementById("err_msgPsw").innerHTML = "*您的密码强度太弱了,建议重设"; break; case 2 : //如果密码由数字、小写字母、大写字母或其它特殊符号当中的两种类型组成,则判定为中 Wcolor="pwd pwd_Medium_c"; Mcolor="pwd pwd_Medium_c"; Scolor="pwd pwd_c pwd_c_r"; Color_Html="中"; document.getElementById("err_msgPsw").style.display="block"; document.getElementById("err_msgPsw").innerHTML = "*您的密码强度一般,可以重设"; break; case 3 : //如果密码由数字、小写字母、大写字母或其它特殊符号当中的三种类型以上组成,则判定为强 Wcolor="pwd pwd_Strong_c"; Mcolor="pwd pwd_Strong_c"; Scolor="pwd pwd_Strong_c pwd_Strong_c_r"; Color_Html="强"; document.getElementById("err_msgPsw").innerHTML = "您的密码很强 很不错!"; break; default : Wcolor="pwd pwd_c"; Mcolor="pwd pwd_c pwd_f"; Scolor="pwd pwd_c pwd_c_r"; Color_Html="无"; document.getElementById("err_msgPsw").style.display="none"; break; } document.getElementById('pwd_Weak').className=Wcolor; document.getElementById('pwd_Medium').className=Mcolor; document.getElementById('pwd_Strong').className=Scolor; document.getElementById('pwd_Medium').innerHTML=Color_Html; } //点 注册,先检查 验证码正确否? ----> 正确再发起ajax, // 将电话和密码一起提交后台,数据库添加数据 function checkCode(){ var yanZM = $("#yanZM").val(); //获取输入的验证码的 值 if(yanZM == null || yanZM == ''){ $("#msgYanZM").html("验证码不能为空!"); }else{ $.ajax({ type: "post", // 用POST方式传输 url: "yanZheng.do", // 目标地址 data:{ phone:$("#regPhone").val(), yanZm:$(".yanZM").val(), psw:$("#regPsw").val() }, success: function (data){ console.log("验证码验证ok之后返回给前台的data:"+data); if(data == "yes"){ $("#Tankuang").show(); //注册成功后 前台的变化 }else if(data == "no"){ $("#Tankuang2").show();//注册失败后 前台的弹框 } } }); } }; //注册成功失败后的弹出框 $("#ok").click(function(){ $("#Tankuang").remove(); }); $("#no").click(function(){ $("#Tankuang2").remove(); }) script>
注册 路由功能模块msgModule.js: 其中有向手机发送验证码 用户收到云端发来的验证码,并且输入正确的手机验证码之后,才会连同 用户名(手机号)+设置的密码 一同提交到后台,加进后台数据库
/** * Created by hasee on 2017/4/21. */ "use strict"; // app id VzqpaQR1XG0IJ87wuYGgy8r9-gzGzoHsz // App Key Nfzq4VQlpz5ig0H4bz6BPK5I // 用老杨的短信平台测试密码 // appId:"6WFhctzJ8lUqXPtySoY8gvMB-gzGzoHsz", // appKey: "1ytqz7vdgjp68yUEswX4LEbD" const db = require("./configdb.js"); // 引入 封装后的专门链接数据库的模块 const AV = require("leanengine"); AV.init({ // 申请的免费短信云服务 https://leancloud.cn appId:"VzqpaQR1XG0IJ87wuYGgy8r9-gzGzoHsz", appKey: "Nfzq4VQlpz5ig0H4bz6BPK5I" }); let msgModule= { sendMsg: function (req, resp) { let phone = req.body.phone; console.log(phone) /*调用requestSmsCode 发送短信接口*/ AV.Cloud.requestSmsCode({ // 详见API https://leancloud.cn/docs/sms_guide-js.html mobilePhoneNumber: phone, name: '厨师定制', op: '账号注册', ttl: 10 //验证码过期时间,单位是分钟 }).then(function (data) { //发送成功后执行的回调函数 console.log(); resp.send("验证码已发送") }, function (err) { //发送失败后执行的回调函数 console.log(err); resp.send("验证码发送失败" + err) }); }, // 调用验证 验证码的接口 verifySmsCode(验证码,手机号) yanZheng: function (req, resp) { let phone = req.body.phone; let yanZm = req.body.yanZm; let psw = req.body.psw; console.log(phone); console.log(psw); AV.Cloud.verifySmsCode(yanZm,phone).then(function(data){ //验证码输入正确后执行的回调函数 //console.log(data); // 验证码成功之后 再把账号密码 添加写进数据库 //console.log("验证码正确之后打印data"+data); db.connection("INSERT INTO g_login VALUES (NULL,?,?)", [phone,psw],function(err,data){ //console.log(err); console.log(444444); resp.send("yes"); }) }, function(err){ //验证失败后的回调操作 console.log(err); resp.send("no"); }); }, // 检查 手机号是否已被注册 checkPhone: function (req, resp) { let phone = req.body.userPhone; console.log(phone); db.connection("select n_username from g_login where n_username=? ", [phone], function (err, data) { console.log(data); if (data.length > 0 && data != undefined) { resp.send("*该用户已存在"); }else{ resp.send("该账号可以注册"); } }) } }专门用于 服务器路由层 连接数据库的模块:configdb.js (已封装,直接传递参数,即可连接数据库)module.exports=msgModule; // 公开这个路由对象,里面的方法才可以被调用
/** * Created by hasee on 2017/4/21. */ "use strict"; const mysql = require("mysql"); exports.connection=function(sql,arr,fn){ console.log("进入mysql"); let myConnection = mysql.createConnection({ host:"localhost", user:"root", password:"root", port:"3306", //默认不写就是3306 database:"logindb" }); myConnection.connect(); myConnection.query(sql,arr,fn); myConnection.end(); }