作者 RuoYi

验证码类型支持(数组计算、字符验证)

@@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
21 <druid.version>1.1.14</druid.version> 21 <druid.version>1.1.14</druid.version>
22 <bitwalker.version>1.19</bitwalker.version> 22 <bitwalker.version>1.19</bitwalker.version>
23 <swagger.version>2.9.2</swagger.version> 23 <swagger.version>2.9.2</swagger.version>
  24 + <kaptcha.version>2.3.2</kaptcha.version>
24 <pagehelper.boot.version>1.2.5</pagehelper.boot.version> 25 <pagehelper.boot.version>1.2.5</pagehelper.boot.version>
25 <fastjson.version>1.2.70</fastjson.version> 26 <fastjson.version>1.2.70</fastjson.version>
26 <oshi.version>3.9.1</oshi.version> 27 <oshi.version>3.9.1</oshi.version>
@@ -138,6 +139,13 @@ @@ -138,6 +139,13 @@
138 <version>${jwt.version}</version> 139 <version>${jwt.version}</version>
139 </dependency> 140 </dependency>
140 141
  142 + <!--验证码 -->
  143 + <dependency>
  144 + <groupId>com.github.penggle</groupId>
  145 + <artifactId>kaptcha</artifactId>
  146 + <version>${kaptcha.version}</version>
  147 + </dependency>
  148 +
141 <!-- 定时任务--> 149 <!-- 定时任务-->
142 <dependency> 150 <dependency>
143 <groupId>com.ruoyi</groupId> 151 <groupId>com.ruoyi</groupId>
1 package com.ruoyi.web.controller.common; 1 package com.ruoyi.web.controller.common;
2 2
3 -import java.io.ByteArrayOutputStream; 3 +import java.awt.image.BufferedImage;
4 import java.io.IOException; 4 import java.io.IOException;
5 import java.util.concurrent.TimeUnit; 5 import java.util.concurrent.TimeUnit;
  6 +import javax.annotation.Resource;
  7 +import javax.imageio.ImageIO;
6 import javax.servlet.http.HttpServletResponse; 8 import javax.servlet.http.HttpServletResponse;
7 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.beans.factory.annotation.Value;
  11 +import org.springframework.util.FastByteArrayOutputStream;
8 import org.springframework.web.bind.annotation.GetMapping; 12 import org.springframework.web.bind.annotation.GetMapping;
9 import org.springframework.web.bind.annotation.RestController; 13 import org.springframework.web.bind.annotation.RestController;
  14 +import com.google.code.kaptcha.Producer;
