接口概述
基本信息
- 接口功能: 导入 ZIP 文件(包含地理空间数据 GBD 格式文件)
- 接口说明: 导入包含水储存量元数据的 ZIP 文件,支持多图层 GDB 格式解析
请求参数
{"file":MultipartFile,// ZIP 格式文件(必填)"overwriteMode":Boolean// 覆盖模式(可选,默认 false)}依赖关系
Controller → Service → Utils → GDAL/GeoTools → Database ↓ ↓ ↓ ↓ Swagger ServiceImpl FileParser Entity ↑ ↑ ↑ Mapper FileHandleMapper详细流程分析
第1步:文件接收与验证
控制器处理
@PostMapping("/biz/skscclysj/importZip")publicCommonResult<Map<String,Object>>importZip(@RequestParam("file")MultipartFilefile,@RequestParam(value="overwriteMode",defaultValue="false")BooleanoverwriteMode){returnCommonResult.data(dbSkscclysjService.importZipFile(file,overwriteMode));}处理逻辑:
- 接收 MultipartFile 参数(ZIP 格式)
- 调用服务层处理文件
- 返回处理结果
第2步:服务层处理
核心流程
@Transactional(rollbackFor=Exception.class)publicMap<String,Object>importZipFile(MultipartFilefile,BooleanoverwriteMode){Map<String,Object>result=newHashMap<>();List<String>errorList=newArrayList<>();Map<String,Integer>layerCounts=newHashMap<>();inttotalCount=0;intsuccessCount=0;interrorCount=0;// 1. 创建临时目录StringtempDir=System.getProperty("java.io.tmpdir")+File.separator+"dbyts_import_"+IdUtil.simpleUUID();PathtempPath=Paths.get(tempDir);// 2. 文件格式校验StringoriginalFilename=file.getOriginalFilename();if(ObjectUtil.isEmpty(originalFilename)||!originalFilename.toLowerCase().endsWith(".zip")){thrownewCommonException("文件格式错误,仅支持ZIP格式文件");}// 3. 解压ZIP文件FileHandleUtils.unzipFile(file,tempPath);// 4. 查找GDB文件List<File>gdbFiles=FileHandleUtils.findGdbFiles(tempPath.toFile());if(gdbFiles.isEmpty()){thrownewCommonException("ZIP文件中未找到GDB格式文件");}// 5. 解析每个GDB文件for(FilegdbFile:gdbFiles){GbdFileParser.MultiLayerGdbResultgdbResult=GbdFileParser.parseMultiLayerGdbWithGdal(gdbFile,"dbyts");// 6. 处理每个图层的数据for(StringlayerName:gdbResult.getLayerNames()){List<?>layerData=gdbResult.getLayerDataMap().get(layerName);intlayerSuccess=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);returnresult;}第3步:文件处理工具 (FileHandleUtils)
文件解压功能
publicstaticvoidunzipFile(MultipartFilefile,PathdestPath)throwsIOException{try(ZipInputStreamzipInputStream=newZipInputStream(file.getInputStream(),StandardCharsets.UTF_8)){ZipEntryentry;while((entry=zipInputStream.getNextEntry())!=null){if(!entry.isDirectory()){PathfilePath=destPath.resolve(entry.getName());Files.createDirectories(filePath.getParent());try(FileOutputStreamfos=newFileOutputStream(filePath.toFile())){IoUtil.copy(zipInputStream,fos);}}zipInputStream.closeEntry();}}}GDB 文件查找
publicstaticList<File>findGdbFiles(Filedirectory){List<File>gdbFiles=newArrayList<>();File[]files=directory.listFiles();if(files!=null){for(Filefile:files){if(file.isDirectory()){// 检查是否为GDB目录(.gdb扩展名的目录)if(file.getName().toLowerCase().endsWith(".gdb")){gdbFiles.add(file);}else{// 递归查找子目录gdbFiles.addAll(findGdbFiles(file));}}elseif(isGdbFile(file.getName())){gdbFiles.add(file);}}}returngdbFiles;}第4步:GDB 文件解析 (GbdFileParser)
多图层 GDB 解析
publicstaticMultiLayerGdbResultparseMultiLayerGdbWithGdal(FilegdbFile,StringdataType){MultiLayerGdbResultresult=newMultiLayerGdbResult();Map<String,List<?>>layerDataMap=newHashMap<>();try{// 使用 GDAL 解析 GDB 文件gdal.AllRegister();DataSourcedataSource=ogr.Open(gdbFile.getAbsolutePath());if(dataSource==null){thrownewRuntimeException("无法打开 GDB 文件: "+gdbFile.getAbsolutePath());}intlayerCount=dataSource.GetLayerCount();for(inti=0;i<layerCount;i++){Layerlayer=dataSource.GetLayer(i);StringlayerName=layer.GetName();// 根据数据类型过滤图层if(isValidLayer(layerName,dataType)){List<?>layerData=parseLayerData(layer,dataType);layerDataMap.put(layerName,layerData);}}result.setLayerDataMap(layerDataMap);result.setLayerNames(newArrayList<>(layerDataMap.keySet()));}catch(Exceptione){log.error("解析 GDB 文件失败: {}",gdbFile.getName(),e);thrownewRuntimeException("解析 GDB 文件失败: "+e.getMessage());}returnresult;}第5步:图层数据处理
图层分发处理
privateintprocessLayerData(StringlayerName,List<?>layerData,booleanoverwriteMode,List<String>errorList){intsuccessCount=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(Exceptione){log.error("处理图层 {} 数据时发生异常: {}",layerName,e.getMessage(),e);errorList.add("图层 "+layerName+" 处理异常: "+e.getMessage());}returnsuccessCount;}第6步:数据库操作
水库数据处理 (processSkData)
privateintprocessSkData(List<DbSkscclysj>layerData,booleanoverwriteMode,List<String>errorList){intsuccessCount=0;for(DbSkscclysjxzq:layerData){try{// 检查是否存在重复数据QueryWrapper<DbSkscclysj>queryWrapper=newQueryWrapper<>();queryWrapper.lambda().eq(DbSkscclysj::getSkbm,xzq.getSkbm());queryWrapper.lambda().eq(DbSkscclysj::getXjxzqdm,xzq.getXjxzqdm());queryWrapper.lambda().eq(DbSkscclysj::getSjclsj,xzq.getSjclsj());DbSkscclysjexistingXzq=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(Exceptione){log.error("导入水库数据失败: {}",e.getMessage(),e);errorList.add("水库数据导入失败: "+e.getMessage());}}returnsuccessCount;}数据库映射
<!-- 自定义插入方法,处理几何数据 --><insertid="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><!-- 自定义更新方法 --><updateid="updateWithGeometry"parameterType="vip.xxx.DbSkscclysj">UPDATE db_skscclysj<set><!-- 更新字段 --></set>WHERE id = #{id}</update>技术依赖
关键依赖库
<!-- 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 环境配置
# 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确保数据一致性 - 错误收集: 收集所有错误信息,不因单个错误中断
- 日志记录: 详细记录处理过程和错误信息
- 结果统计: 返回成功和失败的统计信息
总结
这个接口是一个复杂的多功能地理空间数据导入接口,通过分层架构实现了从文件接收到数据库存储的完整流程。该接口支持多种水体类型的导入,具有良好的错误处理和性能优化,为水体数据管理提供了强大的数据导入能力。