功能需求1. dao层设计redis对应的key
设计储存关注对象信息的健值对
keyvalue 设计储存粉丝信息的健值对
keyvalue 2. Service层处理关注和取关的业务
1. 触发关注、取关事件-redis事务处理
==opsForZSet().add(key, value)====opsForZSet().remove(key, value)== 2. 查询关注对象的数量
==opsForZSet().size(key)== 3. 查询当前对象的粉丝数量4. 查询当前用户是否对目标用户的关注状态 3.Controller层处理请求
1. 处理关注、取关异步请求2. 更新访问用户个人主页的请求 4. View层处理模板页面
1.处理关注、取关事件按钮
按钮样式根据关注状态动态改变关注的显示根据关注状态动态显示关注事件对应的js文件定义
在按钮标签前添加隐藏标签==$(btn).prev().val()== 2. 显示指定用户关注了多少人3. 显示指定用户的粉丝数测试结果:
功能需求参考牛客网高级项目教程
狂神说Redis教程笔记
1.在用户的个人信息页面,点击关注,可以关注该用户,并将关注数据用redis存储
在关注状态下,再次点击,会取消关注 统计该用户关注了多少人,以及粉丝有多少人与点赞功能类似,数据特点是数据量大、变化快、且数据字段较少,因此采用redis储存比较合适 1. dao层设计redis对应的key
若A关注了B,则A是B的fans(粉丝),B是A的Followee(目标)。关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体。若关注的目标是帖子、题目等,也就是收藏,直接根据实体类型和id确定,今后开发收藏功能也可以复用 设计储存关注对象信息的健值对 key
与点赞不同,点赞不需要查询统计我一共点了多少赞,故,不需要储存目标entityId,但关注需要
故,关注需要指明实体类型,对人是关注,对其他实体则是是收藏
关注需要指明当前用户的userId,这样方便被关注者统计多少粉丝,
因为value不能放userId,要放被关注目标的id,这样,方便统计关注了多少人
// 实体类型为user时,为某人关注了某人 // 实体类型为帖子时,为某人收藏了某帖子 follow:target:userId:entityTypevalue
value用有序集合Zset储存关注目标的id,和分数,分数以关注时间表示
这样,便于统计关注了多少实体,以及这些实体展示时,可以按照一定规则排序
public static String getFollowTarget(int userId, int entityType) {
return PREFIX_FOLLOW_TARGET + SPLIT + userId + SPLIT + entityType;
}
设计储存粉丝信息的健值对
key
与点赞逻辑类似,要储存某个实体的粉丝信息,即某个实体收到的关s注或收到的收藏的粉丝信息因此,要指明这个实体的类型和id
value中储存粉丝的id,因此能发出关注或收藏的主体只能是user类型,因此只需储存userId即可 value
要储存粉丝的id,为今后方便将粉丝按照一定规则罗列出来,用zset还要储存一个对应的分数,用关注时间表示
public static String getFollowFans(int entityType, int entityId) {
return PREFIX_FOLLOW_FANS + SPLIT + entityType + SPLIT + entityId;
}
2. Service层处理关注和取关的业务
1. 触发关注、取关事件-redis事务处理
可以与之前的点赞逻辑一样,先判断当前用户是否关注了目的对象,根据关注状态处理不同的事件
也可以结合前端设计模块触发不同的事件,
即关注与取关前端按钮不同,点击不同按钮会触发不同事件,无需在一个按钮触发后,进行查询判断,本例中采用此种策略
注意,点击关注或取关后,目标对象的粉丝信息也会发生改变,因此,这两个事件应该放在一个事务中处理
opsForZSet().add(key, value) opsForZSet().remove(key, value)
public void follow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String followTargetKey = RedisKeyUtil.getFollowTarget(userId, entityType);
String followFans = RedisKeyUtil.getFollowFans(entityType, entityId);
// 开启事务
operations.multi();
// 储存关注对象信息-关注对象的粉丝信息
operations.opsForZSet().add(followTargetKey, entityId, System.currentTimeMillis());
operations.opsForZSet().add(followFans, userId, System.currentTimeMillis());
// 提交事务并返回
return operations.exec();
}
});
}
public void unFollow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String followTargetKey = RedisKeyUtil.getFollowTarget(userId, entityType);
String followFans = RedisKeyUtil.getFollowFans(entityType, entityId);
// 开启事务
operations.multi();
// 储存关注对象信息-关注对象的粉丝信息
operations.opsForZSet().remove(followTargetKey, entityId);
operations.opsForZSet().remove(followFans, userId);
// 提交事务并返回
return operations.exec();
}
});
}
2. 查询关注对象的数量
opsForZSet().size(key)
与opsForZSet().zCard(key)功能一样,查询成员数量
public long findFollowTargetCnt(int userId, int entityType, int entityId) {
String followTarget = RedisKeyUtil.getFollowTarget(userId, entityType);
return redisTemplate.opsForZSet().size(followTarget);
}
3. 查询当前对象的粉丝数量
public long findFollowFans(int entityType, int entityId) {
String followFans = RedisKeyUtil.getFollowFans(entityType, entityId);
return redisTemplate.opsForZSet().zCard(followFans);
}
4. 查询当前用户是否对目标用户的关注状态
opsForZSet().score(key, member)
通过查询成员函数的分数是否存在,来判断menber是否在集合中
public boolean hasFollowed(int userId, int entityType, int entityId) {
String followTargetKey = RedisKeyUtil.getFollowTarget(userId, entityType);
return redisTemplate.opsForZSet().score(followTargetKey, entityId) != null;
}
3.Controller层处理请求
1. 处理关注、取关异步请求
要拦截判断是否登录从拦截器中的当前线程容器中获取登录用户的id
@RequestMapping(value = "/follow", method = RequestMethod.POST)
@ResponseBody
public String follow(int entityType, int entityId) {
User user = hostHolder.getUser();
if(user == null) {
throw new IllegalArgumentException("用户没有登录!");
}
followService.follow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, "关注成功!");
}
@RequestMapping(value = "/follow", method = RequestMethod.POST)
@ResponseBody
public String unfollow(int entityType, int entityId) {
User user = hostHolder.getUser();
if(user == null) {
throw new IllegalArgumentException("用户没有登录!");
}
followService.unFollow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, "已取消关注!");
}
2. 更新访问用户个人主页的请求
注意:获取当前用户信息时,要先判断是否为null,未登录状态也可以访问指定用户主页
// 获取当前用户对指定用户的关注状态
boolean hasFollowed = false;
if(hostHolder.getUser() != null) {
hasFollowed = followService.hasFollowed(
hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
model.addAttribute("hasFollowed", hasFollowed);
整体代码如下:
@RequestMapping(value = "/profile/{userId}", method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
// 先获取要访问的用户
User user = userService.findUserById(userId);
if(user == null) {
throw new IllegalArgumentException("该用户不存在!");
}
// 将指定用户信息封装
model.addAttribute("user", user);
// 获取指定用户的点赞数量
int likeCount = likeService.findUserLikeCount(userId);
model.addAttribute("likeCount", likeCount);
// 获取指定用户的关注对象数量
long followTargetCnt = followService.findFollowTargetCnt(userId, ENTITY_TYPE_USER);
model.addAttribute("followTargetCnt", followTargetCnt);
// 获取指定用户的粉丝数量
long followFans = followService.findFollowFans(ENTITY_TYPE_USER, userId);
model.addAttribute("followFans", followFans);
// 获取当前用户对指定用户的关注状态
boolean hasFollowed = false;
if(hostHolder.getUser() != null) {
hasFollowed = followService.hasFollowed(
hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
model.addAttribute("hasFollowed", hasFollowed);
return "/site/profile";
}
4. View层处理模板页面
1.处理关注、取关事件按钮
按钮样式根据关注状态动态改变
th:关注的显示根据关注状态动态显示
如果用户没有登录或者访问的就是自己的主页,无需显示关注的按钮
关注事件对应的js文件定义 在按钮标签前添加隐藏标签
为了传入指定用户id,在按钮标签前添加隐藏标签
$(btn).prev().val()
取到指定按钮标签前一个标签的内容
$(function(){
$(".follow-btn").click(follow);
});
function follow() {
var btn = this;
if($(btn).hasClass("btn-info")) { // 关注按钮
// 关注TA
$.post(
CONTEXT_PATH + "/follow",
{"entityType":3,"entityId":$(btn).prev().val()},
function (data) {
data = $.parseJSON(data);
if(data.code == 0) {
window.location.reload(); // 为了完整显示当前个人主页数据,需要刷新页面,其他网页关注可以不必刷新
} else {
alert(data.msg);
}
}
);
} else {
// 取消关注
$.post(
CONTEXT_PATH + "/unfollow",
{"entityType":3,"entityId":$(btn).prev().val()},
function (data) {
data = $.parseJSON(data);
if(data.code == 0) {
window.location.reload();
} else {
alert(data.msg);
}
}
);
}
}
2. 显示指定用户关注了多少人
关注了 5 人3. 显示指定用户的粉丝数
粉丝数 123 人测试结果:
未登录状态下,查看某用户个人主页
登录状态下,
访问自己的主页
访问别人的主页
点击关注按钮
点击取消关注按钮



