前端架构设计实战:从组件思维到系统思维

前端架构设计实战:从组件思维到系统思维

引言

当你的应用从几个页面增长到几十个页面、从单人开发变成多人协作、从简单展示变成复杂交互时,一个核心问题会浮现出来:如何设计一套可持续演进的架构?

架构设计不是一蹴而就的,它是在不断迭代中形成的。本文将从五个维度展开:

  • 架构模式:从MVC到Clean Architecture的演进
  • 数据流设计:单向数据流与事件溯源
  • 性能架构:从渲染层到网络层的性能设计
  • 安全架构:前端安全威胁与防御体系
  • 可观测性架构:日志、监控、链路追踪

一、架构模式:从MVC到Clean Architecture

1.1 传统MVC在前端的困境

很多前端应用最初采用MVC模式,但随着业务复杂化,问题逐渐暴露:

javascript 复制代码
// ❌ 传统MVC的典型问题
class UserController {
  constructor() {
    this.model = new UserModel();
    this.view = new UserView();
  }

  async getUser(id) {
    // 控制器直接操作模型和视图
    const user = await this.model.fetch(id);

    // 问题1:视图和模型直接耦合
    this.view.display(user);

    // 问题2:业务逻辑散落在控制器中
    if (user.role === 'admin') {
      this.view.showAdminPanel();
    } else {
      this.view.showUserPanel();
    }

    // 问题3:难以测试
    // 控制器依赖具体的模型和视图实现
  }
}

1.2 Clean Architecture:依赖规则

Clean Architecture的核心原则是依赖方向必须由外向内,业务逻辑不依赖任何框架或UI。

复制代码
┌─────────────────────────────┐
│      Presentation Layer     │  React组件、页面
├─────────────────────────────┤
│      Application Layer      │  用例、业务逻辑编排
├─────────────────────────────┤
│      Domain Layer           │  实体、值对象、业务规则
├─────────────────────────────┤
│      Infrastructure Layer   │  API调用、存储、第三方服务
└─────────────────────────────┘

1.3 实战:构建Clean Architecture前端

typescript 复制代码
// domain/entities/User.ts - 领域实体(纯业务逻辑,不依赖任何框架)
export class User {
  private constructor(
    private readonly _id: string,
    private _name: string,
    private _email: string,
    private _role: UserRole,
    private _status: UserStatus
  ) {}

  static create(props: CreateUserProps): User {
    // 业务规则校验
    if (!props.name || props.name.length < 2) {
      throw new DomainError('用户名至少需要2个字符');
    }

    if (!isValidEmail(props.email)) {
      throw new DomainError('邮箱格式不正确');
    }

    return new User(
      props.id || generateId(),
      props.name,
      props.email,
      props.role || 'viewer',
      'active'
    );
  }

  // 业务方法:变更角色
  changeRole(newRole: UserRole): void {
    if (this._status === 'suspended') {
      throw new DomainError('已暂停的用户不能变更角色');
    }

    if (this._role === 'admin' && newRole !== 'admin') {
      // 业务规则:至少保留一个管理员
      this.validateNotLastAdmin();
    }

    this._role = newRole;
  }

  // 业务方法:停用账号
  suspend(): void {
    if (this._role === 'admin') {
      throw new DomainError('不能停用管理员账号');
    }
    this._status = 'suspended';
  }

  // 业务方法:激活账号
  activate(): void {
    this._status = 'active';
  }

  // 值对象:获取用户信息(不暴露内部状态)
  toDTO(): UserDTO {
    return {
      id: this._id,
      name: this._name,
      email: this._email,
      role: this._role,
      status: this._status
    };
  }

  private validateNotLastAdmin(): void {
    // 这个校验需要外部数据,通过依赖注入
    // 在应用层完成
  }
}

// domain/repositories/UserRepository.ts - 仓储接口(定义数据访问契约)
export interface UserRepository {
  findById(id: string): Promise<User | null>;
  findByEmail(email: string): Promise<User | null>;
  findAll(params: QueryParams): Promise<PaginatedResult<User>>;
  save(user: User): Promise<void>;
  delete(id: string): Promise<void>;
}

// domain/services/UserDomainService.ts - 领域服务(跨实体的业务逻辑)
export class UserDomainService {
  constructor(
    private readonly userRepository: UserRepository
  ) {}

