yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelErrorCodeConstants.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,34 @@ package cn.iocoder.yudao.framework.excel.core.enums; import cn.iocoder.yudao.framework.common.exception.ErrorCode; /** * Excel ç¸å ³çé误ç * * <p>ç»ä¸å¨ {@link cn.iocoder.yudao.framework.excel.core.util.ExcelUtils} 䏿åºï¼ * 让ææå¯¼å ¥æ¥å£å¨éå°åæä»¶ãæ ¼å¼ä¸å¯¹ã模æ¿ä¸å¹é æ¶é½è¿åå¯è¯»ç䏿æç¤ºï¼ * è䏿¯å«ç³çãç³»ç»å¼å¸¸ã500ã</p> * * <p>é误ç åºé´ï¼1_099_000_000 ~ 1_099_999_999ï¼æ¡æ¶çº§ï¼é¿å ä¸ä¸å¡æ¨¡åå²çªï¼</p> * * @author è¶ çº§ç®¡çå */ public interface ExcelErrorCodeConstants { // ========== Excel æä»¶æ¬èº«çé®é¢ ========== ErrorCode EXCEL_FILE_EMPTY = new ErrorCode(1_099_000_001, "è¯·éæ©è¦å¯¼å ¥ç Excel æä»¶"); ErrorCode EXCEL_FILE_READ_FAILED = new ErrorCode(1_099_000_002, "Excel æä»¶è¯»å失败ï¼è¯·ç¡®è®¤æä»¶æªæåä¸ä¸º .xls/.xlsx æ ¼å¼"); ErrorCode EXCEL_FILE_EMPTY_CONTENT = new ErrorCode(1_099_000_003, "Excel å 容为空ï¼è¯·æç §æ¨¡æ¿å¡«åæ°æ®ååå¯¼å ¥"); // ========== 模æ¿ä¸å¹é ========== ErrorCode EXCEL_HEAD_NOT_MATCH = new ErrorCode(1_099_000_010, "Excel 表头ä¸å¯¼å ¥æ¨¡æ¿ä¸ä¸è´ï¼è¯·ä¸è½½ææ°æ¨¡æ¿éæ°å¡«å"); ErrorCode EXCEL_TEMPLATE_NOT_MATCH = new ErrorCode(1_099_000_011, "Excel 模æ¿çæ¬è¿æ§æä¸å¹é ï¼è¯·ä¸è½½ææ°æ¨¡æ¿éæ°å¡«å"); // ========== åå æ ¼æ°æ®é®é¢ ========== ErrorCode EXCEL_DATA_CONVERT_FAILED = new ErrorCode(1_099_000_020, "第 %d è¡ç¬¬ %d åæ°æ®æ ¼å¼ä¸æ£ç¡®ï¼%s"); ErrorCode EXCEL_DATA_ILLEGAL = new ErrorCode(1_099_000_021, "Excel æ°æ®æ ¼å¼ä¸æ£ç¡®ï¼è¯·æ£æ¥å¡«åå 容åéè¯"); } yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
@@ -1,9 +1,16 @@ package cn.iocoder.yudao.framework.excel.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.util.StrUtil; import cn.idev.excel.ExcelWriter; import cn.idev.excel.FastExcelFactory; import cn.idev.excel.converters.longconverter.LongStringConverter; import cn.idev.excel.exception.ExcelAnalysisException; import cn.idev.excel.exception.ExcelCommonException; import cn.idev.excel.exception.ExcelDataConvertException; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import lombok.extern.slf4j.Slf4j; import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy; import cn.iocoder.yudao.framework.excel.core.handler.DateAsTextCellWriteHandler; import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; @@ -14,11 +21,15 @@ import java.io.InputStream; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.excel.core.enums.ExcelErrorCodeConstants.*; /** * Excel å·¥å ·ç±» * * @author è¶ çº§ç®¡çå */ @Slf4j public class ExcelUtils { /** @@ -82,13 +93,61 @@ response.setContentType("application/vnd.ms-excel;charset=UTF-8"); } public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException { /** * 读å Excel æä»¶ * * <p>ææå¯¼å ¥æ¥å£é½ç»è¿è¿éï¼å æ¤å¨è¿éç»ä¸æãåæä»¶ / 空æä»¶ / 模æ¿ä¸å¹é / åå æ ¼æ ¼å¼éã * çåç§åºå±å¼å¸¸ï¼è½¬æ¢æå¯è¯»ç䏿æç¤ºï¼ServiceExceptionï¼ã * è¿æ ·ä¸å¡æ¥å£ä¸å¿ åèªå try-catchï¼ä¹ä¸ä¼ååºç°å«ç³çãç³»ç»å¼å¸¸ã500ã</p> * * @param file Excel æä»¶ * @param head 表头对åºç VO ç±» * @return è§£æç»æ */ public static <T> List<T> read(MultipartFile file, Class<T> head) { // 1. æä»¶æ¬èº«ä¸ºç©º if (file == null || file.isEmpty()) { throw exception(EXCEL_FILE_EMPTY); } // 2. è§£æï¼éç±»è¯å«å¼å¸¸ï¼ç»åºå¯è¯»æç¤ºï¼ List<T> list; // åè https://t.zsxq.com/zM77F å¸åï¼å¢å try å¤çï¼å ¼å®¹ windows åºæ¯ try (InputStream inputStream = file.getInputStream()) { return FastExcelFactory.read(inputStream, head, null) list = FastExcelFactory.read(inputStream, head, null) .autoCloseStream(false) // ä¸è¦èªå¨å ³éï¼äº¤ç» Servlet èªå·±å¤ç .doReadAllSync(); } catch (IOException ex) { log.warn("[read][Excel æä»¶è¯»åå¤±è´¥ï¼æä»¶å({})]", file.getOriginalFilename(), ex); throw exception(EXCEL_FILE_READ_FAILED); } catch (ExcelDataConvertException ex) { // åå æ ¼æ°æ®æ ¼å¼ä¸å¯¹ï¼ä¾å¦ææ¬åå¡«äºæ°åãæ¥æåå¡«äºä¹±ç String detail = StrUtil.blankToDefault(ex.getMessage(), "å 容ä¸åç±»åä¸å¹é "); log.warn("[read][Excel åå æ ¼æ°æ®æ ¼å¼é误ï¼è¡({}) å({})]", ex.getRowIndex(), ex.getColumnIndex(), ex); throw exception(EXCEL_DATA_CONVERT_FAILED, (ex.getRowIndex() == null ? 0 : ex.getRowIndex()) + 1, (ex.getColumnIndex() == null ? 0 : ex.getColumnIndex()) + 1, StrUtil.maxLength(detail, 60)); } catch (ExcelAnalysisException ex) { // æä»¶æåãå å¯ã䏿¯çæ£ç Excelãè¡¨å¤´ä¸æ¨¡æ¿å¯¹ä¸ä¸ç String rootMsg = StrUtil.blankToDefault(ExceptionUtil.getRootCauseMessage(ex), ""); log.warn("[read][Excel è§£æå¤±è´¥ï¼æä»¶å({})ï¼åå ({})]", file.getOriginalFilename(), rootMsg, ex); if (StrUtil.containsAnyIgnoreCase(rootMsg, "head", "Can not find", "autoTrim", "Convert")) { throw exception(EXCEL_HEAD_NOT_MATCH); } throw exception(EXCEL_TEMPLATE_NOT_MATCH); } catch (ExcelCommonException ex) { log.warn("[read][Excel éç¨å¼å¸¸ï¼æä»¶å({})]", file.getOriginalFilename(), ex); throw exception(EXCEL_FILE_READ_FAILED); } catch (Exception ex) { // å åºï¼ä»»ä½æªé¢æçè§£æå¼å¸¸é½è½¬æå¯è¯»æç¤ºï¼é¿å ç´æ¥ 500ãç³»ç»å¼å¸¸ã log.warn("[read][Excel è§£æåºç°æªé¢æå¼å¸¸ï¼æä»¶å({})]", file.getOriginalFilename(), ex); throw exception(EXCEL_FILE_READ_FAILED); } // 3. è§£æåºæ¥æ¯ç©ºè¡¨ï¼åªæè¡¨å¤´ãæ²¡ææ°æ®è¡ï¼ if (CollUtil.isEmpty(list)) { throw exception(EXCEL_FILE_EMPTY_CONTENT); } return list; } } yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException; import tools.jackson.databind.exc.InvalidFormatException; @@ -96,6 +97,10 @@ } if (ex instanceof MaxUploadSizeExceededException) { return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex); } // ä¸ä¼ ç±»æ¥å£æ¶å°é multipart 请æ±ï¼å端æ¼ä¼ æä»¶ / ç¨ JSON è°ç¨äºå¯¼å ¥æ¥å£ï¼å±äºå®¢æ·ç«¯é®é¢ï¼ä¸åºæ 500 å¤ç if (ex instanceof MultipartException) { return multipartExceptionHandler((MultipartException) ex); } if (ex instanceof NoHandlerFoundException) { return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex); @@ -181,6 +186,18 @@ * * ä¾å¦è¯´ï¼æ¥å£ä¸è®¾ç½®äº @RequestBody å®ä½ä¸ xx 屿§ç±»å为 Integerï¼ç»æä¼ é xx åæ°ç±»å为 String */ /** * å¤çæä»¶ä¸ä¼ æ¥å£æ¶å°é multipart è¯·æ± * * <p>ä¾å¦è¯´ï¼Excel å¯¼å ¥æ¥å£æ¼ä¼ file åæ®µãæåç«¯ç¨ application/json è°ç¨äºå¯¼å ¥æ¥å£ã * è¿ç±»é½æ¯å®¢æ·ç«¯ç¨æ³é®é¢ï¼è¿å 400 æç¡®æç¤ºï¼è䏿¯ 500ãç³»ç»å¼å¸¸ãã</p> */ @ExceptionHandler(MultipartException.class) public CommonResult<?> multipartExceptionHandler(MultipartException ex) { log.warn("[multipartExceptionHandler]", ex); return CommonResult.error(BAD_REQUEST.getCode(), "请æ±åæ°ä¸æ£ç¡®:è¯·ä½¿ç¨ multipart/form-data ä¸ä¼ æä»¶"); } @ExceptionHandler(HttpMessageNotReadableException.class) @SuppressWarnings("PatternVariableCanBeUsed") public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) { @@ -192,7 +209,9 @@ if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) { return CommonResult.error(BAD_REQUEST.getCode(), "请æ±åæ°ç±»åé误: request body 缺失"); } return defaultExceptionHandler(ServletUtils.getRequest(), ex); // å ¶å®ååºåå失败ï¼å¦æå¯¹è±¡ä¼ ææ°ç»ãåæ®µç±»åä¸å¹é ï¼åæ ·æ¯å®¢æ·ç«¯æ¥æé®é¢ï¼ä¸åºæ 500 å¤ç return CommonResult.error(BAD_REQUEST.getCode(), String.format("请æ±åæ°ç±»åé误:%s", StrUtil.maxLength(ex.getMessage(), 120))); } /** @@ -358,27 +377,31 @@ errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request)); // 设置å¼å¸¸å段 errorLog.setExceptionName(e.getClass().getName()); errorLog.setExceptionMessage(ExceptionUtil.getMessage(e)); errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e)); errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e)); // infra_api_error_log çè¿äºå齿¯ NOT NULLï¼è MyBatis-Plus æå ¥æ¶ä¼è·³è¿ null åæ®µï¼ // äºæ¯ä»»ä¸å段为 null é½ä¼åæ "Field 'xxx' doesn't have a default value"ï¼å¼å¸¸æ¥å¿åèè®°ä¸ä¸æ¥ errorLog.setExceptionMessage(StrUtil.nullToEmpty(ExceptionUtil.getMessage(e))); errorLog.setExceptionRootCauseMessage(StrUtil.nullToEmpty(ExceptionUtil.getRootCauseMessage(e))); errorLog.setExceptionStackTrace(StrUtil.nullToEmpty(ExceptionUtil.stacktraceToString(e))); StackTraceElement[] stackTraceElements = e.getStackTrace(); Assert.notEmpty(stackTraceElements, "å¼å¸¸ stackTraceElements ä¸è½ä¸ºç©º"); StackTraceElement stackTraceElement = stackTraceElements[0]; errorLog.setExceptionClassName(stackTraceElement.getClassName()); errorLog.setExceptionFileName(stackTraceElement.getFileName()); errorLog.setExceptionMethodName(stackTraceElement.getMethodName()); // æ é¡¶å ç´ å¯è½æ¯ native æ¹æ³æ Lambdaï¼getFileName() 为 nullï¼ // infra_api_error_log çè¿äºåæ¯ NOT NULLï¼ä¸º null ä¼å¯¼è´å¼å¸¸æ¥å¿æå ¥å¤±è´¥ errorLog.setExceptionFileName(StrUtil.nullToEmpty(stackTraceElement.getFileName())); errorLog.setExceptionMethodName(StrUtil.nullToEmpty(stackTraceElement.getMethodName())); errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber()); // è®¾ç½®å ¶å®å段 errorLog.setTraceId(TracerUtils.getTraceId()); errorLog.setApplicationName(applicationName); errorLog.setRequestUrl(request.getRequestURI()); errorLog.setTraceId(StrUtil.nullToEmpty(TracerUtils.getTraceId())); errorLog.setApplicationName(StrUtil.nullToEmpty(applicationName)); errorLog.setRequestUrl(StrUtil.nullToEmpty(request.getRequestURI())); Map<String, Object> requestParams = MapUtil.<String, Object>builder() .put("query", ServletUtils.getParamMap(request)) .put("body", ServletUtils.getBody(request)).build(); errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); errorLog.setRequestMethod(request.getMethod()); errorLog.setUserAgent(ServletUtils.getUserAgent(request)); errorLog.setUserIp(ServletUtils.getClientIP(request)); errorLog.setRequestParams(StrUtil.nullToEmpty(JsonUtils.toJsonString(requestParams))); errorLog.setRequestMethod(StrUtil.nullToEmpty(request.getMethod())); errorLog.setUserAgent(StrUtil.nullToEmpty(ServletUtils.getUserAgent(request))); errorLog.setUserIp(StrUtil.nullToEmpty(ServletUtils.getClientIP(request))); errorLog.setExceptionTime(LocalDateTime.now()); } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
@@ -44,9 +44,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_IMPORT_FILE_EMPTY; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_IMPORT_FILE_INVALID; @Tag(name = "管çåå° - ç¨æ·") @RestController @@ -242,16 +239,8 @@ @PreAuthorize("@ss.hasPermission('system:user:import')") public CommonResult<UserImportRespVO> importExcel(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { // 空æä»¶ / éæ³æä»¶ç»åºæç¡®æç¤ºï¼é¿å åºå±å¼å¸¸åæãç³»ç»å¼å¸¸ã if (file == null || file.isEmpty()) { throw exception(USER_IMPORT_FILE_EMPTY); } List<UserImportExcelVO> list; try { list = ExcelUtils.read(file, UserImportExcelVO.class); } catch (Exception ex) { throw exception(USER_IMPORT_FILE_INVALID); } // 空æä»¶ / åæä»¶ / 模æ¿ä¸å¹é çï¼ExcelUtils.read å·²ç»ä¸è½¬æ¢ä¸ºå¯è¯»ç䏿æç¤º List<UserImportExcelVO> list = ExcelUtils.read(file, UserImportExcelVO.class); return success(userService.importUserList(list, updateSupport)); } yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; @@ -416,21 +417,22 @@ */ @Test public void testImportUserList_01() { // åå¤åæ° // åå¤åæ°ï¼é¨é¨åç§°å¨ç³»ç»ä¸ä¸åå¨ UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { o.setDeptName("ä¸åå¨çé¨é¨-" + System.currentTimeMillis()); o.setEmail(randomEmail()); o.setMobile(randomMobile()); }); // mock æ¹æ³ï¼æ¨¡æå¤±è´¥ doThrow(new ServiceException(DEPT_NOT_FOUND)).when(deptService).validateDeptList(any()); // mockï¼ç³»ç»ä¸æ²¡æå¹é çé¨é¨ when(deptService.getDeptList(any(DeptListReqVO.class))).thenReturn(newArrayList()); // è°ç¨ UserImportRespVO respVO = userService.importUserList(newArrayList(importUser), true); // æè¨ // æè¨ï¼è¯¥è¡å é¨é¨ä¸åå¨è失败 assertEquals(0, respVO.getCreateUsernames().size()); assertEquals(0, respVO.getUpdateUsernames().size()); assertEquals(1, respVO.getFailureUsernames().size()); assertEquals(DEPT_NOT_FOUND.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername())); assertTrue(respVO.getFailureUsernames().get(importUser.getUsername()).contains("ä¸åå¨")); } /** @@ -446,10 +448,13 @@ o.setMobile(randomMobile()); }); // mock deptService çæ¹æ³ // é¨é¨å·²æ¹ä¸ºæåç§°å¯¼å ¥ï¼mock getDeptList è¿åä¸ä¸ªå¯å¹é çé¨é¨ DeptDO dept = randomPojo(DeptDO.class, o -> { o.setId(importUser.getDeptId()); o.setId(1L); o.setName(importUser.getDeptName()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); when(deptService.getDeptList(any(DeptListReqVO.class))).thenReturn(newArrayList(dept)); when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); // mock passwordEncoder çæ¹æ³ when(passwordEncoder.encode(eq("yudaoyuanma"))).thenReturn("java"); @@ -481,11 +486,13 @@ o.setEmail(randomEmail()); o.setMobile(randomMobile()); }); // mock deptService çæ¹æ³ // é¨é¨å·²æ¹ä¸ºæåç§°å¯¼å ¥ï¼mock getDeptList è¿åä¸ä¸ªå¯å¹é çé¨é¨ DeptDO dept = randomPojo(DeptDO.class, o -> { o.setId(importUser.getDeptId()); o.setId(1L); o.setName(importUser.getDeptName()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); when(deptService.getDeptList(any(DeptListReqVO.class))).thenReturn(newArrayList(dept)); when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); // è°ç¨ @@ -513,11 +520,13 @@ o.setEmail(randomEmail()); o.setMobile(randomMobile()); }); // mock deptService çæ¹æ³ // é¨é¨å·²æ¹ä¸ºæåç§°å¯¼å ¥ï¼mock getDeptList è¿åä¸ä¸ªå¯å¹é çé¨é¨ DeptDO dept = randomPojo(DeptDO.class, o -> { o.setId(importUser.getDeptId()); o.setId(1L); o.setName(importUser.getDeptName()); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); when(deptService.getDeptList(any(DeptListReqVO.class))).thenReturn(newArrayList(dept)); when(deptService.getDept(eq(dept.getId()))).thenReturn(dept); // è°ç¨