四、Redis,一站式高性能存储方案

、Redis,一站式高性能存储方案

四、Redis,一站式高性能存储方案

4.1、spring整合redis

  • 导入依赖:spring-boot-starter-data-redis

    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-redis</artifactId>
    		</dependency>
    
  • 配置redis

    • 配置数据库参数
    # RedisProperties
    spring.redis.database=11
    spring.redis.host=localhost
    spring.redis.port=6379
    
    • 编写配置类,构造RedisTemplate

      config.RedisConfig.java

      @Configuration
      public class RedisConfig {
      
          @Bean
          public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
              RedisTemplate<String, Object> template = new RedisTemplate<>();
              template.setConnectionFactory(factory);
      
              // 设置key的序列化方式
              template.setKeySerializer(RedisSerializer.string());
              // 设置value的序列化方式
              template.setValueSerializer(RedisSerializer.json());
              // 设置hash的key的序列化方式
              template.setHashKeySerializer(RedisSerializer.string());
              // 设置hash的value的序列化方式
              template.setHashValueSerializer(RedisSerializer.json());
      
              template.afterPropertiesSet();
              return template;
          }
      }
      
  • 访问redis

    • redisTemplate.opsForValue()
    • redisTemplate.opsForHash()
    • redisTemplate.opsForList()
    • redisTemplate.opsForSet()
    • redisTemplate.opsForZSet()
        // 多次访问同一个key
        @Test
        public void testBoundOperations() {
            String redisKey = "test:count";
            BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
            operations.increment();
            System.out.println(operations.get());
        }
    
        // 编程式事务(用得多,事务中间查询没用
        @Test
        public void testTransactional() {
            Object obj = redisTemplate.execute(new SessionCallback() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    String redisKey = "test:tx";
    
                    operations.multi();
    
                    operations.opsForSet().add(redisKey, "zhangsan");
                    operations.opsForSet().add(redisKey, "lisi");
                    operations.opsForSet().add(redisKey, "wangwu");
    
                    System.out.println(operations.opsForSet().members(redisKey));//[]
    
                    return operations.exec();
                }
            });
            System.out.println(obj);//[1, 1, 1, [zhangsan, lisi, wangwu]]
        }
    

4.2、点赞

四、Redis,一站式高性能存储方案

(1)service层

生成key的工具类RedisKeyUtil

直接写业务层,调用redisTemplate往redis中读取和存入数据即可,面向key编程,为了能够复用key,先创建一个可生成key的工具类: RedisKeyUtil,有些key是静态常量的,而有些key是动态的,由方法加入更明确的值并返回。

RedisKeyUtil.java:

实体的赞使用set集合,里面放入的是多个用户的id。set(userId), 既可以获取当前实体点赞数量,又可以获取点赞的用户,能够最大程度地满足各种各样需求的变化。

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

    // 某个实体的赞.entityType:被点赞的实体:帖子1、评论2、回复3;
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }
}

LikeService

@Service
public class LikeService {
    @Autowired
    private RedisTemplate redisTemplate;
    //点赞
    //userID:谁点的赞;entityType:被点赞的实体:帖子1、评论2、回复3;
    public void like(int userId, int entityType, int entityId){
        //先拼好两个key
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        //判断某个用户是否对某个实体点过赞
        boolean isMember = operations.opsForSet().isMember(entityLikeKey,userId);
        if(isMember){
            operations.opsForSet().remove(entityLikeKey, userId);
        }
        else{
            operations.opsForSet().add(entityLikeKey, 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;
    }
}

(2)controller层

LikeController

异步请求:

@Controller
public class LikeController implements CommunityConstant {
    @Autowired
    private LikeService likeService;
 
    @Autowired
    private HostHolder hostHolder;
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @RequestMapping(path="/like",method = RequestMethod.POST)
    @ResponseBody
    public String like(int entityType, int entityId){
        //获取当前用户
        User user = hostHolder.getUser();
        //点赞
        likeService.like(user.getId(), entityType, entityId);
        //获取点赞总数量
        long likeCount = likeService.findEntityLikeCount(entityType,entityId);
        //目前是否被点赞的状态
        int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
        //返回的结果
        Map<String, Object> map = new HashMap<>();
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);
        
        //返回结果
        return CommunityUtil.getJSONString(0,null, map);
    }
}

discuss-detail.html:

可以给三个地方点赞:给帖子点赞、给评论点赞、给回复点赞

HomeController

完善在首页显示帖子点赞的数量

