springBoot+redis+拦截器简单实现互斥登录


一、互斥登录?

互斥登录,从字面上理解就是登录存在交叉影响,一个账号登录后,进行二次登录,在实际生活中,很多网站都做了多点登录互斥的操作,简单来说就是同一个账号,只能在一台电脑上登录,如果有人在其他地方登录,那么原来登录的地方就会自动下线,再进行操作就会弹出登录界面,需要登录的软件不管是手机端还是PC端都是这样,比如企鹅,微信,

二、创建流程与编码

1. 正常创建SpringBoot项目

pom文件依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

前端模板是thymeleaf,缓存数据redis,我们创建项目时需要勾选Thymeleaf&&Redis

2. 项目结构

在这里插入图片描述

3.1 pojo(实体类)

@Data
public class User implements Serializable {
    private Integer id;

    private String password;
    
	//版本
    private Integer version;
}

3.2 config(配置类)

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private RedisTemplate<Object,Object> redisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        Interceptor interceptor=new Interceptor(redisTemplate);
        registry.addInterceptor(interceptor)
                .excludePathPatterns("/toLogin","/inLogin");
    }

    @EventListener({ApplicationReadyEvent.class})
    void applicationReadyEvent() {
        // 这里需要注url:端口号+方法名
        String url = "http://localhost:8081/toLogin";
        Runtime runtime = Runtime.getRuntime();
        try {
            runtime.exec("rundll32 url.dll,FileProtocolHandler " + url);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

这里需要在配置类中装配好redis再添加到拦截器内,拦截器内需要重写一个无参构造把redis带入

3.3 Interceptor(拦截器)

public class Interceptor implements HandlerInterceptor {

    private RedisTemplate<Object,Object>redisTemplate;

    public Interceptor(RedisTemplate<Object,Object> redisTemplate) {
        this.redisTemplate=redisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Integer id = Integer.parseInt(request.getParameter("id"));
        String password = request.getParameter("password");
        Integer version = Integer.parseInt(request.getParameter("version"));
        User user =(User) redisTemplate.opsForValue().get("user");

        //判断是否是二次登录的用户
        if (id.equals(user.getId())){
            //再判断身份是否过期
            if (!version.equals(user.getVersion())){
                response.setStatus(444);
                System.out.println("拦截,下线...");
                return false;
            }
        }else {
            return true;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

3.4 controller(控制层)

@Controller
public class UserController {

    @Resource
    private RedisTemplate<Object,Object>redisTemplate;

    /**
     * 进入登录页
     * @return
     */
    @RequestMapping("/toLogin")
    public String login(){
        return "login";
    }

    /**
     * 执行登录
     * @param id
     * @param password
     * @param model
     * @return
     */
    @PostMapping("/inLogin")
    public String inLogin(Integer id, String password, Model model){
    	//由于是单线程项目 所以直接从redis获取用户信息判断
        User user=(User) redisTemplate.opsForValue().get("user");
        //无此用户,新增
        if (user==null){
            user.setId(id);
            user.setPassword(password);
            user.setVersion(1);
        } else {
        	//存在此用户,版本号+1
            user.setVersion(user.getVersion()+1);
        }
        model.addAttribute("user",user);
        //存入redis缓存供之后请求拦截使用
        redisTemplate.opsForValue().set("user",user);
        return "index";
    }

    /**
     * 测试
     * @return
     */
    @RequestMapping("/text")
    public String text(){
        return "success";
    }

}

3.5 index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<form th:action="@{/text}">
    <input type="hidden" name="id" th:value="${user.id}">
    <input type="hidden" name="password" th:value="${user.password}">
    <input type="hidden" name="version" th:value="${user.version}">
    <input type="submit" value="测试">
</form>
</body>
</html>

三、执行流程

打开两个路径都指向登录的页面

在这里插入图片描述
两个页面前后登录一样的id与pwd

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

登录完成后我们看第一个页面测试,也就是第一个用户登录,现在它的账号已被第二个页面登录,省份状态已过期,不能直接执行其他操作,我们直接给出状态码444

点击测试按钮,看dug

在这里插入图片描述
id一致,是存在二次登录的用户…下一步

在这里插入图片描述
再看版本号不一致肯定直接退出了,拦截器返回false

在这里插入图片描述
前端状态码444

在这里插入图片描述

那么互斥登录成不成功就看第二个页面是否能测试成功,拦截器是否能返回true

在这里插入图片描述
点击测试看dug

在这里插入图片描述
版本号一致肯定返回true了

在这里插入图片描述

测试成功

总结

此次只是单片机的一个程序,实际互斥登录多运用于多线程程序里,本文分享仅仅只是纯分享解决的一个思路,有问题还请大佬指出。


版权声明:本文为m0_57198980原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。