Commit 5f6887bb7affa493e94d3e13387beaa52bce943b
1 parent
441f44f7
险情管理导出图片,用户管理导入调整
Showing
12 changed files
with
548 additions
and
20 deletions
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java
0 → 100644
| 1 | +package com.zteits.urbanops.framework.excel.core.convert; | |
| 2 | + | |
| 3 | +import cn.idev.excel.converters.Converter; | |
| 4 | +import cn.idev.excel.enums.CellDataTypeEnum; | |
| 5 | +import cn.idev.excel.metadata.GlobalConfiguration; | |
| 6 | +import cn.idev.excel.metadata.data.ReadCellData; | |
| 7 | +import cn.idev.excel.metadata.data.WriteCellData; | |
| 8 | +import cn.idev.excel.metadata.property.ExcelContentProperty; | |
| 9 | +import lombok.extern.slf4j.Slf4j; | |
| 10 | + | |
| 11 | +@Slf4j | |
| 12 | +public class BusinessLineConvert implements Converter<Object> { | |
| 13 | + | |
| 14 | + @Override | |
| 15 | + public Class<?> supportJavaTypeKey() { | |
| 16 | + throw new UnsupportedOperationException("暂不支持,也不需要"); | |
| 17 | + } | |
| 18 | + | |
| 19 | + @Override | |
| 20 | + public CellDataTypeEnum supportExcelTypeKey() { | |
| 21 | + throw new UnsupportedOperationException("暂不支持,也不需要"); | |
| 22 | + } | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 导入:业务线名称(园林,市政) → 编码(yl,sz) | |
| 26 | + */ | |
| 27 | + @Override | |
| 28 | + public Object convertToJavaData(ReadCellData<?> readCellData, ExcelContentProperty contentProperty, | |
| 29 | + GlobalConfiguration globalConfiguration) { | |
| 30 | + String cellValue = readCellData.getStringValue(); | |
| 31 | + if (cellValue == null || cellValue.isBlank()) { | |
| 32 | + return null; | |
| 33 | + } | |
| 34 | + | |
| 35 | + try { | |
| 36 | + // 按逗号切割 | |
| 37 | + String[] lineArray = cellValue.split(","); | |
| 38 | + StringBuilder codeSb = new StringBuilder(); | |
| 39 | + | |
| 40 | + for (String line : lineArray) { | |
| 41 | + String code = convertLineToCode(line.trim()); | |
| 42 | + if (codeSb.length() > 0) { | |
| 43 | + codeSb.append(","); | |
| 44 | + } | |
| 45 | + codeSb.append(code); | |
| 46 | + } | |
| 47 | + | |
| 48 | + return codeSb.toString(); | |
| 49 | + } catch (Exception e) { | |
| 50 | + log.error("[convertToJavaData][业务线转换异常] {}", cellValue, e); | |
| 51 | + return null; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * 导出:业务线编码(yl,sz) → 名称(园林,市政) | |
| 57 | + */ | |
| 58 | + @Override | |
| 59 | + public WriteCellData<?> convertToExcelData(Object value, ExcelContentProperty contentProperty, | |
| 60 | + GlobalConfiguration globalConfiguration) { | |
| 61 | + if (value == null) { | |
| 62 | + return new WriteCellData<>(""); | |
| 63 | + } | |
| 64 | + | |
| 65 | + try { | |
| 66 | + String codeValue = String.valueOf(value); | |
| 67 | + String[] codeArray = codeValue.split(","); | |
| 68 | + StringBuilder nameSb = new StringBuilder(); | |
| 69 | + | |
| 70 | + for (String code : codeArray) { | |
| 71 | + String name = convertCodeToLine(code.trim()); | |
| 72 | + if (nameSb.length() > 0) { | |
| 73 | + nameSb.append(","); | |
| 74 | + } | |
| 75 | + nameSb.append(name); | |
| 76 | + } | |
| 77 | + | |
| 78 | + return new WriteCellData<>(nameSb.toString()); | |
| 79 | + } catch (Exception e) { | |
| 80 | + log.error("[convertToExcelData][业务线转换异常] {}", value, e); | |
| 81 | + return new WriteCellData<>(""); | |
| 82 | + } | |
| 83 | + } | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * 名称 → 编码 | |
| 87 | + */ | |
| 88 | + private String convertLineToCode(String line) { | |
| 89 | + return switch (line) { | |
| 90 | + case "园林" -> "yl"; | |
| 91 | + case "市政" -> "sz"; | |
| 92 | + case "物业" -> "wy"; | |
| 93 | + case "秩序管理" -> "zx"; | |
| 94 | + case "环境卫生" -> "hj"; | |
| 95 | + default -> ""; | |
| 96 | + }; | |
| 97 | + } | |
| 98 | + | |
| 99 | + /** | |
| 100 | + * 编码 → 名称 | |
| 101 | + */ | |
| 102 | + private String convertCodeToLine(String code) { | |
| 103 | + return switch (code) { | |
| 104 | + case "yl" -> "园林"; | |
| 105 | + case "sz" -> "市政"; | |
| 106 | + case "wy" -> "物业"; | |
| 107 | + case "zx" -> "秩序管理"; | |
| 108 | + case "hj" -> "环境卫生"; | |
| 109 | + default -> ""; | |
| 110 | + }; | |
| 111 | + } | |
| 112 | +} | ... | ... |
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java
0 → 100644
| 1 | +package com.zteits.urbanops.framework.excel.core.convert; | |
| 2 | + | |
| 3 | +import cn.idev.excel.converters.Converter; | |
| 4 | +import cn.idev.excel.enums.CellDataTypeEnum; | |
| 5 | +import cn.idev.excel.metadata.GlobalConfiguration; | |
| 6 | +import cn.idev.excel.metadata.data.ImageData; | |
| 7 | +import cn.idev.excel.metadata.data.ReadCellData; | |
| 8 | +import cn.idev.excel.metadata.data.WriteCellData; | |
| 9 | +import cn.idev.excel.metadata.property.ExcelContentProperty; | |
| 10 | +import cn.idev.excel.util.FileUtils; | |
| 11 | +import cn.idev.excel.util.ListUtils; | |
| 12 | +import lombok.extern.slf4j.Slf4j; | |
| 13 | +import org.springframework.util.StringUtils; | |
| 14 | + | |
| 15 | +import java.io.ByteArrayOutputStream; | |
| 16 | +import java.io.File; | |
| 17 | +import java.io.InputStream; | |
| 18 | +import java.net.HttpURLConnection; | |
| 19 | +import java.net.URL; | |
| 20 | +import java.util.List; | |
| 21 | + | |
| 22 | +@Slf4j | |
| 23 | +public class ImageListConverter implements Converter<List<String>> { | |
| 24 | + | |
| 25 | + // 横向排列:每张图片宽度 + 间距 | |
| 26 | + private static final int IMAGE_WIDTH = 120; | |
| 27 | + private static final int IMAGE_GAP = 15; | |
| 28 | + private static final int MAX_IMAGE_SIZE = 10 * 1024 * 1024; | |
| 29 | + private static final int CONNECT_TIMEOUT = 5000; | |
| 30 | + private static final int READ_TIMEOUT = 10000; | |
| 31 | + | |
| 32 | + @Override | |
| 33 | + public Class<?> supportJavaTypeKey() { | |
| 34 | + return List.class; | |
| 35 | + } | |
| 36 | + | |
| 37 | + @Override | |
| 38 | + public CellDataTypeEnum supportExcelTypeKey() { | |
| 39 | + return CellDataTypeEnum.EMPTY; | |
| 40 | + } | |
| 41 | + | |
| 42 | + @Override | |
| 43 | + public List<String> convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, | |
| 44 | + GlobalConfiguration globalConfiguration) { | |
| 45 | + return null; | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public WriteCellData<?> convertToExcelData(List<String> imageList, ExcelContentProperty contentProperty, | |
| 50 | + GlobalConfiguration globalConfiguration) { | |
| 51 | + try { | |
| 52 | + if (imageList == null || imageList.isEmpty()) { | |
| 53 | + return new WriteCellData<>("无图片"); | |
| 54 | + } | |
| 55 | + | |
| 56 | + List<ImageData> imageDataList = ListUtils.newArrayList(); | |
| 57 | + int leftOffset = 0; // 横向偏移量 | |
| 58 | + | |
| 59 | + for (String path : imageList) { | |
| 60 | + if (!StringUtils.hasText(path)) continue; | |
| 61 | + | |
| 62 | + byte[] imageBytes = path.startsWith("http") ? loadNetworkImage(path) : loadLocalImage(path); | |
| 63 | + if (imageBytes == null || imageBytes.length == 0 || imageBytes.length > MAX_IMAGE_SIZE) { | |
| 64 | + log.warn("跳过无效图片:{}", path); | |
| 65 | + continue; | |
| 66 | + } | |
| 67 | + | |
| 68 | + ImageData imageData = new ImageData(); | |
| 69 | + imageData.setImage(imageBytes); | |
| 70 | + imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG); | |
| 71 | + | |
| 72 | + // ====================== 核心:横向偏移 ====================== | |
| 73 | + imageData.setLeft(leftOffset); | |
| 74 | + // ============================================================= | |
| 75 | + | |
| 76 | + imageDataList.add(imageData); | |
| 77 | + leftOffset += IMAGE_WIDTH + IMAGE_GAP; | |
| 78 | + } | |
| 79 | + | |
| 80 | + if (imageDataList.isEmpty()) { | |
| 81 | + return new WriteCellData<>("无有效图片"); | |
| 82 | + } | |
| 83 | + | |
| 84 | + WriteCellData<?> writeCellData = new WriteCellData<>(); | |
| 85 | + writeCellData.setType(CellDataTypeEnum.EMPTY); | |
| 86 | + writeCellData.setImageDataList(imageDataList); | |
| 87 | + | |
| 88 | + return writeCellData; | |
| 89 | + | |
| 90 | + } catch (Exception e) { | |
| 91 | + log.error("图片导出失败", e); | |
| 92 | + return new WriteCellData<>("图片加载失败"); | |
| 93 | + } | |
| 94 | + } | |
| 95 | + | |
| 96 | + private byte[] loadLocalImage(String imagePath) throws Exception { | |
| 97 | + File file = new File(imagePath); | |
| 98 | + if (!file.exists() || !file.isFile()) { | |
| 99 | + log.warn("本地图片不存在:{}", imagePath); | |
| 100 | + return null; | |
| 101 | + } | |
| 102 | + return FileUtils.readFileToByteArray(file); | |
| 103 | + } | |
| 104 | + | |
| 105 | + private byte[] loadNetworkImage(String imageUrl) throws Exception { | |
| 106 | + URL url = new URL(imageUrl); | |
| 107 | + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | |
| 108 | + conn.setConnectTimeout(CONNECT_TIMEOUT); | |
| 109 | + conn.setReadTimeout(READ_TIMEOUT); | |
| 110 | + try { | |
| 111 | + if (conn.getResponseCode() != 200) return null; | |
| 112 | + try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { | |
| 113 | + byte[] buffer = new byte[4096]; | |
| 114 | + int len; | |
| 115 | + while ((len = in.read(buffer)) != -1) out.write(buffer, 0, len); | |
| 116 | + return out.toByteArray(); | |
| 117 | + } | |
| 118 | + } finally { | |
| 119 | + conn.disconnect(); | |
| 120 | + } | |
| 121 | + } | |
| 122 | +} | ... | ... |
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java
0 → 100644
| 1 | +package com.zteits.urbanops.framework.excel.core.handler; | |
| 2 | + | |
| 3 | +import cn.idev.excel.write.handler.SheetWriteHandler; | |
| 4 | +import cn.idev.excel.write.handler.context.SheetWriteHandlerContext; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | +import org.apache.poi.ss.usermodel.Sheet; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 图片列自动加宽处理器 | |
| 10 | + */ | |
| 11 | +@RequiredArgsConstructor | |
| 12 | +public class ImageColumnWidthHandler implements SheetWriteHandler { | |
| 13 | + | |
| 14 | + // 需要加宽的列索引(从0开始),比如你的图片列在第3列,就传2 | |
| 15 | + private final int imageColumnIndex; | |
| 16 | + // 加宽后的列宽(单位:字符宽度,Excel默认1个字符宽度≈256像素) | |
| 17 | + private final int columnWidth; | |
| 18 | + | |
| 19 | + @Override | |
| 20 | + public void afterSheetCreate(SheetWriteHandlerContext context) { | |
| 21 | + Sheet sheet = context.getWriteSheetHolder().getSheet(); | |
| 22 | + // 设置指定列的宽度 | |
| 23 | + sheet.setColumnWidth(imageColumnIndex, columnWidth); | |
| 24 | + } | |
| 25 | +} | ... | ... |
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 { |
| 44 | 44 | } |
| 45 | 45 | |
| 46 | 46 | public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException { |
| 47 | - return FastExcelFactory.read(file.getInputStream(), head, null) | |
| 48 | - .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 | |
| 49 | - .doReadAllSync(); | |
| 47 | + List<T> list = FastExcelFactory.read(file.getInputStream(), head, null) | |
| 48 | + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 | |
| 49 | + .doReadAllSync(); | |
| 50 | + | |
| 51 | + // 过滤:对象不为 null + 不是全字段 null | |
| 52 | + if (list != null) { | |
| 53 | + list.removeIf(item -> { | |
| 54 | + if (item == null) return true; | |
| 55 | + // 判断所有字段是否都为 null | |
| 56 | + for (java.lang.reflect.Field field : item.getClass().getDeclaredFields()) { | |
| 57 | + field.setAccessible(true); | |
| 58 | + try { | |
| 59 | + if (field.get(item) != null) { | |
| 60 | + return false; | |
| 61 | + } | |
| 62 | + } catch (Exception e) { } | |
| 63 | + } | |
| 64 | + return true; | |
| 65 | + }); | |
| 66 | + } | |
| 67 | + return list; | |
| 50 | 68 | } |
| 51 | 69 | |
| 52 | 70 | public static <T> List<T> read(MultipartFile file, Class<T> head, Integer startIndex) throws IOException { | ... | ... |
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; |
| 2 | 2 | |
| 3 | 3 | import cn.idev.excel.annotation.ExcelIgnoreUnannotated; |
| 4 | 4 | import cn.idev.excel.annotation.ExcelProperty; |
| 5 | +import cn.idev.excel.annotation.write.style.ColumnWidth; | |
| 6 | +import com.zteits.urbanops.framework.excel.core.convert.ImageListConverter; | |
| 5 | 7 | import com.zteits.urbanops.framework.excel.core.convert.ListToStringConverter; |
| 6 | -import com.zteits.urbanops.module.garden.dal.dataobject.emergencytask.EmergencyMachineryDO; | |
| 7 | 8 | import io.swagger.v3.oas.annotations.media.Schema; |
| 8 | 9 | import lombok.Data; |
| 9 | 10 | |
| ... | ... | @@ -52,7 +53,9 @@ public class EmergencyTaskExcleRespVO { |
| 52 | 53 | private LocalDateTime rescueStartTime; |
| 53 | 54 | |
| 54 | 55 | @Schema(description = "抢险中照片", requiredMode = Schema.RequiredMode.REQUIRED) |
| 55 | - @ExcelProperty(value="抢险中照片", converter = ListToStringConverter.class) | |
| 56 | + //@ExcelProperty(value="抢险中照片", converter = ListToStringConverter.class) | |
| 57 | + @ExcelProperty(value = "抢险中照片", converter = ImageListConverter.class) | |
| 58 | + @ColumnWidth(70) | |
| 56 | 59 | private List<String> emergencyImg; |
| 57 | 60 | |
| 58 | 61 | @Schema(description = "树种(如:国槐)") |
| ... | ... | @@ -72,7 +75,9 @@ public class EmergencyTaskExcleRespVO { |
| 72 | 75 | private Integer personnelCount; |
| 73 | 76 | |
| 74 | 77 | @Schema(description = "抢险结束照片", requiredMode = Schema.RequiredMode.REQUIRED) |
| 75 | - @ExcelProperty(value="抢险结束照片", converter = ListToStringConverter.class) | |
| 78 | + //@ExcelProperty(value="抢险结束照片", converter = ListToStringConverter.class) | |
| 79 | + @ExcelProperty(value = "抢险结束照片", converter = ImageListConverter.class) | |
| 80 | + @ColumnWidth(70) | |
| 76 | 81 | private List<String> emergencyEndImg; |
| 77 | 82 | |
| 78 | 83 | @Schema(description = "备注") |
| ... | ... | @@ -111,4 +116,4 @@ public class EmergencyTaskExcleRespVO { |
| 111 | 116 | @ExcelProperty("归属公司") |
| 112 | 117 | private String companyName; |
| 113 | 118 | |
| 114 | -} | |
| 115 | 119 | \ No newline at end of file |
| 120 | +} | ... | ... |
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; |
| 18 | 18 | import com.zteits.urbanops.module.system.service.permission.PermissionService; |
| 19 | 19 | import com.zteits.urbanops.module.system.service.permission.RoleService; |
| 20 | 20 | import com.zteits.urbanops.module.system.service.user.AdminUserService; |
| 21 | +import com.zteits.urbanops.module.system.util.dept.DeptCacheHelper; | |
| 21 | 22 | import io.swagger.v3.oas.annotations.Operation; |
| 22 | 23 | import io.swagger.v3.oas.annotations.Parameter; |
| 23 | 24 | import io.swagger.v3.oas.annotations.Parameters; |
| 24 | 25 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 25 | 26 | import jakarta.annotation.Resource; |
| 27 | +import jakarta.annotation.security.PermitAll; | |
| 26 | 28 | import jakarta.servlet.http.HttpServletResponse; |
| 27 | 29 | import jakarta.validation.Valid; |
| 28 | 30 | import org.springframework.security.access.prepost.PreAuthorize; |
| ... | ... | @@ -40,6 +42,7 @@ import java.util.stream.Collectors; |
| 40 | 42 | import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.EXPORT; |
| 41 | 43 | import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; |
| 42 | 44 | import static com.zteits.urbanops.framework.common.util.collection.CollectionUtils.convertList; |
| 45 | +import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getDeptId; | |
| 43 | 46 | |
| 44 | 47 | @Tag(name = "管理后台 - 用户") |
| 45 | 48 | @RestController |
| ... | ... | @@ -213,11 +216,31 @@ public class UserController { |
| 213 | 216 | public void importTemplate(HttpServletResponse response) throws IOException { |
| 214 | 217 | // 手动创建导出 demo |
| 215 | 218 | List<UserImportExcelVO> list = Arrays.asList( |
| 216 | - UserImportExcelVO.builder().username("yunai").deptId(1L).email("yunai@iocoder.cn").mobile("15601691300") | |
| 217 | - .nickname("全域").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(), | |
| 218 | - UserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300") | |
| 219 | - .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build() | |
| 219 | + UserImportExcelVO.builder() | |
| 220 | + .username("yunai") | |
| 221 | + .deptId(255L) | |
| 222 | + .email("yunai@iocoder.cn") | |
| 223 | + .mobile("15601691300") | |
| 224 | + .nickname("全域") | |
| 225 | + // .status(CommonStatusEnum.ENABLE.getStatus()) | |
| 226 | + .sex(SexEnum.MALE.getSex()) | |
| 227 | + .busiLine("园林") // 简洁示例 | |
| 228 | + .isInner(1) | |
| 229 | + .build(), | |
| 230 | + UserImportExcelVO.builder() | |
| 231 | + .username("yuanma") | |
| 232 | + .deptId(255L) | |
| 233 | + .email("yuanma@iocoder.cn") | |
| 234 | + .mobile("15601701300") | |
| 235 | + .nickname("源码") | |
| 236 | + //.status(CommonStatusEnum.DISABLE.getStatus()) | |
| 237 | + .sex(SexEnum.FEMALE.getSex()) | |
| 238 | + .busiLine("园林,物业") | |
| 239 | + .isInner(1) | |
| 240 | + .build() | |
| 220 | 241 | ); |
| 242 | + //初始化部门 | |
| 243 | + DeptCacheHelper.init(deptService, getDeptId()); | |
| 221 | 244 | // 输出 |
| 222 | 245 | ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list); |
| 223 | 246 | } | ... | ... |
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; |
| 2 | 2 | |
| 3 | 3 | import cn.idev.excel.annotation.ExcelProperty; |
| 4 | 4 | import com.zteits.urbanops.framework.excel.core.annotations.DictFormat; |
| 5 | +import com.zteits.urbanops.framework.excel.core.annotations.ExcelColumnSelect; | |
| 5 | 6 | import com.zteits.urbanops.framework.excel.core.convert.DictConvert; |
| 6 | 7 | import com.zteits.urbanops.module.system.enums.DictTypeConstants; |
| 8 | +import com.zteits.urbanops.framework.excel.core.convert.BusinessLineConvert; | |
| 9 | +import com.zteits.urbanops.module.system.framework.excel.convert.DeptConvert; | |
| 10 | +import com.zteits.urbanops.module.system.framework.excel.core.DeptExcelColumnSelectFunction; | |
| 7 | 11 | import lombok.AllArgsConstructor; |
| 8 | 12 | import lombok.Builder; |
| 9 | 13 | import lombok.Data; |
| ... | ... | @@ -18,13 +22,14 @@ import lombok.NoArgsConstructor; |
| 18 | 22 | @NoArgsConstructor |
| 19 | 23 | public class UserImportExcelVO { |
| 20 | 24 | |
| 21 | - @ExcelProperty("登录名称") | |
| 25 | + @ExcelProperty("用户账户") | |
| 22 | 26 | private String username; |
| 23 | 27 | |
| 24 | 28 | @ExcelProperty("用户名称") |
| 25 | 29 | private String nickname; |
| 26 | 30 | |
| 27 | - @ExcelProperty("部门编号") | |
| 31 | + @ExcelProperty(value = "部门编号", converter = DeptConvert.class) | |
| 32 | + @ExcelColumnSelect(functionName = DeptExcelColumnSelectFunction.NAME) | |
| 28 | 33 | private Long deptId; |
| 29 | 34 | |
| 30 | 35 | @ExcelProperty("用户邮箱") |
| ... | ... | @@ -37,11 +42,14 @@ public class UserImportExcelVO { |
| 37 | 42 | @DictFormat(DictTypeConstants.USER_SEX) |
| 38 | 43 | private Integer sex; |
| 39 | 44 | |
| 40 | - @ExcelProperty(value = "账号状态", converter = DictConvert.class) | |
| 41 | - @DictFormat(DictTypeConstants.COMMON_STATUS) | |
| 42 | - private Integer status; | |
| 45 | + //@ExcelProperty(value = "账号状态", converter = DictConvert.class) | |
| 46 | + //@DictFormat(DictTypeConstants.COMMON_STATUS) | |
| 47 | + //private Integer status; | |
| 43 | 48 | |
| 44 | - @ExcelProperty("业务线") | |
| 49 | + @ExcelProperty(value = "业务线", converter = BusinessLineConvert.class) | |
| 45 | 50 | private String busiLine; |
| 46 | 51 | |
| 52 | + @ExcelProperty(value = "是否是内部员工", converter = DictConvert.class) | |
| 53 | + @DictFormat(DictTypeConstants.SYSTEM_IS_INNER) | |
| 54 | + private Integer isInner; | |
| 47 | 55 | } | ... | ... |
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.system.framework.excel.convert; | |
| 2 | + | |
| 3 | +import cn.hutool.core.convert.Convert; | |
| 4 | +import cn.hutool.extra.spring.SpringUtil; | |
| 5 | +import cn.idev.excel.converters.Converter; | |
| 6 | +import cn.idev.excel.enums.CellDataTypeEnum; | |
| 7 | +import cn.idev.excel.metadata.GlobalConfiguration; | |
| 8 | +import cn.idev.excel.metadata.data.ReadCellData; | |
| 9 | +import cn.idev.excel.metadata.data.WriteCellData; | |
| 10 | +import cn.idev.excel.metadata.property.ExcelContentProperty; | |
| 11 | +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; | |
| 12 | +import com.zteits.urbanops.module.system.service.dept.DeptService; | |
| 13 | +import lombok.extern.slf4j.Slf4j; | |
| 14 | + | |
| 15 | +import java.util.List; | |
| 16 | +import java.util.Map; | |
| 17 | +import java.util.stream.Collectors; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * 若依框架 部门Excel转换器(官方规范版) | |
| 21 | + * 全程走缓存,绝不重复查询数据库 | |
| 22 | + * | |
| 23 | + * @author ruoyi | |
| 24 | + */ | |
| 25 | +@Slf4j | |
| 26 | +public class DeptConvert implements Converter<Object> { | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 若依 部门Service(走系统缓存) | |
| 30 | + */ | |
| 31 | + private static final DeptService deptService = SpringUtil.getBean(DeptService.class); | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 若依系统全局缓存:ID -> 部门名称 | |
| 35 | + */ | |
| 36 | + private static final Map<Long, String> DEPT_CACHE; | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * 若依系统全局缓存:名称 -> 部门ID | |
| 40 | + */ | |
| 41 | + private static final Map<String, Long> DEPT_NAME_CACHE; | |
| 42 | + | |
| 43 | + // ===================== 静态初始化:只加载一次 ===================== | |
| 44 | + static { | |
| 45 | + // 从若依缓存获取所有部门(只查一次库,后面全走Redis) | |
| 46 | + List<DeptDO> deptList = deptService.getChildDeptList(0L); | |
| 47 | + | |
| 48 | + // ID -> 名称(导出用) | |
| 49 | + DEPT_CACHE = deptList.stream() | |
| 50 | + .collect(Collectors.toMap(DeptDO::getId, DeptDO::getName)); | |
| 51 | + | |
| 52 | + // 名称 -> ID(导入用) | |
| 53 | + DEPT_NAME_CACHE = deptList.stream() | |
| 54 | + .collect(Collectors.toMap(DeptDO::getName, DeptDO::getId)); | |
| 55 | + | |
| 56 | + log.info("【若依部门Excel转换器】初始化缓存成功,共 {} 个部门", deptList.size()); | |
| 57 | + } | |
| 58 | + | |
| 59 | + @Override | |
| 60 | + public Class<?> supportJavaTypeKey() { | |
| 61 | + throw new UnsupportedOperationException("暂不支持,也不需要"); | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + public CellDataTypeEnum supportExcelTypeKey() { | |
| 66 | + throw new UnsupportedOperationException("暂不支持,也不需要"); | |
| 67 | + } | |
| 68 | + | |
| 69 | + // ===================== 导入:Excel部门名称 → ID ===================== | |
| 70 | + @Override | |
| 71 | + public Object convertToJavaData(ReadCellData<?> readCellData, ExcelContentProperty contentProperty, | |
| 72 | + GlobalConfiguration globalConfiguration) { | |
| 73 | + String deptName = readCellData.getStringValue(); | |
| 74 | + | |
| 75 | + // 从缓存获取(0 查库) | |
| 76 | + Long deptId = DEPT_NAME_CACHE.get(deptName); | |
| 77 | + if (deptId == null) { | |
| 78 | + log.error("[convertToJavaData][部门名称({}) 不存在]", deptName); | |
| 79 | + return null; | |
| 80 | + } | |
| 81 | + | |
| 82 | + // 类型转换 | |
| 83 | + Class<?> fieldClazz = contentProperty.getField().getType(); | |
| 84 | + return Convert.convert(fieldClazz, deptId); | |
| 85 | + } | |
| 86 | + | |
| 87 | + // ===================== 导出:ID → 部门名称 ===================== | |
| 88 | + @Override | |
| 89 | + public WriteCellData<?> convertToExcelData(Object value, ExcelContentProperty contentProperty, | |
| 90 | + GlobalConfiguration globalConfiguration) { | |
| 91 | + if (value == null) { | |
| 92 | + return new WriteCellData<>(""); | |
| 93 | + } | |
| 94 | + | |
| 95 | + Long deptId = Convert.toLong(value); | |
| 96 | + // 从缓存获取名称(0 查库) | |
| 97 | + String deptName = DEPT_CACHE.get(deptId); | |
| 98 | + | |
| 99 | + if (deptName == null) { | |
| 100 | + log.error("[convertToExcelData][部门ID({}) 不存在]", deptId); | |
| 101 | + return new WriteCellData<>(""); | |
| 102 | + } | |
| 103 | + | |
| 104 | + return new WriteCellData<>(deptName); | |
| 105 | + } | |
| 106 | +} | ... | ... |
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.system.framework.excel.core; | |
| 2 | + | |
| 3 | +import com.zteits.urbanops.framework.excel.core.function.ExcelColumnSelectFunction; | |
| 4 | +import com.zteits.urbanops.module.system.util.dept.DeptCacheHelper; | |
| 5 | +import org.springframework.stereotype.Service; | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +@Service | |
| 9 | +public class DeptExcelColumnSelectFunction implements ExcelColumnSelectFunction { | |
| 10 | + | |
| 11 | + public static final String NAME = "deptNameList"; | |
| 12 | + | |
| 13 | + @Override | |
| 14 | + public String getName() { | |
| 15 | + return NAME; | |
| 16 | + } | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 从缓存获取部门名称列表,作为Excel下拉选项 | |
| 20 | + */ | |
| 21 | + @Override | |
| 22 | + public List<String> getOptions() { | |
| 23 | + return DeptCacheHelper.getNameList(); // 直接走缓存! | |
| 24 | + } | |
| 25 | + | |
| 26 | +} | ... | ... |
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.system.util.dept; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; | |
| 5 | +import com.zteits.urbanops.module.system.service.dept.DeptService; | |
| 6 | +import lombok.extern.slf4j.Slf4j; | |
| 7 | +import java.util.*; | |
| 8 | +import java.util.function.Function; | |
| 9 | +import java.util.stream.Collectors; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 部门全局缓存工具 | |
| 13 | + * 给 Excel 导入导出、下拉框、转换器统一使用 | |
| 14 | + * 只加载一次,不重复查询 | |
| 15 | + */ | |
| 16 | +@Slf4j | |
| 17 | +public class DeptCacheHelper { | |
| 18 | + | |
| 19 | + private static List<DeptDO> DEPT_LIST; | |
| 20 | + private static Map<Long, DeptDO> DEPT_ID_MAP; | |
| 21 | + private static Map<String, DeptDO> DEPT_NAME_MAP; | |
| 22 | + private static List<String> DEPT_NAME_LIST; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 初始化部门缓存(在导入模板接口中主动调用) | |
| 26 | + */ | |
| 27 | + public static void init(DeptService deptService, Long rootDeptId) { | |
| 28 | + | |
| 29 | + //每次调用都使用传入的 deptId,每次可刷新 | |
| 30 | + if (DEPT_LIST != null) { | |
| 31 | + // 清空旧缓存 | |
| 32 | + DEPT_LIST = null; | |
| 33 | + DEPT_ID_MAP = null; | |
| 34 | + DEPT_NAME_MAP = null; | |
| 35 | + DEPT_NAME_LIST = null; | |
| 36 | + } | |
| 37 | + | |
| 38 | + try { | |
| 39 | + | |
| 40 | + // 1. 批量查询部门(只查一次) | |
| 41 | + DEPT_LIST = deptService.getLastDeptList(rootDeptId); | |
| 42 | + | |
| 43 | + if (CollUtil.isEmpty(DEPT_LIST)) { | |
| 44 | + log.warn("[DeptCacheHelper] 未查询到部门数据"); | |
| 45 | + return; | |
| 46 | + } | |
| 47 | + | |
| 48 | + // 3. 构建各种缓存 | |
| 49 | + DEPT_ID_MAP = DEPT_LIST.stream() | |
| 50 | + .collect(Collectors.toMap(DeptDO::getId, Function.identity())); | |
| 51 | + | |
| 52 | + DEPT_NAME_MAP = DEPT_LIST.stream() | |
| 53 | + .collect(Collectors.toMap(DeptDO::getName, Function.identity())); | |
| 54 | + | |
| 55 | + DEPT_NAME_LIST = DEPT_LIST.stream() | |
| 56 | + .map(DeptDO::getName) | |
| 57 | + .collect(Collectors.toList()); | |
| 58 | + | |
| 59 | + log.info("[DeptCacheHelper] 部门缓存初始化成功,共 {} 个部门", DEPT_LIST.size()); | |
| 60 | + } catch (Exception e) { | |
| 61 | + log.error("[DeptCacheHelper] 部门缓存初始化失败", e); | |
| 62 | + } | |
| 63 | + } | |
| 64 | + | |
| 65 | + // ===================== 提供给外部使用 ===================== | |
| 66 | + public static DeptDO getById(Long id) { | |
| 67 | + return DEPT_ID_MAP == null ? null : DEPT_ID_MAP.get(id); | |
| 68 | + } | |
| 69 | + | |
| 70 | + public static DeptDO getByName(String name) { | |
| 71 | + return DEPT_NAME_MAP == null ? null : DEPT_NAME_MAP.get(name); | |
| 72 | + } | |
| 73 | + | |
| 74 | + public static List<String> getNameList() { | |
| 75 | + return DEPT_NAME_LIST == null ? Collections.emptyList() : DEPT_NAME_LIST; | |
| 76 | + } | |
| 77 | + | |
| 78 | + public static boolean isEmpty() { | |
| 79 | + return CollUtil.isEmpty(DEPT_LIST); | |
| 80 | + } | |
| 81 | +} | ... | ... |
urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java
| ... | ... | @@ -482,7 +482,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 482 | 482 | public void testImportUserList_02() { |
| 483 | 483 | // 准备参数 |
| 484 | 484 | UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { |
| 485 | - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 | |
| 485 | + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 | |
| 486 | 486 | o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 |
| 487 | 487 | o.setEmail(randomEmail()); |
| 488 | 488 | o.setMobile(randomMobile()); |
| ... | ... | @@ -517,7 +517,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 517 | 517 | userMapper.insert(dbUser); |
| 518 | 518 | // 准备参数 |
| 519 | 519 | UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { |
| 520 | - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 | |
| 520 | + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 | |
| 521 | 521 | o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 |
| 522 | 522 | o.setUsername(dbUser.getUsername()); |
| 523 | 523 | o.setEmail(randomEmail()); |
| ... | ... | @@ -549,7 +549,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { |
| 549 | 549 | userMapper.insert(dbUser); |
| 550 | 550 | // 准备参数 |
| 551 | 551 | UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { |
| 552 | - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 | |
| 552 | + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 | |
| 553 | 553 | o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 |
| 554 | 554 | o.setUsername(dbUser.getUsername()); |
| 555 | 555 | o.setEmail(randomEmail()); | ... | ... |