Java 计算查看页面的当前用户数

Java 计算查看页面的当前用户数,java,spring-mvc,mockito,distributed-computing,Java,Spring Mvc,Mockito,Distributed Computing,我正在计算一个页面上查看用户的数量。基本上,当用户查看URLlocalhost:8080/itemdail.do?itemId=1时,页面将显示有多少用户同时在该页面上查看 解决方案方法 当用户查看特定页面时,该页面将继续每1秒向服务器发送一次AJAX请求,然后服务器将使用Map(都使用ConcurrentHashMap进行初始化)来包含itemId(以了解查看的页面)以及IP和超时计数(在内部地图中)每次收到请求后,计数将增加1。在请求过程中有一个线程,每2秒减少超时计数。如果最后,超时计数等

我正在计算一个页面上查看用户的数量。基本上,当用户查看URL
localhost:8080/itemdail.do?itemId=1
时,页面将显示有多少用户同时在该页面上查看

解决方案方法

当用户查看特定页面时,该页面将继续每1秒向服务器发送一次AJAX请求,然后服务器将使用
Map
(都使用
ConcurrentHashMap
进行初始化)来包含
itemId
(以了解查看的页面)以及
IP
超时
计数(在内部
地图中
)每次收到请求后,计数将增加1。在请求过程中有一个线程,每2秒减少超时计数。如果最后,超时计数等于0,应用程序将考虑用户停止查看该页面,然后线程将删除该条目,因此用户的数量将为DECR。1.这种方法的一个小问题是,由于超时数的增加快于减少,如果用户打开页面的时间足够长并关闭页面,那么应用程序将需要一段时间才能知道该用户已经离开页面,因为此时的超时数相当大

实施

// Controller

    @RequestMapping(value = AppConstants.ACTION_NUMBER_VIEWING, method = GET)
    @ResponseBody
    public int userItemViewing(HttpServletRequest request, @RequestParam(value = "itemId") String itemId) {
        try {
            return eventService.countUserOnline(request, itemId);
        } catch (CAServiceException e) {
            e.printStackTrace();
            LOG.error("========== Error when counting number of online user ==========" + e.getMessage());
        }

        return 0;
    }

// Service

private static Map<String, Map<String, Integer>> numberOfCurrentViews;
private Thread countDownOnlineThread;

class OnlineCountingDownRunnable implements Runnable {

    private List<String> timeoutList = new ArrayList<String>();

    private void cleanTimeoutIps() {
        for (String itemId : numberOfCurrentViews.keySet()) {
            Map<String, Integer> currentIps = numberOfCurrentViews.get(itemId);

            for(String ip : timeoutList){
                currentIps.remove(ip);
            }
        }
    }

    @Override
    public void run() {
        try {
            for (String itemId : numberOfCurrentViews.keySet()) {
                Map<String, Integer> currentIps = numberOfCurrentViews.get(itemId);

                for(String ip : currentIps.keySet()){
                    Integer timeout = new Integer(currentIps.get(ip).intValue() - 1);

                    if (timeout == 0) {
                        timeoutList.add(ip);
                    }

                    currentIps.put(ip, timeout);
                }
            }

            cleanTimeoutIps();

            // counting down time must be double increasing time
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            LOG.error("---------------- Thread error in counting down online user: " + e.getMessage());
        }

    }
}