    @Autowired
    private LikeService likeService;
	
	@RequestMapping(path = "/index", method = RequestMethod.GET)
    public String getIndexPage(Model model, Page page) {
        //.....
        long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
        map.put("likeCount", likeCount);
        //.....
    }

index.html

DiscussPostController

方法getDiscussPost获取帖子详情中

(1) 增加获取帖子点赞数量的处理代码

//点赞数量
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST,discussPostId);
model.addAttribute("likeCount", likeCount);
//点赞状态(当前用户是否对帖子点过赞),如果用户未登录的话,也是显示未赞的状态
//likeStatus 为0,否则再去查询
int likeStatus = 0;
if(hostHolder.getUser() != null){
    likeStatus = likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);

(2) 对评论做相同的处理

likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT,comment.getId());
commentVo.put("likeCount", likeCount);
//点赞状态
likeStatus = 0;
if(hostHolder.getUser() != null){
    likeStatus = likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
}
commentVo.put("likeStatus", likeStatus)

(3) 对回复做相同的处理

likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT,reply.getId());
replyVo.put("likeCount", likeCount);
//点赞状态
likeStatus = 0;
if(hostHolder.getUser() != null){
    likeStatus = likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());
}
replyVo.put("likeStatus", likeStatus);

discuss-detail.html

4.3、我收到的赞

四、Redis,一站式高性能存储方案

在用户点赞的是时候,增加一个维度。

RedisKeyUtil

//某个用户的赞
private static final String PREFIX_USER_LIKE = "like:user";

//like:user:userId -> int
public static String getUserLikeKey(int useId){
    return PREFIX_USER_LIKE + SPLIT + useId;
}

LikeService(涉及事务)

涉及到了多个操作,使用事务,另外,查询要放到事务之外,如果放到事务之内,不会立马就返回查询结果

    //userID:谁点的赞;entityType:被点赞的实体:帖子1、评论2、回复3;entityID:实体id
	//entityUserId:被赞的用户id
    public void like(int userId, int entityType, int entityId, int entityUserId){
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {

                String entityLikeKey = 
                    RedisKeyUtil.getEntityLikeKey(entityType, entityId);
                //被赞的用户id的key
                String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
                //判断某个用户是否对某个实体点过赞
                boolean isMember =
                    operations.opsForSet().isMember(entityLikeKey,userId);
                operations.multi();//开启事务:放在查询之后
                if(isMember){//取消赞
                    operations.opsForSet().remove(entityLikeKey, userId);
                    operations.opsForValue().decrement(userLikeKey);
                }else{//点赞
                    operations.opsForSet().add(entityLikeKey, userId);
                    if(operations.opsForValue().get(userLikeKey) == null){
                        operations.opsForValue().set(userLikeKey, 0);
                    }
                    operations.opsForValue().increment(userLikeKey);
                }
                return operations.exec();//表示的是提交事务
            }
        });
    }

额外添加一个查询某个用户获得的赞的数量的方法:

    //查询某个用户获得的赞
	public int findUserLikeCount(int userId){
        String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
        Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
        return count == null ? 0: count.intValue();
    }

LikeController

需要额外添加 一个entityUserId输入参数:

