引言:为什么需要线上数据同步?
在现代Web应用开发中,数据同步是一个核心需求。特别是对于Vue3应用,我们需要确保用户看到的数据始终是最新的。想象一下,如果你正在开发一个电商后台管理系统,库存数据、订单状态等信息需要实时更新,否则可能导致严重的业务问题。
线上数据同步不仅仅是简单的数据获取,它涉及多个层面的考虑:
实时性
:数据变化需要多快反映到用户界面
性能
:频繁的数据请求如何不影响应用性能
可靠性
:网络不稳定时如何保证数据一致性
用户体验
:如何优雅地处理数据加载和更新状态
本文将从基础配置开始,逐步深入到实时更新的优化策略,最后讨论常见问题的排查方法,帮助你构建健壮的Vue3数据同步方案。
一、基础配置:搭建数据同步的基础设施
1.1 选择合适的HTTP客户端
在Vue3中,我们通常使用Axios或Fetch API来处理HTTP请求。这里我推荐使用Axios,因为它提供了更丰富的功能和更好的浏览器兼容性。
首先,我们需要安装Axios:
npm install axios
然后创建一个基础的HTTP客户端配置:
// src/utils/httpClient.js
import axios from ‘axios’;
// 创建Axios实例
const apiClient = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL || ‘https://api.yourdomain.com’,
timeout: 10000, // 10秒超时
headers: {
‘Content-Type’: ‘application/json’,
},
});
// 请求拦截器:统一处理请求前的逻辑
apiClient.interceptors.request.use(
(config) => {
// 从localStorage或Vuex/Pinia中获取token
const token = localStorage.getItem(‘auth_token’);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 记录请求日志(生产环境可以关闭)
console.log(`[API] ${config.method?.toUpperCase()} ${config.url}`);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器:统一处理响应和错误
apiClient.interceptors.response.use(
(response) => {
// 请求成功,直接返回数据
return response.data;
},
(error) => {
// 处理错误响应
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401:
// 未授权,跳转到登录页
window.location.href = ‘/login’;
break;
case 403:
// 无权限
console.error(‘无权限访问该资源’);
break;
case 404:
// 资源不存在
console.error(‘请求的资源不存在’);
break;
case 500:
// 服务器错误
console.error(‘服务器内部错误’);
break;
default:
console.error(`请求失败: ${status}`);
}
return Promise.reject(new Error(data?.message || ‘请求失败’));
} else if (error.request) {
// 请求已发送但无响应
return Promise.reject(new Error(‘网络异常,请检查网络连接’));
} else {
// 请求配置错误
return Promise.reject(new Error(‘请求配置错误’));
}
}
);
export default apiClient;
1.2 状态管理:Pinia vs Vuex
对于Vue3,官方推荐使用Pinia作为状态管理库。它更轻量、类型安全,并且与Composition API完美配合。
安装Pinia:
npm install pinia
在main.ts中配置:
// src/main.ts
import { createApp } from ‘vue’;
import { createPinia } from ‘pinia’;
import App from ‘./App.vue’;
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount(‘#app’);
1.3 创建数据同步Store
让我们创建一个用于管理线上数据的Store:
// src/stores/dataSyncStore.ts
import { defineStore } from ‘pinia’;
import { ref, computed } from ‘vue’;
import apiClient from ‘@/utils/httpClient’;
export interface SyncState {
data: any;
loading: boolean;
error: string | null;
lastSync: Date | null;
syncStatus: ‘idle’ | ‘syncing’ | ‘success’ | ‘error’;
}
export const useDataSyncStore = defineStore(‘dataSync’, () => {
// 响应式状态
const state = ref
data: null,
loading: false,
error: null,
lastSync: null,
syncStatus: ‘idle’,
});
// 计算属性
const hasData = computed(() => state.value.data !== null);
const needsSync = computed(() => {
if (!state.value.lastSync) return true;
const now = new Date();
const last = state.value.lastSync;
// 超过5分钟未同步则需要同步
return now.getTime() – last.getTime() > 5 * 60 * 1000;
});
// 基础同步方法
const syncData = async (endpoint: string, params = {}) => {
if (state.value.loading) {
console.warn(‘同步正在进行中,请稍候…’);
return;
}
state.value.loading = true;
state.value.error = null;
state.value.syncStatus = ‘syncing’;
try {
console.log(`[DataSync] 开始同步数据: ${endpoint}`);
const response = await apiClient.get(endpoint, { params });
state.value.data = response;
state.value.lastSync = new Date();
state.value.syncStatus = ‘success’;
console.log(`[DataSync] 数据同步成功: ${endpoint}`);
return response;
} catch (error) {
state.value.error = error instanceof Error ? error.message : ‘同步失败’;
state.value.syncStatus = ‘error’;
console.error(`[DataSync] 数据同步失败: ${endpoint}`, error);
throw error;
} finally {
state.value.loading = false;
}
};
// 重置状态
const reset = () => {
state.value = {
data: null,
loading: false,
error: null,
lastSync: null,
syncStatus: ‘idle’,
};
};
return {
// 暴露状态
…state,
// 暴露计算属性
hasData,
needsSync,
// 暴露方法
syncData,
reset,
};
});
1.4 在组件中使用数据同步
现在我们可以在Vue组件中使用这个Store:
产品列表
正在同步数据…
同步失败: {{ error }}
| ID | 产品名称 | 库存 | 价格 | 状态 |
|---|---|---|---|---|
| {{ item.id }} | {{ item.name }} | {{ item.stock }} | ¥{{ item.price }} | {{ getStockStatus(item.stock) }} |
二、实时更新策略:让数据保持最新
2.1 轮询(Polling)策略
轮询是最简单但有效的实时更新策略。它通过定期请求服务器来获取最新数据。
// src/composables/usePollingSync.ts
import { ref, onUnmounted } from ‘vue’;
import { useDataSyncStore } from ‘@/stores/dataSyncStore’;
interface PollingOptions {
interval?: number; // 轮询间隔(毫秒)
immediate?: boolean; // 是否立即执行第一次
maxRetries?: number; // 最大重试次数
}
export function usePollingSync(endpoint: string, options: PollingOptions = {}) {
const {
interval = 30000,
immediate = true,
maxRetries = 3,
} = options;
const dataSyncStore = useDataSyncStore();
const pollingTimer = ref
const retryCount = ref(0);
// 开始轮询
const startPolling = async () => {
if (pollingTimer.value) return; // 避免重复启动
console.log(`[Polling] 开始轮询: ${endpoint}, 间隔: ${interval}ms`);
// 立即执行一次
if (immediate) {
try {
await dataSyncStore.syncData(endpoint);
retryCount.value = 0; // 成功后重置重试计数
} catch (error) {
console.error(‘[Polling] 初始同步失败:’, error);
handleRetry();
return;
}
}
// 设置定时轮询
pollingTimer.value = setInterval(async () => {
try {
console.log(‘[Polling] 执行轮询同步’);
await dataSyncStore.syncData(endpoint);
retryCount.value = 0; // 成功后重置重试计数
} catch (error) {
console.error(‘[Polling] 轮询同步失败:’, error);
handleRetry();
}
}, interval);
};
// 处理重试逻辑
const handleRetry = () => {
retryCount.value++;
if (retryCount.value >= maxRetries) {
console.error(`[Polling] 达到最大重试次数(${maxRetries}),停止轮询`);
stopPolling();
return;
}
console.log(`[Polling] 第${retryCount.value}次重试,${interval}ms后重试`);
// 重试时使用指数退避策略
const backoffDelay = interval * Math.pow(2, retryCount.value – 1);
setTimeout(() => {
if (pollingTimer.value) {
dataSyncStore.syncData(endpoint).catch(() => {
// 重试失败继续下一次重试
});
}
}, backoffDelay);
};
// 停止轮询
const stopPolling = () => {
if (pollingTimer.value) {
clearInterval(pollingTimer.value);
pollingTimer.value = null;
console.log(‘[Polling] 轮询已停止’);
}
};
// 组件卸载时自动停止
onUnmounted(() => {
stopPolling();
});
return {
startPolling,
stopPolling,
isPolling: computed(() => pollingTimer.value !== null),
};
}
在组件中使用轮询:
{{ isPolling ? ‘实时更新已开启’ : ‘实时更新已关闭’ }}
在线用户
今日订单
销售额
2.2 WebSocket实时同步
对于需要真正实时更新的场景,WebSocket是更好的选择。让我们创建一个WebSocket管理器:
// src/utils/websocketManager.ts
import { ref } from ‘vue’;
export interface WebSocketOptions {
reconnectInterval?: number; // 重连间隔
maxReconnectAttempts?: number; // 最大重连次数
heartbeatInterval?: number; // 心跳间隔
}
export class WebSocketManager {
private ws: WebSocket | null = null;
private url: string;
private options: Required
private reconnectAttempts = 0;
private heartbeatTimer: number | null = null;
private reconnectTimer: number | null = null;
// 响应式状态
public isConnected = ref(false);
public connectionError = ref
constructor(url: string, options: WebSocketOptions = {}) {
this.url = url;
this.options = {
reconnectInterval: options.reconnectInterval || 3000,
maxReconnectAttempts: options.maxReconnectAttempts || 5,
heartbeatInterval: options.heartbeatInterval || 30000,
};
}
// 连接WebSocket
connect() {
if (this.ws?.readyState === WebSocket.OPEN) {
console.log(‘[WebSocket] 已连接,无需重复连接’);
return;
}
console.log(`[WebSocket] 正在连接: ${this.url}`);
try {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log(‘[WebSocket] 连接成功’);
this.isConnected.value = true;
this.connectionError.value = null;
this.reconnectAttempts = 0;
this.startHeartbeat();
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleMessage(data);
} catch (error) {
console.error(‘[WebSocket] 消息解析失败:’, error);
}
};
this.ws.onerror = (error) => {
console.error(‘[WebSocket] 错误:’, error);
this.connectionError.value = ‘WebSocket连接错误’;
};
this.ws.onclose = () => {
console.log(‘[WebSocket] 连接关闭’);
this.isConnected.value = false;
this.stopHeartbeat();
// 尝试重连
this.attemptReconnect();
};
} catch (error) {
console.error(‘[WebSocket] 连接失败:’, error);
this.connectionError.value = ‘WebSocket连接失败’;
}
}
// 断开连接
disconnect() {
if (this.ws) {
console.log(‘[WebSocket] 手动断开连接’);
this.ws.close();
this.ws = null;
}
this.stopHeartbeat();
this.stopReconnect();
}
// 发送消息
send(data: any) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
console.warn(‘[WebSocket] 连接未就绪,无法发送消息’);
return false;
}
try {
this.ws.send(JSON.stringify(data));
return true;
} catch (error) {
console.error(‘[WebSocket] 发送消息失败:’, error);
return false;
}
}
// 心跳机制
private startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = setInterval(() => {
if (this.isConnected.value) {
this.send({ type: ‘ping’, timestamp: Date.now() });
}
}, this.options.heartbeatInterval);
}
private stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
// 重连机制
private attemptReconnect() {
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
console.error(`[WebSocket] 达到最大重连次数(${this.options.maxReconnectAttempts}),停止重连`);
this.connectionError.value = ‘无法连接到服务器,请手动刷新页面’;
return;
}
this.reconnectAttempts++;
const delay = this.options.reconnectInterval * this.reconnectAttempts;
console.log(`[WebSocket] 第${this.reconnectAttempts}次重连,${delay}ms后尝试`);
this.reconnectTimer = setTimeout(() => {
if (this.ws?.readyState !== WebSocket.OPEN) {
this.connect();
}
}, delay);
}
private stopReconnect() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
this.reconnectAttempts = 0;
}
// 消息处理器(可重写)
protected handleMessage(data: any) {
console.log(‘[WebSocket] 收到消息:’, data);
// 这里可以触发事件或更新状态
}
}
// 创建WebSocket Store
import { defineStore } from ‘pinia’;
import { ref, computed } from ‘vue’;
import { WebSocketManager } from ‘@/utils/websocketManager’;
export const useWebSocketStore = defineStore(‘websocket’, () => {
const wsManager = ref
const messages = ref
const isConnected = computed(() => wsManager.value?.isConnected.value || false);
const connectionError = computed(() => wsManager.value?.connectionError.value || null);
const connect = (url: string) => {
if (wsManager.value) {
wsManager.value.disconnect();
}
wsManager.value = new WebSocketManager(url);
// 重写消息处理器
const originalHandleMessage = wsManager.value.handleMessage;
wsManager.value.handleMessage = (data: any) => {
messages.value.push(data);
// 限制消息数量,避免内存泄漏
if (messages.value.length > 100) {
messages.value.shift();
}
originalHandleMessage.call(wsManager.value, data);
};
wsManager.value.connect();
};
const disconnect = () => {
if (wsManager.value) {
wsManager.value.disconnect();
wsManager.value = null;
}
};
const send = (data: any) => {
return wsManager.value?.send(data) || false;
};
const clearMessages = () => {
messages.value = [];
};
return {
isConnected,
connectionError,
messages,
connect,
disconnect,
send,
clearMessages,
};
});
2.3 在组件中使用WebSocket实时更新
{{ isConnected ? ‘已连接’ : ‘已断开’ }}
实时订单流
{{ formatTime(order.timestamp) }}
¥{{ order.amount }}
{{ order.status }}
三、性能优化:让数据同步更高效
3.1 数据缓存策略
为了避免不必要的请求,我们需要实现智能缓存:
// src/utils/dataCache.ts
import { ref, computed } from ‘vue’;
export interface CacheEntry
data: T;
timestamp: number;
ttl: number; // 生存时间(毫秒)
}
export class DataCache {
private cache = ref
// 设置缓存
set
const entry: CacheEntry
data,
timestamp: Date.now(),
ttl,
};
this.cache.value.set(key, entry);
console.log(`[Cache] 设置缓存: ${key}, TTL: ${ttl}ms`);
}
// 获取缓存
get
const entry = this.cache.value.get(key);
if (!entry) {
console.log(`[Cache] 未找到缓存: ${key}`);
return null;
}
const now = Date.now();
const isExpired = now – entry.timestamp > entry.ttl;
if (isExpired) {
console.log(`[Cache] 缓存已过期: ${key}`);
this.cache.value.delete(key);
return null;
}
console.log(`[Cache] 命中缓存: ${key}`);
return entry.data;
}
// 检查缓存是否存在且有效
has(key: string): boolean {
return this.get(key) !== null;
}
// 删除缓存
delete(key: string) {
this.cache.value.delete(key);
console.log(`[Cache] 删除缓存: ${key}`);
}
// 清空所有缓存
clear() {
this.cache.value.clear();
console.log(‘[Cache] 所有缓存已清空’);
}
// 获取缓存大小
get size(): number {
return this.cache.value.size;
}
// 获取缓存统计信息
getStats() {
const now = Date.now();
let validCount = 0;
let expiredCount = 0;
this.cache.value.forEach((entry) => {
if (now – entry.timestamp <= entry.ttl) {
validCount++;
} else {
expiredCount++;
}
});
return {
total: this.cache.value.size,
valid: validCount,
expired: expiredCount,
};
}
}
// 创建全局缓存实例
export const globalCache = new DataCache();
3.2 智能请求合并
对于频繁的请求,我们可以使用请求合并技术:
// src/utils/requestBatcher.ts
import { ref } from 'vue';
interface BatchRequest {
id: string;
endpoint: string;
params: any;
resolve: (value: any) => void;
reject: (reason: any) => void;
}
export class RequestBatcher {
private batchQueue = ref
private isProcessing = ref(false);
private batchDelay: number;
private maxBatchSize: number;
constructor(options: {
batchDelay?: number; // 批处理延迟(毫秒)
maxBatchSize?: number; // 最大批大小
} = {}) {
this.batchDelay = options.batchDelay || 50;
this.maxBatchSize = options.maxBatchSize || 10;
}
// 添加请求到批次
addRequest
return new Promise((resolve, reject) => {
const requestId = `${endpoint}-${Date.now()}-${Math.random()}`;
this.batchQueue.value.push({
id: requestId,
endpoint,
params,
resolve,
reject,
});
console.log(`[Batcher] 请求已加入批次: ${requestId}`);
// 自动触发批处理
this.scheduleBatch();
});
}
// 安排批处理
private scheduleBatch() {
if (this.isProcessing.value) return;
// 如果队列达到最大大小,立即处理
if (this.batchQueue.value.length >= this.maxBatchSize) {
this.processBatch();
return;
}
// 否则延迟处理
setTimeout(() => {
this.processBatch();
}, this.batchDelay);
}
// 处理批次
private async processBatch() {
if (this.batchQueue.value.length === 0) return;
this.isProcessing.value = true;
// 提取当前批次
const currentBatch = this.batchQueue.value.splice(0, this.maxBatchSize);
console.log(`[Batcher] 处理批次,大小: ${currentBatch.length}`);
// 按端点分组
const groupedByEndpoint = currentBatch.reduce((acc, req) => {
if (!acc[req.endpoint]) {
acc[req.endpoint] = [];
}
acc[req.endpoint].push(req);
return acc;
}, {} as Record
// 处理每个端点的请求
const promises = Object.entries(groupedByEndpoint).map(
async ([endpoint, requests]) => {
// 合并参数(这里简单处理,实际可能需要更复杂的合并逻辑)
const mergedParams = this.mergeParams(requests.map(r => r.params));
try {
// 执行合并后的请求
const apiClient = await import(‘@/utils/httpClient’).then(m => m.default);
const response = await apiClient.get(endpoint, { params: mergedParams });
// 将结果分发给所有请求
requests.forEach(req => {
// 如果是数组,根据索引分配;否则所有请求获得相同结果
if (Array.isArray(response) && response.length === requests.length) {
const index = requests.indexOf(req);
req.resolve(response[index]);
} else {
req.resolve(response);
}
});
} catch (error) {
// 失败时拒绝所有请求
requests.forEach(req => req.reject(error));
}
}
);
await Promise.all(promises);
this.isProcessing.value = false;
// 如果还有剩余请求,继续处理
if (this.batchQueue.value.length > 0) {
this.scheduleBatch();
}
}
// 合并参数(简单实现)
private mergeParams(paramsArray: any[]): any {
if (paramsArray.length === 0) return {};
// 如果所有参数相同,直接返回
const first = JSON.stringify(paramsArray[0]);
if (paramsArray.every(p => JSON.stringify(p) === first)) {
return paramsArray[0];
}
// 否则返回合并后的参数
return paramsArray.reduce((acc, params) => {
return { …acc, …params };
}, {});
}
// 清空队列
clear() {
// 拒绝所有等待中的请求
this.batchQueue.value.forEach(req => {
req.reject(new Error(‘请求被取消’));
});
this.batchQueue.value = [];
console.log(‘[Batcher] 队列已清空’);
}
// 获取队列长度
get queueLength(): number {
return this.batchQueue.value.length;
}
}
// 创建全局批处理器实例
export const globalBatcher = new RequestBatcher({
batchDelay: 100,
maxBatchSize: 5,
});
3.3 虚拟滚动优化大数据列表
当需要展示大量实时数据时,虚拟滚动可以显著提升性能:
3.4 使用虚拟滚动的实时数据展示
实时数据流(虚拟滚动优化)
内存占用: {{ memoryUsage }}MB
{{ item.value }}
四、常见问题排查:解决数据同步中的痛点
4.1 网络问题诊断
网络问题是数据同步最常见的障碍。让我们创建一个网络诊断工具:
// src/utils/networkDiagnostics.ts
import { ref } from ‘vue’;
export interface NetworkStatus {
online: boolean;
latency: number | null;
lastCheck: Date | null;
error: string | null;
}
export class NetworkDiagnostics {
private status = ref
online: navigator.onLine,
latency: null,
lastCheck: null,
error: null,
});
// 检查网络连通性
async checkConnectivity(): Promise
try {
// 使用一个轻量级的API进行测试
const start = Date.now();
const response = await fetch(‘https://www.google.com/favicon.ico’, {
method: ‘HEAD’,
mode: ‘no-cors’,
cache: ‘no-cache’,
});
const end = Date.now();
this.status.value = {
online: true,
latency: end – start,
lastCheck: new Date(),
error: null,
};
return true;
} catch (error) {
this.status.value = {
online: false,
latency: null,
lastCheck: new Date(),
error: error instanceof Error ? error.message : ‘网络连接失败’,
};
return false;
}
}
// 测试API服务器连通性
async checkApiServer(apiBaseUrl: string): Promise<{
reachable: boolean;
responseTime: number;
error?: string;
}> {
const start = Date.now();
try {
const response = await fetch(`${apiBaseUrl}/health`, {
method: ‘GET’,
cache: ‘no-cache’,
signal: AbortSignal.timeout(5000), // 5秒超时
});
const end = Date.now();
if (!response.ok) {
return {
reachable: false,
responseTime: end – start,
error: `HTTP ${response.status}: ${response.statusText}`,
};
}
return {
reachable: true,
responseTime: end – start,
};
} catch (error) {
const end = Date.now();
return {
reachable: false,
responseTime: end – start,
error: error instanceof Error ? error.message : ‘未知错误’,
};
}
}
// 监听网络状态变化
startListening() {
window.addEventListener(‘online’, () => {
console.log(‘[Network] 网络已连接’);
this.status.value.online = true;
this.status.value.error = null;
});
window.addEventListener(‘offline’, () => {
console.log(‘[Network] 网络已断开’);
this.status.value.online = false;
this.status.value.error = ‘网络已断开’;
});
}
// 停止监听
stopListening() {
window.removeEventListener(‘online’, () => {});
window.removeEventListener(‘offline’, () => {});
}
// 获取当前状态
get status() {
return this.status.value;
}
// 生成诊断报告
async generateReport(apiBaseUrl: string): Promise
const report = [`诊断时间: ${new Date().toLocaleString()}`];
// 基础网络状态
report.push(`\n基础网络: ${navigator.onLine ? ‘在线’ : ‘离线’}`);
// 连通性测试
const connectivity = await this.checkConnectivity();
report.push(`互联网连通性: ${connectivity ? ‘正常’ : ‘失败’}`);
if (this.status.value.latency !== null) {
report.push(`延迟: ${this.status.value.latency}ms`);
}
// API服务器测试
const apiTest = await this.checkApiServer(apiBaseUrl);
report.push(`\nAPI服务器: ${apiTest.reachable ? ‘可达’ : ‘不可达’}`);
report.push(`响应时间: ${apiTest.responseTime}ms`);
if (apiTest.error) {
report.push(`错误: ${apiTest.error}`);
}
// 浏览器信息
report.push(`\n浏览器: ${navigator.userAgent}`);
report.push(`平台: ${navigator.platform}`);
return report.join(‘\n’);
}
}
// 创建全局实例
export const networkDiagnostics = new NetworkDiagnostics();
4.2 数据同步错误处理
// src/utils/errorHandler.ts
import { ref } from ‘vue’;
export interface SyncError {
message: string;
code?: string;
endpoint: string;
timestamp: Date;
params?: any;
response?: any;
}
export class ErrorHandler {
private errors = ref
private maxErrors = 50; // 保留最近50条错误
// 记录错误
recordError(error: any, endpoint: string, params?: any) {
const syncError: SyncError = {
message: error instanceof Error ? error.message : String(error),
code: error.code || error.response?.status,
endpoint,
timestamp: new Date(),
params,
response: error.response?.data,
};
this.errors.value.unshift(syncError);
// 限制错误数量
if (this.errors.value.length > this.maxErrors) {
this.errors.value = this.errors.value.slice(0, this.maxErrors);
}
console.error(`[ErrorHandler] 记录错误: ${endpoint}`, syncError);
// 可以在这里发送错误到日志服务器
this.reportToServer(syncError);
return syncError;
}
// 获取特定端点的错误
getErrorsByEndpoint(endpoint: string): SyncError[] {
return this.errors.value.filter(e => e.endpoint === endpoint);
}
// 获取最近的错误
getRecentErrors(count: number = 10): SyncError[] {
return this.errors.value.slice(0, count);
}
// 清除错误
clearErrors() {
this.errors.value = [];
console.log(‘[ErrorHandler] 错误日志已清空’);
}
// 错误统计
getStats() {
const total = this.errors.value.length;
const byEndpoint = this.errors.value.reduce((acc, error) => {
acc[error.endpoint] = (acc[error.endpoint] || 0) + 1;
return acc;
}, {} as Record
const byCode = this.errors.value.reduce((acc, error) => {
const code = error.code || ‘unknown’;
acc[code] = (acc[code] || 0) + 1;
return acc;
}, {} as Record
return {
total,
byEndpoint,
byCode,
recent: this.getRecentErrors(5),
};
}
// 报告到服务器(生产环境使用)
private reportToServer(error: SyncError) {
// 在实际项目中,这里会发送到错误监控平台
// 例如:Sentry, Bugsnag, 或自建日志服务
if (process.env.NODE_ENV === ‘production’) {
// 模拟上报
console.log(‘[ErrorHandler] 上报错误到监控平台:’, error);
}
}
// 生成错误报告
generateReport(): string {
const stats = this.getStats();
let report = `错误报告 – ${new Date().toLocaleString()}\n`;
report += `总错误数: ${stats.total}\n\n`;
report += ‘按端点统计:\n’;
Object.entries(stats.byEndpoint).forEach(([endpoint, count]) => {
report += ` ${endpoint}: ${count}次\n`;
});
report += ‘\n按错误码统计:\n’;
Object.entries(stats.byCode).forEach(([code, count]) => {
report += ` ${code}: ${count}次\n`;
});
report += ‘\n最近错误:\n’;
stats.recent.forEach((error, index) => {
report += ` ${index + 1}. [${error.code}] ${error.endpoint} – ${error.message}\n`;
});
return report;
}
}
// 创建全局错误处理器
export const errorHandler = new ErrorHandler();
4.3 在Store中集成错误处理
// src/stores/dataSyncStore.ts (增强版)
import { defineStore } from ‘pinia’;
import { ref, computed } from ‘vue’;
import apiClient from ‘@/utils/httpClient’;
import { globalCache } from ‘@/utils/dataCache’;
import { errorHandler } from ‘@/utils/errorHandler’;
import { networkDiagnostics } from ‘@/utils/networkDiagnostics’;
export interface SyncState {
data: any;
loading: boolean;
error: string | null;
lastSync: Date | null;
syncStatus: ‘idle’ | ‘syncing’ | ‘success’ | ‘error’;
retryCount: number;
}
export const useDataSyncStore = defineStore(‘dataSync’, () => {
const state = ref
data: null,
loading: false,
error: null,
lastSync: null,
syncStatus: ‘idle’,
retryCount: 0,
});
const hasData = computed(() => state.value.data !== null);
const needsSync = computed(() => {
if (!state.value.lastSync) return true;
const now = new Date();
const last = state.value.lastSync;
return now.getTime() – last.getTime() > 5 * 60 * 1000;
});
// 增强的同步方法,带缓存和错误处理
const syncData = async (endpoint: string, params = {}, options: {
useCache?: boolean;
force?: boolean;
maxRetries?: number;
} = {}) => {
const {
useCache = true,
force = false,
maxRetries = 3,
} = options;
if (state.value.loading) {
console.warn(‘同步正在进行中,请稍候…’);
return;
}
// 检查缓存
const cacheKey = `${endpoint}:${JSON.stringify(params)}`;
if (useCache && !force) {
const cached = globalCache.get(cacheKey);
if (cached) {
console.log(‘[DataSync] 使用缓存数据’);
state.value.data = cached;
state.value.lastSync = new Date();
state.value.syncStatus = ‘success’;
return cached;
}
}
// 检查网络状态
if (!navigator.onLine) {
const error = ‘设备离线,无法同步数据’;
state.value.error = error;
state.value.syncStatus = ‘error’;
errorHandler.recordError(new Error(error), endpoint, params);
throw new Error(error);
}
state.value.loading = true;
state.value.error = null;
state.value.syncStatus = ‘syncing’;
try {
console.log(`[DataSync] 开始同步数据: ${endpoint}`);
const response = await apiClient.get(endpoint, { params });
// 成功,更新状态
state.value.data = response;
state.value.lastSync = new Date();
state.value.syncStatus = ‘success’;
state.value.retryCount = 0;
// 写入缓存
if (useCache) {
globalCache.set(cacheKey, response);
}
console.log(`[DataSync] 数据同步成功: ${endpoint}`);
return response;
} catch (error) {
// 记录错误
errorHandler.recordError(error, endpoint, params);
// 增加重试计数
state.value.retryCount++;
// 处理特定错误
if (error.response?.status === 429) {
// 请求过于频繁
const retryAfter = error.response.headers[‘retry-after’] || 60;
state.value.error = `请求过于频繁,请${retryAfter}秒后重试`;
} else if (error.message.includes(‘timeout’)) {
state.value.error = ‘请求超时,请检查网络连接’;
} else if (error.message.includes(‘Network Error’)) {
state.value.error = ‘网络错误,请检查网络设置’;
} else {
state.value.error = error.message || ‘同步失败’;
}
state.value.syncStatus = ‘error’;
// 自动重试逻辑
if (state.value.retryCount < maxRetries) {
console.log(`[DataSync] 第${state.value.retryCount}次重试,3秒后重试`);
setTimeout(() => {
syncData(endpoint, params, { useCache, force, maxRetries });
}, 3000 * state.value.retryCount); // 指数退避
} else {
console.error(`[DataSync] 达到最大重试次数(${maxRetries}),停止重试`);
state.value.retryCount = 0;
}
throw error;
} finally {
state.value.loading = false;
}
};
// 重置状态
const reset = () => {
state.value = {
data: null,
loading: false,
error: null,
lastSync: null,
syncStatus: ‘idle’,
retryCount: 0,
};
};
// 获取诊断信息
const getDiagnostics = async (apiBaseUrl: string) => {
return await networkDiagnostics.generateReport(apiBaseUrl);
};
// 获取错误统计
const getErrorStats = () => {
return errorHandler.getStats();
};
// 清除缓存
const clearCache = () => {
globalCache.clear();
console.log(‘[DataSync] 缓存已清除’);
};
return {
// 暴露状态
…state,
// 暴露计算属性
hasData,
needsSync,
// 暴露方法
syncData,
reset,
getDiagnostics,
getErrorStats,
clearCache,
};
});
4.4 调试面板组件
数据同步调试面板
网络状态
{{ networkStatus.online ? ‘在线’ : ‘离线’ }}
{{ networkStatus.latency || ‘未知’ }}ms
{{ formatTime(networkStatus.lastCheck) }}
{{ networkStatus.error }}
同步状态
{{ syncState.syncStatus }}
{{ formatTime(syncState.lastSync) }}
{{ syncState.retryCount }}
{{ syncState.error }}
缓存统计
{{ cacheStats.total }}
{{ cacheStats.valid }}
{{ cacheStats.expired }}
错误统计
{{ errorStats.total }}