From 5f6887bb7affa493e94d3e13387beaa52bce943b Mon Sep 17 00:00:00 2001 From: wangqian <1406304305@qq.com> Date: Fri, 22 May 2026 16:58:27 +0800 Subject: [PATCH] 险情管理导出图片,用户管理导入调整 --- urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java | 25 +++++++++++++++++++++++++ urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java | 24 +++++++++++++++++++++--- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/emergencytask/vo/EmergencyTaskExcleRespVO.java | 13 +++++++++---- urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/UserController.java | 31 +++++++++++++++++++++++++++---- urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/vo/user/UserImportExcelVO.java | 20 ++++++++++++++------ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java | 2 ++ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java | 26 ++++++++++++++++++++++++++ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java | 6 +++--- 12 files changed, 548 insertions(+), 20 deletions(-) create mode 100644 urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java create mode 100644 urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java create mode 100644 urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java create mode 100644 urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java create mode 100644 urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java create mode 100644 urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java diff --git a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java new file mode 100644 index 0000000..4781cd0 --- /dev/null +++ b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java @@ -0,0 +1,112 @@ +package com.zteits.urbanops.framework.excel.core.convert; + +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BusinessLineConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + /** + * 导入:业务线名称(园林,市政) → 编码(yl,sz) + */ + @Override + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + String cellValue = readCellData.getStringValue(); + if (cellValue == null || cellValue.isBlank()) { + return null; + } + + try { + // 按逗号切割 + String[] lineArray = cellValue.split(","); + StringBuilder codeSb = new StringBuilder(); + + for (String line : lineArray) { + String code = convertLineToCode(line.trim()); + if (codeSb.length() > 0) { + codeSb.append(","); + } + codeSb.append(code); + } + + return codeSb.toString(); + } catch (Exception e) { + log.error("[convertToJavaData][业务线转换异常] {}", cellValue, e); + return null; + } + } + + /** + * 导出:业务线编码(yl,sz) → 名称(园林,市政) + */ + @Override + public WriteCellData convertToExcelData(Object value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (value == null) { + return new WriteCellData<>(""); + } + + try { + String codeValue = String.valueOf(value); + String[] codeArray = codeValue.split(","); + StringBuilder nameSb = new StringBuilder(); + + for (String code : codeArray) { + String name = convertCodeToLine(code.trim()); + if (nameSb.length() > 0) { + nameSb.append(","); + } + nameSb.append(name); + } + + return new WriteCellData<>(nameSb.toString()); + } catch (Exception e) { + log.error("[convertToExcelData][业务线转换异常] {}", value, e); + return new WriteCellData<>(""); + } + } + + /** + * 名称 → 编码 + */ + private String convertLineToCode(String line) { + return switch (line) { + case "园林" -> "yl"; + case "市政" -> "sz"; + case "物业" -> "wy"; + case "秩序管理" -> "zx"; + case "环境卫生" -> "hj"; + default -> ""; + }; + } + + /** + * 编码 → 名称 + */ + private String convertCodeToLine(String code) { + return switch (code) { + case "yl" -> "园林"; + case "sz" -> "市政"; + case "wy" -> "物业"; + case "zx" -> "秩序管理"; + case "hj" -> "环境卫生"; + default -> ""; + }; + } +} diff --git a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java new file mode 100644 index 0000000..5dedf72 --- /dev/null +++ b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java @@ -0,0 +1,122 @@ +package com.zteits.urbanops.framework.excel.core.convert; + +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ImageData; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.util.FileUtils; +import cn.idev.excel.util.ListUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; + +@Slf4j +public class ImageListConverter implements Converter> { + + // 横向排列:每张图片宽度 + 间距 + private static final int IMAGE_WIDTH = 120; + private static final int IMAGE_GAP = 15; + private static final int MAX_IMAGE_SIZE = 10 * 1024 * 1024; + private static final int CONNECT_TIMEOUT = 5000; + private static final int READ_TIMEOUT = 10000; + + @Override + public Class supportJavaTypeKey() { + return List.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.EMPTY; + } + + @Override + public List convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return null; + } + + @Override + public WriteCellData convertToExcelData(List imageList, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + try { + if (imageList == null || imageList.isEmpty()) { + return new WriteCellData<>("无图片"); + } + + List imageDataList = ListUtils.newArrayList(); + int leftOffset = 0; // 横向偏移量 + + for (String path : imageList) { + if (!StringUtils.hasText(path)) continue; + + byte[] imageBytes = path.startsWith("http") ? loadNetworkImage(path) : loadLocalImage(path); + if (imageBytes == null || imageBytes.length == 0 || imageBytes.length > MAX_IMAGE_SIZE) { + log.warn("跳过无效图片:{}", path); + continue; + } + + ImageData imageData = new ImageData(); + imageData.setImage(imageBytes); + imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG); + + // ====================== 核心:横向偏移 ====================== + imageData.setLeft(leftOffset); + // ============================================================= + + imageDataList.add(imageData); + leftOffset += IMAGE_WIDTH + IMAGE_GAP; + } + + if (imageDataList.isEmpty()) { + return new WriteCellData<>("无有效图片"); + } + + WriteCellData writeCellData = new WriteCellData<>(); + writeCellData.setType(CellDataTypeEnum.EMPTY); + writeCellData.setImageDataList(imageDataList); + + return writeCellData; + + } catch (Exception e) { + log.error("图片导出失败", e); + return new WriteCellData<>("图片加载失败"); + } + } + + private byte[] loadLocalImage(String imagePath) throws Exception { + File file = new File(imagePath); + if (!file.exists() || !file.isFile()) { + log.warn("本地图片不存在:{}", imagePath); + return null; + } + return FileUtils.readFileToByteArray(file); + } + + private byte[] loadNetworkImage(String imageUrl) throws Exception { + URL url = new URL(imageUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(CONNECT_TIMEOUT); + conn.setReadTimeout(READ_TIMEOUT); + try { + if (conn.getResponseCode() != 200) return null; + try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buffer = new byte[4096]; + int len; + while ((len = in.read(buffer)) != -1) out.write(buffer, 0, len); + return out.toByteArray(); + } + } finally { + conn.disconnect(); + } + } +} diff --git a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java new file mode 100644 index 0000000..c8c49ae --- /dev/null +++ b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java @@ -0,0 +1,25 @@ +package com.zteits.urbanops.framework.excel.core.handler; + +import cn.idev.excel.write.handler.SheetWriteHandler; +import cn.idev.excel.write.handler.context.SheetWriteHandlerContext; +import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * 图片列自动加宽处理器 + */ +@RequiredArgsConstructor +public class ImageColumnWidthHandler implements SheetWriteHandler { + + // 需要加宽的列索引(从0开始),比如你的图片列在第3列,就传2 + private final int imageColumnIndex; + // 加宽后的列宽(单位:字符宽度,Excel默认1个字符宽度≈256像素) + private final int columnWidth; + + @Override + public void afterSheetCreate(SheetWriteHandlerContext context) { + Sheet sheet = context.getWriteSheetHolder().getSheet(); + // 设置指定列的宽度 + sheet.setColumnWidth(imageColumnIndex, columnWidth); + } +} diff --git a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java index 84dfded..7a02cdf 100644 --- a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java +++ b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java @@ -44,9 +44,27 @@ public class ExcelUtils { } public static List read(MultipartFile file, Class head) throws IOException { - return FastExcelFactory.read(file.getInputStream(), head, null) - .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 - .doReadAllSync(); + List list = FastExcelFactory.read(file.getInputStream(), head, null) + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 + .doReadAllSync(); + + // 过滤:对象不为 null + 不是全字段 null + if (list != null) { + list.removeIf(item -> { + if (item == null) return true; + // 判断所有字段是否都为 null + for (java.lang.reflect.Field field : item.getClass().getDeclaredFields()) { + field.setAccessible(true); + try { + if (field.get(item) != null) { + return false; + } + } catch (Exception e) { } + } + return true; + }); + } + return list; } public static List read(MultipartFile file, Class head, Integer startIndex) throws IOException { diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/emergencytask/vo/EmergencyTaskExcleRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/emergencytask/vo/EmergencyTaskExcleRespVO.java index 022500e..41cf6d2 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/emergencytask/vo/EmergencyTaskExcleRespVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/emergencytask/vo/EmergencyTaskExcleRespVO.java @@ -2,8 +2,9 @@ package com.zteits.urbanops.module.garden.controller.admin.emergencytask.vo; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.write.style.ColumnWidth; +import com.zteits.urbanops.framework.excel.core.convert.ImageListConverter; import com.zteits.urbanops.framework.excel.core.convert.ListToStringConverter; -import com.zteits.urbanops.module.garden.dal.dataobject.emergencytask.EmergencyMachineryDO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -52,7 +53,9 @@ public class EmergencyTaskExcleRespVO { private LocalDateTime rescueStartTime; @Schema(description = "抢险中照片", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty(value="抢险中照片", converter = ListToStringConverter.class) + //@ExcelProperty(value="抢险中照片", converter = ListToStringConverter.class) + @ExcelProperty(value = "抢险中照片", converter = ImageListConverter.class) + @ColumnWidth(70) private List emergencyImg; @Schema(description = "树种(如:国槐)") @@ -72,7 +75,9 @@ public class EmergencyTaskExcleRespVO { private Integer personnelCount; @Schema(description = "抢险结束照片", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty(value="抢险结束照片", converter = ListToStringConverter.class) + //@ExcelProperty(value="抢险结束照片", converter = ListToStringConverter.class) + @ExcelProperty(value = "抢险结束照片", converter = ImageListConverter.class) + @ColumnWidth(70) private List emergencyEndImg; @Schema(description = "备注") @@ -111,4 +116,4 @@ public class EmergencyTaskExcleRespVO { @ExcelProperty("归属公司") private String companyName; -} \ No newline at end of file +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/UserController.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/UserController.java index 2e2481e..6351b25 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/UserController.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/UserController.java @@ -18,11 +18,13 @@ import com.zteits.urbanops.module.system.service.dept.DeptService; import com.zteits.urbanops.module.system.service.permission.PermissionService; import com.zteits.urbanops.module.system.service.permission.RoleService; import com.zteits.urbanops.module.system.service.user.AdminUserService; +import com.zteits.urbanops.module.system.util.dept.DeptCacheHelper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; @@ -40,6 +42,7 @@ import java.util.stream.Collectors; import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; import static com.zteits.urbanops.framework.common.util.collection.CollectionUtils.convertList; +import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getDeptId; @Tag(name = "管理后台 - 用户") @RestController @@ -213,11 +216,31 @@ public class UserController { public void importTemplate(HttpServletResponse response) throws IOException { // 手动创建导出 demo List list = Arrays.asList( - UserImportExcelVO.builder().username("yunai").deptId(1L).email("yunai@iocoder.cn").mobile("15601691300") - .nickname("全域").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(), - UserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300") - .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build() + UserImportExcelVO.builder() + .username("yunai") + .deptId(255L) + .email("yunai@iocoder.cn") + .mobile("15601691300") + .nickname("全域") + // .status(CommonStatusEnum.ENABLE.getStatus()) + .sex(SexEnum.MALE.getSex()) + .busiLine("园林") // 简洁示例 + .isInner(1) + .build(), + UserImportExcelVO.builder() + .username("yuanma") + .deptId(255L) + .email("yuanma@iocoder.cn") + .mobile("15601701300") + .nickname("源码") + //.status(CommonStatusEnum.DISABLE.getStatus()) + .sex(SexEnum.FEMALE.getSex()) + .busiLine("园林,物业") + .isInner(1) + .build() ); + //初始化部门 + DeptCacheHelper.init(deptService, getDeptId()); // 输出 ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list); } diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/vo/user/UserImportExcelVO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/vo/user/UserImportExcelVO.java index db721df..0ddd6c0 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/vo/user/UserImportExcelVO.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/vo/user/UserImportExcelVO.java @@ -2,8 +2,12 @@ package com.zteits.urbanops.module.system.controller.admin.user.vo.user; import cn.idev.excel.annotation.ExcelProperty; import com.zteits.urbanops.framework.excel.core.annotations.DictFormat; +import com.zteits.urbanops.framework.excel.core.annotations.ExcelColumnSelect; import com.zteits.urbanops.framework.excel.core.convert.DictConvert; import com.zteits.urbanops.module.system.enums.DictTypeConstants; +import com.zteits.urbanops.framework.excel.core.convert.BusinessLineConvert; +import com.zteits.urbanops.module.system.framework.excel.convert.DeptConvert; +import com.zteits.urbanops.module.system.framework.excel.core.DeptExcelColumnSelectFunction; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -18,13 +22,14 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class UserImportExcelVO { - @ExcelProperty("登录名称") + @ExcelProperty("用户账户") private String username; @ExcelProperty("用户名称") private String nickname; - @ExcelProperty("部门编号") + @ExcelProperty(value = "部门编号", converter = DeptConvert.class) + @ExcelColumnSelect(functionName = DeptExcelColumnSelectFunction.NAME) private Long deptId; @ExcelProperty("用户邮箱") @@ -37,11 +42,14 @@ public class UserImportExcelVO { @DictFormat(DictTypeConstants.USER_SEX) private Integer sex; - @ExcelProperty(value = "账号状态", converter = DictConvert.class) - @DictFormat(DictTypeConstants.COMMON_STATUS) - private Integer status; + //@ExcelProperty(value = "账号状态", converter = DictConvert.class) + //@DictFormat(DictTypeConstants.COMMON_STATUS) + //private Integer status; - @ExcelProperty("业务线") + @ExcelProperty(value = "业务线", converter = BusinessLineConvert.class) private String busiLine; + @ExcelProperty(value = "是否是内部员工", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SYSTEM_IS_INNER) + private Integer isInner; } diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java index 3a9b504..006c12a 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java @@ -30,6 +30,8 @@ public interface DictTypeConstants { String WORKORDER_TYPE = "work_order_type"; //工单类型 + String SYSTEM_IS_INNER = "system_is_inner";//是否是内部员工 + diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java new file mode 100644 index 0000000..e819e88 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java @@ -0,0 +1,106 @@ +package com.zteits.urbanops.module.system.framework.excel.convert; + +import cn.hutool.core.convert.Convert; +import cn.hutool.extra.spring.SpringUtil; +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; +import com.zteits.urbanops.module.system.service.dept.DeptService; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 若依框架 部门Excel转换器(官方规范版) + * 全程走缓存,绝不重复查询数据库 + * + * @author ruoyi + */ +@Slf4j +public class DeptConvert implements Converter { + + /** + * 若依 部门Service(走系统缓存) + */ + private static final DeptService deptService = SpringUtil.getBean(DeptService.class); + + /** + * 若依系统全局缓存:ID -> 部门名称 + */ + private static final Map DEPT_CACHE; + + /** + * 若依系统全局缓存:名称 -> 部门ID + */ + private static final Map DEPT_NAME_CACHE; + + // ===================== 静态初始化:只加载一次 ===================== + static { + // 从若依缓存获取所有部门(只查一次库,后面全走Redis) + List deptList = deptService.getChildDeptList(0L); + + // ID -> 名称(导出用) + DEPT_CACHE = deptList.stream() + .collect(Collectors.toMap(DeptDO::getId, DeptDO::getName)); + + // 名称 -> ID(导入用) + DEPT_NAME_CACHE = deptList.stream() + .collect(Collectors.toMap(DeptDO::getName, DeptDO::getId)); + + log.info("【若依部门Excel转换器】初始化缓存成功,共 {} 个部门", deptList.size()); + } + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + // ===================== 导入:Excel部门名称 → ID ===================== + @Override + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + String deptName = readCellData.getStringValue(); + + // 从缓存获取(0 查库) + Long deptId = DEPT_NAME_CACHE.get(deptName); + if (deptId == null) { + log.error("[convertToJavaData][部门名称({}) 不存在]", deptName); + return null; + } + + // 类型转换 + Class fieldClazz = contentProperty.getField().getType(); + return Convert.convert(fieldClazz, deptId); + } + + // ===================== 导出:ID → 部门名称 ===================== + @Override + public WriteCellData convertToExcelData(Object value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (value == null) { + return new WriteCellData<>(""); + } + + Long deptId = Convert.toLong(value); + // 从缓存获取名称(0 查库) + String deptName = DEPT_CACHE.get(deptId); + + if (deptName == null) { + log.error("[convertToExcelData][部门ID({}) 不存在]", deptId); + return new WriteCellData<>(""); + } + + return new WriteCellData<>(deptName); + } +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java new file mode 100644 index 0000000..5269dda --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java @@ -0,0 +1,26 @@ +package com.zteits.urbanops.module.system.framework.excel.core; + +import com.zteits.urbanops.framework.excel.core.function.ExcelColumnSelectFunction; +import com.zteits.urbanops.module.system.util.dept.DeptCacheHelper; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +public class DeptExcelColumnSelectFunction implements ExcelColumnSelectFunction { + + public static final String NAME = "deptNameList"; + + @Override + public String getName() { + return NAME; + } + + /** + * 从缓存获取部门名称列表,作为Excel下拉选项 + */ + @Override + public List getOptions() { + return DeptCacheHelper.getNameList(); // 直接走缓存! + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java new file mode 100644 index 0000000..04c3372 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java @@ -0,0 +1,81 @@ +package com.zteits.urbanops.module.system.util.dept; + +import cn.hutool.core.collection.CollUtil; +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; +import com.zteits.urbanops.module.system.service.dept.DeptService; +import lombok.extern.slf4j.Slf4j; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 部门全局缓存工具 + * 给 Excel 导入导出、下拉框、转换器统一使用 + * 只加载一次,不重复查询 + */ +@Slf4j +public class DeptCacheHelper { + + private static List DEPT_LIST; + private static Map DEPT_ID_MAP; + private static Map DEPT_NAME_MAP; + private static List DEPT_NAME_LIST; + + /** + * 初始化部门缓存(在导入模板接口中主动调用) + */ + public static void init(DeptService deptService, Long rootDeptId) { + + //每次调用都使用传入的 deptId,每次可刷新 + if (DEPT_LIST != null) { + // 清空旧缓存 + DEPT_LIST = null; + DEPT_ID_MAP = null; + DEPT_NAME_MAP = null; + DEPT_NAME_LIST = null; + } + + try { + + // 1. 批量查询部门(只查一次) + DEPT_LIST = deptService.getLastDeptList(rootDeptId); + + if (CollUtil.isEmpty(DEPT_LIST)) { + log.warn("[DeptCacheHelper] 未查询到部门数据"); + return; + } + + // 3. 构建各种缓存 + DEPT_ID_MAP = DEPT_LIST.stream() + .collect(Collectors.toMap(DeptDO::getId, Function.identity())); + + DEPT_NAME_MAP = DEPT_LIST.stream() + .collect(Collectors.toMap(DeptDO::getName, Function.identity())); + + DEPT_NAME_LIST = DEPT_LIST.stream() + .map(DeptDO::getName) + .collect(Collectors.toList()); + + log.info("[DeptCacheHelper] 部门缓存初始化成功,共 {} 个部门", DEPT_LIST.size()); + } catch (Exception e) { + log.error("[DeptCacheHelper] 部门缓存初始化失败", e); + } + } + + // ===================== 提供给外部使用 ===================== + public static DeptDO getById(Long id) { + return DEPT_ID_MAP == null ? null : DEPT_ID_MAP.get(id); + } + + public static DeptDO getByName(String name) { + return DEPT_NAME_MAP == null ? null : DEPT_NAME_MAP.get(name); + } + + public static List getNameList() { + return DEPT_NAME_LIST == null ? Collections.emptyList() : DEPT_NAME_LIST; + } + + public static boolean isEmpty() { + return CollUtil.isEmpty(DEPT_LIST); + } +} diff --git a/urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java b/urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java index e45c16e..9717b40 100644 --- a/urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java +++ b/urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java @@ -482,7 +482,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { public void testImportUserList_02() { // 准备参数 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setEmail(randomEmail()); o.setMobile(randomMobile()); @@ -517,7 +517,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { userMapper.insert(dbUser); // 准备参数 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setUsername(dbUser.getUsername()); o.setEmail(randomEmail()); @@ -549,7 +549,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { userMapper.insert(dbUser); // 准备参数 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setUsername(dbUser.getUsername()); o.setEmail(randomEmail()); -- libgit2 0.21.4