@RequestMapping(path="/like",method = RequestMethod.POST)
@ResponseBody
public String like(int entityType, int entityId, int entityUserId){
    //当前用户点赞
    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<String, Object> map = new HashMap<>();
    map.put("likeCount", likeCount);
    map.put("likeStatus", likeStatus);
    return CommunityUtil.getJSONString(0,null, map);
}

discuss-detail.html 传入entityUserId

UserController

显示个人主页时需要把用户收到的赞的总数量放到Model之中去。

个人主页不仅仅可以显示当前用户的主页,任意用户的主页也可以查看,因此路径中传入userID

    @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 个人主页

4.4、关注、取消关注

四、Redis,一站式高性能存储方案

关键:设计好key!!!

RedisKeyUtil

zset,按照时间来统计,进行排序

public class RedisKeyUtil {
    private static final String PREFIX_FOLLOWEE = "followee";//某个userID的关注
    private static final String PREFIX_FOLLOWER = "follower";//某个uerID的粉丝
    
    // entityType:被关注的实体:用户1、帖子2、题目3...等;
    // 某个用户关注的实体
    // 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;
    }
}

FollowService

事务管理:当前用户关注了实体的同时,被关注的实体的用户的粉丝增加

@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 redisOperations) throws DataAccessException {
                //用户的关注集合
                String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
                //实体的粉丝集合
                String followerKey = 
                    RedisKeyUtil.getFollowerKey(entityType, entityId);
                redisOperations.multi();
                //opsForZSet:有序集合
                redisOperations.opsForZSet().add(
                    followeeKey, entityId, System.currentTimeMillis());
                redisOperations.opsForZSet().add(
                    followerKey, userId, System.currentTimeMillis());
                return redisOperations.exec();
            }
        });
    }
    
    //=======================取关======================
    public void unfollow(int userId, int entityType, int entityId){
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                //用户的关注集合
                String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
                //实体的粉丝集合
                String followerKey = 
                    RedisKeyUtil.getFollowerKey(entityType, entityId);
                redisOperations.multi(); //开启事务
                redisOperations.opsForZSet().remove(followeeKey, entityId);
                redisOperations.opsForZSet().remove(followerKey, userId);
                return redisOperations.exec(); //提交事务
            }
        });
    }
    
    //查询关注的实体的数量 followee:userId:entityType -> zset(entityId,now)
    public long findFolloweeCount(int userId, int entityType){
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        return redisTemplate.opsForZSet().zCard(followeeKey);
    }
 
    //查询实体的粉丝的数量 follower:entityType:entityId -> zset(userId,now)
    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;
    }
}

FollowController(异步请求)

@Controller
public class FollowController implements CommunityConstant {

    @Autowired
    private FollowService followService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private UserService userService;
	
    //关注
    @RequestMapping(path = "/follow", method = RequestMethod.POST)
    @ResponseBody
    public String follow(int entityType, int entityId) {
        User user = hostHolder.getUser();

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

        return CommunityUtil.getJSONString(0, "已关注!");
    }
	//取消关注
    @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, "已取消关注!");
    }
}

UserController

UserController中getProfilePage方法中补充一些代码逻辑

    // 个人主页
    @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);
		//============================新增===================================
        // 关注数量
        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";
    }

4.5、关注列表、粉丝列表

四、Redis,一站式高性能存储方案

FollowService

分页显示

    // ==========================查询某用户userID关注的人============================
    public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {		  //实体类型entityType是 ENTITY_TYPE_USER
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
        //range范围查询默认是从小到大,reverseRange从大到小
        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);
            map.put("followTime", new Date(score.longValue()));
            list.add(map);//一个map封装一个被关注的user的信息
        }

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

FollowController

    //=============查询某一用户关注的人,在路径中传入当前用户的userID============
	@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());//page.getOffset()当前页的起始行
        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";
    }
	
	//==========================查询某用户userId的粉丝===========================
    @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);
    }

profile.html

followee.html

4.6、优化登录模块

四、Redis,一站式高性能存储方案

