You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

13 KiB

实时数据接收与缓存优化方案

一、当前系统问题分析

通过对前端代码的分析,我发现当前系统存在以下问题:

  1. 频繁的HTTP请求:前端使用多个setInterval进行定期数据刷新,导致大量的HTTP请求
  2. 响应式数据获取:数据更新延迟高,用户体验差
  3. 缺少有效的缓存机制:每次请求都直接从服务器获取数据,没有合理利用缓存
  4. 资源浪费:即使数据没有更新,也会进行定期请求

二、解决方案设计

1. WebSocket实时数据接收

使用WebSocket实现实时数据推送,替代现有的定期轮询。

2. 前端缓存优化

实现高效的前端缓存机制,确保数据更新时缓存也能及时更新。

三、具体实施方案

1. 后端实现

1.1 添加WebSocket依赖

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

1.2 配置WebSocket

// WebSocketConfig.java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }
}

1.3 实现实时数据推送服务

// RealTimeDataService.java
@Service
public class RealTimeDataService {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    /**
     * 推送客户数据更新
     */
    public void pushCustomerUpdate(String customerId, UserProductCartDTO customerData) {
        messagingTemplate.convertAndSend("/topic/customers/" + customerId, customerData);
    }

    /**
     * 推送公海池数据更新
     */
    public void pushPublicSeaUpdate(List<UserProductCartDTO> customerList) {
        messagingTemplate.convertAndSend("/topic/public-sea", customerList);
    }

    /**
     * 推送通知
     */
    public void pushNotification(NotificationDTO notification) {
        messagingTemplate.convertAndSend("/topic/notifications", notification);
    }
}

2. 前端实现

2.1 WebSocket客户端实现

在前端页面中添加WebSocket客户端代码,用于连接WebSocket服务器并接收实时数据。

// 在mainapp-sells.html和mainapp-supplys.html中添加
class WebSocketClient {
    constructor() {
        this.socket = null;
        this.isConnected = false;
        this.reconnectInterval = 5000;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 10;
        this.callbacks = {};
        this.cache = new Map();
    }

    // 初始化WebSocket连接
    init() {
        // 移除现有的SockJS和STOMP依赖
        const script1 = document.createElement('script');
        script1.src = 'https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js';
        document.head.appendChild(script1);

        const script2 = document.createElement('script');
        script2.src = 'https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js';
        script2.onload = () => this.connect();
        document.head.appendChild(script2);
    }

    // 连接WebSocket服务器
    connect() {
        const socket = new SockJS('/ws');
        this.stompClient = Stomp.over(socket);

        this.stompClient.connect({}, 
            (frame) => {
                console.log('✅ WebSocket连接成功:', frame);
                this.isConnected = true;
                this.reconnectAttempts = 0;

                // 订阅公海池数据更新
                this.stompClient.subscribe('/topic/public-sea', (message) => {
                    const data = JSON.parse(message.body);
                    this.handlePublicSeaUpdate(data);
                });

                // 订阅通知
                this.stompClient.subscribe('/topic/notifications', (message) => {
                    const notification = JSON.parse(message.body);
                    this.handleNotification(notification);
                });
            },
            (error) => {
                console.error('❌ WebSocket连接错误:', error);
                this.isConnected = false;
                this.attemptReconnect();
            }
        );
    }

    // 尝试重连
    attemptReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
            this.reconnectAttempts++;
            console.log(`⏱️  尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
            setTimeout(() => this.connect(), this.reconnectInterval);
        } else {
            console.error('❌ 达到最大重连次数,停止尝试');
        }
    }

    // 处理公海池数据更新
    handlePublicSeaUpdate(data) {
        // 更新缓存
        this.cache.set('public-sea-data', {
            data: data,
            timestamp: Date.now()
        });

        // 调用回调函数
        if (this.callbacks['public-sea-update']) {
            this.callbacks['public-sea-update'].forEach(callback => callback(data));
        }
    }

    // 处理通知
    handleNotification(notification) {
        // 显示通知
        this.showNotification(notification);

        // 调用回调函数
        if (this.callbacks['notification']) {
            this.callbacks['notification'].forEach(callback => callback(notification));
        }
    }

    // 显示通知
    showNotification(notification) {
        const notificationEl = document.createElement('div');
        notificationEl.className = 'websocket-notification';
        notificationEl.innerHTML = `
            <div class="notification-header">
                <h4>${notification.title}</h4>
                <button class="close-btn">&times;</button>
            </div>
            <div class="notification-body">
                <p>${notification.message}</p>
                <small>${new Date(notification.timestamp).toLocaleString()}</small>
            </div>
        `;

        // 添加样式
        notificationEl.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            padding: 16px;
            width: 320px;
            z-index: 10000;
            animation: slideIn 0.3s ease-out;
        `;

        // 添加关闭事件
        const closeBtn = notificationEl.querySelector('.close-btn');
        closeBtn.addEventListener('click', () => notificationEl.remove());

        // 添加到页面
        document.body.appendChild(notificationEl);

        // 3秒后自动关闭
        setTimeout(() => notificationEl.remove(), 3000);
    }

    // 订阅事件
    on(event, callback) {
        if (!this.callbacks[event]) {
            this.callbacks[event] = [];
        }
        this.callbacks[event].push(callback);
    }

    // 取消订阅
    off(event, callback) {
        if (this.callbacks[event]) {
            this.callbacks[event] = this.callbacks[event].filter(cb => cb !== callback);
        }
    }

