SpringBoot-使用心跳机制+JWT实现用户实时在线数统计

本文介绍了一种使用JWT(Json Web Token)来精确统计用户实时在线数的方法,通过前端定时发送心跳包续期Token,后端维护一个过期时间Map,以此判断用户的在线状态,实现了60秒精度的在线人数统计。

最近再做一个小程序项目,在这个项目中需要有一个管理员用户在线数实时刷新的功能,一开始用的是网上广为流传的做法,即创建一个session监听器,在用户登录时即创建一个session,监听器记录下来并且把count加一,当用户点击注销时把session给remove掉,count减一。但是这个方案只适合估计一个值,而不适合做精确的在线人数判断,譬如,当用户关闭浏览器时并不会触发session监听,当下一次登录时仍然会让count加一;或者在session过期时,session监听并不能做一个实时的响应去将在线数减一,当用户在次登陆,由于cookie中含有的session_id不同而导致session监听器记录下session创建,而使count加一。

一、使用JWT做用户实时在线数判断的原理

本文不仔细地讲解什么是JWT,因为关于这个的文章已经写了很多了,但是在这里还是需要提一下什么是JWT。

1.1、什么是JWT

JWT即Json Web token(Json网络令牌),它由一种特殊的加密方式生成一串包含令牌状态(令牌过期时间、令牌签发时间、令牌的受众等)、用户数据等的字符串。前端在每次请求接口时都需要将这串令牌传送给后端校验这次请求是否正确,数据是否被篡改,用户是否已过期等。
在这里插入图片描述关于如何在SpringBoot下使用JWT的代码,都在这篇文章中:

* SpringBoot学习笔记(16)-整合JWT做身份验证_arong2048的博客-优快云博客

1.2、使用JWT做用户在线数统计的原理

在这里插入图片描述

实现思路:

根据时序图的这套方案,用户如果60s内没有任何操作(不调用接口去传递token)则判定该用户为下线状态,当用户重新登陆或者再次操作则判定为在线状态。这其实是心跳机制思想的一种实现,类似于Redis集群中的哨兵对Master主观下线的过程:每10s对Master发送一个心跳包,10s内没有响应则说明Master已经下线了。这里采用的是60s作为一个生存值,如果60s内该用户没有在此页面(如果在此页面,前端会间隔10s发送一次心跳包对Token进行续期+60s过期时间)上执行任何操作,也就不会携带Token发送请求到后端接口中,那么就无法给map中的token过期时间续期,所以该用户就处于过期状态。

二、具体的代码实现

  • OnlineCounter
@Component
public class OnlineCounter {
    //每次打开此类只初始化一次countMap
    private static Map countMap = new ConcurrentHashMap<String,Object>();

    /**
     * @auther: Arong
     * @description: 解析token并且将数据插入CountMap中
     * @param token
     * @return: void
     * @date: 2019/1/22 17:44
     */
    public void insertToken(String token){
        //获得当前时间(毫秒)
        long currentTime = System.currentTimeMillis();
        //解析token,获得签发时间
        Claims claims = null;
        try {
            claims = JWTUtils.parseJWT(token);
        } catch (Exception e) {
            throw new RuntimeException("token不存在或已过期");
        }
        Date issuedAt = claims.getIssuedAt();
        //以签发时间为key。当前时间+60s为value存入countMap中
        countMap.put(issuedAt.toString(),currentTime+60*1000);
    }

    /**
     * @auther: Arong
     * @description: 获取当前在线用户数
     * @param
     * @return: java.lang.Integer
     * @date: 2019/1/22 17:51
     */
    public Integer getOnlineCount(){
        int onlineCount = 0;
        //获取countMap的迭代器
        Iterator iterator = countMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String,Object>  entry = (Map.Entry<String, Object>) iterator.next();
            Long value = (Long) entry.getValue();
            if (value > System.currentTimeMillis()) {
                //过期时间大于当前时间则没有过期
                onlineCount++;
            }

        }

