【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块

目录

  • 一、Redis入门
    • 基础知识
      • String 类型
      • hash类型
      • 列表
      • 无序集合
      • 有序集合
    • 总结
      • Redis的概念
      • Redis的持久化
      • 想要返回有序集合中元素的个数
  • 二、Spring整合Redis
    • 1、 引入依赖
    • 2、配置Redis
      • 配置文件——application.properties
      • 配置类——RedisConfig
      • 测试
  • 三、点赞
    • 为什么用Redis?
    • 1、service业务层
      • 工具类——RedisKeyUtil
      • 点赞——LikeService
    • 2、视图层——LikeController
    • 3、页面
      • discuss-detail.html
      • discuss.js
    • 4、首页的赞
      • 帖子方法——HomeController
      • 首页——index.html
      • 测试
    • 5、帖子详情页面的赞
      • DiscussPostController
      • discuss-detail.html
      • 测试
  • 四、我收到的赞
    • 1、主体——RedisKeyUtil
    • 2、业务层——LikeService
    • 3、页面
      • discuss-detail.html
      • discuss.js
      • 显示在用户的主页——UserController
      • 首页——index.html
      • 个人主页——profile.html
    • 4、测试
  • 五、关注、取消关注
    • 关注取关功能
      • 1、主体——RedisKeyUtil
      • 2、业务层——FollowService
      • 3、视图层——FollowController
      • 4、页面——profile.js
      • 测试
    • 补充关注多少人,统计数量
      • 1、业务层——FollowService
      • 2、视图层——UserController
      • 3、页面——profile.html
      • 测试
  • 六、关注列表、粉丝列表
    • 1、业务层——FollowService
    • 2、视图层——FollowController
    • 3、页面
      • 个人主页——profile.html
      • 关注的人——followee.html
      • 粉丝——follower.html
    • 测试
  • 七、优化登录模块
    • 1、使用Redis存验证码
      • Redis——RedisKeyUtil
      • 视图层——LoginController
      • 测试
    • 2、使用Redis存登录凭证
      • Redis——RedisKeyUtil
      • 业务层——UserService
    • 3、使用Redis存用户信息
      • UserService

一、Redis入门

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第1张图片
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第2张图片
https://github.com/microsoftarchive/redis

百度网盘下载

默认端口号

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第3张图片

基础知识

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第4张图片

  • 内置16个库,没名字,索引区分

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第5张图片

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第6张图片

String 类型

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第7张图片

hash类型

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第8张图片

列表

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第9张图片

无序集合

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第10张图片

有序集合

分数排序
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第11张图片

查一下库里有多少key
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第12张图片

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第13张图片

设置某一个key的过期时间
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第14张图片

总结

Redis的概念

  • NoSQL是关系型数据库之外的数据库的统称,Redis就是一个NoSQL数据库。
  • Redis以键值对的形式,将数据存储于内存中
  • Redis的值支持多种数据类型,包括字符串、哈希、列表、集合、有序集合等。
  • Redis提供了对值进行运算的命令,如对集合求交集、并集、差集等。

Redis的持久化

  • RDB是以快照的形式,将内存中的数据整体拷贝到硬盘上。
  • 执行RDB存储时会产生阻塞,因此RDB不适合实时备份,而适合定时备份。
  • AOF是以以追加的形式将缓存中的数据存放到硬盘中,不是整体拷贝。
  • AOF操作的实时性好,但是产生的数据体积大,数据的恢复速度慢。

想要返回有序集合中元素的个数

二、Spring整合Redis

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第15张图片

引入依赖

  • spring-boot-starter-data-redis

配置Redis

  • 目配置数据库参数
  • 编写配置类,构造RedisTemplate

访问Redis

  • redisTemplate.opsForvalue ()
  • redisTemplate.opsForHash ()
  • redisTemplate.opsForList ()
  • redisTemplate.opsForset ()
  • redisTemplate.opsForzSet ()

1、 引入依赖

<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-data-redisartifactId>
dependency>

2、配置Redis

配置文件——application.properties

  • redis 数据库
  • 本机
  • 端口
# RedisProperties
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379

配置类——RedisConfig

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第16张图片

package com.nowcoder.community.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
    /**
     * 把对象装配到容器中
     * @param factory:连接工厂,才能访问数据库
     * @return
     */
    @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());// value可能是各种形式的数据
        // 设置hash的key的序列化方式
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置hash的value的序列化方式
        template.setHashValueSerializer(RedisSerializer.json());

        template.afterPropertiesSet();
        return template;
    }

}

测试

