所有的验证码处理器必须实现的接口,创建和验证
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); } }