xiaoyi
5 天以前 481d8165f26fc7e37f0e87f6444ac0340bb563a3
fix: 移植长辉已验证的四类接口 500/体验问题(不含物料颜色字段)

1. BPM 发起流程 500:新增 BpmProcessDefinitionIdUtils 对流程定义编号做幂等 URL 解码(前端双重编码时会查到不存在的编号),并对流程定义缺失做 null 容错;BpmProcessDefinitionInfoMapper 兼容同一 processDefinitionId 多条记录导致的 TooManyResultsException;BpmProcessInstanceConvert 容错流程定义已删除场景

2. HRM 到期提醒 NPE:HrmExpiryRemindServiceImpl.buildEmployeeMap 空集合返回 Map.of(),调用方 get(employeeId) 在 employeeId 为空时触发 NPE,改为 new HashMap<>()

3. Excel 导入模板日期显示为序列号:新增 DateAsTextCellWriteHandler + LocalDate/LocalDateTime 字符串转换器,ExcelUtils 增加 writeTemplate;53 个含日期列的导入模板改用 writeTemplate(导出逻辑不受影响)

4. 用户导入部门改为按名称填写:UserImportExcelVO.deptId(Long) -> deptName(String),模板示例取当前真实部门,导入按名称精确匹配并给出明确提示;空文件/非法文件不再报「系统异常」
已添加4个文件
已修改62个文件
490 ■■■■■ 文件已修改
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/LocalDateStringConverter.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/LocalDateTimeStringConverter.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/DateAsTextCellWriteHandler.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmProcessDefinitionIdUtils.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/CrmCreditRatingController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/MesCalPlanController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkplan/MesDvCheckPlanController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkrecord/MesDvCheckRecordController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/maintenrecord/MesDvMaintenRecordController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/MesDvMeteringCheckController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/repair/MesDvRepairController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/MesProMaintenancePlanController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/mps/MesProMpsController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/MesQcIqcController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/MesQcOqcController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/MesQcRqcController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/tm/tool/MesTmToolController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/MesWmMiscIssueController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourcereceipt/MesWmOutsourceReceiptController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/MesWmProductIssueController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productreceipt/MesWmProductReceiptController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/MesWmProductSalesController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/MesWmReturnIssueController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/MesWmSalesNoticeController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transfer/MesWmTransferController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/LocalDateStringConverter.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.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.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
 * LocalDate å†™å‡ºä¸ºçº¯æ–‡æœ¬ï¼ˆyyyy-MM-dd)的转换器
 *
 * <p>默认 FastExcel æŠŠæ—¥æœŸå†™æˆã€Œæ—¥æœŸå•元格」,在 Excel é‡Œæ˜¾ç¤ºä¸º 46023.0 è¿™ç±»åºåˆ—号,
 * ä½¿ç”¨è€…看不懂该填什么格式。导入模板类场景注册本转换器后,日期以文本输出,直观可读。</p>
 *
 * <p>注意:仅用于「写出模板」,不要用于导入解析。</p>
 *
 * @author è¶…级管理员
 */
public class LocalDateStringConverter implements Converter<LocalDate> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    @Override
    public Class<LocalDate> supportJavaTypeKey() {
        return LocalDate.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    @Override
    public WriteCellData<?> convertToExcelData(LocalDate value, ExcelContentProperty contentProperty,
                                               GlobalConfiguration globalConfiguration) {
        return new WriteCellData<>(value.format(FORMATTER));
    }
}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/LocalDateTimeStringConverter.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.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.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * LocalDateTime å†™å‡ºä¸ºçº¯æ–‡æœ¬ï¼ˆyyyy-MM-dd HH:mm:ss)的转换器
 *
 * <p>用途同 {@link LocalDateStringConverter},避免时间显示为 Excel åºåˆ—号。</p>
 *
 * @author è¶…级管理员
 */
public class LocalDateTimeStringConverter implements Converter<LocalDateTime> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public Class<LocalDateTime> supportJavaTypeKey() {
        return LocalDateTime.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    @Override
    public WriteCellData<?> convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty,
                                               GlobalConfiguration globalConfiguration) {
        return new WriteCellData<>(value.format(FORMATTER));
    }
}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/DateAsTextCellWriteHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package cn.iocoder.yudao.framework.excel.core.handler;
import cn.idev.excel.write.handler.CellWriteHandler;
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * æŠŠæ—¥æœŸå•元格强制写成纯文本的处理器
 *
 * <p>背景:FastExcel åœ¨å†™å‡ºæ—¶ï¼Œå³ä½¿æ³¨å†Œäº†å­—符串转换器,日期字段仍可能被内置的
 * Number è½¬æ¢å™¨æŽ¥ç®¡ï¼Œæœ€ç»ˆåœ¨ Excel é‡Œæ˜¾ç¤ºä¸º 46023.0 è¿™ç±»åºåˆ—号,使用者看不懂。</p>
 *
 * <p>本处理器在单元格写完后介入,把 LocalDate / LocalDateTime ç±»åž‹çš„原始值
 * æ ¼å¼åŒ–为 yyyy-MM-dd / yyyy-MM-dd HH:mm:ss å¹¶ä»¥æ–‡æœ¬å†™å…¥ã€‚</p>
 *
 * <p>仅用于「下载导入模板」,不影响导入解析。</p>
 *
 * @author è¶…级管理员
 */
