仿牛客社区——7.12统计网站数据

UV (Unique Visitor)

独立访客,需通过用户IP 排重统计数据每次访问都要进行统计。

HyperLogLog,性能好,且存储空间小。

DAU (Daily Active User)

日活跃用户,需通过用户ID排重统计数据访问过一次,则认为其活跃。

Bitmap,性能好、且可以统计精确的结果

定义的redisKey常量

public static final String PREFIX_UV="uv";  //独立用户

public static final String PREFIX_DAU="dau";  //活跃用户
private static final String SPLIT=":";
//获取用户key
	public static String getUserKey(int userId){
		return PREFIX_USER+SPLIT+userId;
	}

	//获取单日uv的key
	public static String getUVKey(String date){
		return PREFIX_UV+SPLIT+date;
	}

	//获取区间uv的key
	public static String getUVKey(String startDate,String endDate){
		return PREFIX_UV+SPLIT+startDate+SPLIT+endDate;
	}

	//获取单日dau的key
	public static String getDAUKey(String date){
		return PREFIX_DAU+SPLIT+date;
	}
	//获取区间dau的key
	public static String getDAUKey(String startDate,String endDate){
		return PREFIX_DAU+SPLIT+startDate+SPLIT+endDate;
	}

controller

@Controller
public class DataController {
	@Autowired
	private DataService dataService;

	@RequestMapping(value = "/data",method = {RequestMethod.GET,RequestMethod.POST})
	public String getDataPage(){
		return "/site/admin/data";
	}

	//统计独立访客
	@RequestMapping(value = "/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(value = "/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 uv = dataService.calculateDAU(start,end);
		model.addAttribute("dauResult",uv);
		model.addAttribute("dauStartDate",start);
		model.addAttribute("dauEndDate",end);
		return "forward:/data";   //转发:在一个请求中
	}
}

service

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

	private static SimpleDateFormat df=new SimpleDateFormat("yyyyMMdd");   //统一处理日期格式

	//将指定的ip计入uv
	//使用hyperloglog
	public void recordUV(String ip){
		//构建key
		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 RuntimeException("参数不能为空!");
		}
		//获取指定日期内key的集合
		List<String> keyList=new ArrayList<>();
		//获取获取系统的当前日历对象
		Calendar calendar=Calendar.getInstance();
		calendar.setTime(start);//将时间对象data设置为新的日历
		while(!calendar.getTime().after(end)){ //从当前时间到end之前的这一段时间
			//获取当前时间的key,并将key装到集合中去
			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中
	//使用bitmap

	public void recordDAU(int userId){
		//构建key
		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 RuntimeException("参数不能为空!");
		}
		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());
			}
		});
	}
}

Intercepeor

在请求中获取ip

@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.getRemoteUser();
		dataService.recordUV(ip);

		//统计dau
		User user = hostHolder.getUser();
		if(user!=null){
			int userId = user.getId();
			dataService.recordDAU(userId);
		}
		return true;
	}
}

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>

另:该功能只有管理员才能访问,因此需要在之前写过的springsecurity进行相关授权配置即可

猜你喜欢

转载自blog.csdn.net/zssxcj/article/details/131232629