package com.nowcoder.community;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.TimeUnit;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTests {

    @Autowired
    private RedisTemplate redisTemplate;

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

    // hash表
    @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));   // 获取索引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 testKeys() {
        redisTemplate.delete("test:user");

        System.out.println(redisTemplate.hasKey("test:user"));

        redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);
    }

    // 批量发送命令,节约网络开销.
    // 多次访问同一个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());       // 6
    }

    // 编程式事务,不严格满足ACID
    // 事务 存到队列中,最后一起提交,
    @Test
    public void testTransaction() {
        // 方法内部传一个接口实例
        Object result = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                String redisKey = "text:tx";

                // 启用事务
                redisOperations.multi();
                redisOperations.opsForSet().add(redisKey, "zhangsan");
                redisOperations.opsForSet().add(redisKey, "lisi");
                redisOperations.opsForSet().add(redisKey, "wangwu");

                System.out.println(redisOperations.opsForSet().members(redisKey));   // 中间查询无效

                // 提交事务
                return redisOperations.exec();
            }
        });
        System.out.println(result);
    }

}

三、点赞

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第17张图片
点赞

  • 支持对帖子、评论点赞。
  • 第1次点赞,第2次取消点赞。

首页点赞数量

  • 统计帖子的点赞数量。

详情页点赞数量

  • 统计点赞数量
  • 显示点赞状态。

为什么用Redis?

数据存到redis

  • 赞这个东西呢,更新频率比较高而且要实时,存在mysql会被高频读写,导致表的io高。读内存比硬盘快
  • 这种需要高并发io的数据不适合存放在关系型数据库,磁盘io速度慢,顶不住

1、service业务层

工具类——RedisKeyUtil

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第18张图片

package com.nowcoder.community.util;

/**
 * 简单工具类
 */
public class RedisKeyUtil {

    private static final String SPLIT = ":";   // 分割
    private static final String PREFIX_ENTITY_LIKE = "like:entity";     // 存储赞

    /**
     * 某个实体的赞(帖子、评论),like:entity:entityType:entityId -> set(userId)
     * @param entityType:实体类型
     * @param entityId:实体id
     * @return
     */
    // 某个实体的赞(帖子、评论)
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }

}

点赞——LikeService

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第19张图片

package com.nowcoder.community.service;

import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class LikeService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 点赞(第一次赞,第二次取消)
     * @param userId:用户
     * @param entityType:点赞实体
     * @param entityId:实体id
     */
    // 点赞
    public void like(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        // 判断 userId 在不在集合中
        boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
        if (isMember) {
            // 点过赞,取消
            redisTemplate.opsForSet().remove(entityLikeKey, userId);
        } else {
            redisTemplate.opsForSet().add(entityLikeKey, userId);
        }
    }

    /**
     * 查询某实体点赞的数量
     * @param entityType:点赞实体
     * @param entityId:实体id
     * @return
     */
    // 查询某实体点赞的数量
    public long findEntityLikeCount(int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().size(entityLikeKey);
    }

    /**
     * 查询某人对某实体的点赞状态,返回整数,1-赞
     * @param userId
     * @param entityType:点赞实体
     * @param entityId:实体id
     * @return
     */
    // 查询某人对某实体的点赞状态
    public int findEntityLikeStatus(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
    }

}

2、视图层——LikeController

package com.nowcoder.community.controller;

@Controller
public class LikeController {

    @Autowired
    private LikeService likeService;

    @Autowired
    private HostHolder hostHolder;

    /**
     * 点赞
     * @param entityType:实体
     * @param entityId:id
     * @return
     */
    @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封装
        Map<String, Object> map = new HashMap<>();
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);

        // 返回json格式数据
        return CommunityUtil.getJSONString(0, null, map);
    }

}

3、页面

三个位置的赞
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第20张图片

discuss-detail.html

第一个赞
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

discuss.js

依赖的js文件

  • 帖子详情页面
  • like方法
    【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第21张图片
function like(btn, entityType, entityId) {
    $.post(
        CONTEXT_PATH + "/like",
        {"entityType":entityType,"entityId":entityId},
        function(data) {
            data = $.parseJSON(data);
            if(data.code == 0) {
                $(btn).children("i").text(data.likeCount);
                $(btn).children("b").text(data.likeStatus==1?'已赞':"赞");
            } else {
                alert(data.msg);
            }
        }
    );
}

4、首页的赞

帖子方法——HomeController

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第22张图片

首页——index.html

赞的数量
在这里插入图片描述

测试

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第23张图片

5、帖子详情页面的赞

DiscussPostController

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第24张图片

