老板发了我2千奖金,原因是我用Java实现了管理员可以修改任意用户Session的功能

大家好,我曹尼玛;
在这里插入图片描述
家里穷,所以我非常努力,白天上班,晚上加班学习,虽然大专刚毕业,工资只有8千,长得也丑,没有女朋友,但是我依然热爱生活,努力工作;
在这里插入图片描述
前2个月,公司接了个几十万的小单子,3个程序员简单分析了下业务,CRUD狂撸了1个多月,就很轻松的交付了,客户试用了下,发现10个Bug。(主要原因老板为了省钱,没有招测试小美眉,让程序员自己测试)
在这里插入图片描述
然后老板安排了一个光棍老屌丝,长得比我还丑,头发比我还油腻,可能是太忙,没空洗头,直接去客户现场直接修Bug。
在这里插入图片描述
不过话说回来,这个老屌丝技术还是可以的,10个Bug,三天就修完了,而且客户测试没问题。被客户放回来了。
在这里插入图片描述
系统正式上线了,线上测试,老板估计开心死了,等着收那8万块钱尾款。门口老板的凯德拉克,方向盘已经握不准了。
在这里插入图片描述
第二天,老板接到客户电话,说发现系统缺陷,严重影响用户体验。老板顿时不开心了,把老屌丝技术叫过来。
在这里插入图片描述
“全蛋同志,客户系统有个系统缺陷,你明天去搞下,那边管理员给用户后台开了VIP之后,用户无法立刻看到VIP信息,必须重新登录才能看到,用户体验太差了,辛苦你去处理下”。老屌丝说,“哦哦哦,好的”;
在这里插入图片描述
老屌丝技术在客户那边研究了好几天,懵逼了,有点难啊,当前用户自己操作自己的Session,一般不都是这个逻辑吗?
管理员操作,不好搞啊,查了百度,也没找到具体方案,推送消息,前端实时刷新,对系统性能影响太大,不适合。老屌丝忧虑了。
在这里插入图片描述
搞了2天,熬夜了2天,没搞定,人也更显老了,头发更油腻了,直接回公司了,跟老板说,搞不定。老板发火了,这都搞不定,你好意思拿20K。然后对我们说,谁能搞定,奖金2千。其他人没头绪,其实我也不会,不过我网上找了个师傅,他肯定会。所以我跟老板说,我来试试吧。主要我穷,缺陷。不然我没事去填坑干嘛!
在这里插入图片描述
老板直接开他的凯迪拉克送我去了客户现场,走之前,跟我说,“好好干,干好了,有彩蛋送你,顺便瞟了一眼后排的肤白貌美的前台小敏(老板他表妹)”。
在这里插入图片描述
我咽了下口水,直接说,“放心老板,一定完成任务!”;
其实我也不会。这东西,我学习网上的教程,都是只讲到当前用户修改自己的session,没听过其他代码操作修改其他用户的session。不过我坚信肯定能实现。查了半天资料,稍微有点头绪,不过还是很乱。无奈只能去问我的师傅 java1234_小锋 老师,我把问题描述给了老师,锋哥说,这个能搞定的,微信语音+文字+图 把原理说给我听,一开始看得我晕,不过我仔细的整理,思考后,终于把思路理清了。那种感觉真好。
在这里插入图片描述
我在纸上画了设计,查了API,基本逻辑搞定,简单吃了个快餐,然后脱掉衣服和裤子,直接开撸代码,撸了2小时,就撸完了,很认真的测试了下,没毛病。我靠,原来也挺简单。
在这里插入图片描述
第二天客户直接测试,完美,客户直叫 “ 666 ”。顺便把尾款直接打给公司了,客户还要求每年的系统运维负责人必须是我。
在这里插入图片描述
回到公司,老板搞了两桌请员工吃饭,给了我2千奖金,让我好好干。安排座位的时候,特意让前台小敏安排在我旁边,一直给我倒酒,后来我迷迷糊糊醉了,第二天醒来,发现自己在酒店房间里,靠。
在这里插入图片描述
正好周日,我把这次实现管理员修改任意用户Session功能的技术方案整理出来,分享给大家,希望大家喜欢,另外还录制了一份配套视频教程,方便初学者看懂。

