大家好,我曹尼瑪;
家里窮,所以我非常努力,白天上班,晚上加班學習,雖然大專剛畢業,工資只有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)]](https://img.uj5u.com/2021/08/13/255837130733075.png)
瀏覽器地址欄輸入:http://localhost/user/getUserInfo 獲取用戶資訊
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iOCX5TpL-1628499380950)(C:\Users\java1234\Desktop\動態修改用戶session資料\課件\動態修改用戶Session課件.assets\image-20210809013012513.png)]](https://img.uj5u.com/2021/08/13/255837130733076.png)
后端列印出sessionId

我們通過谷歌開發者工具,F12;
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LldCEkcS-1628499380951)(C:\Users\java1234\Desktop\動態修改用戶session資料\課件\動態修改用戶Session課件.assets\image-20210808234229846.png)]](https://img.uj5u.com/2021/08/13/2558371307330726.png)
后端同時回傳了SessionId,作為Cookie;
瀏覽器地址欄輸入:http://localhost/user/getUserInfo
回傳用戶資訊,同時我們看到,請求的時候帶了Cookie里的SessionId,去后端查詢指定Session資訊;
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-P2BsNk3l-1628499380951)(C:\Users\java1234\Desktop\動態修改用戶session資料\課件\動態修改用戶Session課件.assets\image-20210808234440913.png)]](https://img.uj5u.com/2021/08/13/2558371307330727.png)
3 動態修改用戶Session場景分析
當前用戶自身是可以通過sesssion.setAttribute方法修改session資訊的,

但是我們在某些情況,業務上要求非自身用戶修改Session;
比如管理員后臺充值好vip后,資料是修改了,但是登錄用戶的Session沒變化,用戶看到的依然是非Vip,需要重新登錄后,才能看到vip資訊,用戶體驗就差勁了;如果我們可以動態的去修改任意一個用戶的Session資訊,那用戶無需登錄,重繪網頁就立即能看到vip資訊,那用戶體驗就上來了,
4 動態修改Session原理介紹
我們終極解決方案如下圖:

我們可以創建一個Session監聽器,來監聽用戶Session的創建和銷毀事件,所以這里,我們可以去維護一個sessionId和Session物件關系的存盤介質,一般情況下可以用HashMap,正好是key-value鍵值對,假如高并發情況,也可以存盤到告訴快取Redis,當然物件的話,注意要序列化;
同時每次用戶登錄后,我們可以得到userId和sessionId,我們也用一個存盤介質維護起來,我們這里為了測驗方便,用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)]](https://img.uj5u.com/2021/08/13/255837130733077.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】關注這個放蕩不羈的程式員,關注后回復【資料】有我準備的一線大廠筆試面試資料以及簡歷模板,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/293355.html
標籤:java