discuss-detail.html

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第25张图片

四、我收到的赞

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第26张图片

1、主体——RedisKeyUtil

    /**
     * 某个用户获得的赞
     * @param userId
     * @return
     */
    // 某个用户的赞
    // like:user:userId -> int
    public static String getUserLikeKey(int userId) {
        return PREFIX_USER_LIKE + SPLIT + userId;
    }

2、业务层——LikeService

  • 保证事务性
  • 点赞方法
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);
                String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);

                // 判断 userId 在不在集合中
                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);
                    operations.opsForValue().increment(userLikeKey);
                }

                return operations.exec();
            }
        });
    }

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

3、页面

discuss-detail.html

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

discuss.js

function like(btn, entityType, entityId, entityUserId) {
    $.post(
        CONTEXT_PATH + "/like",
        {"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId},
        function(data) {
            data = $.parseJSON(data);
            if(data.code == 0) {
                $(btn).children("i").text(data.likeCount);
                $(btn).children("b").text(data.likeStatus==1?'已赞':"赞");
            } else {
                alert(data.msg);
            }
        }
    );
}

显示在用户的主页——UserController

 /**
     * 个人主页
     * @param userId
     * @param model
     * @return
     */
    // 个人主页
    @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

在这里插入图片描述
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第27张图片

个人主页——profile.html

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第28张图片
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第29张图片
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第30张图片

4、测试

刷新数据库
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第31张图片

五、关注、取消关注

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第32张图片

关注取关功能

1、主体——RedisKeyUtil


    /**
     * 某个用户关注的实体,followee:userId:entityType -> zset(entityId,now)
     * @param userId
     * @param entityType
     * @return
     */
    // 某个用户关注的实体
    // followee:userId:entityType -> zset(entityId,now)
    public static String getFolloweeKey(int userId, int entityType) {
        return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
    }

    /**
     * 某个实体拥有的粉丝
     * @param entityType
     * @param entityId
     * @return
     */
    // 某个实体拥有的粉丝
    // follower:entityType:entityId -> zset(userId,now)
    public static String getFollowerKey(int entityType, int entityId) {
        return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
    }

2、业务层——FollowService

package com.nowcoder.community.service;

import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;

/**
 * 关注相关的业务
 */
@Service
public class FollowService {

    @Autowired
    private RedisTemplate redisTemplate;  // 存到Redis

    /**
     * 关注
     * @param userId
     * @param entityType
     * @param entityId
     */
    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();
            }
        });
    }

    /**
     * 取关
     * @param userId
     * @param entityType
     * @param entityId
     */
    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;
    }

}

3、视图层——FollowController

package com.nowcoder.community.controller;

import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.FollowService;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class FollowController {

    @Autowired
    private FollowService followService;

    @Autowired
    private HostHolder hostHolder;

    /**
     * 关注(异步)
     * @param entityType
     * @param entityId
     * @return
     */
    @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, "已关注!");
    }

    /**
     * 取关
     * @param entityType
     * @param entityId
     * @return
     */
    @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, "已取消关注!");
    }

}

4、页面——profile.js

$(function(){
	$(".follow-btn").click(follow);
});

function follow() {
	var btn = this;
	if($(btn).hasClass("btn-info")) {
		// 关注TA
		$.post(
			CONTEXT_PATH + "/follow",
			{"entityType":3,"entityId":$(btn).prev().val()},
			function(data) {
				data = $.parseJSON(data);
				if(data.code == 0) {
					window.location.reload();
				} else {
					alert(data.msg);
				}
			}
		);
		// $(btn).text("已关注").removeClass("btn-info").addClass("btn-secondary");
	} else {
		// 取消关注
		$.post(
			CONTEXT_PATH + "/unfollow",
			{"entityType":3,"entityId":$(btn).prev().val()},
			function(data) {
				data = $.parseJSON(data);
				if(data.code == 0) {
					window.location.reload();
				} else {
					alert(data.msg);
				}
			}
		);
		//$(btn).text("关注TA").removeClass("btn-secondary").addClass("btn-info");
	}
}

测试

需要补充关注多少人,关注状态
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第33张图片

补充关注多少人,统计数量

