由于是软件公司,项目用户量很小,而且是传统项目,所以用session来存储用户的登录状态。前端是移动端,我为session对象写了一个工具类,供自己用,记录一下,说不定以后还会用到。
先上session工具的代码:
package com.xxxx.utils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
public class SessionUtil {
private static final Long sessionMaxAge = 86400L;
private SessionUtil() {
}
private static class SessionUtilInner {
private static final SessionUtil SESSION_UTIL = new SessionUtil();
}
public static SessionUtil getSessionUtil() {
return SessionUtilInner.SESSION_UTIL;
}
private static ConcurrentHashMap<String, HttpSession> sessionMap;
static {
sessionMap = new ConcurrentHashMap<>();
}
public static ConcurrentHashMap<String, HttpSession> getSessionMap() {
sessionMap = cleanMap(sessionMap);
return sessionMap;
}
private static synchronized ConcurrentHashMap<String, HttpSession> cleanMap(
ConcurrentHashMap<String, HttpSession> map) {
if (map.size() < 1) {
return map;
}
Set<Entry<String, HttpSession>> entrySet = map.entrySet();
List<String> list = new ArrayList<>();
for (Entry<String, HttpSession> entry : entrySet) {
// 如果session过期了,就清除掉这个session
long max_age = sessionMaxAge * 1000L;
long time = new Date().getTime();
long creationTime = entry.getValue().getCreationTime();
long sessionAge = time - creationTime;
if (sessionAge > max_age) {
list.add(entry.getKey());
entry.getValue().setMaxInactiveInterval(1);
}
}
if (list != null && list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
map.remove(list.get(i));
}
}
list = null;
return map;
}
public static synchronized void cleanOldSession(String userInfo) {
if (sessionMap != null && sessionMap.size() > 0) {
Set<Entry<String, HttpSession>> entrySet = sessionMap.entrySet();
String sessionKey = "";
for (Entry<String, HttpSession> entry : entrySet) {
if (StringUtils.isNotBlank(userInfo)
&& userInfo.equals((String) entry.getValue().getAttribute("userInfo"))) {
sessionKey = entry.getKey();
entry.getValue().setMaxInactiveInterval(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
}
if (StringUtils.isNotBlank(sessionKey)) {
sessionMap.remove(sessionKey);
}
}
}
}
工具使用单例与工厂模式,单例用的懒汉式,因为懒汉式存在线程安全问题,所有用classloader机制,用内部类方式创建单例对象来保证线程安全。
session设定的有效时间是-1,即无过期时间,完全用代码来控制session的过期。这里有个问题,就是session的过期优先级是代码>web.xml配置>容器配置(我们容器用的tomcat),但是这只是一个优先级,代码虽然设置了不过期,但是如果web.xml或者tomcat的配置文件中设置了有效期的话,还是会过期。所以需要去掉web.xml和tomcat里关于session过期的配置。
然后,我们session有效时间是一天,每次取session添加用户的时候,都清理一遍超时的session。(这一机制建立在用户量少的的基础上,如果像电商项目一时间有几千几万用户登录,那就不可取了,不过那种项目一般使用redis来存储用户登录状态,也不用自己写session工具了不是?)
还有一个清理session的方法用于实现单点登录,传入用户存在session中的信息,如果一致,说明该用户又登录了,清除掉他以前的登录信息即session对象,保证每个用户只有一个登录对象。
工具类说完了,说一下使用;
String sessionInfo = appUser.getOpenid() + "|" + appUser.getUserName();
SessionUtil.cleanOldSession(sessionInfo);
HttpSession session = request.getSession(true);
session.setAttribute("userInfo", sessionInfo);
session.setMaxInactiveInterval(Integer.parseInt(sessionInvalidTime.trim())); // 设置session
String sessionKey = System.currentTimeMillis() + "";
SessionUtil.getSessionMap().put(sessionKey, session);
appUser.setSessinKey(sessionKey);
我session对象中存的是用户的openid(微信的用户id)和用户名,key是userInfo,每次登录时,先清除旧的session,然后通过
request.getSession(true)
创建一个新的session对象,存入信息,我的sessionkey用的是当前时间毫秒数(老生常谈,如果用户多可以用其他方法保证唯一,我们不需要暂时,需要的时候最简单的方法就是用uuid去掉_后返回),存到session工具类的map里面,把sessionkey返回给前端,所有需要登录的才能访问的接口,都要带着sessionkey来,才能访问。
然后,编写一个拦截器:
package com.xxxx.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.xxxx.utils.SessionUtil;
import com.xxxx.RegisterInfo;
import com.xxxx.Constant;
import com.xxxx.JsonUtil;
public class VisitIntercepeter extends HandlerInterceptorAdapter {
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
// TODO 自动生成的方法存根
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
// TODO 自动生成的方法存根
}
@SuppressWarnings("rawtypes")
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = null;
String sessionID = "";
if (StringUtils.isNotBlank(request.getParameter("sessinKey"))) {
sessionID = request.getParameter("sessinKey");
} else if (StringUtils.isNotBlank(request.getHeader("sessinKey"))) {
sessionID = request.getHeader("sessinKey");
}
if (sessionID != null) {
session = (HttpSession) SessionUtil.getSessionMap().get(sessionID);
if (session != null) {
String sessionValue = (String) session.getAttribute("userInfo");
if (StringUtils.isNotBlank(sessionValue)) {
return true;
}
}
}
RegisterInfo registerInfo = new RegisterInfo<>();
registerInfo.setCode(Constant.MESSAGE_ERROR_CODE);
registerInfo.setMessage(Constant.MESSAGE_SESSION_OVERDUE);
registerInfo.setResult(false);
String json = JsonUtil.parseBeanToJson(registerInfo);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(json);
return false;
}
}
我们的sessionkey存在head和参数中都是可以的,用sessionkey来拦截没有登录的用户。
这样就大功告成了,项目部署的时候记得前面讲到的关于容易对session配置的问题,这个很重要。