域名驱动多租户入驻:后台配置 + 前端解析

13 阅读5分钟

域名驱动多租户入驻:后台配置 + 前端解析

摘要:线上租户以访问域名为准,机构与域名的绑定在后台维护;前端在启动阶段用当前域名请求 getByDomain 解析机构信息等,写入会话并在后续请求携带 机构信息。src/tenant 与构建变量承担可选白标定制,与域名租户上下文分层,不宜混写成「一租户一构建产物」。


一、先分清两件事

多租户常被说成「一套代码服务很多家机构」。在本项目里,建议拆成两层理解:

层次回答的问题谁配置前端落点
业务租户(域名)当前访问的是哪家机构后台维护域名与机构关系getByDomain机构信息 → 请求头
前端白标包(可选)页面品牌、路由、权限、主题是否与标准版不同工程侧 src/tenant/*、构建变量loadTenantConfig@tenant 资源

入驻的主线是:后台开通机构并绑定域名 → 用户用该域名访问 → 前端解析机构上下文 → 业务接口按租户隔离。
createOrg.js、多租户构建脚本属于「新机构要深定制页面时」的工程补充,不能替代后台域名配置。

build/tenant-domain-map.js 租户身份在构建期由 VITE_TENANT_ID 参与白标构建;运行时「你是哪家机构」仍由域名 + 后台决定。


二、入驻在业务上包含什么

从「机构能对外服务」倒推,通常至少包括:

  1. 后台:创建机构、分配 机构关键字段 / appId、绑定访问域名(可含测试域、正式域)。
  2. 前端运行时:用当前域名换机构信息,并把 机构关键字段 带入后续 API。
  3. (可选)工程侧:从 tenant-standard 脚手架出新租户目录,改 Logo、协议页、路由覆盖、权限开关;独立租户写入 independentTenants 等。

课程、订单、钱包等数据的租户隔离在后端;前端职责是稳定带上租户上下文,避免串租户。


三、运行时主链路:域名 → 机构 → 请求头

3.1 用域名换机构

店铺/机构信息通过半登录接口按域名查询,开发环境可用 VITE_APP_DOMAIN 模拟线上域名:

export function getShopInfo(params) {
  return request({
    url: "/getByDomain",
    method: "get",
    params: {
       hostname:  window.location.hostname,
    },
  })
}

要点:

  • 生产以 window.location.hostname 为准。
  • 域名与机构的映射在后台维护,前端不写死映射表。
  • 接口路径带 semiLogin,与未登录也可访问的站点场景一致。

3.2 路由守卫里初始化店铺与 orgCode

应用启动后 initRouter 注册全局 beforeEach,在进页面前 await ensureShopInfo()。成功后将 机构关键字段 写入 sessionStorage(键与 configId 组合),并更新 Pinia:

            try {
                shopStore.setLoading(true);
                const shopRes = await getShopInfo();
                if (shopRes && shopRes.data) {
                    sessionStorage.setItem('机构关键字段_'+sessionStorage.getItem('configId'), shopRes.data['机构关键字段信息']);
                    let shopInfo = {
                        ...shopRes.data
                    };
                    shopStore.setShopInfo(shopInfo);
                }

ensureShopInfo 有缓存:Pinia 已有店铺信息则不再请求;失败时可回退 localStorage 中的 shopId。新机构验收时要覆盖「域名已配、接口 200、首屏守卫执行」与「域名未配、接口失败」两类路径。

3.3 请求拦截器:把租户上下文带给后端

request 拦截器从 Cookie、sessionStorage 取 token 与 机构关键字段,写入 Authorization机构关键字段;并从租户配置取 x-app-id

    let token = Cookies.get('t_'+sessionStorage.getItem('configId'))
    // ...
    if (token) {
        config.headers['Authorization'] = ''
    }
    let 机构关键字 = sessionStorage.getItem('机构关键字段_'+sessionStorage.getItem('configId')) || ''
    if (机构关键字段) {
      config.headers['机构关键字段'] = 机构关键字段值
    }
    // ...
    const tenantStore = useTenantStore();
    if (tenantStore.config && tenantStore.config.appInfo) {
        config.headers['x-app-id'] = tenantStore.config.appInfo.appId;
    }

configIdmain.js 里由租户配置的 appInfo.appId 写入 sessionStorage,用于隔离不同应用下的 token、机构关键字段 键名。同一前端包服务多域名时,域名解析出的 机构关键字段配置里的 appId 会共同构成请求上下文。


四、应用启动顺序(与白标配置并行)

main.js 中:创建应用 → initRouter(含上述守卫)→ initTenantConfig(站点 meta、主题、configId)→ initPermissions → 挂载。

    const router = await initRouter();
    app.use(router);
    const tenantConfig = await initTenantConfig(app);
    app.config.globalProperties.tenantConfig = tenantConfig;
    setGlobalConfig(tenantConfig);
    sessionStorage.setItem('configId', tenantConfig?.appInfo?.appId || '');
    await initPermissions();

域名租户白标配置在启动阶段并行就绪:前者靠守卫拉机构,后者靠 loadTenantConfig 加载 src/tenant/<id>/config/index.js(构建期 VITE_TENANT_ID 决定加载哪一份)。缺配置文件会直接报错,与「域名是否已在后台绑定」是两类问题。


五、工程侧入驻:createOrg 与白标目录

需要新机构独立品牌资源、协议、路由时,可用根目录 createOrg.js:创建 src/tenant/<tenantId>/,从 tenant-standard 复制图片、permissions.jsassets/public,生成 config/index.js;若选独立租户,写入 build/tenant-domain-map.jsindependentTenants

脚本不会自动改 package.json 或后台域名,README 要求手动增加 dev:tenant-xxx / build:tenant-xxx,并在后台完成域名绑定。

租户路由通过 name 覆盖平台路由,并合并 beforeEnter,避免换组件时丢掉平台守卫(详见 router/index.jsinitRouter 合并逻辑)。权限来自各租户 assets/js/permissions.js,由 hasPermission 做点路径判断。


六、开发 / 生产差异

场景域名来源注意
生产用户访问的 hostname后台域名与机构一致
本地VITE_APP_DOMAIN 或本机 host模拟线上域名,否则 getByDomain 可能对不上
白标构建VITE_TENANT_ID决定打进产物的 config、静态资源、@tenant 别名

线上「多域名、一套前端」依赖后台域名表 + 前端 getByDomain;深定制可再出多份带不同 VITE_TENANT_ID 的构建产物,二者可组合,但不是同一概念。


七、端到端流程(入驻视角)

sequenceDiagram
  participant Admin as 后台配置
  participant User as 用户浏览器
  participant FE as 前端
  participant API as api-c

  Admin->>Admin: 创建机构并绑定域名
  User->>FE: 访问已绑定域名
  FE->>API: getByDomain(domain)
  API-->>FE: orgCode机构信息等
  FE->>FE: sessionStorage与Pinia
  User->>FE: 路由跳转
  FE->>API: 业务请求带org-code等

八、验收清单(建议)

  1. 后台:机构、机构关键字段、域名(含测试域)已保存且生效。
  2. 用该域名访问:getByDomain 返回预期机构名与 机构关键字段
  3. 任意业务请求:请求头含 机构关键字段(及约定的 x-app-id)。
  4. 若走白标脚手架:config/index.js、协议静态页、权限与构建脚本可本地/预发验证。
  5. 异常:未绑定域名、接口失败时,页面与接口错误可感知,避免静默串到默认机构。

九、小结

多租户入驻的主线是域名在后台配置、前端按域名解析机构并贯穿请求链createOrgsrc/tenant 解决的是白标与工程交付。写方案或专栏时应用「域名租户」与「白标包」两层表述,避免与「单包单租户构建」混为一谈。

支付、分账、宝付监管、独立商户结算属订单与资金域,可在后续篇章单独写;本篇只界定前端在「租户识别」上的边界。