- Redis
(1)简介
- Redis 是一个高性能的 key-value 数据库 
- 原子 – Redis的所有操作都是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。 
- 非关系形数据库 
- 数据全部存在内存中,性能高。 
(2)数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
- string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。 
- Redis hash 是一个键值(key=>value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。 
- Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。 
- Redis 的 Set 是 string 类型的无序集合,集合是通过hash实现的 
- Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 
(3)基本操作
@Test
public void testStrings() {
    String redisKey = "test:count";
    redisTemplate.opsForValue().set(redisKey, 1);
    System.out.println(redisTemplate.opsForValue().get(redisKey));
    System.out.println(redisTemplate.opsForValue().increment(redisKey));
    System.out.println(redisTemplate.opsForValue().decrement(redisKey));
}
@Test
public void testHashes() {
    String redisKey = "test:user";
    redisTemplate.opsForHash().put(redisKey, "id", 1);
    redisTemplate.opsForHash().put(redisKey, "username", "zhangsan");
    System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));
    System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));
}
@Test
public void testLists() {
    String redisKey = "test:ids";
    redisTemplate.opsForList().leftPush(redisKey, 101);
    redisTemplate.opsForList().leftPush(redisKey, 102);
    redisTemplate.opsForList().leftPush(redisKey, 103);
    System.out.println(redisTemplate.opsForList().size(redisKey));
    System.out.println(redisTemplate.opsForList().index(redisKey, 0));
    System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));
    System.out.println(redisTemplate.opsForList().leftPop(redisKey));
    System.out.println(redisTemplate.opsForList().leftPop(redisKey));
    System.out.println(redisTemplate.opsForList().leftPop(redisKey));
}
@Test
public void testSets() {
    String redisKey = "test:teachers";
    redisTemplate.opsForSet().add(redisKey, "刘备", "关羽", "张飞", "赵云", "诸葛亮");
    System.out.println(redisTemplate.opsForSet().size(redisKey));
    System.out.println(redisTemplate.opsForSet().pop(redisKey));
    System.out.println(redisTemplate.opsForSet().members(redisKey));
}
@Test
public void testSortedSets() {
    String redisKey = "test:students";
    redisTemplate.opsForZSet().add(redisKey, "唐僧", 80);
    redisTemplate.opsForZSet().add(redisKey, "悟空", 90);
    redisTemplate.opsForZSet().add(redisKey, "八戒", 50);
    redisTemplate.opsForZSet().add(redisKey, "沙僧", 70);
    redisTemplate.opsForZSet().add(redisKey, "白龙马", 60);
    System.out.println(redisTemplate.opsForZSet().zCard(redisKey));
    System.out.println(redisTemplate.opsForZSet().score(redisKey, "八戒"));
    System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey, "八戒"));
    System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey, 0, 2));
}
多次访问同一个key
@Test
public void testBoundOperations() {
    String redisKey = "test:count";
    BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
    operations.increment();
    operations.increment();
    operations.increment();
    operations.increment();
    operations.increment();
    System.out.println(operations.get());
}
(4)spring 配置 redis
引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>在 application.properties 中声明:访问哪个库,host地址,端口号
# RedisProperties
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379在 config 下实现 RedisConfig 类
注入连接工厂才能访问数据库 RedisConnectionFactory factory
实例化 bean new RedisTemplate<>();
设置工厂后有访问数据库能力 template.setConnectionFactory(factory);
指定序列化方式(数据转化方式)
//定义自定义的redis对象
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        //主要配置 序列化的方式
        //设置key 的 序列化方式
        redisTemplate.setKeySerializer(RedisSerializer.string());
        //设置value的序列化方式
        redisTemplate.setValueSerializer(RedisSerializer.json());
        //设置hash 的 key序列化
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        //设置 hash 的 value 序列化
        redisTemplate.setHashValueSerializer(RedisSerializer.json());
        //出发 使其生效
        redisTemplate.afterPropertiesSet();
        return  redisTemplate;
    }(5)Redis 事务 管理
事务内命令不会立即执行,提交后统一执行
使用编程式事务进行管理,声明式事务用的少
调用 redisTemplate ,方法内部做匿名实现
SessionCallback() 里方法execute重写,内部实现事务逻辑
启用事务 operations.multi();
提交事务 operations.exec();
// 编程式事务
@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);
}2.点赞
 
   (1)业务层
