使用 SpringBoot + JWT+ Redis 实现在线人数统计功能
使用 SpringBoot + JWT+ Redis 实现在线人数统计功能
在线人数统计是Web应用中非常重要的一环,尤其对于大型网站和高流量平台而言。精准的在线人数统计不仅能够帮助网站管理员监控用户活动,还能在用户高峰期优化资源分配和服务器负载。传统的实现方式通常依赖于数据库,但在处理高并发数据时,数据库可能成为性能瓶颈。为了提高统计的实时性和系统的可扩展性,我们可以利用Redis作为存储解决方案,并结合JWT进行用户身份验证。
Redis,作为一种高性能的内存数据存储解决方案,能够高效地处理大量并发读写操作,非常适合用于存储和管理在线用户数据。它的Set数据结构使得我们可以轻松地管理在线用户列表,同时利用其过期时间功能来自动处理用户的会话超时问题。
JWT(JSON Web Token)则提供了一种简洁的方式来实现用户身份验证。它通过在用户登录时生成一个包含用户信息和有效期的Token,使得服务器在后续请求中可以通过解析Token来验证用户身份,从而简化了会话管理的复杂度。
在本设计方案中,我们将详细介绍如何使用Spring Boot框架搭建后端服务,如何结合JWT实现用户认证,以及如何利用Redis高效管理在线用户数据。我们还将展示如何使用Thymeleaf和Bootstrap构建前端页面,实现在线人数的实时展示。
通过本方案,你将学会如何将这些技术结合起来,创建一个高效、可靠的在线人数统计系统,提升用户体验并优化系统性能。
运行效果:
若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。
技术栈
Spring Boot3.3:用于构建后端应用程序。
JWT:用于用户认证和会话管理。
Redis:用于高效存储和更新在线用户数据。
Thymeleaf:用于前端模板渲染。
Bootstrap:用于前端样式和响应式设计。
Jquery:提供了一种高效的前端JavaScript库,帮助简化HTML文档遍历、事件处理、动画和Ajax交互,增强网页的响应式设计和用户体验。
项目结构
后端:Spring Boot + JWT + Redis
前端:Thymeleaf + Bootstrap + Jquery
环境配置
Maven配置(pom.xml
)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>online-user-statistics</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>online-user-statistics</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<jjwt.version>0.12.6</jjwt.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JSON Web Token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件(application.yml
)
spring:
redis:
host: localhost
port: 6379
password: 123456
security:
jwt:
secret: 37507d4cb936fdfb5dbb12a9a3983733
expiration: 3600
后端实现
WT 生成和解析工具类
package com.icoderoad.online_user_statistics.util;
import static java.time.temporal.ChronoUnit.SECONDS;
import java.security.Key;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
@Component
public class JwtUtil {
@Value("${spring.security.jwt.secret}")
private String secretKey;
@Value("${spring.security.jwt.expiration}")
private long expiration;// 过期时间以秒为单位
private Key key;
public String issueToken(String subject) {
return issueToken(subject, Map.of());
}
public String issueToken(String subject, String ...scopes) {
return issueToken(subject, Map.of("scopes", scopes));
}
public String issueToken(String subject, List<String> scopes) {
return issueToken(subject, Map.of("scopes", scopes));
}
public String issueToken(
String subject,
Map<String, Object> claims) {
String token = Jwts
.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(Date.from(Instant.now()))
.setExpiration(
Date.from(
Instant.now().plus(expiration, SECONDS)
)
)
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
return token;
}
public String getSubject(String token) {
return getClaims(token).getSubject();
}
private Claims getClaims(String token) {
Claims claims = Jwts
.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims;
}
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secretKey.getBytes());
}
public boolean isTokenValid(String jwt, String username) {
String subject = getSubject(jwt);
return subject.equals(username) && !isTokenExpired(jwt);
}
private boolean isTokenExpired(String jwt) {
Date today = Date.from(Instant.now());
return getClaims(jwt).getExpiration().before(today);
}
}
Redis配置和在线人数管理
package com.icoderoad.online_user_statistics.service;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class OnlineUserService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String ONLINE_USERS_KEY = "online_users";
public void userLogin(String username) {
redisTemplate.opsForSet().add(ONLINE_USERS_KEY, username);
redisTemplate.expire(ONLINE_USERS_KEY, 1, TimeUnit.HOURS); // 设置过期时间
}
public void userLogout(String username) {
redisTemplate.opsForSet().remove(ONLINE_USERS_KEY, username);
}
public Long getOnlineUserCount() {
return redisTemplate.opsForSet().size(ONLINE_USERS_KEY);
}
public Set<String> getOnlineUsers() {
return redisTemplate.opsForSet().members(ONLINE_USERS_KEY);
}
}
控制器实现
package com.icoderoad.online_user_statistics.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.icoderoad.online_user_statistics.service.OnlineUserService;
import com.icoderoad.online_user_statistics.util.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private OnlineUserService onlineUserService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public String login(@RequestParam String username, HttpServletRequest request) {
// 生成JWT token
String token = jwtUtil.issueToken(username);
// 登录并记录在线用户
onlineUserService.userLogin(username);
return token;
}
@PostMapping("/logout")
public String logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
// 从请求中提取JWT token
String token = request.getHeader("Authorization").replace("Bearer ", "");
String username = jwtUtil.getSubject(token);
// 注销并更新在线用户
onlineUserService.userLogout(username);
}
return "Logged out";
}
@GetMapping("/onlineCount")
public Long getOnlineUserCount() {
return onlineUserService.getOnlineUserCount();
}
}
视图控制器
package com.icoderoad.online_user_statistics.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
前端实现
Thymeleaf模板(index.html
)
在 src/main/resources/templates
目录下创建 index.html
模板文件。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>在线人数统计</title>
<!-- 使用Bootstrap 4.5.2 CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>在线人数统计</h1>
<button id="loginBtn" class="btn btn-primary">登录</button>
<button id="logoutBtn" class="btn btn-secondary">登出</button>
<h2>在线人数: <span id="onlineCount">0</span></h2>
</div>
<!-- 使用jQuery 3.5.1 -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<!-- 使用Bootstrap 4.5.2 JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
$('#loginBtn').click(function() {
$.post('/api/login', {username: 'testuser'})
.done(function(token) {
localStorage.setItem('token', token);
updateOnlineCount();
});
});
$('#logoutBtn').click(function() {
const token = localStorage.getItem('token');
$.post('/api/logout', {}, {
headers: {
'Authorization': 'Bearer ' + token
}
})
.done(function() {
localStorage.removeItem('token');
updateOnlineCount();
});
});
function updateOnlineCount() {
$.get('/api/onlineCount')
.done(function(count) {
$('#onlineCount').text(count);
});
}
// 页面加载时更新在线人数
updateOnlineCount();
});
</script>
</body>
</html>
总结
本文详细介绍了如何使用 Spring Boot、JWT 和 Redis 来实现一个在线人数统计系统。通过结合这些技术,我们可以高效地管理用户会话,并实时统计在线人数。后端使用 JWT 进行认证,Redis 用于存储在线用户数据,前端使用 Thymeleaf 和 Bootstrap 进行展示。这个系统不仅提高了用户体验,还保证了系统的高效性和可靠性。