使用redis模拟cookie-session,例子:实现验证码功能

在前后端分离架构中不建议使用cookie-session机制实现端状态识别

原因:
1.前后端分离存在跨域问题,cookie无法共享

2.后台服务器一旦建立集群,可能导致session数据丢失,即·后台有多台服务器,每个服务器存的session不一样,导致访问到不同的服务器时导致找不到对应的session

3.前端浏览器如果禁用session,则该机制无法生效

4.后台服务器需要维护session对象,又内存开销

所以我们可以使用redis来模拟session-cookie机制

我们可以再后端通过雪花算法生成一个独一的sessionid,然后存入redis中,并把这个sessionId值发送给前端,前端再通过发请求数据->里面包含sessionId,后端再通过sessionId去redis取出值进行比较。我们还可以使用redis的数据过期模式来模拟session的过期机制 

下面我们通过实现验证码的功能来举例 

第一步:了解前端要我们返回的数据变量名字,变量类型

{
    "code": 1,
    "data": {
        "imageData": "iVBORw0KGgoAAAANSUh...省略...AAAPoAAAAoCAYAAADX=", //base64格式图片
        "sessionId": "1479063316897845248" //保存在redis中验证码对应的key,模拟sessioinId
    }
}

 我们来封装一个这个json数据->由外到内

1.封装code,data成一个result类,专门用于返回数据

 <!--jackson相关注解,实现日期格式转换和类型格式转换并序列化等-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>
/**
 * 返回数据类
 * @JsonInclude 保证序列化json的时候,如果是null的对象,key也会消失
 * @param <T>
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class R<T> implements Serializable {
    private static final long serialVersionUID = 7735505903525411467L;

    // 成功值,默认为1
    private static final int SUCCESS_CODE = 1;
    // 失败值,默认为0
    private static final int ERROR_CODE = 0;

    //状态码
    private int code;
    //消息
    private String msg;
    //返回数据
    private T data;

    private R(int code){
        this.code = code;
    }
    private R(int code, T data){
        this.code = code;
        this.data = data;
    }
    private R(int code, String msg){
        this.code = code;
        this.msg = msg;
    }
    private R(int code, String msg, T data){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> R<T> ok(){
        return new R<T>(SUCCESS_CODE,"success");
    }
    public static <T> R<T> ok(String msg){
        return new R<T>(SUCCESS_CODE,msg);
    }
    public static <T> R<T> ok(T data){
        return new R<T>(SUCCESS_CODE,data);
    }
    public static <T> R<T> ok(String msg, T data){
        return new R<T>(SUCCESS_CODE,msg,data);
    }

    public static <T> R<T> error(){
        return new R<T>(ERROR_CODE,"error");
    }
    public static <T> R<T> error(String msg){
        return new R<T>(ERROR_CODE,msg);
    }
    public static <T> R<T> error(int code, String msg){
        return new R<T>(code,msg);
    }
    public static <T> R<T> error(ResponseCode res){
        return new R<T>(res.getCode(),res.getMessage());
    }

    public int getCode(){
        return code;
    }
    public String getMsg(){
        return msg;
    }
    public T getData(){
        return data;
    }
}

2.封装data里面的数据

data里面的数据我们其实可以不用特意封装成一个类,因为只有两个key,且没有什么实际意义,

最重要的是验证码不用与数据库进行交互,只需要访问redis即可,所以我们可以直接使用Map

第二步:导入redis依赖

<!--redis场景依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis创建连接池,默认不会创建连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

 在yml文件中写redis的配置,如果我们把所有配置信息都写在application.yml文件中,就会导致这个文件十分复杂,所以我们额外创建一个application-cache.yml文件,在此文件中写redis配置,再通过application.yml来激活application-cache.yml

spring:
  profiles:
    active: cache #激活其他配置文件
spring:
  # 配置缓存
  redis:
    host: 192.168.230.100
    port: 6379
    database: 0 #Redis数据库索引(默认为0)
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 1  # 连接池中的最小空闲连接
    timeout: PT10S # 连接超时时间

application-cahe.yml没有叶子的解决方法

找到对应的模块添加spring

再点击这个小叶子进行添加 

自定义redis序列化

@Configuration
public class RedisCacheConfig {
    /**
     * 配置redisTemplate bean,自定义数据的序列化的方式,避免使用默认的jdk序列化方式
     * jdk序列化缺点:
     * 1.阅读体验差
     * 2.序列化后内容体积比较大,占用过多内存
     * @param redisConnectionFactory 连接redis的工厂,底层有场景依赖启动时,自动加载
     * @return
     */
    //TODO:方法名必须是redisTemplate,这是bean id 如果自己装配了这个类的bean,SpringBoot就不会自动装配了
    //TODO:而底层又是使用了redisTemplate这个bean id 的,所以方法名必须为redisTemplate
    @Bean
    public RedisTemplate redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){//不加@Autowired也行
        //1.构建RedisTemplate模板对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //2.为不同的数据结构设置不同的序列化方案
        //设置key序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        //设置value序列化方式
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        //设置hash中field字段序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        //设置hash中value的序列化方式
        template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        //5.初始化参数设置
        template.afterPropertiesSet();
        return template;
    }
}

 测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRedisCache {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void test(){
        ValueOperations valueOperations = redisTemplate.opsForValue();//取出操作String类型的操作类
        valueOperations.set("name","ajx");
        System.out.println(valueOperations.get("name"));
    }
}

