JooLun Plus 百科 ⬇ 源码下载

08.10 商城用户在线统计原理

102353 于 2026-10-17 14:14:33 +08:00 修改了此页面

原理分析

  • 用户登录后,每隔一段时间请求接口时,表示当前用户在线。服务器则可以定义一个时间差,例如:5分钟内客户端有请求,视为在线用户
  • Redis基于内存进行读写,性能自然比关系型数据库好得多,而且它所提供的Zset可以很方便的构建出一个在线用户的统计服务
  • 服务端监控到有接口请求,则更新在线状态

实现代码

ThirdSessionInterceptor拦截器中调用UserOnlineUtil方法

/**
 * Copyright (C) 2018-2019
 * All rights reserved, Designed By www.joolun.com
 * 注意:
 * 本软件为www.joolun.com开发研制,未经购买不得使用
 * 购买后可获得全部源代码(禁止转卖、分享、上传到码云、github等开源平台)
 * 一经发现盗用、分享等行为,将追究法律责任,后果自负
 */
package com.joolun.cloud.mall.api.util;

import cn.hutool.core.util.StrUtil;
import com.joolun.cloud.common.data.tenant.TenantContextHolder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * 在线用户统计工具类
 * 参考:https://www.jb51.net/article/210809.htm
 * @author www.joolun.com
 * @date 2022-06-02 14:20:02
 */
@Component
public class UserOnlineUtil {
	private static final String ONLINE_USERS = "online_users";

	@Resource
	private RedisTemplate redisTemplate;

	/**
	 * 添加用户在线信息
	 * @param clientType 用户端
	 * @param userId
	 * @return
	 */
	public Boolean online(String clientType, String userId) {
		return this.redisTemplate.opsForZSet().add(StrUtil.format("{}:{}:{}", TenantContextHolder.getTenantId(), ONLINE_USERS, clientType), userId, Instant.now().toEpochMilli());
	}

	/**
	 * 获取一定时间内,在线的用户数量
	 * @param clientType 用户端
	 * @param duration
	 * @return
	 */
	public Long count(String clientType, Duration duration) {
		LocalDateTime now = LocalDateTime.now();
		return this.redisTemplate.opsForZSet().count(StrUtil.format("{}:{}:{}", TenantContextHolder.getTenantId(), ONLINE_USERS, clientType),
				now.minus(duration).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
				now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
	}

	/**
	 * 获取所有在线过的用户数量,不论时间
	 * @param clientType 用户端
	 * @return
	 */
	public Long count(String clientType) {
		return this.redisTemplate.opsForZSet().zCard(StrUtil.format("{}:{}:{}", TenantContextHolder.getTenantId(), ONLINE_USERS, clientType));
	}

	/**
	 * 清除超过一定时间没在线的用户数据
	 * @param duration
	 * @return
	 */
	public Long clear(Duration duration) {
		return this.redisTemplate.opsForZSet().removeRangeByScore(StrUtil.format("{}:{}", TenantContextHolder.getTenantId(), ONLINE_USERS), 0,
				LocalDateTime.now().minus(duration).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
	}
}