项目的源代码及使用方法请点击这里. 运行demo需要先安装node
这篇文章不会贴上全部代码, 只会说下思路. 具体实现还请到github库中看源码.
基本结构
index.html
: 首页
sign_in.html
: 登录页面
sign_up.html
: 注册页面
server.js
: 服务器
db/users
: 模拟数据库, 实质上是一个数组, 每个数组项是一个对象, 表示一个用户
用户可以直接进入首页, 通过登录的方式进入首页. 通过登录的方式进入首页时设置了cookie
, 此时可以再首页看到用户的邮箱.
用户注册的账号会存储在db/users
文件中. 当用户以相同的邮箱注册时会提示用户已注册. 注册时也会验证邮箱的有效性和密码的一致性.
服务器主要实现路由操作. 当使用不同的路由时采取不同的动作.
首页index.html
由于是模拟登录, 因此首页只会展示用户的邮箱(通过cookie
). 若没有cookie
, 则不显示邮箱
<body>
<h1>欢迎 __user__ 登录</h1>
</body>
__user__
是一个占位符, server.js
程序在后台运行. 当用户登录时, server.js
设置__user__
为用户邮箱
string = string.replace('__user__', foundUser.email) //foundUser为登录用户
注册页sign_up.html
我们在进行注册的时候需要输入密码, 而且还要确认这个密码. 因此结构如下:
<form action="" id="signUpForm">
<div class="row">
<label for="">邮箱</label>
<input type="text" name="email">
<span class="message"></span><!--错误处理占位-->
</div>
<div class="row">
<label for="">密码</label>
<input type="password" name="password">
<span class="message"></span>
</div>
<div class="row">
<label for="">确认密码</label>
<input type="password" name="password_confirmation">
<span class="message"></span>
</div>
<div class="row">
<input type="submit" value="注册">
</div>
</form>
对于邮箱, 密码等我们需要进行检验, 比如:
- 邮箱是否有效, 有
@
符 - 邮箱是否未注册.
- 两次密码是否输入一致
- 全部都是必填项.
若全部复合条件, 则使用post
方法向服务器发送请求.
//假设已经引入jQuery库
$form = $('#signUpForm')
$form.on('submit', function(e){
e.preventDefault()
let hash = {}
let need = ['email', 'password', 'password_confirmation']
need.forEach(function(name){
let value = $form.find(`[name=${name}]`).val()
hash[name] = value
})
$form.find('.message').each(function(index, span){
$(span).text('')
})
if(hash['email'] === '') {
$form.find('[name="email"]').siblings('.message').text("请填写邮箱")
return
}
if(hash['password'] === '') {
$form.find('[name="password"]').siblings('.message').text("请填写密码")
return
}
if(hash['password_confirmation'] === '') {
$form.find('[name="password_confirmation"]').siblings('.message').text("请填写密码")
return
}
if(hash["password"] !== hash['password_confirmation']) {
$form.find('[name="password_confirmation"]').siblings('.message').text("密码不匹配")
return
}
$.post('/sign_up', hash)
.then(function(response){
let message = response
console.log(message)
if (message.email && message.email === 'successed') {
$form.find('[name="email"]').siblings('.message')
.text('注册成功')
}
}, (xhr)=>{
console.log(xhr)
//服务器端设置了application/json头
let {message}=xhr.responseJSON
console.log(message)
if (message.email && message.email === 'invalid') {
$form.find('[name="email"]').siblings('.message')
.text('邮箱格式错误')
} else if (message.email && message.email === 'used') {
$form.find('[name="email"]').siblings('.message')
.text('邮箱已被注册')
}
})
})
$.post
方法后面可以接Promise
的方法来根据服务器返回的数据(JSON
)显示结果
登录页sign_in.html
这个页面时登录页面, 因此只需要邮箱和密码就可以了
<form action="" id="signInForm">
<div class="row">
<label for="">邮箱</label>
<input type="text" name="email">
<span class="message"></span>
</div>
<div class="row">
<label for="">密码</label>
<input type="password" name="password">
<span class="message"></span>
</div>
<div class="row">
<input type="submit" value="登录">
</div>
</form>
当然还是需要通过server.js
返回的数据进行验证:
- 账户是否存在
- 密码是否正确
- 全部为必填项
//假设已经引入jQuery
$form = $('#signInForm')
$form.on('submit', function(e){
e.preventDefault()
let hash = {}
let need = ['email', 'password']
need.forEach(function(name){
let value = $form.find(`[name=${name}]`).val()
hash[name] = value
})
$form.find('.message').each(function(index, span){
$(span).text('')
})
if(hash['email'] === '') {
$form.find('[name="email"]').siblings('.message').text("请填写邮箱")
return
}
if(hash['password'] === '') {
$form.find('[name="password"]').siblings('.message').text("请填写密码")
return
}
$.post('/sign_in', hash)
.then(function (response){
window.location.href='/'
}, ()=>{
alert('邮箱或密码错误')
})
})
服务器server.js
server.js
主要实现了以下路由
if (path === '/') {
//返回index.html, 若是用户登录则获取已设置好的cookie
} else if (path === '/sign_up' && method === 'GET') {
//返回注册页
} else if (path === '/sign_up' && method === 'POST') {
//获取注册页的post请求体(用户注册信息), 根据请求体返回响应JSON数据
} else if (path === '/sign_in' && method === 'GET') {
//返回登录页
} else if (path === '/sign_in' && method === 'POST') {
//获取登录页的post请求体(用户登录信息), 根据请求体返回JSON数据, 且设置cookie
}
因为请求体不是一次性返送的, 每次传给服务器的数据大小有要求, 因此我们需要服务器接收完整的请求体才能做出下一步动作. 在server.js
中实现完整读取请求体的函数readBody
.
function readBody(request) {
return new Promise(function (resolve, reject){
let body = [] //请求体
request.on('data', function(chunk){
console.log('服务器正在接收请求体')
// 监听data事件
body.push(chunk);
}).on('end', function(){
// 监听end事件, 当服务器接收POST请求中的全部数据触发
console.log('服务器接收完毕请求体')
body = Buffer.concat(body).toString();
resolve(body)
})
})
}
我们已经知道server.js
的大致架构. 比较重要的是两个处理, 一个是用户注册时的处理, 一个是用户登录时的处理.
用户注册
当用户进行注册时, sign_up.html
首先会自己检测必填项是否已填写. 然后通过post
方法将用户填入的邮箱和密码信息发送给server.js
服务器. 服务器会一次进行以下工作:
- 获取请求体(邮箱, 密码), 注意要解码
- 邮箱检测(
@
符号)及密码一致性检验(即两次密码是否相同) - 遍历
db/users
文件, 检测是否未注册 - 返回响应, 若已注册通知用户重新填写, 否则告诉用户注册成功
当注册成功时, 数据会写入db/users
文件.
假设我们注册的是[email protected]
, 密码为:1
. 那么我们就会在db/users文件中看到如下账号:
[{"email": "[email protected]", "password": "1"}] //注意users的内容是个数组, 每个数组项是对象, 代表一个账户
用户登录
假设用户已经注册好账户了, 在登录页sign_in.html
输入邮箱([email protected]
)和密码(1
). 点击登录会做一下事情:
- 检查必填项
- 向服务器发送响应
- 遍历
db/users
文件, 检测是否存在用户 - 返回响应, 若不存在则告诉用户错误, 否则先设置
cookie
再转向登录页index.html
index.html
读取请求体中的cookie
信息并显示出来
当直接进入首页index.html
时, 不会显示cookie
信息.
cookie
cookie上文中简单提到了. 用户登录时会设置cookie, 登录成功读取cookie值并显示.
当点击sign_in.html页面的登录按钮时, , server.js服务器会将设置cookie
response.setHeader('Set-Cookie', `sign_in_email=${email}`) //设置email的cookie
服务器将设置的cookie返回给浏览器, 因为邮箱及密码没有问题则再发送一次包含cookie的请求, 这次请求是告诉服务器我要进入登录页.
登录页会取出请求体中的cookie并显示.
let cookies = request.headers.cookie.split('; ') //获取请求体中的cookie并分割
let hash = {}
for (let i = 0; i < cookies.length; i++) {
let parts = cookies[i].split('=')
let key = parts[0]
let value = parts[1]
hash[key] = value//hash中存放的是key-value形式的cookie
}
//hash中存在登录账户的cookie
let email = hash.sign_in_email
let users = fs.readFileSync('./db/users', 'utf8')
users = JSON.parse(users)
let foundUser
for (let i = 0; i < users.length; i++) {
if (users[i].email === email) {
foundUser = users[i]
break
}
}
// 将占位符__user__换成cookie值. foundUser是登录账户的cookie
string = string.replace('__user__', foundUser.email)
当用户直接进入index.html页面而不是通过登录方式进入时, 服务器就不会设置cookie. 此时就不显示cookie, 给占位符一个统一的名称即可.
string = string.replace('__user__', '同学')