这里我整理了几种方法:

方法一:通过监听器HttpSessionListener

步骤一:编写监听器

//实现HttpSessionListener接口
@WebListener
public class OnlineUserListener implements HttpSessionListener {
    public static List<String> list = new ArrayList<String>();

    //监听session的创建,synchronized 防并发bug
    @Override
    public synchronized void sessionCreated(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("【HttpSessionListener监听器】list  增加");
        list.add(session.getId());
        session.getServletContext().setAttribute("count", list.size());
    }

    @Override
    public synchronized void sessionDestroyed(HttpSessionEvent httpSessionEvent) {//监听session的撤销
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("【HttpSessionListener监听器】list  减少");
        list.remove(session.getId());
        session.getServletContext().setAttribute("count", list.size());
    }
}

步骤二:编写Controller:

@RestController
@Slf4j
public class OnlineUsersController {

    /**
     * 在线人数
     *
     * @return
     */
    @PostMapping("/getOnlineCount")
    public Integer getOnlineUsersStatus(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        try {
            //把sessionId记录在浏览器
            Cookie c = new Cookie("JSESSIONID", URLEncoder.encode(httpServletRequest.getSession().getId(), "utf-8"));
            c.setPath("/");
            //先设置cookie有效期为2天,不用担心,session不会保存2天
            c.setMaxAge(48 * 60 * 60);
            httpServletResponse.addCookie(c);
        } catch (Exception e) {
            e.printStackTrace();
        }
        HttpSession session = httpServletRequest.getSession();
        Integer count = (Integer) session.getAttribute("count");
        log.debug(String.valueOf(count));
        return count;
    }
}

当用户打开浏览器,获取session,进行一个在线人数的展示,但是这个这个方法有两个弊端:

第一个问题:

当用户只是打开浏览器了,就已经有人数的显示,此时用户并没有登录网站

第二个问题:

当用户登录成功过,由于session的有效时间为30分钟,所以,在30分钟内,这个人数依旧是不准确的

方法二:通过读取Redis中登录成功的用户数量

步骤一:在配置文件中添加

# redis配置

spring.redis.host=192.168.8.92
spring.redis.port=6379

步骤二:编写Controller层

@RestController
@Slf4j
public class OnlineUsersContrller {
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 登录-获取在线人数
     *
     * @return
     */
    @PostMapping("/getOnlineCount")
    public Result<Integer> getRedis() {
        //获得所有的key
        Set<String> keys = redisTemplate.keys("sys:cache:user::" + "*");
        if (keys.size() > 0) {
            return Result.OK(keys.size());
        } else {
            return Result.OK(0);
        }
    }
}

当用户登录成功后,用户信息存储在redis缓存中的时候,通过获取redis中已存在的用户信息的数量,能够实现在线人数的显示。
同样,这个方法也存在一个问题,就是当用户没有点击退出,而是关闭了整个浏览器,redis中不能及时做出响应,虽然一段时间后redis能够自动做出处理,清理长时间无操作的用户信息,但是这个也并不进行实时效果

方法三:通过WebSocket实现实时展示在线人数

步骤一:导入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

步骤二:编写OnlineUserCountWebSocket类

@Component
@ServerEndpoint("/websocket")  //该注解表示该类被声明为一个webSocket终端
public class OnlineUserCountWebSocket {

    private Session session;

    //初始在线人数
    private static int onlineNum = 0;
    
    private static CopyOnWriteArraySet<OnlineUserCountWebSocket> userCountWebSockets =
            new CopyOnWriteArraySet<OnlineUserCountWebSocket>();
    
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        userCountWebSockets.add(this);
        addOnlineCount();
    }

    @OnClose
    public void onClose() {
        userCountWebSockets.remove(this);
        subOnlineCount();
    }

    public synchronized int getonlineNum() {
        return OnlineUserCountWebSocket.onlineNum;
    }

    public synchronized int subOnlineCount() {
        return OnlineUserCountWebSocket.onlineNum--;
    }

    public synchronized int addOnlineCount() {
        return OnlineUserCountWebSocket.onlineNum++;
    }

}

