附近商户模块
这里我们需要实现我的用户在点开我的商户的时候能够看到我距离商户的距离,并根据距离来排序
这里实现起来就是将数据库中的坐标导入到redis当中就行,根据这种思路我们要做的就是去查询商户,然后将商户的信息存到redis

这里先获取到所有商户,因为我的商户类型肯定是需要按照一样的在一块排序,所以我下面就使用stream流的下游收集器来进行收集,不懂的可以看看主包的stream流篇章,里面有详细解释,我拿到stream流后得到的是map,去遍历一下,将里面的·字段存到redis里面就行,这样就完成了关于商户数据的导入
java
package com.hmdp;
import com.hmdp.entity.Shop;
import com.hmdp.service.IShopService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCommand;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
import java.awt.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@SpringBootTest
@Slf4j
public class test3 {
@Resource
private IShopService shopService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
public void insertShopDistance(){
//1.获取所有店铺
List<Shop> shops = shopService.list();
//2.根据店铺的id进行分类
Map<Long, List<Shop>> collect = shops.stream().collect(Collectors.groupingBy(Shop::getTypeId));
for (Map.Entry<Long, List<Shop>> entry : collect.entrySet()) {
Long l = entry.getKey();
List<Shop> value = entry.getValue();
String key = "shop:geo:" + l;
List<RedisGeoCommands.GeoLocation<String>> geoLocationList = value.stream().map(shop -> {
return new RedisGeoCommands.GeoLocation<String>(shop.getId().toString(), new Point(shop.getX(), shop.getY()));
}).collect(Collectors.toList());
stringRedisTemplate.opsForGeo().add(key, geoLocationList);
}
}
}
计算商户到用户之间的距离
这里因为我的定位不一定打开,所以我在使用软件的时候,我不一定传递后端含有我的坐标,所以我这里还是需要先去判断是不是我传递给后端的数据里面是不是存在我的坐标,要是不存在的话,就直接按照默认去查询,要是存在的话,我就需要去redis当中去取数据,然后解析

首先需要先计算当前的页码,这样我能够知道我该返回给前端的数据从哪里开始,然后取redis当中取出数据,就是取搜索,这里需要传递的是自己的位置,搜索的距离,以及我限制返回的数量
,接下来我们取判断是不是我从redis取出来的是空集合,要是空集合的话,我直接返回给前端一个空集合就行,要是不是的话,这时候我就需要取解析里面的数据,然后封装给我的商户,当然,这里做出了多一步的判断,要是我的页码是不是大于结果集的长度,因为这时候要是大于的话,我得到的null,再去数据库中查找的话,是不是就会疯狂报错?所以这里先判断一下,然后我就需要取遍历,获取店铺的id,距离,最后取数据库中查找,封装给店铺
用户签到
java
/**
* 实现签到功能
* @return
*/
@Override
public Result sign() {
Long userId = UserHolder.getUser().getId();
String suffix = LocalDateTimeUtil.format(LocalDateTime.now(), ":yyyyMM");
String key = "sign:" + userId + suffix;
//获取今天是本月的第几天
int dayOfMonth = LocalDateTime.now().getDayOfMonth();
//直接存储进去
redisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
return Result.ok() ;
}
}
很简单,就是取使用bitmap就行
java
/**
* 统计签到功能
* @return
*/
@Override
public Result signCount() {
Long userId = UserHolder.getUser().getId();
String suffix = LocalDateTimeUtil.format(LocalDateTime.now(), ":yyyyMM");
String key = "sign:" + userId + suffix;
int dayOfMonth = LocalDateTime.now().getDayOfMonth();
List<Long> field = redisTemplate.opsForValue().bitField(key
, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
if (field == null || field.size() == 0) {
return Result.ok(0);
}
long num = field.get(0);
int count=0;
while(true){
if ((num &1) == 0){
break;
}
else {
count++;
}
num = num >> 1;
}
return Result.ok(count);
}
}
统计签到的功能需要注意的就是我这里使用了移位的方式,根据我的十进制数字在转换成二进制的时候是01存储,这样我就能看到二进制的最后一位是0还是1,要是0的话就说明用户没有签到要是1的话就说明签到了,我的count需要加1,然后取移位
使用UV来实现对于浏览量的计算
首先我们搞懂两个概念:
- UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
- PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。
通常来说UV会比PV大很多,所以衡量同一个网站的访问量,我们需要综合考虑很多因素,所以我们只是单纯的把这两个值作为一个参考值
UV统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是如果每个访问的用户都保存到Redis中,数据量会非常恐怖,那怎么处理呢?
Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。相关算法原理大家可以参考:https://juejin.cn/post/6844903785744056333#heading-0
Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb ,内存占用低 的令人发指!作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。
java
}
@Test
public void queryShopDistance(){
String[] strings = new String[1000];
int j=0;
for (int i = 0; i < 1000000; i++) {
j=i%1000;
strings[j] ="java"+i;
if (j==999){
stringRedisTemplate.opsForHyperLogLog().add("java",strings);
}
}
System.out.println(stringRedisTemplate.opsForHyperLogLog().size("java"));
}
这里就是需要记忆的就是api,其实现在存在ai,记忆也不需要了,知道这个东西能够实现就行

这里我的误差也就几千条对不对,但是占用的内存是很少的,这就符合我的生产需要,接下来主包要去学微服务了,等主包看完微服务再来看高级篇