  // 业务规则:确保系统至少有一个管理员
  async ensureAtLeastOneAdmin(userId: string): Promise<void> {
    const admins = await this.userRepository.findAll({
      role: 'admin',
      status: 'active'
    });

    if (admins.items.length <= 1) {
      const user = await this.userRepository.findById(userId);
      if (user?.role === 'admin') {
        throw new DomainError('系统必须至少保留一个管理员');
      }
    }
  }
}
typescript 复制代码
// application/usecases/UserUseCases.ts - 应用层(用例编排)
export class UserUseCases {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly userDomainService: UserDomainService,
    private readonly authService: AuthService,
    private readonly eventBus: EventBus
  ) {}

  // 用例:创建用户
  async createUser(input: CreateUserInput): Promise<UserDTO> {
    // 1. 业务规则校验
    const existingUser = await this.userRepository.findByEmail(input.email);
    if (existingUser) {
      throw new ApplicationError('邮箱已被注册');
    }

    // 2. 创建领域实体
    const user = User.create({
      name: input.name,
      email: input.email,
      role: input.role
    });

    // 3. 持久化
    await this.userRepository.save(user);

    // 4. 发布事件
    this.eventBus.publish(new UserCreatedEvent(user.toDTO()));

    // 5. 返回结果
    return user.toDTO();
  }

  // 用例:变更用户角色
  async changeUserRole(input: ChangeRoleInput): Promise<UserDTO> {
    // 1. 获取用户
    const user = await this.userRepository.findById(input.userId);
    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    // 2. 执行领域服务(检查管理员数量)
    await this.userDomainService.ensureAtLeastOneAdmin(input.userId);

    // 3. 执行领域方法
    user.changeRole(input.newRole);

    // 4. 持久化
    await this.userRepository.save(user);

    // 5. 发布事件
    this.eventBus.publish(new UserRoleChangedEvent(user.toDTO()));

    return user.toDTO();
  }

  // 用例:获取用户列表
  async getUsers(params: QueryParams): Promise<PaginatedResult<UserDTO>> {
    const result = await this.userRepository.findAll(params);
    return {
      ...result,
      items: result.items.map(user => user.toDTO())
    };
  }
}
typescript 复制代码
// infrastructure/repositories/UserRepositoryImpl.ts - 基础设施层(技术实现)
export class UserRepositoryImpl implements UserRepository {
  constructor(
    private readonly apiClient: ApiClient,
    private readonly cacheManager: CacheManager
  ) {}

  async findById(id: string): Promise<User | null> {
    // 1. 先查缓存
    const cached = await this.cacheManager.get(`user:${id}`);
    if (cached) {
      return this.toDomain(cached);
    }

    // 2. 缓存未命中,请求API
    const response = await this.apiClient.get(`/users/${id}`);
    if (!response.data) {
      return null;
    }

    // 3. 写入缓存
    await this.cacheManager.set(`user:${id}`, response.data, 300);

    // 4. 转换为领域实体
    return this.toDomain(response.data);
  }

  async save(user: User): Promise<void> {
    const dto = user.toDTO();

    // 1. 请求API
    await this.apiClient.put(`/users/${dto.id}`, dto);

    // 2. 更新缓存
    await this.cacheManager.set(`user:${dto.id}`, dto, 300);
  }

