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.CheckAuth; import com.yj.earth.business.domain.ModelLibrary; 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; 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.*; import java.util.stream.Collectors; @Tag(name = "模型库管理") @CheckAuth @RestController @RequestMapping("/modelLibrary") public class ModelLibraryController { @Resource private ModelLibraryService modelLibraryService; @Resource private FileInfoService fileInfoService; @Operation(summary = "创建模型库") @PostMapping("/createModelLibrary") public ApiResponse createModelLibrary(@RequestBody CreateModelLibraryDto createModelLibraryDto) { try { // 参数校验 String folderPath = createModelLibraryDto.getPath(); String modelName = createModelLibraryDto.getName(); // 处理路径、组合为完整模型库文件路径 File parentDir = new File(folderPath); File modelFile = new File(parentDir, modelName); String modelPath = modelFile.getAbsolutePath().replace("\\", "/"); // 检查父目录是否存在、不存在则创建 if (!parentDir.exists()) { boolean mkdirsSuccess = parentDir.mkdirs(); if (!mkdirsSuccess) { return ApiResponse.failure("创建父目录失败:" + folderPath); } } // 检查模型库文件是否已存在 if (modelFile.exists()) { if (modelFile.isDirectory()) { return ApiResponse.failure("同名目录已存在、无法创建文件:" + modelPath); } return ApiResponse.failure("模型库文件已存在:" + modelPath); } // 创建模型库文件( boolean createSuccess = modelFile.createNewFile(); if (!createSuccess) { return ApiResponse.failure("创建模型库文件失败:" + modelPath); } // 添加模型库信息 addModelLibrary(modelPath); SQLiteUtil.initializationModel(modelPath); return ApiResponse.success(null); } catch (Exception e) { return ApiResponse.failure("创建模型库失败:" + e.getMessage()); } } @Operation(summary = "导入模型库") @PostMapping("/importModelLibrary") public ApiResponse importModelLibrary(@RequestParam("modelPath") @Parameter(description = "模型库路径") String modelPath) { addModelLibrary(modelPath); return ApiResponse.success(null); } @Operation(summary = "添加模型类型") @PostMapping("/addModelType") public ApiResponse addModelType(@RequestBody AddModelTypeDto addModelTypeDto) throws SQLException, IllegalAccessException, InstantiationException { String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } // 检查父级是否存在 String parentId = addModelTypeDto.getParentId(); if (parentId != null) { String sql = "SELECT * FROM model_type WHERE id = ?"; List params = new ArrayList<>(); params.add(parentId); ModelType modelType = SQLiteUtil.queryForObject(modelPath, sql, params, ModelType.class); if (modelType == null) { return ApiResponse.failure("父级模型类型不存在"); } } String sql = "INSERT INTO model_type " + "(id, name, parent_id, tree_index, created_at) " + "VALUES (?, ?, ?, ?, ?)"; List params = new ArrayList<>(); params.add(UUID.fastUUID().toString(true)); params.add(addModelTypeDto.getName()); params.add(addModelTypeDto.getParentId()); params.add(0); params.add(LocalDateTime.now()); SQLiteUtil.executeUpdate(modelPath, sql, params); return ApiResponse.success(null); } @Operation(summary = "修改模型类型名称") @PostMapping("/updateModelTypeName") public ApiResponse updateModelTypeName(@RequestBody UpdateModelTypeNameDto updateModelTypeNameDto) throws SQLException { String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } String sql = "UPDATE model_type SET name = ? WHERE id = ?"; List params = new ArrayList<>(); params.add(updateModelTypeNameDto.getName()); params.add(updateModelTypeNameDto.getId()); SQLiteUtil.executeUpdate(modelPath, sql, params); return ApiResponse.success(null); } @Operation(summary = "删除模型类型") @PostMapping("/deleteModelType") public ApiResponse deleteModelType(@Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws SQLException { String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } String sql = "DELETE FROM model_type WHERE id = ?"; List params = new ArrayList<>(); params.add(modelTypeId); SQLiteUtil.executeUpdate(modelPath, sql, params); return ApiResponse.success(null); } @Operation(summary = "模型类型列表") @GetMapping("/modelTypeList") public ApiResponse modelTypeTree() throws SQLException, IllegalAccessException, InstantiationException { return ApiResponse.success(modelTypeList()); } @Operation(summary = "添加模型文件") @PostMapping("/addModelFile") public ApiResponse addModelFile(@RequestParam("files") MultipartFile[] files, @Parameter(description = "模型类型ID") @RequestParam("modelTypeId") String modelTypeId) throws IOException, SQLException { // 获取最新的模型库路径 String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } // 循环处理每个上传的文件 for (MultipartFile file : files) { // 跳过空文件 if (file.isEmpty()) { continue; } // 获取文件信息 String fileName = file.getOriginalFilename(); if (fileName == null) { continue; } String fileSuffix = FileUtil.extName(fileName); String fileNameWithoutSuffix = FileUtil.mainName(fileName); // 构建插入SQL String sql = "INSERT INTO model " + "(id, model_type_id, model_name, model_type, model_data, created_at) " + "VALUES (?, ?, ?, ?, ?, ?)"; List params = new ArrayList<>(); String modelId = UUID.fastUUID().toString(true); params.add(modelId); params.add(modelTypeId); params.add(fileNameWithoutSuffix); params.add(fileSuffix); params.add(file.getBytes()); params.add(LocalDateTime.now()); // 执行插入操作 SQLiteUtil.executeUpdate(modelPath, sql, params); } return ApiResponse.success(null); } @Operation(summary = "获取模型数据") @GetMapping("/data/{type}/{modelId}/{fileSuffix}") public ResponseEntity 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 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 { // 获取最新的模型库 String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } // 获取分类ID及其所有子分类ID的列表 List 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, model.model_type_id as modelTypeId, model.model_name as modelName, model.model_type as modelType, model.poster_type as posterType, 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 IN (?) """.replace("?", idsWithQuotes); // 使用所有分类ID作为查询参数 List params = new ArrayList<>(); List 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 { // 获取最新的模型库路径 String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } // 动态构建SQL更新语句和参数 StringBuilder sql = new StringBuilder("UPDATE model SET updated_at = ? "); List params = new ArrayList<>(); // 始终更新时间 params.add(LocalDateTime.now()); // 处理封面文件 if (file != null && !file.isEmpty()) { String fileSuffix = FileUtil.extName(file.getOriginalFilename()); sql.append(", poster_type = ?, poster_data = ? "); params.add(fileSuffix); params.add(file.getBytes()); } // 处理模型名称 if (StringUtils.hasText(modelName)) { sql.append(", model_name = ? "); params.add(modelName); } // 拼接WHERE条件 sql.append("WHERE id = ?"); params.add(modelId); // 执行更新 SQLiteUtil.executeUpdate(modelPath, sql.toString(), params); return ApiResponse.success(null); } @Operation(summary = "删除模型") @PostMapping("/deleteModel") public ApiResponse deleteModel(@Parameter(description = "模型ID") @RequestParam("modelId") String modelId) throws SQLException { String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } String sql = "DELETE FROM model WHERE id = ?"; List params = new ArrayList<>(); params.add(modelId); SQLiteUtil.executeUpdate(modelPath, sql, params); return ApiResponse.success(null); } @Operation(summary = "拖动层级") @PostMapping("/dragModelType") public ApiResponse dragModelType(@RequestBody List dragModelTypeDtoList) throws SQLException, IllegalAccessException, InstantiationException { String modelPath = getModelLibrary(); if (modelPath == null) { return ApiResponse.failure("请先创建或导入模型库"); } // 遍历数据列表 for (DragModelTypeDto dragModelTypeDto : dragModelTypeDtoList) { String id = dragModelTypeDto.getId(); String parentId = dragModelTypeDto.getParentId(); String treeIndex = dragModelTypeDto.getTreeIndex(); List params = new ArrayList<>(); params.add(parentId); params.add(treeIndex); params.add(id); SQLiteUtil.executeUpdate(modelPath, "UPDATE model_type SET parent_id = ?, tree_index = ? WHERE id = ?", params); } // 返回树列表 return ApiResponse.success(modelTypeList()); } private List modelTypeList() throws SQLException, IllegalAccessException, InstantiationException { String modelPath = getModelLibrary(); if (modelPath == null) { return null; } String sql = """ SELECT id, name, parent_id as parentId, tree_index as treeIndex, created_at as createdAt, updated_at as updatedAt FROM model_type ORDER BY tree_index ASC """; // 查询所有模型的类型 List modelTypes = SQLiteUtil.queryForList(modelPath, sql, null, ModelType.class); // 转换为树形结构 return buildModelTypeTree(modelTypes); } private List buildModelTypeTree(List modelTypes) { List treeNodes = modelTypes.stream() .map(modelType -> new ModelTypeVo(modelType)) .collect(Collectors.toList()); // 构建节点ID到节点的映射 Map nodeMap = treeNodes.stream() .collect(Collectors.toMap(ModelTypeVo::getId, node -> node)); // 根节点列表 List rootNodes = new ArrayList<>(); // 为每个节点添加子节点 for (ModelTypeVo node : treeNodes) { String parentId = node.getParentId(); if (parentId == null || parentId.isEmpty()) { // 没有父节点的是根节点 rootNodes.add(node); } else { // 找到父节点并添加为子节点 ModelTypeVo parentNode = nodeMap.get(parentId); if (parentNode != null) { parentNode.getChildren().add(node); } } } // 排序根节点 rootNodes.sort(Comparator.comparingInt(ModelTypeVo::getTreeIndex)); // 递归排序所有子节点 sortChildren(rootNodes); return rootNodes; } // 递归排序所有子节点 private void sortChildren(List nodes) { if (nodes == null || nodes.isEmpty()) { return; } // 排序当前层级节点 nodes.sort(Comparator.comparingInt(ModelTypeVo::getTreeIndex)); // 递归排序下一层级 for (ModelTypeVo node : nodes) { sortChildren(node.getChildren()); } } private String getModelLibrary() { // 获取启用的模型库 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ModelLibrary::getIsEnable, 1); ModelLibrary modelLibrary = modelLibraryService.getOne(queryWrapper); if (modelLibrary == null) { return null; } return modelLibrary.getPath(); } private void addModelLibrary(String modelPath) { // 查询系统所有的模型库 List modelLibraries = modelLibraryService.list(); // 遍历并更新状态 for (ModelLibrary modelLibrary : modelLibraries) { // 设置启用状态为0 modelLibrary.setIsEnable(0); modelLibraryService.updateById(modelLibrary); } // 检查相同路径的模型库是否已存在 LambdaQueryWrapper pathWrapper = new LambdaQueryWrapper<>(); pathWrapper.eq(ModelLibrary::getPath, modelPath); ModelLibrary existingModel = modelLibraryService.getOne(pathWrapper); // 若存在相同路径的模型库、不做处理、仅仅更新状态为显示 if (existingModel != null) { existingModel.setIsEnable(1); modelLibraryService.updateById(existingModel); return; } else { // 新增模型库 ModelLibrary newModel = new ModelLibrary(); File file = FileUtil.file(modelPath); newModel.setId(UUID.fastUUID().toString(true)); newModel.setPath(modelPath); newModel.setName(FileUtil.extName(file)); newModel.setIsEnable(1); modelLibraryService.save(newModel); } } /** * 根据模型分类ID、查询该分类ID及其所有子分类ID的列表(递归包含所有层级子分类) */ private List getModelTypeIdsWithChildren(String modelTypeId) throws SQLException, IllegalAccessException, InstantiationException { // 结果列表初始化 List 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 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 childParams = new ArrayList<>(); childParams.add(currentTypeId); List 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 checkParams = new ArrayList<>(); checkParams.add(typeId); List existTypes = SQLiteUtil.queryForList(modelPath, checkSql, checkParams, ModelType.class); // 若查询结果非空、说明分类存在 return existTypes != null && !existTypes.isEmpty(); } }