文章目录

  • 功能需求
  • 1.service查询业务
  • 1. 查询关注对象列表
  • `opsForZSet().reverseRange`
  • `score(key, member)`
  • `new Date(score.longValue())`
  • 2. 查询粉丝列表数据
  • 2.Controller层处理请求
  • 关注列表
  • 粉丝列表
  • 3.处理模板页面
  • 处理链接
  • 关注列表模板
  • 粉丝列表模板


参考牛客网高级项目教程

狂神说Redis教程笔记

功能需求

  • 与主页和私信列表类似,开发显示关注对象列表和粉丝列表信息网页
  • 列表中的每一项包含用户基本信息和关注状态

1.service查询业务

1. 查询关注对象列表

  • 因redis储存的value是zset集合,有menber成员和分数
  • 因此与主页、私信列表不同,前者用mysql储存的对象,在controller层可以通过属性拿到对应信息
  • 而redis储存的是value,成员和分数需要分别查询,因此,需要在业务层将两个数据进行map封装
opsForZSet().reverseRange
  • 查询指定范围的value-类型统一为用户类型
  • 返回的是有序集合,虽用set接收,框架内部对set集合做了有序的处理
  • reverseRange表示按照分数倒序的方式排序,默认是正序
score(key, member)
  • 查询指定key中的member的分数
new Date(score.longValue())
  • new Date(long date),接收long类型
/**
* 获取关注目标对象列表信息-支持分页查询
* 放进list列表中,每个列表将查询到的value中的member,score用map封装好
* @param userId    指定用户的id-类型统一为用户
* @return
*/
public List<Map<String, Object>> followList(int userId, int offset, int limit) {
    List<Map<String, Object>> list = new ArrayList<>();
    String followKey = RedisKeyUtil.getFollowTarget(userId, ENTITY_TYPE_USER);

    // 倒序查询指定范围的value
    // 虽返回的是有序集合,框架内部对set集合做了有序的处理
    Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followKey, offset, offset + limit - 1);
    // 边界处理
    if(targetIds == null) {
        return null;
    }
    // 将获取的value中的member与score分别拿出放进map中
    for (Integer targetId : targetIds) {
        Map<String, Object> map = new HashMap<>();
        // 添加关注目标用户
        User user = userMapper.selectById(targetId);
        map.put("userId", user);
        // 查询指定member的score-添加关注时间
        Double score = redisTemplate.opsForZSet().score(followKey, targetId);
        map.put("followTime", new Date(score.longValue()));
        list.add(map);
    }
    return list;
}

2. 查询粉丝列表数据

  • 处理逻辑与查询关注对象列表一样
  • 封装的数据变量名最好保存一致,方便今后封装-统一处理
/**
 * 查询粉丝类别-封装的数据变量名最好保存一致,方便今后封装-统一处理
 * @param userId    指定用户的id-类型为用户
 * @param offset
 * @param limit
 * @return
 */
public List<Map<String, Object>> findFollowers(int userId, int offset, int limit) {
    String fansKey = RedisKeyUtil.getFollowFans(ENTITY_TYPE_USER, userId);
    Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(fansKey, offset, offset + limit - 1);

    if (targetIds == null) {
        return null;
    }

    List<Map<String, Object>> list = new ArrayList<>();
    for (Integer targetId : targetIds) {
        Map<String, Object> map = new HashMap<>();
        User user = userMapper.selectById(targetId);
        map.put("user", user);
        Double score = redisTemplate.opsForZSet().score(fansKey, targetId);
        map.put("followTime", new Date(score.longValue()));
        list.add(map);
    }

    return list;
}

2.Controller层处理请求

关注列表

  • 1.先查询和判断要访问的用户是否存在-防止手动输入url错误
  • 数据列表中基本信息都在service层包装好了,只要传递过来就可以
  • 需要另外判断每个列表中关注的用户的关注状态,因需要登录用户信息,故需要在controller层处理
  • 判断是否关注,需要验证访问用户登录信息,多处使用,封装起来
/**
 * 处理访问关注对象的列表
 * @param userId    指定用户的id-查询指定用户关注的人
 * @param page
 * @param model
 * @return
 */
