Day82(8)-F:\code\hm-dianping\hm-dianping
黑马点评
附近商铺
GEO数据结构
连接成功
Last login: Tue Jan 13 12:24:02 2026 from 192.168.100.1
[root@bogon ~]# redis-cli
127.0.0.1:6379> geoadd g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx
(error) ERR Protocol error: unauthenticated multibulk length
127.0.0.1:6379> auth 123321
Error: Server closed the connection
127.0.0.1:6379> auth 123321
OK
127.0.0.1:6379> geoadd g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx
(integer) 3
127.0.0.1:6379> geodist g1 bjn bjx
"5729.9533"
127.0.0.1:6379> geodist g1 bjn bjx km
"5.7300"
127.0.0.1:6379> geodist g1 bjx bjz km
"9.0916"
127.0.0.1:6379> geosearch g1 fromlonlat 116.397904 39.909005 byradius 10 km withdist
"bjz"
"2.6361"
"bjn"
"5.1452"
"bjx"
"6.6723"
127.0.0.1:6379> geopos g1 bjz
"116.42802804708480835"
"39.90373880538094653"
127.0.0.1:6379> geohash g1 bjz
"wx4g12k21s0"
附近商铺
技术栈
- 通过stream,将list中的元素按照元素的属性分类,返回一个map,map的key就是该属性的性质
- GeoLocation
的使用 - 分页查询,stream流后面记得加上.collect(Collectors.toList());但是这样可能由于一次性处理太多导致爆错,可以改成forEach;opsForGeo()查询语法
1.通过stream,将list中的元素按照元素的属性分类,返回一个map,map的key就是该属性的性质(7)
2.GeoLocation的使用(15-24),只操作一次数据库,现创建出一个空的List<RedisGeoCommands.GeoLocation>,再遍历将数据添加到这个对象(17-22),然后一次性操作redis(24)
public static class GeoLocation<T> {
private final T name;
private final Point point;
@Test
void loadShopData(){
//1.查询店铺信息
List<Shop> list = shopService.list();
//2.把店铺按照typeId分组,typeId一致的放到一个集合中
//Map<Long,List<Shop>> map = new HashMap<>();
Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
//3.分批完成写入redis
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
//3.1.获取类型id
Long typeId = entry.getKey();
String key = SHOP_GEO_KEY + typeId;
//3.2.获取同类型的店铺的集合
List<Shop> value = entry.getValue();
List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
//3.3.写入redis GEOADD key 经度 纬度 member
for (Shop shop : value) {
//stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY()),shop.getId().toString());
locations.add(new RedisGeoCommands.GeoLocation<>(
shop.getId().toString(),new Point(shop.getX(),shop.getY()))
);
}
//只需要操作一个redis
stringRedisTemplate.opsForGeo().add(key,locations);
}
}
插件maven-helper快速exclude依赖
3.分页查询,stream流后面记得加上.collect(Collectors.toList());(49-51)但是这样可能由于一次性处理太多导致爆错,可以改成forEach;opsForGeo()查询语法(18-24)
@Override
public Result queryShopByTypeId(Integer typeId, Integer current, Double x, Double y) {
//1.判断是否需要根据坐标查询
if (x == null||y == null){
//不需要坐标查询,按数据库查询
// 根据类型分页查询
Page<Shop> page = query()
.eq("type_id", typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
// 返回数据
return Result.ok(page.getRecords());
}
//2.计算分页参数
int from = (current - 1)*SystemConstants.DEFAULT_PAGE_SIZE;
int end = current*SystemConstants.DEFAULT_PAGE_SIZE;
//3.查询redis、按照距离排序、分页。结果:shopId、distance
String key = SHOP_GEO_KEY + typeId;
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()//GEOSEARCH KEY BYLONLAT X Y BYRADIUS 10 WITHDISTANCE
.search(
key,
GeoReference.fromCoordinate(x, y), //BYLONLAT X Y
new Distance(5000),//BYRADIUS 10
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)//WITHDISTANCE和分页
);
//4.解析出id
if (results == null){
return Result.ok(Collections.emptyList());
}
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
//4.1.截取
List<Long> ids = new ArrayList<>(list.size());
Map<String,Distance> distanceMap = new HashMap<>(list.size());
//防止跳过的部分比总数还多
if (list.size()<=from){
//没有下一页了,结束
return Result.ok(Collections.emptyList());
}
list.stream().skip(from).forEach(result ->{
//4.2.获取店铺id
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
//4.3.获取距离
Distance distance = result.getDistance();
distanceMap.put(shopIdStr,distance);
});
//5.根据id查询shop
String idStr = StrUtil.join(",", ids);
List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
shops.stream().map(shop ->
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue())//Distance类里面除了具体的value数字,还有metric单位
).collect(Collectors.toList());
//6.返回
return Result.ok(shops);
}
用户签到
BitMap
位图BitMap:把bite位与某种状态进行映射
布隆过滤器的底层也是基于BitMap
512=29,1M=220,1B=2^3, 512MB=2^32b
bitfield bm1 get u2 0一般用来查找,因为修改用bitset更方便
u表示:BITFIELD命令中,u代表Unsigned(无符号整数),就是不带正负号;2表示查两个;0表示从0号位开始查
签到功能
技术栈
- 将LocalDateTime.now()时间日期数据转换为自定义的格式,年月日
- 获取本月的第几天
1.将LocalDateTime.now()时间日期数据转换为自定义的格式,年月日(8)
2.获取本月的第几天(11)
@Override
public Result sign() {
//1.获取当前登录的用户
Long userId = UserHolder.getUser().getId();
//2.获取日期
LocalDateTime now = LocalDateTime.now();
//3.拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyy:MM"));
String key = USER_SIGN_KEY + userId + keySuffix;
//4.获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
//5.写入redis SETBIT key offset 1
stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
return Result.ok();
}
签到统计
知识点
-
|(按位或) 相当于“加法/合并”:只要有一个 1,结果就是 1。它会让数字变“大”。&(按位与) 相当于“过滤/提取”:必须两个都是 1,结果才是 1。它会让数字变“小”。
技术栈
- bitField的使用,有关BitFieldSubCommands的编辑
1.bitField的使用,有关BitFieldSubCommands的编辑(13-15)
@Override
public Result signCount() {
//1.获取当前登录的用户
Long userId = UserHolder.getUser().getId();
//2.获取日期
LocalDateTime now = LocalDateTime.now();
//3.拼接key
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyy:MM"));
String key = USER_SIGN_KEY + userId + keySuffix;
//4.获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth();
//5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字
List<Long> result = stringRedisTemplate.opsForValue().bitField(
key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
);
if (result == null||result.isEmpty()){
//没有任何签到结果
return Result.ok(0);
}
Long num = result.get(0);
if (num == null||num == 0){
return Result.ok(0);
}
//6.循环遍历
int count = 0;
while (true){
//6.1.让这个数字与1做&运算,得到数字的最后一个bit位
//6.2.判断这个bit位是否为0
if ((num & 1) == 0){
//6.3.如果为0,说明未签到,结束
break;
}else {
//6.4.如果不为0,说明已签到,计数器+1
count++;
}
//6.5.把数字右移一位,抛弃最后一位bit位
num>>>=1;
}
return Result.ok(count);
}
UV统计
附带滤重
知识点
- 查看linux中redis的内存
127.0.0.1:6379> info memory
Memory
used_memory:2120392
used_memory_human:2.02M
技术栈:
- 大量数据分批次插入
- opsForHyperLogLog方法的使用
1.大量数据分批次插入(5-12)
2.opsForHyperLogLog方法的使用
@Test
void testHyperLoglog(){
String[] values = new String[1000];
int j = 0;
for (int i = 0;i<1000000;i++){
j = i % 1000;
values[j] = "user_" + i;
if (j == 999){
//发送到redis
stringRedisTemplate.opsForHyperLogLog().add("hl2",values);
}
}
//统计数量
Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
System.out.println("count = " + count);
}

浙公网安备 33010602011771号