开发点赞功能
,首页和详情页显示点赞数量
,详情页还要显示点赞状态
-
点赞
- 支持对帖子、评论点赞。
- 第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>