上网的时候总能碰到这档子事,要回个什么帖子的时候,检测到你没登录,直接跳转到登录界面去了,或者账号被好基友在别的地方登录,我的被挤掉了,得重新登录,今儿就用过滤器Filter和监听器Listener实现这两个功能。
我用的是Spring+Spring MVC+Mybatis框架,不过貌似今天实现的功能和Spring还有MyBatis都没啥 关系,只和Spring MVC有联系。
首先new一个BaseController,作为所有Controller的父类,而这个BaseController的功能就是在Session中存入用户信息和读取用户信息的,这样所有的Controller都可以读取用户信息。
public class BaseController {
protected User getSessionUser(HttpServletRequest request){
return (User)request.getSession().getAttribute(CommonConstant.USER_CONTEXT);
}
protected void setSessionUser(HttpServletRequest request,User user){
request.getSession().setAttribute(CommonConstant.USER_CONTEXT, user);
}
}
接下来肯定就是喜闻乐见的LoginController了,重要步骤中已经写了注释,不赘述了
@Controller
@RequestMapping("/login")
public class LoginController extends BaseController{
private static Logger log=Logger.getLogger(BoardController.class);
@Autowired
private UserService userService;
@Autowired
private BoardService boardService;
@RequestMapping("/index")//主页,主页上有登录按钮
public ModelAndView index(){
ModelAndView mav=new ModelAndView("/index");
List<Board> list=boardService.getList();
mav.addObject("list",list);
return mav;
}
@RequestMapping("/doLogin")//执行登录的操作
public ModelAndView login(HttpServletRequest request,User user){
User dbUser=userService.getUserByName(user.getName());
ModelAndView mav=new ModelAndView("/login/login");
if(dbUser==null){
mav.addObject("error","用户不存在");
}else if(!dbUser.getPassword().equals(user.getPassword())){
mav.addObject("error","用户密码错误");
}else {
user.setId(dbUser.getId());
user.setType(dbUser.getType());
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(CommonConstant.DateToInt(new Date()));
user.setLocked(CommonConstant.USER_LOCKED);
LoginLog log=new LoginLog();
log.setIp(user.getLastIp());
log.setLoginDateTime(user.getLastVisit());
log.setuId(user.getId());
userService.loginSuccess(user,log);//登录成功,修改用户的个别信息和添加登录日志,写在一个Service中方便回滚
this.setSessionUser(request, user);//在session中添加用户信息
mav=new ModelAndView("redirect:/login/index");//登录成功之后跳转到首页
}
return mav;
}
@RequestMapping("/doRegister")
public ModelAndView register(HttpServletRequest request,User user){
ModelAndView mav=new ModelAndView("/login/register");
User dbUser=userService.getUserByName(user.getName());
if(dbUser!=null){
mav.addObject("error","用户已存在");
}else{
user.setCredit(100);
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(CommonConstant.DateToInt(new Date()));
user.setLocked(CommonConstant.USER_NOT_LOCKED);
user.setType(CommonConstant.USER_COMMON);
userService.register(user);
this.setSessionUser(request, user);
mav=new ModelAndView("redirect:/login/index");
}
return mav;
}
@RequestMapping("/logout")//退出操作
public ModelAndView logout(HttpSession session){
session.removeAttribute(CommonConstant.USER_CONTEXT);//在session中取消用户信息
return new ModelAndView("redirect:/login/index");
}
@RequestMapping("/initLogin")//跳转到登录界面
public String initLogin(){
return "login/login";
}
@RequestMapping("/initRegister")
public String initRegister(){
return "login/register";
}
}
因为这是个论坛的小Demo,所以会有发帖和回复操作,而无论是发帖还是回复,都必须得登录才能操作的嘛,如果游客点击相关按钮,直接跳转到登录界面。这里用Filter实现功能。
Filter可以监听到每一次请求,我们定义相关需要登录才能访问的访问路径,如果请求的路径和我们定义的路径相匹配但是用户还未登录,直接在Filter改变请求的路径,指向登录路径。
public class ForumFilter implements Filter{
private static final String FILTERED_REQUEST="@ @session_context_fifered_request";
private static final String [] resources={"/board/add","/topic/add","/post/add"};
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if(request!=null&&request.getAttribute(FILTERED_REQUEST)!=null){
chain.doFilter(request, response);
}else{
request.setAttribute(FILTERED_REQUEST, "123");
HttpServletRequest httpRequst=(HttpServletRequest)request;
User user=(User)httpRequst.getSession().getAttribute(CommonConstant.USER_CONTEXT);
if(user==null&&needLogin(httpRequst)){
request.getRequestDispatcher("/login/initLogin").forward(request, response);//指向登录路径
}else{
chain.doFilter(request, response);//不改变请求,直接执行
}
}
}
private boolean needLogin(HttpServletRequest request){//判断请求路径与需要登录才能执行的路径相匹配
for(String str:resources){
if(str.equals(request.getServletPath())){
return true;
}
}
return false;
}
@Override
public void init(FilterConfig arg0) throws ServletException {}
}
现在再去实现挤下线功能,这里使用Listener去实现,Listener实现HttpSessionAttributeListener接口可以监听Session的状态,如果对Session执行setAttribute操作,就会被Listener监听到,这正是我们想要的
public class LoginListener implements HttpSessionAttributeListener{
private static final Logger log=Logger.getLogger(LoginListener.class);
private static final String PREFIX="listener:";
private Map<String,HttpSession> map=new HashMap<>();
@Override
public void attributeAdded(HttpSessionBindingEvent arg0) {
if(arg0.getValue()!=null &&arg0.getValue() instanceof User){
User user=(User)arg0.getValue();
String str=PREFIX+user.getId();
log.info(user.toString());
HttpSession session=map.get(str);
if(session!=null){
session.removeAttribute(CommonConstant.USER_CONTEXT);
map.remove(str);
map.put(str, arg0.getSession());
}else{
map.put(str, arg0.getSession());
}
}
}
@Override
public void attributeRemoved(HttpSessionBindingEvent arg0) {}
@Override
public void attributeReplaced(HttpSessionBindingEvent arg0) {}
}
这里我们先定义一个Map,key为String,value为HttpSession,这里的key值用listener:+id ,这样可以保证不会重复,当别的地方上线了,我们从map中取出相应的HttpSession,去掉其中的我们定义的User,这样他再进行诸如发帖之类的操作时,检测到其Session中没有相应的用户信息,则会直接跳转到登录页面
最后记得在web.xml中把过滤器和监听器配置好
<filter>
<filter-name>forumFilter</filter-name>
<filter-class>com.faker.filter.ForumFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>forumFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>com.faker.filter.LoginListener</listener-class>
</listener>