前言
对于前后端分离的项目,Cookie和Session是前后端通信的两种重要手段,我们今天来看看两者的区别,以及提供一个引出Redis的使用案例
一 Cookie
它的流程是这样的👇
拿登录模块举个例子
看代码👇
前端发请求
this.$http
.post("/user/login", this.$qs.stringify({
usernmae:'张三'
password:'abc'
}))
.then((response) => {
if (response.data.code == 200) {
console.log(response.data.data)
this.$message({
message: `${response.data.data.uname}欢迎回来`,
type: "success",
});
sessionStorage.setItem("token", response.data.data.token);//把回来的token set进sessionStorage中等着下次使用
this.$router.push("/index");
}
});
后台
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public ETResponse queryUserByUsernameAndPwd(String username,String password){
String token = UUID.randomUUID().toString().replace("-", "");
Users u = service.queryUserByUsernameAndPwd(username, password);
if (u != null && uname.equals(u.getUname())) {
u.setToken(token);
return sr.setResponse(u,"200");//这里回去了Cookie
}
return sr.setResponse("", "445");//用户名或密码错误
}
再下一次前端发送请求就可以把sessionStorage中的token发给后台,后台根据这个字符串去查询某个唯一 的用户,并且获得该用户的权限,那你会说,刚刚我们后台生成"token"不是直接给前台了吗?我们也没有存下来了,怎么查?
没错,所以说这时候我们就要用到一个大件 –Redis
二 Redis的使用
那么前后端通信的流程就变成这样了👇
再加上第二次交互👇
感觉稍微有亿点点乱,不过感觉两张图结合起来看还算可以,好吧 我们还是看代码吧👇
这时候前端登录完毕,需要根据该用户的身份展示对应的菜单,那么这就是第二次交互了,那么需要先改改第一次交互的代码,需要添加商向Redis中存token=用户对象这一段代码
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public ETResponse queryUserByUsernameAndPwd(String username,String password){
String token = UUID.randomUUID().toString().replace("-", "");
Users u = service.queryUserByUsernameAndPwd(username, password);
if (u != null && uname.equals(u.getUname())) {
try {
RedisUtil ru = RedisUtil.getRedisUtil();//打开Redis
u.setToken(token);//组装对象
ru.setex(token, 1800, JSONObject.toJSONString(u));//存进Redis中
} catch (Exception e) {
e.printStackTrace();
return sr.setResponse("", "446");//Redis数据库异常
}
} else {
return sr.setResponse("", "445");//用户名或密码错误
}
return sr.setResponse(u,"200");//登陆成功,这里回去了Cookie
}
ok接着就是 前端第二次交互 发送token
直接从SessionStrorage中获取token 发送
this.$http
.post("/user/queryMenuByUserId", this.$qs.stringify({token:sessionStorage.getItem('token')}))
.then((response) => {
this.menuList =response.data.data
});
后端接 :Controller中的方法:流程就是 拿token 去Redis找对象 拿到对象去数据库里找结果集,至于这个动态菜单功能的实现 前面我有讲过,不太清楚的朋友可以直接跳转过去看看根据用户权限的不同实现动态菜单详解
@RequestMapping("/queryMenuByUserId")
@ResponseBody
public ETResponse queryMenuByUserID(String token){
RedisUtil ru =RedisUtil.getRedisUtil();//打开Redis工具 我资源里上传了
String userInfo =ru.get(token);// 相当于是一个Map
Users u =JSONObject.parseObject(userInfo,Users.class);
List<Menus> menus =service.queryMenuByUserId(u.getId());
List<Menus> tops =new ArrayList<>();
for (Menus menu : menus) {
if(menu.getPid()==-1){
tops.add(menu);
}
}
for (Menus top : tops) {
SetNodes.setNodes(top,menus);
}
return sr.setResponse(tops,"200");
}
ok这样一来,我们Redis的实例就说完了
三 Session
那么Session又是干什么的呢?
Session跟Cookie差不多,也是一个用来前后端通信的信件,那么我们看看什么是Session它又和Cookie有什么区别呢?
这样我们给刚才的登录模块 加一个验证码的功能,而最终前端的效果就是这样
那么这个验证码的图片加载,肯定是从后台过来的,也就是说真正的第一次前后台交互是在登录这个组件的Created()钩子中完成的,那么所谓的Session对向就是在这个节点发送给了后台,
前端
Code() {
this.$http
.get("/user/checkCode", { responseType: "blob" })
.then((response) => {
this.CheckCodeUrl = window.URL.createObjectURL(response.data);
this.form.sessionid = response.headers.sessionid;
});
},
created() {
this.Code();
},
没什么毛病吧,空参请求拿后端生成的图片
那么Controller
@RequestMapping("/checkCode")
public void checkCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
int WIDTH = 120;//生成图片的宽度
int HEIGHT = 30;
// TODO Auto-generated method stub
String createTypeFlag = req.getParameter("createTypeFlag");//接收客户端传递的createTypeFlag标识
//在内存中创建一张图片
BufferedImage bi = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
//得到图片
Graphics g = bi.getGraphics();
//设置图片的背景色
setBackGround(g);
//设置图片的边框
setBorder(g);
//在图片上画干扰线
drawRandomLine(g);
//在图片上放上随机字符
String randomString = this.drawRandomNum((Graphics2D)g, createTypeFlag);
//将随机数存在session中
HttpSession hs =req.getSession();//重点在这里 从Request中获得session对象
hs.setAttribute("checkcode", randomString);
//设置响应头通知浏览器以图片的形式打开
resp.setContentType("image/jpeg");
//设置响应头控制浏览器不要缓存
resp.setDateHeader("expries", -1);
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("sessionid",hs.getId());//而session对象的id赛给响应头给了前端
//将图片传给浏览器
ImageIO.write(bi, "jpg", resp.getOutputStream());
}
而第二次交互 就在登录了看看又添上了Session校验之后,我们的代码变成什么样子了
前端登录的方法,最终版
submitForm(formName) {
// 为表单绑定验证功能
this.$refs[formName].validate((valid) => {
if (valid) {
this.$http
.post("/user/login", this.$qs.stringify(this.form))
.then((response) => {
if (response.data.code == 200) {
console.log(response.data.data)
this.$message({
message: `${response.data.data.uname}欢迎回来`,
type: "success",
});
sessionStorage.setItem("token", response.data.data.token);
this.$router.push("/index");
} else if (response.data.code == 447) {
this.$message({
message: "验证码错误",
type: "error",
});
} else if (response.data.code == 445) {
this.$message({
message: "用户名或密码错误",
type: "error",
});
}else{
this.$message({
message: "Redis数据库异常",
type: "error",
});
}
});
} else {
this.dialogVisible = true;
return false;
}
});
}
后端Cookie+Redis+Session的最终版
Controller
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public ETResponse queryUserByUsernameAndPwd(@RequestParam("username") String uname,@RequestParam("password") String pwd,String checkCode,@RequestParam("sessionid") String sessionId){
HttpSession hs =UserControllerListener.getSessionById(sessionId);
String str =hs.getAttribute("checkcode")+"";//查看Session中的验证码
if(str.equalsIgnoreCase(checkCode)) {
String token = UUID.randomUUID().toString().replace("-", "");
Users u = service.queryUserByUsernameAndPwd(uname, pwd);
if (u != null && uname.equals(u.getUname())) {
try {
RedisUtil ru = RedisUtil.getRedisUtil();
u.setToken(token);
ru.setex(token, 1800, JSONObject.toJSONString(u));
} catch (Exception e) {
e.printStackTrace();
return sr.setResponse("", "446");//Redis数据库异常
}
} else {
return sr.setResponse("", "445");//用户名或密码错误
}
return sr.setResponse(u,"200");//这里回去了Cookie
}else {
return sr.setResponse("","447");//验证码不对
}
}
而由于跨域的问题,我们request每次携带的Session都是新的,如果我们不处理那么我们第一次set进去的参数永远找不到,所以我们需要通过某种手段来存一下第一次交互的时候Session对象–ControllerListener
@WebListener
public class UserControllerListener implements HttpSessionListener {
private static final Map<String, HttpSession> map = new ConcurrentHashMap<>();
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
HttpSession hs = httpSessionEvent.getSession();
map.put(hs.getId(),hs);//在Session创建的时候自动监听 put进入Map集合
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
}
public static HttpSession getSessionById(String sessionid){
return map.get(sessionid);//通过Sessionid找到第一次的Session 这样就能拿到里面的参数,看看和这次传入的参数是否是一致的判断输入是否有误
}
}
流程:
总结
今天用一个登录模块把Cookie Session Redis三者都用起来了,流程都比较复杂一点,说的比较杂乱纯原创,喜欢的话给个赞吧