参考牛客网高级项目教程
狂神说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);
}
}
);
}
测试结果:
- 登录状态:进行点赞,并显示点赞信息
- 未登录状态显示点赞信息