从先后端分别学习——注册/登陆流程1

今天来研究一个小小的功能。当咱们进入一个网站,它怎么判断我是否是它的用户?让用户登陆呗,若是它能正常登陆,它就是个人用户呗?你有没想过它是怎么判断我是否是它用户的?此次就来从先后端来说一讲是怎么来实现这个功能的。css

注册

注册通常流程能够简单的分为填写信息,验证信息,提示用户,写入数据库,注册成功,大体流程以下图所示。html

这里用 JS 完成最简单的注册流程,跑通逻辑,实际工做中远比这复杂。前端

简化验证环节,只检查邮箱是否输入正确node

注册页面

首先准备一个最简单的注册页面如,上图所示。ajax

CSS 这里有两个注意点:数据库

  1. labellabel::after不一样字数的文字,两端对齐
  2. labelinput居中对齐用vertical-align:middle
*{padding:0;margin:0;box-sizing:border-box;}
body{
    display: flex;
    justify-content: center;
    align-items: center;
    height:100vh;
}
.sign_in_form{
    border:1px solid red;
    padding:20px;
    width:400px;
}
.row{
    margin-bottom: 10px;
}
h1{
    text-align: center;
}
input{
    vertical-align: middle;
}
label{
    vertical-align: middle;
    /*border:1px solid green;*/
    width:5em;
    display: inline-block;
    height:20px;
    line-height:20px;
    overflow: hidden;
    text-align: justify;
}
label::after{
    content:'';
    display: inline-block;
    /*border:1px solid blue;*/
    width:100%;
}

HTML 文件:编程

<form class="sign_in_form">
    <h1>注册</h1>
    <div class="row">
        <label for="email">用户名</label>
        <input type="text" id="email" name="email">
        <span class="error"></span>
    </div>
    <div class="row">
        <label for="password">密码</label>
        <input type="password" id="password" name="password">
        <span class="error"></span>
    </div>
    <div class="row">
        <label for="password_confirmation">确认密码</label>
        <input type="password" id="password_confirmation" name="password_confirmation">
        <span class="error"></span>
    </div>
    <div class="row">
        <input type="submit" value="注册">
    </div>
</form>

server 文件写一个路由:当咱们访问首页时,跳转页面(这里默认跳转注册页面)json

if (path === '/'){
    let string = fs.readFileSync('./signUp.html','utf8')
    response.setHeader('Content-Type','text/html;charset=utf-8')
    response.statusCode = 200
    response.write(string)
    response.end()
}

至此一个简单的登陆页面就完成了,当咱们点击注册按钮时,就会像服务器发送一个请求。segmentfault

发起 POST 请求


从上图中咱们能够看到,form表单能够发送一个GET,请求体变成查询参数附在URL上,这是GET请求的一个特性,后台经过读取查询参数就能够获知请求信息。后端

这里就产生了一问题,帐户密码放在URL上太不安全了,别人一眼就能看到个人密码,这样确定不行。

固然form表单能够发起POST请求,但咱们这里用ajax发送请求

let $signInForm = $('.sign_in_form')
let userInfoHash ={}
$signInForm.on('submit',function(e){
    e.preventDefault()
    let findUser = ['email','password','password_confirmation']
    findUser.forEach((key)=>{
        let value = $(this).find(`input[name=${key}]`).val()
            userInfoHash[key] = value
        })
    $.post('/sign_up',hash).then(
        (response)=>{console.log(response)},
        (response)=>{console.log(response)}
    )
})

当点击注册按钮时,经过findUser对象提供的key,找到对应的value,用户所填写的信息,将被保存到userInfoHash中,经过POST请求传递给服务器。

服务器端作个路由,当我请求路径为sign_up且为POST请求,里面才会执行。

if(path === '/sign_up' && method === 'POST'){
    let body = []
    request.on('data',(chunk)=>{
        body.push(chunk)
    }).on('end',()=>{
        body = Buffer.concat(body).toString()
        console.log(body)
    })
    response.statusCode = 200
    response.end()
}

HTTP传送方法是将数据一段一段上传,因此在服务器端须要分别获取数据,而后在将他们拼接成一块儿,转变成后端须要的字符串。

上面的写法有个问题——点击按钮发送请求后,客户端一直收不到响应,就会报错

Promise

其实HTTP传送的时是一个异步的过程,里面还没执行完,外面就已经执行了,这边能够用Promise来解决下这个问题

function readBody(request) {
    return new Promise((resolve,reject) =>{
        let body = []
        request.on('data',(chunk)=>{
            body.push(chunk)
        }).on('end',()=>{
            body = Buffer.concat(body).toString()
            resolve(body)
        })
    })
}

readBody内部返回一个Promise对象,成功调用resolve函数,失败调用reject函数,这边就默认它会成功。

因此上面代码能够改写成:

if(path === '/sign_up' && method === 'POST'){
    readBody(request).then(
        (body)=>{
            console.log(body)
            response.statusCode = 200
            response.end()
        })
}

调用readBody函数后,由于Promise返回的是一个对象能够直接在后面用.then()操做,成功执行前面resolve函数,失败执行后面reject函数,不过这里要注意,若是真出错了真正的错误信息在第二个.then()resolve函数里,以下所示:

