目前公司使用JeecgBoot的微服务模式,二次开发了一个项目。项目部署之后,我们发现,前端只要一登陆马上就调logout方法,强制退出。最后,我们经过一番努力,终于解决问题。下面记录一下关于Jeecgboot前端报错的排除方法,以及排除“一登陆就退出”的解决的过程。

首先,我们从代码版本着手。

因为我们本地环境,测试环境都正常,所以我们在想,是不是代码版本问题,检查完之后,发现前后的版本都和测试环境一致,于是排除版本差异的原因。

第二,我们考虑后台问题。

因为后台查不到权限,会导致不能登录,强制退出。检查 权限接口的返回, /permission/getUserPermissionByToken, 没有问题,返回正常。但是,这时候,我们其实已经有点接近问题的答案了,但是我们没有继续在这个点上深入。

/**
	 * 查询用户拥有的菜单权限和按钮权限
	 *
	 * @return
	 */
	@RequestMapping(value = "/getUserPermissionByToken", method = RequestMethod.GET)
	//@DynamicTable(value = DynamicTableConstant.SYS_ROLE_INDEX)
	public Result<?> getUserPermissionByToken(HttpServletRequest request) {
		Result<JSONObject> result = new Result<JSONObject>();
		try {
			//直接获取当前用户不适用前端token
			LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
			if (oConvertUtils.isEmpty(loginUser)) {
				return Result.error("请登录系统!");
			}
			List<SysPermission> metaList = sysPermissionService.queryByUser(loginUser.getUsername());
			//添加首页路由
			//update-begin-author:taoyan date:20200211 for: TASK #3368 【路由缓存】首页的缓存设置有问题,需要根据后台的路由配置来实现是否缓存
			if(!PermissionDataUtil.hasIndexPage(metaList)){
				SysPermission indexMenu = sysPermissionService.list(new LambdaQueryWrapper<SysPermission>().eq(SysPermission::getName,"首页")).get(0);
				metaList.add(0,indexMenu);
			}
			//update-end-author:taoyan date:20200211 for: TASK #3368 【路由缓存】首页的缓存设置有问题,需要根据后台的路由配置来实现是否缓存

			//update-begin--Author:zyf Date:20220425  for:自定义首页地址 LOWCOD-1578
			String version = request.getHeader(CommonConstant.VERSION);
			//update-begin---author:liusq ---date:2022-06-29  for:接口返回值修改,同步修改这里的判断逻辑-----------
			SysRoleIndex roleIndex= sysUserService.getDynamicIndexByUserRole(loginUser.getUsername(),version);
			//update-end---author:liusq ---date:2022-06-29  for:接口返回值修改,同步修改这里的判断逻辑-----------
			//update-end--Author:zyf  Date:20220425  for:自定义首页地址 LOWCOD-1578

			if(roleIndex!=null){
				List<SysPermission> menus = metaList.stream().filter(sysPermission -> "首页".equals(sysPermission.getName())).collect(Collectors.toList());
				//update-begin---author:liusq ---date:2022-06-29  for:设置自定义首页地址和组件----------
				String component = roleIndex.getComponent();
				String routeUrl = roleIndex.getUrl();
				boolean route = roleIndex.isRoute();
				if(oConvertUtils.isNotEmpty(routeUrl)){
					menus.get(0).setComponent(component);
					menus.get(0).setRoute(route);
					menus.get(0).setUrl(routeUrl);
				}else{
					menus.get(0).setComponent(component);
				}
				//update-end---author:liusq ---date:2022-06-29  for:设置自定义首页地址和组件-----------
			}
			
			JSONObject json = new JSONObject();
			JSONArray menujsonArray = new JSONArray();
			this.getPermissionJsonArray(menujsonArray, metaList, null);
			//一级菜单下的子菜单全部是隐藏路由,则一级菜单不显示
			this.handleFirstLevelMenuHidden(menujsonArray);

			JSONArray authjsonArray = new JSONArray();
			this.getAuthJsonArray(authjsonArray, metaList);
			//查询所有的权限
			LambdaQueryWrapper<SysPermission> query = new LambdaQueryWrapper<SysPermission>();
			query.eq(SysPermission::getDelFlag, CommonConstant.DEL_FLAG_0);
			query.eq(SysPermission::getMenuType, CommonConstant.MENU_TYPE_2);
			//query.eq(SysPermission::getStatus, "1");
			List<SysPermission> allAuthList = sysPermissionService.list(query);
			JSONArray allauthjsonArray = new JSONArray();
			this.getAllAuthJsonArray(allauthjsonArray, allAuthList);
			//路由菜单
			json.put("menu", menujsonArray);
			//按钮权限(用户拥有的权限集合)
			json.put("auth", authjsonArray);
			//全部权限配置集合(按钮权限,访问权限)
			json.put("allAuth", allauthjsonArray);
			json.put("sysSafeMode", jeecgBaseConfig.getSafeMode());
			result.setResult(json);
		} catch (Exception e) {
			result.error500("查询失败:" + e.getMessage());  
			log.error(e.getMessage(), e);
		}
		return result;
	}

