7.5 网站数据统计

  • Post author:
  • Post category:其他

目录

 规划RedisKeyUtil

DataService

表现层

1. 记录值

2. 查看值

data.html 

配置权限:只有管理员能使用


UV关注的是访问量,不关注有没有登录,只要访问过就都算,基于IP地址排重统计。DAU,基于用户ID排重统计(更关注用户的有效性),要求结果必须精确。

 规划RedisKeyUtil

    private static final String PREFIX_UV = "uv";
    private static final String PREFIX_DAU = "dau";
    //单日UV
    public static  String getUVKey(String date){
        return PREFIX_UV + SPLIT + date;
    }

    //区间UV
    public static  String getUVKey(String startDate, String endDate){
        return PREFIX_UV +  SPLIT + startDate + SPLIT + endDate;
    }

    //单日活跃用户
    public static String getDAUKey(String date){
        return PREFIX_DAU + SPLIT + date;
    }

    //区间活跃用户
    public static String getDAUKey(String startDate, String endDate){
        return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
    }

DataService

对于HyperLogLog操作的是字符串

但是对于BitMap操作的是字节数组,利用or运算,而且需要使用到RedisConnection实现操作!

@Service
public class DataService {
    @Autowired
    private RedisTemplate redisTemplate;

    private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");

    //将指定的IP计入UV
    public void recordUV(String ip){
        String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
        redisTemplate.opsForHyperLogLog().add(redisKey, ip);
    }

    //统计指定日期范围内的UV
    public long calculateUV(Date start, Date end){
        if(start == null || end == null){
            throw new IllegalArgumentException("参数不能为空");
        }

        //整理该日期范围内的key
        List<String> keyList = new ArrayList<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(start);
        while(!calendar.getTime().after(end)){
            String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));
            keyList.add(key);
            calendar.add(Calendar.DATE, 1);
        }

        //合并这些数据
        String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));
        redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());

        //返回统计的结果
        return  redisTemplate.opsForHyperLogLog().size(redisKey);
    }

    //将指定用户计入DAU
    public void recordDAU(int userId){
        String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));
        redisTemplate.opsForValue().setBit(redisKey, userId, true);
    }

    //统计指定范围内的DAU
    public long calculateDAU(Date start, Date end){
        if(start == null || end == null){
            throw new IllegalArgumentException("参数不能为空");
        }

        //整理该日期范围内的key
        List<byte[]> keyList = new ArrayList<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(start);
        while(!calendar.getTime().after(end)){
            String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));
            keyList.add(key.getBytes());
            calendar.add(Calendar.DATE, 1);
        }

        //进行OR运算
        return(long) redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));
                redisConnection.bitOp(RedisStringCommands.BitOperation.OR,
                        redisKey.getBytes(), keyList.toArray(new byte[0][0]));
                return redisConnection.bitCount(redisKey.getBytes());
            }
        });
    }
}

表现层

1. 记录值

每次请求都得记录(写在拦截器中)DataInterceptor 

@Component
public class DataInterceptor implements HandlerInterceptor {
    @Autowired
    private DataService dataService;
    @Autowired
    private HostHolder hostHolder;
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        //统计UV
        String ip = request.getRemoteHost();
        dataService.recordUV(ip);
        //统计DAU
        User user = hostHolder.getUser();
        if(user != null){
            dataService.recordDAU(user.getId());
        }
        return true;
    }
}

配置拦截器 

        registry.addInterceptor(dataInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.jpg", "/**/*.jpeg", "/**/*.png");

2. 查看值

DataController

import com.newcoder.community.service.DataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.Date;
@Controller
public class DataController {
    @Autowired
    private DataService dataService;

    //统计页面
    @RequestMapping(path ="/data", method = {RequestMethod.GET, RequestMethod.POST})
    public String getDataPage(){
        return "/site/admin/data";
    }
    //统计网站UV
    @RequestMapping(path = "/data/uv", method = RequestMethod.POST)
    public String getUV(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,
                        @DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model){
        long uv = dataService.calculateUV(start, end);
        model.addAttribute("uvResult", uv);
        model.addAttribute("uvStartDate", start);
        model.addAttribute("uvEndDate",end);
        //回到模板页面
        return "forward:/data";//表示我处理到一半,然后接着交给上面的方法进行处理,所以上面的方法要接收两种类型的请求方法方式
        //请求在转发的过程中类型保持不变。
    }

    //统计活跃用户
    @RequestMapping(path = "/data/dau", method = RequestMethod.POST)
    public String getDAU(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,//告诉页面我希望页面传递过来的日期格式
                        @DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model){
        long dau = dataService.calculateDAU(start, end);
        model.addAttribute("dauResult", dau);
        //把日期参数重新传递回给页面
        model.addAttribute("dauStartDate", start);
        model.addAttribute("dauEndDate",end);
        //回到模板页面
        return "forward:/data";//表示我处理到一半,然后接着交给上面的方法进行处理,所以上面的方法要接收两种类型的请求方法方式
        //请求在转发的过程中类型保持不变。
    }
}

data.html 

<!-- 内容 -->
<div class="main">
	<!-- 网站UV -->
	<div class="container pl-5 pr-5 pt-3 pb-3 mt-3">
		<h6 class="mt-3"><b class="square"></b> 网站 UV</h6>
		<form class="form-inline mt-3" method="post" th:action="@{/data/uv}">
			<input type="date" class="form-control" required name="start" th:value="${#dates.format(uvStartDate,'yyyy-MM-dd')}"/>
			<input type="date" class="form-control ml-3" required name="end" th:value="${#dates.format(uvEndDate,'yyyy-MM-dd')}"/>
			<button type="submit" class="btn btn-primary ml-3">开始统计</button>
		</form>
		<ul class="list-group mt-3 mb-3">
			<li class="list-group-item d-flex justify-content-between align-items-center">
				统计结果
				<span class="badge badge-primary badge-danger font-size-14" th:text="${uvResult}">0</span>
			</li>
		</ul>
	</div>
	<!-- 活跃用户 -->
	<div class="container pl-5 pr-5 pt-3 pb-3 mt-4">
		<h6 class="mt-3"><b class="square"></b> 活跃用户</h6>
		<form class="form-inline mt-3" method="post" th:action="@{/data/dau}">
			<input type="date" class="form-control" required name="start" th:value="${#dates.format(dauStartDate,'yyyy-MM-dd')}"/>
			<input type="date" class="form-control ml-3" required name="end" th:value="${#dates.format(dauEndDate,'yyyy-MM-dd')}"/>
			<button type="submit" class="btn btn-primary ml-3">开始统计</button>
		</form>
		<ul class="list-group mt-3 mb-3">
			<li class="list-group-item d-flex justify-content-between align-items-center">
				统计结果
				<span class="badge badge-primary badge-danger font-size-14" th:text="${dauResult}">0</span>
			</li>
		</ul>
	</div>				
</div>

配置权限:只有管理员能使用

.antMatchers(
    "/discuss/delete",
    "/data/**"
)
.hasAnyAuthority(
    AUTHORITY_ADMIN
)

 


版权声明:本文为weixin_37841366原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。