Files
xiaomubiao-master/前端对接指南.md
2026-05-22 16:13:20 +08:00

22 KiB
Raw Permalink Blame History

前端对接指南 - KMZ文件生成系统

📋 概述

本指南详细说明如何在前端应用中对接KMZ文件生成系统包括WebSocket消息监听、API接口调用、文件下载等功能。

🔗 系统架构

前端应用 ←→ WebSocket ←→ 后端服务 ←→ MQTT ←→ GH/YGWRXT主题
    ↓
KMZ文件下载/显示

🚀 快速开始

1. 环境要求

  • 支持WebSocket的现代浏览器
  • JavaScript ES6+ 支持
  • 网络连接到后端服务

2. 基础配置

// 配置信息
const CONFIG = {
    // WebSocket连接地址
    websocketUrl: 'ws://localhost:8080/ws',
    // API基础地址
    apiBaseUrl: 'http://localhost:8080/api/v1',
    // 工作空间ID
    workspaceId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
    // 用户类型
    userType: 1
};

📡 WebSocket连接

1. 建立连接

class WebSocketManager {
    constructor(config) {
        this.config = config;
        this.websocket = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
        this.reconnectInterval = 3000;
    }
    
    connect() {
        try {
            this.websocket = new WebSocket(this.config.websocketUrl);
            this.setupEventListeners();
        } catch (error) {
            console.error('WebSocket连接失败:', error);
            this.handleReconnect();
        }
    }
    
    setupEventListeners() {
        this.websocket.onopen = () => {
            console.log('WebSocket连接已建立');
            this.reconnectAttempts = 0;
        };
        
        this.websocket.onmessage = (event) => {
            this.handleMessage(event);
        };
        
        this.websocket.onclose = () => {
            console.log('WebSocket连接已关闭');
            this.handleReconnect();
        };
        
        this.websocket.onerror = (error) => {
            console.error('WebSocket错误:', error);
        };
    }
    
    handleReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
            this.reconnectAttempts++;
            console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
            setTimeout(() => this.connect(), this.reconnectInterval);
        } else {
            console.error('WebSocket重连失败已达到最大重试次数');
        }
    }
}

2. 消息处理

class MessageHandler {
    constructor() {
        this.handlers = new Map();
        this.setupDefaultHandlers();
    }
    
    setupDefaultHandlers() {
        // GK数据消息
        this.handlers.set('gk_data', (data) => {
            console.log('收到GK数据:', data);
            this.displayGkData(data);
        });
        
        // KMZ数据消息
        this.handlers.set('kmz_data', (data) => {
            console.log('收到KMZ数据:', data);
            this.handleKmzData(data);
        });
        
        // KMZ文件数据消息
        this.handlers.set('kmz_file_data', (data) => {
            console.log('收到KMZ文件数据:', data);
            this.handleKmzFileData(data);
        });
        
        // KMZ生成通知
        this.handlers.set('kmz_notification', (data) => {
            console.log('KMZ文件生成通知:', data);
            this.showNotification('success', data.message);
        });
        
        // KMZ生成错误
        this.handlers.set('kmz_error', (data) => {
            console.error('KMZ文件生成错误:', data);
            this.showNotification('error', data.message);
        });
    }
    
    handleMessage(event) {
        try {
            const message = JSON.parse(event.data);
            const { bizCode, host } = message;
            
            if (this.handlers.has(bizCode)) {
                this.handlers.get(bizCode)(host);
            } else {
                console.log('未处理的消息类型:', bizCode, host);
            }
        } catch (error) {
            console.error('消息解析失败:', error);
        }
    }
}

🗂️ KMZ文件处理

1. KMZ数据处理器

class KmzFileHandler {
    constructor() {
        this.downloadedFiles = new Map();
    }
    
