Java基于Redis实现“附近的人”(含源码下载)
“附近的人”在社交类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实现“附近的人”(含源码下载)相关推荐
- java图书销售系统_基于springboot的小型图书销售系统 源码下载
源码介绍 本系统采用B/S架构,服务器用的是tomcat服务器,数据库使用mysql,数据库连接池使用的是阿里开源的druid连接池,实现了前后端分离,后端框架基于spring boot整合mybat ...
- Markdown编辑器:纯前端演示(可接入项目、含源码下载) - 总结篇
可接入项目,提供全部代码下载. 通过本地html静态文件,演示效果. Editor.md是一款开源的.可嵌入的 Markdown 在线编辑器(组件),基于 CodeMirror.jQuery 和 Ma ...
- 人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载)
人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载) 目录 人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载) 1. ...
- 基于JSP的煤炭销售系统,源码下载
大家好,我是全微毕设团队的创始人,本团队擅长JAVA(SSM,SSH,SPRINGBOOT).PYTHON.PHP.C#.安卓等多项技术. 今天将为大家分析一个煤炭销售系统(陕北具有丰富的煤炭资源.优 ...
- 文章抓取(含源码下载)
最近都左做一些资源采集的工作,比如采集新闻,flash,图片等,下面我们通过一个小例子,来详细的说明一下我采集资源的步骤,希望各位能提点建议,不胜感激. 下面就开始吧!我们这次要采集的是这个少儿英语动 ...
- Java基于Redis实现附近的人(内附源码)
前几天收到一个新的需求,需要实现类似"附近的人"的功能:根据自己当前的定位,获取距离范围内的所有任务地点.刚看到这个需求时有点懵逼,第一想到的就是要利用地球的半径公式去计算距离,也 ...
- java基于ssm开发的弹幕视频网站源码
简介 Java基于ssm的弹幕视频系统,用户注册后可以上传视频进行投稿,也可以浏览视频发送弹幕,在个人中心管理视频.管理弹幕.管理评论等.管理员可以管理视频弹幕评论,查看统计图. 演示视频: http ...
- java毕业设计 ssm公寓宿舍后勤管理系统(含源码+论文)
文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 系统操作流程 3.3 系统结构设计 4 项目获取 1 项目简 ...
- Java SSM毕设 公寓宿舍后勤管理系统(含源码+论文)
文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 系统操作流程 3.3 系统结构设计 4 项目获取 1 项目简 ...
最新文章
- Ubuntu 14.04上使用CMake编译MXNet源码操作步骤(C++)
- linux如何卸载挂载文件
- 在此处打开命令改为CMD或Powershell
- springcloud不使用数据库微服务启动异常解决
- 数据库事务的一致性和原子性浅析
- 【BIEE】14_开发流程介绍
- 20170824关于星光级和低照度你了解多少?
- Love Deterrence【MMD动作+镜头下载】
- Ubuntu配置固定IP
- 2022年京东年货节红包雨攻略,年货节红包最高领8888元红包
- 骚操作!快速创建JSON数据和解析JSON数据
- 简历模板...自行下载
- MySQL派生表联表查询记录
- 豆芽的生长过程观察日记
- Excel文件格式和扩展名不匹配
- 使用响应扩展的响应面(Rx)
- Docker存储卷简述和测试
- 6-4 输出月份英文名 (15分)
- python中seth是什么意思_python中的seth有什么用
- ECharts 折线图左右滑动
热门文章
- Coddington shape factor
- 曙光服务器如何重新设置u盘启动_在中科曙光I620-G20服务器上安装Windows 2008 R2 系统步骤...
- ToggleButton的学习与使用
- 比Python爬虫简单的爬虫方法1-后羿采集器
- GRUB4DOS中文自述文档;Grub4dos中文ReadMe
- 专访:台湾这家设计公司如何hold住甲方爸爸?
- 利用计算机的认识与感受制作海报,手绘pop海报在大学中的应用和现实意义
- python unicode error_python-ValueError:操作参数必须为str或unicode
- HC-05-USB蓝牙模块绑定唯一的蓝牙模块
- Centos7.5 -Vim编辑器和恢复ext4下误删除的文件-Xmanager工具