public class DateAsTextCellWriteHandler implements CellWriteHandler {
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        // åªå¤„理数据行,跳过表头
        if (Boolean.TRUE.equals(context.getHead())) {
            return;
        }
        Object value = context.getOriginalValue();
        if (value == null) {
            return;
        }
        String text = null;
        if (value instanceof LocalDate localDate) {
            text = localDate.format(DATE_FORMATTER);
        } else if (value instanceof LocalDateTime localDateTime) {
            text = localDateTime.format(DATETIME_FORMATTER);
        } else if (value instanceof java.util.Date date) {
            text = date.toInstant().atZone(java.time.ZoneId.systemDefault())
                    .toLocalDate().format(DATE_FORMATTER);
        }
        if (text == null) {
            return;
        }
        Cell cell = context.getCell();
        if (cell == null) {
            return;
        }
        cell.setCellType(CellType.STRING);
        cell.setCellValue(text);
    }
}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
@@ -5,6 +5,7 @@
import cn.idev.excel.converters.longconverter.LongStringConverter;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
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;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
@@ -55,6 +56,32 @@
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }
    /**
     * è¾“出 Excel æ¨¡æ¿ï¼ˆæ—¥æœŸæŒ‰æ–‡æœ¬æ¸²æŸ“)
     *
     * <p>与 {@link #write} çš„区别:额外注册了 {@link DateAsTextCellWriteHandler},
     * æŠŠ LocalDate / LocalDateTime å†™æˆ yyyy-MM-dd æ–‡æœ¬ï¼Œè€Œä¸æ˜¯ Excel æ—¥æœŸåºåˆ—号。
     * ä»…用于「下载导入模板」,导入解析仍走 {@link #read},不受影响。</p>
     */
    public static <T> void writeTemplate(HttpServletResponse response, String filename, String sheetName,
                                         Class<T> head, List<T> data) throws IOException {
        ExcelWriter excelWriter = FastExcelFactory.write(response.getOutputStream(), head)
                .autoCloseStream(false)
                .registerWriteHandler(new ColumnWidthMatchStyleStrategy())
                .registerWriteHandler(new SelectSheetWriteHandler(head))
                .registerWriteHandler(new DateAsTextCellWriteHandler())
                .registerConverter(new LongStringConverter())
                .build();
        try {
            excelWriter.write(data, FastExcelFactory.writerSheet(sheetName).build());
        } finally {
            excelWriter.finish();
            excelWriter.close();
        }
        response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }
    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        // å‚考 https://t.zsxq.com/zM77F å¸–子,增加 try å¤„理,兼容 windows åœºæ™¯
        try (InputStream inputStream = file.getInputStream()) {
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
@@ -107,9 +107,13 @@
        BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class);
        respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))
                .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
        // definition
        respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
        copyTo(processDefinitionInfo, respVO.getProcessDefinition());
        // definition(流程定义可能已被删除,需要容错,否则前端拿到 500)
        if (processDefinition != null) {
            respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
            if (respVO.getProcessDefinition() != null && processDefinitionInfo != null) {
                copyTo(processDefinitionInfo, respVO.getProcessDefinition());
            }
        }
        // user
        if (startUser != null) {
            respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
@@ -15,8 +15,19 @@
        return selectList(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionIds);
    }
    /**
     * æŸ¥è¯¢æµç¨‹å®šä¹‰çš„æ‰©å±•信息
     *
     * <p>历史原因同一 processDefinitionId å¯èƒ½å­˜åœ¨å¤šæ¡è®°å½•(重复部署 / é‡å¤ä¿å­˜æ¨¡åž‹ï¼‰ï¼Œ
     * ç›´æŽ¥ç”¨ selectOne ä¼šæŠ› TooManyResultsException å¯¼è‡´æŽ¥å£ 500,
     * è¿™é‡Œæ”¹ä¸ºå–最新一条,保证接口可用。</p>
     */
    default BpmProcessDefinitionInfoDO selectByProcessDefinitionId(String processDefinitionId) {
        return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId);
        List<BpmProcessDefinitionInfoDO> list = selectList(
                new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>()
                        .eq(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId)
                        .orderByDesc(BpmProcessDefinitionInfoDO::getId));
        return list.isEmpty() ? null : list.get(0);
    }
    default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) {
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmProcessDefinitionIdUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.util.StrUtil;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
 * Flowable æµç¨‹å®šä¹‰ç¼–号工具
 *
 * <p>流程定义编号形如 {@code qingjia:2:553a7ac9-...},包含冒号。
 * å‰ç«¯å¦‚果对该值做了 URL ç¼–码后再作为查询参数传递(双重编码),
 * Spring åªè§£ç ä¸€å±‚,最终拿到的仍是 {@code qingjia%3A2%3A...},
 * Flowable æŒ‰è¯¥ç¼–号查不到流程定义,直接抛 FlowableObjectNotFoundException å¯¼è‡´ 500。</p>
 *
 * <p>这里在入口处做一次幂等的归一化:只在确实残留 % è½¬ä¹‰æ—¶æ‰è§£ç ï¼Œ
 * åˆæ³•的流程定义编号不可能包含 %,因此不会误伤。</p>
 *
 * @author è¶…级管理员
 */
public class BpmProcessDefinitionIdUtils {
    /** æœ€å¤šè§£ç å±‚数,防止异常输入无限循环 */
    private static final int MAX_DECODE_TIMES = 5;
    private BpmProcessDefinitionIdUtils() {
    }
    /**
     * å½’一化流程定义编号:逐层解码残留的 URL è½¬ä¹‰
     *
     * @param id æµç¨‹å®šä¹‰ç¼–号
     * @return å½’一化后的编号;入参为空时原样返回
     */
    public static String normalize(String id) {
        if (StrUtil.isBlank(id) || id.indexOf('%') < 0) {
            return id;
        }
        String current = id;
        for (int i = 0; i < MAX_DECODE_TIMES && current.indexOf('%') >= 0; i++) {
            String decoded;
            try {
                decoded = URLDecoder.decode(current, StandardCharsets.UTF_8);
            } catch (Exception ignored) {
                // è§£ç å¤±è´¥è¯´æ˜Žä¸æ˜¯ URL è½¬ä¹‰ï¼Œä¿æŒåŽŸå€¼
                break;
            }
            if (decoded.equals(current)) {
                break;
            }
            current = decoded;
        }
        return current;
    }
}
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -11,12 +11,14 @@
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmProcessDefinitionIdUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
@@ -57,7 +59,18 @@
    @Override
    public ProcessDefinition getProcessDefinition(String id) {
        return repositoryService.getProcessDefinition(id);
        String definitionId = BpmProcessDefinitionIdUtils.normalize(id);
        if (StrUtil.isBlank(definitionId)) {
            return null;
        }
        // ç¼–号不存在时 Flowable ä¼šæŠ› FlowableObjectNotFoundException,
        // è¿™é‡Œç»Ÿä¸€è½¬æˆ null ç”±è°ƒç”¨æ–¹å¤„理,避免前端拿到 500「系统异常」
        try {
            return repositoryService.getProcessDefinition(definitionId);
        } catch (FlowableObjectNotFoundException ex) {
            log.warn("[getProcessDefinition][流程定义({}) ä¸å­˜åœ¨]", definitionId);
            return null;
        }
    }
    @Override
@@ -65,7 +78,9 @@
        if (CollUtil.isEmpty(ids)) {
            return emptyList();
        }
        return repositoryService.createProcessDefinitionQuery().processDefinitionIds(ids).list();
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionIds(ids.stream().map(BpmProcessDefinitionIdUtils::normalize)
                        .collect(java.util.stream.Collectors.toSet())).list();
    }
    @Override
