项目理解(六)Redis分布式缓存及性能优化

目录
Redis实现点赞与关注
使用Redis优化登录模块


Redis实现点赞与关注

Redis:支持数据类型完善;使用简单;性能好;Redis是一款基于键值对的NoSQL数据库,对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值可以是字符串对象、列表对象、哈希对象、集合对象或有序集合对象:String, hashes, Lists, Sets, Sorted Sets.

Redis将所有的数据都存放在内存中,所以他的读写性能十分惊人;(性能

同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。(安全性

其中:快照的形式:RDB的形式,直接把内存的数据存到硬盘上,优点是数据体积小,如果想从硬盘恢复到内存速度快;缺点是比较耗时,做存储的时候可能会产生阻塞,此时在处理其他的业务会影响其他的业务。不适合实时去做,可以几小时做一次的那种;日志的方式:AOF:每执行一个Redis命令就以日志的形式存下来,可以做到实时的存;但是以不断的追加的形式存的,体积较大,占用磁盘空间,且恢复的时候是把存的命令从头到尾再跑一遍,恢复的速度较慢;

访问Redis:

redisTemplate.opsForValue()
redisTemplate.opsForHash()
redisTemplate.opsForList()
redisTemplate.opsForSet()
redisTemplate.opsForZSet()

@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

可以参考Bean重写序列化:https://blog.csdn.net/lichuangcsdn/article/details/80866253

@Configuration
public class RedisConfig {

/*
* @Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一
* 个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext
* 或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
* */
//实际上,Spring很好的实现了泛型依赖注入
// https://blog.csdn.net/f641385712/article/details/84679147

    /*
    * StringRedisTemplate与RedisTemplate的区别
    两者的关系是StringRedisTemplate继承RedisTemplate。
    两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,
    RedisTemplate只能管理RedisTemplate中的数据。
    默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
    StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
    RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
    * */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        //定义一个bean,方法名为redisTemplate;当声明了这个bean方法,
        // 参数为RedisConnectionFactory,参数也是一个Bean,它会被自动注入进来,被容器装配
        //都配好后,就可以通过redisTemplate访问redis了
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(factory);//template具有了访问数据库的能力

        //写Java程序得到Java类型数据是要存到数据库中,所以要指定一种数据转化的方式

        // 设置key的序列化方式
        template.setKeySerializer(RedisSerializer.string());
        // 设置value的序列化方式
        template.setValueSerializer(RedisSerializer.json());
        // value本身为hash时:设置hash的key的序列化方式
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置hash的value的序列化方式
        template.setHashValueSerializer(RedisSerializer.json());

        template.afterPropertiesSet();//触发生效
        return template;
    }

}

点赞点踩:访问非常频繁;就是一个数量,这样的数据存放到关系型数据的表里,也不方便存储;

RedisKeyUtil类:定义String类型的Key

public class RedisKeyUtil {

    private static final String SPLIT = ":";
    private static final String PREFIX_ENTITY_LIKE = "like:entity";
    private static final String PREFIX_USER_LIKE = "like:user";
    private static final String PREFIX_FOLLOWEE = "followee";
    private static final String PREFIX_FOLLOWER = "follower";
    private static final String PREFIX_KAPTCHA = "kaptcha";
    private static final String PREFIX_TICKET = "ticket";
    private static final String PREFIX_USER = "user";
    private static final String PREFIX_UV = "uv";
    private static final String PREFIX_DAU = "dau";
    private static final String PREFIX_POST = "post";

    //定义一个静态方法
    // 某个实体的赞
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }

    //某个用户所获得的赞的总数,这里是key的格式,value为总数
    //like:user:userId->int
    public static String getUserLikeKey(int userId){
        return PREFIX_USER_LIKE + SPLIT + userId;
    }

    // 某个用户关注的实体:有序集合,以时间为序
    // followee:userId:entityType -> zset(entityId,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;
    }

    // 登录验证码:  kaptcha:owner  其中ower为用户的一个临时凭证
    //每一个key都不同,对应的value为验证码
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

    // 登录的凭证:   kaptcha:ticket
    public static String getTicketKey(String ticket) {
        return PREFIX_TICKET + SPLIT + ticket;
    }

    // 用户  kaptcha:userId
    public static String getUserKey(int userId) {
        return PREFIX_USER + SPLIT + userId;
    }
    // 单日UV
    public static String getUVKey(String date) {
        return PREFIX_UV + SPLIT + date;
    }

    // 区间UV
    public static String getUVKey(String startDate, String endDate) {
        return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
    }

    // 单日活跃用户
    public static String getDAUKey(String date) {
        return PREFIX_DAU + SPLIT + date;
    }

    // 区间活跃用户
    public static String getDAUKey(String startDate, String endDate) {
        return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
    }

    //@@@@@@@@@@@@@@@@@@@添加 帖子分数
    public static String getPostScoreKey() {//key就是固定的字符串,声明为帖子分数;在value中会加入帖子id
        return PREFIX_POST + SPLIT + "score";
    }


}

