若依框架学习

  • 一、01-登录流程
  • 1、用户发送请求进入到登录页面
  • 2、用户点击登录按钮
  • 二、02-首页


一、01-登录流程

1、用户发送请求进入到登录页面

//SysLoginController.java
@GetMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response)
{
    // 如果是Ajax请求,返回Json字符串。
    if (ServletUtils.isAjaxRequest(request))
    {
        return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}");
    }

    return "login";
}
  • 对ajax和视图返回进行区分
//ServletUtils.java

/**
 * 是否是Ajax异步请求
 * 
 * @param request
 */
public static boolean isAjaxRequest(HttpServletRequest request)
{
    String accept = request.getHeader("accept");
    if (accept != null && accept.indexOf("application/json") != -1)
    {
        return true;
    }

    String xRequestedWith = request.getHeader("X-Requested-With");
    if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1)
    {
        return true;
    }

    String uri = request.getRequestURI();
    if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml"))
    {
        return true;
    }

    String ajax = request.getParameter("__ajax");
    if (StringUtils.inStringIgnoreCase(ajax, "json", "xml"))
    {
        return true;
    }
    return false;
}
  • 同时页面发送一个验证码请求
//	SysCaptchaController.java

if ("math".equals(type))
{
    String capText = captchaProducerMath.createText();
    capStr = capText.substring(0, capText.lastIndexOf("@"));
    code = capText.substring(capText.lastIndexOf("@") + 1);
    bi = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(type))
{
    capStr = code = captchaProducer.createText();
    bi = captchaProducer.createImage(capStr);
}
session.setAttribute(Constants.KAPTCHA_SESSION_KEY, code);
out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
out.flush();
  • 若依使用的是谷歌提供的一个验证码生成器com.google.code.kaptcha,可以在配置类中配置验证码生成的参数,最后返回一个ImageIO对象,controller将图片对象通过流的形式写入到前端页面
  • 验证码形式支持字符和数学运算,数学运算需要自定义一个文本生成器`
// 验证码文本生成器	
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator");

2、用户点击登录按钮

  • 前端发送ajax请求
//login.js
function login() {
    $.modal.loading($("#btnSubmit").data("loading"));
    var username = $.common.trim($("input[name='username']").val());
    var password = $.common.trim($("input[name='password']").val());
    var validateCode = $("input[name='validateCode']").val();
    var rememberMe = $("input[name='rememberme']").is(':checked');
    $.ajax({
        type: "post",
        url: ctx + "login",
        data: {
            "username": username,
            "password": password,
            "validateCode": validateCode, //请求体中的验证码会在shiro验证码过滤器中进行验证
            "rememberMe": rememberMe
        },
        success: function(r) {
           //响应数据成功后,根据响应代码跳转到不同的页面
            if (r.code == web_status.SUCCESS) {
               //登录成功,跳转到index页面
                location.href = ctx + 'index';
            } else {
               //登录失败
               //关闭loading蒙层
               $.modal.closeLoading();
               //模拟点击一下刷新验证码按钮
               $('.imgcode').click();
               //清空验证码输入值
               $(".code").val("");
               //显示响应的错误消息
               $.modal.msg(r.msg);
            }
        }
    });
}
  • 发送POST请求到/login
  • 进行shiro权限验证
  • 获取登录的token值
  • 验证成功返回成功ajax数据,失败返回失败ajax数据
@PostMapping("/login")
@ResponseBody
public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
{
    UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
    Subject subject = SecurityUtils.getSubject();
    try
    {
        subject.login(token);
        return success();
    }
    catch (AuthenticationException e)
    {
        String msg = "用户或密码错误";
        if (StringUtils.isNotEmpty(e.getMessage()))
        {
            msg = e.getMessage();
        }
        return error(msg);
    }
}

二、02-首页

  • 发送get请求index视图
  • ModelMap和Model的用法类似
  • SysUser user = ShiroUtils.getSysUser();获取当前登录的用户信息
//SysIndexController.java

// 系统首页
@GetMapping("/index")
public String index(ModelMap mmap)
{
    // 取身份信息
    SysUser user = ShiroUtils.getSysUser();
    // 根据用户id取出菜单
    List<SysMenu> menus = menuService.selectMenusByUser(user);
    mmap.put("menus", menus);
    mmap.put("user", user);
    mmap.put("sideTheme", configService.selectConfigByKey("sys.index.sideTheme"));
    mmap.put("skinName", configService.selectConfigByKey("sys.index.skinName"));
    mmap.put("ignoreFooter", configService.selectConfigByKey("sys.index.ignoreFooter"));
    mmap.put("copyrightYear", RuoYiConfig.getCopyrightYear());
    mmap.put("demoEnabled", RuoYiConfig.isDemoEnabled());
    mmap.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
    mmap.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));

    // 菜单导航显示风格
    String menuStyle = configService.selectConfigByKey("sys.index.menuStyle");
    // 移动端,默认使左侧导航菜单,否则取默认配置
    String indexStyle = ServletUtils.checkAgentIsMobile(ServletUtils.getRequest().getHeader("User-Agent")) ? "index" : menuStyle;

    // 优先Cookie配置导航菜单
    Cookie[] cookies = ServletUtils.getRequest().getCookies();
    for (Cookie cookie : cookies)
    {
        if (StringUtils.isNotEmpty(cookie.getName()) && "nav-style".equalsIgnoreCase(cookie.getName()))
        {
            indexStyle = cookie.getValue();
            break;
        }
    }
    String webIndex = "topnav".equalsIgnoreCase(indexStyle) ? "index-topnav" : "index";
    return webIndex;
}
  • 从数据库查询到的菜单列表是没有树形结构的,进行了二次封装,最终返回树形结构的菜单到视图
//SysMenuServiceImpl.java
/**
 * 根据用户查询菜单
 * 
 * @param user 用户信息
 * @return 菜单列表
 */
@Override
public List<SysMenu> selectMenusByUser(SysUser user)
{
    List<SysMenu> menus = new LinkedList<SysMenu>();
    // 管理员显示所有菜单信息
    if (user.isAdmin())
    {
        menus = menuMapper.selectMenuNormalAll();
    }
    else
    {
        menus = menuMapper.selectMenusByUserId(user.getUserId());
    }
    return getChildPerms(menus, 0);
}

/**
 * 根据父节点的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);
        }
    }
}