当前位置: 首页 > 后端技术 > Node.js

Node&SinglePageApplication让我们来做一个完整的用户系统吧!

时间:2023-04-03 15:23:34 Node.js

1。开场白用户系统是许多网站的基础。本文主要讲解如何编写一个基于Node的单页应用用户系统。本用户系统的功能包括:注册、登录、自动登录、忘记密码、修改密码、邮箱激活。如果在后端使用模板引擎,而不是前后端分离,用户系统似乎并没有那么复杂。在这个Nodejs教程中已经详细介绍过了(这是一个不错的Nodejs教程)。但是如果选择前后端分离的方案,比如接下来要介绍的SPA,那么用户系统该怎么办呢?在模板引擎的方案中,实际上封装了session/cookie,所以操作比较简单。但后者不同。这就需要我们对HTTP相关的概念有一个更清晰的认识。要求会更详细。2.基础知识先介绍一些基础知识。话不多说,要深入了解Cookie,SessionAuthentication的机制非常重要。2.1HTTP2.1.1Cookie&Session众所周知,HTTP是一个无状态的协议。这意味着如果您发送两个相同的请求,您将收到完全相同的响应。然而,在现实生活中,这显然不符合很多场景。因为虽然每个人都点击了按钮,但我是Harry而她是Clara,我们应该收到不同的内容。服务器需要区分我们,这就是cookie发挥作用的地方。我发送一个请求,服务器在响应中添加一个Set-Cookie,并在我们的浏览器中设置一个cookie(点击devtool->Application->Cookies查看),下次我发送请求时,我的header中会包含cookie已设置,当服务器看到cookie时,服务器知道我是Harry。这样就完成了一次认证。但是随之而来的还有一个问题:服务器资源是极其宝贵的,如果每次都进行认证,会造成资源的浪费。另外,如果我想在当前session中临时存储一些信息,存储在cookie中会非常浪费。所以会议来了。session是当前用户的会话信息。它需要用到cookie,但不需要把所有的信息都放在cookie里,只要一个标记就可以了。会话信息存储在服务器上,可以存储在缓存中,数据库中或Redis之类的东西(从未使用过..)。例如,Express-session中的会话标识符是一个名为connect.sid的cookie。此cookie是随机生成的唯一序列代码。每次用户发起请求时,cookie都会跟随到服务器。服务端查看用户的connect.sid,然后从内存、缓存、数据库或者Redis中找到相应的信息,再通过中间件进一步添加到请求中。这样,服务器就可以使用特定于该用户的信息,而无需多次身份验证。因此,cookies是整个用户机制的核心。下面简单介绍一下相关的headers。2.1.2Set-CookieSet-Cookie是请求的头部。标头的格式为NAME=VALUE并以分号“;”分隔。其中几个设置比较常用:expires=Date(设置cookie的过期时间)secure(只在https下使用)HttpOnly(这样cookie就不能被客户端JavaScript修改)maxAge(cookie保留时间,在毫秒)2.2Node.js关于cookie的读取和设置cookie在Nodejs中非常方便。在Express中添加中间件cookie-parser,可以直接将cookie对象赋值给req。在路由回调函数中操作时,可以直接使用req.cookie获取客户端的cookie值。设置客户端的cookie,需要使用res.cookie函数来设置://设置cookie中的name值为nameres.cookie('name',name,{maxAge:1000*60*60*24*30、path:'/',httpOnly:false})session机制Express的session实现需要一个中间件:varsession=require('express-session')app.use(session({secret:settings.cookieSecret,//设置密码"seed”store:newMongoStore({url:'mongodb://localhost/color'//这里使用数据库存储session,如果不设置,会占用内存}),resave:true,saveUninitialized:true}))关于session在Nodejs教程中有介绍,具体比如用户登录后,可以设置req.session.user="harry",然后可以先判断in中有没有req.session在后续所有需要用户登录的场景中,user此项。这样就完成了一个区分,不需要重新认证。2.3前端这里预设的是做一个单页应用。如果使用模板引擎,使用render可以轻松完成登录等功能,但是如果要写一个前后端分离的应用,比如SPA,就得用AJAX来发送和接收用户信息。无论使用什么库来发送和接收AJAX,需要注意一点:即发送的AJAX请求必须包含credentials:'include'以确保可以携带cookie发送到后端,否则req后端的.cookie将不会被接收。3.实例说明3.1确认对于需要确认用户已经登录后才能使用的路由,需要添加一个中间件。这个中间件的作用是检查req.session.user是否已经定义。一般来说,用户登录后,需要设置req.session.user,表示已经登录。functionauthorize(req,res,next){if(req.session.user){next()}else{res.status(401).send({errorMsg:"Unauthorize"})}}3.2Registration对于已注册的过程需要以下步骤。在收到用户的用户名和电子邮件地址后,您需要在数据库中进行搜索。如果您发现相同的姓名或使用电子邮件地址,则必须通知用户该姓名相同。如果没有重名,发邮件到邮箱验证,同时创建一个闲置账号。还有一点需要注意的是,最好不要直接存入密码。建议先加密。这里涉及到多重嵌套异步,可以使用我之前写的这篇文章的co,也可以使用async/await。后面要用回调函数会很难写...function*registerGen(req,res,newUser){try{//检查是否有同名的constuserOfSameName=yieldnewPromise(function(resolve,reject){User.get("NAME",req.body.name,function(err,user){if(err)reject(err)resolve(user)})})//检查是否要重新注册使用相同的电子邮件地址constuserOfSameEmail=yieldnewPromise(function(resolve,reject){User.get("EMAIL",req.body.email,function(err,user){if(err)reject(err)resolve(user)})})//如果是以上两种情况,则发送错误信息。if(userOfSameName){returnres.status(200).send({errorMsg:"此账号已被注册。"})}elseif(userOfSameEmail){returnres.status(200).send({errorMsg:"Thisemailhasalreadybeenregistered."})}//如果成功,创建一个非活动帐户yieldnewPromise(function(resolve,reject){newUser.save(function(err,user){if(err){console.log("Registererror:",err)reject(err)}resolve(user)})})//发送ActivateemailyieldnewPromise(function(resolve,reject){constnameHash=crypto.createHmac('sha256',SECRET).update(req.body.name).digest('hex')constemailHash=crypto.createHmac('sha256',SECRET).update(req.body.email).digest('hex')constbase="http://colors.harryfyodor.tk/activate/"//打开这个链接后,可以立即激活Anajax更新数据库并激活帐户。constlink=`${base}${req.body.name}/${nameHash}|${emailHash}`User.activate({subject:'Colors验证邮件',html:'如果你还没有注册Colors,请忽略此电子邮件。单击下面的链接激活您的帐户。
\激活链接',to:req.body.email},function(err){if(err)reject(err)res.send({ok:true})resolve()})})}catch(e){//如果有错误,就在这里发起,方便调试returnres.status(500).send({msg:"ERROR"})console.log('Error',e)}}functionregister(req,res){//密码需要加密首先,不推荐明文存储。varmd5=crypto.createHash('md5'),password=md5.update(req.body.password).digest('hex');//创建用户,其中User是模型(后端MVC的M)的构造函数。varnewUser=newUser({name:req.body.name,password:password,email:req.body.email})//使用co函数实现同步写入和异步co(registerGen(req,res,newUser))}3.3登录用户登录需要以下步骤,代码不再赘述。这需要非常繁琐的判断语句,但是理解起来却非常简单。3.4邮件通知激活用户需要使用nodemailer库,非常方便易用。您可以在官方网站上查看。如果使用163邮箱作为发送邮箱,一定要特别注意,即密码是否是网易的授权密码。这个需要在163邮箱里设置,然后代码里用到授权密码。这一点需要格外注意。functionsendEmail(detail,callback){varconfig_email={host:'smtp.163.com',post:'25',auth:{user:'example@163.com',pass:'**********'//该密码不是邮箱密码,请先到邮箱设置授权密码。}}vartransporter=nodemailer.createTransport(config_email)vardata={from:config_email.auth.user,to:detail.to,subject:detail.subject,html:detail.html}//异步发送邮件transporter.sendMail(data,function(err,info){if(err){console.log("SendEmailError",err)回调(err)}else{console.log("Messagesent:"+info.response)callback(null);}})}4.总结当然,这个用户登录系统还有很多需要改进的地方(比如安全问题等)。除此之外,还有很多功能需要添加。比如修改密码,比如修改密码等等,看完上面的内容,其实要完成这些功能还是很简单的。有兴趣的可以看看我自己写的一个网站Colors,这是一个基于React和Nodejs的网站,有完整的用户体系。有不懂的可以参考~文章中如有错误或不当欢迎指出,互相交流学习~谢谢阅读~