RuoYi前后端分离

登录

生成验证码

后端生成一个表达式 1+1=?@2
转成图片传到前端,进行展示,并存到redies

前端

  • 获取图片并且存储验证码对应的uuid
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;  //获取验证码对应的key值
        }
      });
    },
  • 获取验证码的接口和request
// 获取验证码
export function getCodeImg() {
  return request({
    url: '/captchaImage',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  })
}
  • Vue获取图片,使用反向代理,url请求前端进行代理,映射到后端,解决跨域问题
  • 将 VUE_APP_BASE_API 替换成空,再映射到http://localhost:8080
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]: ''
        }
      }
    },

后端

/**
 * 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException
{
//创建AjaxResult对象
   AjaxResult ajax = AjaxResult.success();
   //判断是否开启了验证
   boolean captchaOnOff = configService.selectCaptchaOnOff();
   ajax.put("captchaOnOff", captchaOnOff);
   if (!captchaOnOff)
   {
       return ajax;
   }

   // 保存验证码信息
   String uuid = IdUtils.simpleUUID();
   //存一对uuid和对应的key
   String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

   String capStr = null, code = null;
   BufferedImage image = null;

   // 生成验证码
   String captchaType = RuoYiConfig.getCaptchaType();
   if ("math".equals(captchaType))
   {
   	//获取完整验证码 譬如 1+1=?@2
       String capText = captchaProducerMath.createText();
       //分离验证码信息 @之前 1+1=?
       capStr = capText.substring(0, capText.lastIndexOf("@"));
       //分离验证码信息 @之后 答案 2
       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());
   }
   //将图片和uuid放入ajax里面 并返回
   ajax.put("uuid", uuid);
   ajax.put("img", Base64.encode(os.toByteArray()));
   return ajax;
}

执行登录

  • 使用异步任务管理器,结合线程池,实现异步的操作日志记录,和业务逻辑实现异步的解耦合
  • 流程:
    1. 验证验证码
    2. 校验用户名
    3. 生成token,放入ajax
/**
     * 登录方法
     * 
     * @param loginBody 登录信息
     * @return 结果
     */
    @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;
    }
/**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
        boolean captchaOnOff = configService.selectCaptchaOnOff();
        // 验证码开关
        if (captchaOnOff)
        {
            //验证码校验  第一步
            validateCaptcha(username, code, uuid);
        }
        // 用户验证 第二步
        Authentication authentication = null;
        try
        {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        //记录操作日志
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        //修改用户最新登录信息
        recordLoginInfo(loginUser.getUserId());
        // 生成token 第三步
        return tokenService.createToken(loginUser);
    }

校验验证码

/**
     * 校验验证码
     * 
     * @param username 用户名
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid)
    {
        //拼起字符串去redies
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        //从redies中拿到验证码答案和传入的答案code对比
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        //验证码过期
        if (captcha == null)
        {
            //异步写日志
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            //抛出一个异常
            throw new CaptchaExpireException();
        }
        //验证码错误
        if (!code.equalsIgnoreCase(captcha))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
    }

获取用户角色和权限

/**
     * 获取用户信息
     * 
     * @return 用户信息
     */
    @GetMapping("getInfo")
    public AjaxResult getInfo()
    {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        // 角色集合 获取角色权限信息
        Set<String> roles = permissionService.getRolePermission(user);
        // 权限集合 获取菜单数据权限
        Set<String> permissions = permissionService.getMenuPermission(user);
        AjaxResult ajax = AjaxResult.success();
        ajax.put("user", user);
        ajax.put("roles", roles);
        ajax.put("permissions", permissions);
        return ajax;
    }

角色集合 获取角色权限信息

/**
     * 获取角色数据权限
     * 
     * @param user 用户信息
     * @return 角色权限信息
     */
    public Set<String> getRolePermission(SysUser user)
    {
        Set<String> roles = new HashSet<String>();
        // 管理员拥有所有权限
        if (user.isAdmin())
        {
            roles.add("admin");
        }
        else
        {
            roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
        }
        return roles;
    }

