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.*; import com.yj.earth.business.service.MilitaryLibraryService; import com.yj.earth.business.service.SourceService; import com.yj.earth.common.util.ApiResponse; import com.yj.earth.common.util.FileCommonUtil; import com.yj.earth.common.util.JsonUtil; import com.yj.earth.common.util.SQLiteUtil; import com.yj.earth.dto.militaryLibrary.AddMilitaryTypeDto; import com.yj.earth.dto.militaryLibrary.CreateMilitaryLibraryDto; import com.yj.earth.dto.militaryLibrary.DragMilitaryTypeDto; import com.yj.earth.dto.militaryLibrary.UpdateMilitaryTypeNameDto; import com.yj.earth.params.MilitaryParam; import com.yj.earth.params.ModelParam; import com.yj.earth.vo.MilitaryDataVo; import com.yj.earth.vo.MilitaryTypeVo; import com.yj.earth.vo.MilitaryVo; 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.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.nio.file.Files; import java.sql.SQLException; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Tag(name = "军标数据管理") @RestController @RequestMapping("/militaryLibrary") public class MilitaryLibraryController { @Resource private MilitaryLibraryService militaryLibraryService; @Resource private SourceService sourceService; @Value("${server.host}") private String serverHost; @Value("${server.port}") private int serverPort; @Operation(summary = "创建军标库") @PostMapping("/createMilitaryLibrary") public ApiResponse createMilitaryLibrary(@RequestBody CreateMilitaryLibraryDto createDto) { try { // 参数校验与路径处理 String folderPath = createDto.getPath(); String libraryName = createDto.getName(); if (!StringUtils.hasText(folderPath) || !StringUtils.hasText(libraryName)) { return ApiResponse.failure("路径和名称不能为空"); } // 构建完整军标库文件路径(SQLite文件) File parentDir = new File(folderPath); File militaryFile = new File(parentDir, libraryName); String militaryPath = militaryFile.getAbsolutePath().replace("\\", "/"); // 父目录不存在则创建 if (!parentDir.exists()) { boolean mkdirsSuccess = parentDir.mkdirs(); if (!mkdirsSuccess) { return ApiResponse.failure("创建父目录失败: " + folderPath); } } // 校验军标库文件是否已存在 if (militaryFile.exists()) { if (militaryFile.isDirectory()) { return ApiResponse.failure("同名目录已存在、无法创建文件: " + militaryPath); } return ApiResponse.failure("军标库文件已存在: " + militaryPath); } // 创建SQLite文件 boolean createFileSuccess = militaryFile.createNewFile(); if (!createFileSuccess) { return ApiResponse.failure("创建军标库文件失败: " + militaryPath); } // 初始化军标库表结构 SQLiteUtil.initializationMilitary(militaryPath); // 添加军标库配置到数据库 addMilitaryLibrary(militaryPath); return ApiResponse.success(null); } catch (Exception e) { return ApiResponse.failure("创建军标库失败: " + e.getMessage()); } } @Operation(summary = "导入军标库") @PostMapping("/importMilitaryLibrary") public ApiResponse importMilitaryLibrary( @RequestParam("militaryPath") @Parameter(description = "军标库SQLite文件路径") String militaryPath) { try { // 校验路径是否存在 File militaryFile = new File(militaryPath); if (!militaryFile.exists() || !militaryFile.isFile()) { return ApiResponse.failure("军标库文件不存在: " + militaryPath); } // 添加到配置表并启用 addMilitaryLibrary(militaryPath); return ApiResponse.success(null); } catch (Exception e) { return ApiResponse.failure("导入军标库失败: " + e.getMessage()); } } @Operation(summary = "添加军标类型") @PostMapping("/addMilitaryType") public ApiResponse addMilitaryType(@RequestBody AddMilitaryTypeDto addDto) throws SQLException, IllegalAccessException, InstantiationException { // 获取当前启用的军标库路径 String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } // 校验父级类型(若有) String parentId = addDto.getParentId(); if (StringUtils.hasText(parentId)) { String checkParentSql = "SELECT id FROM military_type WHERE id = ?"; List parentParams = new ArrayList<>(); parentParams.add(parentId); MilitaryType parentType = SQLiteUtil.queryForObject( militaryPath, checkParentSql, parentParams, MilitaryType.class ); if (parentType == null) { return ApiResponse.failure("父级军标类型不存在: " + parentId); } } // 插入军标类型 String insertSql = "INSERT INTO military_type " + "(id, name, parent_id, tree_index, created_at) " + "VALUES (?, ?, ?, ?, ?)"; List params = new ArrayList<>(); params.add(UUID.fastUUID().toString(true)); params.add(addDto.getName()); params.add(parentId); params.add(0); params.add(LocalDateTime.now().toString()); SQLiteUtil.executeUpdate(militaryPath, insertSql, params); return ApiResponse.success(null); } @Operation(summary = "修改军标类型名称") @PostMapping("/updateMilitaryTypeName") public ApiResponse updateMilitaryTypeName(@RequestBody UpdateMilitaryTypeNameDto updateDto) throws SQLException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } // 执行更新 String updateSql = "UPDATE military_type SET name = ?, updated_at = ? WHERE id = ?"; List params = new ArrayList<>(); params.add(updateDto.getName()); params.add(LocalDateTime.now().toString()); params.add(updateDto.getId()); SQLiteUtil.executeUpdate(militaryPath, updateSql, params); return ApiResponse.success(null); } @Operation(summary = "删除军标类型") @PostMapping("/deleteMilitaryType") public ApiResponse deleteMilitaryType( @RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws SQLException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } // 执行删除 String deleteSql = "DELETE FROM military_type WHERE id = ?"; List params = new ArrayList<>(); params.add(typeId); SQLiteUtil.executeUpdate(militaryPath, deleteSql, params); return ApiResponse.success(null); } @Operation(summary = "军标类型树形列表") @GetMapping("/militaryTypeTree") public ApiResponse militaryTypeTree(@Parameter(description = "军标名称") @RequestParam(value = "militaryName", required = false) String militaryName) throws SQLException, IllegalAccessException, InstantiationException { List treeList = militaryTypeList(militaryName); if (treeList == null) { return ApiResponse.successWithMessage("请先创建或导入军标库"); } return ApiResponse.success(treeList); } @Operation(summary = "添加军标文件") @PostMapping("/addMilitaryFile") public ApiResponse addMilitaryFile(@RequestParam("filePaths") List filePaths, @RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String typeId) throws IOException, SQLException { String militaryPath = getMilitaryLibrary(); // 循环处理每个绝对路径对应的文件 for (String filePath : filePaths) { File file = new File(filePath); // 解析文件名与后缀 String originalFileName = file.getName(); String fileSuffix = FileUtil.extName(originalFileName); String fileNameWithoutSuffix = FileUtil.mainName(originalFileName); // 插入军标数据 String insertSql = "INSERT INTO military " + "(id, military_type_id, military_name, military_type, military_data, created_at) " + "VALUES (?, ?, ?, ?, ?, ?)"; List params = new ArrayList<>(); params.add(UUID.fastUUID().toString(true)); params.add(typeId); params.add(fileNameWithoutSuffix); params.add(fileSuffix); params.add(Files.readAllBytes(file.toPath())); params.add(LocalDateTime.now().toString()); SQLiteUtil.executeUpdate(militaryPath, insertSql, params); } return ApiResponse.success(null); } @Operation(summary = "获取军标文件数据") @GetMapping("/data/military/{militaryId}/{fileSuffix}") public ResponseEntity getMilitaryData(@PathVariable("militaryId") @Parameter(description = "军标ID") String militaryId, @PathVariable("fileSuffix") @Parameter(description = "军标文件后缀") String fileSuffix) { try { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ResponseEntity.notFound().build(); } // 查询军标二进制数据 String querySql = "SELECT military_name, military_data FROM military WHERE id = ?"; List params = new ArrayList<>(); params.add(militaryId); MilitaryDataVo dataVo = SQLiteUtil.queryForObject( militaryPath, querySql, params, MilitaryDataVo.class ); if (dataVo == null || dataVo.getMilitaryData() == null) { return ResponseEntity.notFound().build(); } // 构建响应头【支持中文文件名+预览】 String originalFileName = dataVo.getMilitaryName() + "." + fileSuffix; String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name()); MediaType contentType = FileCommonUtil.getImageMediaType(fileSuffix); return ResponseEntity.ok() .contentType(contentType) .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + encodedFileName + "\"") .body(dataVo.getMilitaryData()); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @Operation(summary = "根据类型和名称模糊查询军标列表") @PostMapping("/militaryList") public ApiResponse getMilitaryList( @RequestParam("militaryTypeId") @Parameter(description = "军标类型ID") String militaryTypeId, @Parameter(description = "军标名称模糊查询") @RequestParam(value = "name", required = false) String name) throws SQLException, IllegalAccessException, InstantiationException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } // 获取当前类型及所有子类型ID(递归) List typeIdList = getMilitaryTypeIdsWithChildren(militaryTypeId); if (typeIdList == null || typeIdList.isEmpty()) { return ApiResponse.success(new ArrayList<>()); } // 构建IN条件 String idsWithQuotes = typeIdList.stream() .map(id -> "'" + id + "'") .collect(Collectors.joining(",")); // 构建SQL语句 StringBuilder sql = new StringBuilder(""" SELECT m.id, m.military_type_id as militaryTypeId, m.military_name as militaryName, m.military_type as militaryType, m.created_at as createdAt, m.updated_at as updatedAt, t.name as militaryTypeName FROM military m JOIN military_type t ON m.military_type_id = t.id WHERE m.military_type_id IN (%s) """.formatted(idsWithQuotes)); // 如果传入了名称、则增加模糊查询条件 if (name != null && !name.trim().isEmpty()) { // 直接拼接SQL、注意SQL注入风险 sql.append(" AND m.military_name LIKE '%" + name.trim() + "%'"); } // 统一添加排序条件 sql.append(" ORDER BY m.created_at DESC"); // 执行查询 List militaryVoList = SQLiteUtil.queryForList( militaryPath, sql.toString(), null, MilitaryVo.class ); for (MilitaryVo vo : militaryVoList) { vo.setMilitaryDataUrl("/militaryLibrary/data/military/" + vo.getId() + "/" + vo.getMilitaryType()); } return ApiResponse.success(militaryVoList); } @Operation(summary = "更新军标名称") @PostMapping("/updateMilitaryInfo") public ApiResponse updateMilitaryInfo( @RequestParam("militaryId") @Parameter(description = "军标ID") String militaryId, @RequestParam("militaryName") @Parameter(description = "新军标名称") String militaryName) throws SQLException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } String updateSql = "UPDATE military SET military_name = ?, updated_at = ? WHERE id = ?"; List params = new ArrayList<>(); params.add(militaryName); params.add(LocalDateTime.now().toString()); params.add(militaryId); SQLiteUtil.executeUpdate(militaryPath, updateSql, params); return ApiResponse.success(null); } @Operation(summary = "删除军标") @PostMapping("/deleteMilitary") public ApiResponse deleteMilitary(@RequestParam("militaryId") @Parameter(description = "军标ID") String militaryId) throws SQLException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } // 存储被删除的Source ID列表 List deletedSourceIds = new ArrayList<>(); // 获取URL String MilitaryDataUrlByModelId = getMilitaryDataUrlByModelId(militaryId); // 查询资源 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); // 查询类型为 model 的 queryWrapper.eq(Source::getSourceType, "military"); // 查询列表 List sourceList = sourceService.list(queryWrapper); // 遍历数据 for (Source source : sourceList) { // 取出 params 字段 String dataParams = source.getParams(); if (dataParams != null) { // 转换为 Model 对象 MilitaryParam militaryParam = JsonUtil.jsonToObject(dataParams, MilitaryParam.class); if (MilitaryDataUrlByModelId.equals(militaryParam.getUrl())) { // 删除这个资源 sourceService.removeById(source.getId()); // 添加到被删除的ID列表 deletedSourceIds.add(source.getId()); } } } // 执行删除 String deleteSql = "DELETE FROM military WHERE id = ?"; List params = new ArrayList<>(); params.add(militaryId); SQLiteUtil.executeUpdate(militaryPath, deleteSql, params); return ApiResponse.success(deletedSourceIds); } @Operation(summary = "拖动调整军标类型层级") @PostMapping("/dragMilitaryType") public ApiResponse dragMilitaryType(@RequestBody List dragDtoList) throws SQLException, IllegalAccessException, InstantiationException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return ApiResponse.failure("请先创建或导入军标库"); } // 批量更新层级和排序 for (DragMilitaryTypeDto dto : dragDtoList) { String updateSql = "UPDATE military_type " + "SET parent_id = ?, tree_index = ?, updated_at = ? " + "WHERE id = ?"; List params = new ArrayList<>(); params.add(dto.getParentId()); params.add(dto.getTreeIndex()); params.add(LocalDateTime.now().toString()); params.add(dto.getId()); SQLiteUtil.executeUpdate(militaryPath, updateSql, params); } // 返回更新后的树形列表 List treeList = militaryTypeList(null); return ApiResponse.success(treeList); } private String getMilitaryLibrary() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用、0=未启用 MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper); if (library == null) { return null; } return library.getPath(); } private void addMilitaryLibrary(String militaryPath) { // 所有已存在的军标库设置为「未启用」 List existLibraries = militaryLibraryService.list(); for (MilitaryLibrary library : existLibraries) { library.setIsEnable(0); militaryLibraryService.updateById(library); } // 检查路径是否已存在(存在则启用、不存在则新增) LambdaQueryWrapper pathWrapper = new LambdaQueryWrapper<>(); pathWrapper.eq(MilitaryLibrary::getPath, militaryPath); MilitaryLibrary existLibrary = militaryLibraryService.getOne(pathWrapper); if (existLibrary != null) { existLibrary.setIsEnable(1); militaryLibraryService.updateById(existLibrary); } else { MilitaryLibrary newLibrary = new MilitaryLibrary(); newLibrary.setId(UUID.fastUUID().toString(true)); newLibrary.setPath(militaryPath); newLibrary.setName(FileUtil.mainName(militaryPath)); newLibrary.setIsEnable(1); newLibrary.setCreatedAt(LocalDateTime.now()); militaryLibraryService.save(newLibrary); } } /** * 查询军标分类树形结构 */ private List militaryTypeList(String militaryName) throws SQLException, IllegalAccessException, InstantiationException { String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return null; } // 构建基础SQL语句 StringBuilder sqlBuilder = new StringBuilder(""" SELECT DISTINCT military_type.id, military_type.name, military_type.parent_id, military_type.tree_index, military_type.created_at, military_type.updated_at FROM military_type """); // 如果传入了军标名称、则拼接 JOIN 和 WHERE 条件 if (militaryName != null && !militaryName.trim().isEmpty()) { String trimmedName = militaryName.trim(); // 直接拼接SQL sqlBuilder.append(" INNER JOIN military ON military_type.id = military.military_type_id"); sqlBuilder.append(" WHERE military.military_name LIKE '%" + trimmedName + "%'"); } // 为所有查询都加上统一的排序条件 sqlBuilder.append(" ORDER BY military_type.tree_index ASC"); // 执行查询、获取符合条件的军标分类列表 List militaryTypes = SQLiteUtil.queryForList( militaryPath, sqlBuilder.toString(), null, MilitaryType.class ); // 将扁平的分类列表转换为树形结构 return buildMilitaryTypeTree(militaryTypes); } private List buildMilitaryTypeTree(List typeList) { // 转换为VO List voList = typeList.stream() .map(militaryType -> new MilitaryTypeVo(militaryType)).collect(Collectors.toList()); // 构建ID→VO的映射(方便快速查找父级) Map voMap = voList.stream() .collect(Collectors.toMap(MilitaryTypeVo::getId, vo -> vo)); // 组装树形结构 List rootList = new ArrayList<>(); for (MilitaryTypeVo vo : voList) { String parentId = vo.getParentId(); if (parentId == null || parentId.isEmpty()) { rootList.add(vo); } else { MilitaryTypeVo parentVo = voMap.get(parentId); if (parentVo != null) { parentVo.getChildren().add(vo); } } } // 排序 rootList.sort(Comparator.comparingInt(MilitaryTypeVo::getTreeIndex)); sortMilitaryTypeChildren(rootList); return rootList; } private void sortMilitaryTypeChildren(List voList) { if (voList == null || voList.isEmpty()) { return; } // 排序当前层级 voList.sort(Comparator.comparingInt(MilitaryTypeVo::getTreeIndex)); // 递归排序子层级 for (MilitaryTypeVo vo : voList) { sortMilitaryTypeChildren(vo.getChildren()); } } private List getMilitaryTypeIdsWithChildren(String typeId) throws SQLException, IllegalAccessException, InstantiationException { List idList = new ArrayList<>(); if (!StringUtils.hasText(typeId)) { return idList; } String militaryPath = getMilitaryLibrary(); if (militaryPath == null) { return idList; } // 校验类型是否存在 if (!isMilitaryTypeExist(militaryPath, typeId)) { return idList; } // 递归收集ID(包含自身) recursiveGetMilitaryTypeChildren(militaryPath, typeId, idList); return idList; } private void recursiveGetMilitaryTypeChildren(String militaryPath, String currentId, List idList) throws SQLException, IllegalAccessException, InstantiationException { // 先添加当前ID idList.add(currentId); // 查询直接子类型 String querySql = "SELECT id FROM military_type WHERE parent_id = ? ORDER BY tree_index ASC"; List params = new ArrayList<>(); params.add(currentId); List childList = SQLiteUtil.queryForList( militaryPath, querySql, params, MilitaryType.class ); // 递归查询子类型的子类型 if (childList != null && !childList.isEmpty()) { for (MilitaryType child : childList) { recursiveGetMilitaryTypeChildren(militaryPath, child.getId(), idList); } } } /** * 根据军标ID获取模型数据访问URL */ private String getMilitaryDataUrlByModelId(String militaryId) { try { // 获取当前启用的军标库路径 String militaryLibraryPath = getMilitaryLibrary(); if (militaryLibraryPath == null) { return null; } // 查询军标对应的类型后缀 String sql = "SELECT military_type FROM military WHERE id = ?"; List params = new ArrayList<>(); params.add(militaryId); Military military = SQLiteUtil.queryForObject(militaryLibraryPath, sql, params, Military.class); // 得到类型ID String militaryType = military.getMilitaryType(); // 拼接完整URL return "http://" + serverHost + ":" + serverPort + "/militaryLibrary/data/military/" + militaryId + "/" + militaryType; } catch (Exception e) { e.printStackTrace(); return null; } } private boolean isMilitaryTypeExist(String militaryPath, String typeId) throws SQLException, IllegalAccessException, InstantiationException { String checkSql = "SELECT id FROM military_type WHERE id = ?"; List params = new ArrayList<>(); params.add(typeId); List existList = SQLiteUtil.queryForList( militaryPath, checkSql, params, MilitaryType.class ); return existList != null && !existList.isEmpty(); } }