步骤三:前端html页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My WebSocket</title>
</head>
<body>
<h2 οnclick="openTanTan()" style="color:dodgerblue;text-align: center">WeTanTan</h2>
<div id="tantanbox" style="width:800px;height:498px;margin:0 auto;">
    <div id="box-left" style="float:left;width:600px;height:500px;">
        <div id="content" style="height:350px;border:1px solid dodgerblue;"></div>
        <div id="input-content" style="height:145px;border:1px solid dodgerblue;"><textarea id="input-message"
                                                                                            style="width:99%;height:96%"></textarea>
        </div>
        <button id="btn1" οnclick="sendMessage()"
                style="width:38px;height:18px;line-height:18px;border:0;margin:4px 0 0 10px;">send
        </button>
        <button id="btn2" οnclick="ClosedWebSocket()"
                style="width:38px;height:18px;line-height:18px;border:0;margin:4px 0 0 10px;">close
        </button>
    </div>
    <div id="box-right" style="float:left;width:195px;height:497px;border:1px solid dodgerblue">
        <span style="font-size:10px;color:dodgerblue;font-weight:bold;">在线人数:</span>
        <hr/>
        <div id="count-users" style="font-size:10px;color:dodgerblue;font-weight:bold"></div>
    </div>

</div>
</body>
<script>
    var usernames = [];
    var username = "";
    window.onload = function () {
        document.getElementById("tantanbox").style.display = 'none';
    };

    function openTanTan() {
        alert("open your WeTanTan chat!");
        username = window.prompt("输入你的名字:");
        //document.write("welcome to wetantan!<p id='username'>"+username+"</p>");
        usernames.push(username);
        document.getElementById("tantanbox").style.display = 'block';

        /*--------------------开始websocket部分----------- ---------*/

        var ws = null;//申请一个websocket对象
        //判断当前浏览器是不是支持websocket
        if ('window' in window) {
            startWebSocket();
        } else {
            alert("NO SUPPORT!");
            return;
        }
    };

    function startWebSocket() {
        ws = new WebSocket("ws://localhost:8080/websocket");
        document.getElementById("count-users").innerHTML = "";
        ws.onclose = function () {
            sendInnerHtml("<span style='font-size: 20px;color:greenyellow;font-weight: bold'>" + "Bye~~" + "</span>");
        };
        ws.onerror = function () {
            sendInnerHtml("websocket error");
        };
        ws.onopen = function (event) {
            sendInnerHtml("Welcome to WeTanTan Light social web!  " + "<span style='font-size: 30px;font-weight: bold;color:orange'>" + username + "</span>");
        };
        ws.onmessage = function (e) {
            console.log(e)
            document.getElementById("count-users").innerHTML = e.data + "人";
            sendInnerHtml('<br/>' + "127.0.0.1" + ":" + e.data);
        };
        //监听方法,监听websocket关闭
        window.onbeforeunload = function () {
            ws.close();
        };
    }

    function sendInnerHtml(innerHtml) {
        document.getElementById("content").innerHTML += innerHtml + '<br/>';
    };

    function ClosedWebSocket() {
        ws.close();
        setTimeout(function () {
            document.getElementById("tantanbox").style.display = 'none';
        }, 1000);
    };

    function sendMessage() {
        var message = document.getElementById("input-message").value;
        ws.send(message);
        document.getElementById("content").innerHTML = '<br/>' + username + ":" + message;
        document.getElementById("input-message").value = "";
    };
</script>
</html>

方法三参考文章:

总结:如果需求需要特别实时同步人数的话,建议使用webSocket来实现,如果需求能够接受在线人数的偏差,即关闭浏览器人数的偏差,可以使用方法二