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>
相关推荐
香蕉大香蕉(06敬畏市场)10 分钟前
MySQL 单点详解(03)
java·数据库·mysql·面试·八股·大厂实习
赤生云电竞11 分钟前
云游戏边缘节点部署:机房选址与网络拓扑优化实践——接入延迟、VXLAN与GPU虚拟化选型
数据库·游戏
李纲明1 小时前
微信小程序真机预览白屏或 request 失败:先核 request 合法域名与 TLS,再查体验版与本地缓存
缓存·微信小程序·小程序·小程序开发·devdiy·真机预览·合法域名
Y001 小时前
PostgreSQL 权限管理踩坑记:从 42501 到默认权限和 RLS
数据库·postgresql
呆萌很2 小时前
达梦(DM8)数据库常用SQL
数据库·sql
跟太阳说早安2 小时前
极简数据库SQL
数据库·sql
IT规划师2 小时前
Redis 数据类型全解 - List:用对是队列用错是灾难(二)
redis·消息队列·.net
前端 贾公子2 小时前
第 5 章:Codex 从可运行程序到可交付工具
java·前端·数据库
杨云龙UP2 小时前
MySQL 主从复制管理:暂停、恢复、清理主从关系及命令执行位置
运维·数据库·mysql·主从配置·主从命令·reset replica·清除复制