7.10网站数据统计

需要统计的事项:
在这里插入图片描述

UV每次访问都要把用户的ip(不管该用户有无登录)存到某个数据结构里,方便进行统计

DAU只统计登录的用户(根据用户id),对于那些没有登录的游客不进行统计。1表示访问过,为活跃用户;0表示没有访问过,为不活跃用户;

DAU更关注用户用户有效性

1.在RedisKeyUtil中

private static final String PREFIX_UV = "uv";//统计uv相关数据的前缀
private static final String PREFIX_DAU = "dau";//统计dau相关数据的前缀
// 单日UV,记录UV是以天为单位的
public static String getUVKey(String date) {
    return PREFIX_UV + SPLIT + date;
}

// 区间UV,查看一周的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;
}

2.开发完key后,开发数据访问层,因为redis数据访问层可以直接写services

@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()));//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
        calendar.setTime(start);
        while (!calendar.getTime().after(end)) {//calendar.getTime()表明时间,after(end)表示 如果晚于end,有感叹号 表示 时间如果不晚于结束,则循环
            String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));//每次遍历得到key
            keyList.add(key);
            calendar.add(Calendar.DATE, 1);//加一天
        }

        // 合并这些数据
        String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));//redisKey是合并以后数据的key
        redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());//keyList.toArray()表示把keyList 转换为数组

        // 返回统计的结果
        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 connection) throws DataAccessException {
                String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));
                connection.bitOp(RedisStringCommands.BitOperation.OR,//第一个参数是RedisStringCommands.BitOperation.OR 即进行什么样的运算
                        redisKey.getBytes(), keyList.toArray(new byte[0][0]));//keyList.toArray()表示把keyList 转换为数组
                return connection.bitCount(redisKey.getBytes());
            }
        });
    }
}

3.在表现层中,新建拦截器

@Component
public class DataInterceptor implements HandlerInterceptor {

    @Autowired
    private DataService dataService;

    @Autowired
    private HostHolder hostHolder;//得到当前用户id

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//preHandle表示在请求之初统计,统计完后继续向下执行
        // 统计UV
        String ip = request.getRemoteHost();
        dataService.recordUV(ip);

        // 统计DAU
        User user = hostHolder.getUser();
        if (user != null) {//user不为空,才进行统计
            dataService.recordDAU(user.getId());
        }

        return true;
    }
}

之后在WebMvcConfig中,

@Autowired
private DataInterceptor dataInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dataInterceptor)//对于静态资源无需拦截
            .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}

3.新建DataController

要新建这个样子的页面
在这里插入图片描述

@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") 来告诉服务器的日期格式。传入三个参数start,end,model
                        @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><!-- 统计结果th:text="${uvResult}"-->
			</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>

测试:

只有管理员才能访问data

在这里插入图片描述
管理员登录后,直接敲这个路径访问data
在这里插入图片描述
在这里插入图片描述
UA只有一个,因为我们一直用自己的电脑
在这里插入图片描述
刚才我们用普通用户,版主,管理员三个账号进行了操作
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值