Files
yjearth/src/main/java/com/yj/earth/business/controller/IconLibraryController.java
2025-09-30 10:14:40 +08:00

541 lines
21 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
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;
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;
@Tag(name = "图标数据管理")
@CheckAuth
@RestController
@RequestMapping("/iconLibrary")
public class IconLibraryController {
@Resource
private IconLibraryService iconLibraryService;
@Operation(summary = "创建图标库")
@PostMapping("/createIconLibrary")
public ApiResponse createIconLibrary(@RequestBody CreateIconLibraryDto 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 iconFile = new File(parentDir, libraryName);
String iconPath = iconFile.getAbsolutePath().replace("\\", "/");
// 父目录不存在则创建
if (!parentDir.exists()) {
boolean mkdirsSuccess = parentDir.mkdirs();
if (!mkdirsSuccess) {
return ApiResponse.failure("创建父目录失败:" + folderPath);
}
}
// 校验图标库文件是否已存在
if (iconFile.exists()) {
if (iconFile.isDirectory()) {
return ApiResponse.failure("同名目录已存在,无法创建文件:" + iconPath);
}
return ApiResponse.failure("图标库文件已存在:" + iconPath);
}
// 创建SQLite文件
boolean createFileSuccess = iconFile.createNewFile();
if (!createFileSuccess) {
return ApiResponse.failure("创建图标库文件失败:" + iconPath);
}
// 初始化图标库表结构
SQLiteUtil.initializationIcon(iconPath);
// 添加图标库配置到数据库
addIconLibrary(iconPath);
return ApiResponse.success(null);
} catch (Exception e) {
return ApiResponse.failure("创建图标库失败:" + e.getMessage());
}
}
@Operation(summary = "导入图标库")
@PostMapping("/importIconLibrary")
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 addDto) throws SQLException, IllegalAccessException, InstantiationException {
// 获取当前启用的图标库路径
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
// 校验父级类型(若有)
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);
}
}
// 插入图标类型
String insertSql = "INSERT INTO icon_type " +
"(id, name, parent_id, tree_index, created_at) " +
"VALUES (?, ?, ?, ?, ?)";
List<Object> 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(iconPath, insertSql, params);
return ApiResponse.success(null);
}
@Operation(summary = "修改图标类型名称")
@PostMapping("/updateIconTypeName")
public ApiResponse updateIconTypeName(@RequestBody UpdateIconTypeNameDto updateDto)
throws SQLException {
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
// 执行更新
String updateSql = "UPDATE icon_type SET name = ?, updated_at = ? WHERE id = ?";
List<Object> params = new ArrayList<>();
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("/deleteIconType")
public ApiResponse deleteIconType(
@RequestParam("iconTypeId") @Parameter(description = "图标类型ID") String typeId)
throws SQLException {
String iconPath = getIconLibrary();
if (iconPath == null) {
return ApiResponse.failure("请先创建或导入图标库");
}
// 执行删除
String deleteSql = "DELETE FROM icon_type WHERE id = ?";
List<Object> params = new ArrayList<>();
params.add(typeId);
SQLiteUtil.executeUpdate(iconPath, deleteSql, params);
return ApiResponse.success(null);
}
@Operation(summary = "图标类型树形列表")
@GetMapping("/iconTypeTree")
public ApiResponse iconTypeTree() throws SQLException, IllegalAccessException, InstantiationException {
List<IconTypeVo> 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<Object> 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<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 = "根据类型查询图标列表")
@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<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);
}
@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<Object> 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<Object> params = new ArrayList<>();
params.add(iconId);
SQLiteUtil.executeUpdate(iconPath, deleteSql, params);
return ApiResponse.success("删除图标成功");
}
@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);
}
// 返回更新后的树形列表
List<IconTypeVo> treeList = iconTypeList();
return ApiResponse.success(treeList);
}
private String getIconLibrary() {
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用0=未启用
IconLibrary library = iconLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath();
}
private void addIconLibrary(String iconPath) {
// 所有已存在的图标库设置为「未启用」
List<IconLibrary> existLibraries = iconLibraryService.list();
for (IconLibrary library : existLibraries) {
library.setIsEnable(0);
iconLibraryService.updateById(library);
}
// 检查路径是否已存在(存在则启用,不存在则新增)
LambdaQueryWrapper<IconLibrary> pathWrapper = new LambdaQueryWrapper<>();
pathWrapper.eq(IconLibrary::getPath, iconPath);
IconLibrary existLibrary = iconLibraryService.getOne(pathWrapper);
if (existLibrary != null) {
existLibrary.setIsEnable(1);
iconLibraryService.updateById(existLibrary);
} else {
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;
}
// 递归收集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();
}
}