欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis GEO實現(xiàn)搜索附近用戶的項目實踐

 更新時間:2024年05月20日 10:46:40   作者:Java雪荷  
RedisGEO主要用于存儲地理位置信息,并對存儲的信息進行操作,本文主要介紹了Redis GEO實現(xiàn)搜索附近用戶的項目實踐,具有一定的參考價值,感興趣的可以了解一下

前言

做完 homie 匹配系統(tǒng)后就在學習其他東西了,因此擱置了好久。最近呢因為不想學新技術了就打算利用 Redis GEO 實現(xiàn)搜索附近用戶的功能,算是一個拓展吧!整體的實現(xiàn)并不困難,學完 Redis 再看會更輕松(未學過也沒事)。話不多說直接開始擼代碼吧。

設計思路和流程

在 User(用戶)表中添加兩個字段 longitude(經度)和 dimension(維度),用以存儲用戶的經緯度坐標。因為Redis GEO 通過每個用戶的經緯度坐標計算用戶間的距離,同時其 Redis 數(shù)據類型為ZSET,ZSET 是一個有序的 List 類似 Java 的 SortedSet。在此場景 value 就是用戶id,score 是經緯度信息( ZSET 根據 score值升序排序)。

create table hjj.user
(
    username     varchar(256)                       null comment '用戶昵稱',
    id           bigint auto_increment comment 'id'
        primary key,
    userAccount  varchar(256)                       null comment '賬戶',
    avatarUrl    varchar(1024)                      null comment '用戶頭像',
    gender       tinyint                            null comment '用戶性別',
    profile      varchar(512)                       null comment '個人簡介',
    userPassword varchar(512)                       not null comment '用戶密碼',
    phone        varchar(128)                       null comment '電話',
    email        varchar(512)                       null comment '郵箱',
    userStatus   int      default 0                 not null comment '狀態(tài) 0 - 正常',
    createTime   datetime default CURRENT_TIMESTAMP null comment '創(chuàng)建時間',
    updateTime   datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新時間',
    isDelete     tinyint  default 0                 not null comment '是否刪除',
    userRole     int      default 0                 not null comment '用戶角色 0 - 普通用戶 1 - 管理員',
    planetCode   varchar(512)                       null comment '星球編號',
    tags         varchar(1024)                      null comment '標簽列表(json)',
    longitude    decimal(10, 6)                     null comment '經度',
    dimension    decimal(10, 6)                     null comment '緯度'
)
    comment '用戶';

2. 在 UserVO 類中添加distance字段,用以向前端返回每個用戶與自己之間的距離,類型為Double。

/**
 * 用戶信息封裝類
 */
@Data
public class UserVO {
    /**
     * id
     */
    private long id;

    /**
     * 用戶昵稱
     */
    private String username;

    /**
     * 賬戶
     */
    private String userAccount;

    /**
     * 用戶頭像
     */
    private String avatarUrl;

    /**
     * 用戶性別
     */
    private Integer gender;
    /**
     * 用戶簡介
     */
    private String profile;

    /**
     * 電話
     */
    private String phone;

    /**
     * 郵箱
     */
    private String email;

    /**
     * 狀態(tài) 0 - 正常
     */
    private Integer userStatus;

    /**
     * 創(chuàng)建時間
     */
    private Date createTime;

    /**
     * 更新時間
     */
    private Date updateTime;

    /**
     * 用戶角色 0 - 普通用戶 1 - 管理員
     */
    private Integer userRole;

    /**
     * 星球編號
     */
    private String planetCode;
    /**
     * 標簽列表 json
     */
    private String tags;

    /**
     * 用戶距離
     */
    private Double distance;

    private static final long serialVersionUID = 1L;
}

基本業(yè)務實現(xiàn)

導入各個用戶經緯度數(shù)據

編寫測試類導入各個用戶的經緯度信息并且寫入Redis中,Redis GEO會根據它計算出一個 score值。進行 Redis GEO 相關操作時可以使用 Spring Data Redis 提供現(xiàn)成的操作 Redis 的模板——StringRedisTemplate,注意其 Key/Value 都是String類型。

stringRedisTemplate.opsForGeo().add() 支持一次一次地傳入經緯度信息,可以通過List和Map集合類型傳入用戶經緯度信息,這里我們用List集合。第一個參數(shù)為Redis的key,這不用過多介紹。第二個參數(shù)為List類型,泛型為RedisGeoCommands.GeoLocation<String>,其參數(shù)為用戶id和Point(Point可以理解為是一個圓的一個點吧,經緯度就是x/y坐標)。