  private toDomain(data: UserDTO): User {
    return User.create({
      id: data.id,
      name: data.name,
      email: data.email,
      role: data.role
    });
  }
}
ini 复制代码
// presentation/hooks/useUserManagement.ts - 表现层(React Hooks)
export const useUserManagement = () => {
  const [users, setUsers] = useState<UserDTO[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // 依赖注入:通过Context获取用例实例
  const { userUseCases } = useApplicationServices();

  const loadUsers = useCallback(async (params?: QueryParams) => {
    setLoading(true);
    setError(null);

    try {
      const result = await userUseCases.getUsers(params || {});
      setUsers(result.items);
    } catch (e) {
      setError(e instanceof Error ? e.message : '加载失败');
    } finally {
      setLoading(false);
    }
  }, [userUseCases]);

  const createUser = useCallback(async (input: CreateUserInput) => {
    try {
      const newUser = await userUseCases.createUser(input);
      setUsers(prev => [...prev, newUser]);
      return newUser;
    } catch (e) {
      setError(e instanceof Error ? e.message : '创建失败');
      throw e;
    }
  }, [userUseCases]);

  const changeRole = useCallback(async (userId: string, newRole: UserRole) => {
    try {
      const updatedUser = await userUseCases.changeUserRole({
        userId,
        newRole
      });
      setUsers(prev => prev.map(u => 
        u.id === userId ? updatedUser : u
      ));
    } catch (e) {
      setError(e instanceof Error ? e.message : '变更失败');
      throw e;
    }
  }, [userUseCases]);

  // 页面加载时获取数据
  useEffect(() => {
    loadUsers();
  }, [loadUsers]);

  return {
    users,
    loading,
    error,
    createUser,
    changeRole,
    reload: loadUsers
  };
};
ini 复制代码
// presentation/components/UserManagementPage.tsx - 表现层(React组件)
export const UserManagementPage: React.FC = () => {
  const {
    users,
    loading,
    error,
    createUser,
    changeRole
  } = useUserManagement();

  const [showCreateModal, setShowCreateModal] = useState(false);

  if (loading) {
    return <LoadingSpinner />;
  }

  if (error) {
    return <ErrorMessage message={error} onRetry={reload} />;
  }

  return (
    <div className="user-management">
      <PageHeader
        title="用户管理"
        action={
          <Button onClick={() => setShowCreateModal(true)}>
            创建用户
          </Button>
        }
      />

      <UserTable
        users={users}
        onChangeRole={changeRole}
      />

      {showCreateModal && (
        <CreateUserModal
          onClose={() => setShowCreateModal(false)}
          onSubmit={async (data) => {
            await createUser(data);
            setShowCreateModal(false);
          }}
        />
      )}
    </div>
  );
};

二、数据流设计:从Redux到事件溯源

2.1 单向数据流的问题

传统Redux的单向数据流虽然解决了状态管理问题,但在复杂业务场景下仍有不足:

go 复制代码
// ❌ 传统Redux的问题
// 1. 大量样板代码
const FETCH_USER_REQUEST = 'FETCH_USER_REQUEST';
const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';

const fetchUserRequest = () => ({ type: FETCH_USER_REQUEST });
const fetchUserSuccess = (data) => ({ type: FETCH_USER_SUCCESS, payload: data });
const fetchUserFailure = (error) => ({ type: FETCH_USER_FAILURE, payload: error });

// 2. 状态变化逻辑分散
const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USER_REQUEST:
      return { ...state, loading: true };
    case FETCH_USER_SUCCESS:
      return { ...state, loading: false, user: action.payload };
    case FETCH_USER_FAILURE:
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

// 3. 难以追踪状态变化的原因
// 当状态出现bug时,很难知道是哪个action导致的

2.2 事件溯源架构

事件溯源(Event Sourcing)的核心思想是:不保存当前状态,而是保存所有事件,状态由事件推导而来。

typescript 复制代码
// events/domain-events.ts - 领域事件定义
export interface DomainEvent {
  id: string;
  type: string;
  timestamp: number;
  aggregateId: string;
  version: number;
  payload: unknown;
}

export class UserCreatedEvent implements DomainEvent {
  readonly type = 'UserCreated';
  readonly timestamp = Date.now();
  readonly version = 1;

  constructor(
    readonly aggregateId: string,
    readonly payload: {
      name: string;
      email: string;
      role: UserRole;
    }
  ) {}
}

export class UserRoleChangedEvent implements DomainEvent {
  readonly type = 'UserRoleChanged';
  readonly timestamp = Date.now();
  readonly version = 1;

  constructor(
    readonly aggregateId: string,
    readonly payload: {
      oldRole: UserRole;
      newRole: UserRole;
      changedBy: string;
    }
  ) {}
}

export class UserSuspendedEvent implements DomainEvent {
  readonly type = 'UserSuspended';
  readonly timestamp = Date.now();
  readonly version = 1;

  constructor(
    readonly aggregateId: string,
    readonly payload: {
      reason: string;
      suspendedBy: string;
    }
  ) {}
}
csharp 复制代码
// event-store/EventStore.ts - 事件存储
export class EventStore {
  private events: DomainEvent[] = [];
  private snapshots: Map<string, Snapshot> = new Map();

  constructor(
    private readonly storage: StorageAdapter,
    private readonly snapshotThreshold = 100
  ) {}

  // 追加事件
  async appendEvent(event: DomainEvent): Promise<void> {
    // 1. 持久化事件
    await this.storage.append(event);

    // 2. 更新内存事件列表
    this.events.push(event);

    // 3. 检查是否需要创建快照
    const aggregateEvents = await this.getEvents(event.aggregateId);
    if (aggregateEvents.length >= this.snapshotThreshold) {
      await this.createSnapshot(event.aggregateId);
    }
  }

  // 获取聚合的所有事件
  async getEvents(aggregateId: string): Promise<DomainEvent[]> {
    // 1. 检查是否有快照
    const snapshot = this.snapshots.get(aggregateId);
    if (snapshot) {
      // 只获取快照之后的事件
      const events = await this.storage.getEventsAfter(
        aggregateId,
        snapshot.version
      );
      return events;
    }

    // 2. 没有快照,获取全部事件
    return this.storage.getEvents(aggregateId);
  }

  // 创建快照
  private async createSnapshot(aggregateId: string): Promise<void> {
    const events = await this.getEvents(aggregateId);
    const state = this.replay(events);

    const snapshot: Snapshot = {
      aggregateId,
      version: events[events.length - 1].version,
      state,
      timestamp: Date.now()
    };

    this.snapshots.set(aggregateId, snapshot);
    await this.storage.saveSnapshot(snapshot);
  }

  // 重放事件,恢复状态
  replay(events: DomainEvent[]): AggregateState {
    let state: AggregateState = {};

    for (const event of events) {
      state = this.applyEvent(state, event);
    }

    return state;
  }

  // 应用单个事件到状态
  private applyEvent(state: AggregateState, event: DomainEvent): AggregateState {
    switch (event.type) {
      case 'UserCreated':
        return {
          ...state,
          id: event.aggregateId,
          name: event.payload.name,
          email: event.payload.email,
          role: event.payload.role,
          status: 'active',
          createdAt: event.timestamp
        };

      case 'UserRoleChanged':
        return {
          ...state,
          role: event.payload.newRole,
          updatedAt: event.timestamp
        };

      case 'UserSuspended':
        return {
          ...state,
          status: 'suspended',
          suspendedAt: event.timestamp,
          suspendReason: event.payload.reason
        };

      default:
        return state;
    }
  }
}
kotlin 复制代码
// aggregate/UserAggregate.ts - 聚合根
export class UserAggregate {
  private events: DomainEvent[] = [];
  private state: AggregateState = {};

  constructor(
    private readonly eventStore: EventStore,
    private readonly id: string
  ) {}

  // 从事件流恢复聚合状态
  static async load(
    eventStore: EventStore,
    id: string
  ): Promise<UserAggregate> {
    const aggregate = new UserAggregate(eventStore, id);
    const events = await eventStore.getEvents(id);
    aggregate.state = eventStore.replay(events);
    aggregate.events = events;
    return aggregate;
  }

  // 创建新用户
  async createUser(props: CreateUserProps): Promise<void> {
    // 业务规则校验
    if (this.state.id) {
      throw new DomainError('用户已存在');
    }

    if (!isValidEmail(props.email)) {
      throw new DomainError('邮箱格式不正确');
    }

    // 创建事件
    const event = new UserCreatedEvent(this.id, {
      name: props.name,
      email: props.email,
      role: props.role
    });

    // 应用事件到本地状态
    this.applyEvent(event);

    // 持久化事件
    await this.eventStore.appendEvent(event);
  }

  // 变更角色
  async changeRole(newRole: UserRole, changedBy: string): Promise<void> {
    if (this.state.status === 'suspended') {
      throw new DomainError('已暂停的用户不能变更角色');
    }

    if (this.state.role === newRole) {
      return; // 无变化
    }

    const event = new UserRoleChangedEvent(this.id, {
      oldRole: this.state.role,
      newRole,
      changedBy
    });

    this.applyEvent(event);
    await this.eventStore.appendEvent(event);
  }

  // 获取当前状态
  getState(): UserDTO {
    return {
      id: this.state.id,
      name: this.state.name,
      email: this.state.email,
      role: this.state.role,
      status: this.state.status
    };
  }

  // 获取事件历史
  getEventHistory(): DomainEvent[] {
    return this.events;
  }

  private applyEvent(event: DomainEvent): void {
    // 使用EventStore的applyEvent逻辑
    this.state = this.eventStore.replay([...this.events, event]);
    this.events.push(event);
  }
}
csharp 复制代码
// query/UserQueryService.ts - 查询服务(CQRS模式)
export class UserQueryService {
  constructor(
    private readonly eventStore: EventStore,
    private readonly readModel: ReadModel
  ) {}

  // 获取用户列表(从读模型读取,性能优化)
  async getUsers(params: QueryParams): Promise<PaginatedResult<UserDTO>> {
    // 读模型是专门为查询优化的数据存储
    return this.readModel.findUsers(params);
  }

  // 获取用户详情
  async getUserById(id: string): Promise<UserDTO | null> {
    // 1. 先从读模型获取
    const fromReadModel = await this.readModel.findUser(id);
    if (fromReadModel) {
      return fromReadModel;
    }

    // 2. 读模型没有,从事件流重建
    const aggregate = await UserAggregate.load(this.eventStore, id);
    return aggregate.getState();
  }

  // 获取用户变更历史
  async getUserHistory(id: string): Promise<EventHistoryDTO[]> {
    const events = await this.eventStore.getEvents(id);
    return events.map(event => ({
      type: event.type,
      timestamp: event.timestamp,
      version: event.version,
      payload: event.payload
    }));
  }
}
kotlin 复制代码
// read-model/UserReadModel.ts - 读模型实现
export class UserReadModel {
  private users: Map<string, UserDTO> = new Map();
  private indexes: {
    byEmail: Map<string, string>;
    byRole: Map<UserRole, Set<string>>;
    byStatus: Map<UserStatus, Set<string>>;
  } = {
    byEmail: new Map(),
    byRole: new Map(),
    byStatus: new Map()
  };

  constructor(private readonly eventStore: EventStore) {
    this.subscribeToEvents();
  }

  // 订阅事件,实时更新读模型
  private subscribeToEvents(): void {
    // 通过WebSocket或轮询获取新事件
    // 这里简化为直接监听
    this.eventStore.on('event-appended', (event: DomainEvent) => {
      this.handleEvent(event);
    });
  }

  // 处理事件,更新读模型
  private handleEvent(event: DomainEvent): void {
    switch (event.type) {
      case 'UserCreated':
        this.addUser(event);
        break;

      case 'UserRoleChanged':
        this.updateUserRole(event);
        break;

      case 'UserSuspended':
        this.updateUserStatus(event, 'suspended');
        break;

      case 'UserActivated':
        this.updateUserStatus(event, 'active');
        break;
    }
  }

  private addUser(event: UserCreatedEvent): void {
    const user: UserDTO = {
      id: event.aggregateId,
      name: event.payload.name,
      email: event.payload.email,
      role: event.payload.role,
      status: 'active',
      createdAt: event.timestamp
    };

    this.users.set(user.id, user);
    this.indexes.byEmail.set(user.email, user.id);
    this.addToIndex(this.indexes.byRole, user.role, user.id);
    this.addToIndex(this.indexes.byStatus, user.status, user.id);
  }

  private updateUserRole(event: UserRoleChangedEvent): void {
    const user = this.users.get(event.aggregateId);
    if (!user) return;

    // 更新角色索引
    this.removeFromIndex(this.indexes.byRole, user.role, user.id);
    user.role = event.payload.newRole;
    this.addToIndex(this.indexes.byRole, user.role, user.id);
  }

  private updateUserStatus(
    event: DomainEvent,
    newStatus: UserStatus
  ): void {
    const user = this.users.get(event.aggregateId);
    if (!user) return;

    this.removeFromIndex(this.indexes.byStatus, user.status, user.id);
    user.status = newStatus;
    this.addToIndex(this.indexes.byStatus, user.status, user.id);
  }

  // 查询方法
  async findUser(id: string): Promise<UserDTO | null> {
    return this.users.get(id) || null;
  }

  async findUsers(params: QueryParams): Promise<PaginatedResult<UserDTO>> {
    let result = Array.from(this.users.values());

    // 按角色过滤
    if (params.role) {
      const ids = this.indexes.byRole.get(params.role) || new Set();
      result = result.filter(user => ids.has(user.id));
    }

    // 按状态过滤
    if (params.status) {
      const ids = this.indexes.byStatus.get(params.status) || new Set();
      result = result.filter(user => ids.has(user.id));
    }

    // 按邮箱搜索
    if (params.email) {
      const id = this.indexes.byEmail.get(params.email);
      result = result.filter(user => user.id === id);
    }

    // 分页
    const total = result.length;
    const start = (params.page - 1) * params.pageSize;
    const items = result.slice(start, start + params.pageSize);

    return { items, total, page: params.page, pageSize: params.pageSize };
  }

  private addToIndex(
    index: Map<string, Set<string>>,
    key: string,
    id: string
  ): void {
    if (!index.has(key)) {
      index.set(key, new Set());
    }
    index.get(key)!.add(id);
  }

  private removeFromIndex(
    index: Map<string, Set<string>>,
    key: string,
    id: string
  ): void {
    const ids = index.get(key);
    if (ids) {
      ids.delete(id);
      if (ids.size === 0) {
        index.delete(key);
      }
    }
  }
}

三、性能架构:从渲染到网络的全面优化

3.1 渲染性能架构

typescript 复制代码
// performance/RenderOptimizer.ts - 渲染优化器
export class RenderOptimizer {
  private intersectionObserver: IntersectionObserver;
  private idleCallback: IdleDeadline | null = null;
  private priorityQueue: PriorityQueue<Task> = new PriorityQueue();

  constructor() {
    // 使用IntersectionObserver实现懒加载
    this.intersectionObserver = new IntersectionObserver(
      (entries) => this.handleIntersection(entries),
      {
        rootMargin: '50px',
        threshold: 0.1
      }
    );

    // 使用requestIdleCallback处理低优先级任务
    if ('requestIdleCallback' in window) {
      this.idleCallback = requestIdleCallback(
        (deadline) => this.processIdleTasks(deadline)
      );
    }
  }

  // 注册懒加载组件
  observeLazyComponent(element: HTMLElement, loadFn: () => void): void {
    this.intersectionObserver.observe(element);
    // 存储加载函数
    (element as any).__loadFn = loadFn;
  }

  private handleIntersection(
    entries: IntersectionObserverEntry[]
  ): void {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const loadFn = (entry.target as any).__loadFn;
        if (loadFn) {
          // 高优先级任务
          this.priorityQueue.enqueue({
            priority: 10,
            execute: loadFn
          });
          this.intersectionObserver.unobserve(entry.target);
        }
      }
    });
  }

  // 处理空闲任务
  private processIdleTasks(deadline: IdleDeadline): void {
    while (deadline.timeRemaining() > 0 && !this.priorityQueue.isEmpty()) {
      const task = this.priorityQueue.dequeue();
      if (task) {
        task.execute();
      }
    }

    // 继续调度
    if ('requestIdleCallback' in window) {
      this.idleCallback = requestIdleCallback(
        (newDeadline) => this.processIdleTasks(newDeadline)
      );
    }
  }

  // 批量更新DOM(避免布局抖动)
  batchDOMUpdates(updates: Array<() => void>): void {
    // 使用requestAnimationFrame批量执行
    requestAnimationFrame(() => {
      updates.forEach(update => update());
    });
  }
}

