• 第4章 Redis,一站式高性能存储方案(下)


    4.5 关注、取消关注

    image-20220722073112210

    关注、取消关注

    RedisKeyUtil 里增加两个方法去生成key

    因为关注、取关比较高频,所以我们存到key里,所以我们在 RedisKeyUtil 里增加两个方法去生成key

    private static final String PREFIX_FOLLOWEE = "followee";
    private static final String PREFIX_FOLLOWER = "follower";
        // 某个用户关注的实体
        // followee:userId:entityType -> zset(entityId,now)    now代表以当前时间作为分数排序
        public static String getFolloweeKey(int userId, int entityType) {
            return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
        }
    
        // 某个实体拥有的粉丝
        // follower:entityType:entityId -> zset(userId,now)
        public static String getFollowerKey(int entityType, int entityId) {
            return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20220722094852771

    业务层(Service)

    FollowService处理关注、取消关注业务:

    @Service
    public class FollowService {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Autowired
        private UserService userService;
    
        // 关注
        public void follow(int userId, int entityType, int entityId) {
            redisTemplate.execute(new SessionCallback() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
                    String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
    
                    operations.multi();
    
                    operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
                    operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());
    
                    return operations.exec();
                }
            });
        }
        // 取消关注
        public void unfollow(int userId, int entityType, int entityId) {
            redisTemplate.execute(new SessionCallback() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
                    String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
    
                    operations.multi();
    
                    operations.opsForZSet().remove(followeeKey, entityId);
                    operations.opsForZSet().remove(followerKey, userId);
    
                    return operations.exec();
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    image-20220722095248414

    在常量接口CommunityConstant中定义一个常量表示实体类型是User

    image-20220722095403999

    表现层(Controller和themeleaf模板)

    image-20220722095600523

    然后是处理展示个人主页profile.html的模板

    image-20220722095844724

    然后是处理上面关注按钮的profile.js

    image-20220722100114337

    在用户主页显示该用户关注数、粉丝数

    在主页显示正确的状态和数量,主页是通过UserController访问的

    image-20220722101644819

    image-20220722101747941

    展示个人主页的模板profile.html:

    image-20220722102036863

    4.6 关注列表、粉丝列表

    image-20220722153800478

    因为关注列表粉丝列表开发过程很相似,所以这里一起开发

    业务层(Service)

    FollowService

    实现接口

    注入

    		@Autowired
    		private UserService userService;
       // 查询某用户关注的人
        public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {
            String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
            // Set是一个接口,但是后面的实现类是有序的                                      起始索引      结束索引
            Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1);
    
            if (targetIds == null) {
                return null;
            }
    
            List<Map<String, Object>> list = new ArrayList<>();
            for (Integer targetId : targetIds) {
                Map<String, Object> map = new HashMap<>();
                User user = userService.findUserById(targetId);
                map.put("user", user);
                Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);//score当是存的是毫秒数,可以还原回日期
                map.put("followTime", new Date(score.longValue()));
                list.add(map);
            }
    
            return list;
        }
    
        // 查询某用户的粉丝
        public List<Map<String, Object>> findFollowers(int userId, int offset, int limit) {
            String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
            Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);
    
            if (targetIds == null) {
                return null;
            }
    
            List<Map<String, Object>> list = new ArrayList<>();
            for (Integer targetId : targetIds) {
                Map<String, Object> map = new HashMap<>();
                User user = userService.findUserById(targetId);
                map.put("user", user);
                Double score = redisTemplate.opsForZSet().score(followerKey, targetId);
                map.put("followTime", new Date(score.longValue()));
                list.add(map);
            }
    
            return list;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    image-20220722173857277

    image-20220722174012216

    表现层

    FollowController

        @Autowired
        private UserService userService;
    
       @RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET)
        public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
            User user = userService.findUserById(userId);
            if (user == null) {
                throw new RuntimeException("该用户不存在!");
            }
            model.addAttribute("user", user);
    
            page.setLimit(5);
            page.setPath("/followees/" + userId);
            page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));
    
            List<Map<String, Object>> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
            if (userList != null) {
                for (Map<String, Object> map : userList) {
                    User u = (User) map.get("user");
                    // 判断当前用户是否关注了该人
                    map.put("hasFollowed", hasFollowed(u.getId()));
                }
            }
            model.addAttribute("users", userList);
    
            return "/site/followee";
        }
    
        @RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET)
        public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
            User user = userService.findUserById(userId);
            if (user == null) {
                throw new RuntimeException("该用户不存在!");
            }
            model.addAttribute("user", user);
    
            page.setLimit(5);
            page.setPath("/followers/" + userId);
            page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));
    
            List<Map<String, Object>> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
            if (userList != null) {
                for (Map<String, Object> map : userList) {
                    User u = (User) map.get("user");
                    // 判断当前用户是否关注了该人
                    map.put("hasFollowed", hasFollowed(u.getId()));
                }
            }
            model.addAttribute("users", userList);
    
            return "/site/follower";
        }
    
        private boolean hasFollowed(int userId) {
            if (hostHolder.getUser() == null) {
                return false;
            }
    
            return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    image-20220722174118509

    image-20220722174216868

    image-20220722174412567

    接下来就该处理themeleaf模板了

    首先我们要处理 profile.html 页面,因为我们是通过这个页面跳转到关注列表粉丝列表

    image-20220722163723590

    然后处理关注页面followee.html

    image-20220722174602995

    image-20220722174654052

    image-20220722174806220

    image-20220722174935848

    然后是粉丝页面follower.html

    image-20220722175119568

    image-20220722175212535

    image-20220722175327429

    image-20220722175446736

    4.7 优化登录模块

    image-20220723072550670

    1.重构: 使用Redis存储验证码

    RedisKeyUtil

    首先要在redis存验证码,所以我们现在 RedisKeyUtil 里面加方法定义key

    private static final String PREFIX_KAPTCHA = "kaptcha";             // 验证码
    // 存到redis里的验证码的key        参数表示用户临时的凭证(不是登录凭证),很快过期,为了在验证码时表示这个用户
    public static String getKaptchaKey(String owner) {
      return PREFIX_KAPTCHA + SPLIT + owner;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220723081232046

    LoginController:

    image-20220723081344089

    image-20220723082007652

    image-20220723082522443

    2. 重构:使用Redis存储登录凭证

    RedisKeyUtil:

    redis存登录凭证,所以我们现在 RedisKeyUtil 里面加方法定义key

    private static final String PREFIX_TICKET = "ticket";               // 登录凭证
    // 存的redis的登录凭证的key        把登录凭证传进来
    public static String getTicketKey(String ticket) {
      return PREFIX_TICKET + SPLIT + ticket;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220723090145700

    LoginTicketMapper:

    然后我们要使用reid存储凭证,所以我们可以把之前的LoginTicketMapper废弃掉,在类上加上 @Deprecated 表示不推荐使用

    image-20220723090243921

    UserService:

    UserService里重构一下用到LoginTicketMapper的地方

    image-20220723090757396

    image-20220723090958492

    image-20220723091224951

    3. 重构:使用Redis缓存用户信息

    RedisKeyUtil:

    redis存缓存信息,所以我们现在 RedisKeyUtil 里面加方法定义key

    private static final String PREFIX_USER = "user";
    
    // 用户
    public static String getUserKey(int userId) {
      return PREFIX_USER + SPLIT + userId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    UserService

    image-20220723093512824

    然后我们还需要在UserService里修改一些方法

    image-20220723093622708

    image-20220723093716305

    image-20220723093817454

  • 相关阅读:
    【算法leetcode】1051. 高度检查器(rust和go)
    【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具
    图像识别与处理学习笔记(二)图像增强之频率域处理
    2022就业季!一起来谈谈对spring的认知和理解
    【C++】认识类和对象
    PreMaint CMS系统:数字化驱动起重机远程管理的智能未来
    JAVA深化篇_38—— UDP通信的实现和项目案例
    发个地区和对应的价格方案
    Linux【vim】
    【算法基础】分解质因数
  • 原文地址:https://blog.csdn.net/qq_50313418/article/details/126397998