附件商户,用户签到,uv统计功能(geo,bitmap,hyperloglog结构的使用)
目录
- 附近商户
- 一:Geo数据结构
- 二:附近商户搜索
- 用户签到
- 一:BitMap功能演示
- 二:实现签到功能
- 三:统计签到功能
- uv统计
- 一:hyperloglog的用法
- 二:测试百万数据的t'ji
- 二:测试百万数据的t'ji
附近商户
一:Geo数据结构
二:附近商户搜索
编写一个测试方法,将店铺的信息:经纬度传入geo中,且按照typeid分组查询,因为我们查询距离都是在一个模块中查看的,所以需要根据类型分组;member就是店铺的id;
@Test
void loadShopData() {
//获取店铺的所有信息
List<Shop> list = shopService.list();
//使用stream流对list进行分组,按照typeid
Map<Long, List<Shop>> collect = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
//遍历map,获取每个类型的id和每个类型的所有商家,每一次查询都是一个typeid
for (Map.Entry<Long, List<Shop>> longListEntry : collect.entrySet()) {
Long typeId = longListEntry.getKey();
String key =RedisConstants.SHOP_GEO_KEY + typeId;
List<Shop> value = longListEntry.getValue();
//创建一个GeoLocation的集合原来存储geo相关信息
List<RedisGeoCommands.GeoLocation<String>> locations=new ArrayList<>(value.size());
for (Shop shop : value) {
// stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY())
// ,shop.getId().toString());
//写入信息:x,y,member
locations.add( new RedisGeoCommands.GeoLocation<String>(shop.getId().toString(),
new Point(shop.getX(),shop.getY())));
}
//将每个类型的店铺geo信息批量插入redis
stringRedisTemplate.opsForGeo().add(key, locations);
}
然后就要将商品连带着距离返回给前端,并且返回的数据是以距离进行排序:
@Override
@Transactional
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
//判断有没有传入x,和y,没有就直接去数据库查询
if (x == null || y == null) {
Page<Shop> shopPage = query().eq("type_id", typeId).
page(new Page<Shop>(current, SystemConstants.DEFAULT_PAGE_SIZE));
return Result.ok(shopPage.getRecords());
}
String key = RedisConstants.SHOP_GEO_KEY + typeId;
//设置redis-geo中分页查询的参数
Integer from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;//开始的索引
Integer end = current * SystemConstants.DEFAULT_PAGE_SIZE;//结束的索引
//从redis中的geo中搜索附近的店铺id并且按照距离排序
GeoResults<RedisGeoCommands.GeoLocation<String>> search = stringRedisTemplate.opsForGeo().search(
key,
GeoReference.fromCoordinate(x, y),
new Distance(5000),
RedisGeoCommands.
GeoSearchCommandArgs.
newGeoSearchArgs().includeDistance()
.limit(end)
);
if (search == null) {
return Result.ok(Collections.emptyList());
}
//获取geo中的内容,每个内容都包含value的值,和距离
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = search.getContent();
List<Long> ids = new ArrayList<>(content.size());
Map<String, Distance> map = new HashMap<>(content.size());
//遍历每一条内容,并且将获取到的value(店铺id)和距离存储起来,店铺id用list存储,距离因为是和店铺在一起的,所以选择用map来存储,能够保留对应关系
//因为是分页查询,前面获取的是从0到end的所以内容,我们这里要从from开始而不是0开始,所以使用stream流的skip,直接跳过0-from,从from开始
content.stream().skip(from).forEach(geoResult -> {
//将店铺id存入集合,将距离存入map
String name = geoResult.getContent().getName();
ids.add(Long.valueOf(name));
map.put(name, geoResult.getDistance());
}
);
//判断集合是否为空,因为刚才我们从跳过了前面的从from开始,所以获取到的id集合可能为空,所以要判断一下,否则在sql的in中会报错
if (ids==null||ids.isEmpty()){
return Result.ok();
}
//一样的将集合转成字符串,用‘,’隔开
String join = StrUtil.join(",", ids);
//从数据库中查询id集合中的对应的店铺,并且顺序要和ids的顺序一致
List<Shop> shops = query().in("id", ids).
last("order by field(id," + join + ")").list();
//遍历每一个shop,为他们的distance属性赋值
for (Shop shop : shops) {
String id = shop.getId().toString();
Distance distance = map.get(id);
shop.setDistance(distance.getValue());
}
return Result.ok(shops);
}
用户签到
一:BitMap功能演示
用法:setbit:向指定位置存入一个0或1,getbit,从指定位置取出值0/1;bitcount,统计bitmap中值为1的个数;
bitops,查询数组中第一个是0或者1的位置;
二:实现签到功能
实现签到功能我们就使用bitmap,因为签到对我们来说就是签上和没签上,签上就是1,没签上就是0,我们可以以一个月为一个key,然后以bitmap来存储就行,在java中使用bitmap是和字符串一起的:
@Override
public Result sign() {
Long id = UserHolder.getUser().getId();
LocalDate now = LocalDate.now();
String format = now.format(DateTimeFormatter.ofPattern("yyyy:MM"));
String key=RedisConstants.USER_SIGN_KEY+id+format;
//true代表我们设置的值是1;
stringRedisTemplate.opsForValue().setBit(key,now.getDayOfMonth()-1,true);
return Result.ok();
}
三:统计签到功能
统计签到就是从今天开始向前,有多少个连续的1;
首先我们要得到到今天为止这个月所有签到的数据,可以使用bitfield获取
如果从后向前遍历每个bit位:可以先与1做与运算,然后再向右移一位;
@Override
public Result signCount() {
Long userId = UserHolder.getUser().getId();
LocalDate now = LocalDate.now();
String format = now.format(DateTimeFormatter.ofPattern("yyyy:MM"));
String key=RedisConstants.USER_SIGN_KEY+userId+format;
int dayOfMonth = now.getDayOfMonth();
//bitfield key get u14 0表示获取从0开始14个字节,放到这就是从0开始到当前日的所有签到数据
List<Long> longs = stringRedisTemplate.opsForValue().bitField(key,
BitFieldSubCommands.create().
get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
//健壮性判断
if (longs==null||longs.isEmpty()){
return Result.ok();
}
//我们虽然得到的是一个集合,但是我们的值只有一个直接获取就行
Long res = longs.get(0);
if (res==0||res==null){
return Result.ok();
}
//计数器
Integer count=0;
//循环统计
while (true){
//与1做与运算,得到当前最后一位是0还是1
long i = (res&1);
//是1就计数器加1
if (i==1){
count++;
}else {
//不是1直接结束循环
break;
}
//最后要将数字向右移一位,不然会死循环
res>>>=1;
}
return Result.ok(count);
}
uv统计
一:hyperloglog的用法
uv:用户量统计
pv:点击量统计
hhl在redis中如何使用:
pfadd:将要统计的值添加进去,比如用户id
pfcount:统计
pfmerge:合并统计量
二:测试百万数据的t’ji
存中…(img-80mHMYws-1730682836468)]
[外链图片转存中…(img-Jn0VjHSl-1730682836468)]
uv:用户量统计
pv:点击量统计
hhl在redis中如何使用:
pfadd:将要统计的值添加进去,比如用户id
pfcount:统计
pfmerge:合并统计量