将数据存入到 redis 中,以 key 为关键,使用 key 编程,所以为了使 key 能反复利用,给 redis 生成一个工具类(在 util 包下创建 RedisKeyUtil 类):
package com.example.demo.util;
/**
* 生成 redis 工具
*/
public class RedisKeyUtil {
//key 使用 :分开:声明常量
public static final String SPLIT = ".";
//存入帖子或者评论的赞(实体的赞):以某一个前缀开头,声明常量
public static final String PREFIX_ENTITY_LIKE = "like:entity";
//添加静态方法:传入变量拼接到常量中,得到完整 key(生成某个实体的赞)
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
}
在 service 包下创建 LikeService 类:
package com.example.demo.service;
import com.example.demo.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* 点赞业务组件
*/
@Service
public class LikeService {
@Autowired
private RedisTemplate redisTemplate;
//实现点赞的业务方法:传入参数 userId、entityType、entityId
public void like(int userId, int entityType, int entityId) {
//拼接存储的 key
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType,entityId);
//判断当前用户是否已经点赞(没点赞点赞,点赞了再次点取消):在 redis 存的是一个集合,判断 userId 是否在 redis 中即可
boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
if (isMember) {
redisTemplate.opsForSet().remove(entityLikeKey, userId);
} else {
redisTemplate.opsForSet().add(entityLikeKey, userId);
}
}
//查询某实体类点赞的数量:传入 entityType、entityId、判断 key 中有多少个 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) {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType,entityId);
return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
}
}
点赞是在帖子详情页面操作,是一个异步请求,整个页面不刷新,动态改变已赞的数量;在 controller 包下新建 LikeController 类
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.LikeService;
import com.example.demo.util.CommunityUtil;
import com.example.demo.util.HostHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.HashMap;
import java.util.Map;
/**
* 点赞请求
*/
@Controller
public class LikeController {
//调用点赞业务,注入 LikeService
private LikeService likeService;
//当前用户点赞,为了得到当前用户,注入 HostHolder
private HostHolder hostHolder;
//添加处理异步请求方法:声明访问路径、POST 请求、异步请求添加注解 @RequestMapping
@RequestMapping(path = "/like", method = RequestMethod.POST)
//点赞针对某个实体,传入 entityType、entityId 参数
public String like(int entityType, int entityId) {
//获取当前用户
User user = hostHolder.getUser();
//实现点赞:调用 LikeService
likeService.like(user.getId(), entityType, entityId);
//统计点赞数量、点赞状态返回页面,页面根据返回值做数量和状态显示
// 数量
long likeCount = likeService.findEntityLikeCount(entityType, entityId);
// 状态
int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
// 返回的结果(用 Map 封装)
Map map = new HashMap<>();
map.put("likeCount", likeCount);
map.put("likeStatus", likeStatus);
//返回页面
return CommunityUtil.getJSONString(0, null, map);
}
}
前端页面 discuss-detail.html 需要处理的是:对帖子点赞,评论点赞、回复点赞
在 static 下创建 discuss.js:
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);
}
}
);
}
打开 HomeController 类添加赞的数量:
@Autowired
private LikeService likeService;
@RequestMapping(path = "/index", method = RequestMethod.GET)//访问首页路径
public String getIndexPage(Model model, Page page) {
// 方法调用之前,SpringMVC会自动实例化Model和Page,并将Page注入Model.
// 所以,在thymeleaf中可以直接访问Page对象中的数据.
//加入分页功能
page.setRows(discussPostService.findDiscussPostRows(0));//设置总行数
page.setPath("/index");//访问路径
//只是查询到 userId,不是用户名,但是我们需要展示用户名
List list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
//遍历集合,针对每一个 DiscussPost 的 userId 查询到 user,再把数据组装放到新的集合返回给页面
//新建集合,能够封装 DiscussPost 和 User 对象
List
index.html 找到显示赞进行修改:
赞 11
@Autowired
private LikeService likeService;
//查询请求方法
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// 帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
model.addAttribute("post", post);
//第一种方法:在查询的时候使用关联查询 。优点:查询快;缺点:可能存在冗余、耦合
//第二种方法:先查出帖子数据,根据 id 调用 Userservice 查询 User,再通过 Model 将 User 发送给 模板,
// 这样模板得到了帖子,也得到了模板。优点:查询两次,没有冗余;缺点:查询慢
//在这里使用第二种情况,查询慢可以使用 Redis 来优化
// 作者
User user = userService.findUserById(post.getUserId());
// 把作者传给模板
model.addAttribute("user", user);
// 点赞数量
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);
.........
..........
if (commentList != null) {
for (Comment comment : commentList) {
// 评论VO:创建一个 Map,用来封装呈现给页面数据(也就是一个评论,将遍历的评论存入、将评论的作者存入)
Map commentVo = new HashMap<>();
// 评论存入
commentVo.put("comment", comment);
// 作者存入
commentVo.put("user", userService.findUserById(comment.getUserId()));
// 点赞数量
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);
..........
..........
if (replyList != null) {
for (Comment reply: replyList) {
Map replyVo = new HashMap<>();
// 回复
replyVo.put("reply", reply);
// 作者
replyVo.put("user", userService.findUserById(reply.getUserId()));
//评论是没有回复目标的,但是评论的评论也就是回复,才会有回复目标(给那个帖子回复),所以给回复添加回复目标
// 回复目标
User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
replyVo.put("target", target);
// 点赞数量
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);
.........
在 util 包下的 RedisKeyUtil 增加 key:
在 LikeService 类下重构点赞方法:
public void like(int userId, int entityType, int entityId, int entityUserId {
/*//拼接存储的 key
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
//判断当前用户是否已经点赞(没点赞点赞,点赞了再次点取消):在 redis 存的是一个集合,判断 userId 是否在 redis 中即可
boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
if (isMember) {
redisTemplate.opsForSet().remove(entityLikeKey, userId);
} else {
redisTemplate.opsForSet().add(entityLikeKey, userId);
}*/
//需要添加一个维度记录收到的赞的数量,一个业务中需要执行两次的更新操作需要保证事务性,redis保证事务(编程),重构代码
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
//添加实体 key
//添加 User key(需要的是被赞的人,传入实体作者参数)
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
//查询当前用户是否已经点赞
//查询放在事务之外(在事务过程中执行的所有命令不会立刻执行,而是把命令放入队列中,提交事务的时候统一提交,再执行)
boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);
//开启事务
operations.multi();
//执行两次修改操作:没点赞点赞(实体类),那么结果+1(User);点赞了再次点取消(实体类),那么结果-1(User)
if (isMember) {
operations.opsForSet().remove(entityLikeKey, userId);
operations.opsForValue().decrement(userLikeKey);
} else {
operations.opsForSet().add(entityLikeKey, userId);
operations.opsForValue().increment(userLikeKey);
}
//执行事务
return operations.exec();
}
});
}
//补充查询某个用户获得赞的数量:拼接 key,统计数量
public int findUserLikeCount(int userId) {
String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
return count == null ? 0 : count.intValue();
}
在 LikeController 类中修改方法:
@Controller
public class LikeController {
......
public String like(int entityType, int entityId, int entityUserId) {
//获取当前用户
User user = hostHolder.getUser();
//实现点赞:调用 LikeService
likeService.like(user.getId(), entityType, entityId, entityUserId);
......
}
}
修改前端点赞页面 discuss-detail.html:
赞 11
discuss-detail.js:
function like(btn, entityType, entityId, entityUserId) {
$.post(
CONTEXT_PATH + "/like",
{"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId},
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);
}
}
);
}
在 UserController 类中新增个人主页方法
//新增个人主页方法注入 LikeService
@Autowired
private LikeService likeService;
// 个人主页
@RequestMapping(path = "/profile/{userId}", method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
// 用户
model.addAttribute("user", user);
// 点赞数量
int likeCount = likeService.findUserLikeCount(userId);
model.addAttribute("likeCount", likeCount);
return "/site/profile";
}
处理模板 index.html (个人主页添加路径、帖子列表中每一个用户头像应该有一个超链接到主页中):
个人主页
最后处理 profile.html:
个人主页
......
......
......