生成redis key的工具 在 util 下实现 RedisKeyUtil,集合set存储谁给某个实体点的赞
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";
    // 某个实体的赞
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) { //实体类型  实体ID
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }
}Service 下实现 LikeService
@Service
public class LikeService {
    @Autowired
    private RedisTemplate redisTemplate;
    // 点赞
    public void like(int userId, int entityType, int entityId) {
        //获取key
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType,entityId);
        //判断当前用户是否点过赞   即userid 是否在set中
        if(redisTemplate.opsForSet().isMember(entityLikeKey,userId)){
            redisTemplate.opsForSet().remove(entityLikeKey,userId);
        }else {
            redisTemplate.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
- 获取当前用户 
- 调用service点赞方法 
- 获取数量和状态 
- 放入map 
- 返回json格式数据 
@Controller
public class LikeController {
    @Autowired
    private LikeService likeService;
    @Autowired
    private HostHolder hostHolder;
    @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);
    }
}帖子详情页面赞的数量的显示
修改 DiscussPostController 下的 getDiscussPost
//根据 帖子id 查询帖子内容 评论 评论的回复
    @RequestMapping(path = "/detail/{discussPostId}",method = RequestMethod.GET)
    public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page){
        //根据帖子id查询帖子
        DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
        model.addAttribute("post",post);
        //根据userid查询user
        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);
        //查评论的分页信息
        page.setLimit(5);
        page.setPath("/discuss/detail/" + discussPostId);
        page.setRows(post.getCommentCount());
        //评论:给帖子的评论
        //回复:给评论的评论
        //获取所有评论
        List<Comment> commentList = commentService.findCommentsByEntity
                (ENTITY_TYPE_POST,post.getId(), page.getOffset(),page.getLimit());
        //用于封装 每条评论及每条评论的回复。。。
        List<Map<String,Object>> commentVoList = new ArrayList<>();
        //每一条评论 找到评论的作者。找到该评论的回复,回复的作者,回复的用户
        for (Comment comment:commentList) {
            Map<String,Object> 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);
            //获取该评论的所有回复
            List<Comment> replyList = commentService.findCommentsByEntity
                    (ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
            //用于封装 每一条回复的 作者 回复咪表
            List<Map<String, Object>> replyVoList = new ArrayList<>();
            if(replyVoList != null){
                for (Comment reply: replyList) {
                    Map<String,Object> 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);
                    //将 单条回复放入 此 评论 总的 回复表
                    replyVoList.add(replyVo);
                }
            }
            //将回复总表 嵌入 单条评论
            commentVo.put("replys", replyVoList);
            //回复数量
            int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
            commentVo.put("replyCount", replyCount);
            commentVoList.add(commentVo);
        }
        model.addAttribute("comments", commentVoList);
        return "/site/discuss-detail";
    }3.使用Redis存储验证码
LoginController.getKaptcha
        //  老方法 验证码 存入session
        //session.setAttribute("kaptcha", text);
        // 验证码的归属 一个验证码 绑定 一个 kaptchaOwner
        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);LoginController.login
// 检查验证码 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.equals(code)){
            model.addAttribute("codeMsg", "验证码不正确!");
            return "/site/login";
        }4. 使用Redis存储登录凭证
UserService
- login 生成登录凭证 
// 生成登录凭证
        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);- logout 退出登录,ticket取出来再存进去 
public void logout(String ticket) {
        //loginTicketMapper.updateStatus(ticket, 1);
        String redisKey  = RedisKeyUtil.getTicketKey(ticket);
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);
        redisTemplate.opsForValue().set(redisKey,loginTicket);
    }- LoginTicket 查询凭证 
public LoginTicket findLoginTicket(String ticket) {
       // return loginTicketMapper.selectByTicket(ticket);
        String redisKey  = RedisKeyUtil.getTicketKey(ticket);
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        return  loginTicket;
    }5.使用Redis缓存用户数据
查用户时: 先查缓存 在查mysql
UserService
// 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);
    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);
}
public User findUserById(int id) {
//        return userMapper.selectById(id);
    User user = getCache(id);
    if (user == null) {
        user = initCache(id);
    }
    return user;
}
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;
    }
}
public int updateHeader(int userId, String headerUrl) {
//        return userMapper.updateHeader(userId, headerUrl);
    int rows = userMapper.updateHeader(userId, headerUrl);
    clearCache(userId);
    return rows;
}



