@@ -88,7 +103,7 @@
    public ProcessDefinition getActiveProcessDefinition(String key) {
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionTenantId(FlowableUtils.getTenantId())
                .processDefinitionKey(key).active().singleResult();
                .processDefinitionKey(BpmProcessDefinitionIdUtils.normalize(key)).active().singleResult();
    }
    @Override
@@ -203,17 +218,30 @@
    @Override
    public BpmnModel getProcessDefinitionBpmnModel(String id) {
        return repositoryService.getBpmnModel(id);
        String definitionId = BpmProcessDefinitionIdUtils.normalize(id);
        if (StrUtil.isBlank(definitionId)) {
            return null;
        }
        try {
            return repositoryService.getBpmnModel(definitionId);
        } catch (FlowableObjectNotFoundException ex) {
            log.warn("[getProcessDefinitionBpmnModel][流程定义({}) ä¸å­˜åœ¨]", definitionId);
            return null;
        }
    }
    @Override
    public BpmProcessDefinitionInfoDO getProcessDefinitionInfo(String id) {
        return processDefinitionMapper.selectByProcessDefinitionId(id);
        return processDefinitionMapper.selectByProcessDefinitionId(BpmProcessDefinitionIdUtils.normalize(id));
    }
    @Override
    public List<BpmProcessDefinitionInfoDO> getProcessDefinitionInfoList(Collection<String> ids) {
        return processDefinitionMapper.selectListByProcessDefinitionIds(ids);
        if (CollUtil.isEmpty(ids)) {
            return emptyList();
        }
        return processDefinitionMapper.selectListByProcessDefinitionIds(ids.stream()
                .map(BpmProcessDefinitionIdUtils::normalize).collect(java.util.stream.Collectors.toSet()));
    }
    @Override
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -198,9 +198,13 @@
            processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, loginUserId);
        }
        // 1.3 è¯»å–其它相关数据
        ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
                historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId()
                        : reqVO.getProcessDefinitionId());
        String processDefinitionId = historicProcessInstance != null
                ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId();
        ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(processDefinitionId);
        if (processDefinition == null) {
            // æµç¨‹å®šä¹‰ä¸å­˜åœ¨æ—¶è¿”回明确的业务错误,避免 NPE / Flowable å¼‚常变成 500「系统异常」
            throw exception(ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS);
        }
        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService
                .getProcessDefinitionInfo(processDefinition.getId());
        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
