package com.yifu.cloud.plus.v1.batch.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.util.ListUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yifu.cloud.plus.v1.batch.constants.ArchiveConstants;
import com.yifu.cloud.plus.v1.batch.entity.TAttaInfo;
import com.yifu.cloud.plus.v1.batch.entity.TElectronicArchiveDetail;
import com.yifu.cloud.plus.v1.batch.entity.TElectronicArchiveTask;
import com.yifu.cloud.plus.v1.batch.entity.TTaskArchiveRelation;
import com.yifu.cloud.plus.v1.batch.mapper.SysConfigLimitMapper;
import com.yifu.cloud.plus.v1.batch.service.*;
import com.yifu.cloud.plus.v1.batch.vo.ArchiveFeedbackVO;
import com.yifu.cloud.plus.v1.batch.vo.ArchiveImportVO;
import com.yifu.cloud.plus.v1.yifu.archives.entity.TEmployeeInfo;
import com.yifu.cloud.plus.v1.yifu.archives.vo.IdNameNoVo;
import com.yifu.cloud.plus.v1.yifu.archives.vo.TSettleDomainListVo;
import com.yifu.cloud.plus.v1.yifu.archives.vo.TSettleDomainSelectVo;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.ClientNameConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.*;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import com.yifu.cloud.plus.v1.yifu.common.dapr.util.ArchivesDaprUtil;
import com.yifu.cloud.plus.v1.yifu.common.security.util.SecurityUtils;
import com.yifu.cloud.plus.v1.yifu.insurances.constants.InsurancesConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 电子档案导入服务实现
 *
 * @author huych
 * @date 2025-12-17
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class ElectronicArchiveImportServiceImpl implements ElectronicArchiveImportService {

	private final TElectronicArchiveTaskService taskService;
	private final TElectronicArchiveDetailService detailService;
	private final TTaskArchiveRelationService relationService;
	private final TAttaInfoService attaInfoService;
	private final FileUploadService fileUploadService;
	private final ArchivesDaprUtil archivesDaprUtil;
	private final CacheManager cacheManager;
	private final OSSUtil ossUtil;
	private final SysConfigLimitMapper configLimitMapper;

	/**
	 * 自注入代理对象，用于解决@Async注解在类内部调用时失效的问题
	 *
	 * @Lazy 延迟加载，避免循环依赖
	 */
	@Autowired
	@Lazy
	private ElectronicArchiveImportService self;

	//初始化zip导入上限值
	private int maxLimit = 1;

	private AtomicInteger atomicInteger = new AtomicInteger(0);


	/**
	 * 第一步：导入Excel模板文件
	 */
	@Override
	public R<?> importExcelTemplate(InputStream inputStream, String fileName) {
		YifuUser user = SecurityUtils.getUser();
		if (null == user) {
			return R.failed("用户未登录");
		}

		// 成功导入的数据
		List<ArchiveImportVO> successList = new ArrayList<>();

		ExcelUtil<ArchiveImportVO> util = new ExcelUtil<>(ArchiveImportVO.class);

		List<ErrorMessage<ArchiveImportVO>> errorMessageList = new ArrayList<>();

		// 将输入流转换为字节数组，以便后续上传到OSS
		byte[] fileBytes;
		try {
			fileBytes = cn.hutool.core.io.IoUtil.readBytes(inputStream);
		} catch (Exception e) {
			log.error("读取文件流失败", e);
			return R.failed("读取文件失败：" + e.getMessage());
		}

		try {
			// 使用字节数组创建新的输入流用于Excel读取
			InputStream excelInputStream = new java.io.ByteArrayInputStream(fileBytes);
			EasyExcel.read(excelInputStream, ArchiveImportVO.class, new ReadListener<ArchiveImportVO>() {
				public static final int BATCH_COUNT = CommonConstants.BATCH_COUNT;
				private List<ArchiveImportVO> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

				@Override
				public void invoke(ArchiveImportVO data, AnalysisContext context) {
					ReadRowHolder readRowHolder = context.readRowHolder();
					Integer rowIndex = readRowHolder.getRowIndex();
					data.setRowIndex(rowIndex + 1);
					ErrorMessage<ArchiveImportVO> errorMessage = util.checkEntity(data, data.getRowIndex());
					if (Common.isNotNull(errorMessage)) {
						errorMessageList.add(errorMessage);
					} else {
						cachedDataList.add(data);
					}
					if (cachedDataList.size() >= BATCH_COUNT) {
						saveData();
						cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
					}
				}

				@Override
				public void doAfterAllAnalysed(AnalysisContext context) {
					saveData();
				}

				private void saveData() {
					if (CollUtil.isNotEmpty(cachedDataList)) {
						processImportData(cachedDataList, errorMessageList, successList, user);
					}
				}
			}).sheet().doRead();

			// 如果校验失败，返回错误信息
			if (CollUtil.isNotEmpty(errorMessageList)) {
				return R.failed(errorMessageList);
			}

			// 校验通过，生成任务表数据
			if (CollUtil.isEmpty(successList)) {
				return R.failed("没有有效的数据可以导入");
			}

			// 生成任务编号
			String taskCode = generateTaskCode();

			// 创建任务
			TElectronicArchiveTask task = new TElectronicArchiveTask();
			task.setTaskCode(taskCode);
			task.setUploadFileName(fileName);
			task.setStatus(ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
			task.setDeleteFlag(CommonConstants.ZERO_STRING);
			taskService.save(task);

			// 保存任务表附件
			saveTaskAttachment(task.getId(), fileName, inputStream, "task_file" + "/" + task.getId() + "_" + fileName);

			// 上传文件到阿里云OSS根目录
			uploadFileToOSS("task_file" + "/" + task.getId() + "_" + fileName, fileBytes);

			// 建立任务和档案详情的关联关系
			createTaskArchiveRelations(task.getId(), successList);

			return R.ok(task.getId(), "全部校验通过");

		} catch (Exception e) {
			log.error("导入Excel文件失败", e);
			return R.failed("导入失败：" + e.getMessage());
		}
	}


	/**
	 * 处理导入数据
	 */
	private void processImportData(List<ArchiveImportVO> dataList, List<ErrorMessage<ArchiveImportVO>> errorMessageList,
								   List<ArchiveImportVO> successList, YifuUser user) {
		TEmployeeInfo empInfo;
		TSettleDomainSelectVo settleResult;
		R<TEmployeeInfo> empResult;
		Map<String, String> map = new HashMap<>();

		List<String> deptNoList = new ArrayList<>();

		boolean isCheck = true;
		List<Long> roleList = user.getClientRoleMap().get(ClientNameConstants.CLIENT_MVP);
		if (CommonConstants.ZERO_STRING.equals(user.getSystemFlag()) ||
				(Common.isNotNull(roleList) && roleList.stream().anyMatch(role -> role == 1569585030423158786L))) {
			isCheck = false;
		}

		if (Common.isNotNull(user) && isCheck) {
			deptNoList = getDeptNoList(user.getId());
		}


		for (ArchiveImportVO data : dataList) {
			try {
				//超管和档案管理员无需校验项目权限
				if (Common.isNotNull(user) && isCheck) {
					if (Common.isEmpty(deptNoList) || !deptNoList.contains(data.getDeptNo())) {
						ErrorMessage<ArchiveImportVO> errorMessage = new ErrorMessage<>(data.getRowIndex()
								, InsurancesConstants.DEPT_NO_NOT_IN_USER_DEPT_LIST, data);
						errorMessageList.add(errorMessage);
						continue;
					}
				}
				// 1. 校验表格中一个姓名、身份证只能有一条
				String uniqueKey = data.getEmpIdcard() + "_" + data.getEmpName();
				if (null != map.get(uniqueKey)) {
					ErrorMessage<ArchiveImportVO> errorMessage = new ErrorMessage<>(data.getRowIndex()
							, "第" + data.getRowIndex() + "行：姓名和身份证信息存在重复数据", data);
					errorMessageList.add(errorMessage);
					continue;
				} else {
					map.put(uniqueKey, "1");
				}

				// 查询项目信息获取客户信息
				settleResult = archivesDaprUtil.selectDeptByNo(data.getDeptNo());
				if (Common.isEmpty(settleResult) || Common.isEmpty(settleResult.getCustomerCode())) {
					ErrorMessage<ArchiveImportVO> errorMessage = new ErrorMessage<>(data.getRowIndex()
							, "第" + data.getRowIndex() + "行：项目编码[" + data.getDeptNo() + "]信息不存在", data);
					errorMessageList.add(errorMessage);
					continue;
				}

				// 查询人员档案信息获取档案所在地
				IdNameNoVo vo = new IdNameNoVo();
				vo.setName(data.getEmpName());
				vo.setNo(data.getEmpIdcard());
				empResult = archivesDaprUtil.getTEmployeeInfoByIdcardAndName(vo);
				if (empResult == null || empResult.getData() == null) {
					ErrorMessage<ArchiveImportVO> errorMessage = new ErrorMessage<>(data.getRowIndex()
							, "第" + data.getRowIndex() + "行：人员档案信息不存在", data);
					errorMessageList.add(errorMessage);
					continue;
				}
				empInfo = empResult.getData();

				// 查找是否存在相同的电子档案明细
				LambdaQueryWrapper<TElectronicArchiveDetail> wrapper = new LambdaQueryWrapper<>();
				wrapper.eq(TElectronicArchiveDetail::getEmpIdcard, data.getEmpIdcard())
						.eq(TElectronicArchiveDetail::getEmpName, data.getEmpName())
						.eq(TElectronicArchiveDetail::getDeptNo, data.getDeptNo())
						.eq(TElectronicArchiveDetail::getDeleteFlag, CommonConstants.ZERO_STRING)
						.last(CommonConstants.LAST_ONE_SQL);
				TElectronicArchiveDetail existDetail = detailService.getOne(wrapper);

				if (existDetail == null) {
					// 不存在，生成新的明细数据
					TElectronicArchiveDetail detail = new TElectronicArchiveDetail();
					detail.setEmpName(data.getEmpName());
					detail.setEmpIdcard(data.getEmpIdcard());
					detail.setDeptNo(data.getDeptNo());
					detail.setDeptId(settleResult.getId());
					detail.setDeptName(settleResult.getDepartName());
					detail.setCustomerName(settleResult.getCustomerName());
					detail.setCustomerCode(settleResult.getCustomerCode());
					detail.setMaterialType(""); // 初始为空，第三步处理时填充
					detail.setStatus(ArchiveConstants.DETAIL_STATUS_DISABLED);
					detail.setDeleteFlag(CommonConstants.ZERO_STRING);

					// 设置档案所在地（需要从empInfo中获取）
					detail.setFileProvince(empInfo.getFileProvince());
					detail.setFileCity(empInfo.getFileCity());
					detail.setFileTown(empInfo.getFileTown());
					detail.setFileAddress(buildFileAddress(empInfo.getFileProvince(), empInfo.getFileCity(), empInfo.getFileTown()));
					detailService.save(detail);
				}
				successList.add(data);

			} catch (Exception e) {
				log.error("处理第" + data.getRowIndex() + "行数据失败", e);
			}
		}
	}

	/**
	 * 保存任务表附件
	 */
	private void saveTaskAttachment(String taskId, String fileName, InputStream inputStream, String filePath) {
		try {
			TAttaInfo attaInfo = new TAttaInfo();
			attaInfo.setAttaName(fileName);
			attaInfo.setRelationType(ArchiveConstants.ATTA_RELATION_TYPE_TASK);
			attaInfo.setDomainId(taskId);
			attaInfo.setTaskId(taskId);
			attaInfo.setAttaSrc(filePath);
			attaInfo.setAttaType(fileName.substring(fileName.lastIndexOf('.')));
			attaInfo.setStatus(ArchiveConstants.ATTA_STATUS_ENABLED);
			attaInfoService.save(attaInfo);
		} catch (Exception e) {
			log.error("保存任务表附件失败", e);
		}
	}

	/**
	 * 上传文件到阿里云OSS
	 *
	 * @param fileName  文件名
	 * @param fileBytes 文件字节数组
	 */
	private void uploadFileToOSS(String fileName, byte[] fileBytes) {
		try {

			// 使用字节数组创建输入流
			InputStream ossInputStream = new java.io.ByteArrayInputStream(fileBytes);

			// 上传到OSS根目录
			ossUtil.uploadFileByStream(ossInputStream, fileName, "yf-hr-efile-source");

		} catch (Exception e) {
			log.error("上传文件到阿里云OSS异常，文件名：{}，异常信息：{}", fileName, e.getMessage(), e);
		}
	}

	/**
	 * 获取项目权限
	 *
	 * @param userId
	 * @return {@link List< String>}
	 * @author huych
	 */
	public List<String> getDeptNoList(String userId) {
		List<String> deptList = new ArrayList<>(16);
		try {
			R<TSettleDomainListVo> settleDomainList = archivesDaprUtil.getSettleDomainIdsByUserId(userId);
			if (Common.isNotNull(settleDomainList) && settleDomainList.getCode() == CommonConstants.SUCCESS) {
				TSettleDomainListVo data = settleDomainList.getData();
				if (Optional.ofNullable(data).isPresent()) {
					deptList = data.getDeptNos();
				}
			}
		} catch (Exception e) {
			log.error("查询项目信息出错：" + e);
		}
		return deptList;
	}

	/**
	 * 建立任务和档案详情的关联关系
	 */
	private void createTaskArchiveRelations(String taskId, List<ArchiveImportVO> successList) {
		try {
			// 根据姓名、身份证、项目编码去重
			Map<String, ArchiveImportVO> uniqueMap = new HashMap<>();
			for (ArchiveImportVO data : successList) {
				String uniqueKey = data.getEmpIdcard() + "_" + data.getEmpName() + "_" + data.getDeptNo();
				uniqueMap.put(uniqueKey, data);
			}

			// 为每个唯一的人员创建关联关系
			for (ArchiveImportVO data : uniqueMap.values()) {
				// 查找对应的电子档案明细
				LambdaQueryWrapper<TElectronicArchiveDetail> wrapper = new LambdaQueryWrapper<>();
				wrapper.eq(TElectronicArchiveDetail::getEmpIdcard, data.getEmpIdcard())
						.eq(TElectronicArchiveDetail::getEmpName, data.getEmpName())
						.eq(TElectronicArchiveDetail::getDeptNo, data.getDeptNo())
						.eq(TElectronicArchiveDetail::getDeleteFlag, CommonConstants.ZERO_STRING)
						.last(CommonConstants.LAST_ONE_SQL);
				TElectronicArchiveDetail detail = detailService.getOne(wrapper);

				if (detail != null) {
					// 创建关联记录
					TTaskArchiveRelation relation = new TTaskArchiveRelation();
					relation.setTaskId(taskId);
					relation.setArchiveDetailId(detail.getId());
					relationService.save(relation);
				}
			}

		} catch (Exception e) {
			log.error("创建任务关联关系失败，任务ID：{}", taskId, e);
		}
	}

	/**
	 * 第二步：导入ZIP文件
	 */
	@Override
	public R<?> importZipFile(MultipartFile file, String taskId) {
		YifuUser user = SecurityUtils.getUser();
		if (null == user) {
			return R.failed("用户未登录");
		}

		// 查询任务
		TElectronicArchiveTask task = taskService.getById(taskId);
		if (task == null) {
			return R.failed("任务不存在");
		}

		if (!ArchiveConstants.TASK_STATUS_NOT_UPLOADED.equals(task.getStatus())) {
			return R.failed("任务状态不正确，无法上传ZIP文件");
		}

		File tempZipFile = null;
		try {
			// 1. 校验ZIP大小
			long fileSize = file.getSize();
			if (fileSize > ArchiveConstants.ZIP_MAX_SIZE) {
				return R.failed("ZIP文件大小不能超过200M");
			}

			// 2. 校验ZIP不为空
			if (file.isEmpty()) {
				return R.failed("ZIP文件不能为空");
			}

			// 3. 校验同时上传任务数
			maxLimit = configLimitMapper.getSysConfigLimitByKey("UPDOLOAD_MAX_LIMIT");
			if (atomicInteger.incrementAndGet() > maxLimit) {
				atomicInteger.decrementAndGet();
				return R.failed("有其他客服正在上传中，请5分钟后重试");
			}

			// 更新任务信息
			task.setUploadZipName(file.getOriginalFilename());
			task.setStatus(ArchiveConstants.TASK_STATUS_GENERATING);
			taskService.updateById(task);

			// 将ZIP文件保存到临时目录
			String tempDirPath = System.getProperty("java.io.tmpdir") + "zip_load/" + System.currentTimeMillis() + "/";
			File tempDir = new File(tempDirPath);
			if (!tempDir.exists() && !tempDir.mkdirs()) {
				throw new IOException("Failed to create directory: " + tempDirPath);
			}

			tempZipFile = new File(tempDir, file.getOriginalFilename());
			file.transferTo(tempZipFile);

			// 保存ZIP附件信息（记录临时文件路径）
			TAttaInfo zipAtta = new TAttaInfo();
			zipAtta.setAttaName(file.getOriginalFilename());
			zipAtta.setAttaSize(fileSize);
			zipAtta.setRelationType(ArchiveConstants.ATTA_RELATION_TYPE_ZIP);
			zipAtta.setDomainId(taskId);
			zipAtta.setTaskId(taskId);
			zipAtta.setStatus(ArchiveConstants.ATTA_STATUS_DISABLED);
			zipAtta.setAttaSrc(tempZipFile.getAbsolutePath()); // 临时存储文件路径
			attaInfoService.save(zipAtta);

			// 调用异步处理（通过self代理对象调用，使@Async生效）
			// 传递用户ID和用户名，因为异步线程无法获取用户上下文
			asyncProcessUpload(taskId, user.getId(), user.getNickname());

			return R.ok("ZIP文件提交成功，正在处理中");

		} catch (Exception e) {
			log.error("导入ZIP文件失败", e);
			// 清理临时文件
			if (tempZipFile != null && tempZipFile.exists()) {
				FileUtil.del(tempZipFile);
			}
			// 回滚任务状态
			task.setStatus(ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
			taskService.updateById(task);
			atomicInteger.decrementAndGet();
			return R.failed("导入ZIP文件失败：" + e.getMessage());
		}
	}

	/**
	 * 第三步：异步处理上传
	 *
	 * @param taskId   任务ID
	 * @param userId   用户ID（用于上传反馈文件）
	 * @param userName 用户名（用于上传反馈文件）
	 */
	@Override
	@Async
	public void asyncProcessUpload(String taskId, String userId, String userName) {

		List<ArchiveFeedbackVO> feedbackList = new ArrayList<>();
		File tempDir = null;
		File zipSourceFile = null;

		try {
			// 查询任务及关联的明细
			TElectronicArchiveTask task = taskService.getById(taskId);
			if (task == null) {
				log.error("任务不存在，任务ID：{}", taskId);
				return;
			}

			// 查询ZIP附件（从附件导入中获取）
			List<TAttaInfo> zipAttas = attaInfoService.list(new LambdaQueryWrapper<TAttaInfo>()
					.eq(TAttaInfo::getDomainId, taskId)
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_ZIP));

			if (CollUtil.isEmpty(zipAttas)) {
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
				log.error("ZIP附件不存在，任务ID：{}", taskId);
				return;
			}

			TAttaInfo zipAtta = zipAttas.get(0);

			// 从附件记录中获取ZIP文件路径
			if (StrUtil.isBlank(zipAtta.getAttaSrc())) {
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
				log.error("ZIP文件路径为空，任务ID：{}", taskId);
				return;
			}

			zipSourceFile = new File(zipAtta.getAttaSrc());
			if (!zipSourceFile.exists()) {
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
				log.error("ZIP文件不存在，任务ID：{}，文件路径：{}", taskId, zipAtta.getAttaSrc());
				return;
			}

			// 获取任务关联的明细
			List<TTaskArchiveRelation> relations = relationService.list(new LambdaQueryWrapper<TTaskArchiveRelation>()
					.eq(TTaskArchiveRelation::getTaskId, taskId));

			if (CollUtil.isEmpty(relations)) {
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
				log.error("任务没有关联的明细数据，任务ID：{}", taskId);
				return;
			}

			List<String> detailIds = relations.stream()
					.map(TTaskArchiveRelation::getArchiveDetailId)
					.collect(Collectors.toList());

			List<TElectronicArchiveDetail> details = detailService.listByIds(detailIds);

			// 创建临时目录用于解压ZIP文件
			String tempDirPath = System.getProperty("java.io.tmpdir") + File.separator + "archive_file" + File.separator
					+ System.currentTimeMillis() + File.separator;
			tempDir = new File(tempDirPath);
			if (!tempDir.exists() && !tempDir.mkdirs()) {
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
				log.error("创建临时目录失败，任务ID：{}，目录路径：{}", taskId, tempDirPath);
				return;
			}

			// 解压ZIP文件
			try {
				// 验证ZIP文件是否可读
				if (!zipSourceFile.canRead()) {
					log.error("ZIP文件不可读，路径：{}", zipSourceFile.getAbsolutePath());
					updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
					return;
				}
				// 使用指定字符集解压（支持中文文件名）
				// Windows系统通常使用GBK编码，Linux使用UTF-8
				try {
					ZipUtil.unzip(zipSourceFile, tempDir, java.nio.charset.Charset.forName("GBK"));
				} catch (Exception e1) {
					// 如果GBK失败，尝试UTF-8
					ZipUtil.unzip(zipSourceFile, tempDir, java.nio.charset.StandardCharsets.UTF_8);
				}
			} catch (Exception e) {
				log.error("解压ZIP文件失败，任务ID：{}，ZIP路径：{}，临时目录：{}",
						taskId, zipSourceFile.getAbsolutePath(), tempDirPath, e);
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
				return;
			}

			// 获取ZIP包的根目录（第一级目录，身份证+姓名目录）
			File[] zipRootDirs = tempDir.listFiles(File::isDirectory);
			Map<String, List<File>> zipDirMap = new HashMap<>();
			if (zipRootDirs != null && zipRootDirs.length > 0) {
				// 将ZIP中的目录按照 身份证_姓名 建立映射，支持同一个人有多个文件夹
				for (File personDir : zipRootDirs) {
					String dirName = personDir.getName();
					Map<String, String> idNameMap = parseIdCardAndName(dirName);
					if (!idNameMap.isEmpty() && idNameMap.containsKey("idCard") && idNameMap.containsKey("name")) {
						String uniqueKey = idNameMap.get("idCard") + "_" + idNameMap.get("name");
						// 使用List存储，支持同一个人有多个文件夹
						zipDirMap.computeIfAbsent(uniqueKey, k -> new ArrayList<>()).add(personDir);
					}
				}
			}

			// 按照第一步Excel导入的明细列表生成反馈表
			// 用于记录每个人员的处理结果（一个人可能有多个资料类型）
			Map<String, List<String>> personMaterialTypesMap = new HashMap<>();
			Map<String, String> personProcessResultMap = new HashMap<>();
			Map<String, String> personFailReasonMap = new HashMap<>();

			// 遍历第一步导入的所有明细
			for (TElectronicArchiveDetail detail : details) {
				String uniqueKey = detail.getEmpIdcard() + "_" + detail.getEmpName();

				// 检查ZIP中是否存在该人员的目录
				List<File> personDirs = zipDirMap.get(uniqueKey);

				if (personDirs == null || personDirs.isEmpty()) {
					// ZIP中没有该人员的文件夹，记录为跳过
					personProcessResultMap.put(uniqueKey, ArchiveConstants.PROCESS_RESULT_SKIP);
					personFailReasonMap.put(uniqueKey, "压缩包中未找到对应文件夹，跳过处理");
					personMaterialTypesMap.put(uniqueKey, new ArrayList<>());
				} else {
					// ZIP中存在该人员的文件夹，处理其附件（可能有多个文件夹）
					List<String> materialTypes = new ArrayList<>();
					List<String> failReasons = new ArrayList<>();

			// 遍历该人员的所有文件夹
			for (File personDir : personDirs) {
				processPersonDirectoryWithResult(personDir, detail, materialTypes, failReasons, taskId, userId, userName);
			}

					personMaterialTypesMap.put(uniqueKey, materialTypes);

					// 判断处理结果
					if (materialTypes.isEmpty()) {
						// 没有成功上传任何资料类型，记录为失败
						personProcessResultMap.put(uniqueKey, ArchiveConstants.PROCESS_RESULT_FAILED);
						if (failReasons.isEmpty()) {
							personFailReasonMap.put(uniqueKey, "文件夹为空或格式不符");
						} else {
							personFailReasonMap.put(uniqueKey, String.join("；", failReasons));
						}
					} else {
						// 有成功上传的资料类型
						if (failReasons.isEmpty()) {
							personProcessResultMap.put(uniqueKey, ArchiveConstants.PROCESS_RESULT_SUCCESS);
							personFailReasonMap.put(uniqueKey, "");
						} else {
							// 部分成功，部分失败
							personProcessResultMap.put(uniqueKey, ArchiveConstants.PROCESS_RESULT_FAILED);
							personFailReasonMap.put(uniqueKey, String.join("；", failReasons));
						}
					}
				}
			}

			// 按照第一步Excel的顺序生成反馈表
			for (TElectronicArchiveDetail detail : details) {
				String uniqueKey = detail.getEmpIdcard() + "_" + detail.getEmpName();

				ArchiveFeedbackVO feedback = new ArchiveFeedbackVO();
				feedback.setEmpName(detail.getEmpName());
				feedback.setEmpIdcard(detail.getEmpIdcard());
				feedback.setDeptNo(detail.getDeptNo());
				feedback.setDeptName(detail.getDeptName());

				// 设置资料类型（多个用逗号拼接）
				List<String> materialTypes = personMaterialTypesMap.get(uniqueKey);
				if (materialTypes != null && !materialTypes.isEmpty()) {
					feedback.setMaterialType(String.join(",", materialTypes));
				} else {
					feedback.setMaterialType("");
				}

				feedback.setProcessResult(personProcessResultMap.get(uniqueKey));
				feedback.setFailReason(personFailReasonMap.get(uniqueKey));

				feedbackList.add(feedback);
			}

		// 处理完成，更新任务状态
		task.setStatus(ArchiveConstants.TASK_STATUS_GENERATED);
		taskService.updateById(task);

		// 生成反馈表（传递用户信息）
		generateFeedbackExcel(task, feedbackList, userId, userName);
		log.info("异步任务处理完成，任务ID：{}", taskId);

		} catch (Exception e) {
			log.error("异步处理任务失败，任务ID：{}", taskId, e);
			// 更新任务状态为失败
			TElectronicArchiveTask task = taskService.getById(taskId);
			if (task != null) {
				updateTaskStatus(task, ArchiveConstants.TASK_STATUS_NOT_UPLOADED);
			}
		} finally {
			// 清理临时解压目录
			if (tempDir != null && tempDir.exists()) {
				try {
					FileUtil.del(tempDir);
				} catch (Exception e) {
				}
			}

			// 清理上传的ZIP文件
			if (zipSourceFile != null && zipSourceFile.exists()) {
				try {
					FileUtil.del(zipSourceFile.getParentFile());
				} catch (Exception e) {
				}
			}
			atomicInteger.decrementAndGet();
		}
	}

	/**
	 * 更新任务状态
	 */
	private void updateTaskStatus(TElectronicArchiveTask task, String status) {
		task.setStatus(status);
		taskService.updateById(task);
	}

	/**
	 * 生成任务编号
	 */
	@Override
	public synchronized String generateTaskCode() {
		String nowDay = DateUtil.getThisDay();
		Cache cache = cacheManager.getCache(CacheConstants.ARCHIVE_TASK_CODE);
		int nowNums = 1;

		if (cache != null) {
			Object nowNumObj = cache.get(nowDay);
			if (Common.isNotNull(nowNumObj)) {
				SimpleValueWrapper value = (SimpleValueWrapper) nowNumObj;
				nowNums = (Integer) value.get() + 1;
			} else {
				// 从数据库查询当天最大编号
				LambdaQueryWrapper<TElectronicArchiveTask> wrapper = new LambdaQueryWrapper<>();
				wrapper.likeRight(TElectronicArchiveTask::getTaskCode, ArchiveConstants.TASK_CODE_PREFIX + nowDay)
						.orderByDesc(TElectronicArchiveTask::getTaskCode)
						.last("LIMIT 1");
				TElectronicArchiveTask lastTask = taskService.getOne(wrapper);

				if (lastTask != null && StrUtil.isNotBlank(lastTask.getTaskCode())) {
					try {
						String codeNum = lastTask.getTaskCode().substring(lastTask.getTaskCode().length() - 3);
						nowNums = Integer.parseInt(codeNum) + 1;
					} catch (Exception e) {
						log.error("解析任务编号失败", e);
						nowNums = 1;
					}
				}
			}
			cache.put(nowDay, nowNums);
		}

		return ArchiveConstants.TASK_CODE_PREFIX + nowDay + String.format("%03d", nowNums);
	}

	/**
	 * 处理单个人员目录（旧方法，保留向后兼容）
	 */
	private void processPersonDirectory(File personDir, Map<String, TElectronicArchiveDetail> detailMap,
										List<ArchiveFeedbackVO> feedbackList, String taskId) {
		String dirName = personDir.getName();

		// 解析身份证和姓名
		Map<String, String> idNameMap = parseIdCardAndName(dirName);
		if (idNameMap.isEmpty() || !idNameMap.containsKey("idCard") || !idNameMap.containsKey("name")) {
			// 目录格式不正确
			ArchiveFeedbackVO feedback = new ArchiveFeedbackVO();
			feedback.setEmpName(dirName);
			feedback.setEmpIdcard("");
			feedback.setProcessResult(ArchiveConstants.PROCESS_RESULT_SKIP);
			feedback.setFailReason("目录名称格式不正确，无法解析身份证和姓名");
			feedbackList.add(feedback);
			return;
		}

		String idCard = idNameMap.get("idCard");
		String name = idNameMap.get("name");
		String uniqueKey = idCard + "_" + name;

		// 检查是否在第一步导入的数据中
		TElectronicArchiveDetail detail = detailMap.get(uniqueKey);
		if (detail == null) {
			// 不在第一步的文档中
			ArchiveFeedbackVO feedback = new ArchiveFeedbackVO();
			feedback.setEmpName(name);
			feedback.setEmpIdcard(idCard);
			feedback.setDeptNo("");
			feedback.setDeptName("");
			feedback.setProcessResult(ArchiveConstants.PROCESS_RESULT_SKIP);
			feedback.setFailReason("压缩包中未找到对应文件夹，跳过处理");
			feedbackList.add(feedback);
			return;
		}

		// 处理第三级目录（资料类型或直接是文件）
		File[] subFiles = personDir.listFiles();
		if (subFiles == null || subFiles.length == 0) {
			// 文件夹为空
			ArchiveFeedbackVO feedback = createFeedbackVO(detail, "", ArchiveConstants.PROCESS_RESULT_FAILED, "文件夹为空");
			feedbackList.add(feedback);
			return;
		}

		// 用于记录该人员的所有资料类型
		Set<String> materialTypes = new HashSet<>();

		// 遍历第三级，按资料类型分组处理所有文件
		for (File subFile : subFiles) {
			if (subFile.isDirectory()) {
				// 是资料类型目录
				String materialType = subFile.getName();
				//存量、新签、续签、减员
				if (materialType.contains("存量")) {
					materialType = "存量";
				} else if (materialType.contains("新签")) {
					materialType = "新签";
				} else if (materialType.contains("续签")) {
					materialType = "续签";
				} else if (materialType.contains("减员")) {
					materialType = "减员";
				}

				if (isValidMaterialType(materialType)) {
					// 不是有效的资料类型，跳过
					continue;
				}
				// 按资料类型分组处理目录下的所有文件
				processMaterialTypeDirectory(subFile, detail, materialType, feedbackList, materialTypes, taskId);
			} else {
				// 直接是文件，默认为存量类型，按单个文件处理
				List<File> singleFileList = new ArrayList<>();
				singleFileList.add(subFile);
				processMaterialTypeFiles(singleFileList, detail, ArchiveConstants.MATERIAL_TYPE_STOCK, feedbackList, materialTypes, taskId);
			}
		}

		// 更新明细表的资料类型和状态
		if (!materialTypes.isEmpty()) {
			updateDetailMaterialType(detail, materialTypes);
		}
	}

	/**
	 * 处理单个人员目录（返回处理结果，用于按Excel顺序生成反馈表）
	 *
	 * @param personDir     人员目录
	 * @param detail        电子档案明细
	 * @param materialTypes 成功处理的资料类型列表（输出参数）
	 * @param failReasons   失败原因列表（输出参数）
	 * @param taskId        任务ID
	 * @param userId        用户ID
	 * @param userName      用户名
	 */
	private void processPersonDirectoryWithResult(File personDir, TElectronicArchiveDetail detail,
												  List<String> materialTypes, List<String> failReasons, String taskId,
												  String userId, String userName) {
		// 处理第三级目录（资料类型或直接是文件）
		File[] subFiles = personDir.listFiles();
		if (subFiles == null || subFiles.length == 0) {
			// 文件夹为空
			failReasons.add("文件夹为空");
			return;
		}

		// 用于记录该人员的所有资料类型
		Set<String> materialTypesSet = new HashSet<>();

		// 收集直接文件（作为存量类型处理）
		List<File> stockFiles = new ArrayList<>();

		// 遍历第三级，按资料类型分组处理所有文件
		for (File subFile : subFiles) {
			if (subFile.isDirectory()) {
				// 是资料类型目录
				String materialType = subFile.getName();
				//存量、新签、续签、减员
				if (materialType.contains("存量")) {
					materialType = "存量";
				} else if (materialType.contains("新签")) {
					materialType = "新签";
				} else if (materialType.contains("续签")) {
					materialType = "续签";
				} else if (materialType.contains("减员")) {
					materialType = "减员";
				}

				if (isValidMaterialType(materialType)) {
					// 不是有效的资料类型，跳过
					continue;
				}

			// 按资料类型分组处理目录下的所有文件
			String result = processMaterialTypeDirectoryWithResult(subFile, detail, materialType, taskId, userId, userName);
			if ("success".equals(result)) {
				materialTypesSet.add(materialType);
			} else {
				failReasons.add(materialType + "：" + result);
			}
		} else {
				// 直接是文件，收集起来统一作为存量类型处理
				stockFiles.add(subFile);
			}
		}

		// 统一处理所有直接文件（存量类型）
		if (!stockFiles.isEmpty()) {
			String result = processMaterialTypeFilesWithResult(stockFiles, detail, ArchiveConstants.MATERIAL_TYPE_STOCK, taskId, userId, userName);
			if ("success".equals(result)) {
				materialTypesSet.add(ArchiveConstants.MATERIAL_TYPE_STOCK);
			} else {
				failReasons.add(ArchiveConstants.MATERIAL_TYPE_STOCK + "：" + result);
			}
		}

		// 更新明细表的资料类型和状态
		if (!materialTypesSet.isEmpty()) {
			materialTypes.addAll(materialTypesSet);
			updateDetailMaterialType(detail, materialTypesSet);
		}
	}

	/**
	 * 按资料类型处理目录下的所有文件
	 * 同一资料类型下的所有文件作为一个整体处理：先验证所有文件，全部通过后才统一上传
	 */
	private void processMaterialTypeDirectory(File directory, TElectronicArchiveDetail detail,
											  String materialType, List<ArchiveFeedbackVO> feedbackList,
											  Set<String> materialTypes, String taskId) {
		// 收集该资料类型目录下的所有文件（递归收集子目录中的文件）
		List<File> allFiles = new ArrayList<>();
		collectAllFiles(directory, allFiles);

		if (allFiles.isEmpty()) {
			// 文件夹为空
			ArchiveFeedbackVO feedback = createFeedbackVO(detail, materialType,
					ArchiveConstants.PROCESS_RESULT_FAILED, "文件夹为空");
			feedbackList.add(feedback);
			return;
		}

		// 统一处理该资料类型下的所有文件
		processMaterialTypeFiles(allFiles, detail, materialType, feedbackList, materialTypes, taskId);
	}

	/**
	 * 按资料类型处理目录下的所有文件（返回处理结果）
	 *
	 * @param directory    资料类型目录
	 * @param detail       电子档案明细
	 * @param materialType 资料类型
	 * @param taskId       任务ID
	 * @param userId       用户ID
	 * @param userName     用户名
	 * @return "success" 表示成功，其他表示失败原因
	 */
	private String processMaterialTypeDirectoryWithResult(File directory, TElectronicArchiveDetail detail,
														  String materialType, String taskId, String userId, String userName) {
		// 收集该资料类型目录下的所有文件（递归收集子目录中的文件）
		List<File> allFiles = new ArrayList<>();
		collectAllFiles(directory, allFiles);

		if (allFiles.isEmpty()) {
			return "文件夹为空";
		}

		// 统一处理该资料类型下的所有文件
		return processMaterialTypeFilesWithResult(allFiles, detail, materialType, taskId, userId, userName);
	}

	/**
	 * 递归收集目录下的所有文件
	 */
	private void collectAllFiles(File directory, List<File> fileList) {
		File[] files = directory.listFiles();
		if (files == null || files.length == 0) {
			return;
		}

		for (File file : files) {
			if (file.isDirectory()) {
				// 递归收集子目录中的文件
				collectAllFiles(file, fileList);
			} else if (file.isFile()) {
				fileList.add(file);
			}
		}
	}

	/**
	 * 统一处理同一资料类型下的所有文件
	 * 1. 先验证所有文件格式，如果有一个失败则整体失败
	 * 2. 全部验证通过后，统一上传到阿里云
	 * 3. 只生成一条反馈记录
	 */
	private void processMaterialTypeFiles(List<File> files, TElectronicArchiveDetail detail,
										  String materialType, List<ArchiveFeedbackVO> feedbackList,
										  Set<String> materialTypes, String taskId) {
		// 第一步：验证所有文件格式
		for (File file : files) {
			String fileName = file.getName();
			if (checkFileFormat(fileName)) {
				// 有文件格式不符，整体失败，不再处理后续文件
				ArchiveFeedbackVO feedback = createFeedbackVO(detail, materialType,
						ArchiveConstants.PROCESS_RESULT_FAILED,
						"文件格式不符，文件格式需为gif|jpg|jpeg|png|bmp|xls|xlsx|csv|pdf|docx|doc|m4a|mp3|zip|rar|txt：" + fileName);
				feedbackList.add(feedback);
				return;
			}
		}

		// 第二步：所有文件格式验证通过，统一上传到阿里云
		try {
			// 构建OSS目录路径
			String ossDirectory = buildOssDirectory(detail, materialType);

			// 依次上传每个文件
			for (File file : files) {
				String fileName = file.getName();

				// 转换为MultipartFile
				MultipartFile multipartFile = fileToMultipartFile(file);
				if (multipartFile == null) {
					// 文件转换失败，整体失败
					ArchiveFeedbackVO feedback = createFeedbackVO(detail, materialType,
							ArchiveConstants.PROCESS_RESULT_FAILED, "文件转换失败：" + fileName);
					feedbackList.add(feedback);
					return;
				}

				// 上传文件到阿里云OSS，uploadFileReturnAtta会自动生成TAttaInfo记录
				R<TAttaInfo> uploadResult = fileUploadService.uploadFileReturnAtta(
						multipartFile,
						ossDirectory,
						CommonConstants.FOUR_STRING,
						detail.getId(),
						taskId
				);

				if (!R.isSuccess(uploadResult)) {
					// 上传失败，整体失败
					ArchiveFeedbackVO feedback = createFeedbackVO(detail, materialType,
							ArchiveConstants.PROCESS_RESULT_FAILED,
							"文件上传失败：" + fileName + "，原因：" + (uploadResult != null ? uploadResult.getMsg() : "未知错误"));
					feedbackList.add(feedback);
					return;
				}
			}

			// 第三步：所有文件上传成功，记录资料类型并生成成功反馈
			materialTypes.add(materialType);
			ArchiveFeedbackVO feedback = createFeedbackVO(detail, materialType,
					ArchiveConstants.PROCESS_RESULT_SUCCESS, "");
			feedbackList.add(feedback);

		} catch (Exception e) {
			log.error("上传文件失败，资料类型：{}，电子档案详情ID：{}，任务ID：{}", materialType, detail.getId(), taskId, e);
			ArchiveFeedbackVO feedback = createFeedbackVO(detail, materialType,
					ArchiveConstants.PROCESS_RESULT_FAILED, "文件上传异常，原因：" + e.getMessage());
			feedbackList.add(feedback);
		}
	}

	/**
	 * 统一处理同一资料类型下的所有文件（返回处理结果）
	 *
	 * @param files        文件列表
	 * @param detail       电子档案明细
	 * @param materialType 资料类型
	 * @param taskId       任务ID
	 * @param userId       用户ID
	 * @param userName     用户名
	 * @return "success" 表示成功，其他表示失败原因
	 */
	private String processMaterialTypeFilesWithResult(List<File> files, TElectronicArchiveDetail detail,
													  String materialType, String taskId, String userId, String userName) {
		// 第一步：验证所有文件格式
		for (File file : files) {
			String fileName = file.getName();
			if (checkFileFormat(fileName)) {
				// 有文件格式不符，整体失败
				return "文件格式不符，文件格式需为gif|jpg|jpeg|png|bmp|xls|xlsx|csv|pdf|docx|doc|m4a|mp3|zip|rar|txt：" + fileName;
			}
		}

		// 第二步：所有文件格式验证通过，统一上传到阿里云
		try {
			// 构建OSS目录路径
			String ossDirectory = buildOssDirectory(detail, materialType);

			// 依次上传每个文件
			for (File file : files) {
				String fileName = file.getName();

				// 转换为MultipartFile
				MultipartFile multipartFile = fileToMultipartFile(file);
				if (multipartFile == null) {
					// 文件转换失败，整体失败
					return "文件转换失败：" + fileName;
				}

			// 上传文件到阿里云OSS，uploadFileReturnAtta会自动生成TAttaInfo记录
			// 传递用户信息，支持异步场景
			R<TAttaInfo> uploadResult = fileUploadService.uploadFileReturnAtta(
					multipartFile,
					ossDirectory,
					CommonConstants.FOUR_STRING,
					detail.getId(),
					taskId,
					userId,
					userName
			);

			if (!R.isSuccess(uploadResult)) {
				// 上传失败，整体失败
				return "文件上传失败：" + fileName + "，原因：" + (uploadResult != null ? uploadResult.getMsg() : "未知错误");
			}
		}

			// 第三步：所有文件上传成功
			return "success";

		} catch (Exception e) {
			log.error("上传文件失败，资料类型：{}，电子档案详情ID：{}，任务ID：{}", materialType, detail.getId(), taskId, e);
			return "文件上传异常，原因：" + e.getMessage();
		}
	}


	/**
	 * 构建OSS目录路径
	 */
	private String buildOssDirectory(TElectronicArchiveDetail detail, String materialType) {
		// 目录格式：项目编码-姓名-身份证号/资料类型
		return detail.getDeptNo() + "-" + detail.getEmpName() + "-" + detail.getEmpIdcard() +
				"/" + materialType;
	}

	/**
	 * 创建反馈VO
	 */
	private ArchiveFeedbackVO createFeedbackVO(TElectronicArchiveDetail detail, String materialType,
											   String processResult, String failReason) {
		ArchiveFeedbackVO feedback = new ArchiveFeedbackVO();
		feedback.setEmpName(detail.getEmpName());
		feedback.setEmpIdcard(detail.getEmpIdcard());
		feedback.setDeptNo(detail.getDeptNo());
		feedback.setDeptName(detail.getDeptName());
		feedback.setMaterialType(materialType);
		feedback.setProcessResult(processResult);
		feedback.setFailReason(failReason);
		return feedback;
	}

	/**
	 * 判断是否是有效的资料类型
	 */
	private boolean isValidMaterialType(String typeName) {
		return !ArchiveConstants.MATERIAL_TYPE_STOCK.equals(typeName) &&
				!ArchiveConstants.MATERIAL_TYPE_NEW.equals(typeName) &&
				!ArchiveConstants.MATERIAL_TYPE_RENEW.equals(typeName) &&
				!ArchiveConstants.MATERIAL_TYPE_LEAVE.equals(typeName);
	}

	/**
	 * 更新明细表的资料类型和状态
	 */
	private void updateDetailMaterialType(TElectronicArchiveDetail detail, Set<String> newMaterialTypes) {
		// 查找是否存在相同身份证姓名和项目编码的可用明细
		LambdaQueryWrapper<TElectronicArchiveDetail> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(TElectronicArchiveDetail::getEmpIdcard, detail.getEmpIdcard())
				.eq(TElectronicArchiveDetail::getEmpName, detail.getEmpName())
				.eq(TElectronicArchiveDetail::getDeptNo, detail.getDeptNo())
				.eq(TElectronicArchiveDetail::getStatus, ArchiveConstants.DETAIL_STATUS_ENABLED)
				.eq(TElectronicArchiveDetail::getDeleteFlag, CommonConstants.ZERO_STRING)
				.last(CommonConstants.LAST_ONE_SQL);

		TElectronicArchiveDetail existDetail = detailService.getOne(wrapper);

		if (existDetail != null) {
			// 存在可用明细，合并资料类型
			Set<String> existTypes = new HashSet<>();
			if (StrUtil.isNotBlank(existDetail.getMaterialType())) {
				existTypes.addAll(Arrays.asList(existDetail.getMaterialType().split(",")));
			}
			existTypes.addAll(newMaterialTypes);

			// 更新资料类型
			existDetail.setMaterialType(String.join(",", existTypes));
			detailService.updateById(existDetail);
		} else {
			// 不存在可用明细，更新当前明细为可用
			detail.setMaterialType(String.join(",", newMaterialTypes));
			detail.setStatus(ArchiveConstants.DETAIL_STATUS_ENABLED);
			detailService.updateById(detail);
		}
	}

	/**
	 * File转MultipartFile
	 */
	private MultipartFile fileToMultipartFile(File file) {
		try {
			FileInputStream input = new FileInputStream(file);
			return new MockMultipartFile(
					"file",
					file.getName(),
					null,
					input
			);
		} catch (Exception e) {
			log.error("File转MultipartFile失败", e);
			return null;
		}
	}

	/**
	 * 生成反馈表Excel
	 *
	 * @param task         任务信息
	 * @param feedbackList 反馈列表
	 * @param userId       用户ID
	 * @param userName     用户名
	 */
	private void generateFeedbackExcel(TElectronicArchiveTask task, List<ArchiveFeedbackVO> feedbackList, 
									   String userId, String userName) {
		if (CollUtil.isEmpty(feedbackList)) {
			log.warn("反馈列表为空，跳过生成反馈表，任务ID：{}", task.getId());
			return;
		}

		try {
			// 创建临时文件
			String tempFilePath = System.getProperty("java.io.tmpdir") + File.separator +
					task.getTaskCode() + "结果反馈表" + ".xlsx";
			File feedbackFile = new File(tempFilePath);

			// 使用EasyExcel生成反馈表
			EasyExcel.write(feedbackFile, ArchiveFeedbackVO.class)
					.sheet("处理结果反馈")
					.doWrite(feedbackList);
			
			log.info("反馈表Excel文件生成成功，文件路径：{}，记录数：{}", tempFilePath, feedbackList.size());

			// 上传反馈表到OSS（传递用户信息）
			if (StrUtil.isNotBlank(userId)) {
				R<TAttaInfo> uploadResult = fileUploadService.uploadFileReturnAtta(
						fileToMultipartFile(feedbackFile),
						"archive/feedback",
						ArchiveConstants.ATTA_RELATION_TYPE_FEEDBACK,
						task.getId(),
						null,  // taskId为null
						userId,
						userName
				);

				if (R.isSuccess(uploadResult)) {
					log.info("反馈表上传成功，任务ID：{}，附件ID：{}", task.getId(), uploadResult.getData().getId());
				} else {
					log.error("反馈表上传失败，任务ID：{}，失败原因：{}", task.getId(), uploadResult.getMsg());
				}
			} else {
				log.error("用户ID为空，无法上传反馈表，任务ID：{}", task.getId());
			}

			// 删除临时文件
			FileUtil.del(feedbackFile);
			log.info("临时反馈表文件已删除：{}", tempFilePath);

		} catch (Exception e) {
			log.error("生成反馈表失败，任务ID：{}，异常信息：", task.getId(), e);
		}
	}

	/**
	 * 解析身份证和姓名信息
	 * 支持格式：姓名-身份证号、身份证号-姓名、姓名身份证号、身份证号姓名
	 */
	private Map<String, String> parseIdCardAndName(String folderName) {
		Map<String, String> result = new HashMap<>();
		if (folderName == null || folderName.trim().isEmpty()) {
			return result;
		}

		// 统一处理全角和半角横线
		folderName = folderName.replace("—", "-");

		// 身份证正则表达式：18位，前17位为数字，最后一位可以是数字或X（大小写）
		String idCardPattern = "\\d{17}[\\dXx]";

		// 情况1：包含分隔符"-"
		if (folderName.contains("-")) {
			String[] parts = folderName.split("-");
			if (parts.length == 2) {
				String part1 = parts[0].trim();
				String part2 = parts[1].trim();

				if (part1.matches(idCardPattern)) {
					result.put("idCard", part1.toUpperCase()); // 统一转为大写
					result.put("name", part2);
				} else if (part2.matches(idCardPattern)) {
					result.put("name", part1);
					result.put("idCard", part2.toUpperCase());
				}
			}
			return result;
		}

		// 情况2：无分隔符，直接拼接
		// 使用正则表达式查找身份证号
		Pattern pattern = Pattern.compile(idCardPattern);
		Matcher matcher = pattern.matcher(folderName);

		if (matcher.find()) {
			String idCard = matcher.group().toUpperCase();
			int start = matcher.start();
			int end = matcher.end();

			// 提取姓名部分（身份证前后部分合并）
			String name;
			if (start == 0) {
				// 身份证在开头
				name = folderName.substring(end).trim();
			} else if (end == folderName.length()) {
				// 身份证在结尾
				name = folderName.substring(0, start).trim();
			} else {
				// 身份证在中间（理论上不应该出现，但处理一下）
				name = (folderName.substring(0, start) + folderName.substring(end)).trim();
			}

			result.put("idCard", idCard);
			result.put("name", name);
		}

		return result;
	}

	/**
	 * 校验文件格式
	 */
	private boolean checkFileFormat(String fileName) {
		if (StrUtil.isBlank(fileName)) {
			return true;
		}

		String ext = FileUtil.extName(fileName).toLowerCase();
		for (String format : ArchiveConstants.SUPPORTED_FILE_FORMATS) {
			if (format.equalsIgnoreCase(ext)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 单个新增人员附件
	 */
	@Override
	public R<?> uploadSinglePersonArchive(String empIdcard, String empName, String deptNo,
										  String deptName, String materialType, MultipartFile[] files) {
		YifuUser user = SecurityUtils.getUser();
		if (null == user) {
			return R.failed("用户未登录");
		}

		// 参数校验
		if (StrUtil.isBlank(empIdcard) || StrUtil.isBlank(empName) || StrUtil.isBlank(deptNo) ||
				StrUtil.isBlank(deptName) || StrUtil.isBlank(materialType)) {
			return R.failed("参数不能为空");
		}

		// 校验附件
		if (files == null || files.length == 0) {
			return R.failed("附件不能为空");
		}

		// 校验资料类型是否有效
		if (isValidMaterialType(materialType)) {
			return R.failed("资料类型不正确，必须为：存量、新签、续签、减员");
		}

		// 校验身份证格式
		if (!empIdcard.matches("\\d{18}")) {
			return R.failed("身份证号格式不正确");
		}

		try {
			// 1. 查询项目信息获取客户信息
			TSettleDomainSelectVo settleResult = archivesDaprUtil.selectDeptByNo(deptNo);
			if (settleResult == null) {
				return R.failed("项目编码[" + deptNo + "]信息不存在");
			}

			// 2. 查询人员档案信息获取档案所在地
			IdNameNoVo vo = new IdNameNoVo();
			vo.setName(empName);
			vo.setNo(empIdcard);
			R<TEmployeeInfo> empResult = archivesDaprUtil.getTEmployeeInfoByIdcardAndName(vo);
			if (empResult == null || empResult.getData() == null) {
				return R.failed("人员档案信息不存在");
			}
			TEmployeeInfo empInfo = empResult.getData();

			// 3. 查找是否存在相同的电子档案明细
			LambdaQueryWrapper<TElectronicArchiveDetail> wrapper = new LambdaQueryWrapper<>();
			wrapper.eq(TElectronicArchiveDetail::getEmpIdcard, empIdcard)
					.eq(TElectronicArchiveDetail::getEmpName, empName)
					.eq(TElectronicArchiveDetail::getDeptNo, deptNo)
					.eq(TElectronicArchiveDetail::getStatus, ArchiveConstants.DETAIL_STATUS_ENABLED)
					.eq(TElectronicArchiveDetail::getDeleteFlag, CommonConstants.ZERO_STRING)
					.last(CommonConstants.LAST_ONE_SQL);
			TElectronicArchiveDetail detail = detailService.getOne(wrapper);

			// 4. 如果不存在，创建新的明细
			if (detail == null) {
				detail = new TElectronicArchiveDetail();
				detail.setEmpName(empName);
				detail.setEmpIdcard(empIdcard);
				detail.setDeptNo(deptNo);
				detail.setDeptName(settleResult.getDepartName());
				detail.setDeptId(settleResult.getId());
				detail.setCustomerName(settleResult.getCustomerName());
				detail.setCustomerCode(settleResult.getCustomerCode());
				detail.setMaterialType(materialType);
				detail.setStatus(ArchiveConstants.DETAIL_STATUS_ENABLED);
				detail.setDeleteFlag(CommonConstants.ZERO_STRING);
				detail.setFileProvince(empInfo.getFileProvince());
				detail.setFileCity(empInfo.getFileCity());
				detail.setFileTown(empInfo.getFileTown());
				detail.setFileAddress(buildFileAddress(empInfo.getFileProvince(), empInfo.getFileCity(), empInfo.getFileTown()));
				detailService.save(detail);
			} else {
				// 存在则更新资料类型（追加）
				Set<String> existTypes = new HashSet<>();
				if (StrUtil.isNotBlank(detail.getMaterialType())) {
					existTypes.addAll(Arrays.asList(detail.getMaterialType().split(",")));
				}
				existTypes.add(materialType);
				detail.setMaterialType(String.join(",", existTypes));
				detailService.updateById(detail);
			}

			// 5. 上传附件到阿里云OSS
			int successCount = 0;
			int failCount = 0;
			StringBuilder failReasons = new StringBuilder();

			for (MultipartFile file : files) {
				if (file == null || file.isEmpty()) {
					continue;
				}

				// 校验文件格式
				String fileName = file.getOriginalFilename();
				if (checkFileFormat(fileName)) {
					failCount++;
					failReasons.append(fileName).append("：文件格式不符；");
					continue;
				}

				try {
					// 构建OSS目录路径
					String ossDirectory = buildOssDirectory(detail, materialType);

					// 上传文件到OSS
					R<TAttaInfo> uploadResult = fileUploadService.uploadFileReturnAtta(
							file,
							ossDirectory,
							CommonConstants.FOUR_STRING,
							detail.getId()
					);

					if (R.isSuccess(uploadResult)) {
						successCount++;
					} else {
						failCount++;
						failReasons.append(fileName).append("：上传失败；");
					}
				} catch (Exception e) {
					log.error("上传文件失败：{}", fileName, e);
					failCount++;
					failReasons.append(fileName).append("：上传异常；");
				}
			}

			// 6. 返回处理结果
			String resultMsg = String.format("处理完成，成功：%d个，失败：%d个", successCount, failCount);
			if (failCount > 0) {
				resultMsg += "。失败原因：" + failReasons.toString();
			}

			return R.ok(null, resultMsg);

		} catch (Exception e) {
			log.error("单个新增人员附件失败", e);
			return R.failed("处理失败：" + e.getMessage());
		}
	}

	/**
	 * 单个新增人员附件（通过附件ID列表）
	 * 前端先调用文件上传接口获取附件ID列表，再调用此方法更新附件的domainId
	 */
	@Override
	public R<?> uploadSinglePersonArchiveByAttaIds(String empIdcard, String empName, String deptNo,
												   String deptName, String materialType, List<String> attaIdList) {
		YifuUser user = SecurityUtils.getUser();
		if (null == user) {
			return R.failed("用户未登录");
		}

		// 参数校验
		if (StrUtil.isBlank(empIdcard) || StrUtil.isBlank(empName) || StrUtil.isBlank(deptNo) ||
				StrUtil.isBlank(deptName) || StrUtil.isBlank(materialType)) {
			return R.failed("参数不能为空");
		}

		// 校验附件ID列表
		if (attaIdList == null || attaIdList.isEmpty()) {
			return R.failed("附件ID列表不能为空");
		}

		// 校验资料类型是否有效
		if (isValidMaterialType(materialType)) {
			return R.failed("资料类型不正确，必须为：存量、新签、续签、减员");
		}

		// 校验身份证格式
		if (!empIdcard.matches("\\d{18}")) {
			return R.failed("身份证号格式不正确");
		}

		try {
			// 1. 查询项目信息获取客户信息
			TSettleDomainSelectVo settleResult = archivesDaprUtil.selectDeptByNo(deptNo);
			if (settleResult == null) {
				return R.failed("项目编码[" + deptNo + "]信息不存在");
			}

			// 2. 查询人员档案信息获取档案所在地
			IdNameNoVo vo = new IdNameNoVo();
			vo.setName(empName);
			vo.setNo(empIdcard);
			R<TEmployeeInfo> empResult = archivesDaprUtil.getTEmployeeInfoByIdcardAndName(vo);
			if (empResult == null || empResult.getData() == null) {
				return R.failed("人员档案信息不存在");
			}
			TEmployeeInfo empInfo = empResult.getData();

			// 3. 查找是否存在相同的电子档案明细
			LambdaQueryWrapper<TElectronicArchiveDetail> wrapper = new LambdaQueryWrapper<>();
			wrapper.eq(TElectronicArchiveDetail::getEmpIdcard, empIdcard)
					.eq(TElectronicArchiveDetail::getEmpName, empName)
					.eq(TElectronicArchiveDetail::getDeptNo, deptNo)
					.eq(TElectronicArchiveDetail::getStatus, ArchiveConstants.DETAIL_STATUS_ENABLED)
					.eq(TElectronicArchiveDetail::getDeleteFlag, CommonConstants.ZERO_STRING)
					.last(CommonConstants.LAST_ONE_SQL);
			TElectronicArchiveDetail detail = detailService.getOne(wrapper);

			// 创建任务,假删除任务
			TElectronicArchiveTask task = new TElectronicArchiveTask();
			task.setTaskCode("DZDAPL_SINGLE");
			task.setUploadFileName("单个上传");
			task.setStatus(ArchiveConstants.TASK_STATUS_GENERATED);
			task.setDeleteFlag(CommonConstants.ONE_STRING);
			taskService.save(task);
			// 4. 如果不存在，创建新的明细
			if (detail == null) {
				detail = new TElectronicArchiveDetail();
				detail.setEmpName(empName);
				detail.setEmpIdcard(empIdcard);
				detail.setDeptNo(deptNo);
				detail.setDeptName(settleResult.getDepartName());
				detail.setDeptId(settleResult.getId());
				detail.setCustomerName(settleResult.getCustomerName());
				detail.setCustomerCode(settleResult.getCustomerCode());
				detail.setMaterialType(materialType);
				detail.setStatus(ArchiveConstants.DETAIL_STATUS_ENABLED);
				detail.setDeleteFlag(CommonConstants.ZERO_STRING);
				detail.setFileProvince(empInfo.getFileProvince());
				detail.setFileCity(empInfo.getFileCity());
				detail.setFileTown(empInfo.getFileTown());
				detail.setFileAddress(buildFileAddress(empInfo.getFileProvince(), empInfo.getFileCity(), empInfo.getFileTown()));
				detailService.save(detail);

			} else {
				// 存在则更新资料类型（追加）
				Set<String> existTypes = new HashSet<>();
				if (StrUtil.isNotBlank(detail.getMaterialType())) {
					existTypes.addAll(Arrays.asList(detail.getMaterialType().split(",")));
				}
				existTypes.add(materialType);
				detail.setMaterialType(String.join(",", existTypes));
				detailService.updateById(detail);
			}

			// 建立任务和档案详情的关联关系
			TTaskArchiveRelation relation = new TTaskArchiveRelation();
			relation.setTaskId(task.getId());
			relation.setArchiveDetailId(detail.getId());
			relationService.save(relation);

			// 5. 更新附件的domainId
			int successCount = 0;
			int failCount = 0;
			StringBuilder failReasons = new StringBuilder();

			for (String attaId : attaIdList) {
				if (StrUtil.isBlank(attaId)) {
					continue;
				}

				try {
					// 查询附件信息
					TAttaInfo attaInfo = attaInfoService.getById(attaId);
					if (attaInfo == null) {
						failCount++;
						failReasons.append("附件ID[").append(attaId).append("]不存在；");
						continue;
					}

					// 更新domainId
					attaInfo.setDomainId(detail.getId());
					attaInfo.setTaskId(task.getId());
					boolean updateSuccess = attaInfoService.updateById(attaInfo);

					if (updateSuccess) {
						successCount++;
					} else {
						failCount++;
						failReasons.append("附件ID[").append(attaId).append("]更新失败；");
					}
				} catch (Exception e) {
					log.error("更新附件domainId失败，附件ID：{}", attaId, e);
					failCount++;
					failReasons.append("附件ID[").append(attaId).append("]更新异常；");
				}
			}

			// 6. 返回处理结果
			String resultMsg = String.format("处理完成，成功：%d个，失败：%d个", successCount, failCount);
			if (failCount > 0) {
				resultMsg += "。失败原因：" + failReasons.toString();
			}

			return R.ok(detail.getId(), resultMsg);

		} catch (Exception e) {
			log.error("单个新增人员附件失败", e);
			return R.failed("处理失败：" + e.getMessage());
		}
	}

	/**
	 * 构建档案所在地地址字符串
	 * 格式：安徽省/合肥市 或 安徽省/合肥市/庐阳区
	 *
	 * @param provinceCode 省代码
	 * @param cityCode     市代码
	 * @param townCode     区代码
	 * @return 档案所在地地址字符串
	 */
	private String buildFileAddress(Integer provinceCode, Integer cityCode, Integer townCode) {
		if (provinceCode == null) {
			return "";
		}

		StringBuilder address = new StringBuilder();

		// 获取省名称
		String provinceName = (String) RedisUtil.redis.opsForValue().get(CacheConstants.AREA_LABEL + provinceCode);
		if (StrUtil.isNotBlank(provinceName)) {
			address.append(provinceName);
		}

		// 获取市名称
		if (Common.isNotNull(cityCode)) {
			String cityName = (String) RedisUtil.redis.opsForValue().get(CacheConstants.AREA_LABEL + cityCode);
			if (StrUtil.isNotBlank(cityName)) {
				if (address.length() > 0) {
					address.append("/");
				}
				address.append(cityName);
			}
		}

		// 获取区名称（如果存在）
		if (Common.isNotNull(townCode)) {
			String townName = (String) RedisUtil.redis.opsForValue().get(CacheConstants.AREA_LABEL + townCode);
			if (StrUtil.isNotBlank(townName)) {
				if (address.length() > 0) {
					address.append("/");
				}
				address.append(townName);
			}
		}

		return address.toString();
	}
}

