前端架构设计实战:从组件思维到系统思维
引言
当你的应用从几个页面增长到几十个页面、从单人开发变成多人协作、从简单展示变成复杂交互时,一个核心问题会浮现出来:如何设计一套可持续演进的架构?
架构设计不是一蹴而就的,它是在不断迭代中形成的。本文将从五个维度展开:
- 架构模式:从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, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
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