stringRedisTemplate.opsForGeo().add()傳入的參數(shù):

    @Test
    public void importUserGEOByRedis() {
        List<User> userList = userService.list(); // 查詢所有用戶
        String key = RedisConstant.USER_GEO_KEY; // Redis的key
        List<RedisGeoCommands.GeoLocation<String>> locationList = new ArrayList<>(userList.size()); // 初始化地址(經緯度)List
        for (User user : userList) {
            locationList.add(new RedisGeoCommands.GeoLocation<>(String.valueOf(user.getId()), new Point(user.getLongitude(),
                    user.getDimension()))); // 往locationList添加每個用戶的經緯度數(shù)據
        }
        stringRedisTemplate.opsForGeo().add(key, locationList); // 將每個用戶的經緯度信息寫入Redis中
    }

結果:

獲取用戶 id = 1 與其他用戶的距離

編寫一個測試類計算用戶 id = 1 與其他用戶之間的距離。利用stringRedisTemplate.opsForGeo().distance()方法,其主要參數(shù)為member1和member2,Metric是計算距離的單位類型。從名稱就可以知道m(xù)ember1和member2其實就是用戶1和用戶2的信息,因為我們在上面用 locationList.add() 添加用戶id和用戶的經度坐標,所以這兩個member就是用戶id咯。

?所以寫個循環(huán)就可以算出用戶 id = 1 與其他用戶的距離

    @Test
    public void getUserGeo() {
        String key = RedisConstant.USER_GEO_KEY;
        List<User> userList = userService.list();

        // 計算每個用戶與登錄用戶的距離
        for (User user : userList) {
            Distance distance = stringRedisTemplate.opsForGeo().distance(key,
                    "1", String.valueOf(user.getId()), RedisGeoCommands.DistanceUnit.KILOMETERS);
            System.out.println("User: " + user.getId() + ", Distance: " +
                    distance.getValue() + " " + distance.getUnit());
        }
    }

結果:

搜索附近用戶

利用現(xiàn)成的 stringRedisTemplate.opsForGeo().radius 方法,第一個參數(shù)依然是Redis的key,第二個參數(shù)是Circle,看代碼和名稱就知道其是一個圓(傳入Point即圓心和圓的半徑)。想象搜索附近的用戶就是搜索以你為圓心,半徑為搜索距離的圓內的用戶。理解這些代碼就能順理成章的擼出來了,是不是不算難。

    @Test
    public void searchUserByGeo() {
        User loginUser = userService.getById(1);
        Distance geoRadius = new Distance(1500, RedisGeoCommands.DistanceUnit.KILOMETERS);
        Circle circle  = new Circle(new Point(loginUser.getLongitude(), loginUser.getDimension()), geoRadius);
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs
                .newGeoRadiusArgs().includeCoordinates();
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().radius(RedisConstant.USER_GEO_KEY, circle, geoRadiusCommandArgs);
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : results) {
            if (!result.getContent().getName().equals("1")) {
                System.out.println(result.getContent().getName()); // 打印1500km內的用戶id
            }
        }
    }

注意:搜索附近的用戶會搜索到自己,所以可以加一個判斷以排除自己。

結果:

?應用至項目中

改寫用戶推薦接口

注意返回類型是UserVO不是User,因為我的前端展示了推薦用戶和自己之間的距離。

UserController.recommendUsers:

    @GetMapping("/recommend")
    public BaseResponse<List<UserVO>> recommendUsers(long pageSize, long pageNum, HttpServletRequest request){
        User loginUser = userService.getLoginUser(request);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.ne("id", loginUser.getId());
        IPage<User> page = new Page<>(pageNum, pageSize);
        IPage<User> userIPage = userService.page(page, queryWrapper);

        String redisUserGeoKey = RedisConstant.USER_GEO_KEY;
        // 將User轉換為UserVO
        List<UserVO> userVOList = userIPage.getRecords().stream()
                .map(user -> {
                    // 查詢距離
                    Distance distance = stringRedisTemplate.opsForGeo().distance(redisUserGeoKey,
                            String.valueOf(loginUser.getId()), String.valueOf(user.getId()),
                            RedisGeoCommands.DistanceUnit.KILOMETERS);
                    double value = distance.getValue();

                    // 創(chuàng)建UserVO對象并設置屬性
                    UserVO userVO = new UserVO();
                    // 這里可以用BeanUtils.copyProperties(),就沒必要重復set了
                    userVO.setId(user.getId());
                    userVO.setUsername(user.getUsername());
                    userVO.setUserAccount(user.getUserAccount());
                    userVO.setAvatarUrl(user.getAvatarUrl());
                    userVO.setGender(user.getGender());
                    userVO.setProfile(user.getProfile());
                    userVO.setPhone(user.getPhone());
                    userVO.setEmail(user.getEmail());
                    userVO.setUserStatus(user.getUserStatus());
                    userVO.setCreateTime(user.getCreateTime());
                    userVO.setUpdateTime(user.getUpdateTime());
                    userVO.setUserRole(user.getUserRole());
                    userVO.setPlanetCode(user.getPlanetCode());
                    userVO.setTags(user.getTags());
                    userVO.setDistance(value); // 設置距離值
                    return userVO;
                })
                .collect(Collectors.toList());
        System.out.println(userVOList);
        return ResultUtils.success(userVOList);
    }

