从前后端分别学习——注册/登录流程1
今天来研究一个小小的功能。当我们进入一个网站,它怎么判断我是不是它的用户?让用户登录呗,如果它能正常登录,它就是我的用户呗?你有没想过它是怎么判断我是不是它用户的?这次就来从前后端来讲一讲是怎么来实现这个功能的。
注册
注册一般流程可以简单的分为填写信息,验证信息,提示用户,写入数据库,注册成功,大致流程如下图所示。
这里用 JS 完成最简单的注册流程,跑通逻辑,实际工作中远比这复杂。
image
简化验证环节,只检查邮箱是否输入正确
注册页面
image
首先准备一个最简单的注册页面如,上图所示。
CSS 这里有两个注意点:
-
label和label::after不同字数的文字,两端对齐 -
label和input居中对齐用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 文件写一个路由:当我们访问首页时,跳转页面(这里默认跳转注册页面)
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()
}
至此一个简单的登录页面就完成了,当我们点击注册按钮时,就会像服务器发送一个请求。
发起 POST 请求
image
从上图中我们可以看到,
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传送方法是将数据一段一段上传,所以在服务器端需要分别获取数据,然后在将他们拼接成一起,转变成后端需要的字符串。
上面的写法有个问题——点击按钮发送请求后,客户端一直收不到响应,就会报错
image
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中有无@符号与password和password_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"
}
}`)
}
如果前面都层高,最后进入写数据库环节:读取数据库内容——判断用户是否存在(这边是判断邮箱)——不存在,写入数据库;存在发送响应信息。
至此注册环节全部结束,这边要特别注意,数据库读取出来的内容是字符串
总结
- 数据类型在编程中非常重要,刚开始接触时,老把符合
JSON语法的字符串当成对象,弄清楚数据类型至关重要
- 前后端交互——字符串,不管是请求还是响应,都是字符串
- 后端与数据库交互——字符串
- 没有
if...else解决不了问题,如果有加一个for循环 - 没有
console.log解决不了了bug,如果有那是console.log不够多
网站登录流程可参阅:从前后端分别学习——注册/登录流程2