// 使用示例
const optimizer = new RenderOptimizer();

// 注册懒加载组件
const heavyComponent = document.getElementById('heavy-component');
optimizer.observeLazyComponent(heavyComponent, () => {
  // 动态导入组件
  import('./HeavyComponent').then(module => {
    // 渲染组件
  });
});

3.2 网络性能架构

typescript 复制代码
// network/NetworkArchitecture.ts - 网络层架构
export class NetworkArchitecture {
  private cache: CacheManager;
  private prefetcher: Prefetcher;
  private requestQueue: RequestQueue;
  private retryManager: RetryManager;

  constructor() {
    this.cache = new CacheManager({
      maxAge: 5 * 60 * 1000, // 5分钟
      maxSize: 100 // 最多缓存100个请求
    });

    this.prefetcher = new Prefetcher({
      prefetchDelay: 2000, // 页面加载2秒后开始预取
      maxPrefetch: 5 // 最多预取5个资源
    });

    this.requestQueue = new RequestQueue({
      maxConcurrent: 6, // 最大并发数
      maxQueueSize: 50 // 最大队列长度
    });

    this.retryManager = new RetryManager({
      maxRetries: 3,
      backoffFactor: 2,
      initialDelay: 1000
    });
  }

  // 智能请求方法
  async smartRequest<T>(
    config: RequestConfig
  ): Promise<T> {
    const cacheKey = this.generateCacheKey(config);

    // 1. 检查缓存
    const cached = await this.cache.get(cacheKey);
    if (cached && !config.forceRefresh) {
      return cached.data as T;
    }

    // 2. 加入请求队列
    return this.requestQueue.enqueue(async () => {
      try {
        // 3. 执行请求(带重试)
        const response = await this.retryManager.execute(async () => {
          return this.executeRequest(config);
        });

        // 4. 写入缓存
        if (config.cacheable !== false) {
          await this.cache.set(cacheKey, response);
        }

        return response;
      } catch (error) {
        // 5. 错误处理
        return this.handleError(error, config);
      }
    });
  }