1 Session会话简介

session 是另一种记录服务器和客户端会话状态的机制,并且session 是基于 cookie 实现的。服务器要知道当前请求发给自己的是谁,为了做这种区分,服务器就是要给每个客户端分配不同的"身份标识",然后客户端每次向服务器发请求的时候,都带上这个“身份标识”。

Cookie是浏览器实现的一种数据存储技术。一般由服务器生成,发送给浏览器(客户端也可进行cookie设置)进行存储,下一次请求同一网站时会把该cookie发送给服务器。
在这里插入图片描述

2 简单实例准备

我们做一个简单实例,模拟用户登录,以及获取登录用户信息;
新建一个springboot项目modify-session,最终目录结构如下:
在这里插入图片描述
项目pom.xml,引入一个web依赖即可;

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.java1234</groupId>
    <artifactId>modify-session</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>modify-session</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动类ModifySessionApplication

package com.java1234;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ModifySessionApplication {

    public static void main(String[] args) {
        SpringApplication.run(ModifySessionApplication.class, args);
    }

}

项目配置文件application.yml

server:
  port: 80
  servlet:
    context-path: /

用户实体类User

package com.java1234.entity;

/**
 * 用户信息
 * @author java1234_小锋
 * @site www.java1234.com
 * @company 南通小锋网络科技有限公司
 * @create 2021-08-03 21:14
 */
public class User {

    public Integer id;

    public String userName;

    private String password;

    private String level="common";  // common  普通会员  vip  vip会员


    public User() {
    }

    public User(Integer id, String userName, String password) {
        this.id = id;
        this.userName = userName;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }
}

新建一个UserController,提供两个接口方法,分别是模拟用户登录,和获取用户信息:

package com.java1234.controller;

import com.java1234.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/**
 * 用户controller
 * @author java1234_小锋
 * @site www.java1234.com
 * @company 南通小锋网络科技有限公司
 * @create 2021-08-03 21:10
 */
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 模拟用户登录
     * @return
     */
    @RequestMapping("/login")
    public String login(HttpSession session){
        User uesr=new User(1,"java1234","123456");
        session.setAttribute("currentUser",uesr);
        System.out.println(session.getId());
        return "success";
    }

    /**
     * 获取当前用户信息
     * @param session
     * @return
     */
    @RequestMapping("/getUserInfo")
    public User getUserInfo(HttpSession session){
        return (User)session.getAttribute("currentUser");
    }
}

我们启动项目;

浏览器地址栏输入:http://localhost/user/login
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JudtP3u1-1628499380949)(C:\Users\java1234\Desktop\动态修改用户session数据\课件\动态修改用户Session课件.assets\image-20210808233938494.png)]

浏览器地址栏输入:http://localhost/user/getUserInfo 获取用户信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOCX5TpL-1628499380950)(C:\Users\java1234\Desktop\动态修改用户session数据\课件\动态修改用户Session课件.assets\image-20210809013012513.png)]
后端打印出sessionId
在这里插入图片描述
我们通过谷歌开发者工具,F12;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LldCEkcS-1628499380951)(C:\Users\java1234\Desktop\动态修改用户session数据\课件\动态修改用户Session课件.assets\image-20210808234229846.png)]
后端同时返回了SessionId,作为Cookie;

浏览器地址栏输入:http://localhost/user/getUserInfo

返回用户信息,同时我们看到,请求的时候带了Cookie里的SessionId,去后端查询指定Session信息;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2BsNk3l-1628499380951)(C:\Users\java1234\Desktop\动态修改用户session数据\课件\动态修改用户Session课件.assets\image-20210808234440913.png)]

3 动态修改用户Session场景分析

当前用户自身是可以通过sesssion.setAttribute方法修改session信息的。
在这里插入图片描述
但是我们在某些情况,业务上要求非自身用户修改Session;

比如管理员后台充值好vip后,数据是修改了,但是登录用户的Session没变化,用户看到的依然是非Vip,需要重新登录后,才能看到vip信息,用户体验就差劲了;如果我们可以动态的去修改任意一个用户的Session信息,那用户无需登录,刷新网页就立即能看到vip信息,那用户体验就上来了。

