5.Redis 实现点赞 优化登陆(验证码 token..)

文章介绍了Redis作为高性能的key-value数据库的特性,包括其原子性操作、数据类型如字符串、哈希、列表、集合和有序集合,以及基本操作。通过Spring配置展示了如何在Java应用中使用Redis,包括连接工厂、序列化方式设置。同时,文章还详细讨论了Redis的事务管理和点赞功能的实现,包括点赞状态的存储和查询。
摘要由CSDN通过智能技术生成
  1. 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

  1. 获取当前用户

  1. 调用service点赞方法

  1. 获取数量和状态

  1. 放入map

  1. 返回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

  1. 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);
  1. 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);
    }
  1. 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值