  // 预取关键资源
  prefetchCriticalResources(resources: ResourceConfig[]): void {
    this.prefetcher.addResources(resources);
  }

  // 预连接
  preconnect(origins: string[]): void {
    origins.forEach(origin => {
      const link = document.createElement('link');
      link.rel = 'preconnect';
      link.href = origin;
      document.head.appendChild(link);
    });
  }

  // 预加载
  preload(resources: ResourceConfig[]): void {
    resources.forEach(resource => {
      const link = document.createElement('link');
      link.rel = 'preload';
      link.as = resource.type;
      link.href = resource.url;
      document.head.appendChild(link);
    });
  }

  private generateCacheKey(config: RequestConfig): string {
    return `${config.method}:${config.url}:${JSON.stringify(config.params || {})}`;
  }

  private async executeRequest(config: RequestConfig): Promise<any> {
    const { url, method = 'GET', params, data, headers } = config;

    // 构建完整URL
    const fullUrl = this.buildUrl(url, params);

    // 执行请求
    const response = await fetch(fullUrl, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...headers
      },
      body: data ? JSON.stringify(data) : undefined
    });

    // 检查响应状态
    if (!response.ok) {
      throw new HttpError(response.status, response.statusText);
    }

    return response.json();
  }

  private buildUrl(url: string, params?: Record<string, unknown>): string {
    if (!params) return url;

    const searchParams = new URLSearchParams();
    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined && value !== null) {
        searchParams.append(key, String(value));
      }
    });

    const separator = url.includes('?') ? '&' : '?';
    return `${url}${separator}${searchParams.toString()}`;
  }

  private async handleError(
    error: Error,
    config: RequestConfig
  ): Promise<never> {
    // 错误分类处理
    if (error instanceof HttpError) {
      switch (error.status) {
        case 401:
          // 未授权,跳转登录
          this.redirectToLogin();
          break;

        case 403:
          // 禁止访问
          throw new ForbiddenError('没有权限访问该资源');

        case 404:
          // 资源不存在
          throw new NotFoundError('资源不存在');

        case 500:
          // 服务器错误
          throw new ServerError('服务器内部错误');
      }
    }

    // 网络错误
    if (error instanceof TypeError) {
      // 检查网络状态
      if (!navigator.onLine) {
        throw new NetworkError('网络连接已断开');
      }
    }

    throw error;
  }
}

