springboot统一验证码组件设计(二)

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
函数计算FC,每月免费额度15元,12个月
简介: springboot统一验证码组件设计

所有的验证码处理器必须实现的接口,创建和验证

import com.zx.silverfox.common.exception.GlobalException;
import org.springframework.web.context.request.ServletWebRequest;
/**
 * @author zouwei
 */
public interface ValidateCodeProcessor {
    /**
     * 创建验证码
     *
     * @param request
     * @throws Exception
     */
    void create(ServletWebRequest request) throws GlobalException;
    /**
     * 校验验证码
     *
     * @param servletWebRequest
     */
    boolean validate(ServletWebRequest servletWebRequest) throws GlobalException;
}
复制代码

创建验证码接口:

import org.springframework.web.context.request.ServletWebRequest;
public interface ValidateCodeGenerator {
    /**
     * 生成验证码
     *
     * @param request
     * @return
     */
    ValidateCode createValidateCode(ServletWebRequest request);
}
复制代码

存取验证码接口:

import org.springframework.web.context.request.ServletWebRequest;
/** @author zouwei */
public interface ValidateCodeRepository {
    /**
     * 保存验证码
     *
     * @param request
     * @param code
     * @param validateCodeType
     */
    void save(
            ServletWebRequest request,
            ValidateCode code,
            ValidateCodeType validateCodeType,
            String codeKeyValue);
    /**
     * 获取验证码
     *
     * @param request
     * @param validateCodeType
     * @return
     */
    ValidateCode get(
            ServletWebRequest request, ValidateCodeType validateCodeType, String codeKeyValue);
    /**
     * 移除验证码
     *
     * @param request
     * @param codeType
     */
    void remove(ServletWebRequest request, ValidateCodeType codeType, String codeKeyValue);
}
复制代码

存取验证码的具体实现,我就使用了redis来做,其他的小伙伴也可以使用其他存储方案来做:

import com.zx.silverfox.common.validate.code.ValidateCode;
import com.zx.silverfox.common.validate.code.ValidateCodeRepository;
import com.zx.silverfox.common.validate.code.ValidateCodeType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.concurrent.TimeUnit;
/** @author zouwei */
public class RedisValidateCodeRepository implements ValidateCodeRepository {
    @Autowired private RedisTemplate<Object, Object> redisTemplate;
    @Override
    public void save(
            ServletWebRequest request,
            ValidateCode code,
            ValidateCodeType type,
            String codeKeyValue) {
        redisTemplate.opsForValue().set(buildKey(type, codeKeyValue), code, 30, TimeUnit.MINUTES);
    }
    @Override
    public ValidateCode get(ServletWebRequest request, ValidateCodeType type, String codeKeyValue) {
        Object value = redisTemplate.opsForValue().get(buildKey(type, codeKeyValue));
        if (value == null) {
            return null;
        }
        return (ValidateCode) value;
    }
    @Override
    public void remove(ServletWebRequest request, ValidateCodeType type, String codeKeyValue) {
        redisTemplate.delete(buildKey(type, codeKeyValue));
    }
    /**
     * @param type
     * @param key
     * @return
     */
    private String buildKey(ValidateCodeType type, String key) {
        return "code:" + type.toString().toLowerCase() + ":" + key;
    }
}
复制代码

发送验证码前需要处理的接口:

import com.zx.silverfox.common.exception.GlobalException;
import org.springframework.web.context.request.ServletWebRequest;
/**
 * @author zouwei
 */
public interface ValidateCodeHandler<C extends ValidateCode> {
    /**
     * 是否匹配成功
     * @param request
     * @param validateCodeType
     * @return
     */
    boolean support(ServletWebRequest request, ValidateCodeType validateCodeType);
    /**
     * 开始处理发送验证码前的逻辑
     * @param request
     * @param validateCodeType
     * @param validateCode
     * @throws GlobalException
     */
    void beforeSend(ServletWebRequest request, ValidateCodeType validateCodeType, C validateCode) throws GlobalException;
}
复制代码

