1. 登录功能

image.png

1.1 获取登录验证码

image.png

前端分析:

前端通过getCode方法发送请求

  1. getCode() {
  2. getCodeImg().then(res => {
  3. this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
  4. if (this.captchaOnOff) {
  5. this.codeUrl = "data:image/gif;base64," + res.img;
  6. this.loginForm.uuid = res.uuid;
  7. }
  8. });
  9. },

getCodeImg方法中封装了调用前端的api,getCodeImg源码:

  1. // 获取验证码
  2. export function getCodeImg() {
  3. return request({
  4. url: '/captchaImage',
  5. headers: {
  6. isToken: false
  7. },
  8. method: 'get',
  9. timeout: 20000
  10. })
  11. }

由上面的代码可知,request对后端的请求肯定还进行了一层封装
request中才创建axios实例

  1. // 创建axios实例
  2. const service = axios.create({
  3. // axios中请求配置有baseURL选项,表示请求URL公共部分
  4. baseURL: process.env.VUE_APP_BASE_API,
  5. // 超时
  6. timeout: 10000
  7. })

process.env.VUE_APP_BASE_API在vue.config.js中配置

  1. // webpack-dev-server 相关配置
  2. devServer: {
  3. host: '0.0.0.0',
  4. port: port,
  5. open: true,
  6. proxy: {
  7. // detail: https://cli.vuejs.org/config/#devserver-proxy
  8. [process.env.VUE_APP_BASE_API]: {
  9. target: `http://localhost:8080`,
  10. changeOrigin: true,
  11. pathRewrite: {
  12. ['^' + process.env.VUE_APP_BASE_API]: ''
  13. }
  14. }
  15. },
  16. disableHostCheck: true
  17. },

上述代码只是将process.env.VUE_APP_BASE_API代理到了http://localhost:8080,在代理的过程中会将http://localhost/dev-api转化为’ ‘

后端分析:

依赖:

  1. <!-- 验证码 -->
  2. <dependency>
  3. <groupId>com.github.penggle</groupId>
  4. <artifactId>kaptcha</artifactId>
  5. <exclusions>
  6. <exclusion>
  7. <artifactId>javax.servlet-api</artifactId>
  8. <groupId>javax.servlet</groupId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  12. <!-- redis 缓存操作 -->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-data-redis</artifactId>
  16. </dependency>