改寫匹配用戶接口

UserController.matchUsers:

    /**
     * 推薦最匹配的用戶
     * @return
     */
    @GetMapping("/match")
    public BaseResponse<List<UserVO>> matchUsers(long num, HttpServletRequest request){
        if (num <=0 || num > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        return ResultUtils.success(userService.matchUsers(num ,loginUser));
    }

UserServiceImpl.matchUsers:

    @Override
    public List<UserVO> matchUsers(long num, User loginUser) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNotNull("tags");
        queryWrapper.ne("id", loginUser.getId());
        queryWrapper.select("id","tags");
        List<User> userList = this.list(queryWrapper);

        String tags = loginUser.getTags();
        Gson gson = new Gson();
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());
        // 用戶列表的下表 => 相似度'
        List<Pair<User,Long>> list = new ArrayList<>();
        // 依次計算當前用戶和所有用戶的相似度
        for (int i = 0; i <userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getTags();
            //無標簽的 或當前用戶為自己
            if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()){
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            //計算分數(shù)
            long distance = AlgorithmUtils.minDistance(tagList, userTagList);
            list.add(new Pair<>(user,distance));
        }
        //按編輯距離有小到大排序
        List<Pair<User, Long>> topUserPairList = list.stream()
                .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .limit(num)
                .collect(Collectors.toList());
        //有順序的userID列表
        List<Long> userListVo = topUserPairList.stream().map(pari -> pari.getKey().getId()).collect(Collectors.toList());

        //根據id查詢user完整信息
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.in("id",userListVo);
        Map<Long, List<User>> userIdUserListMap = this.list(userQueryWrapper).stream()
                .map(user -> getSafetyUser(user))
                .collect(Collectors.groupingBy(User::getId));

        List<User> finalUserList = new ArrayList<>();
        for (Long userId : userListVo){
            finalUserList.add(userIdUserListMap.get(userId).get(0));
        }

        String redisUserGeoKey = RedisConstant.USER_GEO_KEY;
        List<UserVO> finalUserVOList = finalUserList.stream().map(user -> {
            Distance distance = stringRedisTemplate.opsForGeo().distance(redisUserGeoKey, String.valueOf(loginUser.getId()),
                    String.valueOf(user.getId()), RedisGeoCommands.DistanceUnit.KILOMETERS);


            UserVO userVO = new UserVO();
            userVO.setId(user.getId());
            // 這里可以用BeanUtils.copyProperties(),就沒必要重復set了
            userVO.setUsername(user.getUsername());
            userVO.setUserAccount(user.getUserAccount());
            userVO.setAvatarUrl(user.getAvatarUrl());
            userVO.setGender(user.getGender());
            userVO.setProfile(user.getProfile());
            userVO.setPhone(user.getPhone());
            userVO.setEmail(user.getEmail());
            userVO.setUserStatus(user.getUserStatus());
            userVO.setCreateTime(user.getCreateTime());
            userVO.setUpdateTime(user.getUpdateTime());
            userVO.setUserRole(user.getUserRole());
            userVO.setPlanetCode(user.getPlanetCode());
            userVO.setTags(user.getTags());
            userVO.setDistance(distance.getValue());
            return userVO;

        }).collect(Collectors.toList());
        return finalUserVOList;
    }

添加搜索附近用戶接口

UserController.searchNearby:

    /**
     * 搜索附近用戶
     */
    @GetMapping("/searchNearby")
    public BaseResponse<List<UserVO>> searchNearby(int radius, HttpServletRequest request) {
        if (radius <= 0 || radius > 10000) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser(request);
        User loginUser = userService.getById(user.getId());
        List<UserVO> userVOList = userService.searchNearby(radius, loginUser);
        return ResultUtils.success(userVOList);
    }

