模型库、矢量文件

This commit is contained in:
2025-09-29 13:56:36 +08:00
parent dea2dbd508
commit 7d141998ff
27 changed files with 1143 additions and 94 deletions

View File

@ -9,18 +9,24 @@ import com.yj.earth.business.domain.ModelType;
import com.yj.earth.business.service.FileInfoService;
import com.yj.earth.business.service.ModelLibraryService;
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.dto.militaryLibrary.DragMilitaryTypeDto;
import com.yj.earth.dto.modelLibrary.AddModelTypeDto;
import com.yj.earth.dto.modelLibrary.CreateModelLibraryDto;
import com.yj.earth.dto.modelLibrary.DragModelTypeDto;
import com.yj.earth.dto.modelLibrary.UpdateModelTypeNameDto;
import com.yj.earth.vo.ModelDataVo;
import com.yj.earth.vo.ModelTypeVo;
import com.yj.earth.vo.ModelVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
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.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@ -28,11 +34,11 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Tag(name = "模型库管理")
@ -157,37 +163,6 @@ public class ModelLibraryController {
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 = "添加模型文件")
@PostMapping("/addModelFile")
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
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 (?, ?, ?, ?, ?, ?)";
String url = fileInfoService.uploadWithPreview(file);
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(fileNameWithoutSuffix);
params.add(fileSuffix);
params.add(url);
params.add(file.getBytes());
params.add(LocalDateTime.now());
// 执行插入操作
@ -231,6 +206,72 @@ public class ModelLibraryController {
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 = "根据模型类型查看模型列表")
@PostMapping("/modelList")
public ApiResponse modelList(@Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws SQLException, IllegalAccessException, InstantiationException {
@ -239,7 +280,18 @@ public class ModelLibraryController {
if (modelPath == null) {
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 = """
SELECT
model.id,
@ -247,24 +299,34 @@ public class ModelLibraryController {
model.model_name as modelName,
model.model_type as modelType,
model.poster_type as posterType,
model.poster,
model.data,
model.view,
model.created_at as createdAt,
model.updated_at as updatedAt,
model_type.name as modelTypeName from
model JOIN model_type ON model.model_type_id = model_type.id
WHERE model.model_type_id = ?
""";
model_type.name as modelTypeName
FROM model
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<>();
params.add(modelTypeId);
List<ModelVo> modelVos = SQLiteUtil.queryForList(modelPath, sql, params, ModelVo.class);
List<ModelVo> modelVos = SQLiteUtil.queryForList(modelPath, sql, null, 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);
}
@Operation(summary = "更新模型信息")
@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();
@ -279,10 +341,9 @@ public class ModelLibraryController {
// 处理封面文件
if (file != null && !file.isEmpty()) {
String fileSuffix = FileUtil.extName(file.getOriginalFilename());
String url = fileInfoService.uploadWithPreview(file);
sql.append(", poster_type = ?, poster = ? ");
sql.append(", poster_type = ?, poster_data = ? ");
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,
updated_at as updatedAt FROM model_type ORDER BY tree_index ASC
""";
// 查询所有模型类型
// 查询所有模型类型
List<ModelType> modelTypes = SQLiteUtil.queryForList(modelPath, sql, null, ModelType.class);
// 转换为树形结构
return buildModelTypeTree(modelTypes);
@ -354,11 +415,14 @@ public class ModelLibraryController {
List<ModelTypeVo> treeNodes = modelTypes.stream()
.map(modelType -> new ModelTypeVo(modelType))
.collect(Collectors.toList());
// 构建节点ID到节点的映射
Map<String, ModelTypeVo> nodeMap = treeNodes.stream()
.collect(Collectors.toMap(ModelTypeVo::getId, node -> node));
// 根节点列表
List<ModelTypeVo> rootNodes = new ArrayList<>();
// 为每个节点添加子节点
for (ModelTypeVo node : treeNodes) {
String parentId = node.getParentId();
@ -373,9 +437,29 @@ public class ModelLibraryController {
}
}
}
// 排序根节点
rootNodes.sort(Comparator.comparingInt(ModelTypeVo::getTreeIndex));
// 递归排序所有子节点
sortChildren(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() {
// 获取启用的模型库
LambdaQueryWrapper<ModelLibrary> queryWrapper = new LambdaQueryWrapper<>();
@ -417,4 +501,69 @@ public class ModelLibraryController {
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();
}
}