文章目录
- 功能需求
- 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>
粉丝列表模板
- 处理逻辑类似