“附近的人”在社交类APP已成为标配的功能,Low一点的实现方式可以把坐标存至关系型数据库,通过计算的坐标点距离实现,这种计算可行但计算速度远不及内存操作级别的NoSql数据库。

基于Redis数据库实现附近的人信息缓存,服务由Spring-boot框架搭建。

控制器(Controller)类

@RestController
public class Controller {@Autowiredprivate NearbyBiz nearbyBiz;@RequestMappingpublic String helloWord() {return "HelloWord";}// 附近的人@RequestMapping(value = "nearby")public Result<List<NearbyBO>> nearby(@Valid NearbyPO paramObj) {return nearbyBiz.nearby(paramObj);}
}

业务类

@Service
public class NearbyBiz {/** 2017-09-01 毫秒值/1000 (秒) **/private static final int BASE_SORT_NUM = 1504195200;/** 最大距离 **/private static final int MAX_DISTANCE = 3000;/** 8小时(秒) **/private static final int EIGHT_HOUR_SECOND = 60 * 60 * 8;/** 附近的人缓存key值,p1-城市编号,p2-地区编号 **/private static final String NEARBY_CACHE_KEY = "nearby_%s_%s";/** 附近的人用户缓存key值,p1-城市编号,p2-地区编号,p3-用户id **/private static final String NEARBY_USER_CACHE_KEY = "nearby_user_%s_%s_%s";@Autowiredprivate RedisDao redisDao;// 线程池@Autowiredprivate ThreadPoolTaskExecutor threadPoolTaskExecutor;// 附近的人public Result<List<NearbyBO>> nearby(NearbyPO paramObj) {int nowSortNum = (int) (new Date().getTime() / 1000);// 此处仅为了减低排序的序号( 获取缓存集合最大排序下标)int endIndex = nowSortNum - BASE_SORT_NUM;// 缓存key值String cacheKey = String.format(NEARBY_CACHE_KEY, paramObj.getCityCode(), paramObj.getAdCode());// 取同一城市地区&&八小时区间范围数据(八小时之前缓存数据会删除)Set<String> redisNearby = redisDao.getSetByKeyAndScore(cacheKey, endIndex - EIGHT_HOUR_SECOND, endIndex);// 开启新线程写入数据(让主线程“专心”处理主业务)threadPoolTaskExecutor.execute(new InsertCache(paramObj, cacheKey, endIndex));if (redisNearby.size() == 0)return new Result<List<NearbyBO>>(false, "附近查无用户", null);List<NearbyBO> result = new ArrayList<NearbyBO>(redisNearby.size());boolean oneself = true;for (String item : redisNearby) {NearbyPO cacheNearby = JSONObject.parseObject(item, NearbyPO.class);// 缓存里可能有用户自己if (cacheNearby.getId().intValue() == paramObj.getId())continue;double distance = countDistance(paramObj.getLongitude(), paramObj.getLatitude(), cacheNearby.getLongitude(),cacheNearby.getLatitude());// 大于限定距离if (distance > MAX_DISTANCE)continue;result.add(new NearbyBO(cacheNearby.getId(), cacheNearby.getName(), distance));oneself = false;}if (oneself)return new Result<List<NearbyBO>>(false, "附近查无用户", null);return new Result<List<NearbyBO>>(true, "获取成功", result);}// 把用户定位信息写入缓存private class InsertCache implements Runnable {// 用户提交的最新坐标信息private NearbyPO paramObj;// “附近的人”缓存集合keyprivate String cacheKey;// 获取缓存集合最大排序下标private Integer endIndex;public InsertCache(NearbyPO paramObj, String cacheKey, Integer endIndex) {this.paramObj = paramObj;this.cacheKey = cacheKey;this.endIndex = endIndex;}@Overridepublic void run() {String userCacheKey = String.format(NEARBY_USER_CACHE_KEY, paramObj.getCityCode(), paramObj.getAdCode(),paramObj.getId());String cacheNewData = JSONObject.toJSONString(paramObj);String cacheUserPosition = redisDao.getOneStringByKey(userCacheKey);// 确保用户坐标信息缓存清除慢于“附近的人”坐标信息redisDao.setOneStringByKey(userCacheKey, cacheNewData, EIGHT_HOUR_SECOND + 60);// 保存用户坐标信息至“附近的人”缓存集合redisDao.addOneStringToZSet(cacheKey, cacheNewData, cacheUserPosition, endIndex);}}/*** 计算两经纬度点之间的距离(单位:米)* * @param longitude1*            坐标1经度* @param latitude1*            坐标1纬度* @param longitude2*            坐标2经度* @param latitude2*            坐标1纬度* @return*/private static double countDistance(double longitude1, double latitude1, double longitude2, double latitude2) {double radLat1 = Math.toRadians(latitude1);double radLat2 = Math.toRadians(latitude2);double a = radLat1 - radLat2;double b = Math.toRadians(longitude1) - Math.toRadians(longitude2);double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));s = s * 6378137.0;s = Math.round(s * 10000) / 10000;return s;}
}

