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

最近再做一个小程序项目,在这个项目中需要有一个管理员用户在线数实时刷新的功能,一开始用的是网上广为流传的做法,即创建一个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
<think>好的,我现在需要帮助用户设计一个基于UniApp的图书馆座位预约系统。首先,我得理清用户的需求。用户提到了功能需求、技术架构和实现方案,所以我要覆盖这三个方面。 首先,功能需求。根据引用[1]、[2]、[4],系统通常分为学生端和管理端。学生端需要注册登录、座位查询、预约、暂离、签到等功能。管理端需要管理学生信息、座位信息、预约记录等。可能还需要结合门禁系统,引用[4]提到进出刷卡据确定是否在馆,所以签到可能涉及位置或门禁验证。 接下来是技术架构。用户指定使用UniApp,所以前端用Vue.js和UniApp框架,支持多端。后端方面,引用[1]用Java,引用[3]用Node.js,用户可能需要选择合适的技术。考虑到UniApp的前后端分离,后端可以用Spring Boot或Node.js,据库用MySQL。引用[3]和[4]都提到MySQL,所以据库选型明确。此外,引用[1]用了Mybatis,引用[3]用Express,可能需要根据团队熟悉度选择。 实现方案需要分步骤。首先环境搭建,安装HBuilder X和依赖。然后设计据库,包括学生表、座位表、预约记录等。引用[1]和[4]提到座位信息管理、预约记录,所以据库设计要涵盖这些。接着前后端开发,前端使用UniApp组件,后端提供API。接口文档需要明确,比如预约座位、暂离、签到等接口。测试和部署阶段要确保多端兼容,引用[4]提到与门禁系统结合,可能需要硬件对接。 注意事项方面,引用[2]提到高峰期并发,需要Redis缓存或队列处理。引用[4]提到用户体验,界面要美观易用。权限管理方面,管理员和学生权限不同,需做好控制。 可能的问题包括座位状态实时更新,如何防止重复预约,签到机制如何设计。引用[2]中的门禁系统集成可能需要蓝牙或定位验证用户是否到馆。此外,引用[1]用了token认证,需要在后端实现JWT来管理用户登录状态。 总结下来,我需要将功能模块细化,技术选型明确,并分步骤说明实现过程,同时考虑扩展性和性能优化。确保引用中的关键点都被涵盖,比如前后端分离、据库设计、权限管理和并发处理。</think>### 基于UniApp的图书馆座位预约系统设计与实现方案 #### 一、功能需求分析 1. **学生端功能**: - 用户认证:微信授权登录 + 学号绑定(需对接学校认证系统)[^2] - 座位查询:可视化展示楼层平面图,支持筛选空闲/占用状态(颜色区分)[^4] - 预约管理:支持即时预约(30分钟内到馆签到)、定时预约(提前24小时) - 暂离功能:15分钟临时锁座(需扫码离座/归座)[^2] - 签到机制:蓝牙信标定位 + 二维码双重验证[^4] - 违约记录:超时未签到扣信用分,累计3次禁用预约权限[^1] 2. **管理端功能**: - 动态座位管理:支持区域划分(普通区/静音区/研讨间)[^4] - 预约规则配置:开放时间、最大预约时长(建议4-8小时可调) - 据看板:实时监控入馆人、高峰时段统计[^2] - 异常处理:强制释放占座(超时未签入自动释放)[^1] - 消息推送:系统公告、预约提醒(微信服务通知)[^3] #### 二、技术架构设计 $$系统架构图$$ ```mermaid graph TD A[UniApp前端] --> B[Vue.js组件化开发] B --> C{多端适配} C -->|微信小程序| D[微信API] C -->|H5| E[浏览器API] F[SpringBoot后端] --> G[Restful API] G --> H[MySQL集群] H --> I[Redis缓存] I --> J[定时任务] J --> K[座位状态同步] ``` 1. **前端技术栈**: - 核心框架:UniApp + Vue3 + Vite - 可视化组件:uCharts实现座位热力图[^4] - 地图引擎:集成腾讯地图SDK实现楼层导航 - 状态管理:Pinia管理全局预约状态 2. **后端技术栈**: - 微服务架构:SpringCloud Alibaba + Nacos注册中心 - 持久层:MyBatis-Plus + Sharding-JDBC分库分表 - 实时通信:WebSocket推送座位状态变更[^3] - 安全机制JWT令牌 + 接口签名验证 3. **据库设计**: ```sql CREATE TABLE seat ( seat_id VARCHAR(20) PRIMARY KEY, -- 座位编号A1-001 zone ENUM('普通区','静音区','研讨间'), status ENUM('空闲','已预约','使用中','维护中'), last_used DATETIME ); CREATE TABLE reservation ( order_id BIGINT AUTO_INCREMENT, user_id VARCHAR(12) NOT NULL, seat_id VARCHAR(20) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME, checkin_time DATETIME, status TINYINT(1) DEFAULT 0 -- 0待签到 1使用中 2已完成 3已取消 ); ``` #### 三、关键实现方案 1. **实时座位状态同步**: - 采用Redis发布订阅模式,当座位状态变更时触发事件 - 前端通过WebSocket建立长连接接收更新- 心跳检测机制保持连接活性(间隔30秒) 2. **预约冲突解决算法**: $$冲突检测公式:\exists t \in [t_{start}, t_{end}] \cap [t'_{start}, t'_{end}]$$ ```python def check_availability(seat_id, new_start, new_end): existing = Reservation.objects.filter( seat_id=seat_id, status__in=[0, 1], end_time__gt=new_start, start_time__lt=new_end ) return not existing.exists() ``` 3. **高并发优化**: - 使用Redisson分布式锁处理预约请求 - 据库读写分离 + 垂直分库(用户库、座位库、订单库) - 热点据预加载:每日凌晨生成座位状态快照 #### 四、实施步骤建议 1. **环境搭建**: - 安装HBuilderX 3.6+,配置微信开发者工具 - 创建UniApp项目选择vue3模板 - 后端使用IDEA创建SpringBoot 2.7+项目 2. **核心功能开发顺序**: 1. 用户认证模块(微信登录+学号绑定) 2. 座位地图渲染组件(使用svg动态绘制) 3. 预约状态机实现(包括超时自动释放) 4. 消息通知系统(模板消息+短信双通道) 3. **测试重点**: - 压力测试:JMeter模拟500并发预约 - 兼容性测试:iOS/Android微信客户端版本覆盖 - 异常测试:断网重连后的状态同步 #### 五、扩展性设计 1. **智能推荐模块**: - 基于用户历史偏好推荐座位(靠窗/插座位置) - 使用协同过滤算法:$$similarity(u,v) = \frac{\sum_{i}(r_{ui} - \bar{r}_u)(r_{vi} - \bar{r}_v)}{\sqrt{\sum_{i}(r_{ui} - \bar{r}_u)^2} \sqrt{\sum_{i}(r_{vi} - \bar{r}_v)^2}}$$ 2. **物联网集成**: - 通过ESP32开发板连接座位传感器 - 使用MQTT协议上报实时占用状态 - 硬件状态与系统据双重校验
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BoringRong

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

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

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

打赏作者

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

抵扣说明:

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

余额充值