10 import com.ruoyi.common.constant.Constants; 15 import com.ruoyi.common.constant.Constants;
11 import com.ruoyi.common.core.domain.AjaxResult; 16 import com.ruoyi.common.core.domain.AjaxResult;
12 import com.ruoyi.common.core.redis.RedisCache; 17 import com.ruoyi.common.core.redis.RedisCache;
13 -import com.ruoyi.common.utils.VerifyCodeUtils;  
14 import com.ruoyi.common.utils.sign.Base64; 18 import com.ruoyi.common.utils.sign.Base64;
15 import com.ruoyi.common.utils.uuid.IdUtils; 19 import com.ruoyi.common.utils.uuid.IdUtils;
16 20
@@ -22,41 +26,61 @@ import com.ruoyi.common.utils.uuid.IdUtils; @@ -22,41 +26,61 @@ import com.ruoyi.common.utils.uuid.IdUtils;
22 @RestController 26 @RestController
23 public class CaptchaController 27 public class CaptchaController
24 { 28 {
  29 + @Resource(name = "captchaProducer")
  30 + private Producer captchaProducer;
  31 +
  32 + @Resource(name = "captchaProducerMath")
  33 + private Producer captchaProducerMath;
  34 +
25 @Autowired 35 @Autowired
26 private RedisCache redisCache; 36 private RedisCache redisCache;
27 37
  38 + // 验证码类型
  39 + @Value("${ruoyi.captchaType}")
  40 + private String captchaType;
  41 +
28 /** 42 /**
29 * 生成验证码 43 * 生成验证码
30 */ 44 */
31 @GetMapping("/captchaImage") 45 @GetMapping("/captchaImage")
32 public AjaxResult getCode(HttpServletResponse response) throws IOException 46 public AjaxResult getCode(HttpServletResponse response) throws IOException
33 { 47 {
34 - // 生成随机字串  
35 - String verifyCode = VerifyCodeUtils.generateVerifyCode(4);  
36 - // 唯一标识 48 + // 保存验证码信息
37 String uuid = IdUtils.simpleUUID(); 49 String uuid = IdUtils.simpleUUID();
38 String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; 50 String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
39 51
40 - redisCache.setCacheObject(verifyKey, verifyCode, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);  
41 - // 生成图片  
42 - int w = 111, h = 36;  
43 - ByteArrayOutputStream stream = new ByteArrayOutputStream();  
44 - VerifyCodeUtils.outputImage(w, h, stream, verifyCode);  
45 - try 52 + String capStr = null, code = null;
  53 + BufferedImage image = null;
  54 +
  55 + // 生成验证码
  56 + if ("math".equals(captchaType))
46 { 57 {
47 - AjaxResult ajax = AjaxResult.success();  
48 - ajax.put("uuid", uuid);  
49 - ajax.put("img", Base64.encode(stream.toByteArray()));  
50 - return ajax; 58 + String capText = captchaProducerMath.createText();
  59 + capStr = capText.substring(0, capText.lastIndexOf("@"));
  60 + code = capText.substring(capText.lastIndexOf("@") + 1);
  61 + image = captchaProducerMath.createImage(capStr);
51 } 62 }
52 - catch (Exception e) 63 + else if ("char".equals(captchaType))
53 { 64 {
54 - e.printStackTrace();  
55 - return AjaxResult.error(e.getMessage()); 65 + capStr = code = captchaProducer.createText();
  66 + image = captchaProducer.createImage(capStr);
56 } 67 }
57 - finally 68 +
  69 + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
  70 + // 转换流信息写出
  71 + FastByteArrayOutputStream os = new FastByteArrayOutputStream();
  72 + try
58 { 73 {
59 - stream.close(); 74 + ImageIO.write(image, "jpg", os);
  75 + }
  76 + catch (IOException e)
  77 + {
  78 + return AjaxResult.error(e.getMessage());
60 } 79 }
  80 +
  81 + AjaxResult ajax = AjaxResult.success();
  82 + ajax.put("uuid", uuid);
  83 + ajax.put("img", Base64.encode(os.toByteArray()));
  84 + return ajax;
61 } 85 }
62 } 86 }
@@ -12,6 +12,8 @@ ruoyi: @@ -12,6 +12,8 @@ ruoyi:
12 profile: D:/ruoyi/uploadPath 12 profile: D:/ruoyi/uploadPath
13 # 获取ip地址开关 13 # 获取ip地址开关
14 addressEnabled: false 14 addressEnabled: false
  15 + # 验证码类型 math 数组计算 char 字符验证
  16 + captchaType: char
15 17
16 # 开发环境配置 18 # 开发环境配置
17 server: 19 server:
@@ -35,6 +35,18 @@ @@ -35,6 +35,18 @@
35 <artifactId>druid-spring-boot-starter</artifactId> 35 <artifactId>druid-spring-boot-starter</artifactId>
36 </dependency> 36 </dependency>
37 37
  38 + <!-- 验证码 -->
  39 + <dependency>
  40 + <groupId>com.github.penggle</groupId>
  41 + <artifactId>kaptcha</artifactId>
  42 + <exclusions>
  43 + <exclusion>
  44 + <artifactId>javax.servlet-api</artifactId>
  45 + <groupId>javax.servlet</groupId>
  46 + </exclusion>
  47 + </exclusions>
  48 + </dependency>
  49 +