    // 处理KMZ数据不包含文件内容
    handleKmzData(kmzData) {
        console.log('KMZ文件信息:', kmzData.kmzFile);
        
        // 显示文件信息
        this.displayKmzInfo(kmzData);
        
        // 提供下载按钮
        this.createDownloadButton(kmzData);
    }
    
    // 处理KMZ文件数据包含文件内容
    handleKmzFileData(kmzData) {
        console.log('KMZ文件数据:', kmzData);
        
        // 自动下载文件
        this.autoDownloadKmzFile(kmzData);
        
        // 显示文件信息
        this.displayKmzInfo(kmzData);
        
        // 解析并显示航点信息
        if (kmzData.waypoints) {
            this.displayWaypoints(kmzData.waypoints);
        }
    }
    
    // 自动下载KMZ文件
    autoDownloadKmzFile(kmzData) {
        if (!kmzData.fileData) {
            console.warn('KMZ数据中不包含文件内容');
            return;
        }
        
        try {
            // 解码Base64数据
            const byteCharacters = atob(kmzData.fileData);
            const byteNumbers = new Array(byteCharacters.length);
            
            for (let i = 0; i < byteCharacters.length; i++) {
                byteNumbers[i] = byteCharacters.charCodeAt(i);
            }
            
            const byteArray = new Uint8Array(byteNumbers);
            const blob = new Blob([byteArray], { type: 'application/zip' });
            
            // 创建下载链接
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = kmzData.kmzFile.fileName;
            a.style.display = 'none';
            
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);
            
            console.log('KMZ文件下载完成:', kmzData.kmzFile.fileName);
            
        } catch (error) {
            console.error('KMZ文件下载失败:', error);
        }
    }
    
    // 显示KMZ文件信息
    displayKmzInfo(kmzData) {
        const infoContainer = document.getElementById('kmz-info');
        if (!infoContainer) return;
        
        const fileInfo = kmzData.kmzFile;
        infoContainer.innerHTML = `
            <div class="kmz-file-info">
                <h3>KMZ文件信息</h3>
                <p><strong>文件名:</strong> ${fileInfo.fileName}</p>
                <p><strong>文件大小:</strong> ${this.formatFileSize(fileInfo.fileSize)}</p>
                <p><strong>文件类型:</strong> ${fileInfo.fileType}</p>
                <p><strong>任务ID:</strong> ${kmzData.taskId}</p>
                <p><strong>设备SN:</strong> ${kmzData.deviceSn}</p>
                <p><strong>生成时间:</strong> ${new Date(kmzData.timestamp).toLocaleString()}</p>
            </div>
        `;
    }
    
    // 显示航点信息
    displayWaypoints(waypoints) {
        const waypointsContainer = document.getElementById('waypoints-info');
        if (!waypointsContainer) return;
        
        let waypointsHtml = '<h3>航点信息</h3><ul>';
        waypoints.forEach((waypoint, index) => {
            waypointsHtml += `
                <li>
                    航点 ${index + 1}: 
                    经度 ${waypoint.longitude}, 
                    纬度 ${waypoint.latitude}, 
                    高度 ${waypoint.altitude}m
                </li>
            `;
        });
        waypointsHtml += '</ul>';
        
        waypointsContainer.innerHTML = waypointsHtml;
    }
    
    // 格式化文件大小
    formatFileSize(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
    
    // 创建下载按钮
    createDownloadButton(kmzData) {
        const downloadBtn = document.createElement('button');
        downloadBtn.textContent = '下载KMZ文件';
        downloadBtn.className = 'download-btn';
        downloadBtn.onclick = () => this.downloadKmzFile(kmzData);
        
        const container = document.getElementById('kmz-actions');
        if (container) {
            container.appendChild(downloadBtn);
        }
    }
    
    // 下载KMZ文件
    async downloadKmzFile(kmzData) {
        try {
            const response = await fetch(`${CONFIG.apiBaseUrl}/kmz/download/${kmzData.taskId}?fileType=${kmzData.fileType || 'dji'}`);
            
            if (response.ok) {
                const blob = await response.blob();
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = kmzData.kmzFile.fileName;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                window.URL.revokeObjectURL(url);
            } else {
                throw new Error(`下载失败: ${response.statusText}`);
            }
        } catch (error) {
            console.error('下载KMZ文件失败:', error);
            this.showNotification('error', '下载失败: ' + error.message);
        }
    }
}

