Ver Fonte

添加数据下载

gushoubang há 2 meses atrás
pai
commit
d94562b88e

+ 110 - 0
siwei-modules/siwei-apply/src/main/java/com/siwei/apply/controller/NodeLandDebugController.java

@@ -0,0 +1,110 @@
+package com.siwei.apply.controller;
+
+import com.siwei.apply.mapper.NodeLandMapper;
+import com.siwei.apply.service.NodeLandService;
+import com.siwei.common.core.domain.R;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * NodeLand 调试控制器
+ * 用于调试和排查 shppath 返回问题
+ * 
+ * @author siwei-zhx
+ */
+@RestController
+@RequestMapping("/debug/nodeland")
+public class NodeLandDebugController {
+
+    private static final Logger logger = LoggerFactory.getLogger(NodeLandDebugController.class);
+
+    @Autowired
+    private NodeLandMapper nodeLandMapper;
+
+    @Autowired
+    private NodeLandService nodeLandService;
+
+    /**
+     * 直接调用 Mapper 查询,查看原始返回数据
+     */
+    @GetMapping("/mapper/{nodeId}")
+    public R<Map<String, String>> testMapper(@PathVariable String nodeId) {
+        try {
+            logger.info("调试:直接调用 Mapper 查询,nodeId: {}", nodeId);
+            
+            Map<String, String> result = nodeLandMapper.selectGeomByNodeId(nodeId);
+            
+            logger.info("调试:Mapper 返回结果: {}", result);
+            
+            if (result == null) {
+                return R.fail("未查询到数据");
+            }
+            
+            return R.ok(result);
+            
+        } catch (Exception e) {
+            logger.error("调试:Mapper 查询异常", e);
+            return R.fail("查询异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 调用 Service 查询,查看处理后的数据
+     */
+    @GetMapping("/service/{nodeId}")
+    public R<Map<String, Object>> testService(@PathVariable String nodeId) {
+        try {
+            logger.info("调试:调用 Service 查询,nodeId: {}", nodeId);
+            
+            Map<String, Object> result = nodeLandService.getGeomByNodeId(nodeId);
+            
+            logger.info("调试:Service 返回结果: {}", result);
+            
+            if (result == null) {
+                return R.fail("未查询到数据");
+            }
+            
+            return R.ok(result);
+            
+        } catch (Exception e) {
+            logger.error("调试:Service 查询异常", e);
+            return R.fail("查询异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 检查数据库中是否存在相关数据
+     */
+    @GetMapping("/check/{nodeId}")
+    public R<Map<String, Object>> checkData(@PathVariable String nodeId) {
+        try {
+            logger.info("调试:检查数据库数据,nodeId: {}", nodeId);
+            
+            // 这里可以添加更多的检查逻辑
+            Map<String, String> mapperResult = nodeLandMapper.selectGeomByNodeId(nodeId);
+            Map<String, Object> serviceResult = nodeLandService.getGeomByNodeId(nodeId);
+            
+            Map<String, Object> checkResult = new java.util.HashMap<>();
+            checkResult.put("mapperResult", mapperResult);
+            checkResult.put("serviceResult", serviceResult);
+            checkResult.put("mapperHasShppath", mapperResult != null && mapperResult.containsKey("shppath"));
+            checkResult.put("serviceHasShppath", serviceResult != null && serviceResult.containsKey("shppath"));
+            checkResult.put("shppathValue", mapperResult != null ? mapperResult.get("shppath") : null);
+            
+            logger.info("调试:检查结果: {}", checkResult);
+            
+            return R.ok(checkResult);
+            
+        } catch (Exception e) {
+            logger.error("调试:数据检查异常", e);
+            return R.fail("检查异常:" + e.getMessage());
+        }
+    }
+}

+ 3 - 0
siwei-modules/siwei-apply/src/main/java/com/siwei/apply/service/impl/NodeLandServiceImpl.java

@@ -73,6 +73,9 @@ public class NodeLandServiceImpl implements NodeLandService {
             // 添加geomDbId - 单个值
             response.put("geomDbId", result.get("geomDbId"));
 
+            // 添加shppath - SHP文件路径
+            response.put("shppath", result.get("shppath"));
+
             // 处理geoms - 转换为字符串数组
             String geomsStr = result.get("geoms");
             if (geomsStr != null && !geomsStr.trim().isEmpty()) {

+ 3 - 1
siwei-modules/siwei-apply/src/main/resources/mapper/NodeLandMapper.xml

@@ -21,14 +21,16 @@
     <select id="selectGeomByNodeId" resultType="map" parameterType="String">
         SELECT
             nl.geom_db_id as "geomDbId",
+            tgd.shppath as "shppath",
             array_to_string(array_agg(ST_AsEWKT(gd.geom)), '|') as "geoms",
             ST_AsEWKT(ST_Envelope(ST_Union(gd.geom))) as "envelope",
             ST_AsEWKT(ST_Centroid(ST_Union(gd.geom))) as "centroid"
         FROM t_node_land nl
         LEFT JOIN t_geom_db_details gd ON nl.geom_db_id = gd.upload_id
+        LEFT JOIN t_geom_db tgd ON nl.geom_db_id = tgd.id
         WHERE nl.node_id = #{nodeId}
         AND gd.geom IS NOT NULL
-        GROUP BY nl.geom_db_id
+        GROUP BY nl.geom_db_id, tgd.shppath
     </select>
 
     <!-- 根据node_id和geom_db_id创建记录 -->

+ 13 - 2
siwei-modules/siwei-file/src/main/java/com/siwei/file/config/ResourcesConfig.java

@@ -30,9 +30,13 @@ public class ResourcesConfig implements WebMvcConfigurer
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry)
     {
-        /** 本地文件上传路径 */
+        /** 本地文件上传路径 - 通过 /statics 前缀访问 */
         registry.addResourceHandler(localFilePrefix + "/**")
                 .addResourceLocations("file:" + localFilePath + File.separator);
+
+        /** 直接通过绝对路径访问文件 */
+        registry.addResourceHandler(localFilePath + "/**")
+                .addResourceLocations("file:" + localFilePath + File.separator);
     }
     
     /**
@@ -40,11 +44,18 @@ public class ResourcesConfig implements WebMvcConfigurer
      */
     @Override
     public void addCorsMappings(CorsRegistry registry) {
-        // 设置允许跨域的路由
+        // 设置允许跨域的路由 - 静态资源前缀访问
         registry.addMapping(localFilePrefix  + "/**")
                 // 设置允许跨域请求的域名
                 .allowedOrigins("*")
                 // 设置允许的方法
                 .allowedMethods("GET");
+
+        // 设置允许跨域的路由 - 绝对路径访问
+        registry.addMapping(localFilePath + "/**")
+                // 设置允许跨域请求的域名
+                .allowedOrigins("*")
+                // 设置允许的方法
+                .allowedMethods("GET");
     }
 }

+ 302 - 0
test-absolute-path-access.html

@@ -0,0 +1,302 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>绝对路径文件访问测试</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            max-width: 900px;
+            margin: 50px auto;
+            padding: 20px;
+            background-color: #f5f5f5;
+        }
+        .container {
+            background-color: white;
+            padding: 30px;
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+        }
+        h1 {
+            color: #333;
+            text-align: center;
+            margin-bottom: 30px;
+        }
+        .section {
+            margin-bottom: 30px;
+            padding: 20px;
+            border: 1px solid #ddd;
+            border-radius: 5px;
+        }
+        .section h3 {
+            color: #555;
+            margin-top: 0;
+        }
+        input[type="text"], input[type="file"] {
+            width: 100%;
+            padding: 8px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            margin-bottom: 10px;
+            box-sizing: border-box;
+        }
+        button {
+            padding: 8px 16px;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+            margin-right: 10px;
+            margin-bottom: 10px;
+        }
+        button:hover {
+            background-color: #0056b3;
+        }
+        button:disabled {
+            background-color: #6c757d;
+            cursor: not-allowed;
+        }
+        .example {
+            background-color: #f8f9fa;
+            padding: 15px;
+            border-radius: 4px;
+            margin: 10px 0;
+            font-family: monospace;
+            font-size: 14px;
+            border-left: 4px solid #007bff;
+        }
+        .result {
+            background-color: #d4edda;
+            border: 1px solid #c3e6cb;
+            padding: 15px;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .error {
+            background-color: #f8d7da;
+            border: 1px solid #f5c6cb;
+            padding: 15px;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .info {
+            background-color: #d1ecf1;
+            border: 1px solid #bee5eb;
+            padding: 15px;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .url-display {
+            background-color: #f8f9fa;
+            padding: 10px;
+            border-radius: 4px;
+            margin: 10px 0;
+            font-family: monospace;
+            font-size: 12px;
+            word-break: break-all;
+            border: 1px solid #dee2e6;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>绝对路径文件访问测试</h1>
+        
+        <div class="section">
+            <h3>配置说明</h3>
+            <div class="info">
+                <strong>当前配置:</strong><br>
+                文件存储路径:/home/siwei/uploadPath<br>
+                文件服务地址:http://127.0.0.1:9202<br><br>
+                
+                <strong>支持的访问方式:</strong><br>
+                方式1:http://127.0.0.1:9202/statics/相对路径<br>
+                方式2:http://127.0.0.1:9202/home/siwei/uploadPath/相对路径 (新增)
+            </div>
+        </div>
+
+        <div class="section">
+            <h3>1. 文件上传测试</h3>
+            <p>先上传一个文件,获取完整路径用于测试</p>
+            <input type="file" id="fileInput" accept="*/*">
+            <button onclick="uploadFile()" id="uploadBtn">上传文件</button>
+            <div id="uploadResult" style="display:none;"></div>
+        </div>
+
+        <div class="section">
+            <h3>2. 绝对路径访问测试</h3>
+            <p>输入完整的文件路径进行访问测试</p>
+            <input type="text" id="absolutePath" placeholder="输入绝对路径,如:/home/siwei/uploadPath/2025/08/01/文件_20250801154318A001.zip">
+            <button onclick="testAbsolutePath()">测试绝对路径访问</button>
+            <button onclick="fillExamplePath()">填入示例路径</button>
+            <div id="absoluteResult" style="display:none;"></div>
+        </div>
+
+        <div class="section">
+            <h3>3. 路径对比测试</h3>
+            <p>对比两种访问方式的效果</p>
+            <input type="text" id="relativePath" placeholder="输入相对路径,如:2025/08/01/文件_20250801154318A001.zip">
+            <button onclick="testBothPaths()">对比测试</button>
+            <div id="compareResult" style="display:none;"></div>
+        </div>
+
+        <div class="section">
+            <h3>示例路径</h3>
+            <div class="example">
+                <strong>假设文件路径:</strong><br>
+                完整路径:/home/siwei/uploadPath/2025/08/01/文件_20250801154318A001.zip<br><br>
+                
+                <strong>访问方式:</strong><br>
+                方式1:http://127.0.0.1:9202/statics/2025/08/01/文件_20250801154318A001.zip<br>
+                方式2:http://127.0.0.1:9202/home/siwei/uploadPath/2025/08/01/文件_20250801154318A001.zip
+            </div>
+        </div>
+    </div>
+
+    <script>
+        function uploadFile() {
+            const fileInput = document.getElementById('fileInput');
+            const file = fileInput.files[0];
+            const resultDiv = document.getElementById('uploadResult');
+            const uploadBtn = document.getElementById('uploadBtn');
+            
+            if (!file) {
+                alert('请选择文件');
+                return;
+            }
+
+            const formData = new FormData();
+            formData.append('file', file);
+
+            uploadBtn.disabled = true;
+            uploadBtn.textContent = '上传中...';
+            resultDiv.style.display = 'block';
+            resultDiv.innerHTML = '<div class="info">正在上传文件...</div>';
+
+            fetch('http://127.0.0.1:9202/upload', {
+                method: 'POST',
+                body: formData
+            })
+            .then(response => response.json())
+            .then(data => {
+                uploadBtn.disabled = false;
+                uploadBtn.textContent = '上传文件';
+                
+                if (data.code === 200) {
+                    const result = data.data;
+                    const absolutePath = result.path;
+                    const relativePath = absolutePath.replace('/home/siwei/uploadPath/', '');
+                    
+                    resultDiv.innerHTML = `
+                        <div class="result">
+                            <strong>✅ 上传成功!</strong><br>
+                            文件名:${result.name}<br>
+                            完整路径:${result.path}<br>
+                            相对路径:${relativePath}<br><br>
+                            
+                            <strong>测试链接:</strong><br>
+                            <button onclick="window.open('http://127.0.0.1:9202/statics/${relativePath}', '_blank')">方式1:/statics 访问</button>
+                            <button onclick="window.open('http://127.0.0.1:9202${absolutePath}', '_blank')">方式2:绝对路径访问</button><br><br>
+                            
+                            <button onclick="document.getElementById('absolutePath').value='${absolutePath}'">填入绝对路径测试框</button>
+                            <button onclick="document.getElementById('relativePath').value='${relativePath}'">填入相对路径测试框</button>
+                        </div>
+                    `;
+                } else {
+                    resultDiv.innerHTML = `<div class="error">❌ 上传失败:${data.msg}</div>`;
+                }
+            })
+            .catch(error => {
+                uploadBtn.disabled = false;
+                uploadBtn.textContent = '上传文件';
+                console.error('Error:', error);
+                resultDiv.innerHTML = `<div class="error">❌ 上传异常:${error.message}</div>`;
+            });
+        }
+
+        function testAbsolutePath() {
+            const absolutePath = document.getElementById('absolutePath').value.trim();
+            const resultDiv = document.getElementById('absoluteResult');
+            
+            if (!absolutePath) {
+                alert('请输入绝对路径');
+                return;
+            }
+            
+            const testUrl = `http://127.0.0.1:9202${absolutePath}`;
+            
+            resultDiv.style.display = 'block';
+            resultDiv.innerHTML = `
+                <div class="info">
+                    <strong>测试URL:</strong><br>
+                    <div class="url-display">${testUrl}</div>
+                    <button onclick="window.open('${testUrl}', '_blank')">在新窗口打开</button>
+                    <button onclick="testUrlAccess('${testUrl}', 'absoluteResult')">测试访问状态</button>
+                </div>
+            `;
+        }
+
+        function testBothPaths() {
+            const relativePath = document.getElementById('relativePath').value.trim();
+            const resultDiv = document.getElementById('compareResult');
+            
+            if (!relativePath) {
+                alert('请输入相对路径');
+                return;
+            }
+            
+            const staticUrl = `http://127.0.0.1:9202/statics/${relativePath}`;
+            const absoluteUrl = `http://127.0.0.1:9202/home/siwei/uploadPath/${relativePath}`;
+            
+            resultDiv.style.display = 'block';
+            resultDiv.innerHTML = `
+                <div class="info">
+                    <strong>方式1 - /statics 前缀:</strong><br>
+                    <div class="url-display">${staticUrl}</div>
+                    <button onclick="window.open('${staticUrl}', '_blank')">打开</button>
+                    <button onclick="testUrlAccess('${staticUrl}', 'static-status')">测试</button>
+                    <span id="static-status"></span><br><br>
+                    
+                    <strong>方式2 - 绝对路径:</strong><br>
+                    <div class="url-display">${absoluteUrl}</div>
+                    <button onclick="window.open('${absoluteUrl}', '_blank')">打开</button>
+                    <button onclick="testUrlAccess('${absoluteUrl}', 'absolute-status')">测试</button>
+                    <span id="absolute-status"></span>
+                </div>
+            `;
+        }
+
+        function testUrlAccess(url, statusElementId) {
+            const statusElement = document.getElementById(statusElementId);
+            if (statusElement) {
+                statusElement.innerHTML = ' 🔄 测试中...';
+            }
+            
+            fetch(url, { method: 'HEAD' })
+                .then(response => {
+                    if (statusElement) {
+                        if (response.ok) {
+                            statusElement.innerHTML = ' ✅ 可访问';
+                            statusElement.style.color = 'green';
+                        } else {
+                            statusElement.innerHTML = ` ❌ ${response.status}`;
+                            statusElement.style.color = 'red';
+                        }
+                    }
+                })
+                .catch(error => {
+                    if (statusElement) {
+                        statusElement.innerHTML = ' ❌ 网络错误';
+                        statusElement.style.color = 'red';
+                    }
+                });
+        }
+
+        function fillExamplePath() {
+            document.getElementById('absolutePath').value = '/home/siwei/uploadPath/2025/08/01/文件_20250801154318A001.zip';
+        }
+    </script>
+</body>
+</html>

+ 356 - 0
test-nodeland-shppath.html

@@ -0,0 +1,356 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>NodeLand 接口 shppath 测试</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            max-width: 900px;
+            margin: 50px auto;
+            padding: 20px;
+            background-color: #f5f5f5;
+        }
+        .container {
+            background-color: white;
+            padding: 30px;
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+        }
+        h1 {
+            color: #333;
+            text-align: center;
+            margin-bottom: 30px;
+        }
+        .section {
+            margin-bottom: 30px;
+            padding: 20px;
+            border: 1px solid #ddd;
+            border-radius: 5px;
+        }
+        .section h3 {
+            color: #555;
+            margin-top: 0;
+        }
+        input[type="text"] {
+            width: 70%;
+            padding: 8px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+        }
+        button {
+            padding: 8px 16px;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+            margin-left: 10px;
+        }
+        button:hover {
+            background-color: #0056b3;
+        }
+        .result {
+            background-color: #d4edda;
+            border: 1px solid #c3e6cb;
+            padding: 15px;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .error {
+            background-color: #f8d7da;
+            border: 1px solid #f5c6cb;
+            padding: 15px;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .info {
+            background-color: #d1ecf1;
+            border: 1px solid #bee5eb;
+            padding: 15px;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .json-display {
+            background-color: #f8f9fa;
+            padding: 15px;
+            border-radius: 4px;
+            margin: 10px 0;
+            font-family: monospace;
+            font-size: 12px;
+            white-space: pre-wrap;
+            border: 1px solid #dee2e6;
+            max-height: 300px;
+            overflow-y: auto;
+        }
+        .field-highlight {
+            background-color: #fff3cd;
+            padding: 2px 4px;
+            border-radius: 3px;
+            font-weight: bold;
+        }
+        .download-btn {
+            background-color: #28a745;
+            margin-top: 10px;
+        }
+        .download-btn:hover {
+            background-color: #218838;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>NodeLand 接口 shppath 字段测试</h1>
+        
+        <div class="section">
+            <h3>SQL 查询修改说明</h3>
+            <div class="info">
+                <strong>修改内容:</strong><br>
+                1. 添加了 <span class="field-highlight">tgd.shppath as "shppath"</span> 字段<br>
+                2. 增加了 <span class="field-highlight">LEFT JOIN t_geom_db tgd ON nl.geom_db_id = tgd.id</span> 关联<br>
+                3. 在 GROUP BY 中添加了 <span class="field-highlight">tgd.shppath</span><br><br>
+                
+                <strong>现在返回的字段:</strong><br>
+                • geomDbId - 几何数据库ID<br>
+                • <span class="field-highlight">shppath - SHP文件路径(新增)</span><br>
+                • geoms - 几何数据数组<br>
+                • envelope - 外边框<br>
+                • centroid - 中心点
+            </div>
+        </div>
+
+        <div class="section">
+            <h3>接口测试</h3>
+            <p>测试 /nodeland/geom/{nodeId} 接口是否正确返回 shppath 字段</p>
+            <input type="text" id="nodeId" placeholder="输入 nodeId,如:test-node-001">
+            <button onclick="testNodeLandGeom()">测试正式接口</button>
+            <button onclick="fillExampleNodeId()">填入示例ID</button>
+            <div id="testResult" style="display:none;"></div>
+        </div>
+
+        <div class="section">
+            <h3>调试工具</h3>
+            <p>分步调试,查看每一层的数据返回情况</p>
+            <input type="text" id="debugNodeId" placeholder="输入 nodeId 进行调试">
+            <button onclick="testMapper()">1. 测试 Mapper 层</button>
+            <button onclick="testService()">2. 测试 Service 层</button>
+            <button onclick="checkAllData()">3. 完整检查</button>
+            <div id="debugResult" style="display:none;"></div>
+        </div>
+
+        <div class="section">
+            <h3>返回数据结构</h3>
+            <div class="info">
+                <strong>期望的返回格式:</strong>
+                <div class="json-display">{
+  "code": 200,
+  "msg": "操作成功",
+  "data": {
+    "geomDbId": "uuid-string",
+    "shppath": "/home/siwei/uploadPath/2025/08/01/文件_20250801154318A001.zip",
+    "geoms": ["SRID=4326;POLYGON(...)", "SRID=4326;POLYGON(...)"],
+    "envelope": "SRID=4326;POLYGON(...)",
+    "centroid": "SRID=4326;POINT(...)"
+  }
+}</div>
+            </div>
+        </div>
+    </div>
+
+    <script>
+        function testNodeLandGeom() {
+            const nodeId = document.getElementById('nodeId').value.trim();
+            const resultDiv = document.getElementById('testResult');
+            
+            if (!nodeId) {
+                alert('请输入 nodeId');
+                return;
+            }
+
+            resultDiv.style.display = 'block';
+            resultDiv.innerHTML = '<div class="info">正在请求接口...</div>';
+
+            // 根据实际的服务端口调整URL
+            const url = `http://127.0.0.1:9201/nodeland/geom/${nodeId}`;
+            
+            fetch(url)
+                .then(response => response.json())
+                .then(data => {
+                    if (data.code === 200) {
+                        const result = data.data;
+                        
+                        // 检查是否包含 shppath 字段
+                        const hasShppath = result && result.shppath !== undefined;
+                        const shppathValue = result ? result.shppath : null;
+                        
+                        let resultHtml = `
+                            <div class="${hasShppath ? 'result' : 'error'}">
+                                <strong>${hasShppath ? '✅' : '❌'} 接口返回${hasShppath ? '成功' : '失败'}!</strong><br><br>
+                                
+                                <strong>字段检查:</strong><br>
+                                • geomDbId: ${result?.geomDbId ? '✅ 存在' : '❌ 缺失'}<br>
+                                • shppath: ${hasShppath ? '✅ 存在' : '❌ 缺失'}<br>
+                                • geoms: ${result?.geoms ? '✅ 存在' : '❌ 缺失'}<br>
+                                • envelope: ${result?.envelope ? '✅ 存在' : '❌ 缺失'}<br>
+                                • centroid: ${result?.centroid ? '✅ 存在' : '❌ 缺失'}<br><br>
+                        `;
+                        
+                        if (hasShppath && shppathValue) {
+                            resultHtml += `
+                                <strong>shppath 值:</strong><br>
+                                <div class="json-display">${shppathValue}</div>
+                                
+                                <strong>文件下载测试:</strong><br>
+                                <button class="download-btn" onclick="testFileDownload('${shppathValue}')">测试文件下载</button>
+                                <button class="download-btn" onclick="window.open('http://127.0.0.1:9202${shppathValue}', '_blank')">直接下载</button><br><br>
+                            `;
+                        }
+                        
+                        resultHtml += `
+                                <strong>完整返回数据:</strong><br>
+                                <div class="json-display">${JSON.stringify(data, null, 2)}</div>
+                            </div>
+                        `;
+                        
+                        resultDiv.innerHTML = resultHtml;
+                    } else {
+                        resultDiv.innerHTML = `
+                            <div class="error">
+                                <strong>❌ 接口调用失败</strong><br>
+                                错误码:${data.code}<br>
+                                错误信息:${data.msg}<br><br>
+                                <strong>完整响应:</strong><br>
+                                <div class="json-display">${JSON.stringify(data, null, 2)}</div>
+                            </div>
+                        `;
+                    }
+                })
+                .catch(error => {
+                    console.error('Error:', error);
+                    resultDiv.innerHTML = `
+                        <div class="error">
+                            <strong>❌ 请求异常</strong><br>
+                            错误信息:${error.message}<br><br>
+                            <strong>可能的原因:</strong><br>
+                            • 服务未启动(检查端口 9201)<br>
+                            • 网络连接问题<br>
+                            • 跨域问题<br>
+                            • nodeId 不存在
+                        </div>
+                    `;
+                });
+        }
+
+        function testFileDownload(shppath) {
+            if (!shppath) {
+                alert('shppath 为空');
+                return;
+            }
+            
+            const downloadUrl = `http://127.0.0.1:9202${shppath}`;
+            
+            // 测试文件是否可访问
+            fetch(downloadUrl, { method: 'HEAD' })
+                .then(response => {
+                    if (response.ok) {
+                        alert(`✅ 文件可访问!\n状态码: ${response.status}\n即将打开下载链接`);
+                        window.open(downloadUrl, '_blank');
+                    } else {
+                        alert(`❌ 文件不可访问\n状态码: ${response.status}\n请检查文件是否存在`);
+                    }
+                })
+                .catch(error => {
+                    alert(`❌ 文件访问测试失败\n错误: ${error.message}`);
+                });
+        }
+
+        function fillExampleNodeId() {
+            document.getElementById('nodeId').value = 'test-node-001';
+            document.getElementById('debugNodeId').value = 'test-node-001';
+        }
+
+        // 调试功能
+        function testMapper() {
+            const nodeId = document.getElementById('debugNodeId').value.trim();
+            if (!nodeId) {
+                alert('请输入 nodeId');
+                return;
+            }
+
+            debugRequest(`http://127.0.0.1:9201/debug/nodeland/mapper/${nodeId}`, 'Mapper 层测试');
+        }
+
+        function testService() {
+            const nodeId = document.getElementById('debugNodeId').value.trim();
+            if (!nodeId) {
+                alert('请输入 nodeId');
+                return;
+            }
+
+            debugRequest(`http://127.0.0.1:9201/debug/nodeland/service/${nodeId}`, 'Service 层测试');
+        }
+
+        function checkAllData() {
+            const nodeId = document.getElementById('debugNodeId').value.trim();
+            if (!nodeId) {
+                alert('请输入 nodeId');
+                return;
+            }
+
+            debugRequest(`http://127.0.0.1:9201/debug/nodeland/check/${nodeId}`, '完整数据检查');
+        }
+
+        function debugRequest(url, title) {
+            const resultDiv = document.getElementById('debugResult');
+            resultDiv.style.display = 'block';
+            resultDiv.innerHTML = `<div class="info">正在执行 ${title}...</div>`;
+
+            fetch(url)
+                .then(response => response.json())
+                .then(data => {
+                    let resultHtml = `<div class="${data.code === 200 ? 'result' : 'error'}">`;
+                    resultHtml += `<strong>${title} 结果:</strong><br>`;
+                    resultHtml += `状态码:${data.code}<br>`;
+                    resultHtml += `消息:${data.msg}<br><br>`;
+
+                    if (data.code === 200 && data.data) {
+                        resultHtml += `<strong>返回数据分析:</strong><br>`;
+
+                        if (title.includes('Mapper')) {
+                            const hasShppath = data.data.shppath !== undefined;
+                            resultHtml += `• shppath 字段:${hasShppath ? '✅ 存在' : '❌ 不存在'}<br>`;
+                            if (hasShppath) {
+                                resultHtml += `• shppath 值:${data.data.shppath || '(空值)'}<br>`;
+                            }
+                        } else if (title.includes('Service')) {
+                            const hasShppath = data.data.shppath !== undefined;
+                            resultHtml += `• shppath 字段:${hasShppath ? '✅ 存在' : '❌ 不存在'}<br>`;
+                            if (hasShppath) {
+                                resultHtml += `• shppath 值:${data.data.shppath || '(空值)'}<br>`;
+                            }
+                        } else if (title.includes('完整')) {
+                            resultHtml += `• Mapper 有 shppath:${data.data.mapperHasShppath ? '✅' : '❌'}<br>`;
+                            resultHtml += `• Service 有 shppath:${data.data.serviceHasShppath ? '✅' : '❌'}<br>`;
+                            resultHtml += `• shppath 实际值:${data.data.shppathValue || '(空值)'}<br>`;
+                        }
+                    }
+
+                    resultHtml += `<br><strong>完整响应数据:</strong><br>`;
+                    resultHtml += `<div class="json-display">${JSON.stringify(data, null, 2)}</div>`;
+                    resultHtml += `</div>`;
+
+                    resultDiv.innerHTML = resultHtml;
+                })
+                .catch(error => {
+                    resultDiv.innerHTML = `
+                        <div class="error">
+                            <strong>❌ ${title} 请求失败</strong><br>
+                            错误:${error.message}
+                        </div>
+                    `;
+                });
+        }
+    </script>
+</body>
+</html>