在WEB開發中,服務器能夠爲每一個用戶瀏覽器建立一個會話對象(session對象),注意:一個瀏覽器獨佔一個session對象(默認狀況下)。所以,在須要保存用戶數據時,服務器程序能夠把用戶數據寫到用戶瀏覽器獨佔的session中,當用戶使用瀏覽器訪問其它程序時,其它程序能夠從用戶的session中取出該用戶的數據,爲用戶服務。html
服務器是如何實現一個session爲一個用戶瀏覽器服務的?前端
服務器建立session出來後,會把session的id號,以cookie的形式回寫給客戶機,這樣,只要客戶機的瀏覽器不關,再去訪問服務器時,都會帶着session的id號去,服務器發現客戶機瀏覽器帶session id過來了,就會使用內存中與之對應的session爲之服務。java
注:上文描述,參考自文末的參考連接中第1條連接。node
DelayQueue
隊列中的元素必須是Delayed接口的實現類,該類內部實現了getDelay()
和compareTo()
方法,第一個方法是比較兩個任務的延遲時間進行排序,第二個方法用來獲取延遲時間。DelayQueue
隊列沒有大小限制,所以向隊列插數據不會阻塞DelayQueue
中的元素只有當其指定的延遲時間到了,纔可以從隊列中獲取到該元素。不然線程阻塞。DelayQueue
中的元素不能爲null
DelayQueue
內部是使用PriorityQueue
實現的。compareTo()
比較後越小的越先取出來。注:上文描述,參考自文末的參考連接中第2條連接。ios
html
,利用ajax
(後期改成axios
)來請求json
交互,restful
風格springboot
爲基礎框架,接口暴露爲適應跨域要求,利用在控制層添加@CrossOrigin
註解實現JSESSIONID
每次請求都會變化,致使後端沒法維護一個合適的session
session
的存儲與維護。session
的特色session
有自動過時時間,到期後系統會自動清理。session
,該key
值過時時間重置DelayQueue
的設計concurrentHashmap
來保存session
信息DelayQueue
延遲隊列來存儲concurrentHashmap
中的key
sessionListener
,專門開啓一個守護線程(阻塞式take
)從DelayQueue
隊列中獲取過時的指針,再根據指針刪除concurrentHashmap
中對應元素。sessionId
的設計,可利用uuid
或其餘規則來實現。sessionId
,按照必定格式返回給前臺。sessionId
後,可存儲到cookie
中,將其封裝到http
的header
中,後續請求均附帶該header
axios
中,具體請求示例以下:var headers = { 'Content-Type': 'application/json', 'Access-Control-Allow-Credentials': 'true', 'Authorization': $.cookie("jsessionId"), }; axios({ headers: headers, method: method, //GET、PUT、POST、PATCH、DELETE等 url: url, timeout: 50000, // 請求的超時時間 data: data, }) .then(function (response) { //TODO 正確返回後的處理或回調 }) .catch(function (error) { if (error.response) { console.log(error.response); } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js console.log(error.request); } else { // Something happened in setting up the request that triggered an Error console.log('Error', error.message); } });
DelayQueue
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * @Auther: jiangcaijun * @Date: 2018/4/17 15:15 * @Description: 延遲隊列,單例模式。利用ConcurrentHashMap來存儲信息 */ public class CacheSingleton<K, V> { /*session自動過時時間,單位:秒*/ private static int liveTime = 5; //在類內部實例化一個實例 private static CacheSingleton instance = new CacheSingleton(); //私有的構造函數,外部沒法訪問 private CacheSingleton(){ Thread t = new Thread(){ @Override public void run(){ dameonCheckOverdueKey(); } }; t.setDaemon(true); t.start(); } //對外提供獲取實例的靜態方法 public static CacheSingleton getInstance() { return instance; } public ConcurrentHashMap<K, V> concurrentHashMap = new ConcurrentHashMap<K, V>(); public DelayQueue<DelayedItem<K>> delayQueue = new DelayQueue<DelayedItem<K>>(); /** * 根據key,獲取相應的值 * @param k * @return */ public Object get(K k){ V v = concurrentHashMap.get(k); DelayedItem<K> tmpItem = new DelayedItem<K>(k, liveTime); if (v != null) { delayQueue.remove(tmpItem); delayQueue.put(tmpItem); System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "獲取 "+ k + "成功,生命週期從新計算:"+ liveTime +"秒")); }else{ System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "獲取"+ k +"失敗,對象已過時")); } return v; } /** * 移除相應的鍵值對 * @param k */ public void remove(K k){ V v = concurrentHashMap.get(k); DelayedItem<K> tmpItem = new DelayedItem<K>(k, liveTime); if (v != null) { delayQueue.remove(tmpItem); System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "主動刪除 "+ k + "成功")); }else{ System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "刪除失敗,該 "+ k +"已被刪除")); } } /** * 插入鍵值對 * @param k * @param v */ public void put(K k,V v){ V v2 = concurrentHashMap.put(k, v); DelayedItem<K> tmpItem = new DelayedItem<K>(k, liveTime); if (v2 != null) { delayQueue.remove(tmpItem); System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "覆蓋插入 "+ k + ",生命週期從新計算:"+ liveTime +"秒")); }else{ System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "新插入 "+ k + ",生命週期初始化:"+ liveTime +"秒")); } delayQueue.put(tmpItem); } /** * 專門開啓一個守護線程(阻塞式)從 delayQueue 隊列中獲取過時的指針,再根據指針刪除hashmap中對應元素。 */ public void dameonCheckOverdueKey(){ System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()), "守護進程開啓")); while (true) { DelayedItem<K> delayedItem = null; try { delayedItem = delayQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } if (delayedItem != null) { concurrentHashMap.remove(delayedItem.getT()); System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()),"自動刪除過時key: "+delayedItem.getT())); } try { Thread.sleep(300); } catch (Exception e) { // TODO: handle exception } } } /** * TODO */ public static void main(String[] args) throws InterruptedException { /*模擬客戶端調用*/ CacheSingleton.getInstance().put("1", 1); CacheSingleton.getInstance().put("2", 2); Thread.sleep(4000); CacheSingleton.getInstance().get("2"); Thread.sleep(2000); CacheSingleton.getInstance().get("2"); Thread.sleep(2000); CacheSingleton.getInstance().get("2"); Thread.sleep(5500); CacheSingleton.getInstance().put("1", 2); CacheSingleton.getInstance().get("2"); Thread.sleep(5000); System.out.println(String.format("%s \t %s",new SimpleDateFormat("HH:mm:ss").format(new Date()),"main方法結束")); } } class DelayedItem<T> implements Delayed{ private T t; private long liveTime ; private long removeTime; public DelayedItem(T t,long liveTime){ this.setT(t); this.liveTime = liveTime; this.removeTime = TimeUnit.NANOSECONDS.convert(liveTime, TimeUnit.SECONDS) + System.nanoTime(); } @Override public int compareTo(Delayed o) { if (o == null) return 1; if (o == this) return 0; if (o instanceof DelayedItem){ DelayedItem<T> tmpDelayedItem = (DelayedItem<T>)o; if (liveTime > tmpDelayedItem.liveTime ) { return 1; }else if (liveTime == tmpDelayedItem.liveTime) { return 0; }else { return -1; } } long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); return diff > 0 ? 1:diff == 0? 0:-1; } @Override public long getDelay(TimeUnit unit) { return unit.convert(removeTime - System.nanoTime(), unit); } public T getT() { return t; } public void setT(T t) { this.t = t; } @Override public int hashCode(){ return t.hashCode(); } @Override public boolean equals(Object object){ if (object instanceof DelayedItem) { return object.hashCode() == hashCode() ?true:false; } return false; } }
在過時時間爲5秒
的狀況下,模擬session
,main
方法運行,輸出爲:ajax
16:56:25 新插入 1,生命週期初始化:5秒 16:56:25 守護進程開啓 16:56:25 新插入 2,生命週期初始化:5秒 16:56:29 獲取 2成功,生命週期從新計算:5秒 16:56:30 自動刪除過時key: 1 16:56:31 獲取 2成功,生命週期從新計算:5秒 16:56:33 獲取 2成功,生命週期從新計算:5秒 16:56:38 自動刪除過時key: 2 16:56:38 新插入 1,生命週期初始化:5秒 16:56:38 獲取2失敗,對象已過時 16:56:43 自動刪除過時key: 1 16:56:43 main方法結束
利用aop
的環繞aroud
,在請求過來時,查看該sessionId
是否存在該delayQueue
中,簡要代碼以下:spring
import com.bigdata.weathercollect.constant.GlobalConstant; import com.bigdata.weathercollect.exception.UnauthorizedException; import com.bigdata.weathercollect.service.ServiceStatus; import com.bigdata.weathercollect.session.CacheSingleton; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Auther: jiangcaijun * @Date: 2018/4/16 15:58 * @Description: * @Component:註冊到Spring容器,必須加入這個註解 * @Aspect // 該註解標示該類爲切面類,切面是由通知和切點組成的。 */ @Component @Aspect public class ExceptionAspect { private static Logger logger = LoggerFactory.getLogger(ExceptionAspect.class); @Autowired private HttpServletRequest request; /** * 這裏會報錯,但不影響運行 */ @Autowired private HttpServletResponse response; @Pointcut("execution(public * com.bigdata.weathercollect.controller.*.*(..))") public void exceptionAspect() { } @Around("exceptionAspect()") public Object around(ProceedingJoinPoint joinPoint){ String url = request.getRequestURI(); ServiceStatus serviceStatus = null; Boolean flag = false; if(url != null){ String jsessionId = request.getHeader("Authorization"); if(StringUtils.isNotBlank(jsessionId)) { //這裏進行sessionId的校驗 if(CacheSingleton.getInstance().get(jsessionId) != null){ // logger.info("該用戶已登錄,id:{}", jsessionId); flag = true; } } if(!flag){ logger.error("該用戶未登錄"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); return new ServiceStatus(ServiceStatus.Status.Fail, "還沒有登錄或會話已過時",401); } } try { return joinPoint.proceed(); } catch (UnauthorizedException e) { logger.error("出現Exception:url爲" + url + ";錯誤類型爲"+e.getMessage()+""); serviceStatus = new ServiceStatus(ServiceStatus.Status.Fail, "認證失敗:" + e.getMessage(),401); } catch (Exception e) { logger.error("出現Exception:url爲" + url + ";錯誤類型爲"+e.getMessage()+""); serviceStatus = new ServiceStatus(ServiceStatus.Status.Fail, "失敗:" + e.getMessage(),500); } catch (Throwable throwable) { throwable.printStackTrace(); } return serviceStatus; } }
注:其中,ServiceStatus
爲自定義的json
返回封裝的類,不影響閱讀,故代碼未貼出來。apache
參考連接:json