Redis接口类

public interface RedisDao {/*** * 根据key值获取String* * @param key* @return*/public String getOneStringByKey(String key);/*** * 缓存一个String* * @param key* @param value* @param timeoutSeconds*/public void setOneStringByKey(String key, String value, int timeoutSeconds);/*** 在获取元素下标区间之外的元素会被删除* * @param key* @param beginScore*            获取元素的排序开始下标* @param endScore*            获取元素的排序结束下标* @return 指定排序下标范围内的元素*/public Set<String> getSetByKeyAndScore(String key, int beginScore, int endScore);/*** * @param key* @param newVal*            新值* @param oldVal*            旧值(非空则删除元素)* @param score*            排序(使用时间基准值来判断是否删除元素)*/public void addOneStringToZSet(String key, String newVal, String oldVal, double score);}

Redis实现类

@Repository
public class RedisDaoImpl implements RedisDao {@Autowiredprotected RedisTemplate<String, String> redisTemplate;@Overridepublic String getOneStringByKey(String key) {return redisTemplate.opsForValue().get(key);}@Overridepublic void setOneStringByKey(String key, String value, int timeoutSeconds) {redisTemplate.opsForValue().set(key, value, timeoutSeconds, TimeUnit.SECONDS);}@Overridepublic Set<String> getSetByKeyAndScore(String key, int beginScore, int endScore) {redisTemplate.opsForZSet().removeRangeByScore(key, 1, beginScore - 1);return redisTemplate.opsForZSet().rangeByScore(key, beginScore, endScore);}@Overridepublic void addOneStringToZSet(String key, String newVal, String oldVal, double score) {if (oldVal != null)redisTemplate.opsForZSet().remove(key, oldVal);redisTemplate.opsForZSet().add(key, newVal, score);}
}

入参类(省略get,set方法)

public class NearbyPO {@NotNull(message = "id值不能为空")private Integer id;@NotBlank(message = "名称不能为空")private String name;@NotNull(message = "城市编码不能为空")private Integer cityCode;@NotNull(message = "地区编码不能为空")private Integer adCode;@NotNull(message = "经度不能为空")private Double longitude;@NotNull(message = "纬度不能为空")private Double latitude;
}

出参类(省略get,set方法)

public class NearbyBO {//用户idprivate Integer id;//用户名称private String name;//距离private Double distance;
}

出参统一封装类(省略get,set方法)

public class Result<T> {private boolean success = true;private String msg = "";private T data = null;public Result() {super();}public Result(boolean success) {super();this.success = success;}public Result(boolean success, T data) {super();this.success = success;this.data = data;}public Result(boolean success, String msg, T data) {super();this.success = success;this.msg = msg;this.data = data;}
}

参考数据

深圳市cityCode:440300
深圳市-福田区adCode:440304
深圳市-南山区adCode:440305

1号用户在深圳南山区定位

http://localhost:8080/nearby?id=1&name=1号用户&cityCode=440300&adCode=440305&longitude=113.9572334290&latitude=22.5829485425

Redis缓存

把1号用户定位信息缓存至“深圳市-南山区”附近的人集合(nearby_440300_440305【固定前缀+城市编号+区编号】),并保存用户当前的定位信息(TTL为8小时+60秒,有效保存用户最新定位信息的同时设置了过期时间,为缓存数据库的过期数据提供支持)

请求结果

{"success":false,"msg":"附近查无用户","data":null}

当前深圳市南山区只有1号用户使用附近的人,所以查无用户

2号用户在深圳南山区定位

http://localhost:8080/nearby?id=2&name=2号用户&cityCode=440300&adCode=440305&longitude=113.9582334290&latitude=22.5829485425

Redis缓存

把2号用户定位信息追加至“深圳市-南山区”附近的人集合,并保存2号用户当前的定位信息

请求结果

{"success":true,"msg":"获取成功","data":[{"id":1,"name":"1号用户","distance":102.0}]}

匹配到1号用户,距离为102米

1号用户在深圳福田区定位

http://localhost:8080/nearby?id=1&name=1号用户&cityCode=440300&adCode=440304&longitude=114.0180015564&latitude=22.5471230766

Redis缓存

把1号用户定位信息缓存至“深圳市-福田区”附近的人集合,并保存1号用户在福田区的定位信息;不影响1号用户在南山区附近的人缓存信息

请求结果

{"success":false,"msg":"附近查无用户","data":null}

福田区当前只有1号用户定位,所以查无附近的人

1号用户在深圳南山区再次定位请求

http://localhost:8080/nearby?id=1&name=1号用户&cityCode=440300&adCode=440305&longitude=113.9572334290&latitude=22.5829485425

Redis缓存

