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

参考牛客网高级项目教程

狂神说Redis教程笔记

功能需求

在这里插入图片描述

  • 1.对帖子、评论、回复可以进行点赞操作

    • 无需刷新整个网页,采用异步请求
    • 由于点赞频繁,且数据单一且动态变化
      • 用关系型数据库存io操作性能较低,改用redis存储
      • 数据单一,只有点赞一个属性字段需要更改,且针对不同类型不同id点赞的类型动态变化,
        • 因此,设计mysql表比较浪费,以及为了操作一个变化的赞字段,要将很多相关帖子用户信息字段都查询到,也比较浪费,
        • 使用redis设计不同的key比较方便
  • 2.redis储存点赞数据后,刷新页面后,要能够在页面显示这些点赞信息

    • 因此,需要更新之前开发的controller和模板页面相关处理

1. dao层设计redis的key生成工具

  • 由于针对不同的帖子、评论、回复等实体要定义不同的key,可以封装成一个工具类,方便生成key
public class RedisLikeUtil {
    // redis字符串拼接符:
    private static final String SPLIT = ":";
    // redis定义点赞的key的前缀
    private static final String PREFIX_ENTITY_LIKE = "like:entity";

    /**
     * 某个实体的赞的key
     * like:entity:entityType:entityId -> set(userId)
     * @param entityType    实体类型
     * @param entityId      实体id
     * @return              针对特定实体的赞的key
     */
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }
}

2. Service层处理点赞业务

添加赞或删除赞

  • 添加赞或删除赞,根据当前用户点赞的状态来定
  • value不是简单的使用String类型,而是用集合set类型,
    • 集合中装每个访问用户的id,方便查询指定用户的点赞状态
isMember(key, userId)
remove(key, userId)
add(key, userId)
/**
 * 添加赞或删除赞,根据当前用户点赞的状态来定
 * @param userId        当前访问用户id
 * @param entityType    访问的实体类型
 * @param entityId      访问的实体id
 */
public void like(int userId, int entityType, int entityId) {
    String key = RedisLikeUtil.getEntityLikeKey(entityType, entityId);
    Boolean isMember = redisTemplate.opsForSet().isMember(key, userId);
    if(isMember) {
        redisTemplate.opsForSet().remove(key, userId);
    } else {
        redisTemplate.opsForSet().add(key, userId);
    }
}

查询指定实体的点赞状态

  • **用数字来表示点赞的状态,方便扩展,**今后可能还会有点彩等其他状态
/**
 * 查询指定实体的点赞状态
 * @param userId
 * @param entityType
 * @param entityId
 * @return         用数字来表示点赞的状态,方便扩展
 */
public int findEntityLikeStatus(int userId, int entityType, int entityId) {
    String key = RedisLikeUtil.getEntityLikeKey(entityType, entityId);
    return redisTemplate.opsForSet().isMember(key, userId) ? LIKE_STATUS_YES : LIKE_STATUS_NO;
}
 /**
     * 点赞状态:位赞
     */
    int LIKE_STATUS_NO = 0;

    /**
     * 点赞状态:已赞
     */
    int LIKE_STATUS_YES = 1;
}

查询指定实体的点赞数量

  • 注意返回类型是long类型
size(key)
/**
 * 查询指定实体的点赞数量
 * @param entityType
 * @param entityId
 * @return  注意返回类型是long类型
 */
public long findEntityLikeCount(int entityType, int entityId) {
    return redisTemplate.opsForSet().size(RedisLikeUtil.getEntityLikeKey(entityType, entityId));
}

3. Controller层处理异步请求和显示点赞状态信息

点赞异步请求处理

  • 权限认证-今后在拦截器或过滤器阶段统一管理,先获取当前用户
  • 用map封装多个数据信息,传给json,交给js渲染处理
@Controller
public class LikeController {
    @Autowired
    LikeService likeService;
    
    @Autowired
    HostHolder hostHolder;

    /**
     * 处理点赞的异步请求
     * @param entityType   
     * @param entityId
     * @return  json字符串,不传递msg,如果有问题,直接在网页alert提示
     */
    @RequestMapping(value = "/like", method = RequestMethod.POST)
    @ResponseBody
    public String like(int entityType, int entityId) {
        // 用来封装信息的map
        Map<String, Object> map = new HashMap<>();
        // 权限-统一管理,先获取当前用户
        User user = hostHolder.getUser();
        // 点赞事件处理
        likeService.like(user.getId(), entityType, entityId);
        // 点赞数量获取
        long likeCount = likeService.findEntityLikeCount(entityType, entityId);
        // 点赞状态获取
        int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
        // 统一封装到map传给前端页面
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);
        return CommunityUtil.getJSONString(0, null, map);
    }
}

点赞数量和状态显示

访问主页的请求中添加点赞信息

在这里插入图片描述

if(posts != null) {
    for (DiscussPost post : posts) {
        Map<String, Object> map = new HashMap<>();
        // 帖子信息
        map.put("post", post);
        // 帖子作者信息
        User user = userService.findUserById(post.getUserId());
        map.put("user", user);
        // 帖子点赞信息
        long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
        map.put("likeCount", likeCount);
        mapList.add(map);
    }
}
访问帖子详情页面请求中添加点赞信息
1.帖子点赞信息

在这里插入图片描述