38 <!-- 获取系统信息 --> 50 <!-- 获取系统信息 -->
39 <dependency> 51 <dependency>
40 <groupId>com.github.oshi</groupId> 52 <groupId>com.github.oshi</groupId>
  1 +package com.ruoyi.framework.config;
  2 +
  3 +import java.util.Properties;
  4 +import org.springframework.context.annotation.Bean;
  5 +import org.springframework.context.annotation.Configuration;
  6 +import com.google.code.kaptcha.impl.DefaultKaptcha;
  7 +import com.google.code.kaptcha.util.Config;
  8 +import static com.google.code.kaptcha.Constants.*;
  9 +
  10 +/**
  11 + * 验证码配置
  12 + *
  13 + * @author ruoyi
  14 + */
  15 +@Configuration
  16 +public class CaptchaConfig
  17 +{
  18 + @Bean(name = "captchaProducer")
  19 + public DefaultKaptcha getKaptchaBean()
  20 + {
  21 + DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
  22 + Properties properties = new Properties();
  23 + // 是否有边框 默认为true 我们可以自己设置yes,no
  24 + properties.setProperty(KAPTCHA_BORDER, "yes");
  25 + // 验证码文本字符颜色 默认为Color.BLACK
  26 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
  27 + // 验证码图片宽度 默认为200
  28 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
  29 + // 验证码图片高度 默认为50
  30 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
  31 + // 验证码文本字符大小 默认为40
  32 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
  33 + // KAPTCHA_SESSION_KEY
  34 + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
  35 + // 验证码文本字符长度 默认为5
  36 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
  37 + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
  38 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
  39 + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
  40 + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
  41 + Config config = new Config(properties);
  42 + defaultKaptcha.setConfig(config);
  43 + return defaultKaptcha;
  44 + }
  45 +
  46 + @Bean(name = "captchaProducerMath")
  47 + public DefaultKaptcha getKaptchaBeanMath()
  48 + {
  49 + DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
  50 + Properties properties = new Properties();
  51 + // 是否有边框 默认为true 我们可以自己设置yes,no
  52 + properties.setProperty(KAPTCHA_BORDER, "yes");
  53 + // 边框颜色 默认为Color.BLACK
  54 + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
  55 + // 验证码文本字符颜色 默认为Color.BLACK
  56 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
  57 + // 验证码图片宽度 默认为200
  58 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
  59 + // 验证码图片高度 默认为50
  60 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
  61 + // 验证码文本字符大小 默认为40
  62 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
  63 + // KAPTCHA_SESSION_KEY
  64 + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
  65 + // 验证码文本生成器
  66 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");
  67 + // 验证码文本字符间距 默认为2
  68 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
  69 + // 验证码文本字符长度 默认为5
  70 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
  71 + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
  72 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
  73 + // 验证码噪点颜色 默认为Color.BLACK
  74 + properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
  75 + // 干扰实现类
  76 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
  77 + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
  78 + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
  79 + Config config = new Config(properties);
  80 + defaultKaptcha.setConfig(config);
  81 + return defaultKaptcha;
  82 + }
  83 +}
  1 +package com.ruoyi.framework.config;
  2 +
  3 +import java.util.Random;
  4 +import com.google.code.kaptcha.text.impl.DefaultTextCreator;
  5 +
  6 +/**
  7 + * 验证码文本生成器
  8 + *
  9 + * @author ruoyi
  10 + */
  11 +public class KaptchaTextCreator extends DefaultTextCreator
  12 +{
  13 + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
  14 +
  15 + @Override
  16 + public String getText()
  17 + {
  18 + Integer result = 0;
  19 + Random random = new Random();
  20 + int x = random.nextInt(10);
  21 + int y = random.nextInt(10);
  22 + StringBuilder suChinese = new StringBuilder();
  23 + int randomoperands = (int) Math.round(Math.random() * 2);
  24 + if (randomoperands == 0)
  25 + {
  26 + result = x * y;
  27 + suChinese.append(CNUMBERS[x]);
  28 + suChinese.append("*");
  29 + suChinese.append(CNUMBERS[y]);
  30 + }
  31 + else if (randomoperands == 1)
  32 + {
  33 + if (!(x == 0) && y % x == 0)
  34 + {
  35 + result = y / x;
  36 + suChinese.append(CNUMBERS[y]);
  37 + suChinese.append("/");
  38 + suChinese.append(CNUMBERS[x]);
  39 + }
  40 + else
  41 + {
  42 + result = x + y;
  43 + suChinese.append(CNUMBERS[x]);
  44 + suChinese.append("+");
  45 + suChinese.append(CNUMBERS[y]);
  46 + }
  47 + }
  48 + else if (randomoperands == 2)
  49 + {
  50 + if (x >= y)
  51 + {
  52 + result = x - y;
  53 + suChinese.append(CNUMBERS[x]);
  54 + suChinese.append("-");
  55 + suChinese.append(CNUMBERS[y]);
  56 + }
  57 + else
  58 + {
  59 + result = y - x;
  60 + suChinese.append(CNUMBERS[y]);
  61 + suChinese.append("-");
  62 + suChinese.append(CNUMBERS[x]);
  63 + }
  64 + }
  65 + else
  66 + {
  67 + result = x + y;
  68 + suChinese.append(CNUMBERS[x]);
  69 + suChinese.append("+");
  70 + suChinese.append(CNUMBERS[y]);
  71 + }
  72 + suChinese.append("=?@" + result);
  73 + return suChinese.toString();
  74 + }
  75 +}
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> 29 <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
30 </el-input> 30 </el-input>
31 <div class="login-code"> 31 <div class="login-code">
32 - <img :src="codeUrl" @click="getCode" /> 32 + <img :src="codeUrl" @click="getCode" class="login-code-img"/>
33 </div> 33 </div>
34 </el-form-item> 34 </el-form-item>
35 <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> 35 <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
@@ -200,4 +200,7 @@ export default { @@ -200,4 +200,7 @@ export default {
200 font-size: 12px; 200 font-size: 12px;
201 letter-spacing: 1px; 201 letter-spacing: 1px;
202 } 202 }
  203 +.login-code-img {
  204 + height: 38px;
  205 +}
203 </style> 206 </style>