模型库、矢量文件
This commit is contained in:
BIN
lib/proj.db
Normal file
BIN
lib/proj.db
Normal file
Binary file not shown.
1
license/yjearth.lic
Normal file
1
license/yjearth.lic
Normal file
@ -0,0 +1 @@
|
|||||||
|
41b/ujShRZRf9Aa433FD3uyIZuxWSSqXWXlc2dyQfJ75ED0HNbadcdsPF5CaMuJ6K2c3U/eBcWiXXw090/O7M5mJze/MavZ4dhk4dZIukMik2Jrufq9vkpQW+3/LWMFurfFWnFDZIf7Ptuoj4BvuX9h/qJ3oYUDCj14JFAR6ge7ZUtqT1yBvSl/eVexWpCiXfpNSm79whbkkpQjgjWts5/bgQ9cOYdBaVOBJnQkRxNzb4fKh3jkuyEADR+VZg2sRnjLJChAg7DYvIou8Zy16Ag==
|
||||||
6
pom.xml
6
pom.xml
@ -175,6 +175,12 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- YAML -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- GDAL -->
|
<!-- GDAL -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.gdal</groupId>
|
<groupId>org.gdal</groupId>
|
||||||
|
|||||||
@ -72,6 +72,6 @@ public class AuthGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println(generateAuth("标准版", 1000, 30, "25F429FDA965007B72BB7A6B2C03535A"));
|
System.out.println(generateAuth("标准版", 1000, 365, "8661A5D7040288C20E17A1D117E20045"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import java.nio.file.Paths;
|
|||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
// 授权文件存储路径、项目根目录下的license目录
|
// 授权文件存储路径、项目根目录下的 license 目录
|
||||||
private static final String AUTH_FILE_PATH = "license/yjearth.lic";
|
private static final String AUTH_FILE_PATH = "license/yjearth.lic";
|
||||||
|
|
||||||
@GetMapping("/info")
|
@GetMapping("/info")
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.yj.earth.business.controller;
|
||||||
|
|
||||||
|
import com.yj.earth.common.util.GdalUtil;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
@Tag(name = "矢量数据管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/gdal")
|
||||||
|
public class GdalController {
|
||||||
|
|
||||||
|
@Operation(summary = "导入矢量数据")
|
||||||
|
@PostMapping("/import")
|
||||||
|
public void importDataStreamGzip(@Parameter(description = "矢量文件路径", required = true) @RequestParam("path") String path, HttpServletResponse response) throws IOException {
|
||||||
|
|
||||||
|
// 解析矢量文件、得到JSON数据
|
||||||
|
String jsonData = GdalUtil.readVectorToJson(path);
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
String filename = URLEncoder.encode("data.gz", StandardCharsets.UTF_8);
|
||||||
|
response.setContentType("application/gzip");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
|
// 使用GZIP压缩并输出
|
||||||
|
try (OutputStream outputStream = response.getOutputStream();
|
||||||
|
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
|
||||||
|
gzipOutputStream.write(jsonData.getBytes(StandardCharsets.UTF_8));
|
||||||
|
gzipOutputStream.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,9 @@ import com.graphhopper.config.Profile;
|
|||||||
import com.graphhopper.util.shapes.GHPoint;
|
import com.graphhopper.util.shapes.GHPoint;
|
||||||
import com.yj.earth.annotation.CheckAuth;
|
import com.yj.earth.annotation.CheckAuth;
|
||||||
import com.yj.earth.business.domain.FileInfo;
|
import com.yj.earth.business.domain.FileInfo;
|
||||||
|
import com.yj.earth.business.domain.WebSource;
|
||||||
import com.yj.earth.business.service.FileInfoService;
|
import com.yj.earth.business.service.FileInfoService;
|
||||||
|
import com.yj.earth.business.service.WebSourceService;
|
||||||
import com.yj.earth.common.config.GraphHopperProperties;
|
import com.yj.earth.common.config.GraphHopperProperties;
|
||||||
import com.yj.earth.common.util.ApiResponse;
|
import com.yj.earth.common.util.ApiResponse;
|
||||||
import com.yj.earth.model.Point;
|
import com.yj.earth.model.Point;
|
||||||
@ -40,6 +42,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/graphhopper")
|
@RequestMapping("/graphhopper")
|
||||||
public class GraphHopperController {
|
public class GraphHopperController {
|
||||||
|
@Resource
|
||||||
|
private WebSourceService webSourceService;
|
||||||
@Resource
|
@Resource
|
||||||
private FileInfoService fileInfoService;
|
private FileInfoService fileInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
@ -61,6 +65,15 @@ public class GraphHopperController {
|
|||||||
return ApiResponse.success(fileInfoService.list(queryWrapper));
|
return ApiResponse.success(fileInfoService.list(queryWrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取地图列表(网页版)")
|
||||||
|
@CheckAuth
|
||||||
|
@GetMapping("/web/list")
|
||||||
|
public ApiResponse webList() {
|
||||||
|
LambdaQueryWrapper<WebSource> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(WebSource::getType, "pbf");
|
||||||
|
return ApiResponse.success(webSourceService.list(queryWrapper));
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "加载地图数据")
|
@Operation(summary = "加载地图数据")
|
||||||
@PostMapping("/loadMap")
|
@PostMapping("/loadMap")
|
||||||
@CheckAuth
|
@CheckAuth
|
||||||
@ -120,6 +133,57 @@ public class GraphHopperController {
|
|||||||
return ApiResponse.success(null);
|
return ApiResponse.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "加载地图数据(网页版)")
|
||||||
|
@PostMapping("/web/loadMap")
|
||||||
|
@CheckAuth
|
||||||
|
public ApiResponse webLoadMap(@Parameter(description = "文件路径") @RequestParam String path) {
|
||||||
|
File osmFile = new File(path);
|
||||||
|
if (!osmFile.exists()) {
|
||||||
|
return ApiResponse.failure("地图文件不存在: " + path);
|
||||||
|
}
|
||||||
|
if (!osmFile.isFile() || !osmFile.getName().endsWith(".pbf")) {
|
||||||
|
return ApiResponse.failure("仅支持有效的.pbf格式OSM文件");
|
||||||
|
}
|
||||||
|
// 防止并发加载
|
||||||
|
if (isLoading.get()) {
|
||||||
|
return ApiResponse.failure("地图正在加载中、请稍后查询状态");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记加载状态
|
||||||
|
isLoading.set(true);
|
||||||
|
isLoaded.set(false);
|
||||||
|
|
||||||
|
// 异步执行: 删除旧数据 → 创建新实例 → 加载新地图
|
||||||
|
new Thread(() -> {
|
||||||
|
GraphHopper newHopper = null;
|
||||||
|
try {
|
||||||
|
// 关键步骤1: 彻底删除旧地图数据目录
|
||||||
|
deleteOldGraphDir();
|
||||||
|
// 关键步骤2: 创建全新的GraphHopper实例
|
||||||
|
newHopper = createNewGraphHopperInstance(path);
|
||||||
|
// 关键步骤3: 加载新地图
|
||||||
|
newHopper.importOrLoad();
|
||||||
|
// 关键步骤4: 加载成功 → 替换当前实例 + 更新状态
|
||||||
|
currentHopper = newHopper;
|
||||||
|
isLoaded.set(true);
|
||||||
|
log.info("地图加载成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 加载失败 → 清理新实例资源
|
||||||
|
if (newHopper != null) {
|
||||||
|
newHopper.close();
|
||||||
|
}
|
||||||
|
isLoaded.set(false);
|
||||||
|
e.printStackTrace();
|
||||||
|
log.error("地图加载失败: " + e.getMessage());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// 无论成功/失败、释放加载锁
|
||||||
|
isLoading.set(false);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
return ApiResponse.success(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "路径规划")
|
@Operation(summary = "路径规划")
|
||||||
@PostMapping("/route")
|
@PostMapping("/route")
|
||||||
@CheckAuth
|
@CheckAuth
|
||||||
|
|||||||
@ -265,6 +265,7 @@ public class IconLibraryController {
|
|||||||
icon.icon_name as iconName,
|
icon.icon_name as iconName,
|
||||||
icon.icon_type as iconType,
|
icon.icon_type as iconType,
|
||||||
icon.data,
|
icon.data,
|
||||||
|
|
||||||
icon.view,
|
icon.view,
|
||||||
icon.created_at as createdAt,
|
icon.created_at as createdAt,
|
||||||
icon.updated_at as updatedAt,
|
icon.updated_at as updatedAt,
|
||||||
|
|||||||
@ -9,18 +9,24 @@ import com.yj.earth.business.domain.ModelType;
|
|||||||
import com.yj.earth.business.service.FileInfoService;
|
import com.yj.earth.business.service.FileInfoService;
|
||||||
import com.yj.earth.business.service.ModelLibraryService;
|
import com.yj.earth.business.service.ModelLibraryService;
|
||||||
import com.yj.earth.common.util.ApiResponse;
|
import com.yj.earth.common.util.ApiResponse;
|
||||||
|
import com.yj.earth.common.util.FileCommonUtil;
|
||||||
import com.yj.earth.common.util.SQLiteUtil;
|
import com.yj.earth.common.util.SQLiteUtil;
|
||||||
import com.yj.earth.dto.militaryLibrary.DragMilitaryTypeDto;
|
import com.yj.earth.dto.militaryLibrary.DragMilitaryTypeDto;
|
||||||
import com.yj.earth.dto.modelLibrary.AddModelTypeDto;
|
import com.yj.earth.dto.modelLibrary.AddModelTypeDto;
|
||||||
import com.yj.earth.dto.modelLibrary.CreateModelLibraryDto;
|
import com.yj.earth.dto.modelLibrary.CreateModelLibraryDto;
|
||||||
import com.yj.earth.dto.modelLibrary.DragModelTypeDto;
|
import com.yj.earth.dto.modelLibrary.DragModelTypeDto;
|
||||||
import com.yj.earth.dto.modelLibrary.UpdateModelTypeNameDto;
|
import com.yj.earth.dto.modelLibrary.UpdateModelTypeNameDto;
|
||||||
|
import com.yj.earth.vo.ModelDataVo;
|
||||||
import com.yj.earth.vo.ModelTypeVo;
|
import com.yj.earth.vo.ModelTypeVo;
|
||||||
import com.yj.earth.vo.ModelVo;
|
import com.yj.earth.vo.ModelVo;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@ -28,11 +34,11 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Tag(name = "模型库管理")
|
@Tag(name = "模型库管理")
|
||||||
@ -157,37 +163,6 @@ public class ModelLibraryController {
|
|||||||
return ApiResponse.success(modelTypeList());
|
return ApiResponse.success(modelTypeList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "拖动模型类型树")
|
|
||||||
@PostMapping("/dragModelType")
|
|
||||||
public ApiResponse dragMilitaryType(@RequestBody DragModelTypeDto dragModelTypeDto) throws SQLException {
|
|
||||||
String militaryPath = getModelLibrary();
|
|
||||||
if (militaryPath == null) {
|
|
||||||
return ApiResponse.failure("请先创建或导入模型库");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态构建SQL和参数
|
|
||||||
List<String> updateFields = new ArrayList<>();
|
|
||||||
List<Object> params = new ArrayList<>();
|
|
||||||
|
|
||||||
// 判断 parentId 是否存在
|
|
||||||
if (dragModelTypeDto.getParentId() != null) {
|
|
||||||
updateFields.add("parent_id = ?");
|
|
||||||
params.add(dragModelTypeDto.getParentId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断 treeIndex 是否存在
|
|
||||||
if (dragModelTypeDto.getTreeIndex() != null) {
|
|
||||||
updateFields.add("tree_index = ?");
|
|
||||||
params.add(dragModelTypeDto.getTreeIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建完整 SQL
|
|
||||||
String sql = "UPDATE model_type SET " + String.join(", ", updateFields) + " WHERE id = ?";
|
|
||||||
params.add(dragModelTypeDto.getId());
|
|
||||||
SQLiteUtil.executeUpdate(militaryPath, sql, params);
|
|
||||||
return ApiResponse.success(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "添加模型文件")
|
@Operation(summary = "添加模型文件")
|
||||||
@PostMapping("/addModelFile")
|
@PostMapping("/addModelFile")
|
||||||
public ApiResponse addModelFile(@RequestParam("files") MultipartFile[] files, @Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws IOException, SQLException {
|
public ApiResponse addModelFile(@RequestParam("files") MultipartFile[] files, @Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws IOException, SQLException {
|
||||||
@ -212,16 +187,16 @@ public class ModelLibraryController {
|
|||||||
|
|
||||||
// 构建插入SQL
|
// 构建插入SQL
|
||||||
String sql = "INSERT INTO model " +
|
String sql = "INSERT INTO model " +
|
||||||
"(id, model_type_id, model_name, model_type, data, created_at) " +
|
"(id, model_type_id, model_name, model_type, model_data, created_at) " +
|
||||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
String url = fileInfoService.uploadWithPreview(file);
|
|
||||||
List<Object> params = new ArrayList<>();
|
List<Object> params = new ArrayList<>();
|
||||||
params.add(UUID.fastUUID().toString(true));
|
String modelId = UUID.fastUUID().toString(true);
|
||||||
|
params.add(modelId);
|
||||||
params.add(modelTypeId);
|
params.add(modelTypeId);
|
||||||
params.add(fileNameWithoutSuffix);
|
params.add(fileNameWithoutSuffix);
|
||||||
params.add(fileSuffix);
|
params.add(fileSuffix);
|
||||||
params.add(url);
|
params.add(file.getBytes());
|
||||||
params.add(LocalDateTime.now());
|
params.add(LocalDateTime.now());
|
||||||
|
|
||||||
// 执行插入操作
|
// 执行插入操作
|
||||||
@ -231,6 +206,72 @@ public class ModelLibraryController {
|
|||||||
return ApiResponse.success(null);
|
return ApiResponse.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取模型数据")
|
||||||
|
@GetMapping("/data/{type}/{modelId}/{fileSuffix}")
|
||||||
|
public ResponseEntity<byte[]> modelData(
|
||||||
|
@Parameter(description = "数据类型") @PathVariable("type") String type,
|
||||||
|
@Parameter(description = "模型ID") @PathVariable("modelId") String modelId,
|
||||||
|
@Parameter(description = "模型类型") @PathVariable(value = "fileSuffix") String fileSuffix) {
|
||||||
|
try {
|
||||||
|
// 获取最新的模型库
|
||||||
|
String modelPath = getModelLibrary();
|
||||||
|
if (modelPath == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
params.add(modelId);
|
||||||
|
String sql = null;
|
||||||
|
if (type.equals("model")) {
|
||||||
|
sql = "SELECT model_name, model_data FROM model WHERE id = ?";
|
||||||
|
} else {
|
||||||
|
sql = "SELECT model_name, poster_data FROM model WHERE id = ?";
|
||||||
|
}
|
||||||
|
// 查询模型数据
|
||||||
|
ModelDataVo modelDataVo = SQLiteUtil.queryForObject(
|
||||||
|
modelPath,
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
ModelDataVo.class
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modelDataVo == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建原始文件名(使用从数据库查询的名称更合理)
|
||||||
|
String originalFileName = modelDataVo.getModelName() + "." + fileSuffix;
|
||||||
|
|
||||||
|
// 对文件名进行URL编码处理、解决非ASCII字符问题
|
||||||
|
String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name());
|
||||||
|
|
||||||
|
// 根据图片后缀获取对应的MediaType
|
||||||
|
MediaType contentType = FileCommonUtil.getImageMediaType(fileSuffix);
|
||||||
|
|
||||||
|
// 设置响应头、使用 inline 确保可以预览
|
||||||
|
if (type.equals("model")) {
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(contentType)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"inline; filename=\"" + encodedFileName + "\"")
|
||||||
|
.body(modelDataVo.getModelData());
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(contentType)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"inline; filename=\"" + encodedFileName + "\"")
|
||||||
|
.body(modelDataVo.getPosterData());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "根据模型类型查看模型列表")
|
@Operation(summary = "根据模型类型查看模型列表")
|
||||||
@PostMapping("/modelList")
|
@PostMapping("/modelList")
|
||||||
public ApiResponse modelList(@Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws SQLException, IllegalAccessException, InstantiationException {
|
public ApiResponse modelList(@Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws SQLException, IllegalAccessException, InstantiationException {
|
||||||
@ -239,7 +280,18 @@ public class ModelLibraryController {
|
|||||||
if (modelPath == null) {
|
if (modelPath == null) {
|
||||||
return ApiResponse.failure("请先创建或导入模型库");
|
return ApiResponse.failure("请先创建或导入模型库");
|
||||||
}
|
}
|
||||||
// 多表联查、查询所有的模型
|
|
||||||
|
// 获取分类ID及其所有子分类ID的列表
|
||||||
|
List<String> typeIdList = getModelTypeIdsWithChildren(modelTypeId);
|
||||||
|
if (typeIdList.isEmpty()) {
|
||||||
|
return ApiResponse.success(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String idsWithQuotes = typeIdList.stream()
|
||||||
|
.map(id -> "'" + id + "'")
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
|
||||||
|
// 多表联查、查询所有指定分类及其子分类下的模型
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT
|
SELECT
|
||||||
model.id,
|
model.id,
|
||||||
@ -247,24 +299,34 @@ public class ModelLibraryController {
|
|||||||
model.model_name as modelName,
|
model.model_name as modelName,
|
||||||
model.model_type as modelType,
|
model.model_type as modelType,
|
||||||
model.poster_type as posterType,
|
model.poster_type as posterType,
|
||||||
model.poster,
|
|
||||||
model.data,
|
|
||||||
model.view,
|
|
||||||
model.created_at as createdAt,
|
model.created_at as createdAt,
|
||||||
model.updated_at as updatedAt,
|
model.updated_at as updatedAt,
|
||||||
model_type.name as modelTypeName from
|
model_type.name as modelTypeName
|
||||||
model JOIN model_type ON model.model_type_id = model_type.id
|
FROM model
|
||||||
WHERE model.model_type_id = ?
|
JOIN model_type ON model.model_type_id = model_type.id
|
||||||
""";
|
WHERE model.model_type_id IN (?)
|
||||||
|
""".replace("?", idsWithQuotes);
|
||||||
|
|
||||||
|
// 使用所有分类ID作为查询参数
|
||||||
List<Object> params = new ArrayList<>();
|
List<Object> params = new ArrayList<>();
|
||||||
params.add(modelTypeId);
|
List<ModelVo> modelVos = SQLiteUtil.queryForList(modelPath, sql, null, ModelVo.class);
|
||||||
List<ModelVo> modelVos = SQLiteUtil.queryForList(modelPath, sql, params, ModelVo.class);
|
// 循环遍历数据
|
||||||
|
for (ModelVo modelVo : modelVos) {
|
||||||
|
if (modelVo.getModelType() != null) {
|
||||||
|
modelVo.setModelDataUrl("/modelLibrary/data/model/" + modelVo.getId() + "/" + modelVo.getModelType());
|
||||||
|
}
|
||||||
|
if (modelVo.getPosterType() != null) {
|
||||||
|
modelVo.setPosterDataUrl("/modelLibrary/data/poster/" + modelVo.getId() + "/" + modelVo.getPosterType());
|
||||||
|
}
|
||||||
|
}
|
||||||
return ApiResponse.success(modelVos);
|
return ApiResponse.success(modelVos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "更新模型信息")
|
@Operation(summary = "更新模型信息")
|
||||||
@PostMapping("/uploadModelInfo")
|
@PostMapping("/uploadModelInfo")
|
||||||
public ApiResponse uploadModelInfo(@Parameter(description = "模型封面文件") @RequestParam(value = "file", required = false) MultipartFile file, @Parameter(description = "模型ID") @RequestParam("modelId") String modelId, @Parameter(description = "模型名称") @RequestParam(value = "modelName", required = false) String modelName) throws IOException, SQLException {
|
public ApiResponse uploadModelInfo(@Parameter(description = "模型封面文件") @RequestParam(value = "file", required = false) MultipartFile file,
|
||||||
|
@Parameter(description = "模型ID") @RequestParam("modelId") String modelId,
|
||||||
|
@Parameter(description = "模型名称") @RequestParam(value = "modelName", required = false) String modelName) throws IOException, SQLException {
|
||||||
|
|
||||||
// 获取最新的模型库路径
|
// 获取最新的模型库路径
|
||||||
String modelPath = getModelLibrary();
|
String modelPath = getModelLibrary();
|
||||||
@ -279,10 +341,9 @@ public class ModelLibraryController {
|
|||||||
// 处理封面文件
|
// 处理封面文件
|
||||||
if (file != null && !file.isEmpty()) {
|
if (file != null && !file.isEmpty()) {
|
||||||
String fileSuffix = FileUtil.extName(file.getOriginalFilename());
|
String fileSuffix = FileUtil.extName(file.getOriginalFilename());
|
||||||
String url = fileInfoService.uploadWithPreview(file);
|
sql.append(", poster_type = ?, poster_data = ? ");
|
||||||
sql.append(", poster_type = ?, poster = ? ");
|
|
||||||
params.add(fileSuffix);
|
params.add(fileSuffix);
|
||||||
params.add(url);
|
params.add(file.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理模型名称
|
// 处理模型名称
|
||||||
@ -344,7 +405,7 @@ public class ModelLibraryController {
|
|||||||
tree_index as treeIndex, created_at as createdAt,
|
tree_index as treeIndex, created_at as createdAt,
|
||||||
updated_at as updatedAt FROM model_type ORDER BY tree_index ASC
|
updated_at as updatedAt FROM model_type ORDER BY tree_index ASC
|
||||||
""";
|
""";
|
||||||
// 查询所有模型类型
|
// 查询所有模型的类型
|
||||||
List<ModelType> modelTypes = SQLiteUtil.queryForList(modelPath, sql, null, ModelType.class);
|
List<ModelType> modelTypes = SQLiteUtil.queryForList(modelPath, sql, null, ModelType.class);
|
||||||
// 转换为树形结构
|
// 转换为树形结构
|
||||||
return buildModelTypeTree(modelTypes);
|
return buildModelTypeTree(modelTypes);
|
||||||
@ -354,11 +415,14 @@ public class ModelLibraryController {
|
|||||||
List<ModelTypeVo> treeNodes = modelTypes.stream()
|
List<ModelTypeVo> treeNodes = modelTypes.stream()
|
||||||
.map(modelType -> new ModelTypeVo(modelType))
|
.map(modelType -> new ModelTypeVo(modelType))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 构建节点ID到节点的映射
|
// 构建节点ID到节点的映射
|
||||||
Map<String, ModelTypeVo> nodeMap = treeNodes.stream()
|
Map<String, ModelTypeVo> nodeMap = treeNodes.stream()
|
||||||
.collect(Collectors.toMap(ModelTypeVo::getId, node -> node));
|
.collect(Collectors.toMap(ModelTypeVo::getId, node -> node));
|
||||||
|
|
||||||
// 根节点列表
|
// 根节点列表
|
||||||
List<ModelTypeVo> rootNodes = new ArrayList<>();
|
List<ModelTypeVo> rootNodes = new ArrayList<>();
|
||||||
|
|
||||||
// 为每个节点添加子节点
|
// 为每个节点添加子节点
|
||||||
for (ModelTypeVo node : treeNodes) {
|
for (ModelTypeVo node : treeNodes) {
|
||||||
String parentId = node.getParentId();
|
String parentId = node.getParentId();
|
||||||
@ -373,9 +437,29 @@ public class ModelLibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 排序根节点
|
||||||
|
rootNodes.sort(Comparator.comparingInt(ModelTypeVo::getTreeIndex));
|
||||||
|
|
||||||
|
// 递归排序所有子节点
|
||||||
|
sortChildren(rootNodes);
|
||||||
|
|
||||||
return rootNodes;
|
return rootNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 递归排序所有子节点
|
||||||
|
private void sortChildren(List<ModelTypeVo> nodes) {
|
||||||
|
if (nodes == null || nodes.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 排序当前层级节点
|
||||||
|
nodes.sort(Comparator.comparingInt(ModelTypeVo::getTreeIndex));
|
||||||
|
// 递归排序下一层级
|
||||||
|
for (ModelTypeVo node : nodes) {
|
||||||
|
sortChildren(node.getChildren());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getModelLibrary() {
|
private String getModelLibrary() {
|
||||||
// 获取启用的模型库
|
// 获取启用的模型库
|
||||||
LambdaQueryWrapper<ModelLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<ModelLibrary> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
@ -417,4 +501,69 @@ public class ModelLibraryController {
|
|||||||
modelLibraryService.save(newModel);
|
modelLibraryService.save(newModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据模型分类ID、查询该分类ID及其所有子分类ID的列表(递归包含所有层级子分类)
|
||||||
|
*/
|
||||||
|
private List<String> getModelTypeIdsWithChildren(String modelTypeId) throws SQLException, IllegalAccessException, InstantiationException {
|
||||||
|
// 结果列表初始化
|
||||||
|
List<String> typeIdList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 校验入参:分类ID不能为空
|
||||||
|
if (StringUtils.isEmpty(modelTypeId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前启用的模型库路径
|
||||||
|
String modelPath = getModelLibrary();
|
||||||
|
if (modelPath == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验目标分类ID是否存在(避免无效ID导致递归无结果)
|
||||||
|
if (!isModelTypeExist(modelPath, modelTypeId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归查询: 收集当前分类及所有子分类ID
|
||||||
|
recursiveGetChildIds(modelPath, modelTypeId, typeIdList);
|
||||||
|
|
||||||
|
return typeIdList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归查询子分类ID、并将结果存入列表
|
||||||
|
*/
|
||||||
|
private void recursiveGetChildIds(String modelPath, String currentTypeId, List<String> resultList) throws SQLException, IllegalAccessException, InstantiationException {
|
||||||
|
// 先将当前分类ID加入结果列表(确保包含自身)
|
||||||
|
resultList.add(currentTypeId);
|
||||||
|
|
||||||
|
// 查询当前分类的「直接子分类」(按 tree_index 排序、保持树形结构原有顺序)
|
||||||
|
String childSql = "SELECT id FROM model_type WHERE parent_id = ? ORDER BY tree_index ASC";
|
||||||
|
List<Object> childParams = new ArrayList<>();
|
||||||
|
childParams.add(currentTypeId);
|
||||||
|
List<ModelType> childModelTypes = SQLiteUtil.queryForList(
|
||||||
|
modelPath, childSql, childParams, ModelType.class
|
||||||
|
);
|
||||||
|
|
||||||
|
// 若存在子分类、递归查询每个子分类的子分类
|
||||||
|
if (childModelTypes != null && !childModelTypes.isEmpty()) {
|
||||||
|
for (ModelType childType : childModelTypes) {
|
||||||
|
recursiveGetChildIds(modelPath, childType.getId(), resultList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验模型分类ID是否存在于模型库中
|
||||||
|
*/
|
||||||
|
private boolean isModelTypeExist(String modelPath, String typeId)
|
||||||
|
throws SQLException, IllegalAccessException, InstantiationException {
|
||||||
|
String checkSql = "SELECT id FROM model_type WHERE id = ?";
|
||||||
|
List<Object> checkParams = new ArrayList<>();
|
||||||
|
checkParams.add(typeId);
|
||||||
|
List<ModelType> existTypes = SQLiteUtil.queryForList(modelPath, checkSql, checkParams, ModelType.class);
|
||||||
|
// 若查询结果非空、说明分类存在
|
||||||
|
return existTypes != null && !existTypes.isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,120 @@
|
|||||||
|
package com.yj.earth.business.controller;
|
||||||
|
|
||||||
|
import com.yj.earth.common.util.ApiResponse;
|
||||||
|
import com.yj.earth.dto.system.UpdateSystemServiceDto;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Tag(name = "系统服务管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/systemService")
|
||||||
|
public class SystemController {
|
||||||
|
@Value("${server.host}")
|
||||||
|
private String serverHost;
|
||||||
|
@Value("${server.port}")
|
||||||
|
private Integer serverPort;
|
||||||
|
|
||||||
|
// 配置文件路径(jar包所在目录的application.yml)
|
||||||
|
private static final String CONFIG_FILE_PATH = "application.yml";
|
||||||
|
|
||||||
|
// 重启脚本路径(app同级目录)
|
||||||
|
private static final String WINDOWS_REBOOT_SCRIPT = "../reboot.bat";
|
||||||
|
private static final String LINUX_REBOOT_SCRIPT = "../reboot.sh";
|
||||||
|
|
||||||
|
@Operation(summary = "读取系统服务配置")
|
||||||
|
@PostMapping("/info")
|
||||||
|
public ApiResponse readSystemServiceConfig() {
|
||||||
|
Map<String, Object> map = Map.of("serverHost", serverHost, "serverPort", serverPort);
|
||||||
|
return ApiResponse.success(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改系统服务配置并重启")
|
||||||
|
@PostMapping("/update")
|
||||||
|
public ApiResponse updateSystemService(@RequestBody UpdateSystemServiceDto updateSystemServiceDto) {
|
||||||
|
try {
|
||||||
|
// 读取并更新YAML配置
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
File configFile = new File(CONFIG_FILE_PATH);
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
return ApiResponse.failure("配置文件不存在");
|
||||||
|
}
|
||||||
|
// 解析YAML内容
|
||||||
|
Map<String, Object> configMap;
|
||||||
|
try (InputStream in = Files.newInputStream(Paths.get(CONFIG_FILE_PATH))) {
|
||||||
|
configMap = yaml.load(in);
|
||||||
|
if (configMap == null) {
|
||||||
|
configMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新配置
|
||||||
|
Map<String, Object> serverMap = (Map<String, Object>) configMap.getOrDefault("server", new HashMap<>());
|
||||||
|
if (updateSystemServiceDto.getServerHost() != null && !updateSystemServiceDto.getServerHost().isEmpty()) {
|
||||||
|
serverMap.put("host", updateSystemServiceDto.getServerHost());
|
||||||
|
}
|
||||||
|
if (updateSystemServiceDto.getServerPort() != null) {
|
||||||
|
serverMap.put("port", updateSystemServiceDto.getServerPort());
|
||||||
|
}
|
||||||
|
configMap.put("server", serverMap);
|
||||||
|
// 保存配置
|
||||||
|
DumperOptions options = new DumperOptions();
|
||||||
|
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
|
options.setPrettyFlow(true);
|
||||||
|
Yaml writeYaml = new Yaml(options);
|
||||||
|
try (FileWriter writer = new FileWriter(configFile, StandardCharsets.UTF_8)) {
|
||||||
|
writeYaml.dump(configMap, writer);
|
||||||
|
}
|
||||||
|
// 执行重启脚本
|
||||||
|
executeRebootScript();
|
||||||
|
return ApiResponse.success(null);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
return ApiResponse.failure("更新配置失败:" + e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.failure("操作失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行重启脚本
|
||||||
|
*/
|
||||||
|
private void executeRebootScript() throws IOException, InterruptedException {
|
||||||
|
String scriptPath;
|
||||||
|
ProcessBuilder processBuilder;
|
||||||
|
|
||||||
|
// 判断操作系统类型、选择对应的脚本
|
||||||
|
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||||
|
scriptPath = WINDOWS_REBOOT_SCRIPT;
|
||||||
|
// 检查Windows脚本是否存在
|
||||||
|
if (!new File(scriptPath).exists()) {
|
||||||
|
throw new FileNotFoundException("Windows重启脚本不存在: " + scriptPath);
|
||||||
|
}
|
||||||
|
processBuilder = new ProcessBuilder(scriptPath);
|
||||||
|
} else {
|
||||||
|
scriptPath = LINUX_REBOOT_SCRIPT;
|
||||||
|
// 检查Linux脚本是否存在
|
||||||
|
if (!new File(scriptPath).exists()) {
|
||||||
|
throw new FileNotFoundException("Linux重启脚本不存在: " + scriptPath);
|
||||||
|
}
|
||||||
|
// 给脚本添加执行权限
|
||||||
|
new ProcessBuilder("chmod", "+x", scriptPath).start().waitFor();
|
||||||
|
processBuilder = new ProcessBuilder(scriptPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行脚本(在新进程中运行、不阻塞当前请求)
|
||||||
|
processBuilder.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
package com.yj.earth.business.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.yj.earth.annotation.RoleAccess;
|
||||||
|
import com.yj.earth.business.domain.WebSource;
|
||||||
|
import com.yj.earth.business.service.SourceService;
|
||||||
|
import com.yj.earth.business.service.WebSourceService;
|
||||||
|
import com.yj.earth.common.util.ApiResponse;
|
||||||
|
import com.yj.earth.dto.source.AddModelSourceDto;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Tag(name = "网页版独有接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/webSource")
|
||||||
|
public class WebSourceController {
|
||||||
|
@Value("${sync.folder}")
|
||||||
|
private String syncFolder;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WebSourceService webSourceService;
|
||||||
|
@Resource
|
||||||
|
private SourceService sourceService;
|
||||||
|
private static final List<String> SUPPORT_EXTENSIONS = Arrays.asList("clt", "mbtiles", "pak", "pbf");
|
||||||
|
|
||||||
|
@RoleAccess(roleNames = "管理员")
|
||||||
|
@Operation(summary = "同步数据")
|
||||||
|
@PostMapping("/sync")
|
||||||
|
public ApiResponse sync() {
|
||||||
|
try {
|
||||||
|
Path syncPath = Paths.get(syncFolder);
|
||||||
|
// 验证文件夹有效性
|
||||||
|
if (!Files.exists(syncPath) || !Files.isDirectory(syncPath)) {
|
||||||
|
return ApiResponse.failure("同步文件夹不存在或不是一个有效的目录");
|
||||||
|
}
|
||||||
|
// 循环处理所有支持的文件类型、减少重复代码
|
||||||
|
for (String extension : SUPPORT_EXTENSIONS) {
|
||||||
|
List<String> filePaths = getFilesByExtension(syncPath, extension);
|
||||||
|
// 批量处理同类型文件
|
||||||
|
filePaths.forEach(this::saveFileIfNotExists);
|
||||||
|
}
|
||||||
|
return ApiResponse.success(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ApiResponse.failure("同步数据失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 若文件路径不存在则保存到数据库
|
||||||
|
*/
|
||||||
|
private void saveFileIfNotExists(String filePath) {
|
||||||
|
// 检查路径是否已存在
|
||||||
|
LambdaQueryWrapper<WebSource> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(WebSource::getPath, filePath);
|
||||||
|
if (webSourceService.count(queryWrapper) > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建并保存 WebSource 对象
|
||||||
|
WebSource webSource = new WebSource();
|
||||||
|
webSource.setName(FileUtil.getName(filePath));
|
||||||
|
webSource.setType(FileUtil.extName(filePath));
|
||||||
|
webSource.setPath(filePath);
|
||||||
|
webSourceService.save(webSource);
|
||||||
|
|
||||||
|
if (!webSource.getType().equals("pbf")) {
|
||||||
|
addModelSourceIfNotExists(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 若文件路径不存在则保存到数据库
|
||||||
|
*/
|
||||||
|
private void addModelSourceIfNotExists(String filePath) {
|
||||||
|
AddModelSourceDto addModelSourceDto = new AddModelSourceDto();
|
||||||
|
addModelSourceDto.setSourcePath(filePath);
|
||||||
|
addModelSourceDto.setId(UUID.randomUUID().toString(true));
|
||||||
|
sourceService.addModelSource(addModelSourceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取所有文件")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ApiResponse list(@RequestParam(required = false) @Parameter(description = "文件类型") String type) {
|
||||||
|
if (type != null) {
|
||||||
|
LambdaQueryWrapper<WebSource> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(WebSource::getType, type);
|
||||||
|
return ApiResponse.success(webSourceService.list(queryWrapper));
|
||||||
|
}
|
||||||
|
return ApiResponse.success(webSourceService.list());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定目录下特定扩展名的文件
|
||||||
|
*/
|
||||||
|
private List<String> getFilesByExtension(Path directory, String extension) throws IOException {
|
||||||
|
try (Stream<Path> stream = Files.list(directory)) {
|
||||||
|
return stream
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(path -> {
|
||||||
|
String fileName = path.getFileName().toString();
|
||||||
|
return fileName.toLowerCase().endsWith("." + extension.toLowerCase());
|
||||||
|
})
|
||||||
|
.map(Path::toAbsolutePath)
|
||||||
|
.map(Path::toString)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,11 +22,13 @@ public class Model {
|
|||||||
@Schema(description = "海报类型")
|
@Schema(description = "海报类型")
|
||||||
private String posterType;
|
private String posterType;
|
||||||
@Schema(description = "海报数据")
|
@Schema(description = "海报数据")
|
||||||
private String poster;
|
private byte[] posterData;
|
||||||
|
@Schema(description = "海报URL")
|
||||||
|
private String posterUrl;
|
||||||
@Schema(description = "模型数据")
|
@Schema(description = "模型数据")
|
||||||
private String data;
|
private byte[] modelData;
|
||||||
@Schema(description = "模型视图")
|
@Schema(description = "模型数据URL")
|
||||||
private String view;
|
private String modelUrl;
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
|
|||||||
43
src/main/java/com/yj/earth/business/domain/WebSource.java
Normal file
43
src/main/java/com/yj/earth/business/domain/WebSource.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package com.yj.earth.business.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class WebSource implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_UUID)
|
||||||
|
@Schema(description = "主键")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "资源名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "资源路径")
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@Schema(description = "资源类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.yj.earth.business.mapper;
|
||||||
|
|
||||||
|
import com.yj.earth.business.domain.WebSource;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Mapper 接口
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 周志雄
|
||||||
|
* @since 2025-09-26
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface WebSourceMapper extends BaseMapper<WebSource> {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.yj.earth.business.service;
|
||||||
|
|
||||||
|
import com.yj.earth.business.domain.WebSource;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
public interface WebSourceService extends IService<WebSource> {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.yj.earth.business.service.impl;
|
||||||
|
|
||||||
|
import com.yj.earth.business.domain.WebSource;
|
||||||
|
import com.yj.earth.business.mapper.WebSourceMapper;
|
||||||
|
import com.yj.earth.business.service.WebSourceService;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 服务实现类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 周志雄
|
||||||
|
* @since 2025-09-26
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class WebSourceServiceImpl extends ServiceImpl<WebSourceMapper, WebSource> implements WebSourceService {
|
||||||
|
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
|||||||
excludePathPatterns.add("/data/clt/**");
|
excludePathPatterns.add("/data/clt/**");
|
||||||
excludePathPatterns.add("/data/mbtiles/**");
|
excludePathPatterns.add("/data/mbtiles/**");
|
||||||
excludePathPatterns.add("/data/pak/**");
|
excludePathPatterns.add("/data/pak/**");
|
||||||
|
excludePathPatterns.add("/systemService/**");
|
||||||
|
excludePathPatterns.add("/**");
|
||||||
|
|
||||||
// 注册 Sa-Token 拦截器
|
// 注册 Sa-Token 拦截器
|
||||||
registry.addInterceptor(new SaInterceptor(handle -> {
|
registry.addInterceptor(new SaInterceptor(handle -> {
|
||||||
|
|||||||
51
src/main/java/com/yj/earth/common/util/FileCommonUtil.java
Normal file
51
src/main/java/com/yj/earth/common/util/FileCommonUtil.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package com.yj.earth.common.util;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FileCommonUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将本地文件转换为 MultipartFile
|
||||||
|
*/
|
||||||
|
public static MultipartFile convertToMultipartFile(File file) {
|
||||||
|
try (FileInputStream inputStream = new FileInputStream(file)) {
|
||||||
|
return new MockMultipartFile(
|
||||||
|
"files",
|
||||||
|
file.getName(),
|
||||||
|
MediaType.APPLICATION_OCTET_STREAM_VALUE,
|
||||||
|
inputStream
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MediaType getImageMediaType(String suffix) {
|
||||||
|
String lowerSuffix = suffix.toLowerCase();
|
||||||
|
switch (lowerSuffix) {
|
||||||
|
case ".jpg":
|
||||||
|
case ".jpeg":
|
||||||
|
return MediaType.IMAGE_JPEG;
|
||||||
|
case ".glb":
|
||||||
|
return MediaType.valueOf("model/gltf-binary");
|
||||||
|
case ".png":
|
||||||
|
return MediaType.IMAGE_PNG;
|
||||||
|
case ".gif":
|
||||||
|
return MediaType.IMAGE_GIF;
|
||||||
|
case ".bmp":
|
||||||
|
return MediaType.valueOf("image/bmp");
|
||||||
|
case ".webp":
|
||||||
|
return MediaType.valueOf("image/webp");
|
||||||
|
case ".svg":
|
||||||
|
return MediaType.valueOf("image/svg+xml");
|
||||||
|
default:
|
||||||
|
return MediaType.APPLICATION_OCTET_STREAM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,28 +0,0 @@
|
|||||||
package com.yj.earth.common.util;
|
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class FileUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将本地文件转换为 MultipartFile
|
|
||||||
*/
|
|
||||||
public static MultipartFile convertToMultipartFile(File file) {
|
|
||||||
try (FileInputStream inputStream = new FileInputStream(file)) {
|
|
||||||
return new MockMultipartFile(
|
|
||||||
"files",
|
|
||||||
file.getName(),
|
|
||||||
MediaType.APPLICATION_OCTET_STREAM_VALUE,
|
|
||||||
inputStream
|
|
||||||
);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
82
src/main/java/com/yj/earth/common/util/GdalUtil.java
Normal file
82
src/main/java/com/yj/earth/common/util/GdalUtil.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package com.yj.earth.common.util;
|
||||||
|
|
||||||
|
import org.gdal.gdal.gdal;
|
||||||
|
import org.gdal.ogr.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class GdalUtil {
|
||||||
|
|
||||||
|
// 静态初始化
|
||||||
|
static {
|
||||||
|
gdal.SetConfigOption("PROJ_LIB", "./lib");
|
||||||
|
gdal.AllRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readVectorToJson(String filePath) {
|
||||||
|
// 打开矢量文件
|
||||||
|
DataSource dataSource = ogr.Open(filePath);
|
||||||
|
if (dataSource == null) {
|
||||||
|
throw new RuntimeException("无法打开矢量文件: " + filePath);
|
||||||
|
}
|
||||||
|
// 存储所有数据的Map
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
// 获取图层数量
|
||||||
|
int layerCount = dataSource.GetLayerCount();
|
||||||
|
result.put("layerCount", layerCount);
|
||||||
|
// 存储所有图层数据
|
||||||
|
List<Map<String, Object>> layers = new ArrayList<>();
|
||||||
|
for (int i = 0; i < layerCount; i++) {
|
||||||
|
Layer layer = dataSource.GetLayer(i);
|
||||||
|
if (layer == null) continue;
|
||||||
|
// 图层信息
|
||||||
|
Map<String, Object> layerData = new HashMap<>();
|
||||||
|
layerData.put("layerName", layer.GetName());
|
||||||
|
layerData.put("featureCount", layer.GetFeatureCount());
|
||||||
|
// 获取字段定义
|
||||||
|
FeatureDefn featureDefn = layer.GetLayerDefn();
|
||||||
|
int fieldCount = featureDefn.GetFieldCount();
|
||||||
|
List<Map<String, Object>> fields = new ArrayList<>();
|
||||||
|
for (int j = 0; j < fieldCount; j++) {
|
||||||
|
FieldDefn fieldDefn = featureDefn.GetFieldDefn(j);
|
||||||
|
Map<String, Object> fieldInfo = new HashMap<>();
|
||||||
|
fieldInfo.put("name", fieldDefn.GetName());
|
||||||
|
fieldInfo.put("type", fieldDefn.GetFieldTypeName(fieldDefn.GetFieldType()));
|
||||||
|
fields.add(fieldInfo);
|
||||||
|
}
|
||||||
|
layerData.put("fields", fields);
|
||||||
|
// 读取所有要素
|
||||||
|
List<Map<String, Object>> features = new ArrayList<>();
|
||||||
|
layer.ResetReading();
|
||||||
|
Feature feature;
|
||||||
|
|
||||||
|
while ((feature = layer.GetNextFeature()) != null) {
|
||||||
|
Map<String, Object> featureData = new HashMap<>();
|
||||||
|
// 存储属性信息
|
||||||
|
Map<String, Object> attributes = new HashMap<>();
|
||||||
|
for (int j = 0; j < fieldCount; j++) {
|
||||||
|
attributes.put(featureDefn.GetFieldDefn(j).GetName(), feature.GetFieldAsString(j));
|
||||||
|
}
|
||||||
|
featureData.put("attributes", attributes);
|
||||||
|
// 存储几何信息
|
||||||
|
Geometry geometry = feature.GetGeometryRef();
|
||||||
|
if (geometry != null) {
|
||||||
|
featureData.put("geometryType", geometry.GetGeometryName());
|
||||||
|
featureData.put("wkt", geometry.ExportToWkt());
|
||||||
|
}
|
||||||
|
features.add(featureData);
|
||||||
|
feature.delete(); // 释放资源
|
||||||
|
}
|
||||||
|
layerData.put("features", features);
|
||||||
|
layers.add(layerData);
|
||||||
|
}
|
||||||
|
result.put("layers", layers);
|
||||||
|
// 关闭数据源
|
||||||
|
dataSource.delete();
|
||||||
|
// 转换为JSON并返回
|
||||||
|
return JsonUtil.toJson(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/main/java/com/yj/earth/common/util/SQLiteConverter.java
Normal file
274
src/main/java/com/yj/earth/common/util/SQLiteConverter.java
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
import java.sql.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SQLiteConverter {
|
||||||
|
// SQLite JDBC驱动
|
||||||
|
private static final String JDBC_DRIVER = "org.sqlite.JDBC";
|
||||||
|
|
||||||
|
// 原始数据库路径
|
||||||
|
private static final String ORIGINAL_DB_PATH = "jdbc:sqlite:D:\\YJEarth.model";
|
||||||
|
|
||||||
|
// 新数据库路径
|
||||||
|
private static final String NEW_DB_PATH = "jdbc:sqlite:E:\\通用模型库.model";
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("===== 开始数据库转换程序 =====");
|
||||||
|
System.out.println("原始数据库: " + ORIGINAL_DB_PATH);
|
||||||
|
System.out.println("目标数据库: " + NEW_DB_PATH + "\n");
|
||||||
|
|
||||||
|
// 使用try-with-resources自动管理连接资源
|
||||||
|
try (Connection originalConn = DriverManager.getConnection(ORIGINAL_DB_PATH);
|
||||||
|
Connection newConn = DriverManager.getConnection(NEW_DB_PATH)) {
|
||||||
|
|
||||||
|
System.out.println("✅ 成功连接到原始数据库");
|
||||||
|
System.out.println("✅ 成功创建并连接到新数据库\n");
|
||||||
|
|
||||||
|
// 在新数据库中创建表结构
|
||||||
|
System.out.println("===== 开始创建新表结构 =====");
|
||||||
|
createNewTables(newConn);
|
||||||
|
System.out.println("===== 新表结构创建完成 =====");
|
||||||
|
|
||||||
|
// 迁移mode_types表数据到model_type表
|
||||||
|
System.out.println("\n===== 开始迁移mode_types表数据 =====");
|
||||||
|
MigrationResult modeTypesResult = migrateModeTypes(originalConn, newConn);
|
||||||
|
System.out.println("===== mode_types表数据迁移完成 =====");
|
||||||
|
System.out.printf(" 成功: %d 条, 失败: %d 条, 总计: %d 条%n",
|
||||||
|
modeTypesResult.successCount,
|
||||||
|
modeTypesResult.failureCount,
|
||||||
|
modeTypesResult.totalCount);
|
||||||
|
|
||||||
|
// 迁移models表数据到model表
|
||||||
|
System.out.println("\n===== 开始迁移models表数据 =====");
|
||||||
|
MigrationResult modelsResult = migrateModels(originalConn, newConn);
|
||||||
|
System.out.println("===== models表数据迁移完成 =====");
|
||||||
|
System.out.printf(" 成功: %d 条, 失败: %d 条, 总计: %d 条%n",
|
||||||
|
modelsResult.successCount,
|
||||||
|
modelsResult.failureCount,
|
||||||
|
modelsResult.totalCount);
|
||||||
|
|
||||||
|
if (modeTypesResult.failureCount > 0 || modelsResult.failureCount > 0) {
|
||||||
|
System.out.println("\n⚠️ 注意:部分记录迁移失败、已跳过错误记录");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("\n===== 数据库转换处理完成! =====");
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.err.println("\n❌ 数据库连接错误: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迁移结果数据类
|
||||||
|
*/
|
||||||
|
private static class MigrationResult {
|
||||||
|
int successCount;
|
||||||
|
int failureCount;
|
||||||
|
int totalCount;
|
||||||
|
|
||||||
|
MigrationResult(int success, int failure, int total) {
|
||||||
|
this.successCount = success;
|
||||||
|
this.failureCount = failure;
|
||||||
|
this.totalCount = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在新数据库中创建表结构
|
||||||
|
*/
|
||||||
|
private static void createNewTables(Connection conn) throws SQLException {
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
// 创建model_type表
|
||||||
|
System.out.println("正在创建model_type表...");
|
||||||
|
String createModelTypeTable = "CREATE TABLE IF NOT EXISTS \"model_type\" (" +
|
||||||
|
"\"id\" TEXT," +
|
||||||
|
"\"name\" TEXT," +
|
||||||
|
"\"parent_id\" TEXT," +
|
||||||
|
"\"tree_index\" INTEGER," +
|
||||||
|
"\"created_at\" TEXT," +
|
||||||
|
"\"updated_at\" TEXT," +
|
||||||
|
"PRIMARY KEY (\"id\")" +
|
||||||
|
");";
|
||||||
|
stmt.execute(createModelTypeTable);
|
||||||
|
System.out.println("✅ model_type表创建完成");
|
||||||
|
|
||||||
|
// 创建model表
|
||||||
|
System.out.println("正在创建model表...");
|
||||||
|
String createModelTable = "CREATE TABLE IF NOT EXISTS \"model\" (" +
|
||||||
|
"\"id\" TEXT," +
|
||||||
|
"\"model_type_id\" TEXT," +
|
||||||
|
"\"model_name\" TEXT," +
|
||||||
|
"\"model_type\" TEXT," +
|
||||||
|
"\"poster_type\" TEXT," +
|
||||||
|
"\"poster\" TEXT," +
|
||||||
|
"\"data\" TEXT," +
|
||||||
|
"\"data_bytes\" BLOB," +
|
||||||
|
"\"view\" TEXT," +
|
||||||
|
"\"created_at\" TEXT," +
|
||||||
|
"\"updated_at\" TEXT," +
|
||||||
|
"PRIMARY KEY (\"id\")" +
|
||||||
|
");";
|
||||||
|
stmt.execute(createModelTable);
|
||||||
|
System.out.println("✅ model表创建完成");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迁移mode_types表数据到model_type表
|
||||||
|
*/
|
||||||
|
private static MigrationResult migrateModeTypes(Connection originalConn, Connection newConn) throws SQLException {
|
||||||
|
// 先获取记录总数、用于显示进度
|
||||||
|
int totalCount;
|
||||||
|
try (PreparedStatement countStmt = originalConn.prepareStatement("SELECT COUNT(*) FROM mode_types");
|
||||||
|
ResultSet countRs = countStmt.executeQuery()) {
|
||||||
|
countRs.next();
|
||||||
|
totalCount = countRs.getInt(1);
|
||||||
|
System.out.println("发现 " + totalCount + " 条mode_types记录待迁移");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询原始表数据
|
||||||
|
try (PreparedStatement selectStmt = originalConn.prepareStatement("SELECT * FROM mode_types");
|
||||||
|
ResultSet rs = selectStmt.executeQuery()) {
|
||||||
|
|
||||||
|
// 插入到新表、关闭自动提交以提高性能
|
||||||
|
newConn.setAutoCommit(false);
|
||||||
|
|
||||||
|
try (PreparedStatement insertStmt = newConn.prepareStatement(
|
||||||
|
"INSERT INTO model_type (" +
|
||||||
|
"id, name, parent_id, tree_index, created_at, updated_at" +
|
||||||
|
") VALUES (?, ?, ?, ?, ?, ?)")) {
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
int failureCount = 0;
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
try {
|
||||||
|
// 生成新的UUID作为ID
|
||||||
|
String newId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
String typeName = rs.getString("type_name");
|
||||||
|
|
||||||
|
// 映射字段
|
||||||
|
insertStmt.setString(1, newId);
|
||||||
|
insertStmt.setString(2, typeName);
|
||||||
|
insertStmt.setString(3, rs.getString("p_id"));
|
||||||
|
insertStmt.setInt(4, 0);
|
||||||
|
insertStmt.setString(5, rs.getString("created_at"));
|
||||||
|
insertStmt.setString(6, rs.getString("updated_at"));
|
||||||
|
|
||||||
|
insertStmt.executeUpdate();
|
||||||
|
successCount++;
|
||||||
|
|
||||||
|
// 每迁移10条记录或最后一批记录时打印进度
|
||||||
|
if (successCount % 10 == 0 || (successCount + failureCount) == totalCount) {
|
||||||
|
printProgress(successCount + failureCount, totalCount);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// 捕获单条记录的异常、跳过该记录
|
||||||
|
failureCount++;
|
||||||
|
System.err.printf("\n❌ 迁移mode_types记录失败 (序号: %d): %s%n",
|
||||||
|
successCount + failureCount, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newConn.commit();
|
||||||
|
return new MigrationResult(successCount, failureCount, totalCount);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
newConn.rollback();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
newConn.setAutoCommit(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迁移models表数据到model表
|
||||||
|
*/
|
||||||
|
private static MigrationResult migrateModels(Connection originalConn, Connection newConn) throws SQLException {
|
||||||
|
// 先获取记录总数、用于显示进度
|
||||||
|
int totalCount;
|
||||||
|
try (PreparedStatement countStmt = originalConn.prepareStatement("SELECT COUNT(*) FROM models");
|
||||||
|
ResultSet countRs = countStmt.executeQuery()) {
|
||||||
|
countRs.next();
|
||||||
|
totalCount = countRs.getInt(1);
|
||||||
|
System.out.println("发现 " + totalCount + " 条models记录待迁移");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询原始表数据
|
||||||
|
try (PreparedStatement selectStmt = originalConn.prepareStatement("SELECT * FROM models");
|
||||||
|
ResultSet rs = selectStmt.executeQuery()) {
|
||||||
|
|
||||||
|
// 插入到新表、关闭自动提交以提高性能
|
||||||
|
newConn.setAutoCommit(false);
|
||||||
|
|
||||||
|
try (PreparedStatement insertStmt = newConn.prepareStatement(
|
||||||
|
"INSERT INTO model (" +
|
||||||
|
"id, model_type_id, model_name, model_type, poster_type, " +
|
||||||
|
"poster, data, data_bytes, view, created_at, updated_at" +
|
||||||
|
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
int failureCount = 0;
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
try {
|
||||||
|
// 生成新的UUID作为ID
|
||||||
|
String newId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
String modelName = rs.getString("model_name");
|
||||||
|
|
||||||
|
// 映射字段
|
||||||
|
insertStmt.setString(1, newId);
|
||||||
|
insertStmt.setString(2, rs.getString("p_id"));
|
||||||
|
insertStmt.setString(3, modelName);
|
||||||
|
insertStmt.setString(4, rs.getString("model_type"));
|
||||||
|
insertStmt.setString(5, rs.getString("poster_type"));
|
||||||
|
|
||||||
|
// 处理BLOB字段
|
||||||
|
byte[] posterBlob = rs.getBytes("poster");
|
||||||
|
insertStmt.setString(6, posterBlob != null ? new String(posterBlob) : null);
|
||||||
|
insertStmt.setString(7, null);
|
||||||
|
|
||||||
|
byte[] dataBlob = rs.getBytes("data");
|
||||||
|
insertStmt.setBytes(8, dataBlob);
|
||||||
|
insertStmt.setString(9, null);
|
||||||
|
|
||||||
|
insertStmt.setString(10, rs.getString("created_at"));
|
||||||
|
insertStmt.setString(11, rs.getString("updated_at"));
|
||||||
|
|
||||||
|
insertStmt.executeUpdate();
|
||||||
|
successCount++;
|
||||||
|
|
||||||
|
// 每迁移10条记录或最后一批记录时打印进度
|
||||||
|
if (successCount % 10 == 0 || (successCount + failureCount) == totalCount) {
|
||||||
|
printProgress(successCount + failureCount, totalCount);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// 捕获单条记录的异常、跳过该记录
|
||||||
|
failureCount++;
|
||||||
|
System.err.printf("\n❌ 迁移models记录失败 (序号: %d, 名称: %s): %s%n",
|
||||||
|
successCount + failureCount,
|
||||||
|
rs.getString("model_name"),
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newConn.commit();
|
||||||
|
return new MigrationResult(successCount, failureCount, totalCount);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
newConn.rollback();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
newConn.setAutoCommit(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印迁移进度
|
||||||
|
*/
|
||||||
|
private static void printProgress(int current, int total) {
|
||||||
|
double percentage = (current * 100.0) / total;
|
||||||
|
System.out.printf("迁移进度: %d/%d (%.1f%%)\r", current, total, percentage);
|
||||||
|
System.out.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -41,8 +41,7 @@ public class SQLiteUtil {
|
|||||||
/**
|
/**
|
||||||
* 执行查询并返回对象列表
|
* 执行查询并返回对象列表
|
||||||
*/
|
*/
|
||||||
public static <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz)
|
public static <T> List<T> queryForList(String dbFilePath, String sql, List<Object> params, Class<T> clazz) throws SQLException, IllegalAccessException, InstantiationException {
|
||||||
throws SQLException, IllegalAccessException, InstantiationException {
|
|
||||||
List<T> resultList = new ArrayList<>();
|
List<T> resultList = new ArrayList<>();
|
||||||
|
|
||||||
// 使用try-with-resources确保资源自动关闭
|
// 使用try-with-resources确保资源自动关闭
|
||||||
@ -320,10 +319,9 @@ public class SQLiteUtil {
|
|||||||
"model_type_id" TEXT,
|
"model_type_id" TEXT,
|
||||||
"model_name" TEXT,
|
"model_name" TEXT,
|
||||||
"model_type" TEXT,
|
"model_type" TEXT,
|
||||||
|
"model_data" BLOB,
|
||||||
"poster_type" TEXT,
|
"poster_type" TEXT,
|
||||||
"poster" TEXT,
|
"poster_data" BLOB,
|
||||||
"data" TEXT,
|
|
||||||
"view" TEXT,
|
|
||||||
"created_at" TEXT,
|
"created_at" TEXT,
|
||||||
"updated_at" TEXT,
|
"updated_at" TEXT,
|
||||||
PRIMARY KEY ("id")
|
PRIMARY KEY ("id")
|
||||||
|
|||||||
@ -175,7 +175,7 @@ public class SdkUtil {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("读取sdk配置文件失败", e);
|
log.error("读取sdk配置文件失败", e);
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
log.error("sdk配置文件中server.port格式错误,应为数字类型", e);
|
log.error("sdk配置文件中server.port格式错误、应为数字类型", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
package com.yj.earth.dto.system;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UpdateSystemServiceDto {
|
||||||
|
@Schema(description = "服务地址")
|
||||||
|
private String serverHost;
|
||||||
|
@Schema(description = "服务端口")
|
||||||
|
private Integer serverPort;
|
||||||
|
}
|
||||||
14
src/main/java/com/yj/earth/vo/ModelDataVo.java
Normal file
14
src/main/java/com/yj/earth/vo/ModelDataVo.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.yj.earth.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ModelDataVo {
|
||||||
|
@Schema(description = "模型名称")
|
||||||
|
private String modelName;
|
||||||
|
@Schema(description = "模型数据")
|
||||||
|
private byte[] modelData;
|
||||||
|
@Schema(description = "海报数据")
|
||||||
|
private byte[] posterData;
|
||||||
|
}
|
||||||
@ -1,9 +1,31 @@
|
|||||||
package com.yj.earth.vo;
|
package com.yj.earth.vo;
|
||||||
|
|
||||||
import com.yj.earth.business.domain.Model;
|
import com.yj.earth.business.domain.Model;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class ModelVo extends Model {
|
public class ModelVo {
|
||||||
|
@Schema(description = "主键")
|
||||||
|
private String id;
|
||||||
|
@Schema(description = "模型类型ID")
|
||||||
|
private String modelTypeId;
|
||||||
|
@Schema(description = "模型名称")
|
||||||
|
private String modelName;
|
||||||
|
@Schema(description = "模型类型")
|
||||||
|
private String modelType;
|
||||||
|
@Schema(description = "模型数据URL")
|
||||||
|
private String modelDataUrl;
|
||||||
|
@Schema(description = "海报类型")
|
||||||
|
private String posterType;
|
||||||
|
@Schema(description = "海报URL")
|
||||||
|
private String posterDataUrl;
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
@Schema(description = "模型类型名称")
|
||||||
private String modelTypeName;
|
private String modelTypeName;
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/main/resources/mapper/WebSourceMapper.xml
Normal file
20
src/main/resources/mapper/WebSourceMapper.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.yj.earth.business.mapper.WebSourceMapper">
|
||||||
|
|
||||||
|
<!-- 通用查询映射结果 -->
|
||||||
|
<resultMap id="BaseResultMap" type="com.yj.earth.business.domain.WebSource">
|
||||||
|
<id column="id" property="id" />
|
||||||
|
<result column="name" property="name" />
|
||||||
|
<result column="path" property="path" />
|
||||||
|
<result column="type" property="type" />
|
||||||
|
<result column="created_at" property="createdAt" />
|
||||||
|
<result column="updated_at" property="updatedAt" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 通用查询结果列 -->
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, name, path, type, created_at, updated_at
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user