4 动态修改Session原理介绍

我们终极解决方案如下图:
在这里插入图片描述

我们可以创建一个Session监听器,来监听用户Session的创建和销毁事件,所以这里,我们可以去维护一个sessionId和Session对象关系的存储介质,一般情况下可以用HashMap,正好是key-value键值对,假如高并发情况,也可以存储到告诉缓存Redis,当然对象的话,注意要序列化;

同时每次用户登录后,我们可以得到userIdsessionId,我们也用一个存储介质维护起来,我们这里为了测试方便,用servletContext全局上下文存储,高并发下,依然要选用Redis存储;

有了以上两个核心的存储介质加上session监听器,我们就可以实现动态修改Session了;

具体步骤如下:

第一步:用户登录,得到sessionId和userId;

第二步:把sessionId和userId存储到servletContext全局上下文,格式 { userId : sessionId } ;

第三步:登录请求触发session监听器的sessonCreated方法;

第四步:sessionCreated方法添加session信息到HashMap,格式 { sessionId : session对象 } ;

第五步:管理员登录,根据userId去servletContext查询sessionId;

第六步:得到sessionId后去hashMap里去查询session对象;

通过以上步骤,得到指定用户的Session对象后,就可以任意操作了;

5 动态修改Session实现

我们来实现下具体代码:

我们定义一个自定义session上下文MySessionContext,里面定义HashMap属性来存储session信息,格式 sessionId :session对象;

package com.java1234.custom;

import javax.servlet.http.HttpSession;
import java.util.HashMap;

/**
 * 自定义session上下文
 * @author java1234_小锋
 * @site www.java1234.com
 * @company 南通小锋网络科技有限公司
 * @create 2021-08-05 10:39
 */
public class MySessionContext {

    private static MySessionContext instance;
    // session map存储session  如果session较多,影响到系统性能的话,可以把用redis,key-value  sessionId->session对象 session对象序列化
    public static HashMap<String,HttpSession> sessionMap;

    private MySessionContext() {
        sessionMap = new HashMap<String,HttpSession>();
    }

    /**
     * 单例
     * @return
     */
    public static MySessionContext getInstance() {
        if (instance == null) {
            instance = new MySessionContext();
        }
        return instance;
    }

    /**
     * 添加session
     * @param session
     */
    public synchronized void addSession(HttpSession session) {
        if (session != null) {
            System.out.println("session添加成功!");
            sessionMap.put(session.getId(), session);
        }
    }

    /**
     * 删除session
     * @param session
     */
    public synchronized void delSession(HttpSession session) {
        if (session != null) {
            System.out.println("session删除成功!");
            sessionMap.remove(session.getId());
        }
    }

    /**
     * 根据sessionId获取session
     * @param sessionID
     * @return
     */
    public synchronized HttpSession getSession(String sessionID) {
        if (sessionID == null) {
            return null;
        }
        return sessionMap.get(sessionID);
    }
}

新建session监听器SessionListener,监听session创建和销毁;

session创建的时候,把session信息存储到自定义session上下文;session销毁时,自定义session上下文中也删除掉该session;

注意,要加@WebListener注解

package com.java1234.listener;

import com.java1234.custom.MySessionContext;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * session监听器
 * @author java1234_小锋
 * @site www.java1234.com
 * @company 南通小锋网络科技有限公司
 * @create 2021-08-05 10:43
 */
@WebListener
public class SessionListener implements HttpSessionListener {

    // 获取自定义session上下文实例
    private MySessionContext msc = MySessionContext.getInstance();

    /**
     * session创建事件
     * @param se
     */
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("session创建");
        HttpSession session = se.getSession();
        msc.addSession(session);  // 添加当前session到自定义session上下文
    }

    /**
     * session销毁事件
     * @param se
     */
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session销毁");
        HttpSession session = se.getSession();
        //todo 要从数据库或者redis缓存把指定sessionId的用户session信息删除
        msc.delSession(session);  // 从自定义session上下文里删除当前session
    }
}

在springboot项目中,要使得监听器有效,我们启动类要加@ServletComponentScan注解

@ServletComponentScan,自动扫描带有(@WebServlet, @WebFilter, and @WebListener)注解的类,完成注册
在这里插入图片描述
到了这里,我们来测试下监听器是否有效;