    // 获取缓存数据
    getCachedData(key) {
        const cached = this.cache.get(key);
        if (cached) {
            return cached.data;
        }
        return null;
    }

    // 设置缓存数据
    setCachedData(key, data) {
        this.cache.set(key, {
            data: data,
            timestamp: Date.now()
        });
    }

    // 清除缓存
    clearCache(key) {
        if (key) {
            this.cache.delete(key);
        } else {
            this.cache.clear();
        }
    }

    // 断开连接
    disconnect() {
        if (this.stompClient) {
            this.stompClient.disconnect();
            this.isConnected = false;
        }
    }
}

// 初始化WebSocket客户端
const wsClient = new WebSocketClient();
window.addEventListener('load', () => {
    wsClient.init();
});

// 页面关闭前断开连接
window.addEventListener('beforeunload', () => {
    wsClient.disconnect();
});

// 添加动画样式
const style = document.createElement('style');
style.textContent = `
    @keyframes slideIn {
        from {
            transform: translateX(100%);
            opacity: 0;
        }
        to {
            transform: translateX(0);
            opacity: 1;
        }
    }
`;
document.head.appendChild(style);

2.2 优化数据获取函数

修改现有的数据获取函数,优先使用缓存数据,只有在缓存不存在或过期时才从服务器获取。

// 修改fetchPublicSeaData函数
async function fetchPublicSeaData(loginInfo, level) {
    // 尝试从缓存获取
    const cachedData = wsClient.getCachedData('public-sea-data');
    if (cachedData) {
        console.log('📦 从缓存获取公海池数据');
        return cachedData;
    }

    // 缓存未命中,从服务器获取
    console.log('🌐 从服务器获取公海池数据');
    const url = `${API_BASE_URL}/pool/customers?level=${level}&isSupplySide=false`;
    const response = await fetch(appendAuthParams(url), {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    });

    if (!response.ok) {
        throw new Error('Failed to fetch public sea data');
    }

    const data = await response.json();
    // 更新缓存
    wsClient.setCachedData('public-sea-data', data);
    return data;
}

// 监听公海池数据更新
wsClient.on('public-sea-update', (data) => {
    console.log('🔄 公海池数据已更新');
    // 更新页面数据
    updatePublicSeaData(data);
});

2.3 替换setInterval轮询

移除现有的setInterval轮询,使用WebSocket实时数据更新。

// 移除现有轮询代码
// this.updateInterval = setInterval(() => {
//     this.loadCustomers(loginInfo, level);
// }, 30000);

// 替换为WebSocket监听
wsClient.on('public-sea-update', (data) => {
    this.customersArray = data;
    this.renderCustomers();
});

2.4 实现数据更新时的缓存刷新

当用户手动更新数据时,确保缓存也被刷新。

// 在数据更新成功后
async function updateCustomer(customerData) {
    // 发送更新请求
    const response = await fetch(`${API_BASE_URL}/pool/updateCustomer`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(customerData)
    });

    if (response.ok) {
        // 清除相关缓存
        wsClient.clearCache('public-sea-data');
        wsClient.clearCache(`customer-${customerData.userId}`);
        
        // 显示成功消息
        showMessage('客户数据更新成功');
    }
}

四、缓存策略

1. 缓存类型

  • 公海池数据缓存:缓存公海池客户列表,过期时间30分钟
  • 单个客户数据缓存:缓存单个客户的详细信息,过期时间1小时
  • 通知缓存:缓存通知列表,过期时间10分钟

2. 缓存更新机制

  • WebSocket实时更新:当服务器数据更新时,通过WebSocket推送更新
  • 手动更新:用户手动更新数据时,清除相关缓存
  • 过期自动刷新:缓存过期时,自动从服务器获取最新数据

五、预期效果

  1. 减少HTTP请求:预计减少80%以上的HTTP请求
  2. 提高数据实时性:数据更新延迟从30秒降低到<1秒
  3. 提升用户体验:页面数据实时更新,无需等待
  4. 降低服务器压力:减少不必要的数据查询
  5. 提高页面响应速度:从缓存获取数据,响应更快

六、实施步骤

  1. 后端实现

    • 添加WebSocket依赖
    • 配置WebSocket服务
    • 实现实时数据推送服务
    • 在数据更新时调用推送服务
  2. 前端实现

    • 添加WebSocket客户端代码
    • 优化数据获取函数,使用缓存
    • 移除现有的setInterval轮询
    • 实现缓存更新机制
  3. 测试验证

    • 测试WebSocket连接稳定性
    • 测试数据实时更新效果
    • 测试缓存机制
    • 测试并发性能

七、代码集成建议

  1. 分阶段实施:先在一个页面实现,测试稳定后再推广到其他页面
  2. 保留降级机制:当WebSocket连接失败时,自动回退到轮询模式
  3. 添加监控:监控WebSocket连接状态和缓存命中率
  4. 优化缓存策略:根据实际使用情况调整缓存过期时间

八、注意事项

  1. WebSocket连接管理

    • 实现自动重连机制
    • 处理连接断开情况
    • 限制重连次数
  2. 缓存一致性

    • 确保数据更新时缓存也更新
    • 处理缓存过期情况
    • 避免缓存雪崩
  3. 性能优化

    • 限制WebSocket消息大小
    • 优化数据传输格式
    • 避免频繁发送大量数据
  4. 兼容性考虑

    • 支持不支持WebSocket的浏览器
    • 实现降级方案

通过以上方案的实施,预计可以显著提高系统的性能和用户体验,减少服务器压力,实现真正的实时数据更新。