        return onlineCount;
    }
}

  • AdminController
 @Autowired
    private OnlineCounter onlineCounter;

    /**
     * @auther: Arong
     * @description: 获取当前实时在线人数(精确度为60s范围内)
     * @param
     * @return: int
     * @date: 2019/1/19 17:44
     */
    @GetMapping(value = "/getOnlineCount")
    public int getRealOnlineCount() {
        Integer onlines = onlineCounter.getOnlineCount();
        return onlines;
    }

  • JWTInterceptor
	@Autowired
    private OnlineCounter onlineCounter;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 允许跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        //后台管理页面产生的token
        String token = request.getHeader("authorization");
        //判断是否过期
        Optional.ofNullable(token)
                .map(n -> {
                    try {
                        return JWTUtils.parseJWT(n);
                    } catch (Exception e) {
                        throw new RuntimeException("token不存在");
                    }
                })
                .map(n->{
                    //存储该token方便记录在线人数
                    try {
                        onlineCounter.insertToken(token);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return n.getExpiration();
                })
                .orElseThrow(()->new RuntimeException("token已过期!请用户重新登陆!"));

        return true;
    }
  • 获取实时在线人数
/**
     * @auther: Arong
     * @description: 获取当前实时在线人数(精确度为60s范围内)
     * @param
     * @return: int
     * @date: 2019/1/19 17:44
     */
    @GetMapping(value = "/getOnlineCount")
    public int getRealOnlineCount() {
        Integer onlines = onlineCounter.getOnlineCount();
        return onlines;
    }
我通过修改苍穹外卖的部分源码,实现了一个“味来点餐系统“,这是我的主要流程:一、客户端核心流程 1用户登录与认证 o 流程:用户通过客户端输入账号密码 → 后端校验用户信息 → 生成 JWT 令牌并返回给客户端 → 客户端后续请求携带 JWT,通过自定义拦截器进行认证。 o 技术实现使用 JWT 实现无状态认证,ThreadLocal 存储用户信息,拦截器统一校验 Token 有效性。 5. 菜品浏览与购物车管理 o 流程:用户浏览菜品分类和详情 → 选择菜品规格并加入购物车 → 购物车据通过 Redis 缓存,支持实时增删改查。 o 技术实现:Redis 缓存菜品和套餐据,结合布隆过滤器解决缓存穿透问题。 6. 下单与支付 o 流程:用户提交订单 → 后端校验库存并生成订单 → 调用支付接口完成支付 → 支付成功后通过 WebSocket 实时通知商家端。 o 技术实现使用 Redis + Lua 脚本实现分布式锁,防止高并发下菜品超卖。 7. 催单与订单状态查询 o 流程:用户发起催单 → 后端通过 WebSocket 推送消息至商家端。 o 技术实现:WebSocket 实现客户端与商家端的实时通信,结合 Spring Boot 的 @ServerEndpoint 注解管理会话。 ________________________________________ 二、商家端核心流程 5. 员工登录与权限管理 o 流程:商家员工输入账号密码 → 后端校验后生成 JWT 令牌 → 拦截器验证权限(如管理员可操作员工管理模块)。 o 技术实现JWT 认证,AOP 实现公共字段自动填充(如创建时间、修改人)。 6. 菜品与套餐管理 o 流程:商家新增/编辑菜品或套餐 → 上传图片至服务器或云存储。 o 技术实现:MyBatis 实现多表关联查询,Redis 缓存菜品和套餐据。 7. 订单处理与统计 o 流程:商家处理订单(接单、派送、完成) 8. 来单提醒与催单处理 o 流程:新订单触发 WebSocket 推送 → 商家端实时接收提醒;用户催单时,商家端实时接收催单消息。 o 技术实现:WebSocket 实现实时通信。 ________________________________________ 三、系统架构与关键技术 3. 分层架构 o 网关层:Nginx 实现反向代理与负载均衡,提升并发处理能力。 o 应用层:Spring Boot 整合 SpringMVC、MyBatis,支持模块化开发;Swagger 自动生成接口文档。 o 据层:MySQL 存储核心业务据,Redis 缓存高频访问据(如购物车、菜品分类)。 4. 第三方服务集成 o 图片存储:使用云存储或本地存储菜品图片,降低服务器压力。 ________________________________________ 四、项目难点与解决方案 4. 高并发场景下的性能优化 o 使用 Redis 缓存菜品和套餐据,结合布隆过滤器解决缓存穿透问题。 o 通过 Redis + Lua 脚本实现分布式锁,防止高并发下菜品超卖。 5. 实时通信需求 o WebSocket 实现订单状态实时推送,避免轮询带来的性能损耗。 6. 公共字段自动填充 o 基于 AOP 实现公共字段(如创建时间、修改人)的自动填充,降低代码耦合。 如果你记住了上述流程,只用回复OK就可以了。
03-10
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringRong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值