第三步:使用雪花算法生成唯一的sessionId,即一个数字

64位ID (42(时间戳)+5(机房ID)+5(机器ID)+12(序列号-同毫秒内重复累加))

1.导入一个工具类

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

/**
 * 分布式自增长ID实现,底层基于Twitter的Snowflake
 * 64位ID (42(时间戳)+5(机房ID)+5(机器ID)+12(序列号-同毫秒内重复累加))
 * @author itheima
 */
public class IdWorker {
    // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
    private final static long twepoch = 1288834974657L;
    // 机器标识位数
    private final static long workerIdBits = 5L;
    // 数据中心标识位数
    private final static long datacenterIdBits = 5L;
    // 机器ID最大值
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 数据中心ID最大值
    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    // 毫秒内自增位
    private final static long sequenceBits = 12L;
    // 机器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 数据中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    /* 上次生产id时间戳 */
    private static long lastTimestamp = -1L;
    //同毫秒并发控制
    private long sequence = 0L;
	//机器ID
    private final long workerId;
    //机房ID
    private final long datacenterId;

    public IdWorker(){
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }
    /**
     * @param workerId
     *            工作机器ID
     * @param datacenterId
     *            序列号
     */
    public IdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
    /**
     * 获取下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            // 当前毫秒内,则+1
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 当前毫秒内计数满了,则等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID,并返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;

        return nextId;
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * <p>
     * 获取 maxWorkerId
     * </p>
     */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuffer mpid = new StringBuffer();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split("@")[0]);
        }
        /*
         * MAC + PID 的 hashcode 获取16个低位
         */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * <p>
     * 数据标识id部分
     * </p>
     */
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = ((0x000000FF & (long) mac[mac.length - 1])
                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (maxDatacenterId + 1);
            }
        } catch (Exception e) {
            System.out.println(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }
}

2.配置机房id和机器id,并存入IoC容器

    /**
     * 根据雪花算法保证sessionId的唯一性
     * @return 返回第三方bean,加入到IoC容器
     */
    @Bean
    public IdWorker idWorker(){
        //参数一:机器id
        //参数二:机房id
        return new IdWorker(1l,2l);
    }

第四步:导入验证码图片生成包

<!--hutool万能工具包-->
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
</dependency>

第五步:定义一个常量类,用于封装我们使用的数据



/**
 * 
 * @Description 常量类信息封装
 */
public class StockConstant {

    /**
     * 定义校验码的前缀
     */
    public static final String CHECK_PREFIX="CK:";

}

 第六步:业务逻辑

@Service
@Slf4j
public class UserServiceImpl implements UserService {
    @Autowired
    IdWorker idWorker;//sessionId生成器
    @Autowired
    RedisTemplate redisTemplate;//redis

    @Override
    public R<Map<String, String>> getCaptchaCode() {
        //1.生成图片验证码,使用huTool工具包
        /*
        参数一:生成验证码的宽度
        参数二:生成验证码的长度
        参数三:图片中包含验证码的长度
        参数四:干扰线数量
         */
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(250, 40, 4, 5);
        //设置背景颜色
        captcha.setBackground(Color.LIGHT_GRAY);

        //获取校验码
        String code = captcha.getCode();
        //获取经过base64编码处理的图片数据
        String imageData = captcha.getImageBase64();
        //2.生成sessionId,并转换成String类型,防止发送给前端时数据丢失
        String sessionId = String.valueOf(idWorker.nextId());//把Long类型的数据变成String类型再发送给前端

        log.info("当前生成的验证码为:{},sessionId为:{}",code,sessionId);

        //3.将sessionId作为key,验证码作为value保存到redis,TODO:设置验证码的有效时间,即key(sessionID)的存活时间
        //TODO:这里给sessionID加一个前缀,区别这是用于验证码的,到时候可以在redis使用 keys CK*来查看现在redis到底有多少验证码key
        //redisTemplate.opsForValue().set("CK:"+sessionId,code,5, TimeUnit.MINUTES);
        redisTemplate.opsForValue().set(StockConstant.CHECK_PREFIX +sessionId,code,5, TimeUnit.MINUTES);
        //4.组装数据
        Map<String,String>map=new HashMap<>();
        map.put("sessionId",sessionId);//sessionId key值不能乱写,要和前端的变量名字一样
        map.put("imageData",imageData);
        //5.返回数据
        return R.ok(map);
    }
}

第七步:测试接口

