第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;
    }

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

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

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

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

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

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

UserService

image-20220723093512824

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

image-20220723093622708

image-20220723093716305

image-20220723093817454

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值