UserServiceImpl.searchNearby:

@Override
    public List<UserVO> searchNearby(int radius, User loginUser) {
        String geoKey = RedisConstant.USER_GEO_KEY;
        String userId = String.valueOf(loginUser.getId());
        Double longitude = loginUser.getLongitude();
        Double dimension = loginUser.getDimension();
        if (longitude == null || dimension == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "登錄用戶經緯度參數(shù)為空");
        }
        Distance geoRadius = new Distance(radius, RedisGeoCommands.DistanceUnit.KILOMETERS);
        Circle circle = new Circle(new Point(longitude, dimension), geoRadius);
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
                .radius(geoKey, circle);
        List<Long> userIdList = new ArrayList<>();
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : results) {
            String id = result.getContent().getName();
            if (!userId.equals(id)) {
                userIdList.add(Long.parseLong(id));
            }
        }
        List<UserVO> userVOList = userIdList.stream().map(
                id -> {
                    UserVO userVO = new UserVO();
                    User user = this.getById(id);
                    BeanUtils.copyProperties(user, userVO);
                    Distance distance = stringRedisTemplate.opsForGeo().distance(geoKey, userId, String.valueOf(id),
                            RedisGeoCommands.DistanceUnit.KILOMETERS);
                    userVO.setDistance(distance.getValue());
                    return userVO;
                }
        ).collect(Collectors.toList());
        return userVOList;
    }

到此這篇關于Redis GEO實現(xiàn)搜索附近用戶的項目實踐的文章就介紹到這了,更多相關Redis GEO內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家! 

相關文章

  • 詳解Redis如何優(yōu)雅地實現(xiàn)接口防刷

    詳解Redis如何優(yōu)雅地實現(xiàn)接口防刷

    這篇文章主要為大家詳細介紹了Redis優(yōu)雅地實現(xiàn)接口防刷的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-03-03
  • 多維度深入分析Redis的5種基本數(shù)據結構

    多維度深入分析Redis的5種基本數(shù)據結構

    此篇文章主要對Redis的5種基本數(shù)據類型,即字符串(String)、列表(List)、散列(Hash)、集合(Set)、有序集合(Sorted?Set),從使用場景和底層結構出發(fā),進行多維度深入分析。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11
  • Redis報錯:Could not create server TCP listening socket 127.0.0.1:6379: bind:解決方法

    Redis報錯:Could not create server TCP 

    這篇文章主要介紹了Redis報錯:Could not create server TCP listening socket 127.0.0.1:6379: bind:解決方法,是安裝與啟動Redis過程中比較常見的問題,需要的朋友可以參考下
    2023-06-06
  • redis實現(xiàn)刪除list中特定索引的值

    redis實現(xiàn)刪除list中特定索引的值

    這篇文章主要介紹了redis實現(xiàn)刪除list中特定索引的值,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Redis內存回收策略

    Redis內存回收策略

    Redis也會因為內存不足而產生錯誤?,?也可能因為回收過久而導致系統(tǒng)長期的停頓,因此掌握執(zhí)行回收策略十分有必要,具有一定的參考價值,感興趣的可以了解一下
    2021-11-11
  • 詳解Redis中的雙鏈表結構

    詳解Redis中的雙鏈表結構

    這篇文章主要介紹了Redis中的雙鏈表結構,包括listNode結構的API,需要的朋友可以參考下
    2015-08-08
  • Redis批量刪除KEY的方法

    Redis批量刪除KEY的方法

    這篇文章主要介紹了Redis批量刪除KEY的方法,本文借助了Linux xargs命令實現(xiàn),需要的朋友可以參考下
    2014-11-11
  • 使用Redis實現(xiàn)點贊取消點贊的詳細代碼

    使用Redis實現(xiàn)點贊取消點贊的詳細代碼

    這篇文章主要介紹了Redis實現(xiàn)點贊取消點贊的詳細代碼,通過查詢某實體(帖子、評論等)點贊數(shù)量,需要用到事務相關知識,結合示例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-03-03
  • Redis Sorted Set類型使用及應用場景

    Redis Sorted Set類型使用及應用場景

    Sorted Set是Redis常用的一種是數(shù)據類型,本文主要介紹了Redis Sorted Set類型使用及應用場景,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-06-06
  • Redis分布式鎖詳細介紹

    Redis分布式鎖詳細介紹

    大家好,本篇文章主要講的是Redis分布式鎖詳細介紹,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12

最新評論