接口概述
基本信息
- 接口功能: 导入 ZIP 文件(包含地理空间数据 GBD 格式文件)
- 接口说明: 导入包含水储存量元数据的 ZIP 文件,支持多图层 GDB 格式解析
请求参数
json
{
"file": MultipartFile, // ZIP 格式文件(必填)
"overwriteMode": Boolean // 覆盖模式(可选,默认 false)
}
依赖关系
Controller → Service → Utils → GDAL/GeoTools → Database
↓ ↓ ↓ ↓
Swagger ServiceImpl FileParser Entity
↑ ↑ ↑
Mapper FileHandleMapper
详细流程分析
第1步:文件接收与验证
控制器处理
java
@PostMapping("/biz/skscclysj/importZip")
public CommonResult<Map<String, Object>> importZip(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "overwriteMode", defaultValue = "false") Boolean overwriteMode) {
return CommonResult.data(dbSkscclysjService.importZipFile(file, overwriteMode));
}
处理逻辑:
- 接收 MultipartFile 参数(ZIP 格式)
- 调用服务层处理文件
- 返回处理结果
第2步:服务层处理
核心流程
java
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> importZipFile(MultipartFile file, Boolean overwriteMode) {
Map<String, Object> result = new HashMap<>();
List<String> errorList = new ArrayList<>();
Map<String, Integer> layerCounts = new HashMap<>();
int totalCount = 0;
int successCount = 0;
int errorCount = 0;
// 1. 创建临时目录
String tempDir = System.getProperty("java.io.tmpdir") + File.separator + "dbyts_import_" + IdUtil.simpleUUID();
Path tempPath = Paths.get(tempDir);
// 2. 文件格式校验
String originalFilename = file.getOriginalFilename();
if (ObjectUtil.isEmpty(originalFilename) || !originalFilename.toLowerCase().endsWith(".zip")) {
throw new CommonException("文件格式错误,仅支持ZIP格式文件");
}
// 3. 解压ZIP文件
FileHandleUtils.unzipFile(file, tempPath);
// 4. 查找GDB文件
List<File> gdbFiles = FileHandleUtils.findGdbFiles(tempPath.toFile());
if (gdbFiles.isEmpty()) {
throw new CommonException("ZIP文件中未找到GDB格式文件");
}
// 5. 解析每个GDB文件
for (File gdbFile : gdbFiles) {
GbdFileParser.MultiLayerGdbResult gdbResult = GbdFileParser.parseMultiLayerGdbWithGdal(gdbFile, "dbyts");
// 6. 处理每个图层的数据
for (String layerName : gdbResult.getLayerNames()) {
List<?> layerData = gdbResult.getLayerDataMap().get(layerName);
int layerSuccess = processLayerData(layerName, layerData, overwriteMode, errorList);
layerCounts.put(layerName, layerSuccess);
totalCount += layerData.size();
successCount += layerSuccess;
errorCount += (layerData.size() - layerSuccess);
}
}
// 7. 返回结果
result.put("totalCount", totalCount);
result.put("successCount", successCount);
result.put("errorCount", errorCount);
result.put("layerCounts", layerCounts);
result.put("errorDetail", errorList);
return result;
}
第3步:文件处理工具 (FileHandleUtils)
文件解压功能
java
public static void unzipFile(MultipartFile file, Path destPath) throws IOException {
try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
if (!entry.isDirectory()) {
Path filePath = destPath.resolve(entry.getName());
Files.createDirectories(filePath.getParent());
try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) {
IoUtil.copy(zipInputStream, fos);
}
}
zipInputStream.closeEntry();
}
}
}
GDB 文件查找
java
public static List<File> findGdbFiles(File directory) {
List<File> gdbFiles = new ArrayList<>();
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
// 检查是否为GDB目录(.gdb扩展名的目录)
if (file.getName().toLowerCase().endsWith(".gdb")) {
gdbFiles.add(file);
} else {
// 递归查找子目录
gdbFiles.addAll(findGdbFiles(file));
}
} else if (isGdbFile(file.getName())) {
gdbFiles.add(file);
}
}
}
return gdbFiles;
}
第4步:GDB 文件解析 (GbdFileParser)
多图层 GDB 解析
java
public static MultiLayerGdbResult parseMultiLayerGdbWithGdal(File gdbFile, String dataType) {
MultiLayerGdbResult result = new MultiLayerGdbResult();
Map<String, List<?>> layerDataMap = new HashMap<>();
try {
// 使用 GDAL 解析 GDB 文件
gdal.AllRegister();
DataSource dataSource = ogr.Open(gdbFile.getAbsolutePath());
if (dataSource == null) {
throw new RuntimeException("无法打开 GDB 文件: " + gdbFile.getAbsolutePath());
}
int layerCount = dataSource.GetLayerCount();
for (int i = 0; i < layerCount; i++) {
Layer layer = dataSource.GetLayer(i);
String layerName = layer.GetName();
// 根据数据类型过滤图层
if (isValidLayer(layerName, dataType)) {
List<?> layerData = parseLayerData(layer, dataType);
layerDataMap.put(layerName, layerData);
}
}
result.setLayerDataMap(layerDataMap);
result.setLayerNames(new ArrayList<>(layerDataMap.keySet()));
} catch (Exception e) {
log.error("解析 GDB 文件失败: {}", gdbFile.getName(), e);
throw new RuntimeException("解析 GDB 文件失败: " + e.getMessage());
}
return result;
}
第5步:图层数据处理
图层分发处理
java
private int processLayerData(String layerName, List<?> layerData, boolean overwriteMode, List<String> errorList) {
int successCount = 0;
try {
switch (layerName.toLowerCase()) {
case "hlscclysj":
successCount = processHlData((List<DbHlscclysj>) layerData, overwriteMode, errorList);
break;
case "hpscclysj":
successCount = processHpData((List<DbHpscclysj>) layerData, overwriteMode, errorList);
break;
}
} catch (Exception e) {
log.error("处理图层 {} 数据时发生异常: {}", layerName, e.getMessage(), e);
errorList.add("图层 " + layerName + " 处理异常: " + e.getMessage());
}
return successCount;
}
第6步:数据库操作
水库数据处理 (processSkData)
java
private int processSkData(List<DbSkscclysj> layerData, boolean overwriteMode, List<String> errorList) {
int successCount = 0;
for (DbSkscclysj xzq : layerData) {
try {
// 检查是否存在重复数据
QueryWrapper<DbSkscclysj> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(DbSkscclysj::getSkbm, xzq.getSkbm());
queryWrapper.lambda().eq(DbSkscclysj::getXjxzqdm, xzq.getXjxzqdm());
queryWrapper.lambda().eq(DbSkscclysj::getSjclsj, xzq.getSjclsj());
DbSkscclysj existingXzq = this.getOne(queryWrapper);
if (existingXzq != null) {
if (overwriteMode) {
// 覆盖模式:更新现有数据
xzq.setId(existingXzq.getId());
this.baseMapper.updateWithGeometry(xzq);
successCount++;
} else {
// 跳过模式:跳过重复数据
errorList.add("水库代码 " + xzq.getSkbm() + " 已存在,跳过导入");
}
} else {
// 新增数据,使用自定义方法处理几何数据
xzq.setId(IdUtil.simpleUUID());
this.baseMapper.insertWithGeometry(xzq);
successCount++;
}
} catch (Exception e) {
log.error("导入水库数据失败: {}", e.getMessage(), e);
errorList.add("水库数据导入失败: " + e.getMessage());
}
}
return successCount;
}
数据库映射
xml
<!-- 自定义插入方法,处理几何数据 -->
<insert id="insertWithGeometry" parameterType="vip.biz.db.skscclysj.entity.DbSkscclysj">
insert into db_skscclysj (
id, delete_flag, create_user, create_time,update_time, update_user,
ysdm, skbm, skmc, xjxzqmc, xjxzqdm, ssl, sslxdm, sxdxsjly, zkr, fsqsyxj, ksqsyxj,
npjsymj, ksqccyl, fsqccyl, npjsccyl, sccljsff, ytlx, kjlx, bz, sjclr, sjclsj, jcr, jcsj, shhr, shsj,
sxdxsj, jgdysj, sjsx, gxpl, sjnf
) values (
#{id}, #{deleteFlag}, #{createUser}, #{createTime}, #{updateTime}, #{updateUser},
#{ysdm},#{skbm}, #{skmc}, #{xjxzqmc}, #{xjxzqdm}, #{ssl}, #{sslxdm}, #{sxdxsjly}, #{zkr}, #{fsqsyxj}, #{ksqsyxj},
#{npjsymj}, #{ksqccyl}, #{fsqccyl}, #{npjsccyl}, #{sccljsff}, #{ytlx}, #{kjlx}, #{bz}, #{sjclr}, #{sjclsj}, #{jcr}, #{jcsj}, #{shhr}, #{shsj},
#{sxdxsj}, #{jgdysj}, #{sjsx}, #{gxpl}, #{sjnf}
)
</insert>
<!-- 自定义更新方法 -->
<update id="updateWithGeometry" parameterType="vip.xxx.DbSkscclysj">
UPDATE db_skscclysj
<set>
<!-- 更新字段 -->
</set>
WHERE id = #{id}
</update>
技术依赖
关键依赖库
xml
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- GeoTools(地理空间数据处理) -->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>25.0</version>
</dependency>
<!-- GDAL(地理空间数据库) -->
<dependency>
<groupId>org.gdal</groupId>
<artifactId>gdal</artifactId>
<version>3.6.0</version>
</dependency>
<!-- Hutool(工具类) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<!-- EasyExcel(Excel 处理) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
GDAL 环境配置
bash
# Windows 环境变量配置
PATH: C:\OSGeo4W64\bin
GDAL_DATA: C:\OSGeo4W64\share\gdal
PROJ_LIB: C:\OSGeo4W64\share\proj
# JVM 参数配置
-Djava.library.path=C:\OSGeo4W64\bin
-Dgdal.data=C:\OSGeo4W64\share\gdal
错误处理
常见异常类型
- 文件格式错误: 文件不是 ZIP 格式
- 未找到 GDB 文件: ZIP 文件中没有 GDB 格式文件
- 图层解析失败: GDAL 解析 GDB 文件失败
- 数据重复: 根据唯一标识字段检查重复
- 数据库操作失败: 数据库插入或更新失败
异常处理策略
- 事务回滚 : 使用
@Transactional确保数据一致性 - 错误收集: 收集所有错误信息,不因单个错误中断
- 日志记录: 详细记录处理过程和错误信息
- 结果统计: 返回成功和失败的统计信息
总结
这个接口是一个复杂的多功能地理空间数据导入接口,通过分层架构实现了从文件接收到数据库存储的完整流程。该接口支持多种水体类型的导入,具有良好的错误处理和性能优化,为水体数据管理提供了强大的数据导入能力。