🔌 API接口调用

1. API客户端

class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        const defaultOptions = {
            headers: {
                'Content-Type': 'application/json',
            },
        };
        
        const config = { ...defaultOptions, ...options };
        
        try {
            const response = await fetch(url, config);
            const data = await response.json();
            
            if (!response.ok) {
                throw new Error(data.message || `HTTP ${response.status}`);
            }
            
            return data;
        } catch (error) {
            console.error('API请求失败:', error);
            throw error;
        }
    }
    
    // 获取KMZ文件信息
    async getKmzFileInfo(taskId, fileType = 'dji') {
        return this.request(`/kmz/info/${taskId}?fileType=${fileType}`);
    }
    
    // 获取任务KMZ文件列表
    async getKmzFileList(taskId) {
        return this.request(`/kmz/list/${taskId}`);
    }
    
    // 获取设备KMZ文件列表
    async getDeviceKmzFileList(deviceId) {
        return this.request(`/kmz/list/device/${deviceId}`);
    }
    
    // 删除KMZ文件
    async deleteKmzFile(taskId, fileType = 'dji') {
        return this.request(`/kmz/delete/${taskId}?fileType=${fileType}`, {
            method: 'DELETE'
        });
    }
    
    // 重新发送KMZ数据
    async resendKmzData(taskId, fileType = 'dji') {
        return this.request(`/kmz/resend/${taskId}?fileType=${fileType}`, {
            method: 'POST'
        });
    }
    
    // 获取统计信息
    async getKmzStatistics() {
        return this.request('/kmz/statistics');
    }
}

2. 使用示例

// 初始化API客户端
const apiClient = new ApiClient(CONFIG.apiBaseUrl);

// 获取KMZ文件信息
async function loadKmzFileInfo(taskId) {
    try {
        const result = await apiClient.getKmzFileInfo(taskId);
        if (result.success) {
            console.log('KMZ文件信息:', result.data);
            return result.data;
        }
    } catch (error) {
        console.error('获取KMZ文件信息失败:', error);
    }
}

// 获取任务的所有KMZ文件
async function loadTaskKmzFiles(taskId) {
    try {
        const result = await apiClient.getKmzFileList(taskId);
        if (result.success) {
            console.log('任务KMZ文件列表:', result.data);
            return result.data;
        }
    } catch (error) {
        console.error('获取任务KMZ文件列表失败:', error);
    }
}

🎨 用户界面组件

