MST

星途 面试题库

面试题:Angular复杂业务场景下深度优化跨域解决方案

在一个大型Angular项目中,存在多种类型的HTTP请求(如GET、POST、PUT、DELETE等),并且涉及到与多个不同域名的后端服务交互,同时要考虑安全性和性能优化。请设计一套全面的跨域解决方案,并详细说明如何在不同请求类型和后端服务间进行配置和管理,以及怎样通过缓存策略、请求合并等手段进行性能优化。
40.0万 热度难度
前端开发Angular

知识考点

AI 面试

面试题答案

一键面试

跨域解决方案

  1. CORS(跨域资源共享)
    • 服务器端配置:在后端服务器(针对每个不同域名的后端服务)配置允许的源。例如在Node.js中使用express框架,可以这样设置:
const express = require('express');
const app = express();
app.use((req, res, next) => {
    res.setHeader('Access - Control - Allow - Origin', '*');// 允许所有源访问,生产环境应设置具体域名
    res.setHeader('Access - Control - Allow - Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.setHeader('Access - Control - Allow - Headers', 'Content - Type, Authorization');
    next();
});
- **前端配置**:在Angular项目中,无需额外复杂配置,Angular的`HttpClient`会自动处理CORS相关请求头。只要后端正确配置,即可正常发起跨域请求。

2. 代理服务器 - 搭建代理服务器:可以使用http - proxy - middleware在开发环境搭建代理。在Angular项目根目录下创建proxy.conf.json文件:

{
    "/api": {
        "target": "http://your - backend - domain.com",
        "changeOrigin": true,
        "pathRewrite": {
            "^/api": ""
        }
    }
}
- **在Angular CLI中配置**:修改`angular.json`文件,在`architect.serve`下添加:
"proxyConfig": "./proxy.conf.json"

这样在开发环境,所有以/api开头的请求都会被代理到指定的后端域名。

不同请求类型和后端服务间的配置和管理

  1. 封装HTTP服务:在Angular中创建一个通用的HTTP服务,例如HttpService,对不同请求类型进行封装。
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class HttpService {
    constructor(private http: HttpClient) {}

    get<T>(url: string, headers?: HttpHeaders): Observable<T> {
        return this.http.get<T>(url, { headers });
    }

    post<T>(url: string, body: any, headers?: HttpHeaders): Observable<T> {
        return this.http.post<T>(url, body, { headers });
    }

    put<T>(url: string, body: any, headers?: HttpHeaders): Observable<T> {
        return this.http.put<T>(url, body, { headers });
    }

    delete<T>(url: string, headers?: HttpHeaders): Observable<T> {
        return this.http.delete<T>(url, { headers });
    }
}
  1. 针对不同后端服务管理:根据后端服务的不同域名或功能模块,在服务调用时传入不同的基础URL。例如:
const userServiceBaseUrl = 'http://user - backend - domain.com/api';
const productServiceBaseUrl = 'http://product - backend - domain.com/api';

// 使用封装的HttpService
this.httpService.get(`${userServiceBaseUrl}/users`);
this.httpService.post(`${productServiceBaseUrl}/products`, { name: 'new product' });

性能优化

  1. 缓存策略
    • 使用RxJS的shareReplay操作符:对于一些不经常变化的数据请求,例如获取配置信息,可以使用shareReplay进行缓存。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
export class ConfigService {
    private config$: Observable<any>;

    constructor(private http: HttpClient) {}

    getConfig(): Observable<any> {
        if (!this.config$) {
            this.config$ = this.http.get('/api/config').pipe(
                shareReplay(1)
            );
        }
        return this.config$;
    }
}
- **设置合理的缓存时间**:对于缓存的数据,可以设置过期时间。例如,在获取数据时记录时间戳,每次获取缓存数据时检查是否过期。
private cache: { [url: string]: { data: any; timestamp: number } } = {};
private cacheDuration = 60 * 1000; // 1分钟

get<T>(url: string): Observable<T> {
    if (this.cache[url] && Date.now() - this.cache[url].timestamp < this.cacheDuration) {
        return of(this.cache[url].data);
    }
    return this.http.get<T>(url).pipe(
        tap(data => {
            this.cache[url] = { data, timestamp: Date.now() };
        })
    );
}
  1. 请求合并
    • 使用RxJS的forkJoin:当多个请求相互独立但需要同时处理结果时,可以使用forkJoin合并请求。例如,同时获取用户信息和用户订单信息:
import { Component } from '@angular/core';
import { HttpService } from './http.service';
import { forkJoin } from 'rxjs';

@Component({
    selector: 'app - example',
    templateUrl: './example.component.html'
})
export class ExampleComponent {
    constructor(private httpService: HttpService) {}

    ngOnInit() {
        const user$ = this.httpService.get('/api/user');
        const orders$ = this.httpService.get('/api/orders');

        forkJoin([user$, orders$]).subscribe(([user, orders]) => {
            // 处理用户和订单数据
        });
    }
}
- **去重合并**:对于短时间内重复的请求,可以进行去重。例如,维护一个正在进行中的请求队列,当有相同请求时,返回已有的请求Observable,而不是发起新请求。
private inProgressRequests: { [url: string]: Observable<any> } = {};

get<T>(url: string): Observable<T> {
    if (this.inProgressRequests[url]) {
        return this.inProgressRequests[url];
    }
    const request$ = this.http.get<T>(url);
    this.inProgressRequests[url] = request$;
    request$.subscribe(() => {
        delete this.inProgressRequests[url];
    });
    return request$;
}