牛客网后端项目实战(二十七):点赞

本文介绍了一个基于Redis的点赞功能实现方案,包括点赞、取消点赞、显示点赞数量及状态等功能,并详细展示了Redis工具类、业务层和服务层的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发点赞功能,首页和详情页显示点赞数量,详情页还要显示点赞状态

  • 点赞

    • 支持对帖子、评论点赞。
    • 第1次点赞,第2次取消点赞。
  • 首页点赞数量

    • 统计帖子的点赞数量。
  • 详情页点赞数量

    • 统计点赞数量。
    • 显示点赞状态。

在这里插入图片描述

点赞

点赞要考虑性能
由于数据存放在Redis里,DAO层比较简单,直接写一个Redis key工具类,在业务层操作key就行

Redis工具类

package com.nowcoder.community.util;

// Redis Key 工具不用交给容器管理
public class RedisKeyUtil {

    private static final String SPLIT = ":";
    private static final String PREFIX_ENTITY_LIKE = "like:entity"; // 帖子和评论统称为实体

    // 某个实体的赞
    // like:entity:entityType:entityId -> set(userId) 使用集合存储点赞的用户
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }

业务层

package com.nowcoder.community.service;

import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;

@Service
public class LikeService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 点赞
    public void like(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); // 生成实体对应的key
        boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);  // 判断 userId是否在 entityLikeKey 集合里
        if (isMember) {  // 若已存在,说明已经点过赞,将uerId从集合中移除(取消赞)
            redisTemplate.opsForSet().remove(entityLikeKey, userId);
        } else {   // 没点过赞,所以添加到集合中
            redisTemplate.opsForSet().add(entityLikeKey, userId);
        }
    }

    // 查询某实体点赞的数量
    public long findEntityLikeCount(int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().size(entityLikeKey); // 统计数量
    }

    // 查询某人对某实体的点赞状态
    public int findEntityLikeStatus(int userId, int entityType, int entityId) { // 返回int可以表示多个状态
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
    }

表现层

package com.nowcoder.community.controller;

import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.LikeService;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
public class LikeController {

    @Autowired
    private LikeService likeService;

    @Autowired
    private HostHolder hostHolder;

    @RequestMapping(path = "/like", method = RequestMethod.POST)
    @ResponseBody
    public String like(int entityType, int entityId) {
        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<String, Object> map = new HashMap<>();
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);

        return CommunityUtil.getJSONString(0, null, map);
    }

}

逻辑:输入某实体类型和某实体id,获取当前登录用户,执行点赞操作,获取该实体的赞的数量,以及当前登录用户对该实体的电站状态

在首页显示赞

// HomeController 类添加

    @RequestMapping(path = "/index", method = RequestMethod.GET)
    public String getIndexPage(Model model, Page page) {
    			...
                long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
                map.put("likeCount", likeCount);
    }

在详情页显示赞的数量点赞状态

// DiscussPostController包
    @RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
    public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
        // 帖子
        // 作者
		...
        // 点赞数量
        long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId);
        model.addAttribute("likeCount", likeCount);
        // 点赞状态
        int likeStatus = hostHolder.getUser() == null ? 0 :
                likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);
        model.addAttribute("likeStatus", likeStatus);

        // 评论分页信息

        // 评论: 给帖子的评论
        // 回复: 给评论的评论
        // 评论列表

        // 评论VO列表
		...
                // 评论VO
                // 评论
                // 作者
                // 点赞数量
                likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());
                commentVo.put("likeCount", likeCount);
                // 点赞状态
                likeStatus = hostHolder.getUser() == null ? 0 :
                        likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
                commentVo.put("likeStatus", likeStatus);

                // 回复列表
                // 回复VO列表
                        // 回复
                        // 作者
                        // 回复目标
                        // 点赞数量
                        likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId());
                        replyVo.put("likeCount", likeCount);
                        // 点赞状态
                        likeStatus = hostHolder.getUser() == null ? 0 :
                                likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());
                        replyVo.put("likeStatus", likeStatus);
                        replyVoList.add(replyVo);
                    }
                }
                commentVo.put("replys", replyVoList);

                // 回复数量
            }
        }
        model.addAttribute("comments", commentVoList);
        return "/site/discuss-detail";
    }

}

html页面

首页,修改index.html

								<ul class="d-inline float-right">
									<li class="d-inline ml-2">赞 <span th:text="${map.likeCount}">11</span></li>    // 赞的数量
									<li class="d-inline ml-2">|</li>
									<li class="d-inline ml-2">回帖 <span th:text="${map.post.commentCount}">7</span></li>
								</ul>

discuss-detail.html
1、处理点赞超链接,异步请求

				<!-- 作者 -->
				<div class="media pb-3 border-bottom">
					<a href="profile.html">
						<img th:src="${user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
					</a>
					<div class="media-body">
						<div class="mt-0 text-warning" th:utext="${user.username}">寒江雪</div>
						<div class="text-muted mt-3">
							发布于 <b th:text="${#dates.format(post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
							<ul class="d-inline float-right">
								<li class="d-inline ml-2">
									<a href="javascript:;" th:onclick="|like(this,1,${post.id});|" class="text-primary">
										<b th:text="${likeStatus==1?'已赞':'赞'}">赞</b> <i th:text="${likeCount}">11</i>
									</a>
								</li>
								<li class="d-inline ml-2">|</li>
								<li class="d-inline ml-2"><a href="#replyform" class="text-primary">回帖 <i th:text="${post.commentCount}">7</i></a></li>
							</ul>
						</div>
					</div>
				</div>```
<a href="javascript:;" th:onclick="|like(this,1,${post.id});|" class="text-primary">
	<b th:text="${likeStatus==1?'已赞':'赞'}">赞</b> <i th:text="${likeCount}">11</i>
</a>

意思是:超连接 href 是个空的 js ;
onclick表示单击按钮执行like函数, this 表示当前超链接(用来区分点的时哪个赞),对帖子进行点赞,entityType=1,entityId=${post.id} 帖子Id;

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);  // 失败则弹出提示框,统一处理
            }
        }
    );
}

类似的处理 回复列表 和 回帖列表

									<li class="d-inline ml-2">
										<a href="javascript:;" th:onclick="|like(this,2,${cvo.comment.id});|" class="text-primary">
											<b th:text="${cvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${cvo.likeCount}">1</i>)
										</a>
									</li>
											<li class="d-inline ml-2">
												<a href="javascript:;" th:onclick="|like(this,2,${rvo.reply.id});|" class="text-primary">
													<b th:text="${rvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${rvo.likeCount}">1</i>)
												</a>
											</li>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值