前言
经过之前两期,我们理解了ruoyi前端登录页面的流程。这期,我们开始分析后端对应的流程。主要分析获取验证码接口captchaImage和登录接口login。
若依后端结构
首先我们看一下ruoyi后端的模块组成。
- admin——web服务入口
- common——通用工具
- framework——核心模块
- generator——代码生成
- quartz——定时任务
- system——系统模块
ruoyi后端中是将controller文件统一放入admin模块中。
验证码接口
对应文件位置为ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
主要流程为:
- 生成随机的uuid
- 生成随机的验证码图片及结果
- 将uuid和验证码结果存入redis
- 将验证码图片放入返回值中
@GetMapping("/captchaImage")
// AjaxResult统一返回值
public AjaxResult getCode(HttpServletResponse response) throws IOException
{
// 保存验证码信息
// 使用加密的强伪随机数生成器生成该 UUID
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
// 生成验证码
// application.yml中配置ruoyi.captchaType
// 数学模式
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
// 表达式
capStr = capText.substring(0, capText.lastIndexOf("@"));
// 对应结果
code = capText.substring(capText.lastIndexOf("@") + 1);
// 根据表达式生成图片BufferedImage
image = captchaProducerMath.createImage(capStr);
}
// 文本模式
else if ("char".equals(captchaType))
{
// 生成的表达式和结果相同
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
// 将uuid和验证码键值存入redis
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());
}
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
// 转base64格式
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
- ruoyi中默认的uuid生成为Version 4,有概率重复https://www.zhihu.com/question/34876910/answer/88924223
- ruoyi采用ThreadLocalRandom生成随机数,避免多线程竞争争夺
- ruoyi中提供了更安全的随机码生成,但会降低性能
登陆接口
对应文件位置为:ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}
上段代码可谈的不多,即生成token然后返回。
主要来看一下token的生成过程。这里为了方便大家查看,我去除了异常处理的代码。
public String login(String username, String password, String code, String uuid)
{
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
// 从redis中获取uuid对应验证码
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
// captcha为空异常
// code不等于captcha异常
// 用户验证
Authentication authentication = null;
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
// 调用org.springframework.security验证
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
// 用户不存在或密码错误异常
// 其他异常
// 异步记录登录信息
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
// 获取用户信息和权限
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// 修改用户最后登录ip和时间
recordLoginInfo(loginUser.getUser());
// 生成token
return tokenService.createToken(loginUser);
}
下期计划
用户登录后,默认进入/index页面。我们查看src/views/index.vue,发现多是静态内容。但进入此页面时,产生了两个请求getInfo和getRouters。这两个请求是在第2篇—路由拦截跳转中准入限制router.beforeEach调用的。下期便首先分析getInfo.
- 分析获取用户信息