@@ -772,6 +776,9 @@
        // èŽ·å¾—æµç¨‹å®šä¹‰
        ProcessDefinition definition = processDefinitionService
                .getProcessDefinition(createReqVO.getProcessDefinitionId());
        if (definition == null) {
            throw exception(ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS);
        }
        // å‘起流程
        return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,
                createReqVO.getStartUserSelectAssignees());
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -232,7 +232,7 @@
                        .dealTime(null).totalProductPrice(null).discountPercent(null).totalPrice(null).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "商机导入模板.xls", "商机列表", CrmBusinessImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "商机导入模板.xls", "商机列表", CrmBusinessImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -196,7 +196,7 @@
                        .payCycle(null).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "合同导入模板.xls", "合同列表", CrmContractImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "合同导入模板.xls", "合同列表", CrmContractImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/credit/CrmCreditRatingController.java
@@ -101,7 +101,7 @@
                        .score(null).evalType("").evalReason("").operatorName("")
                        .operateTime(null).validUntil(null).remark("").build()
        );
        ExcelUtils.write(response, "客户信用评级导入模板.xls", "客户信用评级列表", CrmCreditRatingImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "客户信用评级导入模板.xls", "客户信用评级列表", CrmCreditRatingImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java
@@ -83,7 +83,7 @@
    @Operation(summary = "下载销售报价导入模板")
    @PreAuthorize("@ss.hasPermission('crm:sale-quotation:create')")
    public void importTemplate(HttpServletResponse response) throws IOException {
        ExcelUtils.write(response, "销售报价导入模板.xls", "报价明细", CrmSaleQuotationImportExcelVO.class, java.util.Collections.emptyList());
        ExcelUtils.writeTemplate(response, "销售报价导入模板.xls", "报价明细", CrmSaleQuotationImportExcelVO.class, java.util.Collections.emptyList());
    }
    @PostMapping("/import")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java
@@ -142,7 +142,7 @@
                        .invoiceTime(null).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "来票导入模板.xls", "来票列表", ErpPurchaseInvoiceImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "来票导入模板.xls", "来票列表", ErpPurchaseInvoiceImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -183,7 +183,7 @@
                        .count(null).taxPercent(null).qcCheckFlag(false).itemRemark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "采购订单导入模板.xls", "采购订单列表", ErpPurchaseOrderImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "采购订单导入模板.xls", "采购订单列表", ErpPurchaseOrderImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java
@@ -146,7 +146,7 @@
                        .demandTime(null).itemRemark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "采购计划导入模板.xls", "采购计划列表", ErpPurchasePlanImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "采购计划导入模板.xls", "采购计划列表", ErpPurchasePlanImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -152,7 +152,7 @@
                        .itemRemark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "采购申请导入模板.xls", "采购申请列表", ErpPurchaseRequestImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "采购申请导入模板.xls", "采购申请列表", ErpPurchaseRequestImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -211,7 +211,7 @@
                        .productPrice(null).count(null).taxPercent(null).needProduction(0).itemRemark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "销售订单导入模板.xls", "销售订单列表", ErpSaleOrderImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "销售订单导入模板.xls", "销售订单列表", ErpSaleOrderImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceHolidayController.java
@@ -101,7 +101,7 @@
                        .type(1).isLegal(1).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "节假日配置导入模板.xls", "节假日配置列表", HrmAttendanceHolidayImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "节假日配置导入模板.xls", "节假日配置列表", HrmAttendanceHolidayImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
@@ -230,7 +230,7 @@
                        .remark("示例数据")
                        .build()
        );
        ExcelUtils.write(response, "考勤导入模板.xls", "考勤记录", HrmAttendanceRecordImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "考勤导入模板.xls", "考勤记录", HrmAttendanceRecordImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/certificate/HrmCertificateController.java
@@ -153,7 +153,7 @@
                        .expireDate(LocalDate.of(2029, 1, 1)).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "证书资质导入模板.xls", "证书资质列表", HrmCertificateImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "证书资质导入模板.xls", "证书资质列表", HrmCertificateImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java
@@ -231,7 +231,7 @@
                        .effectiveDate(LocalDate.of(2026, 1, 1)).status(0).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "员工社保公积金档案导入模板.xls", "员工社保公积金档案列表",
        ExcelUtils.writeTemplate(response, "员工社保公积金档案导入模板.xls", "员工社保公积金档案列表",
                HrmEmployeeSocialSecurityImportExcelVO.class, list);
    }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java
