Angular Universal多子域名/跨域名部署及差异化配置技术问询
Angular Universal 多子域名差异化实现方案
针对你需要用Angular Universal实现单一代码库多子域名差异化运行的需求,核心是在SSR环境下引导前获取请求域名,再通过全局标记控制路由、主题和功能,以下是具体实现步骤:
一、SSR环境中获取请求域名(无需window)
Angular Universal通过REQUEST令牌传递服务端请求上下文,我们可以在服务端渲染时提取请求域名,并注入到应用中:
修改
server.ts,在渲染引擎中提供请求域名令牌:// server.ts import { ngExpressEngine } from '@nguniversal/express-engine'; import { AppServerModule } from './src/main.server'; import { REQUEST } from '@nguniversal/express-engine/tokens'; app.engine('html', ngExpressEngine({ bootstrap: AppServerModule, providers: [ { provide: 'REQUEST_DOMAIN', useFactory: (req: Request) => req.hostname, deps: [REQUEST] } ] }));在应用根模块定义令牌,并处理客户端兼容:
// app.module.ts import { InjectionToken, PLATFORM_ID } from '@angular/core'; import { isPlatformServer } from '@angular/common'; export const REQUEST_DOMAIN = new InjectionToken<string>('REQUEST_DOMAIN'); // 客户端环境下,从location获取域名,避免注入错误 export const requestDomainProvider = { provide: REQUEST_DOMAIN, useFactory: (platformId: object) => isPlatformServer(platformId) ? '' : window.location.hostname, deps: [PLATFORM_ID] };同时在
app.server.module.ts中确保服务端注入生效:// app.server.module.ts import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; import { AppModule } from './app.module'; import { AppComponent } from './app.component'; import { REQUEST_DOMAIN } from './app.module'; import { REQUEST } from '@nguniversal/express-engine/tokens'; @NgModule({ imports: [AppModule, ServerModule], bootstrap: [AppComponent], providers: [ { provide: REQUEST_DOMAIN, useFactory: (req: any) => req.hostname, deps: [REQUEST] } ] }) export class AppServerModule {}
二、引导前设置全局条件标记
使用APP_INITIALIZER在应用引导前解析域名,设置全局标记,确保所有模块初始化时能读取到差异化配置:
// app.module.ts import { APP_INITIALIZER, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { REQUEST_DOMAIN, requestDomainProvider } from './app.module'; import { PLATFORM_ID } from '@angular/core'; import { isPlatformServer } from '@angular/common'; // 全局配置服务,存储域名相关标记 @Injectable({ providedIn: 'root' }) export class DomainConfigService { domainFlags = { isMain: false, isMarketing: false, isTenant: false, tenantId: '' }; parseDomain(hostname: string) { switch(hostname) { case 'www.app.com': this.domainFlags.isMain = true; break; case 'markeeting.app.com': this.domainFlags.isMarketing = true; break; default: if (hostname.endsWith('.app.com')) { this.domainFlags.isTenant = true; this.domainFlags.tenantId = hostname.split('.')[0]; } } } } // 初始化函数,引导前执行域名解析 export function initDomainConfig( config: DomainConfigService, domain: string, platformId: object ) { return () => { const hostname = isPlatformServer(platformId) ? domain : window.location.hostname; config.parseDomain(hostname); return Promise.resolve(); }; } @NgModule({ declarations: [AppComponent], imports: [BrowserModule.withServerTransition({ appId: 'serverApp' })], providers: [ requestDomainProvider, { provide: APP_INITIALIZER, useFactory: initDomainConfig, deps: [DomainConfigService, REQUEST_DOMAIN, PLATFORM_ID], multi: true } ], bootstrap: [AppComponent] }) export class AppModule {}
三、差异化路由配置(营销子域名)
在路由模块中,根据全局标记动态替换路由配置:
// app-routing.module.ts import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DomainConfigService } from './app.module'; // 主域名路由 const mainRoutes: Routes = [ { path: '', loadChildren: () => import('./main/main.module').then(m => m.MainModule) }, // 其他主域名路由 ]; // 营销域名路由 const marketingRoutes: Routes = [ { path: '', loadChildren: () => import('./marketing/marketing.module').then(m => m.MarketingModule) }, // 其他营销域名专属路由 ]; @NgModule({ imports: [RouterModule.forRoot([])], exports: [RouterModule] }) export class AppRoutingModule { constructor(private router: Router, private domainConfig: DomainConfigService) { // 引导后重置路由配置 this.router.resetConfig( this.domainConfig.domainFlags.isMarketing ? marketingRoutes : mainRoutes ); } }
四、租户子域名的主题与功能控制
在组件或服务中注入DomainConfigService,根据标记动态调整UI和功能:
// app.component.ts import { Component, OnInit } from '@angular/core'; import { DomainConfigService } from './app.module'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { constructor(private domainConfig: DomainConfigService) {} ngOnInit() { // 动态设置主题类 const body = document.body; body.classList.remove('main-theme', 'marketing-theme'); if (this.domainConfig.domainFlags.isTenant) { body.classList.add(`tenant-${this.domainConfig.domainFlags.tenantId}`); } else if (this.domainConfig.domainFlags.isMarketing) { body.classList.add('marketing-theme'); } else { body.classList.add('main-theme'); } } // 控制功能显示 canShowFeature(featureKey: string): boolean { // 租户域名隐藏admin面板 if (this.domainConfig.domainFlags.isTenant && featureKey === 'admin-panel') { return false; } return true; } }
在模板中使用控制逻辑:
<!-- app.component.html --> <nav> <a routerLink="/">首页</a> <a *ngIf="canShowFeature('admin-panel')" routerLink="/admin">管理面板</a> </nav> <router-outlet></router-outlet>
内容的提问来源于stack exchange,提问作者oneofthedevs
相关产品推荐
相关产品推荐

