文章目录

  • 功能需求
  • 1. dao层设计redis对应的key
    • 设计储存关注对象信息的健值对
      • key
      • value
    • 设计储存粉丝信息的健值对
      • key
      • value
  • 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:entityType
    

value

  • value用有序集合Zset储存关注目标的id,和分数,分数以关注时间表示

    • 这样,便于统计关注了多少实体,以及这些实体展示时,可以按照一定规则排序
    /*** 某个用户关注的实体,要保存关注的实体类型,还要保存是谁关注的,方便被关注者统计,* 但也要指明被关注的对象具体id,放入value* follow:target:userId:entityType -> zset(entityId,now)* @param userId        当前用户的id* @param entityType    当前用户关注对象的实体类型* @return              储存关注对象信息的key*/
    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还要储存一个对应的分数,用关注时间表示
/*** 某个实体的粉丝* follow:fans:entityType:entityId -> zset(userId,now)* @param entityType    要储存的实体类型* @param entityId      要储存的实体id* @return              返回储存实体类型粉丝信息的key*/
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)
/*** 关注实体事件-被关注对象的粉丝信息也同时更新-增加redis事件处理* @param userId        当前用户id* @param entityType    关注对象类型* @param entityId      关注对象id*/
public void follow(int userId, int entityType, int entityId) {redisTemplate.execute(new SessionCallback() {@Overridepublic 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();}});
}/*** 取消关注实体事件-被关注对象的粉丝信息也同时更新-增加redis事件处理* @param userId        当前用户id* @param entityType    关注对象类型* @param entityId      关注对象id*/
public void unFollow(int userId, int entityType, int entityId) {redisTemplate.execute(new SessionCallback() {@Overridepublic 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)功能一样,查询成员数量
/*** 获取当前用户指定类型关注对象的数量* @param userId* @param entityType* @param entityId* @return*/
public long findFollowTargetCnt(int userId, int entityType, int entityId) {String followTarget = RedisKeyUtil.getFollowTarget(userId, entityType);return redisTemplate.opsForZSet().size(followTarget);
}

3. 查询当前对象的粉丝数量

/*** 获取当前用户的粉丝数* @param entityType* @param entityId* @return*/
public long findFollowFans(int entityType, int entityId) {String followFans = RedisKeyUtil.getFollowFans(entityType, entityId);return redisTemplate.opsForZSet().zCard(followFans);
}

4. 查询当前用户是否对目标用户的关注状态

opsForZSet().score(key, member)

  • 通过查询成员函数的分数是否存在,来判断menber是否在集合中
/*** 判断当前用户userid是否关注了目标对象entityId-通过判断目标对象的粉丝中有无当前对象* @param userId* @param entityType* @param entityId* @return*/
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
/*** 处理关注的异步请求* @param entityType    关注对象类型* @param entityId      关注对象id* @return*/
@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, "关注成功!");
}/*** 处理取消关注的异步请求* @param entityType* @param entityId* @return*/
@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);
    
  • 整体代码如下:

/*** 访问指定用户个人主页的请求* @param userId    指定的用户id* @param model* @return*/
@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:class="|btn ${hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right mr-5 follow-btn|"

关注的显示根据关注状态动态显示

  • 如果用户没有登录或者访问的就是自己的主页,无需显示关注的按钮
<button type="button"th:class="|btn ${hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right mr-5 follow-btn|"th:text="${hasFollowed?'已关注':'关注TA'}" th:if="${loginUser!=null && loginUser.id!=user.id}">关注TA
</button>

关注事件对应的js文件定义

在按钮标签前添加隐藏标签
  • 为了传入指定用户id,在按钮标签前添加隐藏标签
<input type="hidden" id="entityId" th:value="${user.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. 显示指定用户关注了多少人

<span>关注了 <a class="text-primary" href="followee.html" th:text="${followTargetCnt}">5</a> 人</span>

3. 显示指定用户的粉丝数

<span class="ml-4">粉丝数 <a class="text-primary" href="follower.html" th:text="${followFans}">123</a> 人</span>

测试结果:

  • 未登录状态下,查看某用户个人主页

  • 登录状态下,

    • 访问自己的主页

    • 访问别人的主页

  • 点击关注按钮

  • 点击取消关注按钮

