From 461da40bed62d13fba9b119a16bc020639d8c2fa Mon Sep 17 00:00:00 2001 From: ZZX9599 <536509593@qq.com> Date: Tue, 30 Sep 2025 09:55:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=89=E7=B1=BBSQLITE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/GraphHopperController.java | 78 -- .../controller/IconLibraryController.java | 675 +++++++++++------- .../controller/MilitaryLibraryController.java | 19 +- .../controller/WebSourceController.java | 28 +- .../com/yj/earth/business/domain/Icon.java | 2 +- .../com/yj/earth/common/util/SQLiteUtil.java | 11 +- src/main/java/com/yj/earth/vo/IconDataVo.java | 12 + src/main/java/com/yj/earth/vo/IconVo.java | 18 + src/main/java/com/yj/earth/vo/MilitaryVo.java | 2 - 9 files changed, 468 insertions(+), 377 deletions(-) create mode 100644 src/main/java/com/yj/earth/vo/IconDataVo.java diff --git a/src/main/java/com/yj/earth/business/controller/GraphHopperController.java b/src/main/java/com/yj/earth/business/controller/GraphHopperController.java index b403055..d903898 100644 --- a/src/main/java/com/yj/earth/business/controller/GraphHopperController.java +++ b/src/main/java/com/yj/earth/business/controller/GraphHopperController.java @@ -56,86 +56,9 @@ public class GraphHopperController { private final AtomicBoolean isLoading = new AtomicBoolean(false); private final AtomicBoolean isLoaded = new AtomicBoolean(false); - @Operation(summary = "获取地图列表") - @CheckAuth - @GetMapping("/list") - public ApiResponse list() { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(FileInfo::getFileSuffix, "pbf"); - return ApiResponse.success(fileInfoService.list(queryWrapper)); - } - - @Operation(summary = "获取地图列表(网页版)") - @CheckAuth - @GetMapping("/web/list") - public ApiResponse webList() { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(WebSource::getType, "pbf"); - return ApiResponse.success(webSourceService.list(queryWrapper)); - } - @Operation(summary = "加载地图数据") @PostMapping("/loadMap") @CheckAuth - public ApiResponse loadMap(@Parameter(description = "文件ID") @RequestParam String fileId) { - // 参数校验 - if (fileId == null) { - return ApiResponse.failure("文件ID不能为空"); - } - - // 获取并校验OSM文件 - String osmFilePath = fileInfoService.getFileAbsolutePath(fileId); - File osmFile = new File(osmFilePath); - if (!osmFile.exists()) { - return ApiResponse.failure("地图文件不存在: " + osmFilePath); - } - 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(osmFilePath); - // 关键步骤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 = "加载地图数据(网页版)") - @PostMapping("/web/loadMap") - @CheckAuth public ApiResponse webLoadMap(@Parameter(description = "文件路径") @RequestParam String path) { File osmFile = new File(path); if (!osmFile.exists()) { @@ -175,7 +98,6 @@ public class GraphHopperController { isLoaded.set(false); e.printStackTrace(); log.error("地图加载失败: " + e.getMessage()); - } finally { // 无论成功/失败、释放加载锁 isLoading.set(false); diff --git a/src/main/java/com/yj/earth/business/controller/IconLibraryController.java b/src/main/java/com/yj/earth/business/controller/IconLibraryController.java index b19e50f..f1d1889 100644 --- a/src/main/java/com/yj/earth/business/controller/IconLibraryController.java +++ b/src/main/java/com/yj/earth/business/controller/IconLibraryController.java @@ -6,19 +6,24 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.yj.earth.annotation.CheckAuth; import com.yj.earth.business.domain.IconLibrary; import com.yj.earth.business.domain.IconType; -import com.yj.earth.business.service.FileInfoService; import com.yj.earth.business.service.IconLibraryService; 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.iconLibrary.AddIconTypeDto; import com.yj.earth.dto.iconLibrary.CreateIconLibraryDto; import com.yj.earth.dto.iconLibrary.DragIconTypeDto; import com.yj.earth.dto.iconLibrary.UpdateIconTypeNameDto; +import com.yj.earth.vo.IconDataVo; import com.yj.earth.vo.IconTypeVo; import com.yj.earth.vo.IconVo; 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.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; @@ -26,9 +31,12 @@ 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.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,47 +46,52 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/iconLibrary") public class IconLibraryController { + @Resource private IconLibraryService iconLibraryService; - @Resource - private FileInfoService fileInfoService; @Operation(summary = "创建图标库") @PostMapping("/createIconLibrary") - public ApiResponse createIconLibrary(@RequestBody CreateIconLibraryDto createIconLibraryDto) { + public ApiResponse createIconLibrary(@RequestBody CreateIconLibraryDto createDto) { try { // 参数校验与路径处理 - String folderPath = createIconLibraryDto.getPath(); - String iconName = createIconLibraryDto.getName(); + 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 iconFile = new File(parentDir, iconName); + File iconFile = new File(parentDir, libraryName); String iconPath = iconFile.getAbsolutePath().replace("\\", "/"); - // 检查父目录(不存在则创建) + // 父目录不存在则创建 if (!parentDir.exists()) { boolean mkdirsSuccess = parentDir.mkdirs(); if (!mkdirsSuccess) { - return ApiResponse.failure("创建图标库父目录失败:" + folderPath); + return ApiResponse.failure("创建父目录失败:" + folderPath); } } - // 检查图标库文件是否已存在 + // 校验图标库文件是否已存在 if (iconFile.exists()) { if (iconFile.isDirectory()) { - return ApiResponse.failure("同名目录已存在、无法创建图标库文件:" + iconPath); + return ApiResponse.failure("同名目录已存在,无法创建文件:" + iconPath); } return ApiResponse.failure("图标库文件已存在:" + iconPath); } - // 创建图标库文件 - boolean createSuccess = iconFile.createNewFile(); - if (!createSuccess) { + // 创建SQLite文件 + boolean createFileSuccess = iconFile.createNewFile(); + if (!createFileSuccess) { return ApiResponse.failure("创建图标库文件失败:" + iconPath); } - // 新增图标库记录并初始化SQLite表结构 - addIconLibrary(iconPath); + // 初始化图标库表结构 SQLiteUtil.initializationIcon(iconPath); + // 添加图标库配置到数据库 + addIconLibrary(iconPath); return ApiResponse.success(null); } catch (Exception e) { return ApiResponse.failure("创建图标库失败:" + e.getMessage()); @@ -87,311 +100,441 @@ public class IconLibraryController { @Operation(summary = "导入图标库") @PostMapping("/importIconLibrary") - public ApiResponse importIconLibrary(@RequestParam("iconPath") @Parameter(description = "图标库路径") String iconPath) { - addIconLibrary(iconPath); - return ApiResponse.success(null); + public ApiResponse importIconLibrary( + @RequestParam("iconPath") @Parameter(description = "图标库SQLite文件路径") String iconPath) { + try { + // 校验路径是否存在 + File iconFile = new File(iconPath); + if (!iconFile.exists() || !iconFile.isFile()) { + return ApiResponse.failure("图标库文件不存在:" + iconPath); + } + // 添加到配置表并启用 + addIconLibrary(iconPath); + return ApiResponse.success(null); + } catch (Exception e) { + return ApiResponse.failure("导入图标库失败:" + e.getMessage()); + } } @Operation(summary = "添加图标类型") @PostMapping("/addIconType") - public ApiResponse addIconType(@RequestBody AddIconTypeDto addIconTypeDto) throws SQLException, IllegalAccessException, InstantiationException { + public ApiResponse addIconType(@RequestBody AddIconTypeDto addDto) throws SQLException, IllegalAccessException, InstantiationException { + // 获取当前启用的图标库路径 String iconPath = getIconLibrary(); if (iconPath == null) { return ApiResponse.failure("请先创建或导入图标库"); } - // 检查父级图标类型是否存在 - String parentId = addIconTypeDto.getParentId(); - if (parentId != null) { - String sql = "SELECT * FROM icon_type WHERE id = ?"; - List params = new ArrayList<>(); - params.add(parentId); - IconType iconType = SQLiteUtil.queryForObject(iconPath, sql, params, IconType.class); - if (iconType == null) { - return ApiResponse.failure("父级图标类型不存在"); + // 校验父级类型(若有) + String parentId = addDto.getParentId(); + if (StringUtils.hasText(parentId)) { + String checkParentSql = "SELECT id FROM icon_type WHERE id = ?"; + List parentParams = new ArrayList<>(); + parentParams.add(parentId); + IconType parentType = SQLiteUtil.queryForObject( + iconPath, checkParentSql, parentParams, IconType.class + ); + if (parentType == null) { + return ApiResponse.failure("父级图标类型不存在:" + parentId); } } // 插入图标类型 - String sql = "INSERT INTO icon_type " + + String insertSql = "INSERT INTO icon_type " + "(id, name, parent_id, tree_index, created_at) " + "VALUES (?, ?, ?, ?, ?)"; List params = new ArrayList<>(); params.add(UUID.fastUUID().toString(true)); - params.add(addIconTypeDto.getName()); - params.add(addIconTypeDto.getParentId()); + params.add(addDto.getName()); + params.add(parentId); params.add(0); - params.add(LocalDateTime.now()); - SQLiteUtil.executeUpdate(iconPath, sql, params); - return ApiResponse.success(null); - } - - @Operation(summary = "删除图标类型") - @PostMapping("/deleteIconType") - public ApiResponse deleteIconType(@Parameter(description = "图标类型ID") @RequestParam("iconTypeId") String iconTypeId) throws SQLException { - String iconPath = getIconLibrary(); - if (iconPath == null) { - return ApiResponse.failure("请先创建或导入图标库"); - } - - // 删除图标类型 - String sql = "DELETE FROM icon_type WHERE id = ?"; - List params = new ArrayList<>(); - params.add(iconTypeId); - SQLiteUtil.executeUpdate(iconPath, sql, params); + params.add(LocalDateTime.now().toString()); + SQLiteUtil.executeUpdate(iconPath, insertSql, params); return ApiResponse.success(null); } @Operation(summary = "修改图标类型名称") @PostMapping("/updateIconTypeName") - public ApiResponse updateIconTypeName(@RequestBody UpdateIconTypeNameDto updateIconTypeNameDto) throws SQLException { - String iconPath = getIconLibrary(); - if (iconPath == null) { - return ApiResponse.failure("请先创建或导入图标库"); - } - - // 更新图标类型名称 - String sql = "UPDATE icon_type SET name = ? WHERE id = ?"; - List params = new ArrayList<>(); - params.add(updateIconTypeNameDto.getName()); - params.add(updateIconTypeNameDto.getId()); - SQLiteUtil.executeUpdate(iconPath, sql, params); - return ApiResponse.success(null); - } - - @Operation(summary = "图标类型列表") - @GetMapping("/iconTypeTree") - public ApiResponse iconTypeTree() throws SQLException, IllegalAccessException, InstantiationException { - String iconPath = getIconLibrary(); - if (iconPath == null) { - return ApiResponse.failure("请先创建或导入图标库"); - } - - // 查询所有图标类型 - String sql = """ - SELECT id, name, parent_id as parentId, - tree_index as treeIndex, created_at as createdAt, - updated_at as updatedAt FROM icon_type ORDER BY tree_index ASC - """; - List iconTypes = SQLiteUtil.queryForList(iconPath, sql, null, IconType.class); - - // 构建树形结构 - List treeList = buildIconTypeTree(iconTypes); - return ApiResponse.success(treeList); - } - - @Operation(summary = "拖动图标类型树") - @PostMapping("/dragIconType") - public ApiResponse dragIconType(@RequestBody DragIconTypeDto dragIconTypeDto) throws SQLException { - String iconPath = getIconLibrary(); - if (iconPath == null) { - return ApiResponse.failure("请先创建或导入图标库"); - } - - // 动态构建更新SQL - List updateFields = new ArrayList<>(); - List params = new ArrayList<>(); - - if (dragIconTypeDto.getParentId() != null) { - updateFields.add("parent_id = ?"); - params.add(dragIconTypeDto.getParentId()); - } - if (dragIconTypeDto.getTreeIndex() != null) { - updateFields.add("tree_index = ?"); - params.add(dragIconTypeDto.getTreeIndex()); - } - - String sql = "UPDATE icon_type SET " + String.join(", ", updateFields) + " WHERE id = ?"; - params.add(dragIconTypeDto.getId()); - SQLiteUtil.executeUpdate(iconPath, sql, params); - return ApiResponse.success(null); - } - - @Operation(summary = "添加图标文件") - @PostMapping("/addIconFile") - public ApiResponse addIconFile(@RequestParam("files") MultipartFile[] files, - @Parameter(description = "图标类型ID") @RequestParam("iconTypeId") String iconTypeId) - throws IOException, SQLException { - String iconPath = getIconLibrary(); - if (iconPath == 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); - - // 上传文件并获取访问URL - String url = fileInfoService.uploadWithPreview(file); - - // 插入图标记录 - String sql = "INSERT INTO icon " + - "(id, icon_type_id, icon_name, icon_type, data, created_at) " + - "VALUES (?, ?, ?, ?, ?, ?)"; - List params = new ArrayList<>(); - params.add(UUID.fastUUID().toString(true)); - params.add(iconTypeId); - params.add(fileNameWithoutSuffix); - params.add(fileSuffix); - params.add(url); - params.add(LocalDateTime.now()); - - SQLiteUtil.executeUpdate(iconPath, sql, params); - } - - return ApiResponse.success(null); - } - - @Operation(summary = "根据图标类型查看图标列表") - @PostMapping("/iconList") - public ApiResponse iconList(@Parameter(description = "图标类型ID") @RequestParam("iconTypeId") String iconTypeId) - throws SQLException, IllegalAccessException, InstantiationException { - String iconPath = getIconLibrary(); - if (iconPath == null) { - return ApiResponse.failure("请先创建或导入图标库"); - } - - // 多表联查图标数据 - String sql = """ - SELECT - icon.id, - icon.icon_type_id as iconTypeId, - icon.icon_name as iconName, - icon.icon_type as iconType, - icon.data, - - icon.view, - icon.created_at as createdAt, - icon.updated_at as updatedAt, - icon_type.name as iconTypeName - FROM icon - JOIN icon_type ON icon.icon_type_id = icon_type.id - WHERE icon.icon_type_id = ? - """; - List params = new ArrayList<>(); - params.add(iconTypeId); - List iconVos = SQLiteUtil.queryForList(iconPath, sql, params, IconVo.class); - return ApiResponse.success(iconVos); - } - - @Operation(summary = "更新图标信息") - @PostMapping("/updateIconInfo") - public ApiResponse updateIconInfo(@Parameter(description = "图标ID") @RequestParam("iconId") String iconId, - @Parameter(description = "图标名称") @RequestParam(value = "iconName", required = false) String iconName) + public ApiResponse updateIconTypeName(@RequestBody UpdateIconTypeNameDto updateDto) throws SQLException { String iconPath = getIconLibrary(); if (iconPath == null) { return ApiResponse.failure("请先创建或导入图标库"); } - // 无更新字段直接返回成功 - if (!StringUtils.hasText(iconName)) { - return ApiResponse.success(null); - } - - // 更新图标名称 - String sql = "UPDATE icon SET updated_at = ?, icon_name = ? WHERE id = ?"; + // 执行更新 + String updateSql = "UPDATE icon_type SET name = ?, updated_at = ? WHERE id = ?"; List params = new ArrayList<>(); - params.add(LocalDateTime.now()); - params.add(iconName); - params.add(iconId); - SQLiteUtil.executeUpdate(iconPath, sql, params); + params.add(updateDto.getName()); + params.add(LocalDateTime.now().toString()); + params.add(updateDto.getId()); + SQLiteUtil.executeUpdate(iconPath, updateSql, params); return ApiResponse.success(null); } - @Operation(summary = "删除图标") - @PostMapping("/deleteIcon") - public ApiResponse deleteIcon(@Parameter(description = "图标ID") @RequestParam("iconId") String iconId) throws SQLException { + @Operation(summary = "删除图标类型") + @PostMapping("/deleteIconType") + public ApiResponse deleteIconType( + @RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) + throws SQLException { String iconPath = getIconLibrary(); if (iconPath == null) { return ApiResponse.failure("请先创建或导入图标库"); } - // 删除图标 - String sql = "DELETE FROM icon WHERE id = ?"; + // 执行删除 + String deleteSql = "DELETE FROM icon_type WHERE id = ?"; List params = new ArrayList<>(); - params.add(iconId); - SQLiteUtil.executeUpdate(iconPath, sql, params); + params.add(typeId); + + SQLiteUtil.executeUpdate(iconPath, deleteSql, params); return ApiResponse.success(null); } - /** - * 构建图标类型树形结构 - * - * @param iconTypes 图标类型列表 - * @return 树形结构列表 - */ - private List buildIconTypeTree(List iconTypes) { - // 转换为VO列表 - List treeNodes = iconTypes.stream() - .map(IconTypeVo::new) - .collect(Collectors.toList()); - - // 构建ID到VO的映射(便于快速查找父节点) - Map nodeMap = treeNodes.stream() - .collect(Collectors.toMap(IconTypeVo::getId, node -> node)); - - // 组装树形结构 - List rootNodes = new ArrayList<>(); - for (IconTypeVo node : treeNodes) { - String parentId = node.getParentId(); - if (parentId == null || parentId.isEmpty()) { - // 无父节点 → 根节点 - rootNodes.add(node); - } else { - // 有父节点 → 加入父节点的子列表 - IconTypeVo parentNode = nodeMap.get(parentId); - if (parentNode != null) { - parentNode.getChildren().add(node); - } - } - } - return rootNodes; + @Operation(summary = "图标类型树形列表") + @GetMapping("/iconTypeTree") + public ApiResponse iconTypeTree() throws SQLException, IllegalAccessException, InstantiationException { + List treeList = iconTypeList(); + return ApiResponse.success(treeList); + } + + @Operation(summary = "添加图标文件") + @PostMapping("/addIconFile") + public ApiResponse addIconFile(@RequestParam("files") MultipartFile[] files, @RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) throws IOException, SQLException, IllegalAccessException, InstantiationException { + // 获取当前启用的图标库 + String iconPath = getIconLibrary(); + if (iconPath == null) { + return ApiResponse.failure("请先创建或导入图标库"); + } + + // 校验类型是否存在 + if (!isIconTypeExist(iconPath, typeId)) { + return ApiResponse.failure("图标类型不存在:" + typeId); + } + + // 循环处理每个文件 + for (MultipartFile file : files) { + if (file.isEmpty()) { + continue; // 跳过空文件 + } + + // 解析文件名与后缀 + String originalFileName = file.getOriginalFilename(); + if (originalFileName == null) { + continue; + } + String fileSuffix = FileUtil.extName(originalFileName); + String fileNameWithoutSuffix = FileUtil.mainName(originalFileName); + + // 插入图标数据 + String insertSql = "INSERT INTO icon " + + "(id, icon_type_id, icon_name, icon_type, icon_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(file.getBytes()); + params.add(LocalDateTime.now().toString()); + + SQLiteUtil.executeUpdate(iconPath, insertSql, params); + } + + return ApiResponse.success(null); + } + + @Operation(summary = "获取图标文件数据") + @GetMapping("/data/icon/{iconId}/{fileSuffix}") + public ResponseEntity getIconData(@PathVariable("iconId") @Parameter(description = "图标ID") String iconId, @PathVariable("fileSuffix") @Parameter(description = "图标文件后缀") String fileSuffix) { + try { + String iconPath = getIconLibrary(); + if (iconPath == null) { + return ResponseEntity.notFound().build(); + } + // 查询图标二进制数据 + String querySql = "SELECT icon_name, icon_data FROM icon WHERE id = ?"; + List params = new ArrayList<>(); + params.add(iconId); + IconDataVo dataVo = SQLiteUtil.queryForObject( + iconPath, querySql, params, IconDataVo.class + ); + if (dataVo == null || dataVo.getIconData() == null) { + return ResponseEntity.notFound().build(); + } + // 构建响应头【支持中文文件名+预览】 + String originalFileName = dataVo.getIconName() + "." + 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.getIconData()); + } catch (Exception e) { + e.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @Operation(summary = "根据类型查询图标列表") + @PostMapping("/iconList") + public ApiResponse getIconList(@RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) throws SQLException, IllegalAccessException, InstantiationException { + String iconPath = getIconLibrary(); + if (iconPath == null) { + return ApiResponse.failure("请先创建或导入图标库"); + } + + // 获取当前类型及所有子类型ID(递归) + List typeIdList = getIconTypeIdsWithChildren(typeId); + if (typeIdList == null || typeIdList.isEmpty()) { + return ApiResponse.success(new ArrayList<>()); + } + + // 构建IN条件(处理SQL注入风险) + String idsWithQuotes = typeIdList.stream() + .map(id -> "'" + id + "'") + .collect(Collectors.joining(",")); + + // 多表联查 + String querySql = """ + SELECT + i.id, + i.icon_type_id as iconTypeId, + i.icon_name as iconName, + i.icon_type as iconType, + i.created_at as createdAt, + i.updated_at as updatedAt, + t.name as iconTypeName + FROM icon i + JOIN icon_type t ON i.icon_type_id = t.id + WHERE i.icon_type_id IN (%s) + ORDER BY i.created_at DESC + """.replace("%s", idsWithQuotes); + + // 查询并转换为VO + List iconVoList = SQLiteUtil.queryForList( + iconPath, querySql, null, IconVo.class + ); + for (IconVo vo : iconVoList) { + vo.setIconDataUrl("/iconLibrary/data/icon/" + vo.getId() + "/" + vo.getIconType()); + } + return ApiResponse.success(iconVoList); + } + + @Operation(summary = "更新图标名称") + @PostMapping("/updateIconInfo") + public ApiResponse updateIconInfo( + @RequestParam("iconId") @Parameter(description = "图标ID") String iconId, + @RequestParam("iconName") @Parameter(description = "新图标名称") String iconName) + throws SQLException { + String iconPath = getIconLibrary(); + if (iconPath == null) { + return ApiResponse.failure("请先创建或导入图标库"); + } + String updateSql = "UPDATE icon SET icon_name = ?, updated_at = ? WHERE id = ?"; + List params = new ArrayList<>(); + params.add(iconName); + params.add(LocalDateTime.now().toString()); + params.add(iconId); + SQLiteUtil.executeUpdate(iconPath, updateSql, params); + return ApiResponse.success(null); + } + + @Operation(summary = "删除图标") + @PostMapping("/deleteIcon") + public ApiResponse deleteIcon(@RequestParam("iconId") @Parameter(description = "图标ID") String iconId) throws SQLException { + String iconPath = getIconLibrary(); + if (iconPath == null) { + return ApiResponse.failure("请先创建或导入图标库"); + } + + // 执行删除 + String deleteSql = "DELETE FROM icon WHERE id = ?"; + List params = new ArrayList<>(); + params.add(iconId); + + SQLiteUtil.executeUpdate(iconPath, deleteSql, params); + return ApiResponse.success("删除图标成功"); + } + + + @Operation(summary = "拖动调整图标类型层级") + @PostMapping("/dragIconType") + public ApiResponse dragIconType(@RequestBody List dragDtoList) + throws SQLException, IllegalAccessException, InstantiationException { + String iconPath = getIconLibrary(); + if (iconPath == null) { + return ApiResponse.failure("请先创建或导入图标库"); + } + + // 批量更新层级和排序 + for (DragIconTypeDto dto : dragDtoList) { + String updateSql = "UPDATE icon_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(iconPath, updateSql, params); + } + + // 返回更新后的树形列表 + List treeList = iconTypeList(); + return ApiResponse.success(treeList); } - /** - * 获取当前启用的图标库路径 - * - * @return 图标库路径(null表示无启用的图标库) - */ private String getIconLibrary() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(IconLibrary::getIsEnable, 1); - IconLibrary iconLibrary = iconLibraryService.getOne(queryWrapper); - return iconLibrary == null ? null : iconLibrary.getPath(); + queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用,0=未启用 + IconLibrary library = iconLibraryService.getOne(queryWrapper); + return library == null ? null : library.getPath(); } private void addIconLibrary(String iconPath) { - // 查询所有图标库并置为禁用 - List iconLibraries = iconLibraryService.list(); - for (IconLibrary iconLibrary : iconLibraries) { - iconLibrary.setIsEnable(0); - iconLibraryService.updateById(iconLibrary); + // 所有已存在的图标库设置为「未启用」 + List existLibraries = iconLibraryService.list(); + for (IconLibrary library : existLibraries) { + library.setIsEnable(0); + iconLibraryService.updateById(library); } - // 检查路径是否已存在(存在则启用、不存在则新增) + // 检查路径是否已存在(存在则启用,不存在则新增) LambdaQueryWrapper pathWrapper = new LambdaQueryWrapper<>(); pathWrapper.eq(IconLibrary::getPath, iconPath); - IconLibrary existingIconLib = iconLibraryService.getOne(pathWrapper); + IconLibrary existLibrary = iconLibraryService.getOne(pathWrapper); - if (existingIconLib != null) { - existingIconLib.setIsEnable(1); - iconLibraryService.updateById(existingIconLib); + if (existLibrary != null) { + existLibrary.setIsEnable(1); + iconLibraryService.updateById(existLibrary); } else { - // 新增图标库记录 - IconLibrary newIconLib = new IconLibrary(); - File iconFile = FileUtil.file(iconPath); - newIconLib.setId(UUID.fastUUID().toString(true)); - newIconLib.setPath(iconPath); - newIconLib.setName(FileUtil.extName(iconFile)); - newIconLib.setIsEnable(1); - iconLibraryService.save(newIconLib); + IconLibrary newLibrary = new IconLibrary(); + newLibrary.setId(UUID.fastUUID().toString(true)); + newLibrary.setPath(iconPath); + newLibrary.setName(FileUtil.mainName(iconPath)); + newLibrary.setIsEnable(1); + newLibrary.setCreatedAt(LocalDateTime.now()); + iconLibraryService.save(newLibrary); } } + + private List iconTypeList() throws SQLException, IllegalAccessException, InstantiationException { + String iconPath = getIconLibrary(); + if (iconPath == null) { + return new ArrayList<>(); + } + + String querySql = """ + SELECT + id, name, parent_id as parentId, + tree_index as treeIndex, created_at as createdAt, + updated_at as updatedAt + FROM icon_type + ORDER BY tree_index ASC + """; + List typeList = SQLiteUtil.queryForList(iconPath, querySql, null, IconType.class); + + // 构建树形结构 + return buildIconTypeTree(typeList); + } + + private List buildIconTypeTree(List typeList) { + // 转换为VO + List voList = typeList.stream() + .map(iconType -> new IconTypeVo(iconType)).collect(Collectors.toList()); + + // 构建ID→VO的映射(方便快速查找父级) + Map voMap = voList.stream() + .collect(Collectors.toMap(IconTypeVo::getId, vo -> vo)); + + // 组装树形结构 + List rootList = new ArrayList<>(); + for (IconTypeVo vo : voList) { + String parentId = vo.getParentId(); + if (parentId == null || parentId.isEmpty()) { + rootList.add(vo); + } else { + IconTypeVo parentVo = voMap.get(parentId); + if (parentVo != null) { + parentVo.getChildren().add(vo); + } + } + } + + // 排序 + rootList.sort(Comparator.comparingInt(IconTypeVo::getTreeIndex)); + sortIconTypeChildren(rootList); + + return rootList; + } + + private void sortIconTypeChildren(List voList) { + if (voList == null || voList.isEmpty()) { + return; + } + // 排序当前层级 + voList.sort(Comparator.comparingInt(IconTypeVo::getTreeIndex)); + // 递归排序子层级 + for (IconTypeVo vo : voList) { + sortIconTypeChildren(vo.getChildren()); + } + } + + private List getIconTypeIdsWithChildren(String typeId) + throws SQLException, IllegalAccessException, InstantiationException { + List idList = new ArrayList<>(); + if (!StringUtils.hasText(typeId)) { + return idList; + } + + String iconPath = getIconLibrary(); + if (iconPath == null) { + return idList; + } + + // 校验类型是否存在 + if (!isIconTypeExist(iconPath, typeId)) { + return idList; + } + + // 递归收集ID(包含自身) + recursiveGetIconTypeChildren(iconPath, typeId, idList); + return idList; + } + + private void recursiveGetIconTypeChildren(String iconPath, String currentId, List idList) + throws SQLException, IllegalAccessException, InstantiationException { + // 先添加当前ID + idList.add(currentId); + + // 查询直接子类型 + String querySql = "SELECT id FROM icon_type WHERE parent_id = ? ORDER BY tree_index ASC"; + List params = new ArrayList<>(); + params.add(currentId); + List childList = SQLiteUtil.queryForList( + iconPath, querySql, params, IconType.class + ); + + // 递归查询子类型的子类型 + if (childList != null && !childList.isEmpty()) { + for (IconType child : childList) { + recursiveGetIconTypeChildren(iconPath, child.getId(), idList); + } + } + } + + private boolean isIconTypeExist(String iconPath, String typeId) + throws SQLException, IllegalAccessException, InstantiationException { + String checkSql = "SELECT id FROM icon_type WHERE id = ?"; + List params = new ArrayList<>(); + params.add(typeId); + List existList = SQLiteUtil.queryForList( + iconPath, checkSql, params, IconType.class + ); + return existList != null && !existList.isEmpty(); + } } diff --git a/src/main/java/com/yj/earth/business/controller/MilitaryLibraryController.java b/src/main/java/com/yj/earth/business/controller/MilitaryLibraryController.java index 6909a67..ed3cf58 100644 --- a/src/main/java/com/yj/earth/business/controller/MilitaryLibraryController.java +++ b/src/main/java/com/yj/earth/business/controller/MilitaryLibraryController.java @@ -4,20 +4,19 @@ 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.MilitaryLibrary; // 需自定义:对应军标库配置表(同ModelLibrary结构) -import com.yj.earth.business.domain.MilitaryType; // 需自定义:对应military_type表实体 -import com.yj.earth.business.service.FileInfoService; -import com.yj.earth.business.service.MilitaryLibraryService; // 需自定义:军标库Service(同ModelLibraryService逻辑) +import com.yj.earth.business.domain.MilitaryLibrary; +import com.yj.earth.business.domain.MilitaryType; +import com.yj.earth.business.service.MilitaryLibraryService; 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.AddMilitaryTypeDto; // 需自定义:添加军标类型DTO -import com.yj.earth.dto.militaryLibrary.CreateMilitaryLibraryDto; // 需自定义:创建军标库DTO -import com.yj.earth.dto.militaryLibrary.DragMilitaryTypeDto; // 需自定义:拖动军标类型DTO -import com.yj.earth.dto.militaryLibrary.UpdateMilitaryTypeNameDto; // 需自定义:修改军标类型名称DTO +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.vo.MilitaryDataVo; -import com.yj.earth.vo.MilitaryTypeVo; // 需自定义:军标类型树形VO(同ModelTypeVo结构) -import com.yj.earth.vo.MilitaryVo; // 需自定义:军标列表VO(剔除海报相关字段) +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; diff --git a/src/main/java/com/yj/earth/business/controller/WebSourceController.java b/src/main/java/com/yj/earth/business/controller/WebSourceController.java index 5205edd..2898d9c 100644 --- a/src/main/java/com/yj/earth/business/controller/WebSourceController.java +++ b/src/main/java/com/yj/earth/business/controller/WebSourceController.java @@ -36,7 +36,7 @@ public class WebSourceController { private WebSourceService webSourceService; @Resource private SourceService sourceService; - private static final List SUPPORT_EXTENSIONS = Arrays.asList("clt", "mbtiles", "pak", "pbf"); + private static final List SUPPORT_EXTENSIONS = Arrays.asList("clt", "mbtiles", "pak", "pbf", "model"); @RoleAccess(roleNames = "管理员") @Operation(summary = "同步数据") @@ -60,6 +60,18 @@ public class WebSourceController { } } + @Operation(summary = "获取所有文件") + @GetMapping("/list") + public ApiResponse list(@RequestParam(required = false) @Parameter(description = "文件类型") String type) { + if (type != null) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(WebSource::getType, type); + return ApiResponse.success(webSourceService.list(queryWrapper)); + } + return ApiResponse.success(webSourceService.list()); + } + + /** * 若文件路径不存在则保存到数据库 */ @@ -78,7 +90,8 @@ public class WebSourceController { webSource.setPath(filePath); webSourceService.save(webSource); - if (!webSource.getType().equals("pbf")) { + // 如果不是地图文件和模型文件、则调用 SDK 进行模型处理 + if (!webSource.getType().equals("pbf") && !webSource.getType().equals("model")) { addModelSourceIfNotExists(filePath); } } @@ -93,17 +106,6 @@ public class WebSourceController { sourceService.addModelSource(addModelSourceDto); } - @Operation(summary = "获取所有文件") - @GetMapping("/list") - public ApiResponse list(@RequestParam(required = false) @Parameter(description = "文件类型") String type) { - if (type != null) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(WebSource::getType, type); - return ApiResponse.success(webSourceService.list(queryWrapper)); - } - return ApiResponse.success(webSourceService.list()); - } - /** * 获取指定目录下特定扩展名的文件 */ diff --git a/src/main/java/com/yj/earth/business/domain/Icon.java b/src/main/java/com/yj/earth/business/domain/Icon.java index a25cf26..ce4f3b9 100644 --- a/src/main/java/com/yj/earth/business/domain/Icon.java +++ b/src/main/java/com/yj/earth/business/domain/Icon.java @@ -20,7 +20,7 @@ public class Icon { @Schema(description = "图标类型") private String iconType; @Schema(description = "图标数据") - private String data; + private byte[] data; @Schema(description = "图标视图") private String view; @Schema(description = "创建时间") diff --git a/src/main/java/com/yj/earth/common/util/SQLiteUtil.java b/src/main/java/com/yj/earth/common/util/SQLiteUtil.java index 525dfdd..9d1a815 100644 --- a/src/main/java/com/yj/earth/common/util/SQLiteUtil.java +++ b/src/main/java/com/yj/earth/common/util/SQLiteUtil.java @@ -555,10 +555,8 @@ public class SQLiteUtil { } - // ========================== 初始化表结构方法(保留原逻辑) ========================== - /** - * 初始化模型相关表(model_type + model) + * 初始化模型相关表 */ public static void initializationModel(String modelPath) throws SQLException { String sql = """ @@ -592,7 +590,7 @@ public class SQLiteUtil { } /** - * 初始化军标相关表(military_type + military) + * 初始化军标相关表 */ public static void initializationMilitary(String militaryPath) throws SQLException { String sql = """ @@ -624,7 +622,7 @@ public class SQLiteUtil { } /** - * 初始化图标相关表(icon_type + icon) + * 初始化图标相关表 */ public static void initializationIcon(String iconPath) throws SQLException { String sql = """ @@ -646,8 +644,7 @@ public class SQLiteUtil { "icon_type_id" TEXT, "icon_name" TEXT, "icon_type" TEXT, - "data" TEXT, - "view" TEXT, + "icon_data" BLOB, "created_at" TEXT, "updated_at" TEXT, PRIMARY KEY ("id") diff --git a/src/main/java/com/yj/earth/vo/IconDataVo.java b/src/main/java/com/yj/earth/vo/IconDataVo.java new file mode 100644 index 0000000..72a8b70 --- /dev/null +++ b/src/main/java/com/yj/earth/vo/IconDataVo.java @@ -0,0 +1,12 @@ +package com.yj.earth.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class IconDataVo { + @Schema(description = "军标名称") + private String iconName; + @Schema(description = "军标数据") + private byte[] iconData; +} diff --git a/src/main/java/com/yj/earth/vo/IconVo.java b/src/main/java/com/yj/earth/vo/IconVo.java index c31ccd1..2e75ad2 100644 --- a/src/main/java/com/yj/earth/vo/IconVo.java +++ b/src/main/java/com/yj/earth/vo/IconVo.java @@ -2,9 +2,27 @@ package com.yj.earth.vo; import com.yj.earth.business.domain.Icon; import com.yj.earth.business.domain.Military; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.time.LocalDateTime; + @Data public class IconVo extends Icon { + @Schema(description = "图标类型名称") private String iconTypeName; + @Schema(description = "图标数据URL") + private String iconDataUrl; + @Schema(description = "主键") + private String id; + @Schema(description = "图标类型ID") + private String iconTypeId; + @Schema(description = "图标名称") + private String iconName; + @Schema(description = "图标类型") + private String iconType; + @Schema(description = "创建时间") + private LocalDateTime createdAt; + @Schema(description = "更新时间") + private LocalDateTime updatedAt; } diff --git a/src/main/java/com/yj/earth/vo/MilitaryVo.java b/src/main/java/com/yj/earth/vo/MilitaryVo.java index ebd5695..ae5e70c 100644 --- a/src/main/java/com/yj/earth/vo/MilitaryVo.java +++ b/src/main/java/com/yj/earth/vo/MilitaryVo.java @@ -21,8 +21,6 @@ public class MilitaryVo extends Military { private String militaryName; @Schema(description = "军标类型") private String militaryType; - @Schema(description = "军标数据") - private byte[] militaryData; @Schema(description = "创建时间") private LocalDateTime createdAt; @Schema(description = "更新时间")