1号用户在南山区重新定位,刷新定位信息(nearby_user_440300_440305_1【固定前缀+城市编号+区编号+用户id】)

请求结果

{"success":true,"msg":"获取成功","data":[{"id":2,"name":"2号用户","distance":102.0}]}}

2号用户附近的人定位信息并没过期(缓存8小时),附近的人匹配到2号用户

深圳南山区“附近的人”集合Redis缓存信息

项目源码下载(3分)http://download.csdn.net/download/qq_19260029/9976148

更多文章:
Spring boot基于Redis缓存商城分类,商品信息
Eclipse新建Spring-boot项目,打包部署并输出HelloWord

Java基于Redis实现“附近的人”(含源码下载)相关推荐

  1. java图书销售系统_基于springboot的小型图书销售系统 源码下载

    源码介绍 本系统采用B/S架构,服务器用的是tomcat服务器,数据库使用mysql,数据库连接池使用的是阿里开源的druid连接池,实现了前后端分离,后端框架基于spring boot整合mybat ...

  2. Markdown编辑器:纯前端演示(可接入项目、含源码下载) - 总结篇

    可接入项目,提供全部代码下载. 通过本地html静态文件,演示效果. Editor.md是一款开源的.可嵌入的 Markdown 在线编辑器(组件),基于 CodeMirror.jQuery 和 Ma ...

  3. 人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载)

    人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载) 目录 人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载) 1. ...

  4. 基于JSP的煤炭销售系统,源码下载

    大家好,我是全微毕设团队的创始人,本团队擅长JAVA(SSM,SSH,SPRINGBOOT).PYTHON.PHP.C#.安卓等多项技术. 今天将为大家分析一个煤炭销售系统(陕北具有丰富的煤炭资源.优 ...

  5. 文章抓取(含源码下载)

    最近都左做一些资源采集的工作,比如采集新闻,flash,图片等,下面我们通过一个小例子,来详细的说明一下我采集资源的步骤,希望各位能提点建议,不胜感激. 下面就开始吧!我们这次要采集的是这个少儿英语动 ...

  6. Java基于Redis实现附近的人(内附源码)

    前几天收到一个新的需求,需要实现类似"附近的人"的功能:根据自己当前的定位,获取距离范围内的所有任务地点.刚看到这个需求时有点懵逼,第一想到的就是要利用地球的半径公式去计算距离,也 ...

  7. java基于ssm开发的弹幕视频网站源码

    简介 Java基于ssm的弹幕视频系统,用户注册后可以上传视频进行投稿,也可以浏览视频发送弹幕,在个人中心管理视频.管理弹幕.管理评论等.管理员可以管理视频弹幕评论,查看统计图. 演示视频: http ...

  8. java毕业设计 ssm公寓宿舍后勤管理系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 系统操作流程 3.3 系统结构设计 4 项目获取 1 项目简 ...

  9. Java SSM毕设 公寓宿舍后勤管理系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 系统操作流程 3.3 系统结构设计 4 项目获取 1 项目简 ...

最新文章

  1. Ubuntu 14.04上使用CMake编译MXNet源码操作步骤(C++)
  2. linux如何卸载挂载文件
  3. 在此处打开命令改为CMD或Powershell
  4. springcloud不使用数据库微服务启动异常解决
  5. 数据库事务的一致性和原子性浅析
  6. 【BIEE】14_开发流程介绍
  7. 20170824关于星光级和低照度你了解多少?
  8. Love Deterrence【MMD动作+镜头下载】
  9. Ubuntu配置固定IP
  10. 2022年京东年货节红包雨攻略,年货节红包最高领8888元红包
  11. 骚操作!快速创建JSON数据和解析JSON数据
  12. 简历模板...自行下载
  13. MySQL派生表联表查询记录
  14. 豆芽的生长过程观察日记
  15. Excel文件格式和扩展名不匹配
  16. 使用响应扩展的响应面(Rx)
  17. Docker存储卷简述和测试
  18. 6-4 输出月份英文名 (15分)
  19. python中seth是什么意思_python中的seth有什么用
  20. ECharts 折线图左右滑动

热门文章

  1. Coddington shape factor
  2. 曙光服务器如何重新设置u盘启动_在中科曙光I620-G20服务器上安装Windows 2008 R2 系统步骤...
  3. ToggleButton的学习与使用
  4. 比Python爬虫简单的爬虫方法1-后羿采集器
  5. GRUB4DOS中文自述文档;Grub4dos中文ReadMe
  6. 专访:台湾这家设计公司如何hold住甲方爸爸?
  7. 利用计算机的认识与感受制作海报,手绘pop海报在大学中的应用和现实意义
  8. python unicode error_python-ValueError:操作参数必须为str或unicode
  9. HC-05-USB蓝牙模块绑定唯一的蓝牙模块
  10. Centos7.5 -Vim编辑器和恢复ext4下误删除的文件-Xmanager工具