10.点赞 + 我收到的赞

1.点赞 

10.点赞 + 我收到的赞_第1张图片

  • 点赞:支持对帖子、评论点赞;第1次点赞,第2次取消点赞
  • 首页点赞数量:统计帖子的点赞数量
  • 详情页点赞数量:统计点赞数量、显示点赞状态

1.1 生成 redis 工具类

将数据存入到 redis 中,以 key 为关键,使用 key 编程,所以为了使 key 能反复利用,给 redis 生成一个工具类(在 util 包下创建 RedisKeyUtil 类):

  • 提供静态方法访问即可
  • key 使用 :分开:声明常量
  • 存入帖子或者评论的赞(实体的赞):以某一个前缀开头,声明常量
  • 添加静态方法:传入变量拼接到常量中,得到完整 key(生成某个实体的赞)
  • 传入变量:传入实体类型、id
  • 拼接:like:entity:entityType:entityId -> set(userId)
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;
    }
}

1.2 开发点赞业务组件

在 service 包下创建 LikeService 类:

  • 将数据存入 redis 中,注入 RedisTemplate
  • 实现点赞的业务方法:传入参数 userId、entityType、entityId
  • 实现:拼接存储的 key
  • 判断当前用户是否已经点赞(没点赞点赞,点赞了再次点取消):在 redis 存的是一个集合,判断 userId 是否在 redis 中即可 
  • 再添加 查询某实体类点赞的数量 方法:传入 entityType、entityId、判断 key 中有多少个 userId 就是有多少点赞数量
  • 再添加 查询某人对某实体的点赞状态 的方法:传入参数 userId、entityType、entityId
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;
    }

}

1.3 处理表现层

点赞是在帖子详情页面操作,是一个异步请求,整个页面不刷新,动态改变已赞的数量;在 controller 包下新建 LikeController 类

  • 调用点赞业务,注入 LikeService
  • 当前用户点赞,为了得到当前用户,注入 HostHolder
  • 添加处理异步请求方法:声明访问路径、POST 请求、异步请求添加注解 @RequestMapping
  • 点赞针对某个实体,传入 entityType、entityId 参数
  • 获取当前用户
  • 实现点赞:调用 LikeService
  • 统计点赞数量、点赞状态返回页面,页面根据返回值做数量和状态显示 
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 需要处理的是:对帖子点赞,评论点赞、回复点赞

                        
发布于 2019-04-15 15:32:18
                            
发布于 2019-04-15 15:32:18
                                        

在 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);
            }
        }
    );
}

10.点赞 + 我收到的赞_第2张图片

1.4 处理显示首页赞的数量

打开 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> discussPosts = new ArrayList<>();
        if (list != null) {
            for (DiscussPost post : list) {
                Map map = new HashMap<>();//最终结果放到 Map 中,实例化 Map
                map.put("post", post);
                User user = userService.findUserById(post.getUserId());//得到 user 的完整数据
                map.put("user", user);

                //查询赞的数量
                long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
                map.put("likeCount", likeCount);

                discussPosts.add(map);
            }
        }
        model.addAttribute("discussPosts", discussPosts);
        return "/index";//返回模板路径
    }

index.html 找到显示赞进行修改:

  • 11
  • 1.5 处理帖子详情页面赞的数量和状态

    • 查询帖子的赞
        @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);
    
                            .........

    10.点赞 + 我收到的赞_第3张图片

    2.我收到的赞

    • 重构点赞功能:以用户为 key,记录点赞数量;increment(key)、decrement(key)
    • 开发个人主页:以用户为 key,查询点赞数量

    在 util 包下的 RedisKeyUtil 增加 key:

    • 新增前缀 key ,声明常量
    • 添加某个用户的赞的方法
    • 拼接:like:user:userId -> int
       

    2.1 改造点赞的业务方法

    在 LikeService 类下重构点赞方法:

    • 需要添加一个维度记录收到的赞的数量,一个业务中需要执行两次的更新操作需要保证事务性,redis保证事务(编程),重构代码
    • 添加实体 key
    • 添加 User key(需要的是被赞的人,传入实体作者参数)
    • 查询当前用户是否已经点赞
    • 查询放在事务之外(在事务过程中执行的所有命令不会立刻执行,而是把命令放入队列中,提交事务的时候统一提交,再执行)
    •  开启事务
    • 执行两次修改操作:没点赞点赞(实体类),那么结果+1(User);点赞了再次点取消(实体类),那么结果-1(User)
    • 执行事务
    • 补充查询某个用户获得赞的数量:拼接 key,统计数量 
        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();
        }

    2.2 重构表现层

    在 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);
                }
            }
        );
    }

    2.3 在主页显示

    在 UserController 类中新增个人主页方法

    • 设置返回路径,可以是任意用户主页
    • 使用 @PathVariable 解析
    • 给页面携带参数,添加 Model 参数
    • 查找当前用户,然后传给页面
    • 查询用户点赞数量(注入 LikeService),然后传给页面,最后返回模板
        //新增个人主页方法注入 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:

    
    
    
    	
    	
    	
    	
    	
    	个人主页
    
    
    	
    
    		......
            ......
            ......
    	
    	
    	
    
    

    10.点赞 + 我收到的赞_第4张图片

    你可能感兴趣的:(论坛系统,个人论坛系统,spring,boot,spring,mvc,redis,mybatis,kafka,elasticsearch)