3.3 资源加载架构

kotlin 复制代码
// performance/ResourceLoader.ts - 资源加载器
export class ResourceLoader {
  private loadedResources: Map<string, Promise<void>> = new Map();
  private priorityGroups: Map<Priority, Resource[]> = new Map();

  constructor(
    private readonly optimizer: RenderOptimizer
  ) {
    this.initializePriorityGroups();
  }

  private initializePriorityGroups(): void {
    this.priorityGroups.set('critical', []);
    this.priorityGroups.set('high', []);
    this.priorityGroups.set('normal', []);
    this.priorityGroups.set('low', []);
  }

  // 加载关键资源
  loadCritical(resource: Resource): void {
    this.addResource('critical', resource);
    this.processPriorityGroup('critical');
  }

  // 加载高优先级资源
  loadHighPriority(resource: Resource): void {
    this.addResource('high', resource);
    this.processPriorityGroup('high');
  }

  // 加载普通资源
  loadNormal(resource: Resource): void {
    this.addResource('normal', resource);
    this.processPriorityGroup('normal');
  }

  // 加载低优先级资源
  loadLowPriority(resource: Resource): void {
    this.addResource('low', resource);
    // 使用requestIdleCallback延迟加载
    requestIdleCallback(() => {
      this.processPriorityGroup('low');
    });
  }

