![ruoyiCloud架构图](https://img-blog.csdnimg.cn/direct/de700288609b4f40ade1e0c2a3e0ebe7.jpeg#pic_center)
一、验证码生成
1、前端代码实现
(1) ruoyi-ui/src/views/login.vue (html部分,点击图片触发获取验证码动作)
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
(2) ruoyi-ui/src/views/login.vue (js部分导入api目录下的login.js文件中的getCodeImg方法)
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
(3) ruoyi-ui/src/views/login.vue文件中的getCode方法 (使用导入的getCodeImg方法发送请求,并根据响应信息拼接codeUrl)
methods: {
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
},
(4) ruoyi-ui/src/api/login.js文件中的getCodeImg方法
// 获取验证码
export function getCodeImg() {
return request({
url: '/code',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
2、后端代码实现
(1) com/ruoyi/gateway/config/RouterFunctionConfiguration.java (Spring WebFlux中定义路由的方法,用于创建路由规则。定义了一个GET请求路径为"/code"且接受的媒体类型为TEXT_PLAIN的请求断言,当接收到GET请求且路径为"/code"且接受的媒体类型为TEXT_PLAIN时,将请求交给validateCodeHandler处理)
@Configuration
public class RouterFunctionConfiguration
{
@Autowired
private ValidateCodeHandler validateCodeHandler;
@SuppressWarnings("rawtypes")
@Bean
public RouterFunction routerFunction()
{
return RouterFunctions.route(
RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
validateCodeHandler);
}
}
(2) com/ruoyi/gateway/handler/ValidateCodeHandler.java( 实现了接口HandlerFunction HandlerFunction 是 Spring WebFlux 框架的一个接口,用于处理 HTTP 请求并生成响应。HandlerFunction 接口只包含一个 handle () 方法, 该方法接受一个ServerRequest 对象作为输入,返回一个 Mono 类型的响应对象)
@Component
public class ValidateCodeHandler implements HandlerFunction<ServerResponse> {
@Autowired
private ValidateCodeService validateCodeService;
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
AjaxResult ajax;
try {
ajax = validateCodeService.createCaptcha();
} catch (CaptchaException | IOException e) {
return Mono.error(e);
}
return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax));
}
}
(3) com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java (实现了接口ValidateCodeService 后端生成验证码逻辑代码、保存在redis缓存中并以流的方式写到响应的中)
@Override
public AjaxResult createCaptcha() throws IOException, CaptchaException {
AjaxResult ajax = AjaxResult.success();
boolean captchaEnabled = captchaProperties.getEnabled();
ajax.put("captchaEnabled", captchaEnabled);
if (!captchaEnabled) {
return ajax;
}
String uuid = IdUtils.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
String captchaType = captchaProperties.getType();
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);
}
redisService.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;
}
二、登录验证
1、前端代码实现
(1) ruoyi-ui/src/views/login.vue (html部分。点击登录按钮,触发登录处理事件)
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin">
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
(2) ruoyi-ui/src/views/login.vue (js部分。登录处理事件调用 Vuex 中的 Login action,传入 this.loginForm 作为参数)
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
}
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
}
});
}
});
}
(3) ruoyi-ui/src/store/modules/user.js (js部分。登录处理事件 Login action,调用login.js中的login方法)
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
let data = res.data
setToken(data.access_token)
commit('SET_TOKEN', data.access_token)
setExpiresIn(data.expires_in)
commit('SET_EXPIRES_IN', data.expires_in)
resolve()
}).catch(error => {
reject(error)
})
})
},
(4) ruoyi-ui/src/api/login.js (请求后端url:/auth/login)
export function login(username, password, code, uuid) {
return request({
url: '/auth/login',
headers: {
isToken: false
},
method: 'post',
data: { username, password, code, uuid }
})
}
2、Nacos配置(ruoyi-gateway-dev.yml。 匹配请求路径为/auth/**的请求,设置过滤器并指定通过过滤器后的转发地址)
routes:
- id: ruoyi-auth
uri: lb://ruoyi-auth
predicates:
- Path=/auth/**
filters:
- CacheRequestFilter
- ValidateCodeFilter
- StripPrefix=1
3、后端代码实现
(1) CacheRequestFilter(缓存过滤器,基于Spring Web的缓存机制。过滤非GET/DELETE请求则调用ServerWebExchangeUtils.cacheRequestBodyAndRequest方法,该方法会缓存请求体并返回一个新的ServerHttpRequest。如果缓存后的ServerHttpRequest与原始请求相同,直接调用chain.filter(exchange)。如果缓存后的ServerHttpRequest与原始请求不同,通过构建一个新的ServerWebExchange对象,将新的ServerHttpRequest设置为请求,然后调用chain.filter方法处理这个新的ServerWebExchange对象。)
public static class CacheRequestGatewayFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpMethod method = exchange.getRequest().getMethod();
if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) {
return chain.filter(exchange);
}
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
if (serverHttpRequest == exchange.getRequest()) {
return chain.filter(exchange);
}
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
});
}
}
(2) ValidateCodeFilter (验证码过滤器。获取登录请求上送的验证码UUID获取redis中存储的验证码码值CODE是否和登录请求上送的验证码码值CODE一致。)
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) {
return chain.filter(exchange);
}
try {
String rspStr = resolveBodyFromRequest(request);
JSONObject obj = JSON.parseObject(rspStr);
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
} catch (Exception e) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}