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>
相关推荐
用户6279947182627 分钟前
南大通用GBase 8c分布式版本gha_ctl 命令-HI参数详解
数据库
斯汤雷15 分钟前
Matlab绘图案例,设置图片大小,坐标轴比例为黄金比
数据库·人工智能·算法·matlab·信息可视化
SQLplusDB22 分钟前
Oracle 23ai Vector Search 系列之3 集成嵌入生成模型(Embedding Model)到数据库示例,以及常见错误
数据库·oracle·embedding
喝醉酒的小白43 分钟前
SQL Server 可用性组自动种子设定失败问题
数据库
chem41111 小时前
Conmon lisp Demo
服务器·数据库·lisp
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
m0_555762901 小时前
QT 动态布局实现(待完善)
服务器·数据库·qt
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
天上掉下来个程小白2 小时前
Redis-14.在Java中操作Redis-Spring Data Redis使用方式-操作列表类型的数据
java·redis·spring·springboot·苍穹外卖
孪生质数-2 小时前
SQL server 2022和SSMS的使用案例1
网络·数据库·后端·科技·架构