Files
yjearth/src/main/java/com/yj/earth/business/controller/IconLibraryController.java

541 lines
21 KiB
Java
Raw Normal View History

2025-09-26 13:46:54 +08:00
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.IconLibrary;
import com.yj.earth.business.domain.IconType;
import com.yj.earth.business.service.IconLibraryService;
import com.yj.earth.common.util.ApiResponse;
2025-09-30 09:55:07 +08:00
import com.yj.earth.common.util.FileCommonUtil;
2025-09-26 13:46:54 +08:00
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;
2025-09-30 09:55:07 +08:00
import com.yj.earth.vo.IconDataVo;
2025-09-26 13:46:54 +08:00
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;
2025-09-30 09:55:07 +08:00
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
2025-09-26 13:46:54 +08:00
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;
2025-09-30 09:55:07 +08:00
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
2025-09-26 13:46:54 +08:00
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
2025-09-30 09:55:07 +08:00
import java.util.Comparator;
2025-09-26 13:46:54 +08:00
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
2025-09-30 10:14:40 +08:00
@Tag(name = "图标数据管理")
2025-09-26 13:46:54 +08:00
@CheckAuth
@RestController
@RequestMapping("/iconLibrary")
public class IconLibraryController {
2025-09-30 09:55:07 +08:00
2025-09-26 13:46:54 +08:00
@Resource
private IconLibraryService iconLibraryService;
@Operation(summary = "创建图标库")
@PostMapping("/createIconLibrary")
2025-09-30 09:55:07 +08:00
public ApiResponse createIconLibrary(@RequestBody CreateIconLibraryDto createDto) {
2025-09-26 13:46:54 +08:00
try {
// 参数校验与路径处理
2025-09-30 09:55:07 +08:00
String folderPath = createDto.getPath();
String libraryName = createDto.getName();
if (!StringUtils.hasText(folderPath) || !StringUtils.hasText(libraryName)) {
return ApiResponse.failure("路径和名称不能为空");
}
// 构建完整图标库文件路径SQLite文件
2025-09-26 13:46:54 +08:00
File parentDir = new File(folderPath);
2025-09-30 09:55:07 +08:00
File iconFile = new File(parentDir, libraryName);
2025-09-26 13:46:54 +08:00
String iconPath = iconFile.getAbsolutePath().replace("\\", "/");
2025-09-30 09:55:07 +08:00
// 父目录不存在则创建
2025-09-26 13:46:54 +08:00
if (!parentDir.exists()) {
boolean mkdirsSuccess = parentDir.mkdirs();
if (!mkdirsSuccess) {
2025-09-30 09:55:07 +08:00
return ApiResponse.failure("创建父目录失败:" + folderPath);
2025-09-26 13:46:54 +08:00
}
}
2025-09-30 09:55:07 +08:00
// 校验图标库文件是否已存在
2025-09-26 13:46:54 +08:00
if (iconFile.exists()) {
if (iconFile.isDirectory()) {
2025-09-30 09:55:07 +08:00
return ApiResponse.failure("同名目录已存在,无法创建文件:" + iconPath);
2025-09-26 13:46:54 +08:00
}
return ApiResponse.failure("图标库文件已存在:" + iconPath);
}
2025-09-30 09:55:07 +08:00
// 创建SQLite文件
boolean createFileSuccess = iconFile.createNewFile();
if (!createFileSuccess) {
2025-09-26 13:46:54 +08:00
return ApiResponse.failure("创建图标库文件失败:" + iconPath);
}
2025-09-30 09:55:07 +08:00
// 初始化图标库表结构
2025-09-26 13:46:54 +08:00
SQLiteUtil.initializationIcon(iconPath);
2025-09-30 09:55:07 +08:00
// 添加图标库配置到数据库
addIconLibrary(iconPath);
2025-09-26 13:46:54 +08:00
return ApiResponse.success(null);
} catch (Exception e) {
return ApiResponse.failure("创建图标库失败:" + e.getMessage());
}
}
@Operation(summary = "导入图标库")
@PostMapping("/importIconLibrary")
2025-09-30 09:55:07 +08:00
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());
}
2025-09-26 13:46:54 +08:00
}
@Operation(summary = "添加图标类型")
@PostMapping("/addIconType")
2025-09-30 09:55:07 +08:00
public ApiResponse addIconType(@RequestBody AddIconTypeDto addDto) throws SQLException, IllegalAccessException, InstantiationException {
// 获取当前启用的图标库路径
2025-09-26 13:46:54 +08:00
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
// 校验父级类型(若有)
String parentId = addDto.getParentId();
if (StringUtils.hasText(parentId)) {
String checkParentSql = "SELECT id FROM icon_type WHERE id = ?";
List<Object> parentParams = new ArrayList<>();
parentParams.add(parentId);
IconType parentType = SQLiteUtil.queryForObject(
iconPath, checkParentSql, parentParams, IconType.class
);
if (parentType == null) {
return ApiResponse.failure("父级图标类型不存在:" + parentId);
2025-09-26 13:46:54 +08:00
}
}
// 插入图标类型
2025-09-30 09:55:07 +08:00
String insertSql = "INSERT INTO icon_type " +
2025-09-26 13:46:54 +08:00
"(id, name, parent_id, tree_index, created_at) " +
"VALUES (?, ?, ?, ?, ?)";
List<Object> params = new ArrayList<>();
params.add(UUID.fastUUID().toString(true));
2025-09-30 09:55:07 +08:00
params.add(addDto.getName());
params.add(parentId);
2025-09-26 13:46:54 +08:00
params.add(0);
2025-09-30 09:55:07 +08:00
params.add(LocalDateTime.now().toString());
SQLiteUtil.executeUpdate(iconPath, insertSql, params);
2025-09-26 13:46:54 +08:00
return ApiResponse.success(null);
}
2025-09-30 09:55:07 +08:00
@Operation(summary = "修改图标类型名称")
@PostMapping("/updateIconTypeName")
public ApiResponse updateIconTypeName(@RequestBody UpdateIconTypeNameDto updateDto)
throws SQLException {
2025-09-26 13:46:54 +08:00
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
// 执行更新
String updateSql = "UPDATE icon_type SET name = ?, updated_at = ? WHERE id = ?";
2025-09-26 13:46:54 +08:00
List<Object> params = new ArrayList<>();
2025-09-30 09:55:07 +08:00
params.add(updateDto.getName());
params.add(LocalDateTime.now().toString());
params.add(updateDto.getId());
SQLiteUtil.executeUpdate(iconPath, updateSql, params);
2025-09-26 13:46:54 +08:00
return ApiResponse.success(null);
}
2025-09-30 09:55:07 +08:00
@Operation(summary = "删除图标类型")
@PostMapping("/deleteIconType")
public ApiResponse deleteIconType(
@RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId)
throws SQLException {
2025-09-26 13:46:54 +08:00
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
// 执行删除
String deleteSql = "DELETE FROM icon_type WHERE id = ?";
2025-09-26 13:46:54 +08:00
List<Object> params = new ArrayList<>();
2025-09-30 09:55:07 +08:00
params.add(typeId);
SQLiteUtil.executeUpdate(iconPath, deleteSql, params);
2025-09-26 13:46:54 +08:00
return ApiResponse.success(null);
}
2025-09-30 09:55:07 +08:00
@Operation(summary = "图标类型树形列表")
2025-09-26 13:46:54 +08:00
@GetMapping("/iconTypeTree")
public ApiResponse iconTypeTree() throws SQLException, IllegalAccessException, InstantiationException {
2025-09-30 09:55:07 +08:00
List<IconTypeVo> treeList = iconTypeList();
2025-09-26 13:46:54 +08:00
return ApiResponse.success(treeList);
}
@Operation(summary = "添加图标文件")
@PostMapping("/addIconFile")
2025-09-30 09:55:07 +08:00
public ApiResponse addIconFile(@RequestParam("files") MultipartFile[] files, @RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) throws IOException, SQLException, IllegalAccessException, InstantiationException {
// 获取当前启用的图标库
2025-09-26 13:46:54 +08:00
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
// 校验类型是否存在
if (!isIconTypeExist(iconPath, typeId)) {
return ApiResponse.failure("图标类型不存在:" + typeId);
}
2025-09-26 13:46:54 +08:00
2025-09-30 09:55:07 +08:00
// 循环处理每个文件
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue; // 跳过空文件
}
2025-09-26 13:46:54 +08:00
2025-09-30 09:55:07 +08:00
// 解析文件名与后缀
String originalFileName = file.getOriginalFilename();
if (originalFileName == null) {
continue;
}
String fileSuffix = FileUtil.extName(originalFileName);
String fileNameWithoutSuffix = FileUtil.mainName(originalFileName);
2025-09-26 13:46:54 +08:00
2025-09-30 09:55:07 +08:00
// 插入图标数据
String insertSql = "INSERT INTO icon " +
"(id, icon_type_id, icon_name, icon_type, icon_data, created_at) " +
2025-09-26 13:46:54 +08:00
"VALUES (?, ?, ?, ?, ?, ?)";
List<Object> params = new ArrayList<>();
params.add(UUID.fastUUID().toString(true));
2025-09-30 09:55:07 +08:00
params.add(typeId);
2025-09-26 13:46:54 +08:00
params.add(fileNameWithoutSuffix);
params.add(fileSuffix);
2025-09-30 09:55:07 +08:00
params.add(file.getBytes());
params.add(LocalDateTime.now().toString());
2025-09-26 13:46:54 +08:00
2025-09-30 09:55:07 +08:00
SQLiteUtil.executeUpdate(iconPath, insertSql, params);
2025-09-26 13:46:54 +08:00
}
return ApiResponse.success(null);
}
2025-09-30 09:55:07 +08:00
@Operation(summary = "获取图标文件数据")
@GetMapping("/data/icon/{iconId}/{fileSuffix}")
public ResponseEntity<byte[]> 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<Object> 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 = "根据类型查询图标列表")
2025-09-26 13:46:54 +08:00
@PostMapping("/iconList")
2025-09-30 09:55:07 +08:00
public ApiResponse getIconList(@RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId) throws SQLException, IllegalAccessException, InstantiationException {
2025-09-26 13:46:54 +08:00
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
// 获取当前类型及所有子类型ID递归
List<String> 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<IconVo> 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);
2025-09-26 13:46:54 +08:00
}
2025-09-30 09:55:07 +08:00
@Operation(summary = "更新图标名称")
2025-09-26 13:46:54 +08:00
@PostMapping("/updateIconInfo")
2025-09-30 09:55:07 +08:00
public ApiResponse updateIconInfo(
@RequestParam("iconId") @Parameter(description = "图标ID") String iconId,
@RequestParam("iconName") @Parameter(description = "新图标名称") String iconName)
2025-09-26 13:46:54 +08:00
throws SQLException {
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
String updateSql = "UPDATE icon SET icon_name = ?, updated_at = ? WHERE id = ?";
2025-09-26 13:46:54 +08:00
List<Object> params = new ArrayList<>();
params.add(iconName);
2025-09-30 09:55:07 +08:00
params.add(LocalDateTime.now().toString());
2025-09-26 13:46:54 +08:00
params.add(iconId);
2025-09-30 09:55:07 +08:00
SQLiteUtil.executeUpdate(iconPath, updateSql, params);
2025-09-26 13:46:54 +08:00
return ApiResponse.success(null);
}
@Operation(summary = "删除图标")
@PostMapping("/deleteIcon")
2025-09-30 09:55:07 +08:00
public ApiResponse deleteIcon(@RequestParam("iconId") @Parameter(description = "图标ID") String iconId) throws SQLException {
2025-09-26 13:46:54 +08:00
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
2025-09-30 09:55:07 +08:00
// 执行删除
String deleteSql = "DELETE FROM icon WHERE id = ?";
2025-09-26 13:46:54 +08:00
List<Object> params = new ArrayList<>();
params.add(iconId);
2025-09-30 09:55:07 +08:00
SQLiteUtil.executeUpdate(iconPath, deleteSql, params);
return ApiResponse.success("删除图标成功");
2025-09-26 13:46:54 +08:00
}
2025-09-30 09:55:07 +08:00
@Operation(summary = "拖动调整图标类型层级")
@PostMapping("/dragIconType")
public ApiResponse dragIconType(@RequestBody List<DragIconTypeDto> 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<Object> 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);
2025-09-26 13:46:54 +08:00
}
2025-09-30 09:55:07 +08:00
// 返回更新后的树形列表
List<IconTypeVo> treeList = iconTypeList();
return ApiResponse.success(treeList);
2025-09-26 13:46:54 +08:00
}
private String getIconLibrary() {
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
2025-09-30 09:55:07 +08:00
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用0=未启用
IconLibrary library = iconLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath();
2025-09-26 13:46:54 +08:00
}
private void addIconLibrary(String iconPath) {
2025-09-30 09:55:07 +08:00
// 所有已存在的图标库设置为「未启用」
List<IconLibrary> existLibraries = iconLibraryService.list();
for (IconLibrary library : existLibraries) {
library.setIsEnable(0);
iconLibraryService.updateById(library);
2025-09-26 13:46:54 +08:00
}
2025-09-30 09:55:07 +08:00
// 检查路径是否已存在(存在则启用,不存在则新增)
2025-09-26 13:46:54 +08:00
LambdaQueryWrapper<IconLibrary> pathWrapper = new LambdaQueryWrapper<>();
pathWrapper.eq(IconLibrary::getPath, iconPath);
2025-09-30 09:55:07 +08:00
IconLibrary existLibrary = iconLibraryService.getOne(pathWrapper);
2025-09-26 13:46:54 +08:00
2025-09-30 09:55:07 +08:00
if (existLibrary != null) {
existLibrary.setIsEnable(1);
iconLibraryService.updateById(existLibrary);
2025-09-26 13:46:54 +08:00
} else {
2025-09-30 09:55:07 +08:00
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<IconTypeVo> 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<IconType> typeList = SQLiteUtil.queryForList(iconPath, querySql, null, IconType.class);
// 构建树形结构
return buildIconTypeTree(typeList);
}
private List<IconTypeVo> buildIconTypeTree(List<IconType> typeList) {
// 转换为VO
List<IconTypeVo> voList = typeList.stream()
.map(iconType -> new IconTypeVo(iconType)).collect(Collectors.toList());
// 构建ID→VO的映射方便快速查找父级
Map<String, IconTypeVo> voMap = voList.stream()
.collect(Collectors.toMap(IconTypeVo::getId, vo -> vo));
// 组装树形结构
List<IconTypeVo> 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<IconTypeVo> voList) {
if (voList == null || voList.isEmpty()) {
return;
}
// 排序当前层级
voList.sort(Comparator.comparingInt(IconTypeVo::getTreeIndex));
// 递归排序子层级
for (IconTypeVo vo : voList) {
sortIconTypeChildren(vo.getChildren());
}
}
private List<String> getIconTypeIdsWithChildren(String typeId)
throws SQLException, IllegalAccessException, InstantiationException {
List<String> idList = new ArrayList<>();
if (!StringUtils.hasText(typeId)) {
return idList;
}
String iconPath = getIconLibrary();
if (iconPath == null) {
return idList;
}
// 校验类型是否存在
if (!isIconTypeExist(iconPath, typeId)) {
return idList;
2025-09-26 13:46:54 +08:00
}
2025-09-30 09:55:07 +08:00
// 递归收集ID包含自身
recursiveGetIconTypeChildren(iconPath, typeId, idList);
return idList;
}
private void recursiveGetIconTypeChildren(String iconPath, String currentId, List<String> 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<Object> params = new ArrayList<>();
params.add(currentId);
List<IconType> 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<Object> params = new ArrayList<>();
params.add(typeId);
List<IconType> existList = SQLiteUtil.queryForList(
iconPath, checkSql, params, IconType.class
);
return existList != null && !existList.isEmpty();
2025-09-26 13:46:54 +08:00
}
}