1、业务层——FollowService

  • 查询关注的实体的数量
  • 查询实体的粉丝的数量
  • 查询当前用户是否已关注该实体
 /**
     * 查询关注的实体的数量
     * @param userId:谁的主页
     * @param entityType:某一类的关注目标
     * @return
     */
    // 查询关注的实体的数量
    public long findFolloweeCount(int userId, int entityType) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        return redisTemplate.opsForZSet().zCard(followeeKey);   // 得到数据量
    }

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

    /**
     * 查询当前用户是否已关注该实体
     * @param userId 当前用户,登录用户
     * @param entityType 传入实体,对方
     * @param entityId 对方id
     * @return
     */
    // 查询当前用户是否已关注该实体
    public boolean hasFollowed(int userId, int entityType, int entityId) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        // 为空 没关注,不为空 已关注
        return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
    }

2、视图层——UserController

 /**
     * 个人主页
     * @param userId 用户
     * @param model
     * @return
     */
    // 个人主页
    @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";
    }

3、页面——profile.html

在这里插入图片描述

  • 登录才能关注
  • 自己页面不显示关注

在这里插入图片描述

测试

没登录不显示
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第34张图片

关注
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第35张图片
取关
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第36张图片

个人主页
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第37张图片

六、关注列表、粉丝列表

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第38张图片
查看关注的人和粉丝
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第39张图片

1、业务层——FollowService

  /**
     * 查询某用户关注的人
     * @param userId 用户
     * @param offset 分页
     * @param limit 条件
     * @return
     */
    // 查询某用户关注的人(粉丝id 关注时间)
    public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {
        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,存到map中
            Map<String, Object> map = new HashMap<>();
            User user = userService.findUserById(targetId);     // 查user
            map.put("user", user);
            // 查观测时间
            Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);
            map.put("followTime", new Date(score.longValue()));
            list.add(map);
        }

        return list;
    }

    /**
     * 查询某用户的粉丝
     * @param userId
     * @param offset
     * @param limit
     * @return
     */
    // 查询某用户的粉丝
    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;
    }


2、视图层——FollowController

 /**
     * 实现查询关注的人
     * @param userId
     * @param page
     * @param model
     * @return
     */
    @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";
    }

    /**
     * 判断当前用户 有没有关注
     * @param userId
     * @return
     */
    private boolean hasFollowed(int userId) {
        if (hostHolder.getUser() == null) {
            return false;
        }

        return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
    }

3、页面

个人主页——profile.html

在这里插入图片描述

关注的人——followee.html

thymeleaf 模板
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第40张图片

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第41张图片
选项
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第42张图片
关注列表

分页

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第43张图片

粉丝——follower.html

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第44张图片
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第45张图片
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第46张图片
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第47张图片

测试

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第48张图片

七、优化登录模块

【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第49张图片
原因

  • redis 效率高
  • 验证码比较频繁访问、刷新,对性能要求高,无需永久保存,目前存在session中
  • 避免session共享的问题
  • 登录凭证
  • 用户信息

1、使用Redis存验证码

Redis——RedisKeyUtil

	 private static final String PREFIX_KAPTCHA = "kaptcha";  // 验证码
    /**
     * 登录验证码,重构
     * @param owner 临时标记字符串,在cookie中
     * @return
     */
    // 登录验证码,临时标记用户
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

视图层——LoginController


    /**
     * 获取验证码方法
     * @param response
     */
    @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());
        }
    }

    /**
     * 登录时判断验证码
     * @param username
     * @param password
     * @param code
     * @param rememberme
     * @param model
     * @param response
     * @param kaptchaOwner
     * @return
     */
    @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";
        }

        // 检查账号,密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map<String, Object> map = userService.login(username, password, expiredSeconds);
        if (map.containsKey("ticket")) {
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            cookie.setPath(contextPath);    // cookie路径——整个项目
            cookie.setMaxAge(expiredSeconds);       // cookie 有效时间
            response.addCookie(cookie);         // 把 cookie 发送给页面上
            return "redirect:/index";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";
        }
    }

测试

刷新验证码ok
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第50张图片
也可以登录
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第51张图片

2、使用Redis存登录凭证

登出不需要凭证

Redis——RedisKeyUtil

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

业务层——UserService

首先在这个类加上
【论坛java项目】第四章 Redis,一站式高性能存储方案:Redis入门、Spring整合Redis、点赞、收到的赞、关注、取消关注、关注列表、粉丝列表、优化登录模块_第52张图片

// 用户在页面输入的密码是明文,存的是加密后的,MD5
    // expiredSeconds 多长时间后,凭证过期
    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);

        // Redis传进来
        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);

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

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

    /**
     * 查询登录凭证
     * @param ticket
     * @return
     */
    //查询登录凭证
    public LoginTicket findLoginTicket(String ticket) {
//        return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

3、使用Redis存用户信息

UserService

    @Autowired
    private RedisTemplate redisTemplate;

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

   

你可能感兴趣的:(java,redis,spring)