当我们写一个登录接口时,都会想到使用一个图片生成一个动态验证码去增加我们功能的严谨性和复杂性,下面就开始讲述我们如何在项目中去使用它。
一、创建枚举
public enum LoginCodeEnum {
/**
* 算数
*/
ARITHMETIC,
/**
* 中文
*/
CHINESE,
/**
* 中文闪图
*/
CHINESE_GIF,
/**
* 闪图
*/
GIF,
SPEC
}
二、创建一个实体类
@Data
public class LoginCode {
/**
* 验证码配置
*/
private LoginCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration = 2L;
/**
* 验证码内容长度
*/
private int length = 2;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height = 36;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize = 25;
/**
* 验证码前缀
* @return
*/
private String codeKey;
public LoginCodeEnum getCodeType() {
return codeType;
}
}
三、创建配置类
@Configuration
public class LoginCodeConfig {
private LoginCode loginCode;
/**
* 获取验证码生产类
* @return
*/
public Captcha getCaptcha(){
if(Objects.isNull(loginCode)){
loginCode = new LoginCode();
if(Objects.isNull(loginCode.getCodeType())){
loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);
}
}
return switchCaptcha(loginCode);
}
/**
* 依据配置信息生产验证码
* @param loginCode
* @return
*/
private Captcha switchCaptcha(LoginCode loginCode){
Captcha captcha = null;
synchronized (this){
switch (loginCode.getCodeType()){
case ARITHMETIC:
captcha = new FixedArithmeticCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case CHINESE:
captcha = new ChineseCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case CHINESE_GIF:
captcha = new ChineseGifCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case GIF:
captcha = new GifCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
break;
case SPEC:
captcha = new SpecCaptcha(loginCode.getWidth(),loginCode.getHeight());
captcha.setLen(loginCode.getLength());
default:
System.out.println("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
}
}
if(StringUtils.isNotBlank(loginCode.getFontName())){
captcha.setFont(new Font(loginCode.getFontName(),Font.PLAIN,loginCode.getFontSize()));
}
return captcha;
}
static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
public FixedArithmeticCaptcha(int width,int height){
super(width,height);
}
@Override
protected char[] alphas() {
// 生成随机数字和运算符
int n1 = num(1, 10), n2 = num(1, 10);
int opt = num(3);
// 计算结果
int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
// 转换为字符运算符
char optChar = "+-x".charAt(opt);
this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
this.chars = String.valueOf(res);
return chars.toCharArray();
}
}
四、编写获取验证码的接口
@GetMapping("/getLoginCode")
public ResponseBean getLoginCode(){
Captcha captcha = loginCodeConfig.getCaptcha();
String uuid = "code-key-"+ UUID.randomUUID().toString().replaceAll("-","");
//当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
String captchaValue = captcha.text();
if(captcha.getCharType()-1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")){
captchaValue = captchaValue.split("\\.")[0];
}
// 保存
redisTemplate.opsForHash().put(uuid,"loginCode",captchaValue);
// 验证码信息
log.info("loginCode.uuid:{},captchaValue:{},loginProperties.getLoginCode().getExpiration():{}, TimeUnit.MINUTES:{}",uuid,captchaValue,captcha.text(), TimeUnit.MINUTES);
Map<String,Object> imgResult = new HashMap<String,Object>(2){{
put("img",captcha.toBase64());
put("loginCodeId",uuid);
}};
return new ResponseBean(1,"获取成功",imgResult);
}
五、登录时,相关验证码逻辑
//从redis获取存储的验证码
Object loginCodePwd=redisTemplate.opsForHash().get(bdOrganizeEmployeeDTO.getLoginCodeId(),"loginCode");
//验证码未获取到
if(loginCodePwd==null) return new ResponseBean(CodeEnum.LOGIN_CODE_TIMEOUT.getCode(), CodeEnum.LOGIN_CODE_TIMEOUT.getMsg(), null);
//从redis中删除,验证失败,让前端重新获取验证码,验证成功也不再需要
redisTemplate.opsForHash().delete(bdOrganizeEmployeeDTO.getLoginCodeId(),"loginCode");
//验证码填写不正确
if(!loginCodePwd.toString().equals(bdOrganizeEmployeeDTO.getLoginCode())) return new ResponseBean(CodeEnum.LOGIN_CODE_ERROR.getCode(), CodeEnum.LOGIN_CODE_ERROR.getMsg(), null);