LikeService与FollowService:

@Service
public class LikeService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 点赞
//    public void like(int userId, int entityType, int entityId) {
//        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
//        boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
//        if (isMember) {
//            redisTemplate.opsForSet().remove(entityLikeKey, userId);
//        } else {
//            redisTemplate.opsForSet().add(entityLikeKey, userId);
//        }
//    }

    // =========点赞代码重构:在用户个人主页,添加用户的收到的赞的总数=====================
    public void like(int userId, int entityType, int entityId, int entityUserId) {
        //需要实现事务
        //查询一定要放在事务的过程之外;Redis的事务比较特殊,如果将查询放在事务内,
        // 它不会立即得到结果,在事务过程中的所有命令没有立即执行,而是把这些命令放到了队列中,
        // 当提交事务时统一提交

        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
                String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);//这里需要得到被赞的用户的id,从而得到用户key
                boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);//查询

                operations.multi();//开启事务
                if(isMember){
                    operations.opsForSet().remove(entityLikeKey, userId);
                    operations.opsForValue().decrement(userLikeKey);//对user为key的处理
                }else {
                    operations.opsForSet().add(entityLikeKey, userId);
                    operations.opsForValue().increment(userLikeKey);//对user为key的处理
                }

                operations.exec();//提交事务

                return null;
            }
        });
    }

    //添加:查询某个用户获得的总赞
    public int findUserLikeCount(int userId){
        String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);//这里需要得到被赞的用户的id,得到uerKey
        Integer count = (Integer)redisTemplate.opsForValue().get(userLikeKey);
        return count == null ? 0 : count.intValue();
    }

    // 查询某实体点赞的数量
    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;
    }
}
@Service
public class FollowService implements CommunityConstant {

    @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);//关注的目标的key
                String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);//粉丝的key

                operations.multi();//开始事务

                //对应的key存value,score,其中score为时间
                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();
            }
        });
    }

    // 查询关注的实体的数量
    public long findFolloweeCount(int userId, int entityType) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        return redisTemplate.opsForZSet().zCard(followeeKey);
    }

    // 查询实体的粉丝的数量
    public long findFollowerCount(int entityType, int entityId) {
        String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
        return redisTemplate.opsForZSet().zCard(followerKey);
    }

    // 查询当前用户是否已关注该实体
    public boolean hasFollowed(int userId, int entityType, int entityId) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
    }

    //查询某用户关注的人findFollowees
    public List> findFollowees(int userId, int offset, int limit) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
        Set targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, limit + offset - 1);
        if (targetIds == null) {
            return null;
        }
        List> list = new ArrayList<>();
        for (Integer targetId : targetIds) {//由id查询用户
            Map map = new HashMap<>();
            User user = userService.findUserById(targetId);
            map.put("user", user);
            Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);//查询分数:当时使用时间转化而来的(ms)
            map.put("followTime", new Date(score.longValue()));
            list.add(map);
        }
        return list;
    }
    //查询某用户的粉丝列表
    public List> findFollowers(int userId, int offset, int limit){
        String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
        Set targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset+limit-1);
        if(targetIds == null){
            return null;
        }

        List> list = new ArrayList<>();
        for(Integer targetId : targetIds){
            Map 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;
    }
}