验证码配置:

  1. /**
  2. * 验证码配置
  3. *
  4. * @author ruoyi
  5. */
  6. @Configuration
  7. public class CaptchaConfig
  8. {
  9. @Bean(name = "captchaProducer")
  10. public DefaultKaptcha getKaptchaBean()
  11. {
  12. DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
  13. Properties properties = new Properties();
  14. // 是否有边框 默认为true 我们可以自己设置yes,no
  15. properties.setProperty(KAPTCHA_BORDER, "yes");
  16. // 验证码文本字符颜色 默认为Color.BLACK
  17. properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
  18. // 验证码图片宽度 默认为200
  19. properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
  20. // 验证码图片高度 默认为50
  21. properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
  22. // 验证码文本字符大小 默认为40
  23. properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
  24. // KAPTCHA_SESSION_KEY
  25. properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
  26. // 验证码文本字符长度 默认为5
  27. properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
  28. // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
  29. properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
  30. // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
  31. properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
  32. Config config = new Config(properties);
  33. defaultKaptcha.setConfig(config);
  34. return defaultKaptcha;
  35. }
  36. @Bean(name = "captchaProducerMath")
  37. public DefaultKaptcha getKaptchaBeanMath()
  38. {
  39. DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
  40. Properties properties = new Properties();
  41. // 是否有边框 默认为true 我们可以自己设置yes,no
  42. properties.setProperty(KAPTCHA_BORDER, "yes");
  43. // 边框颜色 默认为Color.BLACK
  44. properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
  45. // 验证码文本字符颜色 默认为Color.BLACK
  46. properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
  47. // 验证码图片宽度 默认为200
  48. properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
  49. // 验证码图片高度 默认为50
  50. properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
  51. // 验证码文本字符大小 默认为40
  52. properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
  53. // KAPTCHA_SESSION_KEY
  54. properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
  55. // 验证码文本生成器
  56. properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");
  57. // 验证码文本字符间距 默认为2
  58. properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
  59. // 验证码文本字符长度 默认为5
  60. properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
  61. // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
  62. properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
  63. // 验证码噪点颜色 默认为Color.BLACK
  64. properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
  65. // 干扰实现类
  66. properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
  67. // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
  68. properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
  69. Config config = new Config(properties);
  70. defaultKaptcha.setConfig(config);
  71. return defaultKaptcha;
  72. }
  73. }
  1. /**
  2. * 生成验证码
  3. */
  4. @GetMapping("/captchaImage")
  5. public AjaxResult getCode(HttpServletResponse response) throws IOException
  6. {
  7. AjaxResult ajax = AjaxResult.success();
  8. boolean captchaOnOff = configService.selectCaptchaOnOff();
  9. ajax.put("captchaOnOff", captchaOnOff);
  10. if (!captchaOnOff)
  11. {
  12. return ajax;
  13. }
  14. // 保存验证码信息
  15. String uuid = IdUtils.simpleUUID();
  16. String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
  17. String capStr = null, code = null;
  18. BufferedImage image = null;
  19. // 生成验证码
  20. String captchaType = RuoYiConfig.getCaptchaType();
  21. if ("math".equals(captchaType))
  22. {
  23. String capText = captchaProducerMath.createText();
  24. capStr = capText.substring(0, capText.lastIndexOf("@"));
  25. code = capText.substring(capText.lastIndexOf("@") + 1);
  26. image = captchaProducerMath.createImage(capStr);
  27. }
  28. else if ("char".equals(captchaType))
  29. {
  30. capStr = code = captchaProducer.createText();
  31. image = captchaProducer.createImage(capStr);
  32. }
  33. redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
  34. // 转换流信息写出
  35. FastByteArrayOutputStream os = new FastByteArrayOutputStream();
  36. try
  37. {
  38. ImageIO.write(image, "jpg", os);
  39. }
  40. catch (IOException e)
  41. {
  42. return AjaxResult.error(e.getMessage());
  43. }
  44. ajax.put("uuid", uuid);
  45. ajax.put("img", Base64.encode(os.toByteArray()));
  46. return ajax;
  47. }

captchOnOff表示验证码是否开:true表示开,false表示关
如果captchOnOff为false则直接return

  1. boolean captchaOnOff = configService.selectCaptchaOnOff();
  2. ajax.put("captchaOnOff", captchaOnOff);
  3. if (!captchaOnOff)
  4. {
  5. return ajax;
  6. }

保存验证码信息,以及生成验证码

  1. // 保存验证码信息
  2. String uuid = IdUtils.simpleUUID();
  3. String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
  4. String capStr = null, code = null;
  5. BufferedImage image = null;
  6. // 生成验证码
  7. String captchaType = RuoYiConfig.getCaptchaType();
  8. if ("math".equals(captchaType))
  9. {
  10. String capText = captchaProducerMath.createText();
  11. capStr = capText.substring(0, capText.lastIndexOf("@"));
  12. code = capText.substring(capText.lastIndexOf("@") + 1);
  13. image = captchaProducerMath.createImage(capStr);
  14. }
  15. redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
  16. // 转换流信息写出
  17. FastByteArrayOutputStream os = new FastByteArrayOutputStream();
  18. try
  19. {
  20. ImageIO.write(image, "jpg", os);
  21. }
  22. catch (IOException e)
  23. {
  24. return AjaxResult.error(e.getMessage());
  25. }
  26. ajax.put("uuid", uuid);
  27. ajax.put("img", Base64.encode(os.toByteArray()));
  28. return ajax;

capText中保存的是整个算术表达式例如:1+1=?@2,capStr中保存的是表达式1 + 1 = ?,code中保存的是2,image是生成的图片数据
将verifyKey和code以键值对的形式保存在redis中

1.2 获取验证码开关配置分析

  1. @Autowired
  2. private ISysConfigService configService;
  1. boolean captchaOnOff = configService.selectCaptchaOnOff();