之前的验证码是存储在session里的,登录凭证存储在数据库中,每次请求都要被拦截然后再数据库中查询,用户的信息也是每次都从数据库中查询得到,效率低。

mysql中login_ticket表作废,user表仍然保存,只是同时缓存在redis中,过了有效时间就过期被清除。

(1)使用redis存储验证码

RedisKeyUtil

验证码应该和用户关联,但是一开始还没登录,因此不能传入userID,为了识别用户是谁,在客户端访问登录页面的时候,给用户发送一个随机生成的凭证owner,存在cookie中并设置过期时间

    private static final String PREFIX_KAPTCHA = "kaptcha";//验证码

    // 登录验证码
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

LoginController

getKaptcha方法之中,替换之前的 session.setAttribute("kaptcha", text) ,将验证码缓存入redis

    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {
        // 生成验证码
        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);//有效时间
        cookie.setPath(contextPath);//整个项目的路径都有效
        response.addCookie(cookie);
        // 将验证码存入Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

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

存入验证码至redis之后,在登录时需要使用,判断用户输入的验证码是否正确

login方法需要使用kaptchaOwner,从 请求参数的Cookie中取即可

    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,Model model, /*HttpSession session, */HttpServletResponse response, @CookieValue("kaptchaOwner") String kaptchaOwner) {
        // 检查验证码
        // String kaptcha = (String) session.getAttribute("kaptcha");
        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";
        }

(2)使用redis存储登录凭证

RedisKeyUtil

private static final String PREFIX_TICKET = "ticket";
//登录的凭证
public static String getTicketKey(String ticket){
    return PREFIX_TICKET + SPLIT + ticket;
}

UserService

login方法

    public Map<String, Object> login(String username, String password, int expiredSeconds) {
        Map<String, Object> 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());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insertLoginTicket(loginTicket);
//============================以下为修改==========================
        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);

        map.put("ticket", loginTicket.getTicket());
        return map;
    }

logout方法:

失效用户登录凭证,不是真正地删除,只是修改状态即可。 先查询再修改状态然后再重新存入redis缓存中去。不删除的原因是将来可能需要查询用户的历史登录时间等。

    public void logout(String ticket) {
//        loginTicketMapper.updateStatus(ticket, 1);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        LoginTicket loginTicket = 
            (LoginTicket)redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);//1:失效状态
        redisTemplate.opsForValue().set(redisKey, loginTicket);
    }

findLoginTicket 查询凭证的方法:

    public LoginTicket findLoginTicket(String ticket){
        //return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

(3)使用Redis缓存用户信息

RedisKeyUtil

private static final String PREFIX_USER = "user";
//用户
public static String getUserKey(int userId){
    return PREFIX_USER + SPLIT + userId;
}

UserService

额外添加几个方法

    //1.优先从缓存中取值
    private User getCatche(int userId){
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }
 
    //2.取不到时初始化缓存数据
    private User initCache(int userId){
        //需要从mysql中查询数据
        User user = userMapper.selectById(userId);
        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);
    }

修改根据用户id获取User的方法 :

    public User findUserById(int id){
        //现在缓存中查找,如果缓存中没有值的话,则需要初始化缓存的值
        User user = getCatche(id);
        if(user == null){
            user = initCache(id);
        }
        return user;
    }

在有修改User对象的地方,要先清空缓存,然后再重新加入缓存(从数据库中读取)

activation方法:

    public int activation(int userId, String code) {
        User user = userMapper.selectById(userId);
        if (user.getStatus() == 1) {
            return ACTIVATION_REPEAT;
        } else if (user.getActivationCode().equals(code)) {
            userMapper.updateStatus(userId, 1);
            clearCache(userId);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

updateHeader方法:

    public int updateHeader(int userId, String headerUrl){
        int rows = userMapper.updateHeader(userId, headerUrl);
        clearCache(userId);
        return rows;
    }
上一篇:个人技术--联表查询


下一篇:Springboot集成WebSocket实现消息推送功能