LikeController与FollowController:

    @RequestMapping(path = "/like", method = RequestMethod.POST)
    @ResponseBody
    public String like(int entityType, int entityId, int entityUserId, int postId) {
        //点赞用异步请求来实现;在前端页面,点击超链接访问服务器;需要用js方法来实现点赞提交请求的逻辑
        //前端页面discuss_detail.html:οnclick="|like(this,1,${post.id},${post.userId});|"
        //discuss.js: function like(btn, entityType, entityId)
        //===========添加kafka重构:在传入参数的时候添加了一个参数:postId================
        // {"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId,"postId":postId}

        User user = hostHolder.getUser();

        // 点赞
        likeService.like(user.getId(), entityType, entityId, entityUserId);

        // 数量
        long likeCount = likeService.findEntityLikeCount(entityType, entityId);
        // 状态
        int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
        // 返回的结果
        Map map = new HashMap<>();
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);

        //添加kafka系统通知部分:触发点赞事件
        //赞的时候发布通知;取消赞的时候就不用发布通知了
        if(likeStatus == 1){//点赞
            Event event = new Event().setTopic(TOPIC_LIKE)
                    .setEntityId(entityId)
                    .setEntityType(entityType)
                    .setUserId(hostHolder.getUser().getId())
                    .setEntityUserId(entityUserId)
                    .setData("postId", postId);

            eventProducer.fireEvent(event);
        }

        // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@计算帖子分数
        if(entityType == ENTITY_TYPE_POST) {
            // 计算帖子分数
            String redisKey = RedisKeyUtil.getPostScoreKey();
            redisTemplate.opsForSet().add(redisKey, postId);
        }

        return CommunityUtil.getJSONString(0, null, map);
    }
@Controller
public class FollowController implements CommunityConstant {

    @Autowired
    private FollowService followService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private UserService userService;

    @Autowired
    private EventProducer eventProducer;

    //关注,传入参数:实体类型,实体id
    //异步的操作,局部更新
    @RequestMapping(path = "/follow", method = RequestMethod.POST)
    @ResponseBody
    public String follow(int entityType, int entityId) {
        User user = hostHolder.getUser();

        followService.follow(user.getId(), entityType, entityId);

        //添加kafka系统通知部分:触发点赞事件
        Event event = new Event().setTopic(TOPIC_FOLLOW)
                .setUserId(hostHolder.getUser().getId())
                .setEntityId(entityId)
                .setEntityType(entityType)
                .setEntityUserId(entityId);//只能关注人,所以实体UserId就是entityId

        eventProducer.fireEvent(event);

        return CommunityUtil.getJSONString(0, "已关注!");
    }

    //取消关注,传入参数:实体类型,实体id
    @RequestMapping(path = "/unfollow", method = RequestMethod.POST)
    @ResponseBody
    public String unfollow(int entityType, int entityId) {
        User user = hostHolder.getUser();

        followService.unfollow(user.getId(), entityType, entityId);

        return CommunityUtil.getJSONString(0, "已取消关注!");
    }

    //显示某个用户的关注列表
    @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> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
        if (userList != null) {//关注列表
            for (Map 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> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
        if (userList != null) {//粉丝列表
            for (Map 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);
    }
}

在帖子详情页面添加:

                //添加点赞有关信息
                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);

用户个人主页:(个人信息)

    //个人主页:
    //个人主页
    @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("该用户不存在");
        }
        //用户(MySQL中)
        model.addAttribute("user", user);
        //用户获赞的数量(Redis中)
        int likeCount = likeService.findUserLikeCount(userId);
        model.addAttribute("likeCount", likeCount);


        //关注的数量
        long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
        model.addAttribute("followeeCount", followeeCount);
        //粉丝的数量
        long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
        model.addAttribute("followerCount", followerCount);

        //是否已关注
        boolean hasFollowed = false;
        if (hostHolder.getUser() != null) {
            hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
        }
        model.addAttribute("hasFollowed", hasFollowed);
        return "/site/profile";
    }

使用Redis优化登录模块

使用Redis存储验证码:验证码需要频繁的访问与刷新,对性能要求较高。验证码不需要永久保存,通常在很短的时间后就会失效。分布式部署,存在session共享问题

使用Redis存储登陆凭证:之前的MySQL存储的Login_ticket登陆信息不需要用了(仅首次登录时,生成登录凭证并加载到Redis缓存中),直接用Redis重构;处理每次请求时,都要查询用户的登陆凭证,访问的频率非常高;

使用Redis缓存用户信息:使用Redis缓存用户,并设置一段时间过期,符合实际业务;实际的User信息也同时还存储在MySQL中。处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。

缓存用户信息:1、查询用户信息时,先尝试在缓存中值,取到了就使用,取不到就在Redis中初始化这个user

2、当更改用户数据,如修改图片,修改密码等;此时需要更新缓存:可以直接把这个用户数据从缓存中删掉,下次访问时,再重新插入一次;如果直接更新数据,一方面时麻烦,还可能有并发的问题。