public int countUserOnline(HttpServletRequest request, String itemId) throws CAServiceException {
    // create a count down timer to detect if the user does not view the page anymore
    String ip = request.getRemoteAddr();

    // init counting down online user map
    if (numberOfCurrentViews == null) {
        numberOfCurrentViews = new ConcurrentHashMap<String, Map<String, Integer>>();
    }

    // start thread to check user online
    if (countDownOnlineThread == null) {
        countDownOnlineThread = new Thread(new OnlineCountingDownRunnable());
        countDownOnlineThread.start();
    }

    LOG.debug("---------------- Requested IP: " + ip);
    if (ip == null || ip.isEmpty()) {
        throw new CAServiceException("======= Cannot detect Ip of the client =======");
    }

    if (numberOfCurrentViews.get(itemId) != null) {
        Map<String, Integer> userView = numberOfCurrentViews.get(itemId);

        if (userView.get(ip) != null) {
            userView.put(ip, userView.get(ip).intValue() + 1);
        } else {
            userView.put(ip, 1);
        }

        numberOfCurrentViews.put(itemId, userView);
    } else {
        Map<String, Integer> ips = new ConcurrentHashMap<String, Integer>();
        ips.put(ip, 1);

        numberOfCurrentViews.put(itemId, ips);
    }

    LOG.debug(String.format(
        "============= %s is seeing there is %s users viewing item %s =============",
        ip, numberOfCurrentViews.get(itemId).size(), itemId
    ));

    return numberOfCurrentViews.get(itemId).size();
}
//控制器
@RequestMapping(value=AppConstants.ACTION\u NUMBER\u查看,method=GET)
@应答器
public int userItemViewing(HttpServletRequest请求,@RequestParam(value=“itemId”)字符串itemId){
试一试{
返回eventService.countUserOnline(请求,itemId);
}捕获(案例服务){
e、 printStackTrace();
LOG.error(“================计算在线用户数时出错===========”+e.getMessage());
}
返回0;
}
//服务
私有静态地图numberofcurrentview;
私有线程countdownlinethread;
类OnlineCountingDownRunnable实现Runnable{
private List timeoutList=new ArrayList();
私有void cleanTimeoutIps(){
for(字符串itemId:NumberOfCurrentView.keySet()){
Map currentIps=numberOfCurrentViews.get(itemId);
用于(字符串ip:timeoutList){
当前ip。删除(ip);
}
}
}
@凌驾
公开募捐{
试一试{
for(字符串itemId:NumberOfCurrentView.keySet()){
Map currentIps=numberOfCurrentViews.get(itemId);
对于(字符串ip:currentIps.keySet()){
整数超时=新整数(currentIps.get(ip.intValue()-1);
如果(超时==0){
timeoutList.add(ip);
}
currentIps.put(ip,超时);
}
}
cleanTimeoutIps();
//倒计时时间必须是双倍递增时间
《睡眠》(2000年);
}捕捉(中断异常e){
e、 printStackTrace();
LOG.error(“--------------在线用户倒计时线程错误:”+e.getMessage());
}
}
}
public int countUserOnline(HttpServletRequest请求,字符串itemId)引发CAServiceException{
//创建倒计时以检测用户是否不再查看页面
字符串ip=request.getRemoteAddr();
//初始化倒计时在线用户地图
如果(numberOfCurrentViews==null){
numberOfCurrentViews=新的ConcurrentHashMap();
}
//启动线程以联机检查用户
if(countDownOnlineThread==null){
countDownOnlineThread=新线程(新的OnlineCountingDownRunnable());
countDownOnlineThread.start();
}
LOG.debug(“--------------请求的IP:+IP”);
if(ip==null | | ip.isEmpty()){
抛出新的CAServiceException(“==========无法检测到客户端的Ip=====”;
}
if(numberOfCurrentViews.get(itemId)!=null){
Map userView=numberOfCurrentViews.get(itemId);
if(userView.get(ip)!=null){
userView.put(ip,userView.get(ip.intValue()+1);
}否则{
userView.put(ip,1);
}
NumberOfCurrentView.put(itemId,userView);
}否则{
Map ips=新的ConcurrentHashMap();
ip.put(ip,1);
numberOfCurrentViews.put(itemId,ips);
}
LOG.debug(String.format(
“======================%s正在看到有%s个用户正在查看项目%s================”,
ip,numberOfCurrentViews.get(itemId).size(),itemId
));
返回numberOfCurrentViews.get(itemId).size();
}
问题

我不知道如何测试此功能,因为它需要多个IP地址才能查看页面。我曾尝试设置JMeter并像这样设置IP欺骗,但没有成功,所以我做了一个类似这样的小模拟测试来查看日志

@Test
public void testCountUserOnline() throws Exception {

    List<HttpServletRequest> requests = new ArrayList<HttpServletRequest>();

    for (int i = 0; i < 10; i ++) {
        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
        Mockito.when(request.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", i));
        requests.add(request);
    }

    List<Thread> threads = new ArrayList<Thread>();
    for (int i = 0; i < 10; i ++) {
        Thread thread = new Thread(new RequestRunnable(requests.get(i)));
        threads.add(thread);
        thread.start();
    }

    for (Thread thread : threads) {
        thread.join();
    }
}

class RequestRunnable implements Runnable {
    private HttpServletRequest request;

    public RequestRunnable(HttpServletRequest request) {
        this.request = request;
    }

    public void run() {
        try {
            int i = 0;
            while (i < 10) {
                eventService.countUserOnline(request, "1");
                i++;
                System.out.println(i);
                Thread.sleep(1000);
            }

        } catch (CAServiceException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
@测试
public void testCountUserOnline()引发异常{
列表请求=新建ArrayList();
对于(int i=0;i<10;i++){
HttpServletRequest=Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRemoteAddr()).thenReturn(String.format(“192.168.1.%s”,i));
请求。添加(请求);
}
List threads=new ArrayList();
对于(int i=0;i<10;i++){
线程线程=新线程(newrequestrunnable(requests.get(i));
线程。添加(线程);
thread.start();
}
用于(线程:线程){
thread.join();
}
}
类RequestRunnable实现Runnable{
私有HttpServletRequest;
公共请求可运行(HttpServletRequest请求){
this.request=请求;
}
公开募捐{
试一试{
int i=0;
而(i<10){
eventService.countUserOnline(请求“1”);
i++;
系统输出打印LN(i);
睡眠(1000);
// make it as static, since this needs to be singleton
private static Map<String, Map<String, Date>> numberOfCurrentViews;

// make it as static, since this needs to be singleton
private static Thread cleaningOffLineUserThread;

class OnlineCountingDownRunnable implements Runnable {

    @Override
    public void run() {
        try {
            while(true) {
                for (String itemId : numberOfCurrentViews.keySet()) {
                    Map<String, Date> userView = numberOfCurrentViews.get(itemId);

                    Iterator<Map.Entry<String, Date>> iterator = userView.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<String, Date> views = iterator.next();

                        Date lastViewTime = views.getValue();
                        Date currentTime = new Date();
                        long seconds = (currentTime.getTime() - lastViewTime.getTime()) / 1000;

                        if (seconds > TIMEOUT) {
                            iterator.remove();
                        }
                    }
                }

                // make the cleaning worker running every 2 seconds
                Thread.sleep(2000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            LOG.error("---------------- Thread error in counting down online user: " + e.getMessage());
        }

    }
}

public int countUserOnline(HttpServletRequest request, String itemId) throws CAServiceException {
    // create a count down timer to detect if the user does not view the page anymore
    String ip = request.getRemoteAddr();

    // init counting down online user map
    if (numberOfCurrentViews == null) {
        numberOfCurrentViews = new ConcurrentHashMap<String, Map<String, Date>>();
    }

    // start thread to check user online
    if (cleaningOffLineUserThread == null) {
        cleaningOffLineUserThread = new Thread(new OnlineCountingDownRunnable());
        cleaningOffLineUserThread.start();
    }

    LOG.debug("---------------- Requested IP: " + ip);
    if (ip == null || ip.isEmpty()) {
        throw new CAServiceException("======= Cannot detect Ip of the client =======");
    }

    Map<String, Date> userView;

    if (numberOfCurrentViews.get(itemId) != null) {
        userView = numberOfCurrentViews.get(itemId);
    } else {
        userView = new ConcurrentHashMap<String, Date>();
    }

    userView.put(ip, new Date());
    numberOfCurrentViews.put(itemId, userView);

    LOG.debug(String.format(
        "============= %s is seeing there is %s users, %s viewing item %s =============",
        ip, numberOfCurrentViews.get(itemId).size(),
        String.valueOf(numberOfCurrentViews.get(itemId).keySet()), itemId
    ));

    return numberOfCurrentViews.get(itemId).size();
}
@Test
public void testCountUserOnline() throws Exception {

    List<HttpServletRequest> requests = new ArrayList<HttpServletRequest>();

    HttpServletRequest request1 = Mockito.mock(HttpServletRequest.class);
    HttpServletRequest request2 = Mockito.mock(HttpServletRequest.class);
    HttpServletRequest request3 = Mockito.mock(HttpServletRequest.class);

    Mockito.when(request1.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", 1));
    Mockito.when(request2.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", 2));
    Mockito.when(request3.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", 3));

    requests.add(request1);
    requests.add(request2);
    requests.add(request3);

    List<Thread> threads = new ArrayList<Thread>();

    Thread thread1 = new Thread(new RequestRunnable(request1, 50, "1", "2"));
    Thread thread2 = new Thread(new RequestRunnable(request2, 20, "1", "3"));
    Thread thread3 = new Thread(new RequestRunnable(request3, 10, "3", "1"));

    threads.add(thread1);
    threads.add(thread2);
    threads.add(thread3);


    for (Thread thread : threads) {
        thread.start();
    }

    for (Thread thread : threads) {
        thread.join();
    }
}

class RequestRunnable implements Runnable {
    private HttpServletRequest request;
    private String [] pageSet;
    private int duration;


    public RequestRunnable(HttpServletRequest request, int duration, String ... pageSet) {
        this.request = request;
        this.pageSet = pageSet;
        this.duration = duration;
    }

    public void run() {
        try {
            int i = 0;
            while(i < duration) {
                i ++;
                for (String page : pageSet) {
                    eventService.countUserOnline(request, page);
                }

                LOG.debug("Second " + i);
                Thread.sleep(1000);
            }

        } catch (CAServiceException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}