@@ -94,7 +94,7 @@
                        .status(0).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "社保公积金方案导入模板.xls", "社保公积金方案列表",
        ExcelUtils.writeTemplate(response, "社保公积金方案导入模板.xls", "社保公积金方案列表",
                HrmSocialSecuritySchemeImportExcelVO.class, list);
    }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmTaxRateConfigController.java
@@ -98,7 +98,7 @@
                        .taxRate(new BigDecimal("3")).quickDeduction(new BigDecimal("0")).status(0).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "个税税率配置导入模板.xls", "个税税率配置列表",
        ExcelUtils.writeTemplate(response, "个税税率配置导入模板.xls", "个税税率配置列表",
                HrmTaxRateConfigImportExcelVO.class, list);
    }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/remind/HrmExpiryRemindServiceImpl.java
@@ -184,8 +184,10 @@
    }
    private Map<Long, HrmEmployeeDO> buildEmployeeMap(java.util.Collection<Long> employeeIds) {
        // å¿…须返回可接受 null key çš„空 Map:调用方会 employeeMap.get(certificate.getEmployeeId()),
        // è€Œ employeeId å…è®¸ä¸ºç©ºï¼ˆè¯ä»¶/合同未绑定员工),Map.of() çš„ get(null) ä¼šæŠ› NPE å¯¼è‡´åˆ—表接口 500
        if (CollUtil.isEmpty(employeeIds)) {
            return Map.of();
            return new HashMap<>();
        }
        return employeeService.getEmployeeList(employeeIds).stream()
                .collect(java.util.stream.Collectors.toMap(HrmEmployeeDO::getId, e -> e, (a, b) -> a));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/MesCalPlanController.java
@@ -119,7 +119,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "排班计划导入模板.xls", "排班计划列表", MesCalPlanImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "排班计划导入模板.xls", "排班计划列表", MesCalPlanImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkplan/MesDvCheckPlanController.java
@@ -125,7 +125,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "点检保养方案导入模板.xls", "点检保养方案列表", MesDvCheckPlanImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "点检保养方案导入模板.xls", "点检保养方案列表", MesDvCheckPlanImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkrecord/MesDvCheckRecordController.java
@@ -162,7 +162,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "设备点检记录导入模板.xls", "设备点检记录列表", MesDvCheckRecordImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "设备点检记录导入模板.xls", "设备点检记录列表", MesDvCheckRecordImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/maintenrecord/MesDvMaintenRecordController.java
@@ -165,7 +165,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "设备保养记录导入模板.xls", "设备保养记录列表", MesDvMaintenRecordImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "设备保养记录导入模板.xls", "设备保养记录列表", MesDvMaintenRecordImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java
@@ -157,7 +157,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "计量器具导入模板.xls", "计量器具列表", MesDvMeteringImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "计量器具导入模板.xls", "计量器具列表", MesDvMeteringImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/MesDvMeteringCheckController.java
@@ -144,7 +144,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "计量检测记录导入模板.xls", "计量检测记录列表", MesDvMeteringCheckImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "计量检测记录导入模板.xls", "计量检测记录列表", MesDvMeteringCheckImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/repair/MesDvRepairController.java
@@ -181,7 +181,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "维修工单导入模板.xls", "维修工单列表", MesDvRepairImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "维修工单导入模板.xls", "维修工单列表", MesDvRepairImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java
@@ -123,7 +123,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "设计项目导入模板.xls", "设计项目列表", MesPdProjectImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "设计项目导入模板.xls", "设计项目列表", MesPdProjectImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
@@ -207,7 +207,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "生产报工导入模板.xls", "生产报工列表", MesProFeedbackImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "生产报工导入模板.xls", "生产报工列表", MesProFeedbackImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenancecalendar/MesProMaintenanceCalendarController.java
@@ -98,7 +98,7 @@
                        .name("8月变电运维排班").workDate(java.time.LocalDate.of(2026, 8, 20)).shift(1).workType(1)
                        .region("110kV åŸŽåŒºçº¿").teamName("变电运维一班").personnel("张三,李四").remark("").build()
        );
        ExcelUtils.write(response, "电力运维日历导入模板.xls", "电力运维日历列表", MesProMaintenanceCalendarImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "电力运维日历导入模板.xls", "电力运维日历列表", MesProMaintenanceCalendarImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/maintenanceplan/MesProMaintenancePlanController.java
@@ -114,7 +114,7 @@
                        .teamName("运维一班").planDate(java.time.LocalDateTime.of(2026, 8, 1, 9, 0, 0))
                        .remark("").itemCode("").itemQuantity(new java.math.BigDecimal("10")).itemRemark("").build()
        );
        ExcelUtils.write(response, "电力作业计划导入模板.xls", "电力作业计划列表", MesProMaintenancePlanImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "电力作业计划导入模板.xls", "电力作业计划列表", MesProMaintenancePlanImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/mps/MesProMpsController.java
@@ -117,7 +117,7 @@
                        .clientName("").requestDate(java.time.LocalDateTime.of(2026, 10, 16, 9, 0, 0))
                        .remark("").build()
        );
        ExcelUtils.write(response, "生产主计划导入模板.xls", "生产主计划列表", MesProMpsImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "生产主计划导入模板.xls", "生产主计划列表", MesProMpsImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
@@ -145,7 +145,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "生产工单导入模板.xls", "生产工单列表", MesProWorkOrderImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "生产工单导入模板.xls", "生产工单列表", MesProWorkOrderImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcController.java
@@ -191,7 +191,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "过程检验单导入模板.xls", "过程检验单列表", MesQcIpqcImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "过程检验单导入模板.xls", "过程检验单列表", MesQcIpqcImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/MesQcIqcController.java
@@ -176,7 +176,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "来料检验单导入模板.xls", "来料检验单列表", MesQcIqcImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "来料检验单导入模板.xls", "来料检验单列表", MesQcIqcImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/oqc/MesQcOqcController.java
@@ -172,7 +172,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "出货检验单导入模板.xls", "出货检验单列表", MesQcOqcImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "出货检验单导入模板.xls", "出货检验单列表", MesQcOqcImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java
@@ -143,7 +143,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "委外检测试验单导入模板.xls", "委外检测试验单列表", MesQcOutsourceTestImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "委外检测试验单导入模板.xls", "委外检测试验单列表", MesQcOutsourceTestImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/MesQcRqcController.java
@@ -161,7 +161,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "退货检验单导入模板.xls", "退货检验单列表", MesQcRqcImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "退货检验单导入模板.xls", "退货检验单列表", MesQcRqcImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/tm/tool/MesTmToolController.java
@@ -123,7 +123,7 @@
                        .status(1).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "工具导入模板.xls", "工具列表", MesTmToolImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "工具导入模板.xls", "工具列表", MesTmToolImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
@@ -137,7 +137,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "到货通知单导入模板.xls", "到货通知单列表", MesWmArrivalNoticeImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "到货通知单导入模板.xls", "到货通知单列表", MesWmArrivalNoticeImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/equipmentreceipt/MesWmEquipmentReceiptController.java
@@ -120,7 +120,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "设备入库单导入模板.xls", "设备入库单列表", MesWmEquipmentReceiptImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "设备入库单导入模板.xls", "设备入库单列表", MesWmEquipmentReceiptImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
@@ -184,7 +184,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "采购入库单导入模板.xls", "采购入库单列表", MesWmItemReceiptImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "采购入库单导入模板.xls", "采购入库单列表", MesWmItemReceiptImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -81,7 +81,7 @@
    @Operation(summary = "下载库存导入模板")
    @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:adjust')")
    public void importTemplate(HttpServletResponse response) throws IOException {
        ExcelUtils.write(response, "库存导入模板.xls", "库存明细", MesWmStockImportExcelVO.class, Collections.emptyList());
        ExcelUtils.writeTemplate(response, "库存导入模板.xls", "库存明细", MesWmStockImportExcelVO.class, Collections.emptyList());
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscissue/MesWmMiscIssueController.java
@@ -151,7 +151,7 @@
                        .sourceDocType("").sourceDocCode("").remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "杂项出库单导入模板.xls", "杂项出库单列表", MesWmMiscIssueImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "杂项出库单导入模板.xls", "杂项出库单列表", MesWmMiscIssueImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java
@@ -145,7 +145,7 @@
                        .sourceDocType("").sourceDocCode("").remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "杂项入库单导入模板.xls", "杂项入库单列表", MesWmMiscReceiptImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "杂项入库单导入模板.xls", "杂项入库单列表", MesWmMiscReceiptImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java
@@ -162,7 +162,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "外协发料单导入模板.xls", "外协发料单列表", MesWmOutsourceIssueImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "外协发料单导入模板.xls", "外协发料单列表", MesWmOutsourceIssueImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourcereceipt/MesWmOutsourceReceiptController.java
@@ -155,7 +155,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "外协入库单导入模板.xls", "外协入库单列表", MesWmOutsourceReceiptImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "外协入库单导入模板.xls", "外协入库单列表", MesWmOutsourceReceiptImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageController.java
@@ -171,7 +171,7 @@
                        .width(new java.math.BigDecimal("40.00")).remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "装箱单导入模板.xls", "装箱单列表", MesWmPackageImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "装箱单导入模板.xls", "装箱单列表", MesWmPackageImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productissue/MesWmProductIssueController.java
@@ -172,7 +172,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "生产领料出库单导入模板.xls", "生产领料出库单列表", MesWmProductIssueImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "生产领料出库单导入模板.xls", "生产领料出库单列表", MesWmProductIssueImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productreceipt/MesWmProductReceiptController.java
@@ -169,7 +169,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "产品收货单导入模板.xls", "产品收货单列表", MesWmProductReceiptImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "产品收货单导入模板.xls", "产品收货单列表", MesWmProductReceiptImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/MesWmProductSalesController.java
@@ -169,7 +169,7 @@
                        .contactName("张三").remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "销售出库单导入模板.xls", "销售出库单列表", MesWmProductSalesImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "销售出库单导入模板.xls", "销售出库单列表", MesWmProductSalesImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnissue/MesWmReturnIssueController.java
@@ -159,7 +159,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "生产退料单导入模板.xls", "生产退料单列表", MesWmReturnIssueImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "生产退料单导入模板.xls", "生产退料单列表", MesWmReturnIssueImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesController.java
@@ -159,7 +159,7 @@
                        .returnReason("质量问题").remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "销售退货单导入模板.xls", "销售退货单列表", MesWmReturnSalesImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "销售退货单导入模板.xls", "销售退货单列表", MesWmReturnSalesImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java
@@ -176,7 +176,7 @@
                        .returnReason("质量不合格").remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "供应商退货单导入模板.xls", "供应商退货单列表", MesWmReturnVendorImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "供应商退货单导入模板.xls", "供应商退货单列表", MesWmReturnVendorImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/MesWmSalesNoticeController.java
@@ -140,7 +140,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "发货通知单导入模板.xls", "发货通知单列表", MesWmSalesNoticeImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "发货通知单导入模板.xls", "发货通知单列表", MesWmSalesNoticeImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transfer/MesWmTransferController.java
@@ -150,7 +150,7 @@
                        .recipientName("张三").remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "转移单导入模板.xls", "转移单列表", MesWmTransferImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "转移单导入模板.xls", "转移单列表", MesWmTransferImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java
@@ -117,7 +117,7 @@
                        .remark("").build()
        );
        // è¾“出
        ExcelUtils.write(response, "资质证书导入模板.xls", "资质证书列表", SrmSupplierCertificateImportExcelVO.class, list);
        ExcelUtils.writeTemplate(response, "资质证书导入模板.xls", "资质证书列表", SrmSupplierCertificateImportExcelVO.class, list);
    }
    @PostMapping("/import")
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
@@ -14,6 +14,7 @@
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
@@ -43,6 +44,9 @@
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
@@ -210,17 +214,23 @@
    }
    @GetMapping("/get-import-template")
    @Operation(summary = "获得导入用户模板")
    @Operation(summary = "获得导入用户模板", description = "部门按部门名称填写,示例取自当前系统真实部门")
    public void importTemplate(HttpServletResponse response) throws IOException {
        // æ‰‹åŠ¨åˆ›å»ºå¯¼å‡º demo
        // ç¤ºä¾‹å–当前真实部门名称,避免使用者照抄官方 demo å¯¼è‡´ã€Œéƒ¨é—¨ä¸å­˜åœ¨ã€
        List<DeptDO> depts = deptService.getDeptList(new DeptListReqVO());
        String sampleDept1 = CollUtil.isNotEmpty(depts) ? depts.get(0).getName() : "请填写部门名称";
        String sampleDept2 = depts.size() > 1 ? depts.get(1).getName() : sampleDept1;
        List<UserImportExcelVO> 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("zhangsan").deptName(sampleDept1).email("zhangsan@example.com")
                        .mobile("13800000001").nickname("张三")
                        .status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
                UserImportExcelVO.builder().username("lisi").deptName(sampleDept2).email("lisi@example.com")
                        .mobile("13800000002").nickname("李四")
                        .status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
        );
        // è¾“出
        ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list);
        // æ—¥æœŸåˆ—按文本输出,避免显示成 Excel åºåˆ—号
        ExcelUtils.writeTemplate(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list);
    }
    @PostMapping("/import")
