亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言

做完 homie 匹配系統(tǒng)后就在學(xué)習(xí)其他東西了,因此擱置了好久。最近呢因?yàn)椴幌雽W(xué)新技術(shù)了就打算利用 Redis GEO 實(shí)現(xiàn)搜索附近用戶的功能,算是一個(gè)拓展吧!整體的實(shí)現(xiàn)并不困難,學(xué)完 Redis 再看會(huì)更輕松(未學(xué)過(guò)也沒(méi)事)。話不多說(shuō)直接開(kāi)始擼代碼吧。

設(shè)計(jì)思路和流程

在 User(用戶)表中添加兩個(gè)字段 longitude(經(jīng)度)和 dimension(維度),用以存儲(chǔ)用戶的經(jīng)緯度坐標(biāo)。因?yàn)镽edis GEO 通過(guò)每個(gè)用戶的經(jīng)緯度坐標(biāo)計(jì)算用戶間的距離,同時(shí)其 Redis 數(shù)據(jù)類型為ZSET,ZSET 是一個(gè)有序的 List 類似 Java 的 SortedSet。在此場(chǎng)景 value 就是用戶id,score 是經(jīng)緯度信息( ZSET 根據(jù) 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 '個(gè)人簡(jiǎn)介',
    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)建時(shí)間',
    updateTime   datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新時(shí)間',
    isDelete     tinyint  default 0                 not null comment '是否刪除',
    userRole     int      default 0                 not null comment '用戶角色 0 - 普通用戶 1 - 管理員',
    planetCode   varchar(512)                       null comment '星球編號(hào)',
    tags         varchar(1024)                      null comment '標(biāo)簽列表(json)',
    longitude    decimal(10, 6)                     null comment '經(jīng)度',
    dimension    decimal(10, 6)                     null comment '緯度'
)
    comment '用戶';

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

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

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

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

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

    /**
     * 用戶性別
     */
    private Integer gender;
    /**
     * 用戶簡(jiǎn)介
     */
    private String profile;

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

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

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

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

    /**
     * 更新時(shí)間
     */
    private Date updateTime;

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

    /**
     * 星球編號(hào)
     */
    private String planetCode;
    /**
     * 標(biāo)簽列表 json
     */
    private String tags;

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

    private static final long serialVersionUID = 1L;
}

基本業(yè)務(wù)實(shí)現(xiàn)

導(dǎo)入各個(gè)用戶經(jīng)緯度數(shù)據(jù)

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

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

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()); // 初始化地址(經(jīng)緯度)List
        for (User user : userList) {
            locationList.add(new RedisGeoCommands.GeoLocation<>(String.valueOf(user.getId()), new Point(user.getLongitude(),
                    user.getDimension()))); // 往locationList添加每個(gè)用戶的經(jīng)緯度數(shù)據(jù)
        }
        stringRedisTemplate.opsForGeo().add(key, locationList); // 將每個(gè)用戶的經(jīng)緯度信息寫(xiě)入Redis中
    }

結(jié)果:

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

編寫(xiě)一個(gè)測(cè)試類計(jì)算用戶 id = 1 與其他用戶之間的距離。利用stringRedisTemplate.opsForGeo().distance()方法,其主要參數(shù)為member1和member2,Metric是計(jì)算距離的單位類型。從名稱就可以知道m(xù)ember1和member2其實(shí)就是用戶1和用戶2的信息,因?yàn)槲覀冊(cè)谏厦嬗?locationList.add() 添加用戶id和用戶的經(jīng)度坐標(biāo),所以這兩個(gè)member就是用戶id咯。

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

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

        // 計(jì)算每個(gè)用戶與登錄用戶的距離
        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());
        }
    }

結(jié)果:

搜索附近用戶

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

    @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內(nèi)的用戶id
            }
        }
    }

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

結(jié)果:

?應(yīng)用至項(xiàng)目中

改寫(xiě)用戶推薦接口

注意返回類型是UserVO不是User,因?yàn)槲业那岸苏故玖送扑]用戶和自己之間的距離。

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轉(zhuǎn)換為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對(duì)象并設(shè)置屬性
                    UserVO userVO = new UserVO();
                    // 這里可以用BeanUtils.copyProperties(),就沒(méi)必要重復(fù)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); // 設(shè)置距離值
                    return userVO;
                })
                .collect(Collectors.toList());
        System.out.println(userVOList);
        return ResultUtils.success(userVOList);
    }

改寫(xiě)匹配用戶接口

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<>();
        // 依次計(jì)算當(dāng)前用戶和所有用戶的相似度
        for (int i = 0; i <userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getTags();
            //無(wú)標(biāo)簽的 或當(dāng)前用戶為自己
            if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()){
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            //計(jì)算分?jǐn)?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());

        //根據(jù)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(),就沒(méi)必要重復(fù)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, "登錄用戶經(jīng)緯度參數(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;
    }

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

相關(guān)文章

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

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

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

    多維度深入分析Redis的5種基本數(shù)據(jù)結(jié)構(gòu)

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

    Redis報(bào)錯(cuò):Could not create server TCP 

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

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

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

    Redis內(nèi)存回收策略

    Redis也會(huì)因?yàn)閮?nèi)存不足而產(chǎn)生錯(cuò)誤?,?也可能因?yàn)榛厥者^(guò)久而導(dǎo)致系統(tǒng)長(zhǎng)期的停頓,因此掌握?qǐng)?zhí)行回收策略十分有必要,具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-11-11
  • 詳解Redis中的雙鏈表結(jié)構(gòu)

    詳解Redis中的雙鏈表結(jié)構(gòu)

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

    Redis批量刪除KEY的方法

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

    使用Redis實(shí)現(xiàn)點(diǎn)贊取消點(diǎn)贊的詳細(xì)代碼

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

    Redis Sorted Set類型使用及應(yīng)用場(chǎng)景

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

    Redis分布式鎖詳細(xì)介紹

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

最新評(píng)論