若依框架前后端分离 项目(前端vue,后端 springboot)。
这里只记录后端springboot。
验证码:
用户进入登录页,请求 /captchaImage 接口获取验证码:
登录
用户点击登录,前端会把如下参数传给后端
登录接口 /login
(0)最终目的:获取token,前端带着token请求 接口。
(1)大致流程:
验证验证码是否正确(过期、输出错误) -> 调用security api来进行用户验证,这里会根据username来查询数据库,判断是否有这个用户,如果有这个用户,接着判断是否是超管(如果是超管,就不会去查询对应的菜单权限;不是超管,会根据userId查询数据库,来获取对应的菜单权限),然后把对应的信息填充在 LoginUser类中,security中,也会记录当前登录的用户信息 -> 最后,更新用户登录ip、时间,然后生成token,返给前端。
(2)详细流程:
登录接口 /login
loginService中的login方法:
下边是security 的api了,也是重点:
authenticationManager.authenticate() 是security内部api,web登录时,内部会调用 authenticationManager.authenticate()对账号和密码做验证,具体细节不做介绍。最终security会调用UserDetailsServiceImpl.loadUserByUsername(String username) 方法,而若依重写了此方法:
在ruoyi-framework中,重点(重写此方法需要实现UserDetailsService接口),重写了loadUserByUsername:
根据username查询数据库,一堆if判断用户状态
如果有该用户,会调用createLoginUser() 方法来返回 LoginUser 类,为什么会返回LoginUser ,因为: LoginUser 实现了 UserDetails 接口。
getMenuPermission() 方法:
LoginUser类,查询出用户信息,会填充在LoginUser中:
接着,菜单权限查询成功, 会异步打印日志,然后更新用户登录ip,日期。
最后,生成token,返给前端:
获取用户信息 /getInfo
如果前端登录请求顺利,接着会进入后台首页,在这之前,路由守卫中,还会判断vuex中是否有角色的key(我的理解就是一个角色标识),如果没有,会请求/getInfo 接口:
下边两个角色查询和权限查询 都是根据用户id来查询的:
角色查询,其实跟登录时的菜单权限查询类似,都是先判断是否是超管,不是回去查询数据库:
权限查询,跟角色查询一样:
没问题,直接返回给前端,数据如下:
前端会把相关信息存到vuex中:
获取路由信息及按钮信息 /getRouters
用户信息请求成功后,会在成功的回调中(路由守卫里边)请求路由、按钮的相关信息,
的对应接口: /getRouters
先看下根据userId 获取菜单树,
getChildPerms() 方法会构建父子级关系(注意这个方法还不是构建前端路由列表),具体,如何递归的自己看源码吧
构建完父子级关系, List<SysMenus> menus 结果为:
最后,构建前端需要的路由树,buildMnus() 方法,要理解这个方法,必须对前台相当了解才行!
设置路由的相关信息,可结合前台页面,添加菜单中的选项:
/**
* 构建前端路由所需要的菜单
*
* @param menus 菜单列表
* @return 路由列表
*/
@Override
public List<RouterVo> buildMenus(List<SysMenu> menus)
{
List<RouterVo> routers = new LinkedList<RouterVo>();
for (SysMenu menu : menus)
{
// 路由的相关信息
RouterVo router = new RouterVo();
// 0 路由是否显示,1 不显示 -> hidden: true/false
router.setHidden("1".equals(menu.getVisible()));
// 设置路由name,且会把name首字母大写 -> name: "name"
router.setName(getRouteName(menu));
// 设置路由访问路径 -> path: "/path"
router.setPath(getRouterPath(menu));
// 设置组件
router.setComponent(getComponent(menu));
// 设置meta信息
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
// 设置Children,如果有的话,子组件默认都是显示的 且 不重定向的。接着递归去设置子组件的一些属性值
List<SysMenu> cMenus = menu.getChildren();
// 【目录】 如:前端 系统管理
if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()))
{
router.setAlwaysShow(true);
router.setRedirect("noRedirect");
router.setChildren(buildMenus(cMenus)); // 递归设置子组件的属性
}
// 【是否是内连接的菜单且没有父级(对应前端页面比如:首页)】
else if (isMenuFrame(menu))
{
// 下边这些跟上边一样了
router.setMeta(null);
List<RouterVo> childrenList = new ArrayList<RouterVo>();
RouterVo children = new RouterVo();
children.setPath(menu.getPath());
children.setComponent(menu.getComponent());
children.setName(StringUtils.capitalize(menu.getPath()));
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
childrenList.add(children);
router.setChildren(childrenList);
}
// ???????? 是否为内链组件 ????????
else if (menu.getParentId().intValue() == 0 && isInnerLink(menu))
{
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
router.setPath("/inner");
List<RouterVo> childrenList = new ArrayList<RouterVo>();
RouterVo children = new RouterVo();
String routerPath = StringUtils.replaceEach(menu.getPath(), new String[] { Constants.HTTP, Constants.HTTPS }, new String[] { "", "" });
children.setPath(routerPath);
children.setComponent(UserConstants.INNER_LINK);
children.setName(StringUtils.capitalize(routerPath));
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
childrenList.add(children);
router.setChildren(childrenList);
}
routers.add(router);
}
return routers;
}
构建完成,直接返给前台数据,前台请求成功的返回结果:
然后动态添加路由信息,vue2.xxx新api addRouters() 可动态添加路由。
此时,一个完整的从前端 请求验证码 -> 登录 -> 获取用户信息 -> 获取菜单权限 请求流程就结束了。。。
补充下:
前台请求过来的菜单权限,还需要过滤,因为像Component组件名都是字符串,下边是过滤的主要方法,替换前端组件,还有就是把菜单组件变为懒加载状态。