從先後端分別學習——註冊/登陸流程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

相關文章
相關標籤/搜索