package com.ningdatech.pmapi.todocenter.manage;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.xxpt.gateway.shared.api.request.OapiMessageWorkNotificationRequest;
import com.alibaba.xxpt.gateway.shared.api.response.OapiMessageWorkNotificationResponse;
import com.alibaba.xxpt.gateway.shared.client.http.ExecutableClient;
import com.alibaba.xxpt.gateway.shared.client.http.IntelligentGetClient;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ningdatech.basic.enumeration.Status;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.pmapi.common.constant.DingConstant;
import com.ningdatech.pmapi.common.enumeration.ExportOptionEnum;
import com.ningdatech.pmapi.common.model.entity.ExcelExportWriter;
import com.ningdatech.pmapi.common.statemachine.event.ProjectStatusChangeEvent;
import com.ningdatech.pmapi.common.statemachine.util.StateMachineUtils;
import com.ningdatech.pmapi.common.util.ExcelDownUtil;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.todocenter.bean.entity.ProcessComment;
import com.ningdatech.pmapi.todocenter.bean.entity.ProgressNode;
import com.ningdatech.pmapi.todocenter.bean.vo.ProcessProgressDetailVo;
import com.ningdatech.pmapi.todocenter.bean.vo.ProgressNodeAuditInfoVo;
import com.ningdatech.pmapi.todocenter.constant.HisProInsEndActId;
import com.ningdatech.pmapi.todocenter.enumeration.IsAppendProjectEnum;
import com.ningdatech.pmapi.todocenter.enumeration.ProcessStatusEnum;
import com.ningdatech.pmapi.todocenter.extension.cmd.BackToHisApprovalNodeCmd;
import com.ningdatech.pmapi.todocenter.extension.cmd.SaveCommentCmd;
import com.ningdatech.pmapi.todocenter.model.dto.req.ReqProcessHandlerDTO;
import com.ningdatech.pmapi.todocenter.model.dto.req.ReqToBeProcessedDTO;
import com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO;
import com.ningdatech.pmapi.todocenter.zwdd.model.MessageContent;
import com.ningdatech.pmapi.todocenter.zwdd.model.MessageText;
import com.ningdatech.pmapi.user.entity.UserInfo;
import com.ningdatech.pmapi.user.service.IUserInfoService;
import com.wflow.bean.do_.UserDo;
import com.wflow.bean.entity.WflowCcTasks;
import com.wflow.bean.entity.WflowModelHistorys;
import com.wflow.bean.entity.WflowModels;
import com.wflow.exception.BusinessException;
import com.wflow.mapper.WflowCcTasksMapper;
import com.wflow.mapper.WflowModelHistorysMapper;
import com.wflow.service.OrgRepositoryService;
import com.wflow.utils.CodeUtil;
import com.wflow.workflow.bean.dto.ProcessInstanceUserDto;
import com.wflow.workflow.bean.process.OrgUser;
import com.wflow.workflow.bean.process.ProcessNode;
import com.wflow.workflow.bean.process.enums.ApprovalModeEnum;
import com.wflow.workflow.bean.process.enums.NodeTypeEnum;
import com.wflow.workflow.bean.process.form.Form;
import com.wflow.workflow.bean.process.props.ApprovalProps;
import com.wflow.workflow.bean.vo.ProcessInstanceVo;
import com.wflow.workflow.bean.vo.ProcessTaskVo;
import com.wflow.workflow.config.WflowGlobalVarDef;
import com.wflow.workflow.enums.ProcessHandlerEnum;
import com.wflow.workflow.service.FormService;
import com.wflow.workflow.service.*;
import com.wflow.workflow.utils.Executor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Maps;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.history.NativeHistoricTaskInstanceQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.flowable.variable.api.history.HistoricVariableInstanceQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.ningdatech.pmapi.todocenter.constant.WorkNotice.PASS_MSG_TEMPLATE;
import static com.ningdatech.pmapi.todocenter.constant.WorkNotice.PASS_MSG_TEMPLATE2;