@@ -232,7 +242,16 @@
    @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 {
        List<UserImportExcelVO> list = ExcelUtils.read(file, UserImportExcelVO.class);
        // ç©ºæ–‡ä»¶ / éžæ³•文件给出明确提示,避免底层异常变成「系统异常」
        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);
        }
        return success(userService.importUserList(list, updateSupport));
    }
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java
@@ -24,8 +24,8 @@
    @ExcelProperty("用户名称")
    private String nickname;
    @ExcelProperty("部门编号")
    private Long deptId;
    @ExcelProperty("部门名称")
    private String deptName;
    @ExcelProperty("用户邮箱")
    private String email;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -47,6 +47,8 @@
    ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用");
    ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
    ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
    ErrorCode USER_IMPORT_FILE_EMPTY = new ErrorCode(1_002_003_010, "请选择要导入的 Excel æ–‡ä»¶");
    ErrorCode USER_IMPORT_FILE_INVALID = new ErrorCode(1_002_003_011, "Excel æ–‡ä»¶æ ¼å¼ä¸æ­£ç¡®ï¼Œè¯·ä¸‹è½½æœ€æ–°æ¨¡æ¿é‡æ–°å¡«å†™");
    ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
    ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
@@ -14,6 +14,7 @@
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportExcelVO;
@@ -581,6 +582,12 @@
        }
        // 1.2 ç³»ç»Ÿå‚数中的初始密码,作为 Excel æœªå¡«å†™å¯†ç æ—¶çš„兜底
        String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY);
        // 1.3 é¢„加载部门:名称 -> éƒ¨é—¨ï¼Œç”¨äºŽæŒ‰åç§°è§£æžï¼ˆæ¨¡æ¿è¦æ±‚填部门名称,而非数据库主键 ID)
        List<DeptDO> allDepts = deptService.getDeptList(new DeptListReqVO());
        Map<String, DeptDO> deptByName = new HashMap<>(Math.max(allDepts.size(), 1));
        for (DeptDO dept : allDepts) {
            deptByName.put(dept.getName().trim(), dept);
        }
        // 2. éåŽ†ï¼Œé€ä¸ªåˆ›å»º or æ›´æ–°
        UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