项目1在线交流平台-4. 使用radis高性能储存方案-5.redis常用使用场景-开发关注功能-redis事务相关推荐

  1. 项目1在线交流平台-4. 使用radis高性能储存方案-1.redis入门-特点、安装与支持数据类型

    文章目录 1.为何使用redis 1.1 NoSQL NoSQL数据库特点 常见NoSQL数据库 1.2 什么是redis,有何特点 1.3 为何选用redis 2. redis的安装与使用 2.1 ...

  2. 项目1在线交流平台-4. 使用radis高性能储存方案-3.redis使用场景-点赞功能

    文章目录 功能需求 1. dao层设计redis的key生成工具 2. Service层处理点赞业务 添加赞或删除赞 ==isMember(key, userId)== ==remove(key, u ...

  3. 项目1在线交流平台-7.构建安全高效的企业服务-2.使用Security自定义社区网页认证与授权

    文章目录 功能需求 一. 废弃登录检查的拦截器 二.授权配置 1. 导包 2. Security配置 2.1 `WebSecurity` 2.2`HttpSecurity` ` http.author ...

  4. 项目1在线交流平台-7.构建安全高效的企业服务-3. Security整合Kafka,ES,Thymeleaf实例-对帖子置顶、加精、删除

    文章目录 功能需求 一.置顶.加精.删除帖子功能的实现 1. dao层处理数据 接口定义 sal语句定义 2. service层业务处理 3. Controller层处理按钮事件异步请求 异步请求及k ...

  5. 项目1在线交流平台-6.Elasticsearch分布式搜索引擎-3.ES结合Kafka应用-开发社区搜索功能

    文章目录 功能需求 一.Service层处理操作ES服务器的数据 二.Controller层处理帖子添加和评论事件请求 1.添加帖子时-触发事件-发布消息 2. 添加评论时-触发发帖事件-发布消息 三 ...

  6. 项目1在线交流平台-3.开发交流社区核心功能模块-7.显示私信信息

    文章目录 功能需求及处理策略 1. dao层处理会话私信数据 数据库设计 接口方法声明,sql编写 接口方法 sql-子查询语句 组函数与分组查询 根据分组结果进行子查询 测试 2.service层业 ...

  7. 小项目----音乐在线播放器

    目录 音乐播放器 项目功能: 项目框架: 项目的创建很重要: 数据库设计 在idea中配置数据库和xml 一.登录板块设计 1创建User类 2创建UserMapper和Controller接口 3. ...

  8. JAVA毕设项目html5在线医疗系统(Vue+Mybatis+Maven+Mysql+sprnig+SpringMVC)

    JAVA毕设项目html5在线医疗系统(Vue+Mybatis+Maven+Mysql+sprnig+SpringMVC) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql ...

  9. 经管资源库项目总结----在线预览office文件的实现与总结

    依旧是这个经管的项目.在线预览作为资源和文档管理系统的一个很酷的并且是如此重要的功能,是必须要实现的.然后百度一下office在线预览,看起来so eazy啊,各种博客各种demo,一下子就做出效果来 ...

最新文章

  1. JavaScript 事件冒泡简介及应用(转)
  2. 弄了个调试呼叫中心用的小机器
  3. c语言用define预处理命令定义,C语言程序设计第八章预处理命令..doc
  4. 不使用jQuery对Web API接口POST,PUT,DELETE数据
  5. confluence 5 mysql_Centos 6.5 安装 Atlassiana Crowd+JIRA+Confluence(Wiki)之一 数据库篇(MySQL5.1)...
  6. qt中new与delete使用示例
  7. 很喜欢VS.NET 2003对条件编译的代码提示
  8. 语言prodave以太网通讯_工业以太网通讯
  9. Linux平台下卸载MySQL的方法
  10. 整车EMC正向开发及仿真
  11. 在定语从句中which和that用法有什么区别
  12. mysql 限制条数_MySQL LIMIT:限制查询结果的条数
  13. OSChina 周三乱弹 —— 我在 if 里,你却在 else
  14. 零基础如何学习SEO网站优化
  15. 初级网络之OSI-网络/路由协议-IP编址-VLAN-ACL-NAT-DHCP-telnet远程
  16. pb调用精伦电子sdtapi.dll读卡函数的心得
  17. 41 linux标准输入设备之矩阵键盘驱动的实现
  18. 三千烦恼,不如淡然一笑
  19. 动态调整android控件(View)的大小
  20. 靠手表“上位”的儿童社交,“红眼”微信做儿童版就有机会了吗?

热门文章

  1. 杂谈 之 闲来无事 (一)
  2. i5 i7 Oracle,Intel Core i5/i7哪款最适合你?Intel Core i5/i7处理器简略对比评测
  3. Optimal Design of Energy-Efficient Multi-User MIMO Systems: Is Massive MIMO the Answer?笔记
  4. ecshop被加入了黑链
  5. Java实现 蓝桥杯 算法提高 成绩排名
  6. hdu-4745 Two Rabbits
  7. 基于三维GIS技术的公路交通数字孪生系统
  8. 尚硅谷_佟刚_SpringMVC_工程实例与代码(自己敲的)
  9. 激光雷达SLAM激光的前端配准算法
  10. Basler工业相机python开发(Pypylon)