  private addResource(priority: Priority, resource: Resource): void {
    const group = this.priorityGroups.get(priority);
    if (group) {
      group.push(resource);
    }
  }

  private async processPriorityGroup(priority: Priority): Promise<void> {
    const group = this.priorityGroups.get(priority);
    if (!group || group.length === 0) return;

    // 清空组
    this.priorityGroups.set(priority, []);

    // 并发加载(限制并发数)
    const concurrencyLimit = this.getConcurrencyLimit(priority);
    const chunks = this.chunkArray(group, concurrencyLimit);

    for (const chunk of chunks) {
      await Promise.all(chunk.map(resource => this.loadResource(resource)));
    }
  }

  private async loadResource(resource: Resource): Promise<void> {
    // 检查是否已加载
    if (this.loadedResources.has(resource.url)) {
      return this.loadedResources.get(resource.url);
    }

    // 创建加载Promise
    const loadPromise = this.performLoad(resource);
    this.loadedResources.set(resource.url, loadPromise);

    try {
      await loadPromise;
    } catch (error) {
      // 加载失败,移除记录以便重试
      this.loadedResources.delete(resource.url);
      throw error;
    }
  }

  private async performLoad(resource: Resource): Promise<void> {
    switch (resource.type) {
      case 'script':
        await this.loadScript(resource);
        break;

      case 'style':
        await this.loadStyle(resource);
        break;

      case 'image':
        await this.loadImage(resource);
        break;

      case 'font':
        await this.loadFont(resource);
        break;

      case 'component':
        await this.loadComponent(resource);
        break;
    }
  }

  private loadScript(resource: Resource): Promise<void> {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = resource.url;
      script.async = true;

      if (resource.module) {
        script.type = 'module';
      }

      script.onload = () => resolve();
      script.onerror = () => reject(new Error(`Failed to load script: ${resource.url}`));

      document.head.appendChild(script);
    });
  }

  private loadStyle(resource: Resource): Promise<void> {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = resource.url;

      link.onload = () => resolve();
      link.onerror = () => reject(new Error(`Failed to load style: ${resource.url}`));

      document.head.appendChild(link);
    });
  }

  private loadImage(resource: Resource): Promise<void> {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.src = resource.url;

      img.onload = () => resolve();
      img.onerror = () => reject(new Error(`Failed to load image: ${resource.url}`));
    });
  }

  private loadFont(resource: Resource): Promise<void> {
    // 使用FontFace API
    const font = new FontFace(resource.family, `url(${resource.url})`);
    return font.load().then(() => {
      document.fonts.add(font);
    });
  }

  private async loadComponent(resource: Resource): Promise<void> {
    // 动态导入组件
    await import(resource.url);
  }

  private getConcurrencyLimit(priority: Priority): number {
    switch (priority) {
      case 'critical': return 4;
      case 'high': return 3;
      case 'normal': return 2;
      case 'low': return 1;
      default: return 2;
    }
  }

  private chunkArray<T>(array: T[], size: number): T[][] {
    const chunks: T[][] = [];
    for (let i = 0; i < array.length; i += size) {
      chunks.push(array.slice(i, i + size));
    }
    return chunks;
  }
}

四、安全架构:前端安全威胁与防御

4.1 前端安全威胁模型

typescript 复制代码
// security/ThreatModel.ts - 威胁模型
export enum ThreatType {
  XSS = 'XSS',                    // 跨站脚本
  CSRF = 'CSRF',                  // 跨站请求伪造
  CLICKJACKING = 'CLICKJACKING',  // 点击劫持
  MITM = 'MITM',                  // 中间人攻击
  DATA_THEFT = 'DATA_THEFT',      // 数据窃取
  DOS = 'DOS'                     // 拒绝服务
}

export interface ThreatAssessment {
  type: ThreatType;
  severity: 'low' | 'medium' | 'high' | 'critical';
  likelihood: 'unlikely' | 'possible' | 'likely' | 'certain';
  impact: 'minimal' | 'significant' | 'severe' | 'catastrophic';
  mitigation: string[];
}

