在之前的文章中,我们实现了简易的注册系统。但是在主流的生产环境中很少会使用用户名登录,因此我们要对注册系统进行完善,注册时必须使用邮箱和验证邮件,并且每次登录和注册时都需要输入验证码。为了便于接下来的使用,还可以给在数据库中添加用户的头像、用户生日和个人简介等信息空位。
数据库的扩充
之前的数据库中只有用户名和密码两个键,我们需要在原来的数据库的基础上添加一个主键,就是用户的邮箱。
MySQL扩充数据库的指令为alter table TABLE add COLUMN TYPE。后端的接口统一为/edit。但是因为我们在之前创建的表中没有主键,因此主键为空,我们不能重新创建一个新的主键,否则会报错。因此这里的方案是删除原先的数据表,创建一个新的(在生产环境中千万不要这么做!)。SQL删除数据表的语句为drop table TABLENAME。
对后端的注册接口也应该有相应的调整:
app.post('/register',(req, res)=>{
const email = req.body.email;
const username = req.body.username;
let password = req.body.password;
password = SHA512(password).toString();
const addSql=`insert into useraccount (email, username, password) values(?,?,?)`;
const selectSql = `select * from useraccount where email = '${email}' or username = '${username}'`;
connection.query(selectSql,(err,result)=>{
if(result.length!==0){
res.status(400).send('邮箱或用户名已被占用');
}else{
connection.query(addSql,[email, username, password],(err)=>{
if(err){
res.status(500).send(`注册失败, ${err}`);
}else{
res.send('注册成功');
}
})
}
})
})
这样的话当邮箱或用户名已被占用时便会产生提示,注意“或”条件在SQL语句中的写法。
但是在实际的生产环境中,网站的用户名可以重复(例如小红书)也可以不重复(例如b站),本网站采取了用户名可以重复的规则,因此上面的代码并不适用,需要稍作修改,这里就不再给出了。而后端代码要注意,我们上传时的author全部改为了作者的电子邮箱,邮箱名称不能对外暴露,因此后端发回文章数据时需要把对应的author字段改为用户名。
app.get('/getArticle/:id',(req, res)=>{
const id = req.params.id;
const selectSql = `select * from articles where id = '${id}'`;
connection.query(selectSql, (err, result)=>{
if(result.length === 0){
res.status(404).end();
}else{
const article = result[0];
const email = article.author;
const changeUsernameSql = `select username from useraccount where email='${email}'`;
connection.query(changeUsernameSql,(err,username)=>{
article.author = username[0].username;
res.send(article);
})
}
})
})
相应的,/login只能使用邮箱登录,对应的前端代码也需要修改,这里也不再给出了。
注册时的验证与验证码
现在我们直接就能注册,不能验证是否为脚本批量自动注册,因此我们需要一个图形验证码来进行识别。
图形验证码的大致流程如下:后端根据参数生成一个图片文件发送给前端,前端按下注册按钮后,输入的验证码会连同其他数据一起被发送到前端进行校验,首先校验验证码,如果验证码错误会直接返回。
先看后端生成验证码的代码,还是需要一个接口/getCaptcha,对应的包使用的是svg-captcha,同时还需要express-session包来存储session,因为值需要在服务器中存储以供校验。
const session = require('express-session');
app.use(session({
secret: 'keyboard cat',
// session秘钥,用来将其注册到cookie中
}))
app.get('/getCaptcha',(req, res)=>{
const captcha = svgCaptcha.create({
size: 6,
ignoreChars: 'oi10',
noise: 3,
color: true,
});
req.session.captcha = captcha.text;
// 把验证码的文本保存到session中,因为文本需要服务器来保存(需要express-session)
res.type('svg');
res.send(captcha.data);
// 把svg格式的验证码发送到客户端中。
})
对应的前端代码如下。注意到我们这里使用的是v-html,这是因为传回去的数据是一个<span>标签,为了其能够被html来渲染,我们需要使用v-html指令。
<script setup>
function getCaptcha(){
axios.get('/server/getCaptcha')
.then((response)=>{
captcha.value = response.data;
})
}
getCaptcha();
// 在最开始需要获取一个验证码
</script>
<template>
<div class="informationInput">
<h1>注册</h1>
<h2>邮箱</h2>
<input v-model="emailInput"/>
<h2>用户名</h2>
<input v-model="usernameInput"/>
<h2>密码</h2>
<input v-model="passwordInput"/>
<h2>验证码</h2>
<input v-model="captchaInput"/>
<div v-html="captcha" @click="getCaptcha"></div>
<button @click="register">注册</button>
</div>
</template>
同理,我们需要给/login页面也加上验证码的验证。这里就不再重复给出代码了。
node应用中的多文件耦合
在上面的代码中,/login和/register方法体中都有一个验证码是否正确的判断,我们在两段代码中使用了相同的代码片段,因此我们可以进行封装。封装是一定不可能被放在相同的文件中,因此我们可以在根目录中单独创建一个functions/checkCaptcha.js文件。
function checkCaptcha(input, realValue){
return input.toLowerCase() === realValue.toLowerCase();
}
module.exports=checkCaptcha;
module.exprts是模块的导出属性,是一个对象,可以有很多的属性。在这个文件中只有一个函数,因此在index.js中的函数也就是你require语句的返回值。如果你不知道应该怎么样使用对应的模块,就看对应的module.exports即可。
因此在index.js中需要这样引用:
const checkCaptcha = require('./funcitons/checkCaptcha')
if(!checkCaptcha(captcha, req.session.captcha)){
req.session.captcha ='';
res.status(400).send('验证码错误');
return;
}
这样代码的重复度就降低了很多。