参考网页
https://blog.csdn.net/u012255097/article/details/53813998#commentBox
说明
例子是以自己在做的实际项目为例,单服务实例。
只是演示用,实际项目中不推荐使用。
单服务实例可以用Guava等。分布式服务可以用Redis等。
方案描述
- SpringBoot接收到http请求
- 拦截器拦截请求,若是登录(或其他特殊的请求),放过;若非登录(或其他特殊的请求),取出token并从token中取出userId(后发现token或cookie信息里实际存储的是userName,所以使用userName而非userId),根据userId(修改为userName)从缓存中取出上次登录的时间loginTime,与现在的系统时间比对,如果在时间范围内,则刷新最后登录时间;如果超时,则拦截器返回,告知http请求登录超时
- 【1】中若是登录请求(或其他特殊的请求)拦截器放过不进行拦截。登录成功后将userId(修改为userName)和现在系统时间(loginTime)存入缓存中保存,留待后面访问时进行超时验证
代码实现
自己创建的简单的缓存类--SimpleCacheMgr
package com.richfit.ruiche.common.simpleCache;
import java.util.HashMap;
import java.util.Map;
/**
* 简单缓存管理类的实现:只是简单封装了 HashMap
*
* @author Administrator
*
*/
public class SimpleCacheMgr {
private static Map cacheMap = new HashMap();
private static SimpleCacheMgr cm = null;
// 构造方法
private SimpleCacheMgr() {
}
public static SimpleCacheMgr getInstance() {
if (cm == null) {
cm = new SimpleCacheMgr();
}
return cm;
}
/**
* 增加缓存
*
* @param key
* @param value
* @param ccm
* 缓存对象
* @return
*/
public boolean addCache(Object key, Object value) {
System.out.println("开始增加缓存-------------");
boolean flag = false;
try {
cacheMap.put(key, value);
System.out.println("增加缓存结束-------------");
System.out.println("now addcache==" + cacheMap.size());
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 获取缓存实体
*/
public Object getValue(Object key) {
Object ob = cacheMap.get(key);
if (ob != null) {
return ob;
} else {
return null;
}
}
/**
* 修改缓存实体
*/
public Object setValue(Object key, Object value){
return cacheMap.put(key, value);
}
/**
* 获取缓存数据的数量
*
* @return
*/
public int getSize() {
return cacheMap.size();
}
/**
* 删除缓存
*
* @param key
* @return
*/
public boolean removeCache(Object key) {
boolean flag = false;
try {
cacheMap.remove(key);
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
}
拦截器 LoginTimeOutInterceptor
package com.richfit.ruiche.Interceptor;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.richfit.ruiche.common.simpleCache.SimpleCacheMgr;
import com.richfit.ruiche.common.specialUrl.SpecialUrl;
import com.richfit.ruiche.util.LoginUtil;
/**
*
* Description:登录超时验证及刷新最新登录时间
*
*/
public class LoginTimeOutInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
String servletPath = request.getServletPath();
if (ifNeedCheck(servletPath)) {
//验证cookie
try {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("sec_login")) {
String userName = LoginUtil.decUserName(cookie.getValue(),0);
if (userName == null) {
return false;
} else {
/**
* 此处获取登录用户名,从缓存中获取并比对上次登录时间,过期则无法登录;没过期则更新最新登录时间
*/
// String loginName = DataAuthorityUtil.getLoginNameByCookies(request);
SimpleCacheMgr cm= SimpleCacheMgr.getInstance();
if(cm.getValue(userName) == null){
return false;
}else{
Long lastLoginTime = (Long) cm.getValue(userName);
//超过了一定的时间值--这里固定写成了10s
if(((new Date().getTime()) - lastLoginTime) > 10 * 1000){
System.out.println("======已经过期啦======");
return false;
}else{
//更新最近登录时间
cm.setValue(userName, new Date().getTime());
System.out.println("======更新登录时间了======");
return true;
}
}
/**
* 此处获取登录用户名,从缓存中获取并比对上次登录时间,过期则无法登录;没过期则更新最新登录时间
*/
}
}
break;
}
}
} catch (Exception ex) {
return false;
}
}
return true;
}
/**
*
* Description:是否需要验证连接判断。可以通过的URL:web端除【登录】和【获取新密码】外所有的连接
* @Version1.0 2017-2-16 上午11:38:36 by 丁兆宁 (dingzhaoning@cnpc.com.cn)
* @param servletPath
* @return
*/
private boolean ifNeedCheck(String servletPath){
List<String> noNeedCheckUrlList = getNoNeedCheckUrlList();
for(String s:noNeedCheckUrlList){
if(servletPath.matches(s)){
return false;
}
}
return true;
}
private List<String> getNoNeedCheckUrlList(){
List<String> list = new ArrayList<String>();
list.addAll(SpecialUrl.getNoNeedCheckUrlList());
return list;
}
}
配置类增加拦截器配置--只截取了部分代码
/**
*
* 在这个类里注册自定义的拦截器
*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.richfit.ruiche.Interceptor")
public class WebAppConfig extends WebMvcConfigurerAdapter{
@Bean
public ReturnMessageInterceptor returnMessageInterceptor(){
return new ReturnMessageInterceptor();
}
@Bean
public ReturnMessageTimeHandlerInterceptor returnMessageTimeHandlerInterceptor(){
return new ReturnMessageTimeHandlerInterceptor();
}
/**
*
* Description:web端登录拦截器
* @return
*/
@Bean
public LoginInterceptor loginInterceptor(){
return new LoginInterceptor();
}
/**
*
* Description:web端访问路径权限拦截器
* @return
*/
@Bean
public AccessInterceptor accessInterceptor(){
return new AccessInterceptor();
}
/**
*
* Description:公务车APP端登录拦截器
* @return
*/
@Bean
public OfficalLoginInterceptor officalLoginInterceptor(){
return new OfficalLoginInterceptor();
}
/**
*
* Description:APP端SessionListener
* @return
*/
@Bean
public SessionListener sessionListener() {
return new SessionListener();
}
@Bean
public LoginTimeOutInterceptor loginTimeOutInterceptor(){
return new LoginTimeOutInterceptor();
}
/**
* 1.这里的拦截器在容器初始化时加载在 HandlerExecutionChain 的 interceptors 变量里
* 2.interceptors 先加载用户定义的拦截器,再加载 SpringMVC 默认的拦截器
* 3.用户定义的拦截器数组数据标号跟下面的顺序一致。pre前处理是正序来处理;post后处理是按倒序来处理
*/
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor((HandlerInterceptor) returnMessageInterceptor());
// registry.addInterceptor((HandlerInterceptor) returnMessageTimeHandlerInterceptor());
registry.addInterceptor((HandlerInterceptor) loginInterceptor());
registry.addInterceptor((HandlerInterceptor) accessInterceptor());
// registry.addInterceptor((HandlerInterceptor) appLoginInterceptor());
registry.addInterceptor((HandlerInterceptor) officalLoginInterceptor());
registry.addInterceptor((HandlerInterceptor) loginTimeOutInterceptor());
}
@Bean
public TaskScheduler scheduledExecutorService() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(8);
scheduler.setThreadNamePrefix("scheduled-thread-");
return scheduler;
}
}
登录成功缓存加入登录最近时间LoginController
/**
* 登录成功在缓存中写入最新登录时间
*/
SimpleCacheMgr cm= SimpleCacheMgr.getInstance();
cm.addCache(name, new Date().getTime());
测试--成功
登录
登录成功,同时控制台打印有以下语句,说明登录成功后增加了缓存
10秒内随便访问一个连接
控制台打印如下
10秒后再次访问
没有返回因为已经被拦截,控制台打印如下
若在10秒内再次访问--页面显示和控制台打印内容跟10秒内第一次访问相同,并且过期时间会顺延10秒
代码解读
这个简单的缓存实现本质上是个系统全局的HashMap<String,Long>()
过期时间现在是固定写为了10秒(拦截器中写死了),可以写在配置文件中进行配置然后应用时获取
现在这个简单的缓存实际上只有一个功能,就是根据登录用户名保存该用户名最近登录时间,如果保存别的不同内容,那么key重复的话怎么解决?
因为缓存的key、value都是用的Object,很简单的一个方法就是直接用HashMap<String,HashMap<String,Long>>()这样的结构存储用户过期时间,key起名为”longinOutTime”,取的时候就根据key名为”longinOutTime”获取HashMap<String,Long>结构,然后再在HashMap<String,Long>结构中存储每个用户名的过期时间。
如果存别的数据,比如菜单,那么key起名为”menu”。value根据实际情况存储即可。
当然,这只是个思路而已,很low,实际项目不会那么干。而且受HashMap大小限制,存储数据是有限的,应用中如果要存储的数据量过大,就会爆掉。
只是跟随单例的服务的,无法多个服务实例共享,无法支持分布式
缓存代码改进(参考网页中的代码)
缓存管理类代码
package com.richfit.ruiche.common.cache;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 缓存管理类
* 相对于 SimpleCacheMgr 增加了缓存属性参数,会根据属性参数的值定期清理缓存
* @author Administrator
*
*/
public class CacheMgr {
private static Map cacheMap = new HashMap();
private static Map cacheConfMap = new HashMap();
private static CacheMgr cm = null;
// 构造方法
private CacheMgr() {
}
public static CacheMgr getInstance() {
if (cm == null) {
cm = new CacheMgr();
Thread t = new ClearCache();
t.start();
}
return cm;
}
/**
* 增加缓存
*
* @param key
* @param value
* @param ccm
* 缓存对象
* @return
*/
public boolean addCache(Object key, Object value, CacheConfModel ccm) {
System.out.println("开始增加缓存-------------");
boolean flag = false;
try {
cacheMap.put(key, value);
cacheConfMap.put(key, ccm);
System.out.println("增加缓存结束-------------");
System.out.println("now addcache==" + cacheMap.size());
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 获取缓存实体
*/
public Object getValue(String key) {
Object ob = cacheMap.get(key);
if (ob != null) {
return ob;
} else {
return null;
}
}
/**
* 获取缓存数据的数量
*
* @return
*/
public int getSize() {
return cacheMap.size();
}
/**
* 删除缓存
*
* @param key
* @return
*/
public boolean removeCache(Object key) {
boolean flag = false;
try {
cacheMap.remove(key);
cacheConfMap.remove(key);
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 清除缓存的类 继承Thread线程类
*/
private static class ClearCache extends Thread {
public void run() {
while (true) {
Set tempSet = new HashSet();
Set set = cacheConfMap.keySet();
Iterator it = set.iterator();
while (it.hasNext()) {
Object key = it.next();
CacheConfModel ccm = (CacheConfModel) cacheConfMap.get(key);
// 比较是否需要清除
if (!ccm.isForever()) {
if ((new Date().getTime() - ccm.getBeginTime()) >= ccm
.getDurableTime() * 60 * 1000) {
// 可以清除,先记录下来
tempSet.add(key);
}
}
}
// 真正清除
Iterator tempIt = tempSet.iterator();
while (tempIt.hasNext()) {
Object key = tempIt.next();
cacheMap.remove(key);
cacheConfMap.remove(key);
}
System.out.println("now thread================>"
+ cacheMap.size());
// 休息
try {
Thread.sleep(60 * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
缓存属性类代码
package com.richfit.ruiche.common.cache;
/**
* 缓存属性类
*
* @author Administrator
*
*/
public class CacheConfModel implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private long beginTime;// 缓存开始时间
private boolean isForever = false;// 是否持久
private int durableTime;// 持续时间
public long getBeginTime() {
return beginTime;
}
public void setBeginTime(long beginTime) {
this.beginTime = beginTime;
}
public boolean isForever() {
return isForever;
}
public void setForever(boolean isForever) {
this.isForever = isForever;
}
public int getDurableTime() {
return durableTime;
}
public void setDurableTime(int durableTime) {
this.durableTime = durableTime;
}
}
代码解读
每次增加缓存数据时会带上以下配置属性:
private long beginTime;// 缓存开始时间
private boolean isForever = false;// 是否持久
private int durableTime;// 持续时间
并且缓存小框架带有定期清除过期缓存的功能。结合上面例子,可以将是否持久设置为false,持续时间根据项目实际需求设置,每次访问都可在拦截器中更新缓存开始时间。这样,到了设置的持续时间,缓存小框架就自动把过期缓存自动清理掉了。
具体的还要实际调试代码,看是否有需要改进的地方。