4.2 XSS防御体系

typescript 复制代码
// security/XSSProtection.ts - XSS防御
export class XSSProtection {
  private static instance: XSSProtection;
  private sanitizer: DOMPurify;

  private constructor() {
    // 配置DOMPurify
    this.sanitizer = DOMPurify();
    this.sanitizer.addHook('afterSanitizeAttributes', (node) => {
      // 移除所有事件处理器
      node.removeAttribute('onerror');
      node.removeAttribute('onload');
      node.removeAttribute('onclick');
    });
  }

  static getInstance(): XSSProtection {
    if (!this.instance) {
      this.instance = new XSSProtection();
    }
    return this.instance;
  }

  // 净化HTML
  sanitizeHTML(html: string): string {
    return this.sanitizer.sanitize(html, {
      ALLOWED_TAGS: [
        'p', 'br', 'strong', 'em', 'ul', 'ol', 'li',
        'a', 'code', 'pre', 'blockquote', 'img'
      ],
      ALLOWED_ATTR: ['href', 'src', 'alt', 'title'],
      ALLOW_DATA_ATTR: false
    });
  }

  // 安全编码
  encodeForHTML(text: string): string {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

  encodeForAttribute(text: string): string {
    return text.replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
  }

  encodeForURL(text: string): string {
    return encodeURIComponent(text)
      .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16)}`);
  }

  encodeForJavaScript(text: string): string {
    return text.replace(/\/g, '\\')
      .replace(/'/g, "\'")
      .replace(/"/g, '\"')
      .replace(/\n/g, '\n')
      .replace(/\r/g, '\r')
      .replace(/</g, '\x3C')
      .replace(/>/g, '\x3E');
  }
}

// 使用示例
const xssProtection = XSSProtection.getInstance();

// 在React中使用
const SafeComponent = ({ userContent }) => {
  const sanitizedContent = useMemo(() => {
    return xssProtection.sanitizeHTML(userContent);
  }, [userContent]);

  return (
    <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
  );
};

4.3 CSRF防御体系

kotlin 复制代码
// security/CSRFProtection.ts - CSRF防御
export class CSRFProtection {
  private static instance: CSRFProtection;
  private token: string | null = null;
  private tokenExpiry: number = 0;

  private constructor() {
    this.initializeToken();
  }

  static getInstance(): CSRFProtection {
    if (!this.instance) {
      this.instance = new CSRFProtection();
    }
    return this.instance;
  }

  // 初始化Token
  private initializeToken(): void {
    // 1. 从Cookie中获取
    const cookieToken = this.getCookie('csrf_token');
    if (cookieToken) {
      this.token = cookieToken;
      return;
    }

    // 2. 从Meta标签获取
    const metaToken = document.querySelector('meta[name="csrf-token"]');
    if (metaToken) {
      this.token = metaToken.getAttribute('content');
      return;
    }

    // 3. 生成新Token
    this.token = this.generateToken();
    this.setCookie('csrf_token', this.token);
  }

  // 生成Token
  private generateToken(): string {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => 
      byte.toString(16).padStart(2, '0')
    ).join('');
  }

  // 获取Token
  getToken(): string {
    // 检查Token是否过期
    if (Date.now() > this.tokenExpiry) {
      this.initializeToken();
    }
    return this.token!;
  }

  // 为请求添加Token
  attachToken(headers: Headers): Headers {
    const token = this.getToken();

    // 使用自定义Header
    headers.set('X-CSRF-Token', token);

    // 双重提交Cookie模式
    headers.set('X-Requested-With', 'XMLHttpRequest');

    return headers;
  }

  // 验证Token(用于响应拦截)
  validateResponse(response: Response): boolean {
    const serverToken = response.headers.get('X-CSRF-Token');
    if (!serverToken
相关推荐
Amos_Web1 小时前
Rspack 源码解析(二十):Typed Hook 系统如何靠宏生成
前端·rust·前端框架
程序员大阳2 小时前
副队长CSS教程(29)–CSS Grid 实战:实现通用图文混排布局
前端·css·grid·图文混排
对空六课2 小时前
无头浏览器访问对跳出率的过滤规则
服务器·前端·算法·数据分析
小番茄夫斯基2 小时前
Nginx 介绍
前端
小番茄夫斯基2 小时前
如果我一个前端页面要被用户看到,原理是什么,用户怎么通过网址就看到页面的,这背后要怎么弄
前端
迅猛龙办公室2 小时前
Python实现获取星期字符串
java·前端·python
小小荧2 小时前
🚀 Node.js 之父离开 Deno,带着整个团队加入了 Cloudflare
前端·node.js
IllegalCreed2 小时前
人中吕布,卡中 Blackwell:我用三国志14的数值给 687 个大模型排了座位
前端