获取菜单数据权限

/**
     * 获取菜单数据权限
     * 
     * @param user 用户信息
     * @return 菜单权限信息
     */
    public Set<String> getMenuPermission(SysUser user)
    {
        Set<String> perms = new HashSet<String>();
        // 管理员拥有所有权限
        if (user.isAdmin())
        {
            perms.add("*:*:*");
        }
        else
        {
            perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
        }
        return perms;
    }

权限集合 获取菜单数据权限

/**
     * 获取路由信息
     * 
     * @return 路由信息
     */
    @GetMapping("getRouters")
    public AjaxResult getRouters()
    {
        Long userId = SecurityUtils.getUserId();
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }

根据用户ID查询菜单

/**
     * 根据用户ID查询菜单
     * 
     * @param userId 用户名称
     * @return 菜单列表
     */
    @Override
    public List<SysMenu> selectMenuTreeByUserId(Long userId)
    {
        List<SysMenu> menus = null;
        if (SecurityUtils.isAdmin(userId))
        {
       		//admin权限直接查询所有节点
            menus = menuMapper.selectMenuTreeAll();
        }
        else
        {
        	//否则根据用户的id来查询节点
            menus = menuMapper.selectMenuTreeByUserId(userId);
        }
        //根据父节点的ID获取所有子节点
        return getChildPerms(menus, 0);
    }

根据父节点的ID获取所有子节点

/**
     * 根据父节点的ID获取所有子节点
     * 
     * @param list 分类表
     * @param parentId 传入的父节点ID
     * @return String
     */
    public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
    {
        List<SysMenu> returnList = new ArrayList<SysMenu>();
        for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
        {
            SysMenu t = (SysMenu) iterator.next();
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == parentId)
            {
            	//递归入口
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }

递归列表

/**
     * 递归列表
     * 
     * @param list
     * @param t
     */
    private void recursionFn(List<SysMenu> list, SysMenu t)
    {
        // 得到子节点列表
        List<SysMenu> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysMenu tChild : childList)
        {
            //有没有属于你的菜单,有的话就递归,没有的话就放弃递归
            if (hasChild(list, tChild))
            {
                //递归,自己调用自己,重复过程找到每个父节点的子节点
                recursionFn(list, tChild);
            }
        }
    }
RuoYi是一个基于Spring Boot和Vue.js的开源框架,它采用前后端分离的架构设计,实现了数据前后端分离开发。在RuoYi中,通过前后端分离的方式,可以实现导出功能。 首先,前端负责显示数据的页面布局和交互操作。在前端页面中,可以通过调用后端接口获取需要导出的数据,并将其展示在页面上。在需要导出的地方,可以添加一个导出按钮或其他触发事件的组件,通过点击按钮或触发事件的方式,调用后端接口进行导出操作。 其次,后端负责处理导出的相关逻辑。在RuoYi的后端代码中,通常会有一个导出的控制层,负责接收前端导出请求,并进行相应的数据处理和导出操作。后端可以通过调用某些工具类或第三方库来生成导出文件,如使用Apache POI库生成Excel文件、使用iText或PDFBox库生成PDF文件等。 最后,导出的结果可以返回给前端进行下载或其他操作。后端处理完导出逻辑后,可以将生成的导出文件保存到服务器的某个位置,然后将文件的路径返回给前端前端根据返回的文件路径,可以通过下载链接或其他方式提供给用户进行文件下载。 总结来说,RuoYi采用前后端分离的架构,通过前端调用后端接口来实现数据的导出。在前端页面中,添加导出按钮或触发事件的组件,通过点击或触发事件的方式调用后端接口。后端处理导出逻辑后,生成导出文件并保存,然后将文件路径返回给前端进行下载或其他操作。这样可以实现RuoYi前后端分离导出功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值