启动项目,浏览器地址栏输入:http://localhost/user/login
在这里插入图片描述

说明监听器触发成功,session添加成功!

session的销毁方法触发,有以下三种方式;

1、超时(一般服务器设置超时时间为30分钟)服务器会销毁session;

2、点击控制台的红色按钮异常关闭服务器要销毁session

3、手动调用session的invalidate方法session.invalidate();

为了演示,我把session有效期设置成30秒;

server:
  port: 80
  servlet:
    context-path: /
    session:
      timeout: 30s

我们执行模拟登录30秒后,只要不继续请求操作,30秒后,触发session销毁事件,session删除;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XielLd5R-1628499658376)(C:\Users\java1234\Desktop\动态修改用户session数据\课件\动态修改用户Session课件.assets\image-20210809012123322.png)]
修改UserController,通过session获取servletContext上下文,存储用户session信息,格式 { userId : sessionId }

/**
     * 模拟用户登录
     * @return
     */
    @RequestMapping("/login")
    public String login(HttpSession session){
        User uesr=new User(1,"java1234","123456");
        session.setAttribute("currentUser",uesr);
        System.out.println(session.getId());
        ServletContext servletContext = session.getServletContext();
        // 模拟存储用户session信息到数据库 用application模拟
        servletContext.setAttribute(String.valueOf(uesr.getId()),session.getId()); // key-value 用户id-sessionId
        return "success";
    }

创建ManagerController测试:

package com.java1234.controller;

import com.java1234.custom.MySessionContext;
import com.java1234.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.util.HashMap;


/**
 * 管理员Controller控制器
 * @author java1234_小锋
 * @site www.java1234.com
 * @company 南通小锋网络科技有限公司
 * @create 2021-08-07 23:05
 */
@RequestMapping("/manager")
@RestController
public class ManagerController {

    /**
     * 模拟用户登录
     * @return
     */
    @RequestMapping("/modifySession")
    public String modifySession(HttpSession session){
        ServletContext servletContext = session.getServletContext();
        String userId="1"; // 修改userId=1的用户
        String sessionId = (String)servletContext.getAttribute(userId);  // 从servletContext上下文 根据userId获取sessionId
        System.out.println("sessionId:"+sessionId);
        HashMap<String, HttpSession> sessionMap = MySessionContext.sessionMap; // 获取sessionMap
        HttpSession currentSession = sessionMap.get(sessionId); // 根据sessionId获取用户session
        User user = (User)currentSession.getAttribute("currentUser"); // 根据session得到用户信息
        user.setLevel("vip"); // 修改内容
        return "success";
    }
}

浏览器地址栏:http://localhost/user/login 模拟登录
在这里插入图片描述

浏览器地址栏:http://localhost/user/getUserInfo 获取用户信息
在这里插入图片描述

我们另外开一个浏览器地址栏:http://localhost/manager/modifySession 模拟管理员修改session
在这里插入图片描述

然后刷新getUserInfo;
在这里插入图片描述

我们发现session会话信息变了。测试成功!

6 高并发下的性能优化

在高并发下,同一时刻登录用户会很多,如果把session都放内存,会影响性能,甚至内存溢出;所以session可以存储到redis;

另外如下图的两个存储介质,也要存储到redis,以达到最佳性能;
在这里插入图片描述

7 完整源码+配套视频教程分享

源码和文档+视频我放在了Github和码云上面,大家有需要参考源码的直接pull下,IDEA工具;

码云地址:https://gitee.com/java_1234/modify-session

Github地址: https://github.com/java1234/modify-session

8 留了一点小作业

如果你是一个有理想的程序员,不妨可以升级下锋哥的实例;

第一:搭建一个高可用的redis集群环境;

第二:springboot+redis实现session存储到redis高速缓存;

第三:如上图的两个存储介质也存储到redis;

在这里插入图片描述

微信搜一搜公众号【java1234】关注这个放荡不羁的程序员,关注后回复【资料】有我准备的一线大厂笔试面试资料以及简历模板。

  • 113
    点赞
  • 256
    收藏
    觉得还不错? 一键收藏
  • 54
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 54
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值