需要登录状态?因为需要识别用户是谁,否则怎么在网站上看到个人信息呢?为什么需要登录系统?因为HTTP是无状态的,什么是无状态呢?也就是说,这个请求和之前的请求没有任何关系,互不认识,没有关联。我们的网站是依赖HTTP请求服务器获取相关数据的,因为HTTP是无状态的,所以我们无从知道用户是谁。所以我们需要其他方式来保护我们的用户数据。当然,这种无状态的好处就是速度。什么是保持登录状态?比如我在百度A页登录了,但是没有找到记录登录状态的地方。然后进入B页面,如何保持登录状态?必须要带url吗?这绝对不安全。您是否要求用户重新登录?再见?用户体验不友好。所以我们需要找一个地方存放用户的登录数据。这样可以给用户带来良好的用户体验。但这种状态一般都有保质期,主要也是为了安全。为了解决这个问题,饼干出现了。CookieCookie的作用是为了解决HTTP协议的无状态缺陷而做出的努力。Cookie存在于浏览器端。即可以存储我们的用户信息。一般cookie会根据服务器发送的响应中一个名为Set-Cookie的头域信息通知浏览器保存cookie。下次发送请求时,会自动在请求报文中添加Cookie值并发送出去。当然,我们也可以自己操作cookies。如下图(图片来源《图解HTTP》)这样我们就可以通过cookie中的信息与服务器进行通信。服务器如何配合?会议!需要看起来Cookie已经达到了让用户保持登录的效果。但是,将用户信息存储在cookie中显然不是很安全。所以这时候我们需要存储一个唯一标识。这个标识就像一把钥匙,比较复杂,看起来不规则,也没有用户信息。只有我们自己的服务器可以知道用户是谁,其他任何人都无法冒充。这时候Session就出现了,Session中存储了用户会话需要的信息。简单理解主要是存储keySession_ID,利用这个keySession_ID查询用户信息。但是这个标识需要存在于Cookie中,所以Session机制需要使用Cookie机制来达到保存标识Session_ID的目的。如下所示。这时候你可能会想,这个Session有什么用呢?生成一个复杂的ID,存储在服务器上。看来我们自己生成一个Session_ID,它也可以存在于Mysql中!没错,就是这样!我个人认为Session其实已经发展成为一个抽象的概念,在业界已经形成了解决方案。也许它刚出现的时候是有自己的规则的,但是现在已经发展了。随着业务的复杂化,各大公司都已经实施了自己的解决方案。Session_id可以是任何你想要的,它可以存在于任何你想要的地方。一般服务器会缓存Session_id,不会和用户信息表混在一起。一是快速获取Session_id。二是因为前面说了,Session_id是有保质期的,为了安全,过一段时间就会过期,所以放在缓存里就行了。常用的放在redis和memcached中。也有一些情况是放在mysql中,可能是因为用户数据比较多。但两者都不会与用户信息表混在一起。Cookie和Session登录状态的区别是不断总结浏览器对网站的第一次请求,服务器生成一个SessionID。将生成的SessionID保存到服务器存储中。通过set-cookie将生成的SessionID返回给浏览器。浏览器收到SessionID,下次发送请求时会带上这个SessionID。服务器接收浏览器发送的SessionID,从Session存储中查找用户状态数据,建立会话。后续请求会将此会话ID交换为有状态会话。登录流程图实现案例(koa2+Mysql)本案例适合对服务器有一定概念的同学。以下只是核心代码。数据库配置的第一步是配置数据库。这里我单独配置了一个文件。因为当项目变大的时候,需要区分开发环境、测试环境、正式环境的数据库。letdbConf=null;constDEV={database:'dandelion',//数据库用户:'root',//用户密码:'xxx',//密码port:'3306',//porthost:'127.0.0.1'//服务ip地址}dbConf=DEV;module.exports=dbConf;数据库连接。constmysql=require('mysql');constdbConf=require('./../config/dbConf');constpool=mysql.createPool({host:dbConf.host,user:dbConf.user,password:dbConf.password,database:dbConf.database,})letquery=function(sql,values){returnnewPromise((resolve,reject)=>{pool.getConnection(function(err,connection){if(err){reject(err)}else{connection.query(sql,values,(err,rows)=>{if(err){reject(err)}else{resolve(rows)}connection.release()})}})})}module.exports={query,}路由配置这里我也单独提取了文件,让路由看起来更舒适且易于管理。constRouter=require('koa-router');constrouter=newRouter();constkoaCompose=require('koa-compose');const{login}=require('../controllers/login');//前缀router.prefix('/api');module.exports=()=>{//登录router.post('/login',登录);returnkoaCompose([router.routes(),router.allowedMethods()]);}中间件注册路由。constrouters=require('../routers');module.exports=(app)=>{app.use(routers());}session_id的生成和存储我的session_id生成使用的是koa-session2库,存储是存在于redis中的,使用的是一个ioredis库。配置文件。constRedis=require("ioredis");const{Store}=require("koa-session2");类RedisStore扩展存储{constructor(){super();this.redis=newRedis();}asyncget(sid,ctx){letdata=awaitthis.redis.get(`SESSION:${sid}`);返回JSON.parse(数据);}asyncset(session,{sid=this.getID(24),maxAge=1000*60*60}={},ctx){try{console.log(`SESSION:${sid}`);//使用redissetEX自动丢弃过期会话awaitthis.redis.set(`SESSION:${sid}`,JSON.stringify(session),'EX',maxAge/1000);}catch(e){}返回sid;}asyncdestroy(sid,ctx){returnawaitthis.redis.del(`SESSION:${sid}`);}}module.exports=RedisStore;入口文件(index.js)constKoa=require('koa');constmiddleware=require('./middleware');//中间件,目录了由constsession=require("koa-session2");//sessionconstStore=require("./utils/Store.js");//redisconstbody=require('koa-body');constapp=newKoa();//session配置app.use(session({store:newStore(),key:"SESSIONID",}));//解析post参数app.use(body());//注册中间件middleware(app);constPORT=3001;//启动服务app.listen(PORT);console.log(`serverisstartingatport${PORT}`);登录界面主要是根据用户的账号密码,获取用户信息,在session中存储用户uid,设置session_id给浏览器,代码很少,因为我用的是现成的库,他们已经给你做了,这里我没有设置session_id的过期时间,这样用户关闭浏览器就消失了。constUserModel=require('../model/UserModel');//用户表相关sql语句constuserModel=newUserModel();/***@description:登录界面*@param{account}帐号*@param{password}密码*@return:登录结果*/asyncfunctionlogin(ctx,next){//获取用户名和密码getconst{account,password}=ctx.request.body;//根据用户名和密码获取用户信息constuserInfo=awaituserModel.getUserInfoByAccount(account,password);//生成session_idctx.session.uid=JSON.stringify(userInfo[0].uid);ctx.body={mes:'登录成功',data:userInfo[0].uid,success:true,};};module.exports={login,};登录后其他接口可以通过这个session_id获取登录状态。//业务接口,获取所有用户需求constDemandModel=require('../../model/DemandModel');constdemandModel=newDemandModel();constshortid=require('js-shortid');constStore=require("../../utils/Store.js");constredis=newStore();asyncfunctionselectUserDemand(ctx,next){//判断用户是否登录,获取SESSIONID在cookieconstSESSIONID=ctx.cookies.get('SESSIONID');if(!SESSIONID){console.log('没有SESSIONID,去登录~');返回假;}//如果有SESSIONID,就去redis中获取数据constredisData=awaitredis.get(SESSIONID);if(!redisData){console.log('SESSIONID已过期,登录~');返回假;}if(redisData&&redisData.uid){console.log(`登录,uid为${redisData.uid}`);constuid=JSON.parse(redisData.uid);//根据session中的uid处理业务逻辑constdata=awaitdemandModel.selectDemandByUid(uid);安慰。日志(数据);ctx.body={mes:'',数据,成功:true,};};module.exports={selectUserDemand,}注意陷阱注1.注意跨域问题2.处理OPTIONS多个预检测问题app.use(async(ctx,next)=>{ctx.set('Access-Control-Allow-Origin','http://test.xue.com');ctx.set('Access-Control-Allow-Credentials',true);ctx.set('Access-Control-Allow-Headers','content-type');ctx.set('Access-Control-Allow-Methods','OPTIONS,GET,HEAD,PUT,POST,DELETE,PATCH');//这个响应头的意思是设置一个相对时间,当这个非简单的请求是从服务器通过检查的那一刻开始,//当经过的毫秒数小于Access-Control-Max-Age时,不需要进行预检查,可以请求直接发送ctx.set('访问控制-最大年龄',3600*24);if(ctx.method=='OPTIONS'){ctx.body=200;}else{等待下一个();}});3。允许发送带有cookie的请求时设置这个参数withCredentials:true,这样请求就可以携带cookieaxios({url:'http://test.xue.com:3001/api/login',method:'post',data:{account:this.account,password:this.password,},withCredentials:true,//允许设置凭据}).then(res=>{console.log(res.data);if(res.data.success){this.$router.push({path:'/index'})}})源码上面的代码只是贴出了核心,前后端源码如下-结束但练手项目还在开发中,网站其他功能还没有全部实现。代码写的很郁闷???但是登录是没有问题的~不过需要提前了解Redis、Mysql、Nginx和基本的服务器操作!如有错误,请指教?