/**
 * @author CMM
 * @since 2023/01/12 16:09
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class TodoCenterManage {

    private final TaskService taskService;
    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;
    private final UserDeptOrLeaderService userDeptOrLeaderService;
    private final FormService formService;
    private final ManagementService managementService;
    private final HistoryService historyService;
    private final WflowModelHistorysMapper modelHistorysMapper;
    private final ProcessNodeCatchService nodeCatchService;
    private final OrgRepositoryService orgRepositoryService;
    private final WflowCcTasksMapper ccTasksMapper;
    private final ProcessModelService processModelService;
    private final IUserInfoService userInfoService;
    private final IProjectService projectService;
    private final StateMachineUtils stateMachineUtils;
    @Resource(name = "executableClient")
    private ExecutableClient client;

    /**
     * 待办中心待我处理项目列表查询
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/01
     */
    public PageVo<ResToBeProcessedDTO> queryPendingProjectList(ReqToBeProcessedDTO param) {
        // 获取登录用户ID
        // long userId = LoginUserUtil.getUserId();

        Long userId = 381496L;
        // Long userId = 6418616L;
        // Long userId = 61769799L;
        TaskQuery taskQuery = taskService.createTaskQuery();
        taskQuery.active().taskCandidateOrAssigned(String.valueOf(userId)).orderByTaskCreateTime().desc();
        List<Task> taskList = taskQuery.list();
        List<Project> results = getToBeProcessedProjects(param, taskList);

        Map<String, Task> taskMap = taskList.stream()
                .collect(Collectors.toMap(Task::getProcessInstanceId, t -> t));
        Set<String> processInsIds = results.stream()
                .map(Project::getInstCode)
                .collect(Collectors.toSet());

        Set<String> staterUsers = new HashSet<>();
        // 将申报项目待登录用户处理的流程实例一次性取出来，减少查询次数
        Map<String, HistoricProcessInstance> instanceMap = CollectionUtil.isNotEmpty(taskList)
                ? historyService.createHistoricProcessInstanceQuery()
                .processInstanceIds(processInsIds).list().stream()
                .collect(Collectors.toMap(HistoricProcessInstance::getId, v -> v))
                : new HashMap<>();

        List<ResToBeProcessedDTO> resVos = results.stream().map(d -> {
            ResToBeProcessedDTO res = new ResToBeProcessedDTO();
            BeanUtils.copyProperties(d, res);
            res.setProjectId(d.getId());
            res.setProcessStatusName(ProcessStatusEnum.getDescByCode(d.getProcessStatus()));
            LocalDateTime processLaunchTime = d.getCreateOn();
            String format = NdDateUtils.format(processLaunchTime, "yyyy-MM-dd HH:mm");
            LocalDateTime launchTime = LocalDateTime.parse(format, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
            res.setProcessLaunchTime(launchTime);

            // 从待办任务列表中取出当前登录用户及项目实例对应的任务
            Task task = taskMap.get(d.getInstCode());
            HistoricProcessInstance instance = instanceMap.get(task.getProcessInstanceId());
            // 从缓存取
            staterUsers.add(String.valueOf(userId));
            ProcessTaskVo processTaskVo = ProcessTaskVo.builder().taskId(task.getId()).taskName(task.getName())
                    .taskDefKey(task.getTaskDefinitionKey()).processDefId(task.getProcessDefinitionId())
                    .executionId(task.getExecutionId()).nodeId(task.getTaskDefinitionKey())
                    .deployId(instance.getDeploymentId()).processDefName(instance.getProcessDefinitionName())
                    .version(instance.getProcessDefinitionVersion()).instanceId(task.getProcessInstanceId())
                    .ownerId(instance.getStartUserId()).createTime(instance.getStartTime())
                    .taskCreateTime(task.getCreateTime()).build();
            res.setProcessTaskInfo(processTaskVo);
            return res;
        }).collect(Collectors.toList());
        // 取用户信息，减少数据库查询，一次构建
        List<ResToBeProcessedDTO> result = null;
        if (CollectionUtil.isNotEmpty(staterUsers)) {
            Map<String, OrgUser> userMap = userDeptOrLeaderService.getUserMapByIds(staterUsers);
            result = resVos.stream()
                    .peek(v -> v.getProcessTaskInfo().setOwner(userMap.get(v.getProcessTaskInfo().getOwnerId())))
                    .collect(Collectors.toList());
            // Map<Long,UserInfo> userMap = userInfoService.getUserMapByIds(staterUsers);
            // result = resVos.stream().peek(v -> v.setOwner(userMap.get(userId)))
            // .collect(Collectors.toList());
        }
        return PageVo.of(result, result.size());
    }

    /**
     * 待办中心列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/01
     */
    public void exportPendingProjectList(HttpServletResponse response, ReqToBeProcessedDTO param) {
        param.setPageNumber(1);
        param.setPageSize(10000);
        // 获取登录用户ID
        // long userId = LoginUserUtil.getUserId();

        Long userId = 381496L;
        // Long userId = 6418616L;
        TaskQuery taskQuery = taskService.createTaskQuery();
        taskQuery.active().taskCandidateOrAssigned(String.valueOf(userId)).orderByTaskCreateTime().desc();
        List<Task> taskList = taskQuery.list();
        List<Project> records = getToBeProcessedProjects(param, taskList);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_待我处理_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_待我处理_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);

        // 表体行数据集合
        List<List<String>> rowList = new ArrayList<>();
        // 表体列数据集合
        List<ExportOptionEnum> columnList = param.getExportOptionList();
        for (ExportOptionEnum column : columnList) {
            List<String> columnValues = new ArrayList<>();
            columnValues.add(column.getDesc());
            records.stream().sorted(Comparator.comparing(Project::getId)).forEach(record -> {
                String s = JSON.toJSONString(record);
                JSONObject jsonObject = JSON.parseObject(s);
                String columnValue = String.valueOf(jsonObject.get(column.toString()));
                columnValues.add(columnValue);
            });
            rowList.add(columnValues);
        }
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(rowList);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(rowList);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);
    }

    /**
     * 获取申报项目列表中待登录用户处理的项目列表
     *
     * @param param
     * @param taskList
     * @return java.util.List<com.ningdatech.pmapi.projectlib.model.entity.Project>
     * @author CMM
     * @since 2023/02/11
     */
    private List<Project> getToBeProcessedProjects(ReqToBeProcessedDTO param, List<Task> taskList) {
        Page<Project> page = param.page();
        LambdaQueryWrapper<Project> wrapper = Wrappers.lambdaQuery(Project.class);
        wrapper.like(StrUtil.isNotBlank(param.getProjectName()), Project::getProjectName, param.getProjectName())
                .like(StrUtil.isNotBlank(param.getBuildUnitName()), Project::getBuildOrgName, param.getBuildUnitName())
                .eq(Objects.nonNull(param.getProjectYear()), Project::getProjectYear, param.getProjectYear())
                .eq(Objects.nonNull(param.getIsSupplement()), Project::getIsTemporaryAugment, param.getIsSupplement())
                .ge(Objects.nonNull(param.getProcessLaunchStartTime()), Project::getBeginTime,
                        param.getProcessLaunchStartTime())
                .le(Objects.nonNull(param.getProcessLaunchEndTime()), Project::getEndTime, param.getProcessLaunchEndTime());
        projectService.page(page, wrapper);

        List<String> taskProcessInsIds = taskList.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());

        // 过滤申报项目中待登录用户审核的项目
        return page.getRecords().stream().filter(w -> taskProcessInsIds.contains(w.getInstCode()))
                .collect(Collectors.toList());
    }

    /**
     * 流程处理相关操作
     *
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/01
     */
    public void handler(ReqProcessHandlerDTO param) {

        // 获取登录用户ID

        // Long userId = LoginUserUtil.getUserId();

        Long userId = 381496L;

        // Long userId = 6418616L;

        // 若进行的是撤回操作(流程发起人和当前流程审核人的前一个审核人操作)
        if (param.getAction().equals(ProcessHandlerEnum.WITHDRAW)){
            HistoricTaskInstance handledTaskInstance = historyService.createHistoricTaskInstanceQuery().taskId(param.getTaskId()).singleResult();
            doWithDrawProcess(handledTaskInstance, userId);
            return;
        }
        Task task = taskService.createTaskQuery().taskId(param.getTaskId()).active().singleResult();
        HashMap<String, Object> formData = new HashMap<>(32);
        if (Objects.isNull(task)) {
            throw new BizException("任务不存在");
        }
        switch (param.getAction()) {
            // 通过
            case PASS:
                formService.updateInstanceFormData(param.getInstanceId(), formData);
                doPass(task, userId, param);
                break;
            // 盖章并通过
            case SEAL_PASS:
                formService.updateInstanceFormData(param.getInstanceId(), formData);
                doSealPass(task, userId, param);
                break;
            // 驳回
            case REJECT:
                formService.updateInstanceFormData(param.getInstanceId(), formData);
                doReject(task, userId, param);
                break;
            // 退回
            case BACK:
                formService.updateInstanceFormData(param.getInstanceId(), formData);
                doBackTask(task, userId, param);
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + param.getAction());
        }
    }

    /**
     * 审批任务:驳回
     *
     * @param task   当前任务
     * @param userId
     * @param param  参数
     */
    private void doReject(Task task, Long userId, ReqProcessHandlerDTO param) {
        // 获取当前申报项目
        Project declaredProject = projectService
                .getOne(Wrappers.lambdaQuery(Project.class).eq(Project::getInstCode, task.getProcessInstanceId()));
        String projectName = declaredProject.getProjectName();

        Map<String, Object> var = new HashMap<>(16);
        var.put("approve_" + task.getId(), param.getAction());

        // 保存审核意见
        if (hasComment(param.getAuditInfo())) {
            // 执行自定义的保存评论的功能
            managementService.executeCommand(new SaveCommentCmd(param.getTaskId(), param.getInstanceId(),
                    String.valueOf(userId), JSONObject.toJSONString(param.getAuditInfo())));
        }

        // TODO 中止流程并使项目进入对应状态,给项目创建人、流程发起人发送浙政钉工作通知：【项目名称】的【流程名称】被驳回，请及时处理。

        // 获取bpm对象
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        // 获取根节点即流程发起节点
        FlowNode rootNode = (FlowNode) bpmnModel.getFlowElement("root");

        // TODO 中止流程并使项目进入对应状态,给项目创建人、流程发起人发送浙政钉工作通知：【项目名称】的【流程名称】被驳回，请及时处理。
        // sendWorkNoticeToStartUser(task, projectName, rootNode);

        // 更新项目状态
        updateRejectProjectStatus(userId,declaredProject);
        taskService.complete(param.getTaskId(), var);
    }

    /**
     * 当为驳回操作时，更新项目表中的项目状态
     * @param userId
     * @param declaredProject
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    private void updateRejectProjectStatus(Long userId, Project declaredProject) {
        // 获取当前流程项目状态
        Integer projectStatusSecond = declaredProject.getStatus();
        // 根据当前状态获取对应的通过事件
        ProjectStatusChangeEvent event = stateMachineUtils.getProjectStatusRejectEvent(projectStatusSecond);
        try {
            stateMachineUtils.execute(declaredProject, event);
            declaredProject.setUpdateOn(LocalDateTime.now());
            declaredProject.setUpdateBy(userId);
        } catch (Exception e) {
            throw new BizException("状态机执行失败!");
        }
        // 更新项目状态到下一个状态
        projectService.updateById(declaredProject);
    }

    /**
     * 获取最新版本的流程配置
     *
     * @param task 当前任务
     * @return com.wflow.bean.entity.WflowModels
     * @author CMM
     * @since 2023/02/01 17:30
     */
    private WflowModels getLastWflowModels(Task task) {
        WflowModels wflowModels = processModelService.getOne(
                Wrappers.lambdaQuery(WflowModels.class).eq(WflowModels::getProcessDefId, task.getProcessDefinitionId()));
        if (ObjectUtil.isNull(wflowModels)) {
            log.warn("流程{}不存在", wflowModels.getFormId());
            throw new BusinessException("不存在该表单");
        }
        return wflowModels;
    }

    /**
     * 审批任务:盖章并通过
     *
     * @param task   当前任务
     * @param userId
     * @param param  参数
     */
    private void doSealPass(Task task, Long userId, ReqProcessHandlerDTO param) {
        Map<String, Object> var = new HashMap<>(16);
        var.put("approve_" + task.getId(), param.getAction());

        // TODO 判断项目申报单位级别，区县单位申报有上级主管单位意见栏，市级单位没有

        // TODO 市级单位：为大数据局；区县单位：为大数据中心(根据附件区分？)
        // 获取当前申报项目
        Project declaredProject = projectService
                .getOne(Wrappers.lambdaQuery(Project.class).eq(Project::getInstCode, task.getProcessInstanceId()));
        // 更新项目状态到下一个状态
        updatePassProjectStatus(userId, declaredProject);
        taskService.complete(param.getTaskId(), var);
    }

    /**
     * 审批任务:通过
     *
     * @param task   当前任务
     * @param userId
     * @param param  参数
     */
    private void doPass(Task task, Long userId, ReqProcessHandlerDTO param) {
        String processInstanceId = task.getProcessInstanceId();
        // 获取当前申报项目
        Project declaredProject = projectService
                .getOne(Wrappers.lambdaQuery(Project.class).eq(Project::getInstCode, task.getProcessInstanceId()));
        String projectName = declaredProject.getProjectName();

        Map<String, Object> var = new HashMap<>(16);
        var.put("approve_" + task.getId(), param.getAction());
        // 保存审核意见
        if (hasComment(param.getAuditInfo())) {
            // 执行自定义的保存评论的功能
            managementService.executeCommand(new SaveCommentCmd(param.getTaskId(), param.getInstanceId(),
                    String.valueOf(userId), JSONObject.toJSONString(param.getAuditInfo())));
        }
        // 获取bpm对象
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        // 传节点定义key 获取当前节点
        FlowNode currentNode = (FlowNode) bpmnModel.getFlowElement(task.getTaskDefinitionKey());


        // TODO 若当前流程是预审流程，需要在提交预审申报的时候，调用状态机判断申报后的项目状态，
        //  若是省级部门联审中，要对接外部接口，获取省级部门联审的结果，更新项目状态（预审申报提交的时候处理）

        // 需要先通过后才能有下一个节点的信息
        taskService.complete(param.getTaskId(), var);

        // 获取流程下一个节点的审核用户ID
        String nextUserId = getNextUserId(currentNode, processInstanceId);
        // 获取当前流程状态
        Integer status = declaredProject.getStatus();
        // 若当前登录用户是最后一个审批人
        HistoricProcessInstance instance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        if (HisProInsEndActId.END.equals(instance.getEndActivityId())) {
            switch (Objects.requireNonNull(ProjectStatusEnum.getValue(status))) {
                // 当前项目状态是单位内部审核中
                case UNDER_INTERNAL_AUDIT:
                // 当前项目状态是预审中
                case PRE_APPLYING:
                // 当前项目状态是部门联审中
                case DEPARTMENT_JOINT_REVIEW:
                // 当前项目状态是方案评审中
                case SCHEME_UNDER_REVIEW:
                // 当前项目状态是终验审核中
                case FINAL_ACCEPTANCE_IS_UNDER_REVIEW:
                    updatePassProjectStatus(userId, declaredProject);
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + status);
            }
        }
        // 若有下一个审核人(当前节点的用户)，向其发送浙政钉工作通知：标题：审核任务 内容：【单位名称】的【项目名称】需要您审核。
        if (Objects.nonNull(nextUserId)) {
            // UserInfo auditUserInfo = userInfoService.getById(Long.valueOf(nextUserId));
            // TODO 获取浙政钉用户dingKey，向其发送浙政钉工作通知
            String msg = String.format(PASS_MSG_TEMPLATE, null, projectName);
            // sendWorkNotice(auditUserInfo,msg);
        } else {
            // 若没有，向发起人发送浙政钉工作通知：【项目名称】已通过【流程名称】，请及时开始下一步操作。
            // TODO 向其发送浙政钉工作通知 获取根节点的孩子节点(即发起人节点)，向其发送浙政钉工作通知
            // 获取根节点即流程发起节点
            // FlowNode rootNode = (FlowNode) bpmnModel.getFlowElement("root");
            // sendWorkNoticeToStartUser(task, projectName, rootNode);
        }
    }

    /**
     * 当为通过操作时，更新项目表中项目状态
     * @param userId
     * @param declaredProject
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    private void updatePassProjectStatus(Long userId, Project declaredProject) {
        // 获取当前流程项目状态
        Integer projectStatusSecond = declaredProject.getStatus();
        // 根据当前状态获取对应的通过事件
        ProjectStatusChangeEvent event = stateMachineUtils.getProjectStatusPassEvent(projectStatusSecond);
        try {
            stateMachineUtils.execute(declaredProject, event);
            declaredProject.setUpdateOn(LocalDateTime.now());
            declaredProject.setUpdateBy(userId);
        } catch (Exception e) {
            throw new BizException("状态机执行失败!");
        }
        // 更新项目状态到下一个状态
        projectService.updateById(declaredProject);
    }

    /**
     * 给流程发起人发送工作通知
     *
     * @param task        当前任务
     * @param projectName 项目名称
     * @param rootNode    流程发起节点
     * @return void
     * @author CMM
     * @since 2023/02/03
     */
    private void sendWorkNoticeToStartUser(Task task, String projectName, FlowNode rootNode) {
        String startUserId = getRootUserId(rootNode, task.getProcessInstanceId());
        UserInfo startUserInfo = userInfoService.getById(Long.valueOf(startUserId));
        // 从历史表获取最新版本的流程
        WflowModels wflowModels = getLastWflowModels(task);
        String formName = wflowModels.getFormName();
        String msg = String.format(PASS_MSG_TEMPLATE2, projectName, formName);
        sendWorkNotice(startUserInfo, msg);
    }

    /**
     * 获取流程发起节点的用户ID
     *
     * @param rootNode 根节点
     * @param processInstanceId
     * @return java.lang.String
     * @author CMM
     * @since 2023/02/02
     */
    private String getRootUserId(FlowNode rootNode, String processInstanceId) {
        String rootUserId = null;
        // 输出连线
        List<SequenceFlow> outgoingFlows = rootNode.getOutgoingFlows();
        // 遍历返回下一个节点信息
        for (SequenceFlow currentOutgoingFlow : outgoingFlows) {
            // 类型自己判断
            FlowElement targetFlowElement = currentOutgoingFlow.getTargetFlowElement();
            // TODO 若要会签需判断候选人
            // 发起事件
            if (targetFlowElement instanceof StartEvent) {
                String actId = targetFlowElement.getId();
                ActivityInstance activityInstance = runtimeService.createActivityInstanceQuery()
                        .processInstanceId(processInstanceId).activityId(actId).singleResult();
                String executionId = activityInstance.getExecutionId();
                rootUserId = runtimeService.getVariable(executionId, "initiator", String.class);
                break;
            }
        }
        return rootUserId;

    }

    /**
     * 获取当前节点的下一个节点的审核用户ID
     *
     * @param currentNode 当前节点
     * @param processInstanceId
     * @return java.lang.String 下一个节点的浙政钉用户ID
     * @author CMM
     * @since 2023/02/02
     */
    private String getNextUserId(FlowNode currentNode, String processInstanceId) {
        String nextUserId = null;
        // 输出连线
        List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();
        // 遍历返回下一个节点信息
        for (SequenceFlow currentOutgoingFlow : outgoingFlows) {
            // 类型自己判断
            FlowElement targetFlowElement = currentOutgoingFlow.getTargetFlowElement();
            // 如果下个审批节点为结束节点,那么跳过该节点
            if (targetFlowElement instanceof EndEvent) {
                continue;
            }
            // TODO 若要会签需判断候选人
            // 用户任务
            if (targetFlowElement instanceof UserTask) {
                String actId = targetFlowElement.getId();
                ActivityInstance activityInstance = runtimeService.createActivityInstanceQuery()
                        .processInstanceId(processInstanceId).activityId(actId).singleResult();
                String executionId = activityInstance.getExecutionId();
                nextUserId = runtimeService.getVariable(executionId, "assignee", String.class);
                break;
            }
        }
        return nextUserId;
    }

    /**
     * 判断处理操作是否含有审核意见
     *
     * @param comment 审核意见
     * @return boolean
     * @author CMM
     * @since 2023/02/01
     */
    private boolean hasComment(ProcessComment comment) {
        return Objects.nonNull(comment)
                && (StrUtil.isNotBlank(comment.getText()) || CollectionUtil.isNotEmpty(comment.getAttachments()));
    }

    /**
     * 发送浙政钉工作通知
     *
     * @param auditUserInfo 审核人信息
     * @param msg           要发送的工作通知
     * @return void
     * @author CMM
     * @since 2023/02/01
     */
    private void sendWorkNotice(UserInfo auditUserInfo, String msg) {
        // TODO 获取浙政钉唯一标识
        String dingKey = null;
        IntelligentGetClient intelligentGetClient = client.newIntelligentGetClient(DingConstant.WORKING_NOTICE);
        OapiMessageWorkNotificationRequest request = new OapiMessageWorkNotificationRequest();
        // 消息体(参考下文示例消息格式)
        MessageText messageText = new MessageText();
        messageText.setMsgType("text");
        MessageContent messageContent = new MessageContent();

        messageContent.setContent(msg);
        messageText.setText(messageContent);
        request.setMsg(JSONUtil.toJsonStr(messageText));
        // 构建唯一的消息ID
        // String bizMsgId = "ZB_URGE_NOTICE_" + "_" + auditUserInfo.getDeptId() + "_" + auditUserInfo.getUserId();
        // request.setBizMsgId(bizMsgId);
        request.setBizMsgId(null);
        // 租户id
        // request.setTenantId(GovDingProperties.tenantId.toString());
        request.setReceiverIds(dingKey);
        // 获取结果
        OapiMessageWorkNotificationResponse apiResult = intelligentGetClient.get(request);
        if (!apiResult.getSuccess() || !JSONUtil.parseObj(apiResult.getContent()).getBool("success")) {
            log.warn("发送工作通知失败: {}", apiResult.getContent());
            throw new BizException(Status.BAD_REQUEST.toString());
        }

    }

    /**
     * 撤销流程处理
     *
     * @param handledTaskInstance   已处理的历史任务实例
     * @param userId 当前登录用户ID
     */
    private void doWithDrawProcess(HistoricTaskInstance handledTaskInstance, Long userId) {
        String processInstanceId = handledTaskInstance.getProcessInstanceId();
        // 获取当前流程实例待审核任务信息
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        // 获取当前流程实例信息
        HistoricProcessInstance historicProcessInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        // 流程发起人ID
        String startUserId = historicProcessInstance.getStartUserId();
        // 获取当前申报项目
        Project declaredProject = projectService.getOne(Wrappers.lambdaQuery(Project.class)
                .eq(Project::getInstCode, processInstanceId));
        String projectName = declaredProject.getProjectName();

        // 获取bpm对象
        BpmnModel bpmnModel = repositoryService.getBpmnModel(handledTaskInstance.getProcessDefinitionId());
        // 传节点定义key 获取传入节点(撤回操作人在流程配置中所在的节点)
        FlowNode handledNode = (FlowNode) bpmnModel.getFlowElement(handledTaskInstance.getTaskDefinitionKey());

        // 获取当前流程状态
        Integer status = declaredProject.getStatus();

        // 判断当前登录用户是否是流程发起人
        if (startUserId.equals(String.valueOf(userId))) {
            // TODO 若是流程发起人点击撤回，项目回到上一个状态，并删除当前审核人对应的待办记录
            // 若是流程发起人点击撤回，项目回到上一个状态，需调用状态机更新项目状态
            switch (Objects.requireNonNull(ProjectStatusEnum.getValue(status))) {
                // 当前项目状态是单位内部审核中
                case UNDER_INTERNAL_AUDIT:
                // 当前项目状态是预审中
                case PRE_APPLYING:
               // 当前项目状态是部门联审中
                case DEPARTMENT_JOINT_REVIEW:
               // 当前项目状态是方案评审中
                case SCHEME_UNDER_REVIEW:
               // 当前项目状态是终验审核中
                case FINAL_ACCEPTANCE_IS_UNDER_REVIEW:
                    updateWithdrawProjectStatus(userId, declaredProject);
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + status);
            }
            List<Execution> executions = runtimeService.createExecutionQuery()
                    .processInstanceId(handledTaskInstance.getProcessInstanceId()).onlyChildExecutions().list();
            // 强制流程指向撤回
            runtimeService.createChangeActivityStateBuilder()
                    .processInstanceId(task.getProcessInstanceId())
                    .moveActivityIdTo(task.getTaskDefinitionKey(), HisProInsEndActId.WITHDRAW)
                    .moveExecutionsToSingleActivityId(executions.stream()
                            .map(Execution::getId)
                            .collect(Collectors.toList()), HisProInsEndActId.WITHDRAW)
                    .changeState();
        } else {
            // TODO 获取前一个审核节点审核人信息

            // TODO 判断前一个审核人的部门和当前登录用户的部门是否是同一个，如果是同一个才可以撤回，否则抛出异常
            Boolean orgFlag = true;
            if (orgFlag){
                // 注意：是前一个审核人，说明此时仍在一个审核流程中，项目状态不需要改变
                // 在审核记录中移除前一个审核人提交过的审核意见
                Comment comment = taskService.getProcessInstanceComments(processInstanceId).stream()
                        .filter(c -> c.getTaskId().equals(handledTaskInstance.getId()))
                        .findFirst()
                        .get();
                taskService.deleteComment(comment.getId());
                // 我已处理中去掉自己之前处理的记录
                String taskInstanceId = handledTaskInstance.getId();
                historyService.deleteHistoricTaskInstance(taskInstanceId);

                List<Execution> executions = runtimeService.createExecutionQuery()
                        .processInstanceId(handledTaskInstance.getProcessInstanceId()).onlyChildExecutions().list();
                // 强制流程指向前一个审核人节点
                runtimeService.createChangeActivityStateBuilder()
                        .processInstanceId(task.getProcessInstanceId())
                        .moveActivityIdsToSingleActivityId(executions.stream()
                                .map(Execution::getActivityId)
                                .collect(Collectors.toList()), handledNode.getId())
                        .changeState();
            }else {
                throw new BizException("下一个审核人和您不是同一个部门,无法撤回!");
            }

        }
    }


    /**
     * 当为撤回操作时，更新项目表中的项目状态为前一个状态
     * @param userId
     * @param declaredProject
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    private void updateWithdrawProjectStatus(Long userId, Project declaredProject) {
        // 获取当前流程项目状态
        Integer projectStatusSecond = declaredProject.getStatus();
        // 根据当前状态获取对应的撤回事件
        ProjectStatusChangeEvent event = stateMachineUtils.getProjectStatusWithdrawEvent(projectStatusSecond);
        try {
            stateMachineUtils.execute(declaredProject, event);
            declaredProject.setUpdateOn(LocalDateTime.now());
            declaredProject.setUpdateBy(userId);
        } catch (Exception e) {
            throw new BizException("状态机执行失败!");
        }
        // 更新项目状态
        projectService.updateById(declaredProject);
    }

    /**
     * 退回流程处理
     *
     * @param task   当前任务
     * @param userId 当前登录用户ID
     * @param param  参数
     */
    private void doBackTask(Task task, Long userId, ReqProcessHandlerDTO param) {
        // 获取当前申报项目
        Project declaredProject = projectService
                .getOne(Wrappers.lambdaQuery(Project.class).eq(Project::getInstCode, task.getProcessInstanceId()));
        String projectName = declaredProject.getProjectName();
        // 获取流程定义
        Process process = ProcessDefinitionUtil.getProcess(task.getProcessDefinitionId());
        // 获取根节点即流程发起节点
        FlowNode rootNode = (FlowNode) process.getFlowElement("root", true);
        // TODO 流程变成【被退回】状态，待我处理中，为流程发起人增加一条待办记录，给项目创建人、流程发起人发送浙政钉工作通知：【项目名称】的【流程名称】被退回，请及时处理。
        sendWorkNoticeToStartUser(task, projectName, rootNode);
        // 执行自定义回退逻辑，回退到流程发起人
        managementService
                .executeCommand(new BackToHisApprovalNodeCmd(runtimeService, param.getTaskId(), rootNode.getId()));
        runtimeService.setVariables(param.getInstanceId(),
                Maps.newHashMap("approve_" + param.getTaskId(), param.getAction()));
        log.info("用户[{}] 退回流程[{}] [{} -> {}]", userId, param.getInstanceId(), task.getTaskDefinitionKey(),
                param.getTargetNode());
    }

    /**
     * 查询流程表单数据及审批的进度步骤
     *
     * @param nodeId     当前获取流程人员关联的流程节点ID
     * @param instanceId 流程实例ID
     * @return 流程进度及表单详情
     */
    public ProcessProgressDetailVo getProcessDetail(String nodeId, String instanceId) {
        HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(instanceId).singleResult();
        // 取表单及表单数据
        HistoricVariableInstance forms = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(instanceId)
                .variableName(WflowGlobalVarDef.WFLOW_FORMS)
                .singleResult();
        List<HistoricVariableInstance> formDatas = null;
        if (nodeId.equals("undefined")) {
            List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(instanceId).list();
            formDatas = historyService
                    .createHistoricVariableInstanceQuery()
                    .executionIds(historicTaskInstances.stream().map(HistoricTaskInstance::getExecutionId).collect(Collectors.toSet()))
                    .processInstanceId(instanceId)
                    .list();
        }else {
            formDatas = historyService
                    .createHistoricVariableInstanceQuery()
                    .processInstanceId(instanceId)
                    .list();
        }
        // 取节点设置
        HistoricVariableInstance nodeProps = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(instanceId).variableName(WflowGlobalVarDef.WFLOW_NODE_PROPS).singleResult();
        Map<String, Object> nodePropsValue = (Map<String, Object>) nodeProps.getValue();
        ProcessNode<?> currentNode = null;
        if (StrUtil.isNotBlank(nodeId)) {
            // 搜索当前版本流程的配置
            WflowModelHistorys modelHistory = modelHistorysMapper.selectOne(
                    new QueryWrapper<>(WflowModelHistorys.builder().processDefId(instance.getProcessDefinitionId())
                            .version(instance.getProcessDefinitionVersion()).build()));
            currentNode = nodeCatchService.reloadProcessByStr(modelHistory.getProcess()).get(nodeId);
        }
        UserDo users = orgRepositoryService.getUserById(instance.getStartUserId());
        OrgUser startUser =
                OrgUser.builder().id(users.getUserId()).name(users.getUserName()).avatar(users.getAvatar()).build();

        List<ProgressNode> taskRecords = getHisTaskRecords(instanceId, nodePropsValue);
        // 获取添加抄送任务
        taskRecords.addAll(getCcTaskRecords(instanceId));
        if (ObjectUtil.isNull(instance.getEndTime())) {
            // TODO 下版实现 获取等待中且还未开始的任务，如果存在条件则需要直接解析条件
            taskRecords.addAll(getFutureTask(instanceId));
        }
        taskRecords =
                taskRecords.stream().sorted(Comparator.comparing(ProgressNode::getStartTime)).collect(Collectors.toList());
        taskRecords.add(0,
                ProgressNode.builder().nodeId("root").name("提交申请").user(startUser).nodeType(NodeTypeEnum.ROOT)
                        .startTime(instance.getStartTime()).finishTime(instance.getStartTime()).taskId("root")
                        .result(ProcessHandlerEnum.PASS).build());
        // 提取全量表单数据
        Map<String, Object> formData =
                formDatas.stream().filter(CodeUtil.distinctByKey(HistoricVariableInstance::getVariableName)).collect(
                        Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
        HistoricVariableInstance variableInstance = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(instanceId).variableName("owner").singleResult();
        ProcessInstanceUserDto owner = (ProcessInstanceUserDto) variableInstance.getValue();
        ProcessProgressDetailVo res = ProcessProgressDetailVo.builder().instanceId(instanceId)
                .version(instance.getProcessDefinitionVersion())
                .formItems(formService.filterFormAndDataByPermConfig((List<Form>) forms.getValue(), formData, currentNode))
                .formData(formData).processDefName(instance.getProcessDefinitionName()).startTempUser(startUser)
                .startDept(null == owner ? null : owner.getOrgName()).result(instance.getEndActivityId())
                .startTime(instance.getStartTime()).progress(taskRecords).build();
        // UserInfo userInfo = userInfoService.getById(Long.valueOf(instance.getStartUserId()));
        // res.setStartUser(userInfo);
        if (Objects.isNull(instance.getEndActivityId())) {
            res.setStatus(ProcessStatusEnum.UNDER_REVIEW.name());
        } else if (HisProInsEndActId.BACK.equals(instance.getEndActivityId())) {
            // TODO 被退回的审核节点状态这里只是暂时这么判断，具体怎么保存这个退回状态，后面讨论
            res.setStatus(ProcessStatusEnum.BE_BACKED.name());
        } else if (HisProInsEndActId.REJECT.equals(instance.getEndActivityId())) {
            res.setStatus(ProcessStatusEnum.BE_REJECTED.name());
        } else if (HisProInsEndActId.END.equals(instance.getEndActivityId())) {
            res.setStatus(ProcessStatusEnum.APPROVED.name());
        }
        return res;
    }

    /**
     * 获取抄送的流程实例信息
     *
     * @param instanceId 实例ID
     * @return 抄送我的流程
     */
    private List<ProgressNode> getCcTaskRecords(String instanceId) {
        Set<String> ccUsers = new HashSet<>();
        List<ProgressNode> ccList = ccTasksMapper
                .selectList(new QueryWrapper<WflowCcTasks>().eq("instance_id", instanceId)).stream().map(task -> {
                    ccUsers.add(task.getUserId());
                    return ProgressNode.builder().nodeId(task.getNodeId()).nodeType(NodeTypeEnum.CC)
                            .name(task.getNodeName()).user(OrgUser.builder().id(task.getUserId()).build())
                            .startTime(task.getCreateTime()).finishTime(task.getCreateTime()).build();
                }).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(ccUsers)) {
            Map<String, OrgUser> userMap = userDeptOrLeaderService.getUserMapByIds(ccUsers);
            ccList.stream().peek(v -> v.setUser(userMap.get(v.getUser().getId()))).collect(Collectors.toList());
        }
        return ccList;
    }

    /**
     * 获取流程的审批历史记录
     *
     * @param instanceId 审批实例ID
     * @param nodeProps  节点设置
     * @return 历史记录列表
     */
    private List<ProgressNode> getHisTaskRecords(String instanceId, Map<String, Object> nodeProps) {
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        Set<String> userSet = new HashSet<>();
        // 获取节点处理结果
        Map<String,
                ProcessHandlerEnum> varMap = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(instanceId).variableNameLike("approve_%").list().stream().collect(
                        Collectors.toMap(HistoricVariableInstance::getVariableName, v -> (ProcessHandlerEnum) v.getValue()));

        Map<String, List<ProgressNodeAuditInfoVo>> commentMap = new HashMap<>();
        // 统一处理所有评论数据，省的多次查询
        List<ProgressNodeAuditInfoVo> cmvos =
                taskService.getProcessInstanceComments(instanceId).stream().map(comment -> {
                    userSet.add(comment.getUserId());
                    ProgressNodeAuditInfoVo progressNodeAuditInfoVo =
                            ProgressNodeAuditInfoVo.builder().id(comment.getId()).taskId(comment.getTaskId())
                                    .commentType(comment.getType()).type("COMMENT").createTime(comment.getTime())
                                    .user(UserInfo.builder().id(Long.valueOf(comment.getUserId())).build()).build();
                    ProcessComment processComment = JSONObject.parseObject(comment.getFullMessage(), ProcessComment.class);
                    progressNodeAuditInfoVo.setText(processComment.getText());
                    progressNodeAuditInfoVo.setAttachments(processComment.getAttachments());
                    return progressNodeAuditInfoVo;
                }).collect(Collectors.toList());
        cmvos.forEach(cm -> {
            // 把评论数据按照task进行归类
            String taskId = Optional.ofNullable(cm.getTaskId()).orElse(instanceId);
            List<ProgressNodeAuditInfoVo> vos = commentMap.computeIfAbsent(taskId, k -> new ArrayList<>());
            vos.add(cm);
        });
        // 再将评论按照任务一次构建，方便取出
        Map<String, ProgressNodeAuditInfoVo> commentsMap =
                cmvos.stream().collect(Collectors.toMap(ProgressNodeAuditInfoVo::getTaskId, c -> c));
        List<ProgressNode> progressNodes =
                list.stream().filter(his -> ObjectUtil.isNotNull(his.getTaskId())).map(his -> {
                    Object props = nodeProps.get(his.getActivityId());
                    ApprovalModeEnum approvalMode = null;
                    if (props instanceof ApprovalProps) {
                        approvalMode = ((ApprovalProps) props).getMode();
                    }
                    userSet.add(his.getAssignee());
                    return ProgressNode.builder().nodeId(his.getActivityId()).name(his.getActivityName())
                            .nodeType(NodeTypeEnum.APPROVAL).user(OrgUser.builder().id(his.getAssignee()).build())
                            .startTime(his.getStartTime()).finishTime(his.getEndTime()).taskId(his.getTaskId())
                            .approvalMode(approvalMode).auditInfo(commentsMap.get(his.getTaskId()))
                            .result(varMap.get("approve_" + his.getTaskId())).build();
                }).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(userSet)) {
            Map<String, OrgUser> map = userDeptOrLeaderService.getUserMapByIds(userSet);
            progressNodes.forEach(n -> n.setUser(map.get(n.getUser().getId())));
        }
        return progressNodes;
    }

    private List<ProgressNode> getFutureTask(String instanceId) {
        // 根据流程遍历后续节点，期间要穿越后续包含并行网关和条件网关的节点
        return Collections.emptyList();
    }

    /**
     * 待办中心我已处理项目列表查询
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/03
     */
    public PageVo<ResToBeProcessedDTO> queryHandledProjectList(ReqToBeProcessedDTO param) {
        // 获取登录用户ID
        // long userId = LoginUserUtil.getUserId();

        // Long userId = 381496L;
        Long userId = 6418616L;
        // Long userId = 61769799L;

        // 自定义sql查询所有已办的任务实例
        String nativeSql = "SELECT aht.* FROM ACT_HI_TASKINST AS aht \n"
                + "LEFT JOIN ACT_HI_VARINST AS ahv ON SUBSTRING(ahv.NAME_, 9) = aht.ID_ AND ahv.NAME_ LIKE 'approve_%'\n"
                + "WHERE aht.ASSIGNEE_ = " + userId + " AND ahv.NAME_ IS NOT NULL \n" + "ORDER BY aht.END_TIME_ DESC";
        NativeHistoricTaskInstanceQuery taskInstanceQuery =
                historyService.createNativeHistoricTaskInstanceQuery().sql(nativeSql);
        List<HistoricTaskInstance> taskInstances = taskInstanceQuery.list();

        Set<String> taskProcessInsIds = taskInstances.stream()
                .map(HistoricTaskInstance::getProcessInstanceId)
                .collect(Collectors.toSet());
        List<Project> results = getHandledProjects(param, taskProcessInsIds);

        // 把已办任务实例一次性取出来，减少查询次数
        Map<String, HistoricTaskInstance> taskInstanceMap = taskInstances.stream()
                .collect(Collectors.toMap(HistoricTaskInstance::getProcessInstanceId, v -> v));

        Map<String, HistoricProcessInstance> instanceMap = CollectionUtil.isNotEmpty(taskInstances)
                ? historyService.createHistoricProcessInstanceQuery()
                .processInstanceIds(taskProcessInsIds)
                .list().stream().collect(Collectors.toMap(HistoricProcessInstance::getId, v -> v))
                : new HashMap<>();
        Set<String> staterUsers = new HashSet<>();

        List<ResToBeProcessedDTO> resVos = results.stream().map(d -> {

            ResToBeProcessedDTO res = new ResToBeProcessedDTO();
            BeanUtils.copyProperties(d, res);
            res.setProjectId(d.getId());
            res.setProcessStatusName(ProcessStatusEnum.getDescByCode(d.getProcessStatus()));
            LocalDateTime processLaunchTime = d.getCreateOn();
            String launchTimeFormat = NdDateUtils.format(processLaunchTime, "yyyy-MM-dd HH:mm");
            LocalDateTime launchTime =
                    LocalDateTime.parse(launchTimeFormat, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
            res.setProcessLaunchTime(launchTime);
            LocalDateTime processHandleTime = d.getUpdateOn();
            String handleTimeFormat = NdDateUtils.format(processHandleTime, "yyyy-MM-dd HH:mm");
            LocalDateTime handleTime =
                    LocalDateTime.parse(handleTimeFormat, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
            res.setProcessHandleTime(handleTime);

            // 从已处理任务实例列表中取出当前登录用户及项目实例对应的任务实例
            HistoricTaskInstance task = taskInstanceMap.get(d.getInstCode());
            HistoricProcessInstance instance = instanceMap.get(task.getProcessInstanceId());
            // 从缓存取
            staterUsers.add(instance.getStartUserId());
            ProcessTaskVo processTaskVo = ProcessTaskVo.builder().taskId(task.getId()).taskName(task.getName())
                    .taskDefKey(task.getTaskDefinitionKey()).processDefId(task.getProcessDefinitionId())
                    .executionId(task.getExecutionId()).nodeId(task.getTaskDefinitionKey())
                    .deployId(instance.getDeploymentId()).processDefName(instance.getProcessDefinitionName())
                    .version(instance.getProcessDefinitionVersion()).instanceId(task.getProcessInstanceId())
                    .ownerId(instance.getStartUserId()).createTime(instance.getStartTime())
                    .taskCreateTime(task.getCreateTime()).build();
            res.setProcessTaskInfo(processTaskVo);
            return res;
        }).collect(Collectors.toList());
        // 取用户信息，减少数据库查询，一次构建
        List<ResToBeProcessedDTO> result = null;
        if (CollectionUtil.isNotEmpty(staterUsers)) {
            Map<String, OrgUser> userMap = userDeptOrLeaderService.getUserMapByIds(staterUsers);
            result = resVos.stream()
                    .peek(v -> v.getProcessTaskInfo().setOwner(userMap.get(v.getProcessTaskInfo().getOwnerId())))
                    .collect(Collectors.toList());
            // Map<Long,UserInfo> userMap = userInfoService.getUserMapByIds(staterUsers);
            // result = resVos.stream().peek(v -> v.setOwner(userMap.get(userId)))
            // .collect(Collectors.toList());

        }
        return PageVo.of(result, result.size());
    }

    /**
     * 获取申报项目列表中登录用户已处理项目列表
     *
     * @param param
     * @param taskProcessInsIds
     * @return java.util.List<com.ningdatech.pmapi.projectlib.model.entity.Project>
     * @author CMM
     * @since 2023/02/11
     */
    private List<Project> getHandledProjects(ReqToBeProcessedDTO param, Set<String> taskProcessInsIds) {
        // 获取入参分页信息
        Page<Project> page = param.page();
        LambdaQueryWrapper<Project> wrapper = Wrappers.lambdaQuery(Project.class);
        wrapper.like(StrUtil.isNotBlank(param.getProjectName()), Project::getProjectName, param.getProjectName())
                .like(StrUtil.isNotBlank(param.getBuildUnitName()), Project::getBuildOrgName, param.getBuildUnitName())
                .eq(Objects.nonNull(param.getProjectYear()), Project::getProjectYear, param.getProjectYear())
                .eq(Objects.nonNull(param.getIsSupplement()), Project::getIsTemporaryAugment, param.getIsSupplement())
                .ge(Objects.nonNull(param.getProcessLaunchStartTime()), Project::getBeginTime,
                        param.getProcessLaunchStartTime())
                .le(Objects.nonNull(param.getProcessLaunchEndTime()), Project::getEndTime, param.getProcessLaunchEndTime());
        projectService.page(page, wrapper);

        // 过滤申报项目中登录用户已审核的项目
        return page.getRecords().stream()
                .filter(w -> taskProcessInsIds.contains(w.getInstCode()))
                .collect(Collectors.toList());
    }

    /**
     * 导出我已处理项目列表
     *
     * @param response 返回数据流
     * @param param    请求参数
     * @return void
     * @author CMM
     * @since 2023/02/06
     */
    public void exportHandledProjectList(HttpServletResponse response, ReqToBeProcessedDTO param) {
        param.setPageNumber(1);
        param.setPageSize(10000);
        // 获取登录用户ID
        // long userId = LoginUserUtil.getUserId();
        Long userId = 381496L;
        // 自定义sql查询所有已办的任务实例
        String nativeSql = "SELECT aht.* FROM ACT_HI_TASKINST AS aht \n"
                + "LEFT JOIN ACT_HI_VARINST AS ahv ON SUBSTRING(ahv.NAME_, 9) = aht.ID_ AND ahv.NAME_ LIKE 'approve_%'\n"
                + "WHERE aht.ASSIGNEE_ = " + userId + " AND ahv.NAME_ IS NOT NULL \n" + "ORDER BY aht.END_TIME_ DESC";
        NativeHistoricTaskInstanceQuery taskInstanceQuery =
                historyService.createNativeHistoricTaskInstanceQuery().sql(nativeSql);
        List<HistoricTaskInstance> taskInstances = taskInstanceQuery.list();

        // 把已办任务流程实例一次性取出来，减少查询次数
        Set<String> taskProcessInsIds = taskInstances.stream()
                .map(HistoricTaskInstance::getProcessInstanceId)
                .collect(Collectors.toSet());
        List<Project> records = getHandledProjects(param, taskProcessInsIds);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_我已处理_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_我已处理_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);

        // 表体行数据集合
        List<List<String>> rowList = new ArrayList<>();
        // 表体列数据集合
        List<ExportOptionEnum> columnList = param.getExportOptionList();
        for (ExportOptionEnum column : columnList) {
            List<String> columnValues = new ArrayList<>();
            columnValues.add(column.getDesc());
            records.stream().sorted(Comparator.comparing(Project::getId)).forEach(record -> {
                String s = JSON.toJSONString(record);
                JSONObject jsonObject = JSON.parseObject(s);
                String columnValue = String.valueOf(jsonObject.get(column.toString()));
                columnValues.add(columnValue);
            });
            rowList.add(columnValues);
        }
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(rowList);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(rowList);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 查询我提交的项目列表
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/06
     */
    public PageVo<ResToBeProcessedDTO> queryMySubmittedProjectList(ReqToBeProcessedDTO param) {

        // Long startUserId = LoginUserUtil.getUserId();
        Long startUserId = 381496L;
        HistoricProcessInstanceQuery instanceQuery = historyService.createHistoricProcessInstanceQuery();
        Executor.builder().ifNotBlankNext(String.valueOf(startUserId), instanceQuery::startedBy);
        List<HistoricProcessInstance> historicProcessInstances =
                instanceQuery.orderByProcessInstanceStartTime().desc().orderByProcessInstanceEndTime().desc().list();
        Set<String> historicProcessInstanceIds =
                historicProcessInstances.stream().map(h -> h.getId()).collect(Collectors.toSet());

        List<Project> results = getMySubmittedProjects(param, historicProcessInstanceIds);

        // 把已办任务流程实例一次性取出来，减少查询次数
        Map<String, HistoricProcessInstance> instanceMap = CollectionUtil.isNotEmpty(historicProcessInstances) ? historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceIds(historicProcessInstances.stream().map(h -> h.getId()).collect(Collectors.toSet()))
                .list().stream().collect(Collectors.toMap(HistoricProcessInstance::getId, v -> v)) : new HashMap<>();

        Set<String> staterUsers = new HashSet<>();

        List<ResToBeProcessedDTO> resVos =
                results.stream().map(d -> {
                    ResToBeProcessedDTO res = new ResToBeProcessedDTO();
                    BeanUtils.copyProperties(d, res);
                    res.setProjectId(d.getId());
                    res.setProcessStatusName(ProcessStatusEnum.getDescByCode(d.getProcessStatus()));
                    LocalDateTime processLaunchTime = d.getCreateOn();
                    String launchTimeFormat = NdDateUtils.format(processLaunchTime, "yyyy-MM-dd HH:mm");
                    LocalDateTime launchTime =
                            LocalDateTime.parse(launchTimeFormat, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
                    res.setProcessLaunchTime(launchTime);
                    HistoricProcessInstance ist = instanceMap.get(d.getInstCode());
                    staterUsers.add(ist.getStartUserId());
                    ProcessInstanceVo processInstanceVo = getProcessInstanceVos(ist);
                    res.setProcessInstanceInfo(processInstanceVo);
                    return res;
                }).collect(Collectors.toList());

        if (CollectionUtil.isNotEmpty(staterUsers)) {
            Map<String, OrgUser> userMap = userDeptOrLeaderService.getUserMapByIds(staterUsers);
            resVos.stream().map(v -> {
                v.getProcessInstanceInfo().setStaterUser(userMap.get(v.getProcessInstanceInfo().getStaterUserId()));
                return v;
            }).collect(Collectors.toList());
        }

        // if (CollectionUtil.isNotEmpty(staterUsers)) {
        // Map<Long, UserInfo> userMap = userInfoService.getUserMapByIds(staterUsers);
        // resVos.stream().peek(v -> v.setOwner(userMap.get(startUserId)))
        // .collect(Collectors.toList());
        // }
        return PageVo.of(resVos, resVos.size());
    }

    /**
     * 获取申报项目列表中当前登录用户发起的项目列表
     *
     * @param param
     * @param historicProcessInstanceIds
     * @return java.util.List<com.ningdatech.pmapi.projectlib.model.entity.Project>
     * @author CMM
     * @since 2023/02/11
     */
    private List<Project> getMySubmittedProjects(ReqToBeProcessedDTO param, Set<String> historicProcessInstanceIds) {
        Page<Project> page = param.page();
        LambdaQueryWrapper<Project> wrapper = Wrappers.lambdaQuery(Project.class);
        wrapper.like(StrUtil.isNotBlank(param.getProjectName()), Project::getProjectName, param.getProjectName())
                .like(StrUtil.isNotBlank(param.getBuildUnitName()), Project::getBuildOrgName, param.getBuildUnitName())
                .eq(Objects.nonNull(param.getProjectYear()), Project::getProjectYear, param.getProjectYear())
                .eq(Objects.nonNull(param.getIsSupplement()), Project::getIsTemporaryAugment, param.getIsSupplement())
                .ge(Objects.nonNull(param.getProcessLaunchStartTime()), Project::getBeginTime,
                        param.getProcessLaunchStartTime())
                .le(Objects.nonNull(param.getProcessLaunchEndTime()), Project::getEndTime, param.getProcessLaunchEndTime());
        projectService.page(page, wrapper);

        // 筛选出申报项目中我发起的项目
        List<Project> results = page.getRecords().stream()
                .filter(d -> historicProcessInstanceIds.contains(d.getInstCode()))
                .collect(Collectors.toList());
        return results;
    }

    /**
     * 获取流程实例信息
     *
     * @param ist 流程实例
     * @return java.util.List<com.wflow.workflow.bean.vo.ProcessInstanceVo>
     * @author CMM
     * @since 2023/02/06
     */
    private ProcessInstanceVo getProcessInstanceVos(HistoricProcessInstance ist) {
        Map<String, String> instanceNodeMap = new HashMap<>();
        ProcessInstanceVo instanceVo = ProcessInstanceVo.builder().processDefId(ist.getProcessDefinitionId())
                .instanceId(ist.getId()).nodeId(instanceNodeMap.get(ist.getId())).formId(ist.getProcessDefinitionKey())
                .staterUserId(ist.getStartUserId()).startTime(ist.getStartTime()).finishTime(ist.getEndTime())
                .processDefName(ist.getProcessDefinitionName()).result(ist.getEndActivityId())
                .version(ist.getProcessDefinitionVersion()).build();

        if (Objects.isNull(ist.getEndActivityId())) {
            instanceVo.setStatus(ProcessStatusEnum.UNDER_REVIEW.name());
        } else if (HisProInsEndActId.BACK.equals(ist.getEndActivityId())) {
            // TODO 被退回的审核节点状态这里只是暂时这么判断，具体怎么保存这个退回状态，后面讨论
            instanceVo.setStatus(ProcessStatusEnum.BE_BACKED.name());
        } else if (HisProInsEndActId.REJECT.equals(ist.getEndActivityId())) {
            instanceVo.setStatus(ProcessStatusEnum.BE_REJECTED.name());
        } else if (HisProInsEndActId.END.equals(ist.getEndActivityId())) {
            instanceVo.setStatus(ProcessStatusEnum.APPROVED.name());
        }

        if (ObjectUtil.isNull(ist.getEndActivityId())) {
            // 没有结束，还在走流程，获取任务
            List<Task> list =
                    taskService.createTaskQuery().processInstanceId(ist.getId()).includeIdentityLinks().active().list();
            instanceVo.setNodeId(Optional.ofNullable(instanceVo.getNodeId()).orElseGet(() -> {
                if (CollectionUtil.isNotEmpty(list)) {
                    return list.get(0).getTaskDefinitionKey();
                }
                return null;
            }));
            instanceVo.setTaskName(StrUtil.join("、", list.stream().map(TaskInfo::getName).collect(Collectors.toSet())));
        } else {
            if (HisProInsEndActId.WITHDRAW.equals(ist.getEndActivityId())) {
                instanceVo.setTaskName(ProcessStatusEnum.WITHDRAW.getDesc());
            } else if (HisProInsEndActId.REJECT.equals(ist.getEndActivityId())) {
                instanceVo.setTaskName(ProcessStatusEnum.BE_REJECTED.getDesc());
            } else {
                instanceVo.setTaskName(ProcessStatusEnum.APPROVED.getDesc());
            }
        }

        return instanceVo;
    }

    /**
     * 待办中心-我发起的项目列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/11
     */
    public void exportMySubmittedProjectList(HttpServletResponse response, ReqToBeProcessedDTO param) {

        param.setPageNumber(1);
        param.setPageSize(10000);
        // Long startUserId = LoginUserUtil.getUserId();
        Long startUserId = 381496L;
        HistoricProcessInstanceQuery instanceQuery = historyService.createHistoricProcessInstanceQuery();
        Executor.builder().ifNotBlankNext(String.valueOf(startUserId), instanceQuery::startedBy);
        List<HistoricProcessInstance> historicProcessInstances = instanceQuery
                .orderByProcessInstanceStartTime().desc()
                .orderByProcessInstanceEndTime().desc()
                .list();
        Set<String> historicProcessInstanceIds = historicProcessInstances.stream()
                .map(h -> h.getId())
                .collect(Collectors.toSet());
        List<Project> records = getMySubmittedProjects(param, historicProcessInstanceIds);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_我发起的_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_我发起的_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);

        // 表体行数据集合
        List<List<String>> rowList = new ArrayList<>();
        // 表体列数据集合
        List<ExportOptionEnum> columnList = param.getExportOptionList();
        for (ExportOptionEnum column : columnList) {
            List<String> columnValues = new ArrayList<>();
            columnValues.add(column.getDesc());
            records.stream().sorted(Comparator.comparing(Project::getId)).forEach(record -> {
                String s = JSON.toJSONString(record);
                JSONObject jsonObject = JSON.parseObject(s);
                String columnValue = String.valueOf(jsonObject.get(column.toString()));
                columnValues.add(columnValue);
            });
            rowList.add(columnValues);
        }
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(rowList);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(rowList);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 待办中心-抄送我的项目列表查询
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/11
     */
    public PageVo<ResToBeProcessedDTO> queryCcMeProjectList(ReqToBeProcessedDTO param) {
        // 获取当前登录用户ID
        // Long userId = LoginUserUtil.getUserId();

        Long userId = 381496L;
        List<WflowCcTasks> ccTasks = ccTasksMapper.selectList(Wrappers.lambdaQuery(WflowCcTasks.class)
                .eq(WflowCcTasks::getUserId, String.valueOf(userId)).orderByDesc(WflowCcTasks::getCreateTime));
        List<String> processInsIds = ccTasks.stream()
                .map(WflowCcTasks::getInstanceId)
                .collect(Collectors.toList());

        List<Project> results = getCcMeProjects(param, processInsIds);

        Set<String> staterUsers = new HashSet<>();
        // 将抄送我的流程实例一次性取出来，减少查询次数
        Map<String, HistoricProcessInstance> instanceMap = CollectionUtil.isNotEmpty(ccTasks)
                ? historyService.createHistoricProcessInstanceQuery()
                .processInstanceIds(ccTasks.stream().map(WflowCcTasks::getInstanceId).collect(Collectors.toSet()))
                .list().stream().collect(Collectors.toMap(HistoricProcessInstance::getId, v -> v))
                : new HashMap<>();
        List<ResToBeProcessedDTO> resVos =
                results.stream().map(d -> {
                    ResToBeProcessedDTO res = new ResToBeProcessedDTO();
                    BeanUtils.copyProperties(d, res);
                    res.setProjectId(d.getId());
                    res.setProcessStatusName(ProcessStatusEnum.getDescByCode(d.getProcessStatus()));
                    LocalDateTime processLaunchTime = d.getCreateOn();
                    String launchTimeFormat = NdDateUtils.format(processLaunchTime, "yyyy-MM-dd HH:mm");
                    LocalDateTime launchTime =
                            LocalDateTime.parse(launchTimeFormat, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
                    res.setProcessLaunchTime(launchTime);
                    HistoricProcessInstance ist = instanceMap.get(d.getInstCode());
                    staterUsers.add(ist.getStartUserId());
                    ProcessInstanceVo processInstanceVo = getProcessInstanceVos(ist);
                    res.setProcessInstanceInfo(processInstanceVo);
                    return res;
                }).collect(Collectors.toList());

        if (CollectionUtil.isNotEmpty(staterUsers)) {
            Map<String, OrgUser> userMap = userDeptOrLeaderService.getUserMapByIds(staterUsers);
            resVos.stream().map(v -> {
                v.getProcessInstanceInfo().setStaterUser(userMap.get(v.getProcessInstanceInfo().getStaterUserId()));
                return v;
            }).collect(Collectors.toList());
        }

        // if (CollectionUtil.isNotEmpty(staterUsers)) {
        // Map<Long, UserInfo> userMap = userInfoService.getUserMapByIds(staterUsers);
        // resVos.stream().peek(v -> v.setOwner(userMap.get(startUserId)))
        // .collect(Collectors.toList());
        // }
        return PageVo.of(resVos, resVos.size());
    }

    /**
     * 获取申报项目表中抄送当前登录用户的项目列表
     *
     * @param param
     * @param processInsIds
     * @return java.util.List<com.ningdatech.pmapi.projectlib.model.entity.Project>
     * @author CMM
     * @since 2023/02/11
     */
    private List<Project> getCcMeProjects(ReqToBeProcessedDTO param, List<String> processInsIds) {
        Page<Project> page = param.page();
        LambdaQueryWrapper<Project> wrapper = Wrappers.lambdaQuery(Project.class);
        wrapper.like(StrUtil.isNotBlank(param.getProjectName()), Project::getProjectName, param.getProjectName())
                .like(StrUtil.isNotBlank(param.getBuildUnitName()), Project::getBuildOrgName, param.getBuildUnitName())
                .eq(Objects.nonNull(param.getProjectYear()), Project::getProjectYear, param.getProjectYear())
                .eq(Objects.nonNull(param.getIsSupplement()), Project::getIsTemporaryAugment, param.getIsSupplement())
                .ge(Objects.nonNull(param.getProcessLaunchStartTime()), Project::getBeginTime,
                        param.getProcessLaunchStartTime())
                .le(Objects.nonNull(param.getProcessLaunchEndTime()), Project::getEndTime, param.getProcessLaunchEndTime());
        projectService.page(page, wrapper);

        return page.getRecords().stream()
                .filter(d -> processInsIds.contains(d.getInstCode()))
                .collect(Collectors.toList());
    }

    /**
     * 待办中心-抄送我的项目列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/11
     */
    public void exportCcMeProjectList(HttpServletResponse response, ReqToBeProcessedDTO param) {

        param.setPageNumber(1);
        param.setPageSize(10000);
        // 获取当前登录用户ID
        // Long userId = LoginUserUtil.getUserId();

        Long userId = 381496L;
        List<WflowCcTasks> ccTasks = ccTasksMapper.selectList(Wrappers.lambdaQuery(WflowCcTasks.class)
                .eq(WflowCcTasks::getUserId, String.valueOf(userId)).orderByDesc(WflowCcTasks::getCreateTime));
        List<String> processInsIds = ccTasks.stream()
                .map(WflowCcTasks::getInstanceId)
                .collect(Collectors.toList());

        List<Project> records = getCcMeProjects(param, processInsIds);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_抄送我的_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsSupplement())) {
            fileName = "待办中心_抄送我的_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);

        // 表体行数据集合
        List<List<String>> rowList = new ArrayList<>();
        // 表体列数据集合
        List<ExportOptionEnum> columnList = param.getExportOptionList();
        for (ExportOptionEnum column : columnList) {
            List<String> columnValues = new ArrayList<>();
            columnValues.add(column.getDesc());
            records.stream().sorted(Comparator.comparing(Project::getId)).forEach(record -> {
                String s = JSON.toJSONString(record);
                JSONObject jsonObject = JSON.parseObject(s);
                String columnValue = String.valueOf(jsonObject.get(column.toString()));
                columnValues.add(columnValue);
            });
            rowList.add(columnValues);
        }
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(rowList);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(rowList);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }
}
