修复下载GDB没有ID字段BUG
This commit is contained in:
@@ -44,6 +44,7 @@ import org.opengis.feature.simple.SimpleFeatureType;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -76,6 +77,9 @@ public class ExportController{
|
|||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final ExportService exportService;
|
private final ExportService exportService;
|
||||||
|
|
||||||
|
@Value("${gdal.proj.lib:}")
|
||||||
|
private String configuredProjLib;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ExportController(ITaskService taskService, IBaseService baseService, UserService userService, ExportService exportService){
|
public ExportController(ITaskService taskService, IBaseService baseService, UserService userService, ExportService exportService){
|
||||||
this.taskService = taskService;
|
this.taskService = taskService;
|
||||||
@@ -301,16 +305,135 @@ public String taskTableDataExport(Integer taskId, String tableName,
|
|||||||
// }
|
// }
|
||||||
// return ResultMessage.success(null).toString();
|
// return ResultMessage.success(null).toString();
|
||||||
//}
|
//}
|
||||||
|
/**
|
||||||
|
* 初始化 GDAL/OGR 环境
|
||||||
|
*/
|
||||||
|
private void initializeGdal() {
|
||||||
|
ogr.RegisterAll();
|
||||||
|
gdal.AllRegister();
|
||||||
|
// 支持中文路径和中文字段名
|
||||||
|
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
|
||||||
|
gdal.SetConfigOption("SHAPE_ENCODING", "");
|
||||||
|
|
||||||
|
// 尝试设置 PROJ_LIB 以避免版本冲突
|
||||||
|
// 优先级:配置文件 > 环境变量 > 自动查找
|
||||||
|
String projLib = null;
|
||||||
|
|
||||||
|
// 1. 首先检查配置文件中的设置
|
||||||
|
if (StringUtils.isNotBlank(configuredProjLib)) {
|
||||||
|
File configProjDb = new File(configuredProjLib, "proj.db");
|
||||||
|
if (configProjDb.exists()) {
|
||||||
|
projLib = configuredProjLib;
|
||||||
|
logger.info("使用配置文件中的 PROJ_LIB: {}", projLib);
|
||||||
|
} else {
|
||||||
|
logger.warn("配置文件中指定的 PROJ_LIB 路径不存在或未找到 proj.db: {}", configuredProjLib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果配置文件未设置,检查环境变量
|
||||||
|
if (StringUtils.isBlank(projLib)) {
|
||||||
|
projLib = System.getenv("PROJ_LIB");
|
||||||
|
if (StringUtils.isNotBlank(projLib)) {
|
||||||
|
File envProjDb = new File(projLib, "proj.db");
|
||||||
|
if (envProjDb.exists()) {
|
||||||
|
logger.info("使用环境变量中的 PROJ_LIB: {}", projLib);
|
||||||
|
} else {
|
||||||
|
logger.warn("环境变量 PROJ_LIB 指定的路径未找到 proj.db: {}", projLib);
|
||||||
|
projLib = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 如果都未设置,尝试从 Java 库路径自动查找
|
||||||
|
if (StringUtils.isBlank(projLib)) {
|
||||||
|
String javaLibPath = System.getProperty("java.library.path");
|
||||||
|
if (StringUtils.isNotBlank(javaLibPath)) {
|
||||||
|
String[] paths = javaLibPath.split(File.pathSeparator);
|
||||||
|
for (String path : paths) {
|
||||||
|
// 检查直接路径
|
||||||
|
File projDbFile = new File(path, "proj.db");
|
||||||
|
if (projDbFile.exists()) {
|
||||||
|
projLib = path;
|
||||||
|
logger.info("自动找到 PROJ 数据库路径: {}", path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 检查 share/proj 子目录
|
||||||
|
File projShareDir = new File(path, "share/proj");
|
||||||
|
if (projShareDir.exists() && new File(projShareDir, "proj.db").exists()) {
|
||||||
|
projLib = projShareDir.getAbsolutePath();
|
||||||
|
logger.info("自动找到 PROJ 数据库路径: {}", projLib);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 设置 PROJ_LIB(如果找到)
|
||||||
|
if (StringUtils.isNotBlank(projLib)) {
|
||||||
|
gdal.SetConfigOption("PROJ_LIB", projLib);
|
||||||
|
logger.info("已设置 PROJ_LIB: {}", projLib);
|
||||||
|
} else {
|
||||||
|
logger.warn("未找到有效的 PROJ_LIB 路径,将使用系统默认路径(可能遇到版本冲突)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建空间参考系统(CGCS2000,EPSG:4490)
|
||||||
|
* 如果 ImportFromEPSG 失败,尝试使用 WKT 字符串
|
||||||
|
*/
|
||||||
|
private SpatialReference createSpatialReference() {
|
||||||
|
SpatialReference srs = new SpatialReference();
|
||||||
|
|
||||||
|
// 首先尝试使用 EPSG 代码
|
||||||
|
try {
|
||||||
|
int result = srs.ImportFromEPSG(4490);
|
||||||
|
if (result == 0) {
|
||||||
|
logger.debug("成功使用 EPSG:4490 创建空间参考");
|
||||||
|
return srs;
|
||||||
|
}
|
||||||
|
logger.warn("EPSG:4490 导入返回错误码: {}", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("EPSG:4490 导入失败,异常: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果失败,尝试使用 WKT 字符串(CGCS2000 的 WKT)
|
||||||
|
logger.info("尝试使用 WKT 字符串创建空间参考");
|
||||||
|
try {
|
||||||
|
String wkt = "GEOGCS[\"CGCS2000\",DATUM[\"China_2000\",SPHEROID[\"CGCS2000\",6378137,298.257222101]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]";
|
||||||
|
int result = srs.ImportFromWkt(wkt);
|
||||||
|
if (result == 0) {
|
||||||
|
logger.info("成功使用 WKT 创建空间参考");
|
||||||
|
return srs;
|
||||||
|
}
|
||||||
|
logger.warn("WKT 导入返回错误码: {}", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("WKT 导入失败,异常: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果都失败,尝试使用 WGS84 作为备选(至少不会崩溃)
|
||||||
|
logger.warn("无法创建 CGCS2000 空间参考,使用 WGS84 (EPSG:4326) 作为备选");
|
||||||
|
try {
|
||||||
|
srs = new SpatialReference();
|
||||||
|
int result = srs.ImportFromEPSG(4326);
|
||||||
|
if (result == 0) {
|
||||||
|
logger.info("成功使用 WGS84 作为备选空间参考");
|
||||||
|
return srs;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("WGS84 导入也失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后的备选:创建一个未定义的空间参考
|
||||||
|
logger.error("所有空间参考创建方法都失败,返回未定义的空间参考");
|
||||||
|
return new SpatialReference();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载数据接口,导出单个任务为gdb
|
* 下载数据接口,导出单个任务为gdb
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "taskGdbExport", produces = "text/html;charset=UTF-8")
|
@RequestMapping(value = "taskGdbExport", produces = "text/html;charset=UTF-8")
|
||||||
public String taskGdbExport(Integer taskId, HttpServletResponse response) throws IOException {
|
public String taskGdbExport(Integer taskId, HttpServletResponse response) throws IOException {
|
||||||
ogr.RegisterAll();
|
initializeGdal();
|
||||||
gdal.AllRegister();
|
|
||||||
// 支持中文路径和中文字段名
|
|
||||||
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
|
|
||||||
gdal.SetConfigOption("SHAPE_ENCODING", "");
|
|
||||||
|
|
||||||
Task task = taskService.getTaskById(taskId);
|
Task task = taskService.getTaskById(taskId);
|
||||||
if (task == null) return ResultMessage.failed("任务不存在").toString();
|
if (task == null) return ResultMessage.failed("任务不存在").toString();
|
||||||
@@ -342,11 +465,7 @@ public String taskGdbExport(Integer taskId, HttpServletResponse response) throws
|
|||||||
public String batchGdbExport(@RequestParam List<Integer> taskIds, HttpServletResponse response) throws IOException {
|
public String batchGdbExport(@RequestParam List<Integer> taskIds, HttpServletResponse response) throws IOException {
|
||||||
if (taskIds == null || taskIds.isEmpty()) return ResultMessage.failed("任务ID列表为空").toString();
|
if (taskIds == null || taskIds.isEmpty()) return ResultMessage.failed("任务ID列表为空").toString();
|
||||||
|
|
||||||
ogr.RegisterAll();
|
initializeGdal();
|
||||||
gdal.AllRegister();
|
|
||||||
// 支持中文路径和中文字段名
|
|
||||||
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
|
|
||||||
gdal.SetConfigOption("SHAPE_ENCODING", "");
|
|
||||||
|
|
||||||
String exportDir = CommonConstant.TEMP_FILE_PATHNAME + "/" + System.currentTimeMillis();
|
String exportDir = CommonConstant.TEMP_FILE_PATHNAME + "/" + System.currentTimeMillis();
|
||||||
File exportDirFile = new File(exportDir);
|
File exportDirFile = new File(exportDir);
|
||||||
@@ -452,8 +571,7 @@ public String taskGdbExport(Integer taskId, HttpServletResponse response) throws
|
|||||||
* 添加带几何的图层到 GDB(支持 Polygon 和 MultiPolygon)
|
* 添加带几何的图层到 GDB(支持 Polygon 和 MultiPolygon)
|
||||||
*/
|
*/
|
||||||
private void addGeometryLayerToGdb(DataSource dataSource, Table table, String layerName, List<Map<String, Object>> dataList) {
|
private void addGeometryLayerToGdb(DataSource dataSource, Table table, String layerName, List<Map<String, Object>> dataList) {
|
||||||
SpatialReference srs = new SpatialReference();
|
SpatialReference srs = createSpatialReference(); // 使用改进的空间参考创建方法
|
||||||
srs.ImportFromEPSG(4490); // CGCS2000
|
|
||||||
|
|
||||||
// ✅ 直接从 JSON 中解析几何类型
|
// ✅ 直接从 JSON 中解析几何类型
|
||||||
int geomType = ogr.wkbPolygon; // 默认 Polygon
|
int geomType = ogr.wkbPolygon; // 默认 Polygon
|
||||||
@@ -487,6 +605,20 @@ public String taskGdbExport(Integer taskId, HttpServletResponse response) throws
|
|||||||
List<String> fieldNameOrder = genFieldNameOrderStringArr(table, "");
|
List<String> fieldNameOrder = genFieldNameOrderStringArr(table, "");
|
||||||
if (fieldNameOrder == null || fieldNameOrder.isEmpty()) return;
|
if (fieldNameOrder == null || fieldNameOrder.isEmpty()) return;
|
||||||
|
|
||||||
|
String primaryKey = TableOperationUtil.getPrimaryKey(table);
|
||||||
|
|
||||||
|
// ✅ 主键字段(默认整数型)
|
||||||
|
if (StringUtils.isNotBlank(primaryKey)) {
|
||||||
|
Field pkField = table.getFields().get(primaryKey);
|
||||||
|
FieldDefn pkDefn = new FieldDefn(primaryKey, ogr.OFTInteger);
|
||||||
|
if (pkField != null && StringUtils.isNotBlank(pkField.getName())) {
|
||||||
|
try {
|
||||||
|
pkDefn.SetAlternativeName(pkField.getName());
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
}
|
||||||
|
layer.CreateField(pkDefn);
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ 字段创建(英文名 + 中文别名)
|
// ✅ 字段创建(英文名 + 中文别名)
|
||||||
for (String fieldName : fieldNameOrder) {
|
for (String fieldName : fieldNameOrder) {
|
||||||
Field field = table.getFields().get(fieldName);
|
Field field = table.getFields().get(fieldName);
|
||||||
@@ -503,6 +635,18 @@ public String taskGdbExport(Integer taskId, HttpServletResponse response) throws
|
|||||||
for (Map<String, Object> row : dataList) {
|
for (Map<String, Object> row : dataList) {
|
||||||
Feature feature = new Feature(layer.GetLayerDefn());
|
Feature feature = new Feature(layer.GetLayerDefn());
|
||||||
|
|
||||||
|
// 主键写入
|
||||||
|
if (StringUtils.isNotBlank(primaryKey)) {
|
||||||
|
Object pkVal = row.get(primaryKey);
|
||||||
|
if (pkVal instanceof Number) {
|
||||||
|
feature.SetField(primaryKey, ((Number) pkVal).longValue());
|
||||||
|
} else if (pkVal != null) {
|
||||||
|
feature.SetField(primaryKey, pkVal.toString());
|
||||||
|
} else {
|
||||||
|
feature.SetFieldNull(primaryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 属性写入
|
// 属性写入
|
||||||
for (String fieldName : fieldNameOrder) {
|
for (String fieldName : fieldNameOrder) {
|
||||||
Object val = row.get(fieldName);
|
Object val = row.get(fieldName);
|
||||||
@@ -549,6 +693,19 @@ public String taskGdbExport(Integer taskId, HttpServletResponse response) throws
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(primaryKey)) {
|
||||||
|
Field pkField = table.getFields().get(primaryKey);
|
||||||
|
FieldDefn pkDefn = new FieldDefn(primaryKey, ogr.OFTInteger);
|
||||||
|
if (pkField != null && StringUtils.isNotBlank(pkField.getName())) {
|
||||||
|
try {
|
||||||
|
pkDefn.SetAlternativeName(pkField.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("⚠️ 设置主键别名失败:" + primaryKey + " → " + pkField.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer.CreateField(pkDefn);
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ 创建字段(英文名 + 中文别名)
|
// ✅ 创建字段(英文名 + 中文别名)
|
||||||
for (String fieldName : fieldNameOrder) {
|
for (String fieldName : fieldNameOrder) {
|
||||||
if (fieldName == null || fieldName.trim().isEmpty()) continue;
|
if (fieldName == null || fieldName.trim().isEmpty()) continue;
|
||||||
@@ -570,6 +727,17 @@ public String taskGdbExport(Integer taskId, HttpServletResponse response) throws
|
|||||||
FeatureDefn featureDefn = layer.GetLayerDefn();
|
FeatureDefn featureDefn = layer.GetLayerDefn();
|
||||||
Feature feature = new Feature(featureDefn);
|
Feature feature = new Feature(featureDefn);
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(primaryKey)) {
|
||||||
|
Object pkVal = row.get(primaryKey);
|
||||||
|
if (pkVal instanceof Number) {
|
||||||
|
feature.SetField(primaryKey, ((Number) pkVal).longValue());
|
||||||
|
} else if (pkVal != null) {
|
||||||
|
feature.SetField(primaryKey, pkVal.toString());
|
||||||
|
} else {
|
||||||
|
feature.SetFieldNull(primaryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (String fieldName : fieldNameOrder) {
|
for (String fieldName : fieldNameOrder) {
|
||||||
Object val = row.get(fieldName);
|
Object val = row.get(fieldName);
|
||||||
feature.SetField(fieldName, val == null ? "" : val.toString());
|
feature.SetField(fieldName, val == null ? "" : val.toString());
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class StoreMediaFileUtil{
|
|||||||
// return destFileName;
|
// return destFileName;
|
||||||
// }
|
// }
|
||||||
public static String zip(String mediaPath, List<String> filePathList, String password) {
|
public static String zip(String mediaPath, List<String> filePathList, String password) {
|
||||||
String destFileName = mediaPath + "/temp.zip";
|
String destFileName = mediaPath + File.separator + "temp.zip";
|
||||||
ZipParameters zipParam = new ZipParameters();
|
ZipParameters zipParam = new ZipParameters();
|
||||||
zipParam.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
|
zipParam.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
|
||||||
zipParam.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
|
zipParam.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
|
||||||
@@ -137,8 +137,20 @@ public class StoreMediaFileUtil{
|
|||||||
ZipFile zipfile = new ZipFile(destFileName);
|
ZipFile zipfile = new ZipFile(destFileName);
|
||||||
|
|
||||||
for (String filePath : filePathList) {
|
for (String filePath : filePathList) {
|
||||||
File srcFile = new File(filePath.contains(CommonConstant.STATIC_RESOURCE_ROOT) ? filePath : mediaPath + filePath);
|
File srcFile;
|
||||||
//File srcFile = new File(filePath);
|
// 判断是否为绝对路径
|
||||||
|
File pathFile = new File(filePath);
|
||||||
|
if (pathFile.isAbsolute()) {
|
||||||
|
// 绝对路径直接使用
|
||||||
|
srcFile = pathFile;
|
||||||
|
} else if (filePath.contains(CommonConstant.STATIC_RESOURCE_ROOT)) {
|
||||||
|
// 包含静态资源根路径,直接使用
|
||||||
|
srcFile = new File(filePath);
|
||||||
|
} else {
|
||||||
|
// 相对路径,需要拼接 mediaPath
|
||||||
|
srcFile = new File(mediaPath + File.separator + filePath);
|
||||||
|
}
|
||||||
|
|
||||||
if (!srcFile.exists()) {
|
if (!srcFile.exists()) {
|
||||||
System.err.println("⚠️ 文件或目录不存在,跳过:" + srcFile.getAbsolutePath());
|
System.err.println("⚠️ 文件或目录不存在,跳过:" + srcFile.getAbsolutePath());
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
spring.profiles.active=dev
|
spring.profiles.active=dev
|
||||||
|
|
||||||
server.port=9001
|
server.port=9006
|
||||||
server.address=0.0.0.0
|
server.address=0.0.0.0
|
||||||
|
|
||||||
spring.datasource.host=localhost
|
spring.datasource.host=localhost
|
||||||
spring.datasource.port=5432
|
spring.datasource.port=5432
|
||||||
spring.datasource.database=tj_project
|
spring.datasource.database=eldc1125important
|
||||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
spring.datasource.url=jdbc:postgresql://${spring.datasource.host}:${spring.datasource.port}/${spring.datasource.database}?useSSL=true&allowMultiQueries=true
|
spring.datasource.url=jdbc:postgresql://${spring.datasource.host}:${spring.datasource.port}/${spring.datasource.database}?useSSL=true&allowMultiQueries=true
|
||||||
spring.datasource.username=postgres
|
spring.datasource.username=postgres
|
||||||
spring.datasource.password=503503
|
spring.datasource.password=123456
|
||||||
spring.datasource.hikari.minimum-idle=5
|
spring.datasource.hikari.minimum-idle=5
|
||||||
spring.datasource.hikari.idle-timeout=180000
|
spring.datasource.hikari.idle-timeout=180000
|
||||||
spring.datasource.hikari.maximum-pool-size=30
|
spring.datasource.hikari.maximum-pool-size=30
|
||||||
@@ -33,7 +33,7 @@ logging.level.cn.edu.whu.boot.*.mapper=debug
|
|||||||
mybatis.mapper-locations=classpath:mapper/**/*.xml
|
mybatis.mapper-locations=classpath:mapper/**/*.xml
|
||||||
mybatis.configuration.map-underscore-to-camel-case=true
|
mybatis.configuration.map-underscore-to-camel-case=true
|
||||||
|
|
||||||
static.file.path=E:/503/TJ_project/static
|
static.file.path=C:/tj_lydc/static
|
||||||
|
|
||||||
ApkFilePath=${static.file.path}/apk
|
ApkFilePath=${static.file.path}/apk
|
||||||
photozippath=${static.file.path}/zip
|
photozippath=${static.file.path}/zip
|
||||||
@@ -71,4 +71,10 @@ spring.mail.properties.mail.smtp.port=465
|
|||||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||||
spring.mail.properties.mail.smtp.starttls.required=true
|
spring.mail.properties.mail.smtp.starttls.required=true
|
||||||
spring.mail.properties.mail.smtp.ssl.enable=true
|
spring.mail.properties.mail.smtp.ssl.enable=true
|
||||||
email.subject=
|
email.subject=
|
||||||
|
|
||||||
|
# GDAL/PROJ 配置
|
||||||
|
# PROJ_LIB 路径(可选,如果不设置则自动查找)
|
||||||
|
# 例如:gdal.proj.lib=E:/Java/jdk1.8.0_201/bin
|
||||||
|
# 或者:gdal.proj.lib=E:/Java/jdk1.8.0_201/bin/share/proj
|
||||||
|
gdal.proj.lib=C:/Java/gdal/bin/proj9/share
|
||||||
Reference in New Issue
Block a user