@@ -589,6 +596,14 @@
        importUsers.forEach(importUser -> {
            int currentIndex = index.getAndIncrement();
            String key = StrUtil.blankToDefault(importUser.getUsername(), "第 " + currentIndex + " è¡Œ");
            // 2.0 è§£æžéƒ¨é—¨ï¼šæŒ‰éƒ¨é—¨åç§°ç²¾ç¡®åŒ¹é…ï¼ŒåŒ¹é…ä¸åˆ°ç»™å‡ºæ˜Žç¡®æç¤ºï¼ˆä¸å†è¦æ±‚填数据库主键 ID)
            DeptDO importDept = resolveImportDept(importUser.getDeptName(), deptByName);
            if (importDept == null && StrUtil.isNotBlank(importUser.getDeptName())) {
                respVO.getFailureUsernames().put(key,
                        "部门「" + importUser.getDeptName().trim() + "」不存在,请填写部门管理中的完整部门名称");
                return;
            }
            Long importDeptId = importDept != null ? importDept.getId() : null;
            // 2.1.0 è§£æžè¯¥è¡Œåˆå§‹å¯†ç ï¼šExcel å¡«å†™äº†å°±ç”¨å¡«å†™çš„,否则读取系统参数
            String effectivePassword = StrUtil.isNotBlank(importUser.getPassword())
                    ? importUser.getPassword() : initPassword;
@@ -600,7 +615,8 @@
            }
            // 2.1.1 æ ¡éªŒå­—段是否符合要求
            try {
                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(effectivePassword));
                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class)
                        .setPassword(effectivePassword).setDeptId(importDeptId));
            } catch (ConstraintViolationException ex) {
                respVO.getFailureUsernames().put(key, ex.getMessage());
                return;
@@ -608,7 +624,7 @@
            // 2.1.2 æ ¡éªŒï¼Œåˆ¤æ–­æ˜¯å¦æœ‰ä¸ç¬¦åˆçš„原因
            try {
                validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
                        importUser.getDeptId(), null);
                        importDeptId, null);
            } catch (ServiceException ex) {
                respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
                return;
@@ -618,6 +634,7 @@
            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
            if (existUser == null) {
                userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
                        .setDeptId(importDeptId) // éƒ¨é—¨å·²æŒ‰åç§°è§£æžï¼Œéœ€æ˜¾å¼å›žå¡« ID
                        .setPassword(encodePassword(effectivePassword)).setPostIds(new HashSet<>())); // è®¾ç½®åˆå§‹å¯†ç åŠç©ºå²—位编号数组
                respVO.getCreateUsernames().add(importUser.getUsername());
                return;
@@ -629,6 +646,7 @@
            }
            AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
            updateUser.setId(existUser.getId());
            updateUser.setDeptId(importDeptId); // éƒ¨é—¨å·²æŒ‰åç§°è§£æžï¼Œéœ€æ˜¾å¼å›žå¡« ID
            // å¯†ç å•独处理:Excel å¡«å†™äº†æ‰æ›´æ–°ï¼›æœªå¡«å†™åˆ™ç½®ç©ºï¼Œä¿æŒåŽŸå¯†ç ä¸å˜ï¼ˆé¿å…æŠŠæ˜Žæ–‡å†™è¿›åº“ï¼‰
            updateUser.setPassword(StrUtil.isNotBlank(importUser.getPassword())
                    ? encodePassword(importUser.getPassword()) : null);
@@ -638,6 +656,23 @@
        return respVO;
    }
    /**
     * è§£æžå¯¼å…¥è¡Œé‡Œçš„部门名称
     *
     * <p>只按部门名称精确匹配(不再要求填写数据库主键 ID),
     * é¿å…ä½¿ç”¨è€…无从得知部门 ID å¯¼è‡´å¯¼å…¥å¿…然失败。</p>
     *
     * @param raw    å¯¼å…¥å¡«å†™çš„部门名称
     * @param byName éƒ¨é—¨åç§° -> éƒ¨é—¨
     * @return åŒ¹é…åˆ°çš„部门;未填写或匹配不到返回 null
     */
    private DeptDO resolveImportDept(String raw, Map<String, DeptDO> byName) {
        if (StrUtil.isBlank(raw)) {
            return null;
        }
        return byName.get(raw.trim());
    }
    @Override
    public List<AdminUserDO> getUserListByStatus(Integer status) {
        return userMapper.selectListByStatus(status);