readBody(request).then(
        ()=>{console.log('success'),
        ()=>{console.log('错误不执行')}).then(
        ()=>{console.log('error')
    })

验证数据

后端成功拿到数据后,这个数据是字符串的形式,后端须要把它一步步拆解出来。

let bodyArr = body.split('&')
let userInfoHash = {}
bodyArr.forEach((e)=>{
    let part = e.split('=')
    userInfoHash[part[0]] = decodeURIComponent(part[1])
})
console.log(userInfoHash)

将拆分出来的数据一一对应的保存到 userInfoHash里。

从这里咱们不难看出,前端想尽一切办法把数据办成字符串传给后端,后端在想尽一切办法把前端传递来数据拆分红能用的格式。

固然了,后台响应的内容也是,前端拿到也是字符串。

当拿到数据后,应对数据进行验证,是否符合要求,这里简化起见,只验证email中有无@符号与passwordpassword_confirmation,若是正确就注册成功。

response.setHeader('Content-Type','application/json;charset=utf-8')
let {email,password,password_confirmation} = userInfoHash
    if(email.indexOf('@') === -1){
        response.statusCode = 400
        response.write(`{
            "errors":{
                "email":"invalid"
            }
        }`)
    }else if(password !== password_confirmation){
        response.statusCode = 400
        response.write(`{
            "errors":{
                "password_confirmation":"mismatch"
            }
        }`)
    }else{
        response.statusCode = 200
        response.write(`{
            "success":"success"
        }`)
    }

这边要注意的是@符号在nodejs会以%40的形式出现因此这边须要对它进行转码。

这里要注意的是后台提供的响应数据要用json的形式传送给前端,若是格式不肯定,前端那边很难操做,也会形成问题的来源,后台传送数据时只需在响应部分加上响应头response.setHeader('Content-Type','application/json;charset=utf-8'),前端拿到后会有相应的转换方法。

$.post('/sign_up',hash).then(
    (response)=>{
        let {success} = response
        if(success === 'success'){
            window.location.herf = '/sign_in'
        }
    },
    (response)=>{
        let {email,password_confirmation} = response.responseJSON.errors
        if(email === 'invalid'){
            $signInForm.find('input[name=email]').siblings('.error').text('邮箱错误')
        }else if(password_confirmation === 'mismatch'){
            $signInForm.find('input[name=password_confirmation]').siblings('.error').text('密码不匹配')
    }
})

根据后台响应的信息,在页面中提示用户相关信息。

其实在用户提交表单时,前端应该先阻止提交,断定一下用户是否填写正确,在发送请求,这样在用户填写错误时候无需发送请求,节约资源。

if(userInfoHash.email === ''){
    $signInForm.find('input[name=email]').siblings('.error').text('填邮箱呀')
    return
}else if(userInfoHash.password === ''){
    $signInForm.find('input[name=password]').siblings('.error').text('填密码呀')
    return
}else if(userInfoHash.password_confirmation === ''){         
    $signInForm.find('input[name=password_confirmation]').siblings('.error').text('确认密码呀')
    return
}else if(userInfoHash.password !== userInfoHash.password_confirmation){
    $signInForm.find('input[name=password_confirmation]').siblings('.error').text('密码不对呀')
    return
}

前端检测用户有没填写,若是没填写的话,直接提示用户,无需提交后台。

存储数据

注册成功后,把数据写入数据库,这里要注意,用户隐私信息不能直接存储在数据库里,这里为了学习方便,故直接保存。

咱们建立一个简单的文件,当作数据库,数据库是以哈希表的形式存储数据

let usersString = fs.readFileSync('./db/db','utf8') //读取数据库文件
let usersArr
try{
    usersArr = JSON.parse(usersString)    //转化成对象
}catch(exception) {
    usersArr = []  
}
let isUse = false
for(let i = 0; i < usersArr.length; i++){    //遍历 usersArr 
    if(usersArr[i].email === email){    //若是 usersArr 中存在用户的邮箱,已经注册
        isUse = true
        break
    }
}
if(isUse){    //若是邮箱存在响应前端操做提示用户
    response.statusCode = 404
    response.write(`{
    "errors":{
        "email":"isUse"
        }
    }`)
}else{    //若是不存在将注册信息写入数据库
    response.statusCode = 200
    usersArr.push(userInfoHash)    //存入刚刚读取出来的 usersArr
    usersArr = JSON.stringify(usersArr)    // 转变成字符串
    fs.writeFileSync('./db/db',usersArr)    //存入数据库
    response.write(`{
        "successes":{
            "success":"success"
        }
    }`)
}

若是前面都层高,最后进入写数据库环节:读取数据库内容——判断用户是否存在(这边是判断邮箱)——不存在,写入数据库;存在发送响应信息。

至此注册环节所有结束,这边要特别注意,数据库读取出来的内容是字符串

总结

  1. 数据类型在编程中很是重要,刚开始接触时,老把符合JSON语法的字符串当成对象,弄清楚数据类型相当重要

    • 先后端交互——字符串,不论是请求仍是响应,都是字符串
    • 后端与数据库交互——字符串
  2. 没有if...else解决不了问题,若是有加一个for循环
  3. 没有console.log解决不了了bug,若是有那是console.log不够多

网站登陆流程可参阅:从先后端分别学习——注册/登陆流程2

相关文章
相关标签/搜索