package org.jeecg.modules.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.dingtalkstorage_1_0.models.*;
import com.aliyun.dingtalkworkflow_1_0.Client;
import com.aliyun.dingtalkworkflow_1_0.models.*;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jeecg.dingtalk.api.base.JdtBaseAPI;
import com.jeecg.dingtalk.api.core.response.Response;
import com.jeecg.dingtalk.api.core.vo.AccessToken;
import com.jeecg.dingtalk.api.core.vo.PageResult;
import com.jeecg.dingtalk.api.department.JdtDepartmentAPI;
import com.jeecg.dingtalk.api.department.vo.Department;
import com.jeecg.dingtalk.api.message.JdtMessageAPI;
import com.jeecg.dingtalk.api.message.vo.ActionCardMessage;
import com.jeecg.dingtalk.api.message.vo.MarkdownMessage;
import com.jeecg.dingtalk.api.message.vo.Message;
import com.jeecg.dingtalk.api.message.vo.TextMessage;
import com.jeecg.dingtalk.api.oauth2.JdtOauth2API;
import com.jeecg.dingtalk.api.oauth2.vo.ContactUser;
import com.jeecg.dingtalk.api.user.JdtUserAPI;
import com.jeecg.dingtalk.api.user.body.GetUserListBody;
import com.jeecg.dingtalk.api.user.vo.User;
import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.dto.message.MessageDto;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.enums.MessageTypeEnum;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.PasswordUtil;
import org.jeecg.common.util.RestUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.util.oss.OssBootUtil;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.modules.message.service.ISysMessageService;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.SysAnnouncementSendMapper;
import org.jeecg.modules.system.mapper.SysUserMapper;
import org.jeecg.modules.system.model.ThirdLoginModel;
import org.jeecg.modules.system.service.ISysDepartService;
import org.jeecg.modules.system.service.ISysThirdAccountService;
import org.jeecg.modules.system.service.ISysThirdAppConfigService;
import org.jeecg.modules.system.service.ISysUserDepartService;
import org.jeecg.modules.system.vo.thirdapp.JdtDepartmentTreeVo;
import org.jeecg.modules.system.vo.thirdapp.SyncInfoVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class DingtalkServiceImpl {

    @Autowired
    private JustAuthProperties justAuthProperties;
    @Autowired
    private JeecgBaseConfig jeecgBaseConfig;
    @Autowired
    private ISysDepartService sysDepartService;
    @Autowired
    private SysUserMapper userMapper;
    @Autowired
    private ISysThirdAccountService sysThirdAccountService;
    @Autowired
    private ISysUserDepartService sysUserDepartService;
    @Autowired
    private SysAnnouncementSendMapper sysAnnouncementSendMapper;
    @Autowired
    private ISysThirdAppConfigService configMapper;
    @Autowired
    private ISysMessageService sysMessageService;

    public String getAccessToken() {
        // update-begin---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        SysThirdAppConfig config = getDingThirdAppConfig();
        if (null != config) {
            return getDingTalkAccessToken(config);
        }
        // update-end---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        return null;
    }

    public void syncDepartmentToLocalRecursion(List<JdtDepartmentTreeVo> departmentTreeList, String sysParentId, String username, SyncInfoVo syncInfo, String accessToken, Boolean syncUser) {

        if (departmentTreeList != null && departmentTreeList.size() != 0) {
            // 记录已经同步过的用户id，当有多个部门的情况时，只同步一次
            Set<String> syncedUserIdSet = new HashSet<>();
            for (JdtDepartmentTreeVo departmentTree : departmentTreeList) {
                LambdaQueryWrapper<SysDepart> queryWrapper = new LambdaQueryWrapper<>();
                // 根据 source_identifier 字段查询
                if (StrUtil.isBlank(departmentTree.getSource_identifier()) && departmentTree.getParent_id() == null) {
                    queryWrapper.eq(SysDepart::getId, departmentTree.getDept_id());
                } else {
                    queryWrapper.eq(SysDepart::getId, departmentTree.getSource_identifier());
                }
                SysDepart sysDepart = sysDepartService.getOne(queryWrapper);
                if (sysDepart != null) {
                    //  执行更新操作
                    SysDepart updateSysDepart = this.dtDepartmentToSysDepart(departmentTree, sysDepart);
                    if (sysParentId != null) {
                        updateSysDepart.setParentId(sysParentId);
                        // 更新父级部门不是叶子结点
                        sysDepartService.updateIzLeaf(sysParentId, CommonConstant.NOT_LEAF);
                    }
                    try {
                        sysDepartService.updateDepartDataById(updateSysDepart, username);
                        String str = String.format("部门 %s 更新成功！", updateSysDepart.getDepartName());
                        syncInfo.addSuccessInfo(str);
                    } catch (Exception e) {
                        this.syncDepartCollectErrInfo(e, departmentTree, syncInfo);
                    }
                    if (departmentTree.hasChildren()) {
                        // 紧接着同步子级
                        this.syncDepartmentToLocalRecursion(departmentTree.getChildren(), updateSysDepart.getId(), username, syncInfo, accessToken, syncUser);
                    }
                    // 判断是否需要同步用户
                    if (syncUser) {
                        this.addDepartUser(updateSysDepart.getId(), departmentTree.getDept_id(), accessToken, syncInfo, syncedUserIdSet);
                    }
                } else {
                    //  执行新增操作
                    SysDepart newSysDepart = this.dtDepartmentToSysDepart(departmentTree, null);
                    if (sysParentId != null) {
                        newSysDepart.setParentId(sysParentId);
                        // 2 = 组织机构
                        newSysDepart.setOrgCategory("2");
                    } else {
                        // 1 = 公司
                        newSysDepart.setOrgCategory("1");
                    }
                    try {
                        sysDepartService.saveDepartData(newSysDepart, username);
                        // 更新钉钉 source_identifier
                        // parentId为空表示是最顶层的部门
                        if (StrUtil.isNotBlank(newSysDepart.getParentId())) {
                            Department updateDtDepart = new Department();
                            updateDtDepart.setDept_id(departmentTree.getDept_id());
                            updateDtDepart.setSource_identifier(newSysDepart.getId());
                            Response response = JdtDepartmentAPI.update(updateDtDepart, accessToken);
                            if (!response.isSuccess()) {
                                throw new RuntimeException(response.getErrmsg());
                            }
                        }
                        String str = String.format("部门 %s 创建成功！", newSysDepart.getDepartName());
                        syncInfo.addSuccessInfo(str);
                        // 判断是否需要同步用户
                        if (syncUser) {
                            this.addDepartUser(newSysDepart.getId(), departmentTree.getDept_id(), accessToken, syncInfo, syncedUserIdSet);
                        }
                    } catch (Exception e) {
                        this.syncDepartCollectErrInfo(e, departmentTree, syncInfo);
                    }
                    // 紧接着同步子级
                    if (departmentTree.hasChildren()) {
                        this.syncDepartmentToLocalRecursion(departmentTree.getChildren(), newSysDepart.getId(), username, syncInfo, accessToken, syncUser);
                    }
                }
            }
        }
    }

    private boolean syncDepartCollectErrInfo(Exception e, Department department, SyncInfoVo syncInfo) {
        String msg;
        if (e instanceof DuplicateKeyException) {
            msg = e.getCause().getMessage();
        } else {
            msg = e.getMessage();
        }
        String str = String.format("部门 %s(%s) 同步失败！错误信息：%s", department.getName(), department.getDept_id(), msg);
        syncInfo.addFailInfo(str);
        return false;
    }

    /**
     * 递归查询所有用户
     */
    private void getUserListByDeptIdRecursion(int deptId, int cursor, List<User> userList, String accessToken) {
        // 根据钉钉部门查询所有钉钉用户，用于反向同步到本地
        GetUserListBody getUserListBody = new GetUserListBody(deptId, cursor, 100);
        Response<PageResult<User>> response = JdtUserAPI.getUserListByDeptId(getUserListBody, accessToken);
        if (response.isSuccess()) {
            PageResult<User> page = response.getResult();
            userList.addAll(page.getList());
            if (page.getHas_more()) {
                this.getUserListByDeptIdRecursion(deptId, page.getNext_cursor(), userList, accessToken);
            }
        }
    }

    /**
     * 保存或修改第三方登录表
     *
     * @param sysThirdAccount 第三方账户表对象，为null就新增数据，否则就修改
     * @param sysUserId       本地系统用户ID
     * @param user            钉钉用户
     */
    private void thirdAccountSaveOrUpdate(SysThirdAccount sysThirdAccount, String sysUserId, User user) {
        if (sysThirdAccount == null) {
            sysThirdAccount = new SysThirdAccount();
            sysThirdAccount.setSysUserId(sysUserId);
            sysThirdAccount.setThirdUserUuid(user.getUnionid());
            sysThirdAccount.setStatus(CommonConstant.USER_UNFREEZE);
            sysThirdAccount.setDelFlag(CommonConstant.DEL_FLAG_0);
            sysThirdAccount.setThirdType(MessageTypeEnum.DD.getType());
        }
        sysThirdAccount.setThirdUserId(user.getUserid());
        if (oConvertUtils.isEmpty(sysThirdAccount.getRealname())) {
            sysThirdAccount.setRealname(user.getName());
        }
        sysThirdAccountService.saveOrUpdate(sysThirdAccount);
    }

    /**
     * 【同步用户】收集同步过程中的错误信息
     */
    private boolean syncUserCollectErrInfo(Exception e, User dtUser, SyncInfoVo syncInfo) {
        String msg;
        if (e instanceof DuplicateKeyException) {
            msg = e.getCause().getMessage();
            String emailUniq = "uniq_sys_user_email";
            if (msg.contains(emailUniq)) {
                msg = "邮箱重复，请更换邮箱";
            }
            String workNoUniq = "uniq_sys_user_work_no";
            if (msg.contains(workNoUniq)) {
                msg = "工号重复，请更换工号";
            }
        } else {
            msg = e.getMessage();
        }
        String str = String.format("用户 %s(%s) 同步失败！错误信息：%s", dtUser.getUserid(), dtUser.getName(), msg);
        syncInfo.addFailInfo(str);
        return false;
    }

    /**
     * 【同步用户】将【钉钉】的User对象转为SysUser（创建新用户）
     */
    private SysUser dtUserToSysUser(User dtUser) {
        SysUser sysUser = new SysUser();
        sysUser.setDelFlag(CommonConstant.DEL_FLAG_0);
        sysUser.setStatus(CommonConstant.USER_UNFREEZE);
        setUsernameAndPassword(sysUser, dtUser.getMobile());
        return this.dtUserToSysUser(dtUser, sysUser);
    }

    private void setUsernameAndPassword(SysUser sysUser, String phone) {
        sysUser.setUsername(phone);
        String password = phone, salt = oConvertUtils.randomGen(8);
        String passwordEncode = PasswordUtil.encrypt(sysUser.getUsername(), password, salt);
        sysUser.setSalt(salt);
        sysUser.setPassword(passwordEncode);
    }

    /**
     * 【同步用户】将【钉钉】的User对象转为SysUser（更新旧用户）
     */
    private SysUser dtUserToSysUser(User dtUser, SysUser oldSysUser) {
        SysUser sysUser = new SysUser();
        BeanUtils.copyProperties(oldSysUser, sysUser);
        sysUser.setTelephone(dtUser.getTelephone());
        // 如果真实姓名为空的情况下，才会改真实姓名
        if (oConvertUtils.isEmpty(oldSysUser.getRealname())) {
            sysUser.setRealname(dtUser.getName());
        }
        // 因为唯一键约束的原因，如果原数据和旧数据相同，就不更新
        if (oConvertUtils.isNotEmpty(dtUser.getEmail()) && !dtUser.getEmail().equals(sysUser.getEmail())) {
            sysUser.setEmail(dtUser.getEmail());
        } else {
            sysUser.setEmail(null);
        }
        // 因为唯一键约束的原因，如果原数据和旧数据相同，就不更新
        if (oConvertUtils.isNotEmpty(dtUser.getMobile()) && !dtUser.getMobile().equals(sysUser.getPhone())) {
            sysUser.setPhone(dtUser.getMobile());
        } else {
            sysUser.setPhone(null);
        }
        // 设置工号，如果工号为空，则使用username
        if (oConvertUtils.isEmpty(dtUser.getJob_number())) {
            sysUser.setWorkNo(dtUser.getUserid());
        } else {
            sysUser.setWorkNo(dtUser.getJob_number());
        }
        // --- 钉钉没有逻辑删除功能
        // sysUser.getDelFlag()
        // --- 钉钉没有冻结、启用禁用功能
        // sysUser.getStatus()
        return sysUser;
    }

    /**
     * 【同步部门】将【钉钉】的Department对象转为SysDepartTreeModel
     */
    private SysDepart dtDepartmentToSysDepart(Department department, SysDepart departTree) {
        SysDepart sysDepart = new SysDepart();
        if (departTree != null) {
            BeanUtils.copyProperties(departTree, sysDepart);
        }
        sysDepart.setDepartName(department.getName());
        sysDepart.setDepartOrder(department.getOrder());
        sysDepart.setId(String.valueOf(department.getDept_id()));
        return sysDepart;
    }

    public int removeThirdAppUser(List<String> userIdList) {
        // 判断启用状态
        SysThirdAppConfig appConfig = getDingThirdAppConfig();
        if (null == appConfig) {
            return -1;
        }
        int count = 0;
        if (userIdList != null && userIdList.size() > 0) {
            // update-begin---author:wangshuai ---date:20230209  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
            String accessToken = this.getDingTalkAccessToken(appConfig);
            // update-end---author:wangshuai ---date:20230209  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
            if (accessToken == null) {
                return count;
            }
            LambdaQueryWrapper<SysThirdAccount> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SysThirdAccount::getThirdType, MessageTypeEnum.DD.getType());
            queryWrapper.in(SysThirdAccount::getSysUserId, userIdList);
            // 根据userId，获取第三方用户的id
            List<SysThirdAccount> thirdAccountList = sysThirdAccountService.list(queryWrapper);
            List<String> thirdUserIdList = thirdAccountList.stream().map(SysThirdAccount::getThirdUserId).collect(Collectors.toList());

            for (String thirdUserId : thirdUserIdList) {
                if (oConvertUtils.isNotEmpty(thirdUserId)) {
                    // 没有批量删除的接口
                    Response<JSONObject> response = JdtUserAPI.delete(thirdUserId, accessToken);
                    if (response.getErrcode() == 0) {
                        count++;
                    }
                }
            }
        }
        return count;

    }

    /**
     * 发送简单的钉钉消息
     *
     * @param title       标题
     * @param id          业务主键ID
     * @param content     消息内容
     * @param thirdUserId 固定的钉钉用户ID
     * @param userIds     系统用户ID，需要转换成钉钉用户ID
     */
    public void sendSimpleMessage(String title, String id, String content, String thirdUserId, String userIds) {
        boolean includeAdmin = StrUtil.isNotBlank(thirdUserId);
        if (StrUtil.isNotBlank(userIds)) {
            List<SysThirdAccount> list = sysThirdAccountService.list(Wrappers.lambdaQuery(SysThirdAccount.class)
                    .in(SysThirdAccount::getSysUserId, Arrays.asList(userIds.split(","))));
            String collect = list.stream().map(SysThirdAccount::getThirdUserId).collect(Collectors.joining(","));
            if (StrUtil.isNotBlank(thirdUserId)) {
                thirdUserId = thirdUserId + "," + collect;
            } else {
                thirdUserId = collect;
            }
        }

        if (StrUtil.isBlank(thirdUserId)) {
            log.warn("计算后的钉钉用户ID为空，不发送消息。消息内容为=[{}]", content);
            return;
        }

        Map<String, AuthConfig> type = justAuthProperties.getType();
        AuthConfig dingtalk = type.get(CommonConstant.DINGTALK);
        String clientId = dingtalk.getClientId();
        String clientSecret = dingtalk.getClientSecret();
        String agentId = dingtalk.getAgentId();

        AccessToken accessToken = JdtBaseAPI.getAccessToken(clientId, clientSecret);

        String mdContent = StrUtil.isBlank(title) ? content : String.format("## %s\n\n%s", title, content);
        Message<MarkdownMessage> mdMsg = new Message<>(agentId, new MarkdownMessage(title, mdContent));
        mdMsg.setTo_all_user(false);
        mdMsg.setUserid_list(StrUtil.split(thirdUserId, ","));

        // Message<TextMessage> textMessage = new Message<>(agentId, new TextMessage(content));
        // textMessage.setTo_all_user(false);
        // textMessage.setUserid_list(StrUtil.split(thirdUserId, ","));

        log.debug("发送钉钉通知，参数为=[{}]", JSONUtil.toJsonStr(mdMsg));
        // Response<String> response = JdtMessageAPI.sendTextMessage(textMessage, accessToken.getAccessToken());
        Response<String> response = JdtMessageAPI.sendMarkdownMessage(mdMsg, accessToken.getAccessToken());
        log.debug("发送钉钉通知，返回结果为=[{}]", JSONUtil.toJsonStr(response));

        String errmsg = response.isSuccess() ? null : response.getErrmsg();
        if (includeAdmin) {
            List<SysThirdAccount> list = sysThirdAccountService.list(Wrappers.lambdaQuery(SysThirdAccount.class)
                    .in(SysThirdAccount::getThirdUserId, Arrays.asList(thirdUserId.split(","))));
            String collect = list.stream().map(SysThirdAccount::getSysUserId).collect(Collectors.joining(","));
            sysMessageService.add(title, id, content, collect, MessageTypeEnum.DD.getType(), errmsg, new Date());
            return;
        }
        sysMessageService.add(title, id, content, userIds, MessageTypeEnum.DD.getType(), errmsg, new Date());
    }

    /**
     * 发送消息
     *
     * @param message
     * @param verifyConfig
     * @return
     */
    public boolean sendMessage(MessageDto message, boolean verifyConfig) {
        Response<String> response;
        if (message.isMarkdown()) {
            response = this.sendMarkdownResponse(message, verifyConfig);
        } else {
            response = this.sendMessageResponse(message, verifyConfig);
        }
        if (response != null) {
            return response.isSuccess();
        }
        return false;
    }

    /**
     * 发送Markdown消息
     *
     * @param message
     * @param verifyConfig
     * @return
     */
    public Response<String> sendMarkdownResponse(MessageDto message, boolean verifyConfig) {
        SysThirdAppConfig config = this.getDingThirdAppConfig();
        if (verifyConfig && null == config) {
            return null;
        }
        String accessToken = this.getAccessToken();
        if (accessToken == null) {
            return null;
        }
        // 封装钉钉消息
        String title = message.getTitle();
        String content = message.getContent();
        String agentId = config.getAgentId();
        Message<MarkdownMessage> mdMessage = new Message<>(agentId, new MarkdownMessage(title, content));
        if (message.getToAll()) {
            mdMessage.setTo_all_user(true);
        } else {
            String[] toUsers = message.getToUser().split(",");
            // 通过第三方账号表查询出第三方userId
            List<SysThirdAccount> thirdAccountList = sysThirdAccountService.listThirdUserIdByUsername(toUsers, MessageTypeEnum.DD.getType());
            List<String> dtUserIds = thirdAccountList.stream().map(SysThirdAccount::getThirdUserId).collect(Collectors.toList());
            mdMessage.setUserid_list(dtUserIds);
        }
        return JdtMessageAPI.sendMarkdownMessage(mdMessage, accessToken);
    }

    public Response<String> sendMessageResponse(MessageDto message, boolean verifyConfig) {
        SysThirdAppConfig config = this.getDingThirdAppConfig();
        if (verifyConfig && null == config) {
            return null;
        }
        String accessToken = this.getAccessToken();
        if (accessToken == null) {
            return null;
        }
        // 封装钉钉消息
        String content = message.getContent();
        String agentId = config.getAgentId();
        Message<TextMessage> textMessage = new Message<>(agentId, new TextMessage(content));
        if (message.getToAll()) {
            textMessage.setTo_all_user(true);
        } else {
            String[] toUsers = message.getToUser().split(",");
            // 通过第三方账号表查询出第三方userId
            List<SysThirdAccount> thirdAccountList = sysThirdAccountService.listThirdUserIdByUsername(toUsers, MessageTypeEnum.DD.getType());
            List<String> dtUserIds = thirdAccountList.stream().map(SysThirdAccount::getThirdUserId).collect(Collectors.toList());
            textMessage.setUserid_list(dtUserIds);
        }
        return JdtMessageAPI.sendTextMessage(textMessage, accessToken);
    }

    public boolean recallMessage(String msgTaskId) {
        Response<JSONObject> response = this.recallMessageResponse(msgTaskId);
        if (response == null) {
            return false;
        }
        return response.isSuccess();
    }

    /**
     * 撤回消息
     *
     * @param msgTaskId
     * @return
     */
    public Response<JSONObject> recallMessageResponse(String msgTaskId) {
        // update-begin---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        SysThirdAppConfig config = this.getDingThirdAppConfig();
        String accessToken = this.getDingTalkAccessToken(config);
        if (accessToken == null) {
            return null;
        }
        String agentId = config.getAgentId();
        return JdtMessageAPI.recallMessage(agentId, msgTaskId, accessToken);
        // update-end---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
    }

    /**
     * 发送卡片消息（SysAnnouncement定制）
     *
     * @param announcement
     * @param ddMobileUrl  钉钉打开网页地址
     * @param verifyConfig 是否验证配置（未启用的APP会拒绝发送）
     * @return
     */
    public Response<String> sendActionCardMessage(SysAnnouncement announcement, String ddMobileUrl, boolean verifyConfig) {
        // update-begin---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        SysThirdAppConfig config = this.getDingThirdAppConfig();
        if (verifyConfig && null == config) {
            return null;
        }
        String accessToken = this.getDingTalkAccessToken(config);
        if (accessToken == null) {
            return null;
        }
        String agentId = config.getAgentId();
        // update-end---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        String emptySuffix = null;
        if (oConvertUtils.isNotEmpty(announcement.getMsgAbstract())) {
            String msgAbstract = announcement.getMsgAbstract().trim();
            log.info("获取钉钉通知参数，msgAbstract: {}", msgAbstract);
            if (msgAbstract.startsWith("{") && msgAbstract.endsWith("}")) {
                // 如果摘要存的是业务扩展参数json，则取公告内容
                emptySuffix = announcement.getMsgContent();
            } else {
                // 如果摘要不为空且是文本格式，则使用摘要
                emptySuffix = msgAbstract;
            }
        } else {
            emptySuffix = "空";
        }

        String markdown = "### " + announcement.getTitile() + "\n" + emptySuffix;
        log.info("钉钉推送参数, markdown: {}", markdown);
        ActionCardMessage actionCard = new ActionCardMessage(markdown);
        actionCard.setTitle(announcement.getTitile());
        actionCard.setSingle_title("详情");
        String baseUrl = null;
        // 优先通过请求获取basepath，获取不到读取 jeecg.domainUrl.pc
        try {
            baseUrl = RestUtil.getBaseUrl();
        } catch (Exception e) {
            log.error(e.getMessage());
            baseUrl = jeecgBaseConfig.getDomainUrl().getPc();
            // e.printStackTrace();
        }

        log.info("获取钉钉打开网页地址，参数 ddMobileUrl: {}", ddMobileUrl);
        String ddSingleUrl = null;
        if (oConvertUtils.isNotEmpty(ddMobileUrl)) {
            ddSingleUrl = ddMobileUrl;
        } else {
            ddSingleUrl = baseUrl + "/sys/annountCement/show/" + announcement.getId();
        }
        actionCard.setSingle_url(ddSingleUrl);
        log.info("获取钉钉打开网页地址，最终地址 ddSingleUrl: {}", ddSingleUrl);

        Message<ActionCardMessage> actionCardMessage = new Message<>(agentId, actionCard);
        if (CommonConstant.MSG_TYPE_ALL.equals(announcement.getMsgType())) {
            actionCardMessage.setTo_all_user(true);
            return JdtMessageAPI.sendActionCardMessage(actionCardMessage, accessToken);
        } else {
            // 将userId转为username
            String[] userIds = null;
            String userId = announcement.getUserIds();
            if (oConvertUtils.isNotEmpty(userId)) {
                userIds = userId.substring(0, (userId.length() - 1)).split(",");
            } else {
                LambdaQueryWrapper<SysAnnouncementSend> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(SysAnnouncementSend::getAnntId, announcement.getId());
                SysAnnouncementSend sysAnnouncementSend = sysAnnouncementSendMapper.selectOne(queryWrapper);
                userIds = new String[]{sysAnnouncementSend.getUserId()};
            }

            if (userIds != null) {
                LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.in(SysUser::getId, userIds);
                List<SysUser> userList = userMapper.selectList(queryWrapper);
                String[] usernameList = userList.stream().map(SysUser::getUsername).toArray(String[]::new);

                // 通过第三方账号表查询出第三方userId
                List<SysThirdAccount> thirdAccountList = sysThirdAccountService.listThirdUserIdByUsername(usernameList, MessageTypeEnum.DD.getType());
                List<String> dtUserIds = thirdAccountList.stream().map(SysThirdAccount::getThirdUserId).collect(Collectors.toList());
                actionCardMessage.setUserid_list(dtUserIds);
                return JdtMessageAPI.sendActionCardMessage(actionCardMessage, accessToken);
            }
        }
        return null;
    }

    /**
     * 获取钉钉的userid值
     *
     * @param uuid
     * @return
     */
    public String getDingTalkUserId(String uuid) {
        String accessToken = getAccessToken();
        Response<String> getUserIdRes = JdtUserAPI.getUseridByUnionid(uuid, accessToken);
        if (!getUserIdRes.isSuccess()) {
            log.error("---- getUseridByUnionid failed: " + JSON.toJSONString(getUserIdRes));
            throw new JeecgBootException("获取钉钉用户信息失败");
        }
        return getUserIdRes.getResult();
    }

    /**
     * OAuth2登录，成功返回登录的SysUser，失败返回null
     */
    public SysUser oauth2Login(String authCode) {
        // update-begin---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        SysThirdAppConfig dtConfig = configMapper.getThirdConfigByThirdType(MessageTypeEnum.DD.getType());
        // update-end---author:wangshuai ---date:20230224  for：[QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------
        // 1. 根据免登授权码获取用户 AccessToken
        String userAccessToken = JdtOauth2API.getUserAccessToken(dtConfig.getClientId(), dtConfig.getClientSecret(), authCode);
        if (userAccessToken == null) {
            log.error("oauth2Login userAccessToken is null");
            throw new JeecgBootException("请查看应用key和应用秘钥是否正确，组织ID是否匹配");
        }
        // 2. 根据用户 AccessToken 获取当前用户的基本信息（不包括userId）
        ContactUser contactUser = JdtOauth2API.getContactUsers("me", userAccessToken);
        if (contactUser == null) {
            log.error("oauth2Login contactUser is null");
            throw new JeecgBootException("获取钉钉用户信息失败");
        }
        String unionId = contactUser.getUnionId();
        // 3. 根据获取到的 unionId 换取用户 userId
        String accessToken = this.getDingTalkAccessToken(dtConfig);
        if (accessToken == null) {
            log.error("oauth2Login accessToken is null");
            throw new JeecgBootException("请查看应用key和应用秘钥是否正确，组织ID是否匹配");
        }
        Response<String> getUserIdRes = JdtUserAPI.getUseridByUnionid(unionId, accessToken);
        if (!getUserIdRes.isSuccess()) {
            log.error("oauth2Login getUseridByUnionid failed: " + JSON.toJSONString(getUserIdRes));
            throw new JeecgBootException("获取钉钉用户信息失败");
        }
        String appUserId = getUserIdRes.getResult();
        log.debug("appUserId: " + appUserId);
        if (appUserId != null) {
            // 判断第三方用户表有没有这个人
            LambdaQueryWrapper<SysThirdAccount> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SysThirdAccount::getThirdType, MessageTypeEnum.DD.getType());
            // update-begin---author:wangshuai---date:2023-12-04---for: auth登录需要联查一下---
            queryWrapper.and((wrapper) -> wrapper.eq(SysThirdAccount::getThirdUserUuid, appUserId).or().eq(SysThirdAccount::getThirdUserId, appUserId));
            // update-end---author:wangshuai---date:2023-12-04---for: auth登录需要联查一下---
            SysThirdAccount thirdAccount = sysThirdAccountService.getOne(queryWrapper);
            if (thirdAccount != null) {
                return this.getSysUserByThird(thirdAccount, null, appUserId, accessToken);
            } else {
                // 直接创建新账号
                User appUser = JdtUserAPI.getUserById(appUserId, accessToken).getResult();
                // update-begin---author:wangshuai ---date:20230328  for：[QQYUN-4883]钉钉auth登录同一个租户下有同一个用户id------------
                // 应该存uuid
                ThirdLoginModel tlm = new ThirdLoginModel(MessageTypeEnum.DD.getType(), appUser.getUnionid(), appUser.getName(), appUser.getAvatar());
                // update-end---author:wangshuai ---date:20230328  for：[QQYUN-4883]钉钉auth登录同一个租户下有同一个用户id------------
                thirdAccount = sysThirdAccountService.saveThirdUser(tlm);
                return this.getSysUserByThird(thirdAccount, appUser, null, null);
            }
        }
        return null;
    }

    /**
     * 根据第三方账号获取本地账号，如果不存在就创建
     *
     * @param thirdAccount
     * @param appUser
     * @param appUserId
     * @param accessToken
     * @return
     */
    private SysUser getSysUserByThird(SysThirdAccount thirdAccount, User appUser, String appUserId, String accessToken) {
        String sysUserId = thirdAccount.getSysUserId();
        if (oConvertUtils.isNotEmpty(sysUserId)) {
            SysUser sysUser = userMapper.selectById(sysUserId);
            setOrgCode(sysUser);
            return sysUser;
        } else {
            // 如果没有 sysUserId ，说明没有绑定账号，获取到手机号之后进行绑定
            if (appUser == null) {
                appUser = JdtUserAPI.getUserById(appUserId, accessToken).getResult();
            }
            // 判断系统里是否有这个手机号的用户
            SysUser sysUser = userMapper.getUserByPhoneAndFlag(appUser.getMobile());
            if (sysUser != null) {
                thirdAccount.setAvatar(appUser.getAvatar());
                thirdAccount.setRealname(appUser.getName());
                thirdAccount.setThirdUserId(appUser.getUserid());
                // update-begin---author:wangshuai ---date:20230328  for：[QQYUN-4883]钉钉auth登录同一个租户下有同一个用户id------------
                thirdAccount.setThirdUserUuid(appUser.getUnionid());
                // update-end---author:wangshuai ---date:20230328  for：[QQYUN-4883]钉钉auth登录同一个租户下有同一个用户id------------
                thirdAccount.setSysUserId(sysUser.getId());
                sysThirdAccountService.updateById(thirdAccount);
                setOrgCode(sysUser);
                return sysUser;
            } else {
                // 没有就走创建逻辑
                SysUser user = sysThirdAccountService.createUser(appUser.getMobile(), appUser.getUnionid());
                setOrgCode(user);
                return user;
            }

        }
    }

    /**
     * 第三方登录设置登录部门编码
     */
    private void setOrgCode(SysUser sysUser) {
        if (sysUser != null && StrUtil.isBlank(sysUser.getOrgCode())) {
            List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
            if (!departs.isEmpty()) {
                userMapper.updateUserDepart(sysUser.getUsername(), departs.get(0).getOrgCode());
            }
        }
    }

    /**
     * 根据类型和租户id获取钉钉配置
     *
     * @return
     */
    private SysThirdAppConfig getDingThirdAppConfig() {
        return configMapper.getThirdConfigByThirdType(MessageTypeEnum.DD.getType());
    }

    /**
     * 获取钉钉accessToken
     *
     * @param config
     * @return
     */
    private String getDingTalkAccessToken(SysThirdAppConfig config) {
        if (null == config) {
            return null;
        }
        AccessToken accessToken = JdtBaseAPI.getAccessToken(config.getClientId(), config.getClientSecret());
        if (accessToken != null) {
            return accessToken.getAccessToken();
        }
        log.warn("获取AccessToken失败");
        return null;
    }

    /**
     * 同步用户和部门
     *
     * @return
     */
    public SyncInfoVo syncThirdAppDepartmentUserToLocal() {
        SyncInfoVo syncInfo = new SyncInfoVo();
        String accessToken = this.getAccessToken();
        if (accessToken == null) {
            syncInfo.addFailInfo("accessToken获取失败！");
            return syncInfo;
        }
        // 获取【钉钉】所有的部门
        List<Department> departments = JdtDepartmentAPI.listAll(accessToken);
        String username = "admin";
        try {
            username = JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest());
        } catch (Exception e) {
            log.warn("获取登录用户的用户名错误，错误信息为[{}]", e.getMessage());
        }

        List<JdtDepartmentTreeVo> departmentTreeList = JdtDepartmentTreeVo.listToTree(departments);
        // 递归同步部门
        this.syncDepartmentToLocalRecursion(departmentTreeList, null, username, syncInfo, accessToken, true);
        return syncInfo;
    }

    /**
     * 添加用户及用户部门关系
     *
     * @param departId        部门id
     * @param dingDepartId    钉钉部门id
     * @param accessToken
     * @param syncInfo
     * @param syncedUserIdSet
     */
    private void addDepartUser(String departId, Integer dingDepartId, String accessToken, SyncInfoVo syncInfo, Set<String> syncedUserIdSet) {
        List<User> userList = new ArrayList<>();
        getUserListByDeptIdRecursion(dingDepartId, 0, userList, accessToken);
        for (User user : userList) {
            if (syncedUserIdSet.contains(user.getUserid())) {
                // 需要同步用户部门
                this.syncAddOrUpdateUserDepart(user.getUserid(), departId);
                continue;
            }
            syncedUserIdSet.add(user.getUserid());
            SysUser userByPhone = userMapper.getUserByPhone(user.getMobile());
            if (user.getName().equals("林挺") || user.getName().equals("张胜平") || user.getName().equals("高祥")) {
                System.out.println("");
            }
            SysThirdAccount sysThirdAccount = sysThirdAccountService.getOneByUuidAndThirdType(user.getUnionid(), MessageTypeEnum.DD.getType(), user.getUserid());

            boolean updateUsernameAndPassword = false;
            if (userByPhone == null && sysThirdAccount != null && StrUtil.isNotBlank(sysThirdAccount.getSysUserId())) {
                // 如果能查出来数据，则这种情况是用户更换了手机号
                userByPhone = userMapper.selectById(sysThirdAccount.getSysUserId());
                updateUsernameAndPassword = true;
            }

            if (null != userByPhone) {
                // 循环到此说明用户匹配成功，进行更新操作
                SysUser updateSysUser = this.dtUserToSysUser(user, userByPhone);
                if (updateUsernameAndPassword) setUsernameAndPassword(updateSysUser, user.getMobile());
                try {
                    userMapper.updateById(updateSysUser);
                    String str = String.format("用户 %s(%s) 更新成功！", updateSysUser.getRealname(), updateSysUser.getUsername());
                    syncInfo.addSuccessInfo(str);
                } catch (Exception e) {
                    this.syncUserCollectErrInfo(e, user, syncInfo);
                }
                // 第三方账号关系表
                this.thirdAccountSaveOrUpdate(sysThirdAccount, updateSysUser.getId(), user);
                // 需要同步用户部门
                this.syncAddOrUpdateUserDepart(updateSysUser.getId(), departId);
            } else {
                // 如果没有匹配到用户，则走创建逻辑
                SysUser newSysUser = this.dtUserToSysUser(user);
                try {
                    log.info("【钉钉同步】新增用户参数为[{}]", JSON.toJSONString(newSysUser));
                    userMapper.insert(newSysUser);
                    String str = String.format("用户 %s(%s) 创建成功！", newSysUser.getRealname(), newSysUser.getUsername());
                    syncInfo.addSuccessInfo(str);
                } catch (Exception e) {
                    this.syncUserCollectErrInfo(e, user, syncInfo);
                }
                // 第三方账号关系表
                this.thirdAccountSaveOrUpdate(sysThirdAccount, newSysUser.getId(), user);
                // 需要同步用户部门
                this.syncAddOrUpdateUserDepart(newSysUser.getId(), departId);
            }
        }
    }

    /**
     * 通过用户id和部门id新增用户部门关系表
     *
     * @param userId
     * @param departId
     */
    private void syncAddOrUpdateUserDepart(String userId, String departId) {
        // 查询用户是否在部门里面
        LambdaQueryWrapper<SysUserDepart> query = new LambdaQueryWrapper<>();
        query.eq(SysUserDepart::getDepId, departId);
        query.eq(SysUserDepart::getUserId, userId);
        long count = sysUserDepartService.count(query);
        if (count == 0) {
            // 不存在，则新增部门用户关系
            SysUserDepart sysUserDepart = new SysUserDepart(null, userId, departId);
            sysUserDepartService.save(sysUserDepart);
        }
    }

    public void getProcessCodeByName(String name) throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        com.aliyun.dingtalkworkflow_1_0.Client client = new com.aliyun.dingtalkworkflow_1_0.Client(config);
        GetProcessCodeByNameHeaders getProcessCodeByNameHeaders = new GetProcessCodeByNameHeaders();
        getProcessCodeByNameHeaders.xAcsDingtalkAccessToken = getAccessToken();
        GetProcessCodeByNameRequest getProcessCodeByNameRequest = new GetProcessCodeByNameRequest()
                .setName(name);
        try {
            GetProcessCodeByNameResponse getProcessCodeByNameResponse = client.getProcessCodeByNameWithOptions(getProcessCodeByNameRequest, getProcessCodeByNameHeaders, new RuntimeOptions());
            System.out.println(JSON.toJSONString(getProcessCodeByNameResponse.getBody()));

            log.info("获取审批模版code结果:{}", JSON.toJSONString(getProcessCodeByNameResponse.getBody()));
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                System.out.println(err.code);
                System.out.println(err.message);

                log.warn("获取审批模版code失败: {}", "code " + err.getCode() + " message: " + err.getMessage());
            }
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                System.out.println(err.code);
                System.out.println(err.message);
                log.warn("获取审批模版code失败: {}", "code " + err.getCode() + " message: " + err.getMessage());
            }
        }
    }

    public String querySchemaByProcessCode(String processCode) throws Exception {
        Client clientWorkflow = createClientWorkflow();

        QuerySchemaByProcessCodeHeaders querySchemaByProcessCodeHeaders = new QuerySchemaByProcessCodeHeaders();
        querySchemaByProcessCodeHeaders.xAcsDingtalkAccessToken = getAccessToken();

        QuerySchemaByProcessCodeRequest querySchemaByProcessCodeRequest = new QuerySchemaByProcessCodeRequest()
                .setProcessCode(processCode);

        try {
            QuerySchemaByProcessCodeResponse querySchemaByProcessCodeResponse = clientWorkflow.querySchemaByProcessCodeWithOptions(querySchemaByProcessCodeRequest, querySchemaByProcessCodeHeaders, new RuntimeOptions());
            log.info("获取审批参数结果为\n{}", JSON.toJSONString(querySchemaByProcessCodeResponse.getBody().getResult()));
            return JSON.toJSONString(querySchemaByProcessCodeResponse.getBody().getResult());
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }
        }
        return null;
    }

    /**
     * 撤回审批实例
     *
     * @param processInstanceId 流程实例ID
     * @throws Exception
     */
    public void recallOrderExamine(String processInstanceId) throws Exception {
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        SysThirdAccount dingtalk = sysThirdAccountService.getOneBySysUserId(user.getId(), MessageTypeEnum.DD.getType());

        TerminateProcessInstanceHeaders terminateProcessInstanceHeaders = new TerminateProcessInstanceHeaders();
        terminateProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken();

        TerminateProcessInstanceRequest terminateProcessInstanceRequest = new TerminateProcessInstanceRequest()
                .setProcessInstanceId(processInstanceId)
                .setIsSystem(true)
                .setRemark("人为操作终止")
                .setOperatingUserId(dingtalk.getThirdUserId());

        Client client = createClientWorkflow();

        try {
            client.terminateProcessInstanceWithOptions(terminateProcessInstanceRequest, terminateProcessInstanceHeaders, new com.aliyun.teautil.models.RuntimeOptions());
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                log.warn("撤销订单审批 失败: {}", "code " + err.getCode() + " message: " + err.getMessage() + " data: " + err.getData());
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }
            throw err;
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                log.warn("撤销订单审批 失败: {}", "code " + err.getCode() + " message: " + err.getMessage() + " data: " + err.getData());
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }
            throw _err;
        }
    }

    private String translateDingtalkAttachment(List<String> urls) throws Exception {
        if (urls == null || urls.isEmpty()) return null;
        SysThirdAppConfig thirdAppConfig = this.getDingThirdAppConfig();
        String accessToken = getDingTalkAccessToken(thirdAppConfig);
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        SysThirdAccount dingtalk = sysThirdAccountService.getOneBySysUserId(user.getId(), MessageTypeEnum.DD.getType());
        List<CommitFileResponse> fileUploadInfos = new ArrayList<>();
        for (String url : urls) {
            if (StrUtil.isNotBlank(url)) {
                // 获取钉盘spaceId
                Long attachmentSpaceId = getAttachmentSpace(accessToken, dingtalk.getThirdUserId(), thirdAppConfig.getAgentId());
                InputStream inputStream = OssBootUtil.getOssFile(url, null);
                String[] split = url.split("/");
                List<CommitFileResponse> fileUploadInfo = getFileUploadInfo(accessToken, new MultipartFile[]{inputStreamToMultipartFile(inputStream, split[split.length - 1])}, String.valueOf(attachmentSpaceId), dingtalk.getThirdUserId());
                fileUploadInfos.addAll(fileUploadInfo);
            }
        }

        JSONArray attachemnts = new JSONArray();
        if (!CollectionUtils.isEmpty(fileUploadInfos)) {
            fileUploadInfos.forEach(c -> {
                CommitFileResponseBody.CommitFileResponseBodyDentry dentry = c.getBody().getDentry();
                JSONObject attachmentJson = new JSONObject();
                attachmentJson.put("fileId", dentry.getId());
                attachmentJson.put("fileName", dentry.getName());
                attachmentJson.put("fileType", dentry.getExtension());
                attachmentJson.put("spaceId", dentry.getSpaceId());
                attachmentJson.put("fileSize", String.valueOf(dentry.getSize()));
                attachemnts.add(attachmentJson);
            });
        }
        return JSON.toJSONString(attachemnts);
    }

    public MultipartFile inputStreamToMultipartFile(InputStream inputStream, String fileName) {
        try {
            byte[] bytes = readAllBytes(inputStream);
            ByteArrayResource resource = new ByteArrayResource(bytes);
            return new MyMultipartFile(fileName, fileName, MediaType.MULTIPART_FORM_DATA_VALUE, resource.getInputStream());
        } catch (Exception e) {
            throw new RuntimeException("Error converting InputStream to MultipartFile", e);
        }
    }

    private byte[] readAllBytes(InputStream inputStream) throws IOException {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int n = 0;
        while (-1 != (n = inputStream.read(buffer))) {
            out.write(buffer, 0, n);
        }
        return out.toByteArray();
    }

    // 获取钉盘spaceId
    public Long getAttachmentSpace(String accessToken, String thirdUserId, String agentId) throws Exception {
        com.aliyun.dingtalkworkflow_1_0.Client client = createClientWorkflow();
        GetAttachmentSpaceHeaders getAttachmentSpaceHeaders = new GetAttachmentSpaceHeaders();
        getAttachmentSpaceHeaders.xAcsDingtalkAccessToken = accessToken;
        GetAttachmentSpaceRequest getAttachmentSpaceRequest = new GetAttachmentSpaceRequest()
                .setUserId(thirdUserId)
                .setAgentId(Long.valueOf(agentId));
        try {
            GetAttachmentSpaceResponse attachmentSpaceWithOptions = client.getAttachmentSpaceWithOptions(getAttachmentSpaceRequest, getAttachmentSpaceHeaders, new RuntimeOptions());
            log.debug("钉盘空间信息: {}", JSON.toJSONString(attachmentSpaceWithOptions));
            return attachmentSpaceWithOptions.getBody().getResult().getSpaceId();
        } catch (Exception e) {
            log.error("获取钉盘信息失败: {}", e.getMessage());
            return null;
        }
    }

    // 获取文件上传信息
    public List<CommitFileResponse> getFileUploadInfo(String accessToken, MultipartFile[] files, String spaceId, String thirdUserId) throws Exception {
        String unionid = "";
        List<CommitFileResponse> commitFileResponses = new ArrayList<>();
        try {
            String url = "https://oapi.dingtalk.com/topapi/v2/user/get?access_token=" + accessToken;
            Map<String, String> map = new HashMap<>();
            map.put("language", "zh_CN");
            map.put("userid", thirdUserId);
            String req = JSON.toJSONString(map);
            String res = HttpUtil.post(url, req);
            // log.debug("文件上传钉盘前获取unionId response: {}", res);
            JSONObject jsonObject = JSON.parseObject(res);
            if (jsonObject.getInteger("errcode") == 0) {
                JSONObject result = JSON.parseObject(jsonObject.getString("result"));
                unionid = result.getString("unionid");
                log.debug("文件上传钉盘前获取unionId {}, response: {}", unionid, res);
            }
        } catch (Exception e) {
            log.error("文件上传钉盘前获取unionId 失败: {}", e.getMessage());
        }
        com.aliyun.dingtalkstorage_1_0.Client client = talkstorageClient();
        GetFileUploadInfoHeaders getFileUploadInfoHeaders = new GetFileUploadInfoHeaders();
        getFileUploadInfoHeaders.xAcsDingtalkAccessToken = accessToken;
        GetFileUploadInfoRequest getFileUploadInfoRequest = new GetFileUploadInfoRequest()
                .setUnionId(unionid)
                .setProtocol("HEADER_SIGNATURE")
                .setMultipart(false);
        try {
            // 执行上传操作
            for (int i = 0; i < files.length; i++) {
                GetFileUploadInfoResponse fileUploadInfoWithOptions = client.getFileUploadInfoWithOptions(spaceId, getFileUploadInfoRequest, getFileUploadInfoHeaders, new RuntimeOptions());
                log.debug("获取文件上传钉盘信息: {}", JSON.toJSONString(fileUploadInfoWithOptions));
                GetFileUploadInfoResponseBody body = fileUploadInfoWithOptions.getBody();
                URL url = new URL(body.headerSignatureInfo.getResourceUrls().get(0));
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                Map<String, String> headers = body.headerSignatureInfo.headers;
                if (headers != null) {
                    for (Map.Entry<String, String> entry : headers.entrySet()) {
                        connection.setRequestProperty(entry.getKey(), entry.getValue());
                    }
                }
                connection.setDoOutput(true);
                connection.setRequestMethod("PUT");
                connection.setUseCaches(false);
                connection.setRequestProperty("Accept-Charset", "UTF-8");
                connection.setRequestProperty("contentType", "UTF-8");
                connection.setRequestProperty("Authorization", body.headerSignatureInfo.headers.get("Authorization"));
                connection.setRequestProperty("x-oss-date", body.headerSignatureInfo.headers.get("x-oss-date"));
                connection.setReadTimeout(10000);
                connection.setConnectTimeout(10000);
                connection.connect();
                OutputStream out = connection.getOutputStream();
                InputStream is = files[i].getInputStream();
                byte[] b = new byte[1024];
                int temp;
                while ((temp = is.read(b)) != -1) {
                    out.write(b, 0, temp);
                }
                out.flush();
                out.close();
                int responseCode = connection.getResponseCode();
                connection.disconnect();
                log.debug("上传文件信息 {}", connection);
                if (responseCode == 200) {
                    log.debug("上传成功");
                    String uploadKey = fileUploadInfoWithOptions.getBody().getUploadKey();
                    // 提交文件
                    CommitFileResponse commitFileResponse = commitFile(accessToken, unionid, uploadKey, files[i].getOriginalFilename(), spaceId);
                    commitFileResponses.add(commitFileResponse);
                } else {
                    log.error("钉盘上传文件失败");
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return commitFileResponses;
    }

    // 提交文件
    public CommitFileResponse commitFile(String accessToken, String unionId, String uploadKey, String fileName, String spaceId) throws Exception {
        com.aliyun.dingtalkstorage_1_0.Client client = talkstorageClient();
        com.aliyun.dingtalkstorage_1_0.models.CommitFileHeaders commitFileHeaders = new com.aliyun.dingtalkstorage_1_0.models.CommitFileHeaders();
        commitFileHeaders.xAcsDingtalkAccessToken = accessToken;
        com.aliyun.dingtalkstorage_1_0.models.CommitFileRequest commitFileRequest = new com.aliyun.dingtalkstorage_1_0.models.CommitFileRequest()
                .setUnionId(unionId)
                .setUploadKey(uploadKey)
                .setName(fileName)
                .setParentId("0");
//                .setOption(option);
        try {
            CommitFileResponse commitFileResponse = client.commitFileWithOptions(spaceId, commitFileRequest, commitFileHeaders, new RuntimeOptions());
            log.debug("文件提交: {}", JSON.toJSONString(commitFileResponse));
            return commitFileResponse;
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    // 存储client
    public com.aliyun.dingtalkstorage_1_0.Client talkstorageClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new com.aliyun.dingtalkstorage_1_0.Client(config);
    }

    // 审批流client
    public com.aliyun.dingtalkworkflow_1_0.Client createClientWorkflow() throws Exception {
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
        config.protocol = "https";
        config.regionId = "central";
        return new com.aliyun.dingtalkworkflow_1_0.Client(config);
    }

    /**
     * 查询流程实例对应的表单组件值
     *
     * @param processInstanceId 流程实例ID
     * @return 表单组件值
     */
    public List<Map<String, Object>> getInstanceFormComponentDetail(String processInstanceId) {
        GetProcessInstanceHeaders headers = new GetProcessInstanceHeaders();
        headers.xAcsDingtalkAccessToken = getAccessToken();

        GetProcessInstanceRequest request = new GetProcessInstanceRequest().setProcessInstanceId(processInstanceId);

        try {
            Client client = createClientWorkflow();
            GetProcessInstanceResponse response = client.getProcessInstanceWithOptions(request, headers, new RuntimeOptions());
            List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues> formComponentValues = response.getBody().getResult().formComponentValues;
            log.debug(JSONUtil.toJsonStr(formComponentValues));
            List<Map<String, Object>> list = new ArrayList<>(formComponentValues.size());
            for (GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues formComponentValue : formComponentValues) {
                Map<String, Object> map = BeanUtil.beanToMap(formComponentValue);
                list.add(map);
            }
            return list;
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                log.warn("查询流程实例信息 失败: {}", "code " + err.getCode() + " message: " + err.getMessage() + " data: " + err.getData());
            }
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                log.warn("查询流程实例信息 失败: {}", "code " + err.getCode() + " message: " + err.getMessage() + " data: " + err.getData());
            }
        }
        return null;
    }

    /**
     * 获取钉钉审批评论
     *
     * @param processInstanceId 流程实例ID
     */
    public List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultOperationRecords> getComments(String processInstanceId) throws Exception {
        Client client = createClientWorkflow();
        com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders();
        getProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken();
        com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest()
                .setProcessInstanceId(processInstanceId);
        try {
            GetProcessInstanceResponse response = client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new RuntimeOptions());
            return response.getBody().getResult().getOperationRecords();
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        }
        return null;
    }

    /**
     * 获取钉钉审批对应的流程实例ID
     */
    public void getProcessInstances(String processCode) throws Exception {
        Client client = createClientWorkflow();
        com.aliyun.dingtalkworkflow_1_0.models.ListProcessInstanceIdsHeaders listProcessInstanceIdsHeaders = new com.aliyun.dingtalkworkflow_1_0.models.ListProcessInstanceIdsHeaders();
        listProcessInstanceIdsHeaders.xAcsDingtalkAccessToken = getAccessToken();
        com.aliyun.dingtalkworkflow_1_0.models.ListProcessInstanceIdsRequest listProcessInstanceIdsRequest = new com.aliyun.dingtalkworkflow_1_0.models.ListProcessInstanceIdsRequest()
                .setProcessCode(processCode)
                .setStartTime(1730385739604L)
                .setNextToken(0L)
                .setMaxResults(10L)
                // .setUserIds(java.util.Arrays.asList("发起userid"))
                .setStatuses(java.util.Arrays.asList("RUNNING"));
        try {
            ListProcessInstanceIdsResponse response = client.listProcessInstanceIdsWithOptions(listProcessInstanceIdsRequest, listProcessInstanceIdsHeaders, new RuntimeOptions());
            List<String> list = response.getBody().getResult().getList();
            log.info("{}", JSONUtil.toJsonStr(list));
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        }
    }

}