验证码实体类:

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/** @author zouwei */
@Data
public class ValidateCode implements Serializable {
    private static final long serialVersionUID = -7827043337909063779L;
    private String code;
    private long expireInSeconds;
    private LocalDateTime expireTime;
    public ValidateCode(String code, LocalDateTime expireTime) {
        this.code = code;
        this.expireTime = expireTime;
    }
    public ValidateCode(String code, long expireInSeconds) {
        this.code = code;
        this.expireInSeconds = expireInSeconds;
        this.expireTime = LocalDateTime.now().plusSeconds(expireInSeconds);
    }
    /**
     * 判断是否过期
     *
     * @return
     */
    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }
    /**
     * 转换成分钟
     *
     * @return
     */
    public long minute() {
        return this.expireInSeconds / 60;
    }
}
复制代码

各种验证码类型,可无限扩展:

public enum ValidateCodeType {
    /** 短信验证码 */
    SMS {
        @Override
        public String getParamNameOnValidate() {
            return "smsCode";
        }
    },
    /** 图片验证码 */
    IMAGE {
        @Override
        public String getParamNameOnValidate() {
            return "imageCode";
        }
    },
    /** 滑动图片验证码 */
    SLIDE {
        @Override
        public String getParamNameOnValidate() {
            return "slideCode";
        }
    };
    public abstract String getParamNameOnValidate();
}
复制代码

还有相关配置类:

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/** @author zouwei */
@Data
@Configuration
@ConfigurationProperties(prefix = "validate.code")
public class ValidateCodeProperties {
    /** 图像验证码 */
    private ImageProperties image = new ImageProperties();
    /** 短信验证码 */
    private SmsProperties sms = new SmsProperties();
    /** 滑动验证码 */
    private SlideImageProperties slide = new SlideImageProperties();
    @Data
    @EqualsAndHashCode(callSuper = true)
    public static class SlideImageProperties extends CodeProperties {
        private String generatorUrl = "/code/slide";
    }
    @Data
    @EqualsAndHashCode(callSuper = true)
    public static class ImageProperties extends CodeProperties {
        private int length = 6;
        private int height = 23;
        private int width = 67;
        private String generatorUrl = "/code/image";
    }
    @Data
    @EqualsAndHashCode(callSuper = true)
    public static class SmsProperties extends CodeProperties {
        private int length = 6;
        private String generatorUrl = "/code/sms";
    }
    @Data
    public abstract static class CodeProperties {
        private long expiredInSecond = 300;
        private String[] filterUrls;
        private String generatorUrl;
    }
}
复制代码

为了开发者使用方便,我也模仿spring boot的方式使用注解自动化配置:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** @author zouwei */
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidateCodeConfigSelector.class)
public @interface EnableValidateCode {
    /**
     * 验证码实现类
     *
     * @return
     */
    Class<? extends AbstractValidateCodeProcessor>[] value() default {
        ImageValidateCodeProcessor.class
    };
    /**
     * 验证码存储方式
     *
     * @return
     */
    Class<? extends ValidateCodeRepository> repository() default RedisValidateCodeRepository.class;
}
复制代码
import com.zx.silverfox.common.filter.ValidateCodeFilter;
import com.zx.silverfox.common.validate.code.AbstractValidateCodeProcessor;
import com.zx.silverfox.common.validate.code.ValidateCodeRepository;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import java.util.Map;
public class ValidateCodeConfigSelector implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attributeMap =
                importingClassMetadata.getAnnotationAttributes(
                        EnableValidateCode.class.getName());
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
        Class<? extends ValidateCodeRepository> repositoryClass = attributes.getClass("repository");
        Class<? extends AbstractValidateCodeProcessor>[] imageProcessorClass =
                (Class<? extends AbstractValidateCodeProcessor>[])
                        attributes.getClassArray("value");
        if (!registry.containsBeanDefinition("validateCodeRepository")) {
            registry.registerBeanDefinition(
                    "validateCodeRepository", new RootBeanDefinition(repositoryClass));
        }
        if (ArrayUtils.isNotEmpty(imageProcessorClass)) {
            for (Class<? extends AbstractValidateCodeProcessor> clazz : imageProcessorClass) {
                registry.registerBeanDefinition(
                        clazz.getSimpleName(), new RootBeanDefinition(clazz));
            }
        }
        if (!registry.containsBeanDefinition("validateCodeFilter")) {
            registry.registerBeanDefinition(
                    "validateCodeFilter", new RootBeanDefinition(ValidateCodeFilter.class));
        }
    }
}
复制代码

上述代码基本框架已经完成,后续代码就是真正地实现图片验证码及短信验证码:

简单图片验证码:

import com.zx.silverfox.common.validate.code.ValidateCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.awt.image.BufferedImage;
/** @author zouwei */
@Data
@EqualsAndHashCode(callSuper = true)
public class ImageValidateCode extends ValidateCode {
    private transient BufferedImage image;
    public ImageValidateCode(BufferedImage image, String code, long expireInSeconds) {
        super(code, expireInSeconds);
        this.image = image;
    }
}
复制代码
import com.zx.silverfox.common.properties.ValidateCodeProperties;
import com.zx.silverfox.common.validate.code.ValidateCode;
import com.zx.silverfox.common.validate.code.ValidateCodeGenerator;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/** @author zouwei */
public class ImageValidateCodeGenerator implements ValidateCodeGenerator {
    private ValidateCodeProperties.ImageProperties imageProperties;
    public ImageValidateCodeGenerator(ValidateCodeProperties.ImageProperties imageProperties) {
        this.imageProperties = imageProperties;
    }
    @Override
    public ValidateCode createValidateCode(ServletWebRequest request) {
        int height =
                ServletRequestUtils.getIntParameter(
                        request.getRequest(), "height", imageProperties.getHeight());
        int width =
                ServletRequestUtils.getIntParameter(
                        request.getRequest(), "width", imageProperties.getWidth());
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        Random random = new Random();
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }
        String sRand = "";
        for (int i = 0; i < imageProperties.getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += rand;
            g.setColor(
                    new Color(
                            20 + random.nextInt(110),
                            20 + random.nextInt(110),
                            20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }
        g.dispose();
        return new ImageValidateCode(image, sRand, imageProperties.getExpiredInSecond());
    }
    /**
     * 生成随机背景条纹
     *
     * @param fc
     * @param bc
     * @return
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
}



相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
1天前
|
SQL 前端开发 NoSQL
SpringBoot+Vue 实现图片验证码功能需求
这篇文章介绍了如何在SpringBoot+Vue项目中实现图片验证码功能,包括后端生成与校验验证码的方法以及前端展示验证码的实现步骤。
SpringBoot+Vue 实现图片验证码功能需求
|
6天前
|
SQL JavaScript 前端开发
vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)
这篇文章详细介绍了如何在Vue.js中使用分页组件展示从数据库查询出来的数据,包括前端Vue页面的表格和分页组件代码,以及后端SpringBoot的控制层和SQL查询语句。
vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)
|
2天前
|
NoSQL JavaScript Java
SpringBoot+Vue+Redis实现验证码功能、一个小时只允许发三次验证码。一次验证码有效期二分钟。SpringBoot整合Redis
这篇文章介绍了如何使用SpringBoot、Vue和Redis实现验证码功能,包括验证码的有效期控制和每小时发送次数限制,以及具体的实现步骤和效果演示。
|
2月前
|
缓存 NoSQL Java
案例 采用Springboot默认的缓存方案Simple在三层架构中完成一个手机验证码生成校验的程序
案例 采用Springboot默认的缓存方案Simple在三层架构中完成一个手机验证码生成校验的程序
79 5
|
2月前
|
NoSQL 前端开发 Java
技术笔记:springboot分布式锁组件spring
技术笔记:springboot分布式锁组件spring
32 1
|
2月前
|
Java API 数据安全/隐私保护
在Spring Boot中,过滤器(Filter)是一种非常有用的组件
在Spring Boot中,过滤器(Filter)是一种非常有用的组件
52 6
|
2月前
|
Java
springboot用户登录使用验证码
springboot用户登录使用验证码
18 0
|
2月前
|
开发框架 安全 Java
信息打点-语言框架&开发组件&FastJson&Shiro&Log4j&SpringBoot等
信息打点-语言框架&开发组件&FastJson&Shiro&Log4j&SpringBoot等
|
2月前
|
消息中间件 Dubbo Java
SpringClou、SpringBoot、SpringCloud-Alibaba各个组件版本对应关系
SpringClou、SpringBoot、SpringCloud-Alibaba各个组件版本对应关系
211 0
|
3月前
|
前端开发 网络架构
1天搞定SpringBoot+Vue全栈开发 (8)前端路由VueRouter(进行组件切换)
1天搞定SpringBoot+Vue全栈开发 (8)前端路由VueRouter(进行组件切换)