验证码:

//=======用Redis重构验证码代码============================================
    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)///路径kaptcha传入到login.html登录页面中了
    public void getKaptcha(HttpServletResponse response) {
        //生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);

        //将验证码存入session
//        session.setAttribute("kaptcha", text);

        //验证码的归属问题:
        String kaptchaOwner = CommunityUtil.generateUUID();
        Cookie cookie  = new Cookie("kaptchaOwner", kaptchaOwner);
        cookie.setMaxAge(60);//60s
        cookie.setPath(contextPath);//整个项目都有效
        response.addCookie(cookie);
        //将验证码存入Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey,text,60, TimeUnit.SECONDS);//直接同时设置了过期时间


        //将图片输出给浏览器
        response.setContentType("image/png");
        //response做响应;获取输出流
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }

登录重构:

    //===========================Redis重构登录============================
    @RequestMapping(path = "/login", method = RequestMethod.POST)//路径相同但为post方法
    public String login(String username, String password, String code, boolean rememberme, Model model,
                        @CookieValue("kaptchaOwner") String kaptchaOwner, HttpServletResponse response) {
        //从Redis来取验证码
        String kaptcha = null;
        if (StringUtils.isNotBlank(kaptchaOwner)) {
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
        }


        if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute("codeMsg", "验证码不正确!");
            return "/site/login";//返回登录页面
        }

        // 检查账号,密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map map = userService.login(username, password, expiredSeconds);
        if (map.containsKey("ticket")) {
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            cookie.setPath(contextPath);
            cookie.setMaxAge(expiredSeconds);
            response.addCookie(cookie);
            return "redirect:/index";//登录成功返回首页
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";//返回登录页面
        }
    }

userService中登录的业务逻辑处理,Redis缓存ticket登录凭证:

    //==========登录的业务逻辑处理=============================
    public Map login(String username, String password, int expiredSeconds) {
        Map map = new HashMap<>();
        //空值处理
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }
        // 验证账号
        User user = userMapper.selectByName(username);
        if (user == null) {
            map.put("usernameMsg", "该账号不存在!");
            return map;
        }

        // 验证状态
        if (user.getStatus() == 0) {
            map.put("usernameMsg", "该账号未激活!");
            return map;
        }

        // 验证密码
        password = CommunityUtil.md5(password + user.getSalt());
        if (!user.getPassword().equals(password)) {
            map.put("passwordMsg", "密码不正确!");
            return map;
        }

        // 生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());//user表中的id
        loginTicket.setTicket(CommunityUtil.generateUUID());//凭证是一个随机生成的字符串
        loginTicket.setStatus(0);//status:0表示有效;1表示无效
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insertLoginTicket(loginTicket);

        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());//由ticket生成Redis中的key
        redisTemplate.opsForValue().set(redisKey, loginTicket);//设置value
        //loginTicket为一个对象,这里Redis会将此对象序列化为字符串格式保存

        map.put("ticket", loginTicket.getTicket());
        return map;
    }
    //重构的登出
    public void logout(String ticket) {
        String redisKey = RedisKeyUtil.getTicketKey(ticket);//由ticket生成Redis中的key
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        //由Object强制转化为LoginTicket

        loginTicket.setStatus(1);//将登录状态设置为1:无效
        redisTemplate.opsForValue().set(redisKey, loginTicket);//再次存入;
        //整个过程相当于更新了Redis中的ticket的status
        // 每一个用户登录都对应一个ticket
    }

每次请求时由ticket查询用户:(拦截器,用户登录与页面访问见系列文章https://blog.csdn.net/liuzewei2015/article/details/93304555)

    //Redis重构:通过ticket得到LoginTicket表对象
    public LoginTicket findLoginTicket(String ticket) {
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

Redis缓存用户信息:

    //Redis重构findUserById
    public User findUserById(int id) {
        User user = getCache(id);
        if(user == null){
            user = initCache(id);
        }
        return user;
    }

    //1、优先从缓存中取值
    //2、取不到时初始化缓存数据
    //3、数据变更是清除缓存数据

    // 1.优先从缓存中取值
    private User getCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2.取不到时初始化缓存数据
    private User initCache(int userId) {
        User user = userMapper.selectById(userId);//先从MySQL中查到
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);//一个小时
        return user;
    }

    // 3.数据变更时清除缓存数据
    private void clearCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }

 

 

你可能感兴趣的:(后端开发与性能优化)