ISysConfigService接口设置selectCaptchaOnOff

  1. public interface ISysConfigService
  2. {
  3. //...
  4. /**
  5. * 获取验证码开关
  6. *
  7. * @return true开启,false关闭
  8. */
  9. public boolean selectCaptchaOnOff();
  10. //...
  11. }

selectCaptchaOnOff方法实现:
从数据库中查询是否是开启,如果数据库中没有配置,则默认返回true,否则返回数据库中的值

  1. /**
  2. * 获取验证码开关
  3. *
  4. * @return true开启,false关闭
  5. */
  6. @Override
  7. public boolean selectCaptchaOnOff()
  8. {
  9. String captchaOnOff = selectConfigByKey("sys.account.captchaOnOff");
  10. if (StringUtils.isEmpty(captchaOnOff))
  11. {
  12. return true;
  13. }
  14. return Convert.toBool(captchaOnOff);
  15. }

selectConfigByKey的实现:

  1. 先从redis缓存中获取配置的值
  2. 如果缓存中没有则从mysql中取值,并把该值存入redis

    1. /**
    2. * 根据键名查询参数配置信息
    3. *
    4. * @param configKey 参数key
    5. * @return 参数键值
    6. */
    7. @Override
    8. public String selectConfigByKey(String configKey)
    9. {
    10. String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
    11. if (StringUtils.isNotEmpty(configValue))
    12. {
    13. return configValue;
    14. }
    15. SysConfig config = new SysConfig();
    16. config.setConfigKey(configKey);
    17. SysConfig retConfig = configMapper.selectConfig(config);
    18. if (StringUtils.isNotNull(retConfig))
    19. {
    20. redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
    21. return retConfig.getConfigValue();
    22. }
    23. return StringUtils.EMPTY;
    24. }

    selectConfig的实现逻辑:

  3. 定义mapper接口

    1. /**
    2. * 查询参数配置信息
    3. *
    4. * @param config 参数配置信息
    5. * @return 参数配置信息
    6. */
    7. public SysConfig selectConfig(SysConfig config);
  4. 实现

    1. <select id="selectConfig" parameterType="SysConfig" resultMap="SysConfigResult">
    2. <include refid="selectConfigVo"/>
    3. <include refid="sqlwhereSearch"/>
    4. </select>

    id为接口的方法名,parameterType为接口的参数类型,resultMap参数接口类型的映射

    1. <resultMap type="SysConfig" id="SysConfigResult">
    2. <id property="configId" column="config_id" />
    3. <result property="configName" column="config_name" />
    4. <result property="configKey" column="config_key" />
    5. <result property="configValue" column="config_value" />
    6. <result property="configType" column="config_type" />
    7. <result property="createBy" column="create_by" />
    8. <result property="createTime" column="create_time" />
    9. <result property="updateBy" column="update_by" />
    10. <result property="updateTime" column="update_time" />
    11. </resultMap>

    selectConfigVo为真正的查询语句sql:

    1. <sql id="selectConfigVo">
    2. select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark
    3. from sys_config
    4. </sql>

    sqlwhereSearch为筛选的语句

    1. <!-- 查询条件 -->
    2. <sql id="sqlwhereSearch">
    3. <where>
    4. <if test="configId !=null">
    5. and config_id = #{configId}
    6. </if>
    7. <if test="configKey !=null and configKey != ''">
    8. and config_key = #{configKey}
    9. </if>
    10. </where>
    11. </sql>

    1.3 验证码信息

    1. // 保存验证码信息
    2. String uuid = IdUtils.simpleUUID();
    3. String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
    4. String capStr = null, code = null;
    5. BufferedImage image = null;

    simpleUUID源码:

    1. /**
    2. * 简化的UUID,去掉了横线
    3. *
    4. * @return 简化的UUID,去掉了横线
    5. */
    6. public static String simpleUUID()
    7. {
    8. return UUID.randomUUID().toString(true);
    9. }

    Constants.CAPTCHA_CODE_KEY为常量,值为:

    1. /**
    2. * 验证码 redis key
    3. */
    4. public static final String CAPTCHA_CODE_KEY = "captcha_codes:";