1. 登录功能
1.1 获取登录验证码
前端分析:
前端通过getCode方法发送请求
getCode() {getCodeImg().then(res => {this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;if (this.captchaOnOff) {this.codeUrl = "data:image/gif;base64," + res.img;this.loginForm.uuid = res.uuid;}});},
getCodeImg方法中封装了调用前端的api,getCodeImg源码:
// 获取验证码export function getCodeImg() {return request({url: '/captchaImage',headers: {isToken: false},method: 'get',timeout: 20000})}
由上面的代码可知,request对后端的请求肯定还进行了一层封装
request中才创建axios实例
// 创建axios实例const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: process.env.VUE_APP_BASE_API,// 超时timeout: 10000})
process.env.VUE_APP_BASE_API在vue.config.js中配置
// webpack-dev-server 相关配置devServer: {host: '0.0.0.0',port: port,open: true,proxy: {// detail: https://cli.vuejs.org/config/#devserver-proxy[process.env.VUE_APP_BASE_API]: {target: `http://localhost:8080`,changeOrigin: true,pathRewrite: {['^' + process.env.VUE_APP_BASE_API]: ''}}},disableHostCheck: true},
上述代码只是将process.env.VUE_APP_BASE_API代理到了http://localhost:8080,在代理的过程中会将http://localhost/dev-api转化为’ ‘
后端分析:
依赖:
<!-- 验证码 --><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><exclusions><exclusion><artifactId>javax.servlet-api</artifactId><groupId>javax.servlet</groupId></exclusion></exclusions></dependency><!-- redis 缓存操作 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
验证码配置:
/*** 验证码配置** @author ruoyi*/@Configurationpublic class CaptchaConfig{@Bean(name = "captchaProducer")public DefaultKaptcha getKaptchaBean(){DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();// 是否有边框 默认为true 我们可以自己设置yes,noproperties.setProperty(KAPTCHA_BORDER, "yes");// 验证码文本字符颜色 默认为Color.BLACKproperties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");// 验证码图片宽度 默认为200properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");// 验证码图片高度 默认为50properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");// 验证码文本字符大小 默认为40properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");// KAPTCHA_SESSION_KEYproperties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");// 验证码文本字符长度 默认为5properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpyproperties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}@Bean(name = "captchaProducerMath")public DefaultKaptcha getKaptchaBeanMath(){DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();// 是否有边框 默认为true 我们可以自己设置yes,noproperties.setProperty(KAPTCHA_BORDER, "yes");// 边框颜色 默认为Color.BLACKproperties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");// 验证码文本字符颜色 默认为Color.BLACKproperties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");// 验证码图片宽度 默认为200properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");// 验证码图片高度 默认为50properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");// 验证码文本字符大小 默认为40properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");// KAPTCHA_SESSION_KEYproperties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");// 验证码文本生成器properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");// 验证码文本字符间距 默认为2properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");// 验证码文本字符长度 默认为5properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");// 验证码噪点颜色 默认为Color.BLACKproperties.setProperty(KAPTCHA_NOISE_COLOR, "white");// 干扰实现类properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpyproperties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}}
/*** 生成验证码*/@GetMapping("/captchaImage")public AjaxResult getCode(HttpServletResponse response) throws IOException{AjaxResult ajax = AjaxResult.success();boolean captchaOnOff = configService.selectCaptchaOnOff();ajax.put("captchaOnOff", captchaOnOff);if (!captchaOnOff){return ajax;}// 保存验证码信息String uuid = IdUtils.simpleUUID();String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;String capStr = null, code = null;BufferedImage image = null;// 生成验证码String captchaType = RuoYiConfig.getCaptchaType();if ("math".equals(captchaType)){String capText = captchaProducerMath.createText();capStr = capText.substring(0, capText.lastIndexOf("@"));code = capText.substring(capText.lastIndexOf("@") + 1);image = captchaProducerMath.createImage(capStr);}else if ("char".equals(captchaType)){capStr = code = captchaProducer.createText();image = captchaProducer.createImage(capStr);}redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);// 转换流信息写出FastByteArrayOutputStream os = new FastByteArrayOutputStream();try{ImageIO.write(image, "jpg", os);}catch (IOException e){return AjaxResult.error(e.getMessage());}ajax.put("uuid", uuid);ajax.put("img", Base64.encode(os.toByteArray()));return ajax;}
captchOnOff表示验证码是否开:true表示开,false表示关
如果captchOnOff为false则直接return
boolean captchaOnOff = configService.selectCaptchaOnOff();ajax.put("captchaOnOff", captchaOnOff);if (!captchaOnOff){return ajax;}
保存验证码信息,以及生成验证码
// 保存验证码信息String uuid = IdUtils.simpleUUID();String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;String capStr = null, code = null;BufferedImage image = null;// 生成验证码String captchaType = RuoYiConfig.getCaptchaType();if ("math".equals(captchaType)){String capText = captchaProducerMath.createText();capStr = capText.substring(0, capText.lastIndexOf("@"));code = capText.substring(capText.lastIndexOf("@") + 1);image = captchaProducerMath.createImage(capStr);}redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);// 转换流信息写出FastByteArrayOutputStream os = new FastByteArrayOutputStream();try{ImageIO.write(image, "jpg", os);}catch (IOException e){return AjaxResult.error(e.getMessage());}ajax.put("uuid", uuid);ajax.put("img", Base64.encode(os.toByteArray()));return ajax;
capText中保存的是整个算术表达式例如:1+1=?@2,capStr中保存的是表达式1 + 1 = ?,code中保存的是2,image是生成的图片数据
将verifyKey和code以键值对的形式保存在redis中
1.2 获取验证码开关配置分析
@Autowiredprivate ISysConfigService configService;
boolean captchaOnOff = configService.selectCaptchaOnOff();
ISysConfigService接口设置selectCaptchaOnOff
public interface ISysConfigService{//.../*** 获取验证码开关** @return true开启,false关闭*/public boolean selectCaptchaOnOff();//...}
selectCaptchaOnOff方法实现:
从数据库中查询是否是开启,如果数据库中没有配置,则默认返回true,否则返回数据库中的值
/*** 获取验证码开关** @return true开启,false关闭*/@Overridepublic boolean selectCaptchaOnOff(){String captchaOnOff = selectConfigByKey("sys.account.captchaOnOff");if (StringUtils.isEmpty(captchaOnOff)){return true;}return Convert.toBool(captchaOnOff);}
selectConfigByKey的实现:
- 先从redis缓存中获取配置的值
如果缓存中没有则从mysql中取值,并把该值存入redis
/*** 根据键名查询参数配置信息** @param configKey 参数key* @return 参数键值*/@Overridepublic String selectConfigByKey(String configKey){String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));if (StringUtils.isNotEmpty(configValue)){return configValue;}SysConfig config = new SysConfig();config.setConfigKey(configKey);SysConfig retConfig = configMapper.selectConfig(config);if (StringUtils.isNotNull(retConfig)){redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());return retConfig.getConfigValue();}return StringUtils.EMPTY;}
selectConfig的实现逻辑:
定义mapper接口
/*** 查询参数配置信息** @param config 参数配置信息* @return 参数配置信息*/public SysConfig selectConfig(SysConfig config);
实现
<select id="selectConfig" parameterType="SysConfig" resultMap="SysConfigResult"><include refid="selectConfigVo"/><include refid="sqlwhereSearch"/></select>
id为接口的方法名,parameterType为接口的参数类型,resultMap参数接口类型的映射
<resultMap type="SysConfig" id="SysConfigResult"><id property="configId" column="config_id" /><result property="configName" column="config_name" /><result property="configKey" column="config_key" /><result property="configValue" column="config_value" /><result property="configType" column="config_type" /><result property="createBy" column="create_by" /><result property="createTime" column="create_time" /><result property="updateBy" column="update_by" /><result property="updateTime" column="update_time" /></resultMap>
selectConfigVo为真正的查询语句sql:
<sql id="selectConfigVo">select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remarkfrom sys_config</sql>
sqlwhereSearch为筛选的语句
<!-- 查询条件 --><sql id="sqlwhereSearch"><where><if test="configId !=null">and config_id = #{configId}</if><if test="configKey !=null and configKey != ''">and config_key = #{configKey}</if></where></sql>
1.3 验证码信息
// 保存验证码信息String uuid = IdUtils.simpleUUID();String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;String capStr = null, code = null;BufferedImage image = null;
simpleUUID源码:
/*** 简化的UUID,去掉了横线** @return 简化的UUID,去掉了横线*/public static String simpleUUID(){return UUID.randomUUID().toString(true);}
Constants.CAPTCHA_CODE_KEY为常量,值为:
/*** 验证码 redis key*/public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