// 1.帖子的数据信息        
        // 查询到指定帖子-由于点击的是帖子主题,一定存在帖子
        DiscussPost discussPost = discussPostService.selectPost(postId);
        model.addAttribute("post", discussPost);
        // 查询到帖子作者信息-帖子必须由指定用户发布,故,一定能查询到用户
        User user = userService.findUserById(discussPost.getUserId());
        model.addAttribute("user", user);
        // 查询帖子的点赞信息-状态和数量
        // 检查是否登录,未登录状态,一定显示未点赞状态
        int likeStatus = hostHolder.getUser() == null ? LIKE_STATUS_NO :
                likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, postId);
        model.addAttribute("likeStatus", likeStatus);
        long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId);
        model.addAttribute("likeCount", likeCount);
        
2.对帖子的一级评论的点赞信息

在这里插入图片描述

// 2.对帖子的评论-数据信息
// 查询帖子的评论分页信息-将关联用户信息一并封装
List<Comment> commentList = commentService.selectCommentList(
    ENTITY_TYPE_POST, discussPost.getId(), page.getOffset(), page.getLimit());
List<Map<String, Object>> commentVoList = new ArrayList<>();
if(commentList != null) {   // 有可能没有评论,注意边界条件
    for(Comment comment : commentList) {
        Map<String, Object> commentVoMap = new HashMap<>();
        // 评论信息
        commentVoMap.put("comment", comment);
        // 评论作者信息
        commentVoMap.put("user", userService.findUserById(comment.getUserId()));
        // 对评论的点赞信息-状态和数量
        // 检查是否登录,未登录状态,一定显示未点赞状态
        likeStatus = hostHolder.getUser() == null ? LIKE_STATUS_NO :
        likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
        commentVoMap.put("likeStatus", likeStatus);
        likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());
        commentVoMap.put("likeCount", likeCount);
3. 对评论的回复的点赞信息

在这里插入图片描述

// 3. 对评论的回复-数据信息                
// 每条评论的回复数量统计
int replyCount = commentService.selectComments(ENTITY_TYPE_COMMENT, comment.getId());
commentVoMap.put("replyCount", replyCount);
// 根据每条评论的id,循环查询每条评论下的所有回复信息
List<Comment> replyList = commentService.selectCommentList(
    ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE); // 回复显示无需分页
List<Map<String, Object>> replyVoList = new ArrayList<>(); // 封装list
if(replyList != null) {
    for(Comment reply : replyList) {
        Map<String, Object> replyVoMap = new HashMap<>();
        replyVoMap.put("reply", reply);
        replyVoMap.put("user", userService.findUserById(reply.getUserId()));
        // 4.对回复的回复-数据信息                        
        // 普通回复中需要用到targetId
        // 找到回复目标用户作者-注意,如果targetId=0,表示无效,无需查询
        User targetUser = reply.getTargetId() == 0 ? null :
        userService.findUserById(reply.getTargetId());
        replyVoMap.put("targetUser", targetUser);

        // 对回复的点赞信息-状态和数量
        // 检查是否登录,未登录状态,一定显示未点赞状态
        likeStatus = hostHolder.getUser() == null ? LIKE_STATUS_NO :
        likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());
        replyVoMap.put("likeStatus", likeStatus);
        likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId());
        replyVoMap.put("likeCount", likeCount);

4. View层模板处理,js定义

显示点赞数量

  • 主页中的每个帖子需要显示
  • 帖子详情页面中,对帖子的赞、对评论的赞、对一级回复、普通回复的赞均需要显示
index.html-主页模板
<li class="d-inline ml-2"><span th:text="${map.likeCount}">11</span></li>
discuss-detail.html-帖子详情页面模板
1.帖子点赞信息
<b th:text="${likeStatus==1?'已赞':''}"></b> <i th:text="${likeCount}">11</i>
2.评论点赞信息
<b th:text="${cvo.likeStatus==1?'已赞':''}"></b>(<i th:text="${cvo.likeCount}">1</i>)
3.回复点赞信息
<b th:text="${rvo.likeStatus==1?'已赞':''}"></b>(<i th:text="${rvo.likeCount}">1</i>)

异步请求js处理

触发点赞事件
onclick
  • onclick事件,当按钮被点击时执行Javascript代码

  • this,定位到当前位置的函数

  • href=“javascript:;”,表示点击按钮后,触发js文件处理

1.帖子点赞事件请求
<a href="javascript:;" class="text-primary" th:onclick="|like(this,1,${post.id});|">
<b th:text="${likeStatus==1?'已赞':''}"></b> <i th:text="${likeCount}">11</i>
</a>
2.评论点赞事件请求
<a href="javascript:;" class="text-primary" th:onclick="|like(this,2,${cvo.comment.id});|">
   <b th:text="${cvo.likeStatus==1?'已赞':''}"></b>(<i th:text="${cvo.likeCount}">1</i>)
</a></li>
3.回复点赞事件请求
<a href="javascript:;" class="text-primary" th:onclick="|like(this,2,${rvo.reply.id});|">
   <b th:text="${rvo.likeStatus==1?'已赞':''}"></b>(<i th:text="${rvo.likeCount}">1</i>)
</a></li>
触发事件后,js定义
<script th:src="@{/js/discuss.js}"></script>
  • 定义like函数,btn,定位到触发的按钮

$(btn).children(“i”)

  • 定位到当前按钮标签的子标签
  • 对触发事件后的msp中的数据进行显示,直接异步显示,而不是刷新页面显示
function like(btn, entityType, entityId) {
    $.post(
        CONTEXT_PATH + "/like",
        {"entityType":entityType,"entityId":entityId},
        function (data) {
            data = $.parseJSON(data);
            if(data.code == 0) {
                $(btn).children("i").text(data.likeCount);
                $(btn).children("b").text(data.likeStatus == 1 ? '已赞' : '赞');
            } else {
                alert(data.msg);
            }
        }
    );
}

测试结果:

  • 登录状态:进行点赞,并显示点赞信息

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 未登录状态显示点赞信息

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值