@RequestMapping(value = "/followee/{userId}", method = RequestMethod.GET)
public String getFollowerList(@PathVariable("userId") int userId, Page page, Model model) {
    // 先查询和判断用户
    User user = userService.findUserById(userId);
    if(user == null) {
        throw new RuntimeException("该用户不存在!");
    }
    model.addAttribute("user", user);
    
    // 设置页面
    page.setPath("/followee/" + userId);
    page.setLimit(5);
    page.setRows((int)followService.findFollowTargetCnt(userId, ENTITY_TYPE_USER));
    
    // 查询列表,并将数据传给前端
    // 数据列表中基本信息都在service层包装好了,只要传递过来就可以
    List<Map<String, Object>> userList = followService.followList(userId, page.getOffset(), page.getLimit());
    // 需要另外判断每个列表中关注的用户的关注状态,因需要登录用户信息,故需要在controller层处理
    addStatus(userList);
    model.addAttribute("users", userList); 
    return "/site/follower";
}

/**
 * 在列表中的每个用户添加上关注的状态信息
* @param userList
*/
private void addStatus(List<Map<String, Object>> userList) {
    if(userList != null) {
        for(Map<String, Object> map : userList) {
            User u = (User) map.get("user");
            // 判断是否关注,需要验证访问用户登录信息,多处使用,封装起来
            map.put("hasFollowed", hasFollowed(u.getId()));
        }
    }
}

/**
* 关注状态判断
* @param userId
* @return
*/
private boolean hasFollowed(int userId) {
    if(hostHolder.getUser() == null) {
        return false;
    }

    return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}

粉丝列表

/**
 * 处理访问粉丝列表的请求
 * @param userId    指定用户的id-查询指定用户关注的人
 * @param page
 * @param model
 * @return
 */
@RequestMapping(value = "/fans/{userId}", method = RequestMethod.GET)
public String getFansList(@PathVariable("userId") int userId, Page page, Model model) {
    // 先查询和判断用户
    User user = userService.findUserById(userId);
    if(user == null) {
        throw new RuntimeException("该用户不存在!");
    }
    model.addAttribute("user", user);

    // 设置页面
    page.setPath("/fans/" + userId);
    page.setLimit(5);
    page.setRows((int)followService.findFollowFansCnt(ENTITY_TYPE_USER, userId));

    // 查询列表,并将数据传给前端
    // 数据列表中基本信息都在service层包装好了,只要传递过来就可以
    List<Map<String, Object>> userList = followService.fansList(userId, page.getOffset(), page.getLimit());
    // 需要另外判断每个列表中关注的用户的关注状态,因需要登录用户信息,故需要在controller层处理
    addStatus(userList);
    model.addAttribute("users", userList);
    return "/site/follower";
}

3.处理模板页面

处理链接

<span>关注了 <a class="text-primary" th:href="@{|/followee/${user.id}|}" th:text="${followTargetCnt}">5</a> 人</span>
<span class="ml-4">粉丝数 <a class="text-primary" th:href="@{|/fans/${user.id}|}" th:text="${followFans}">123</a> 人</span>

关注列表模板

  • 粉丝和关注列表在一个页面上,不同的链接
<li class="nav-item">
   <a class="nav-link position-relative active"  th:href="@{|/followee/${user.id}|}"><i class="text-info" th:utext="${user.username}">Nowcoder</i> 关注的人</a>
</li>
<li class="nav-item">
   <a class="nav-link position-relative" th:href="@{|/fans/${user.id}|}"><i class="text-info" th:utext="${user.username}">Nowcoder</i> 的粉丝</a>
</li>
  • 列表中的每个用户信息-包括关注时间
<a th:href="@{|/user/profile/${map.user.id}|}">
   <img th:src="@{map.user.headerUrl}" class="mr-4 rounded-circle user-header" alt="用户头像" >
</a>
<div class="media-body">
   <h6 class="mt-0 mb-3">
      <span class="text-success" th:utext="${map.user.username}">落基山脉下的闲人</span>
      <span class="float-right text-muted font-size-12">关注于 
         <i th:text="${#dates.format(map.followTime, 'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</i></span>
   </h6>
  • 登录用户对列表中的每个用户的关注状态-及关注按钮
  • 与个人主页关注按钮逻辑一致
<div>
   <input type="hidden" id="entityId" th:value="${map.user.id}">
   <button type="button" th:class="|btn ${map.hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right follow-btn|"
         th:if="${loginUser!=null && loginUser.id!=map.user.id}" th:text="${map.hasFollowed?'已关注':'关注TA'}">关注TA</button>
</div>

查看本机redis账号密码 redis查看用户列表_后端

粉丝列表模板

  • 处理逻辑类似