前端微服务架构下的路由共享机制设计与实践

信息图

一、 微前端的路由困境

微前端架构的核心优势是独立开发、独立部署。但当多个子应用组合成一个完整的用户体验时,路由管理就变得复杂起来。每个子应用有自己的路由体系,而主框架需要统一管理整个应用的路由状态。

核心难题在于:如何让多个独立的应用共享同一个 URL 地址栏?当用户在子应用之间导航时,如何保持路由状态一致?

二、 路由架构方案对比

方案 实现原理 子应用感知 通信复杂度 适用场景
Hash 路由 URL Hash 分段 不需要改造 简单聚合
基座路由分发 基座统一注册路由 需要适配 企业级平台
状态共享 全局路由状态同步 需要改造 复杂交互
iframe 路由 iframe 独立管理 完全隔离 遗留系统集成

三、 基座路由分发方案

3.1 主应用路由配置

// 主应用 routes.js
import { registerMicroApps, start } from 'qiankun';
import { createRouter, createWebHistory } from 'vue-router';

// 主应用路由表
const mainRouter = createRouter({
  history: createWebHistory('/'),
  routes: [
    {
      path: '/',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/app1/*',
      component: () => import('./views/AppContainer.vue'),
      meta: { microApp: 'app1' }
    },
    {
      path: '/app2/*',
      component: () => import('./views/AppContainer.vue'),
      meta: { microApp: 'app2' }
    },
    {
      path: '/app3/*',
      component: () => import('./views/AppContainer.vue'),
      meta: { microApp: 'app3' }
    }
  ]
});

// 微应用注册
registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:3001',
    container: '#micro-app-container',
    activeRule: '/app1',
    props: {
      baseRouter: mainRouter,
      basePath: '/app1'
    }
  },
  {
    name: 'app2',
    entry: '//localhost:3002',
    container: '#micro-app-container',
    activeRule: '/app2',
    props: {
      baseRouter: mainRouter,
      basePath: '/app2'
    }
  }
]);

start({
  sandbox: { experimentalStyleIsolation: true }
});

3.2 子应用路由适配

// 子应用 router.js
function createMicroAppRouter(basePath, baseRouter) {
  const router = createRouter({
    history: createWebHistory(basePath),
    routes: [
      {
        path: '/',
        redirect: '/dashboard'
      },
      {
        path: '/dashboard',
        component: () => import('./views/Dashboard.vue')
      },
      {
        path: '/settings',
        component: () => import('./views/Settings.vue')
      },
      {
        path: '/users/:id',
        component: () => import('./views/UserDetail.vue')
      }
    ]
  });

  // 路由守卫:确保主应用路由同步
  router.beforeEach((to, from, next) => {
    if (baseRouter) {
      // 同步路由状态到主应用
      baseRouter.push(to.fullPath).catch(() => {});
    }
    next();
  });

  return router;
}

// 子应用入口
let appRouter = null;

export async function mount(props) {
  const { basePath, baseRouter, container } = props;

  appRouter = createMicroAppRouter(basePath, baseRouter);

  const app = createApp(App);
  app.use(appRouter);
  app.mount(container ? container.querySelector('#app') : '#app');
}

export async function unmount() {
  if (appRouter) {
    appRouter = null;
  }
}

四、 Hash 路由共存方案

// 适用于简单场景:各子应用使用独立 hash 段
// URL 格式:http://example.com/#/app1/dashboard

class HashRouterManager {
  constructor() {
    this.apps = new Map();
    this.currentApp = null;
    this.setupListener();
  }

  registerApp(name, routes) {
    this.apps.set(name, {
      routes,
      currentRoute: '/'
    });
  }

  setupListener() {
    window.addEventListener('hashchange', () => {
      const hash = window.location.hash.slice(1);
      const { app, path } = this.parseHash(hash);

      if (app && app !== this.currentApp) {
        this.switchApp(app, path);
      } else if (app) {
        this.updateAppRoute(app, path);
      }
    });
  }

  parseHash(hash) {
    const segments = hash.split('/').filter(Boolean);
    const app = segments[0];
    const path = '/' + segments.slice(1).join('/');
    return { app, path };
  }

  switchApp(appName, path) {
    if (this.currentApp) {
      this.apps.get(this.currentApp).currentRoute =
        this.getCurrentHash();
    }

    this.currentApp = appName;
    this.apps.get(appName).currentRoute = path;
    this.renderApp(appName, path);
  }

  getCurrentHash() {
    return window.location.hash.slice(1);
  }

  navigate(appName, path) {
    window.location.hash = `#/${appName}${path}`;
  }

  renderApp(appName, path) {
    const app = this.apps.get(appName);
    const container = document.getElementById('micro-app-container');

    container.innerHTML = '';
    const component = app.routes[path] || app.routes['/'];
    if (component) {
      container.appendChild(component());
    }
  }
}

五、 全局状态同步方案

5.1 路由状态 Store

// shared/route-store.js
class RouteStore {
  constructor() {
    this.state = {
      currentApp: null,
      currentPath: '/',
      history: [],
      appRoutes: {}
    };
    this.listeners = new Set();
    this.setupCommunication();
  }

  setupCommunication() {
    window.addEventListener('message', (event) => {
      if (event.data.type === 'ROUTE_CHANGE') {
        const { app, path } = event.data.payload;
        this.updateRoute(app, path, 'external');
      }
    });
  }

  registerApp(name, routes) {
    this.state.appRoutes[name] = routes;
    this.notify();
  }

  navigate(app, path, trigger = 'internal') {
    this.updateRoute(app, path, trigger);
    this.syncBrowserUrl(app, path);

    if (trigger === 'internal') {
      this.notifyOtherApps(app, path);
    }
  }

  updateRoute(app, path, trigger) {
    const previous = {
      app: this.state.currentApp,
      path: this.state.currentPath
    };

    this.state.currentApp = app;
    this.state.currentPath = path;
    this.state.history.push({
      from: previous,
      to: { app, path },
      trigger,
      timestamp: Date.now()
    });

    if (this.state.history.length > 50) {
      this.state.history.shift();
    }

    this.notify();
  }

  syncBrowserUrl(app, path) {
    const url = `/${app}${path}`;
    window.history.pushState({ app, path }, '', url);
  }

  notifyOtherApps(app, path) {
    const apps = Object.keys(this.state.appRoutes).filter(a => a !== app);

    for (const appName of apps) {
      const iframe = document.querySelector(`[data-app="${appName}"]`);
      if (iframe?.contentWindow) {
        iframe.contentWindow.postMessage({
          type: 'ROUTE_CHANGE',
          payload: { app, path }
        }, '*');
      }
    }
  }

  subscribe(callback) {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback);
  }

  notify() {
    for (const listener of this.listeners) {
      listener(this.state);
    }
  }

  getRouteForApp(appName) {
    if (appName === this.state.currentApp) {
      return this.state.currentPath;
    }
    return '/';
  }

  getState() {
    return { ...this.state };
  }
}

export const routeStore = new RouteStore();

5.2 子应用路由同步 Hook

// React 子应用路由同步
import { useEffect, useCallback } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { routeStore } from '../shared/route-store';

function useMicroAppRoute(appName) {
  const history = useHistory();
  const location = useLocation();

  useEffect(() => {
    const unsubscribe = routeStore.subscribe((state) => {
      if (state.currentApp === appName) {
        const currentPath = location.pathname;
        if (state.currentPath !== currentPath) {
          history.push(state.currentPath);
        }
      }
    });

    return unsubscribe;
  }, [appName, history, location]);

  const navigate = useCallback((path) => {
    routeStore.navigate(appName, path, 'internal');
  }, [appName]);

  return {
    navigate,
    currentPath: location.pathname,
    routeState: routeStore.getState()
  };
}

// Vue 子应用路由同步
import { watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { routeStore } from '../shared/route-store';

function useMicroAppRouteVue(appName) {
  const router = useRouter();
  const route = useRoute();

  watch(() => route.fullPath, (newPath) => {
    if (routeStore.getState().currentApp !== appName) {
      routeStore.navigate(appName, newPath, 'external');
    }
  });

  routeStore.subscribe((state) => {
    if (state.currentApp === appName) {
      const currentPath = route.fullPath;
      if (state.currentPath !== currentPath) {
        router.push(state.currentPath);
      }
    }
  });

  return {
    navigate: (path) => routeStore.navigate(appName, path, 'internal'),
    currentPath: route.fullPath
  };
}

六、 路由缓存与预加载

class RouteCacheManager {
  constructor() {
    this.cache = new Map();
    this.maxCacheSize = 5;
  }

  async navigateWithCache(appName, path) {
    const key = `${appName}:${path}`;

    if (this.cache.has(key)) {
      const cached = this.cache.get(key);
      if (Date.now() - cached.timestamp < 300000) {
        return cached.component;
      }
      this.cache.delete(key);
    }

    const component = await this.loadComponent(appName, path);
    this.setCache(key, component);

    return component;
  }

  setCache(key, component) {
    if (this.cache.size >= this.maxCacheSize) {
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }

    this.cache.set(key, {
      component,
      timestamp: Date.now()
    });
  }

  async preloadRoutes(appName, routes) {
    for (const route of routes) {
      const key = `${appName}:${route.path}`;
      if (!this.cache.has(key)) {
        this.loadComponent(appName, route.path).then(component => {
          this.setCache(key, component);
        });
      }
    }
  }

  async loadComponent(appName, path) {
    const app = document.querySelector(`[data-app="${appName}"]`);
    if (app && app.contentWindow) {
      app.contentWindow.postMessage({
        type: 'LOAD_ROUTE',
        payload: { path }
      }, '*');
    }
  }
}

七、 路由权限集成

class AuthenticatedRouteManager {
  constructor(routeStore, authService) {
    this.routeStore = routeStore;
    this.authService = authService;
    this.setupGuards();
  }

  setupGuards() {
    this.routeStore.subscribe((state) => {
      const { currentApp, currentPath } = state;

      if (currentApp && !this.checkAccess(currentApp, currentPath)) {
        this.routeStore.navigate('auth', '/login');
      }
    });
  }

  checkAccess(appName, path) {
    const user = this.authService.getCurrentUser();
    if (!user) return false;

    const permissions = this.getRoutePermissions(appName, path);
    if (!permissions || permissions.length === 0) return true;

    return permissions.some(p => user.permissions.includes(p));
  }

  getRoutePermissions(appName, path) {
    const permissionMap = {
      'admin': {
        '/dashboard': ['admin.dashboard.view'],
        '/users': ['admin.users.view'],
        '/settings': ['admin.settings.view']
      },
      'dashboard': {
        '/reports': ['report.view']
      }
    };

    return permissionMap[appName]?.[path];
  }

  wrapRoute(appName, path, component) {
    return () => {
      if (!this.checkAccess(appName, path)) {
        return { redirect: '/login' };
      }
      return component();
    };
  }
}

总结

实践 说明 优先级
基座统一路由 主应用管理 URL,子应用不感知 P0
路由命名空间 子应用路由加前缀防止冲突 P0
状态同步机制 子应用切换时保持路由状态 P1

更多推荐