Redis高级数据类型-HyperLogLog&Bitmap以及使用两种数据类型完成网站数据统计

网站数据统计

定义相关的Redis Key

java 复制代码
    /**
     * 单日UV
     */
    public static String getUVKey(String date) {
        return PREFIX_UV+SPLIT+date;
    }

    /**
     * 记录区间UV
     * @param startData 开始日期
     * @param endDate 结束日期
     * @return
     */
    public static String getUVkey(String startData,String endDate){
        return PREFIX_UV+SPLIT+startData+SPLIT+endDate;
    }

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

    /**
     * 区间活跃用户
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return
     */
    public static String getDAUKey(String startDate,String endDate){
        return PREFIX_DAU+SPLIT+startDate+SPLIT+endDate;
    }

定义DataService

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Service
public class DataService {
    @Autowired
    private RedisTemplate redisTemplate;
    //定义日期格式
    private SimpleDateFormat df=new SimpleDateFormat("yyyyMMdd");
    //将指定的IP计入UV
    public void recordUV(String ip){
        //获取redisKey
        String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
        //存入ip
        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运算
         * 使用redis底层的链接调用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,redisKey.getBytes(),keyList.toArray(new byte[0][0]));
                return connection.bitCount(redisKey.getBytes());
            }
        });

    }
}

定义拦截器

java 复制代码
@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;
    }
}

定义Controller

java 复制代码
@Controller
public class DataController {
    @Autowired
    private DataService dataService;

    /**
     * 统计页面的函数
     * @return
     */
    @RequestMapping(path = "/data",method = {RequestMethod.GET,RequestMethod.POST})
    public String getDataPage(){
        return "/site/admin/data";
    }

    /**
     * 统计网站UV
     * @param start
     * @param end
     * @param model
     * @return
     */
    @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);
        //将处理结果发给/data进行另一半的处理
        return "forward:/data";
//        return "/site/admin/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);
        //将处理结果发给/data进行另一半的处理
        return "forward:/data";
//        return "/site/admin/data";
    }
}

添加权限

java 复制代码
.antMatchers(
                        "/discuss/delete",
                        "/data/**"
                )

页面示例

javascript 复制代码
<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>
相关推荐
醉颜凉1 小时前
Redis网络层解析:高性能服务器架构设计与实现
redis·epoll·网络架构·i/o多路复用·事件驱动
醉颜凉1 小时前
Redis 与 Pika 对比:大容量冷热数据落盘方案与混合存储架构
redis·pika·混合存储·存储架构·冷热数据分离
SkyWalking中文站1 小时前
BanyanDB 0.11.0:新特性与升级指南
数据库
₍˄·͈༝·͈˄*₎◞ ̑̑码3 小时前
MyBatis操作数据库
数据库·mybatis
刃神太酷啦5 小时前
Redis 进阶核心:持久化 (RDB/AOF)、事务与主从复制全解析----《Hello Redis!》(5)
linux·c语言·数据库·c++·redis·缓存·bootstrap
三8445 小时前
SSRF 打内网 Redis:gopher 原理与实操
redis·web安全·bootstrap·ssrf
丁丁点灯o5 小时前
Oracle中使用外键的场景及不适用外键的情况
数据库·oracle
天天进步20155 小时前
Pixelle-Video 源码解析 #18:声音克隆功能:参考音频如何影响解说效果?
数据库·音视频
youm20035 小时前
认识Redis
redis·笔记·学习
不懂的浪漫6 小时前
ToDesk 连接 Linux 后分辨率过低的解决方法
linux·运维·数据库