在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