当前位置 博文首页 > 、唐城:社会你“小峰哥“用Java实现了管理员可以修改任意用户Se

    、唐城:社会你“小峰哥“用Java实现了管理员可以修改任意用户Se

    作者:[db:作者] 时间:2021-08-20 18:54


    目录

    1 Session会话简介

    2 简单实例准备

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

    4 动态修改Session原理介绍

    5 动态修改Session实现

    6 高并发下的性能优化

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

    8 留了一点小作业


    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;

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

    cs