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

2017-09-13 20:48:00来源:CSDN作者:qq_19260029人点击

分享

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

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

控制器(Controller)类

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

业务类

@Servicepublic 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";    @Autowired    private RedisDao redisDao;    // 线程池    @Autowired    private 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;        // “附近的人”缓存集合key        private String cacheKey;        // 获取缓存集合最大排序下标        private Integer endIndex;        public InsertCache(NearbyPO paramObj, String cacheKey, Integer endIndex) {            this.paramObj = paramObj;            this.cacheKey = cacheKey;            this.endIndex = endIndex;        }        @Override        public 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实现类

@Repositorypublic class RedisDaoImpl implements RedisDao {    @Autowired    protected RedisTemplate<String, String> redisTemplate;    @Override    public String getOneStringByKey(String key) {        return redisTemplate.opsForValue().get(key);    }    @Override    public void setOneStringByKey(String key, String value, int timeoutSeconds) {        redisTemplate.opsForValue().set(key, value, timeoutSeconds, TimeUnit.SECONDS);    }    @Override    public Set<String> getSetByKeyAndScore(String key, int beginScore, int endScore) {        redisTemplate.opsForZSet().removeRangeByScore(key, 1, beginScore - 1);        return redisTemplate.opsForZSet().rangeByScore(key, beginScore, endScore);    }    @Override    public 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 {    //用户id    private 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

把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号用户定位信息追加至“深圳市-南山区”附近的人集合,并保存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缓存信息
5

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

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台