1. HTML结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>KMZ文件管理系统</title>
    <style>
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        
        .status-indicator {
            padding: 10px;
            margin: 10px 0;
            border-radius: 4px;
        }
        
        .status-connected {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        
        .status-disconnected {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        
        .kmz-file-info {
            background-color: #f8f9fa;
            padding: 15px;
            border-radius: 4px;
            margin: 10px 0;
        }
        
        .waypoints-info {
            background-color: #e9ecef;
            padding: 15px;
            border-radius: 4px;
            margin: 10px 0;
        }
        
        .download-btn {
            background-color: #007bff;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
        }
        
        .download-btn:hover {
            background-color: #0056b3;
        }
        
        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px;
            border-radius: 4px;
            color: white;
            z-index: 1000;
        }
        
        .notification.success {
            background-color: #28a745;
        }
        
        .notification.error {
            background-color: #dc3545;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>KMZ文件管理系统</h1>
        
        <!-- 连接状态 -->
        <div id="connection-status" class="status-indicator status-disconnected">
            WebSocket未连接
        </div>
        
        <!-- KMZ文件信息 -->
        <div id="kmz-info"></div>
        
        <!-- 航点信息 -->
        <div id="waypoints-info"></div>
        
        <!-- 操作按钮 -->
        <div id="kmz-actions"></div>
        
        <!-- 日志区域 -->
        <div id="log-area">
            <h3>系统日志</h3>
            <div id="log-content" style="height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px;"></div>
        </div>
    </div>
    
    <script src="websocket-manager.js"></script>
    <script src="message-handler.js"></script>
    <script src="kmz-file-handler.js"></script>
    <script src="api-client.js"></script>
    <script src="app.js"></script>
</body>
</html>

2. 主应用逻辑

// app.js
class KmzApp {
    constructor() {
        this.websocketManager = new WebSocketManager(CONFIG);
        this.messageHandler = new MessageHandler();
        this.kmzFileHandler = new KmzFileHandler();
        this.apiClient = new ApiClient(CONFIG.apiBaseUrl);
        
        this.init();
    }
    
    init() {
        // 设置消息处理器
        this.websocketManager.onMessage = (data) => {
            this.messageHandler.handleMessage(data);
        };
        
        // 连接WebSocket
        this.websocketManager.connect();
        
        // 更新连接状态
        this.updateConnectionStatus();
        
        // 设置定时器更新状态
        setInterval(() => {
            this.updateConnectionStatus();
        }, 1000);
    }
    
    updateConnectionStatus() {
        const statusElement = document.getElementById('connection-status');
        if (this.websocketManager.websocket && 
            this.websocketManager.websocket.readyState === WebSocket.OPEN) {
            statusElement.textContent = 'WebSocket已连接';
            statusElement.className = 'status-indicator status-connected';
        } else {
            statusElement.textContent = 'WebSocket未连接';
            statusElement.className = 'status-indicator status-disconnected';
        }
    }
    
    // 显示通知
    showNotification(type, message) {
        const notification = document.createElement('div');
        notification.className = `notification ${type}`;
        notification.textContent = message;
        
        document.body.appendChild(notification);
        
        setTimeout(() => {
            document.body.removeChild(notification);
        }, 3000);
    }
    
    // 添加日志
    addLog(message) {
        const logContent = document.getElementById('log-content');
        const timestamp = new Date().toLocaleTimeString();
        const logEntry = document.createElement('div');
        logEntry.textContent = `[${timestamp}] ${message}`;
        logContent.appendChild(logEntry);
        logContent.scrollTop = logContent.scrollHeight;
    }
}

// 启动应用
const app = new KmzApp();

📊 数据格式说明

1. WebSocket消息格式

// GK数据消息
{
    "bizCode": "gk_data",
    "host": {
        "sid": "1581F6GKB244J00402NM",
        "tm": 1706342400000,
        "task_id": "TASK_001",
        "task_name": "巡检任务",
        "task_type": 1,
        "route": [
            {
                "longitude": 112.894686,
                "latitude": 28.207833,
                "altitude": 50.0
            }
        ]
    }
}

// KMZ文件数据消息
{
    "bizCode": "kmz_file_data",
    "host": {
        "messageType": "kmz_file_data",
        "taskId": "TASK_001",
        "taskName": "巡检任务",
        "deviceSn": "1581F6GKB244J00402NM",
        "dataType": "route",
        "timestamp": 1706342400000,
        "kmzFile": {
            "fileName": "巡检任务_1581F6GKB244J00402NM_dji_20250127_143022.kmz",
            "fileSize": 3891,
            "filePath": "/opt/dji/routes/巡检任务_1581F6GKB244J00402NM_dji_20250127_143022.kmz",
            "downloadUrl": "/api/v1/kmz/download/TASK_001",
            "fileType": "kmz"
        },
        "fileData": "UEsDBBQAAAAIAA...", // Base64编码的文件内容
        "waypoints": [
            {
                "longitude": 112.894686,
                "latitude": 28.207833,
                "altitude": 50.0
            }
        ]
    }
}

2. API响应格式

// 成功响应
{
    "success": true,
    "data": {
        // 具体数据
    },
    "message": "操作成功"
}

// 错误响应
{
    "success": false,
    "message": "错误信息",
    "code": "ERROR_CODE"
}

🔧 配置选项

1. 环境配置

const ENV_CONFIG = {
    development: {
        websocketUrl: 'ws://localhost:8080/ws',
        apiBaseUrl: 'http://localhost:8080/api/v1',
        workspaceId: 'e3dea0f5-37f2-4d79-ae58-490af3228069'
    },
    production: {
        websocketUrl: 'wss://your-domain.com/ws',
        apiBaseUrl: 'https://your-domain.com/api/v1',
        workspaceId: 'your-workspace-id'
    }
};

2. 功能配置

const FEATURE_CONFIG = {
    // 是否自动下载KMZ文件
    autoDownload: true,
    // 是否显示详细日志
    verboseLogging: true,
    // 重连配置
    reconnect: {
        maxAttempts: 5,
        interval: 3000
    },
    // 文件大小限制(字节)
    maxFileSize: 10 * 1024 * 1024 // 10MB
};

🚨 错误处理

1. 常见错误类型

class ErrorHandler {
    static handleWebSocketError(error) {
        console.error('WebSocket错误:', error);
        // 处理WebSocket连接错误
    }
    
    static handleApiError(error) {
        console.error('API错误:', error);
        // 处理API调用错误
    }
    
    static handleFileError(error) {
        console.error('文件处理错误:', error);
        // 处理文件下载/解析错误
    }
}

2. 错误恢复策略

class ErrorRecovery {
    static retryOperation(operation, maxRetries = 3) {
        return new Promise((resolve, reject) => {
            let attempts = 0;
            
            const tryOperation = () => {
                attempts++;
                operation()
                    .then(resolve)
                    .catch(error => {
                        if (attempts < maxRetries) {
                            console.log(`操作失败,重试中 (${attempts}/${maxRetries})`);
                            setTimeout(tryOperation, 1000 * attempts);
                        } else {
                            reject(error);
                        }
                    });
            };
            
            tryOperation();
        });
    }
}

📝 最佳实践

1. 性能优化

  • 使用防抖和节流处理频繁的消息
  • 合理使用缓存避免重复请求
  • 及时清理不需要的资源

2. 用户体验

  • 提供清晰的状态指示
  • 显示操作进度
  • 提供错误提示和恢复建议

3. 安全性

  • 验证所有输入数据
  • 使用HTTPS/WSS连接
  • 避免在客户端存储敏感信息

🔍 调试指南

1. 开启调试模式

const DEBUG = true;

if (DEBUG) {
    // 开启详细日志
    console.log('调试模式已开启');
    
    // 监听所有WebSocket消息
    const originalOnMessage = WebSocket.prototype.onmessage;
    WebSocket.prototype.onmessage = function(event) {
        console.log('WebSocket消息:', event.data);
        originalOnMessage.call(this, event);
    };
}

2. 常见问题排查

  1. WebSocket连接失败

    • 检查URL是否正确
    • 确认服务器是否运行
    • 检查网络连接
  2. KMZ文件下载失败

    • 检查文件是否存在
    • 验证API接口是否正常
    • 确认文件权限
  3. 消息处理异常

    • 检查消息格式是否正确
    • 验证消息处理器是否注册
    • 查看控制台错误信息

📞 技术支持

如有问题,请联系开发团队或查看相关文档:

  • API文档/api/v1/kmz/docs
  • 系统日志:检查浏览器控制台和后端日志
  • 问题反馈提交Issue到项目仓库

注意: 本指南基于当前系统实现,如有更新请及时同步文档。