1.依赖(pom.xml)
XML
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>29.0</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>29.0</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>29.0</version>
</dependency>
2.代码实现
java
package com.example.demo.controller;
import org.apache.tomcat.jni.FileInfo;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@RestController
@RequestMapping("/file")
public class FileController {
// 在类中定义一个基础临时目录(相对于项目根目录)
private static final String BASE_TEMP_DIR = "temp";
@PostMapping("/uploadshp")
public ResponseEntity<Map<String, Object>> uploadSHP(@RequestParam("file") MultipartFile file) {
Map<String, Object> result = new HashMap<>();
Path tempDir = null;
try {
// 1. 创建项目下的临时目录: ./temp/shp-upload-xxxxx
Path baseTempPath = Paths.get(BASE_TEMP_DIR).toAbsolutePath().normalize();
Files.createDirectories(baseTempPath); // 确保 temp 目录存在
tempDir = Files.createTempDirectory(baseTempPath, "shp-upload-");
unzipFile(file, tempDir.toAbsolutePath().toString());
String shpFilePath = findShpFile(tempDir.toAbsolutePath().toFile());
if (shpFilePath == null) {
throw new RuntimeException("压缩包中未找到 .shp 文件");
}
Map<String, Object> analysis = analyzeShpFeatures(shpFilePath);
String geometryType = (String) analysis.get("geometryType");
int count = (int) analysis.get("featureCount");
String message;
switch (geometryType) {
case "Point":
message = "成功解析,共 " + count + " 个点";
break;
case "Polygon":
message = "成功解析,共 " + count + " 个地块";
break;
case "LineString":
message = "成功解析,共 " + count + " 条线(非点/面)";
break;
case "Empty":
message = "SHP 文件为空,无任何要素";
break;
default:
message = "不支持的几何类型: " + geometryType + ",共 " + count + " 个要素";
break;
}
result.put("success", true);
result.put("geometryType", geometryType);
result.put("featureCount", count);
result.put("message", message);
} catch (Exception e) {
result.put("success", false);
result.put("message", "解析失败: " + e.getMessage());
e.printStackTrace();
} finally {
if (tempDir != null) {
try {
deleteRecursively(tempDir);
} catch (IOException ignored) {
}
}
}
return ResponseEntity.ok(result);
}
// 解压 ZIP 文件(带路径遍历防护)
private void unzipFile(MultipartFile zipFile, String destDir) throws IOException {
try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// 规范化路径,防止 ../ 攻击
Path destPath = Paths.get(destDir).normalize();
Path filePath = destPath.resolve(entry.getName()).normalize();
if (!filePath.startsWith(destPath)) {
throw new IOException("非法文件路径,可能包含路径遍历攻击: " + entry.getName());
}
if (entry.isDirectory()) {
Files.createDirectories(filePath);
} else {
Files.createDirectories(filePath.getParent());
Files.copy(zis, filePath, StandardCopyOption.REPLACE_EXISTING);
}
zis.closeEntry();
}
}
}
// 查找 .shp 文件(忽略大小写)
private String findShpFile(File dir) {
File[] files = dir.listFiles();
if (files == null) {
return null;
}
for (File file : files) { // ✅ 遍历 File 对象
if (file.isFile()) {
String name = file.getName();
if (name.toLowerCase().endsWith(".shp")) {
return file.getAbsolutePath();
}
} else if (file.isDirectory()) {
String result = findShpFile(file); // 递归子目录
if (result != null) {
return result;
}
}
}
return null;
}
// 分析 SHP 文件:返回几何类型和要素总数
private Map<String, Object> analyzeShpFeatures(String shpFilePath) throws IOException {
File file = new File(shpFilePath);
ShapefileDataStore dataStore = new ShapefileDataStore(file.toURI().toURL());
dataStore.setCharset(Charset.forName("UTF-8")); // 支持中文属性
try {
SimpleFeatureSource source = dataStore.getFeatureSource();
SimpleFeatureCollection features = source.getFeatures();
Map<String, Object> result = new HashMap<>();
if (features.isEmpty()) {
result.put("geometryType", "Empty");
result.put("featureCount", 0);
return result;
}
// 获取第一个要素判断类型(SHP 所有要素类型一致)
String geomTypeName = "Unknown";
try (SimpleFeatureIterator it = features.features()) {
if (it.hasNext()) {
var feature = it.next();
Object geomObj = feature.getDefaultGeometry();
if (geomObj instanceof Geometry geom) {
geomTypeName = geom.getGeometryType(); // 如 "MultiPoint", "Polygon"
}
}
}
// 标准化类型名称
String standardizedType;
if (geomTypeName.contains("Point")) {
standardizedType = "Point";
} else if (geomTypeName.contains("Polygon")) {
standardizedType = "Polygon";
} else if (geomTypeName.contains("Line")) {
standardizedType = "LineString";
} else {
standardizedType = geomTypeName;
}
result.put("geometryType", standardizedType);
result.put("featureCount", features.size()); // 所有要素都是同类型
return result;
} finally {
dataStore.dispose(); // 释放文件句柄
}
}
// 递归删除临时目录
private void deleteRecursively(Path path) throws IOException {
if (Files.exists(path)) {
Files.walk(path)
.sorted((a, b) -> -a.compareTo(b)) // 先删子项,再删目录
.forEach(p -> {
try {
Files.delete(p);
} catch (IOException ignored) {
}
});
}
}
}
3.实现效果