{
    "code": 1,
    "data": {
        "imageData": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAAoCAYAAADXGucZAAAF+klEQVR42u2ca0ybVRiAidGYLMapWRaCmxIXL9F/JjOKW9jGYEhg3KEwBoOxjYtDkIodK/cVZOM+ym0QpFDKJciAUgrllmWY6bxsWeIlJs6Jbj/UgfGff155P/OdtaVfAS2Dnr4/nhT6vQ095+M51/d8brOzs0AQBN+4USUQBIlOEASJThAEiU4QBInOO/e2P2UTqhuCRKcGgCBIdIIgSHSCIBwh+hmfLQyqQOdH/+knUPp9CqTd3QsRCzsgbNFDeH33131Q/t1pGLs6TPVEolMFOjNtX1dA5MJOCPlzuyRRC88LcVRfJDrhhAxc64LwBQ+7kouELroL8VRvJDrhZGT+7M9Elt1/Aaq+kYPxql64Zpgbgspvsy16+9zbkQ75u4u/xVH9k+jEw5qXiwLjnFyqt9Z+2WARZ7piJNlJdNfh9+xcC5zt+6tvFTCBFT+F2Y19/04Ai22+Ueqw70Cyb7DoI73tcDErCAoPe8BZvyeF12ZlHJjG9FyLrunvg1B5JuwIfgce9X4THt/vBZ5hgSBTfACDev2axN/sDYHidvgDeW+q7MY23Sx5MHz/MYob0bt7dSBX5kB4fBQEx4ZB7Il4KKssh+npaeF6oCyEwZ3obSUpFiKbo4p+GSYMQ1yKnlSghEf2vgFue3bbZKvfPqjXfOywEcBGNwTvzfsyebVfNNqN1V1vYbG45ebI77FRsheVlViIbE66PEOQnVvRNReyJSUXqTzlzZ3oaapiScGtZR82GLiYCkTd92TyTl0x2Y3F6+aLds5+v8urL0hKLnLufCmfouOwXOn/DBO4NPY1GNRchJmZaZiZnoK+5o+gJPLFZeI7e+FxSP6Y91tM5ldjwkGt6RBadETVqIZtAQfZdRzG83DTzbfVVhMvxuLnHN67/qWyYF0XIQ2jEHwknAmcmJYMXTrt0v/5jHC/G1qaIDopVhjKcyl6x/ksiyH61MTYskActheFPseV6KnnipjEuyIOw8Tk5LKYlm4ti3EP9OPippvvka8lHlfe131YbSW+IxuAitpKJu/RlEQwmZaPZob1IxB5LIZP0atTDzB5tbVK6QWrCjlXonslJzCJ8+qqXWYFFhNgNqvo69kAZORkMnnrm9SrahC4Er0gyJ3Jiz23VLBxqI8r0bf47GGir9f8ezOCuez/RXT83GYry1rED4uPYvIaxsYk4y4PD/Epeu7BJ1YlL87ZeRLdfKXdlfZUY/7Y5ZKLcUExoauSF+fsLi06b/vouF/uiqKbp7/2f2Z/27DneiuLzfjFx6nLbb4Qt1Isl6LnBWxj8o6PDEgG4+o8Dd2dH0x8EeW9dKPMbixmwzk6333DRjLH45i8+lHpJCjjuJFP0WvSfZm8nZU50tlEdflcib772BEmemF9ne352tI/hBjztP9+Lm76WuTF62Isps46c7mzzsiZvDXqWsm4hpZGPkXvrPqQyVsUstPmghxuueHWG0+iY0acKPFLUbZv6PHCPBbjm36Ki5uOD5MQV95xJb338zbJYbv5Cj0ehnHmctc3NzB5ZUu9u60FOdxyS0hN4lP0KZMRCoOfZQIXh3lCb5Pq34SZJQbaa0Ale8ViLs+D6H1Dly3m6djDt/f2CNdGjUY4WVxgkR33f9JgNxvyO0EWD5fAY6ri02TwuCr+ju+LMZnzh5y+zLjIhjntosD4c1Nrs/A+otF2Ckk01plyXKXAYvbbSimwankodymw1jJLcSD1BFfz9P5rHat66ISYEcfLgye6e3TLMt+sSUxP5vtQC87Bz/ptlTzUMjlu4PJQy1Glwq7k2NMbJya4W5Srv5W/4lNmsFdv/6qKq3J3dGkgIkFmU3JMgeV2H90cw6AOGhXRQrorDtUxmaZRIeP+mGqrrhsOnU4RctvFY6qvx8eAoorv56WNzA2A6oeTkHLPiyXS4GvK3beFh0aOzg1yWW5cWcfDKzgfxx4+NC4SlCX5bN7OvegEQZDoBEGik+gEQaKT6ARBopPoBEGik+gE8VAPwIiQ6ASxTozO/20B1QmJTrig+NQAkOgENQBUN1b8AyOU58YeRiF6AAAAAElFTkSuQmCC",
        "sessionId": "1826774073703927808"
    }
}

成功

到时候前端发送的数据中会包含sessionId,我们再根据sessionId去redis取值比对即可

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落落落sss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值