第三,我们从前端断点。

首先,我们对logout的js文件中的方法断点,在页面上找出来,是哪个位置调用logout方法,导致了强制退出。

在login.js中有这个方法:
 

export function logout(logoutToken) {
  return axios({
    url: '/sys/logout',
    method: 'post',
    headers: {
      'Content-Type': 'application/json;charset=UTF-8',
      'X-Access-Token':  logoutToken
    }
  })
}

这里是调用logout最底层的地方,在这个return 之上,写debugger,然后再启动前端。前端被强制退出的时候,通过控制台,找到调用的代码,发现是在permission.js里,获取权限,并且生成路由的时候报错了,

但是JeecgBootcatch到这个报错后,没有跑出来,而是直接调用了logout方法。代码如下

.catch(() => {
           /* notification.error({
              message: '系统提示',
              description: '请求用户信息失败,请重试!'
            })*/
            store.dispatch('Logout').then(() => {
              next({ path: '/user/login', query: { redirect: to.fullPath } })
            })
          })

这里catch方法调用之后,我们可以把此处的异常信息打印出来,这样就可以定位到是什么原因了,代码稍微修改如下

.catch((e) => {
           /* notification.error({
              message: '系统提示',
              description: '请求用户信息失败,请重试!'
            })*/
console.log("=======报错了,高大上的分割线=====",e)
            store.dispatch('Logout').then(() => {
              next({ path: '/user/login', query: { redirect: to.fullPath } })
            })
          })

此处打印出的报错, 竟然是语法异常,这里可以在浏览器的控制台看到堆栈信息,定位报错位置是permission.js中的此处,本来

constRoutes = generateIndexRouter(menuData);

生成路由的这个方法,进入到里面,是调用了util.js里的方法,生成嵌套路由的时候,报错

if (store.getters.permissionList.length === 0) {
        store.dispatch('GetPermissionList').then(res => {
              const menuData = res.result.menu;
              //console.log(res.message)
              if (menuData === null || menuData === "" || menuData === undefined) {
                return;
              }
              let constRoutes = [];
              constRoutes = generateIndexRouter(menuData);
              // 添加主界面路由
              store.dispatch('UpdateAppRouter',  { constRoutes }).then(() => {
                // 根据roles权限生成可访问的路由表
                // 动态添加可访问路由表
                router.addRoutes(store.getters.addRouters)
                const redirect = decodeURIComponent(from.query.redirect || to.path)
                if (to.path === redirect) {
                  // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
                  next({ ...to, replace: true })
                } else {
                  // 跳转到目的路由
                  next({ path: redirect })
                }
              })
            })
          .catch(() => {
           /* notification.error({
              message: '系统提示',
              description: '请求用户信息失败,请重试!'
            })*/
            store.dispatch('Logout').then(() => {
              next({ path: '/user/login', query: { redirect: to.fullPath } })
            })
          })
      } else {
        next()
      }

下面是生成嵌套路由的地方,也是最终找到的答案所在处,

let URL = (item.meta.url|| '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2)) // URL支持{{ window.xxx }}占位符变量

这个方法,报了语法错误,我们检查几个url的时候发现,其中的一个地址里,包含了错误的符号,后来调查发现,是某个同事手动加上去的,错误的符号导致此处的正则表达式抛出了语法错误,导致了强制调用JeecgBoot的logout方法JeecgBoot退出。

这个地方可以在这个正则表达式上面,console.log这个变量item.meta和item.meta.url

// 生成嵌套路由(子路由)

function  generateChildRouters (data) {
  const routers = [];
  for (let item of data) {
    let component = "";
    if(item.component.indexOf("layouts")>=0){
      component = "components/"+item.component;
    }else{
      component = "views/"+item.component;
    }

    // eslint-disable-next-line
    let URL = (item.meta.url|| '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2)) // URL支持{{ window.xxx }}占位符变量
    if (isURL(URL)) {
      item.meta.url = URL;
    }

至此,问题得到排查,这次JeecgBoot一登陆马上退出logout的问题,和前端没有关系,都是因为后端的数据不对导致的,我们排查问题的时候,可以一开始就选择第三种思路,在本地启动前端,连接线上的后端地址进行调试。这样可以调试jeecgBoot的前端问题。