package com.kingdee.eas.custom.integration.feishu.util;

import com.alibaba.fastjson.JSONObject;
import com.kingdee.bos.BOSException;
import com.kingdee.bos.Context;
import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
import com.kingdee.bos.dao.ormapping.SQLAccessException;
import com.kingdee.bos.metadata.data.SortType;
import com.kingdee.bos.metadata.entity.*;
import com.kingdee.bos.metadata.resource.BizEnumInfo;
import com.kingdee.bos.util.BOSObjectType;
import com.kingdee.bos.util.BOSUuid;
import com.kingdee.eas.base.message.BMCMessageInfo;
import com.kingdee.eas.base.message.MsgBizType;
import com.kingdee.eas.base.message.webservice.WSMessage;
import com.kingdee.eas.base.message.webservice.WfrAssignMessage;
import com.kingdee.eas.base.message.webservice.WfrProcMessage;
import com.kingdee.eas.base.multiapprove.*;
import com.kingdee.eas.base.permission.IUser;
import com.kingdee.eas.base.permission.UserFactory;
import com.kingdee.eas.base.permission.UserInfo;
import com.kingdee.eas.basedata.org.HROrgUnitInfo;
import com.kingdee.eas.common.EASBizException;
import com.kingdee.eas.custom.integration.base.config.AppConfig;
import com.kingdee.eas.custom.integration.base.util.SpringContextHolder;
import com.kingdee.eas.custom.integration.base.service.IntegrationConfigService;
import com.kingdee.eas.custom.integration.base.util.SHRApiUtils;
import com.kingdee.eas.custom.integration.feishu.*;
import com.kingdee.eas.custom.integration.feishu.bizEnum.*;
import com.kingdee.eas.hr.base.AbstractHRBillBaseInfo;
import com.kingdee.eas.hr.base.HRBillStateEnum;
import com.kingdee.shr.base.syssetting.BaseItemStateEnum;
import com.kingdee.shr.base.syssetting.UsePolicy;
import com.kingdee.shr.base.syssetting.util.BaseMetaDataUtil;
import com.kingdee.util.DateTimeUtils;
import com.kingdee.util.enums.DynamicEnum;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 飞书消息工具类
 * @Date 2026/3/10 16:08
 * @Created by HeYuan
 */
public class FeishuMessageUtils {
    private static Logger logger = Logger.getLogger(FeishuMessageUtils.class);
    //移动审批地址
    private static final String MBOS_URL = "https://mbos.kdeascloud.com/mbos/mbosw/getWorkflowUrl?assignId={0}&eid={1}&storeEid={2}&name=approveui.navui&billID={3}";
    //移动端员工自助首页
    private static final String MBOS_SELF_URL = "{0}/api/feishu/?redirect=https%3a%2f%2fmbos.kdeascloud.com%2fmbos%2fpage%2floadPage%3fappid%3d10036%26eid%3d{1}%26path%3dshrlightapp%26name%3dhrserver.navui";
    //桌面端员工自助首页
    private static final String PC_SELF_URL = "{0}/api/feishu/?redirect=%2fdynamic.do%3fuipk%3dshr.perself.homepage.new%26inFrame%3dtrue";
    //打开移动端门户
    private static final String FEISHU_MOBILE_URL = "https://applink.feishu.cn/client/web_app/open?appId={0}&platform=mobile";
    //打开桌面端门户
    private static final String FEISHU_PC_URL = "https://applink.feishu.cn/client/web_app/open?appId={0}&platform=pc";
    //飞书消息桌面端地址变量
    private static final String PC_URL = "${pc_url}";
    //飞书消息安卓端地址变量
    private static final String ANDROID_URL = "${android_url}";
    //飞书消息IOS端地址变量
    private static final String IOS_URL = "${ios_url}";
    //飞书消息URL变量
    private static final String URL = "${url}";

    //HR组织
    private static final HROrgUnitInfo HRORGUNITINFO = new HROrgUnitInfo();

    static {
        HRORGUNITINFO.setId(BOSUuid.read("00000000-0000-0000-0000-000000000000CCE7AED4"));
    }

    /**
     * 发送消息
     *
     * @param messageInfo
     * @return
     */
    public static boolean sendMessage(Context ctx, BMCMessageInfo messageInfo) {
        logger.info("FeishuMessageUtils sendMessage 开始");
        if (messageInfo == null) {
            return false;
        }
        logger.info("FeishuMessageUtils sendMessage title" + messageInfo.getTitle());
        logger.info("FeishuMessageUtils sendMessage body" + messageInfo.getBody());
        logger.info("FeishuMessageUtils sendMessage receiver" + messageInfo.getReceiver());
        try {
            if (ctx.getCaller() == null || StringUtils.isBlank(ctx.getCaller().toString())) {
                ctx.setCaller(new ObjectUuidPK("00000000-0000-0000-0000-00000000000013B7DE7F"));
            }
            //发送消息
            IFeishuMessage iFeishuMessage = FeishuMessageFactory.getLocalInstance(ctx);
            FeishuMessageInfo feishuMessageInfo = new FeishuMessageInfo();
            //消息类型
            MsgBizType bizType = messageInfo.getBizType();
            //消息内容
            String body = messageInfo.getBody().replace("\r", "\\r").replace("\n", "\\n");
            //消息内容
            String content = null;
            if ("PERFWEB".equals(messageInfo.getBizCustomizedFlag())) {
                logger.info("FeishuMessageUtils sendMessage 绩效消息处理");
                //绩效
                //卡片消息
                feishuMessageInfo.setMsgType(MsgTypeEnum.interactive);
                //模板
                String perwebMessageTemplate = getPerwebMessageTemplate();
                //标题
                String title = messageInfo.getTitle().replace("\r", "\\r").replace("\n", "\\n");
                //发送时间
                String sendTime = "";
                if (messageInfo.getSendTime() != null) {
                    sendTime = DateTimeUtils.format(messageInfo.getSendTime(), "yyyy-MM-dd HH:mm:ss");
                } else {
                    sendTime = DateTimeUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
                }
                //构建链接
                //获取飞书应用默认配置
                IntegrationConfigService integrationConfigService = SpringContextHolder.getBean(IntegrationConfigService.class);

                AppConfig appConfig = integrationConfigService.getPrimaryApp("feishu");
                if (appConfig == null) {
                    throw new BOSException("没有获取到飞书应用默认配置!");
                }
                String mbosSelfUrl = MessageFormat.format(FEISHU_MOBILE_URL, appConfig.getAppId());
                String pcSelfUrl = MessageFormat.format(FEISHU_PC_URL, appConfig.getAppId());

                content = perwebMessageTemplate.replace("${title}", title)
                        .replace("${body}", body)
                        .replace("${sendTime}", sendTime)
                        .replace(PC_URL, pcSelfUrl)
                        .replace(URL, pcSelfUrl)
                        .replace(ANDROID_URL, mbosSelfUrl)
                        .replace(IOS_URL, mbosSelfUrl);
            } else {
                logger.info("FeishuMessageUtils sendMessage 获取飞书消息卡片模板");

                //获取飞书消息卡片模板
                IFeishuMessageCard iFeishuMessageCard = FeishuMessageCardFactory.getLocalInstance(ctx);
                FilterInfo filterInfo = new FilterInfo();
                //消息类型
                filterInfo.getFilterItems().add(new FilterItemInfo("msgBizType", bizType.getValue()));
                filterInfo.getFilterItems().add(new FilterItemInfo("state", BaseItemStateEnum.ENABLE));
                //根据创建时间倒序查询
                SorterItemCollection sorters = new SorterItemCollection();
                SorterItemInfo sorterItemInfo = new SorterItemInfo("createTime");
                sorterItemInfo.setSortType(SortType.DESCEND);
                sorters.add(sorterItemInfo);
                EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, null, sorters);
                FeishuMessageCardCollection messageCardCol = iFeishuMessageCard.getFeishuMessageCardCollection(viewInfo);

                if (messageCardCol.isEmpty()) {
                    //没有匹配到模板,使用原始文字
                    //文本消息
                    feishuMessageInfo.setMsgType(MsgTypeEnum.text);
                    content = "{\"text\":\"" + body + "\"}";
                } else {
                    FeishuMessageCardInfo feishuMessageCardInfo = messageCardCol.get(0);
                    //卡片消息
                    feishuMessageInfo.setMsgType(MsgTypeEnum.interactive);
                    //模板
                    String template = feishuMessageCardInfo.getTemplate();
                    //模型属性
                    Map<String, PropertyInfo> properties = BaseMetaDataUtil.getProperties(ctx, "com.kingdee.eas.base.message.app.BMCMessage");
                    for (String key : properties.keySet()) {
                        PropertyInfo propertyInfo = properties.get(key);
                        if (propertyInfo instanceof OwnPropertyInfo) {
                            //模型自有属性
                            //模板变量
                            String variable = "${" + key + "}";
                            OwnPropertyInfo ownPropertyInfo = (OwnPropertyInfo) propertyInfo;
                            DataType dataType = ownPropertyInfo.getDataType();
                            if (DataType.BOOLEAN.equals(dataType)) {
                                if (messageInfo.getBoolean(key)) {
                                    template = template.replace(variable, "是");
                                } else {
                                    template = template.replace(variable, "否");
                                }
                            } else if (DataType.ENUM.equals(dataType)) {
                                String orginalValue = messageInfo.getString(key);
                                if (StringUtils.isNotBlank(orginalValue)) {
                                    BizEnumInfo enumType = ownPropertyInfo.getEnumType();
                                    DynamicEnum anEnum = DynamicEnum.getEnum(enumType.getClassName(), orginalValue);
                                    if (anEnum != null) {
                                        template = template.replace(variable, anEnum.getAlias());
                                    }
                                }
                            } else if (DataType.STRING.equals(dataType)) {
                                String value = messageInfo.getString(key) == null ? "" : messageInfo.getString(key).replace("\r", "\\r").replace("\n", "\\n");
                                template = template.replace(variable, value);
                            } else if (DataType.TIMESTAMP.equals(dataType)) {
                                Timestamp timestamp = messageInfo.getTimestamp(key);
                                String format = "";
                                if (timestamp != null) {
                                    format = DateTimeUtils.format(new Date(timestamp.getTime()), "yyyy-MM-dd HH:mm:ss");
                                }
                                template = template.replace(variable, format);
                            } else if (DataType.TIME.equals(dataType)) {
                                Time time = messageInfo.getTime(key);
                                String format = "";
                                if (time != null) {
                                    format = DateTimeUtils.format(new Date(time.getTime()), "yyyy-MM-dd HH:mm:ss");
                                }
                                template = template.replace(variable, format);
                            } else if (DataType.DATE.equals(dataType)) {
                                java.sql.Date date = messageInfo.getDate(key);
                                String format = "";
                                if (date != null) {
                                    format = DateTimeUtils.format(date, "yyyy-MM-dd HH:mm:ss");
                                }
                                template = template.replace(variable, format);
                            } else if (DataType.DECIMAL.equals(dataType)) {
                                BigDecimal bigDecimal = messageInfo.getBigDecimal(key);
                                String plainString = "";
                                if (bigDecimal != null) {
                                    plainString = bigDecimal.toPlainString();
                                }
                                template = template.replace(variable, plainString);
                            }
                        }
                    }
                    content = template;
                }
            }
            //消息接收人
            IUser iUser = UserFactory.getLocalInstance(ctx);
            UserInfo receive = iUser.getUserByID(new ObjectUuidPK(messageInfo.getReceiver()));
            //接收人
            feishuMessageInfo.setReceive(receive);
            //消息内容
            feishuMessageInfo.setContent(content);
            iFeishuMessage.save(feishuMessageInfo);
            logger.info("FeishuMessageUtils sendMessage 结束");
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("FeishuMessageUtils sendMessage 报错");
            return false;
        }
        return true;
    }


    /**
     * 流程任务完成
     *
     * @param wfrMmessage
     * @return
     */
    public static boolean completedApproveMessage(WfrProcMessage wfrMmessage) {
        logger.error("FeishuMessageUtils completedApproveMessage 开始");
        try {
            Context ctx = SHRApiUtils.getContext(wfrMmessage.getCtx());
            //
            Time updateTime = new Time(wfrMmessage.getUpdateTime().getTime());
            String billID = wfrMmessage.getBillID();
            BOSObjectType bosType = BOSUuid.read(billID).getType();
            //休眠1.5s,等待单据状态更新完毕
            //Thread.sleep(1500);
            AbstractHRBillBaseInfo billInfo = (AbstractHRBillBaseInfo) BaseMetaDataUtil.getBizInterface(ctx, bosType).getValue(new ObjectUuidPK(billID));
            //单据状态
            HRBillStateEnum billState = billInfo.getBillState();

            IFeishuApprovalInstance iFeishuApprovalInstance = FeishuApprovalInstanceFactory.getLocalInstance(ctx);
            FilterInfo filterInfo = new FilterInfo();
            filterInfo.getFilterItems().add(new FilterItemInfo("instanceId", wfrMmessage.getProcInstId()));
            SelectorItemCollection sic = new SelectorItemCollection();
            sic.add("*");
            sic.add("taskList.*");
            EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, sic, null);
            FeishuApprovalInstanceCollection instanceCollection = iFeishuApprovalInstance.getFeishuApprovalInstanceCollection(viewInfo);
            if (!instanceCollection.isEmpty()) {
                FeishuApprovalInstanceInfo instanceInfo = instanceCollection.get(0);
                if (HRBillStateEnum.AUDITED.equals(billState)) {
                    //审批通过
                    instanceInfo.setStatus(InstanceStatusEnum.APPROVED);
                } else if (HRBillStateEnum.AUDITEND.equals(billState)) {
                    //审批不通过
                    instanceInfo.setStatus(InstanceStatusEnum.REJECTED);
                }
                FeishuApprovalInstanceTaskListCollection taskList = instanceInfo.getTaskList();
                for (int i = 0; i < taskList.size(); i++) {
                    FeishuApprovalInstanceTaskListInfo instanceTaskListInfo = taskList.get(i);
                    TaskStatusEnum status = instanceTaskListInfo.getStatus();
                    if (TaskStatusEnum.PENDING.equals(status)) {
                        InstanceStatusEnum infoStatus = instanceInfo.getStatus();
                        Map<String, String> map = new HashMap<>();
                        if (InstanceStatusEnum.APPROVED.equals(infoStatus)) {
                            //任务结束原因: 同意
                            map.put("complete_reason", CompleteReasonEnum.APPROVED_VALUE);
                            //同意
                            instanceTaskListInfo.setStatus(TaskStatusEnum.APPROVED);
                        } else {
                            //任务结束原因: 拒绝
                            map.put("complete_reason", CompleteReasonEnum.REJECTED_VALUE);
                            // 拒绝
                            instanceTaskListInfo.setStatus(TaskStatusEnum.REJECTED);
                        }
                        String extra = JSONObject.toJSONString(map).replace("\"", "\\\"");
                        //更新时间
                        instanceTaskListInfo.setUpdateTime(updateTime);
                        //结束时间
                        instanceTaskListInfo.setEndTime(updateTime);
                        instanceTaskListInfo.setExtra(extra);
                    }
                }
                //流程结束时间
                instanceInfo.setEndTime(updateTime);
                //流程更新时间
                instanceInfo.setUpdateTime(updateTime);
                //流程结束时间
                iFeishuApprovalInstance.save(instanceInfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("流程任务完成 completedApprovMessage 报错: " + e.getMessage(), e);
            return false;
        }
        return true;
    }


    /**
     * 节点任务取消
     *
     * @param wfrMmessage
     * @return
     */
    public static boolean nodeCanceledApproveMessage(WfrAssignMessage wfrMmessage) {
        logger.error("FeishuMessageUtils nodeCanceledApproveMessage 开始");
        try {
            Context ctx = SHRApiUtils.getContext(wfrMmessage.getCtx());
            //
            Time dealTime = new Time(wfrMmessage.getDealTime().getTime());
            //流程任务id
            String assignId = wfrMmessage.getAssignId();

            IFeishuApprovalInstance iFeishuApprovalInstance = FeishuApprovalInstanceFactory.getLocalInstance(ctx);
            IFeishuApprovalInstanceTaskList iFeishuApprovalInstanceTaskList = FeishuApprovalInstanceTaskListFactory.getLocalInstance(ctx);
            FilterInfo filterInfo = new FilterInfo();
            filterInfo.getFilterItems().add(new FilterItemInfo("taskId", assignId));
            SelectorItemCollection sic = new SelectorItemCollection();
            sic.add("*");
            sic.add("parent.status");
            sic.add("parent.id");
            EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, sic, null);
            //审批实例任务列表
            FeishuApprovalInstanceTaskListCollection instanceTaskListCol = iFeishuApprovalInstanceTaskList.getFeishuApprovalInstanceTaskListCollection(viewInfo);
            if (!instanceTaskListCol.isEmpty()) {
                FeishuApprovalInstanceTaskListInfo instanceTaskListInfo = instanceTaskListCol.get(0);
                FeishuApprovalInstanceInfo parent = instanceTaskListInfo.getParent();
                //流程实例状态
                if (InstanceStatusEnum.PENDING.equals(parent.getStatus())) {
                    //转交
                    instanceTaskListInfo.setStatus(TaskStatusEnum.TRANSFERRED);
                    Map<String, String> map = new HashMap<>();
                    //任务结束原因: 转交
                    map.put("complete_reason", CompleteReasonEnum.FORWARD_VALUE);
                    String extra = JSONObject.toJSONString(map).replace("\"", "\\\"");
                    //更新时间
                    instanceTaskListInfo.setUpdateTime(dealTime);
                    //结束时间
                    instanceTaskListInfo.setEndTime(dealTime);
                    instanceTaskListInfo.setExtra(extra);
                    //保存
                    iFeishuApprovalInstanceTaskList.save(instanceTaskListInfo);
                    //触发同步飞书审批实例
                    iFeishuApprovalInstance.syncApprovalInstance(parent.getId().toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("节点任务转交 nodeCanceledApproveMessage 报错: " + e.getMessage(), e);
            return false;
        }
        return true;
    }


    /**
     * 节点任务完成
     *
     * @param wfrMmessage
     * @return
     */
    public static boolean nodeCompletedApproveMessage(WfrAssignMessage wfrMmessage) {
        logger.error("FeishuMessageUtils nodeCompletedApproveMessage 开始");
        try {
            Context ctx = SHRApiUtils.getContext(wfrMmessage.getCtx());
            //
            Time dealTime = new Time(wfrMmessage.getDealTime().getTime());
            //流程任务id
            String assignId = wfrMmessage.getAssignId();

            //多级审批
            IMultiApprove iMultiApprove = MultiApproveFactory.getLocalInstance(ctx);
            MultiApproveCollection multiApproveCollection = iMultiApprove.getMultiApproveCollection("where assignment='" + assignId + "'");
            Boolean isPassValue = true;
            if (multiApproveCollection.isEmpty()) {
                logger.error("多级审批对象为空 assignment: " + assignId);
            } else {
                MultiApproveInfo multiApproveInfo = multiApproveCollection.get(0);
                ApproveResult isPass = multiApproveInfo.getIsPass();
                isPassValue = Boolean.valueOf(isPass.getValue());
            }
            IFeishuApprovalInstance iFeishuApprovalInstance = FeishuApprovalInstanceFactory.getLocalInstance(ctx);
            IFeishuApprovalInstanceTaskList iFeishuApprovalInstanceTaskList = FeishuApprovalInstanceTaskListFactory.getLocalInstance(ctx);
            FilterInfo filterInfo = new FilterInfo();
            filterInfo.getFilterItems().add(new FilterItemInfo("taskId", assignId));
            SelectorItemCollection sic = new SelectorItemCollection();
            sic.add("*");
            sic.add("parent.status");
            sic.add("parent.id");
            EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, sic, null);
            //审批实例任务列表
            FeishuApprovalInstanceTaskListCollection instanceTaskListCol = iFeishuApprovalInstanceTaskList.getFeishuApprovalInstanceTaskListCollection(viewInfo);
            if (!instanceTaskListCol.isEmpty()) {
                FeishuApprovalInstanceTaskListInfo instanceTaskListInfo = instanceTaskListCol.get(0);
                FeishuApprovalInstanceInfo parent = instanceTaskListInfo.getParent();
                //流程实例状态
                if (InstanceStatusEnum.PENDING.equals(parent.getStatus())) {
                    Map<String, String> map = new HashMap<>();
                    if (isPassValue) {
                        //任务结束原因: 同意
                        map.put("complete_reason", CompleteReasonEnum.APPROVED_VALUE);
                        //同意
                        instanceTaskListInfo.setStatus(TaskStatusEnum.APPROVED);
                    } else {
                        //任务结束原因: 拒绝
                        map.put("complete_reason", CompleteReasonEnum.REJECTED_VALUE);
                        // 拒绝
                        instanceTaskListInfo.setStatus(TaskStatusEnum.REJECTED);
                    }
                    String extra = JSONObject.toJSONString(map).replace("\"", "\\\"");
                    //更新时间
                    instanceTaskListInfo.setUpdateTime(dealTime);
                    //结束时间
                    instanceTaskListInfo.setEndTime(dealTime);
                    instanceTaskListInfo.setExtra(extra);
                    //保存
                    iFeishuApprovalInstanceTaskList.save(instanceTaskListInfo);
                    //触发同步飞书审批实例
                    iFeishuApprovalInstance.syncApprovalInstance(parent.getId().toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("节点任务完成 nodeCompletedApprovMessage 报错: " + e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * 撤销流程
     *
     * @param wfrProcMessage
     * @return
     */
    public static boolean abortedApproveMessage(WfrProcMessage wfrProcMessage) {
        logger.error("FeishuMessageUtils abortedApproveMessage 开始");
        try {
            Context ctx = SHRApiUtils.getContext(wfrProcMessage.getCtx());
            IFeishuApprovalInstance iFeishuApprovalInstance = FeishuApprovalInstanceFactory.getLocalInstance(ctx);
            Time endTime = new Time(System.currentTimeMillis());
            //审批实例
            FilterInfo filterInfo = new FilterInfo();
            filterInfo.getFilterItems().add(new FilterItemInfo("instanceId", wfrProcMessage.getProcInstId()));
            SelectorItemCollection sic = new SelectorItemCollection();
            sic.add("*");
            sic.add("taskList.*");
            EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, sic, null);
            FeishuApprovalInstanceCollection instanceCollection = iFeishuApprovalInstance.getFeishuApprovalInstanceCollection(viewInfo);
            if (!instanceCollection.isEmpty()) {
                FeishuApprovalInstanceInfo instanceInfo = instanceCollection.get(0);
                FeishuApprovalInstanceTaskListCollection taskList = instanceInfo.getTaskList();

                Map<String, String> map = new HashMap<>();
                //任务结束原因: 撤销（撤回单据，单据失效）
                map.put("complete_reason", CompleteReasonEnum.RECALL_VALUE);
                String extra = JSONObject.toJSONString(map).replace("\"", "\\\"");

                for (int i = 0; i < taskList.size(); i++) {
                    FeishuApprovalInstanceTaskListInfo instanceTaskListInfo = taskList.get(i);
                    TaskStatusEnum status = instanceTaskListInfo.getStatus();
                    if (TaskStatusEnum.PENDING.equals(status)) {
                        //任务通过但审批人未操作
                        instanceTaskListInfo.setStatus(TaskStatusEnum.DONE);
                        //更新时间
                        instanceTaskListInfo.setUpdateTime(endTime);
                        //结束时间
                        instanceTaskListInfo.setEndTime(endTime);
                        instanceTaskListInfo.setExtra(extra);
                    }
                }
                //流程状态更新为 审批人撤销
                instanceInfo.setStatus(InstanceStatusEnum.CANCELED);
                //流程更新时间
                instanceInfo.setUpdateTime(endTime);
                //流程结束时间
                instanceInfo.setEndTime(endTime);
                iFeishuApprovalInstance.save(instanceInfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("撤销流程 abortedApprovMessage 报错: " + e.getMessage(), e);
            return false;
        }
        return true;
    }


    /**
     * 发送审批消息
     *
     * @param message
     * @return
     */
    public static boolean addApproveMessage(WSMessage message) {
        logger.error("FeishuMessageUtils addApproveMessage 开始");
        //流程实例id
        String procInstId = message.getProcInstId();
        String billId = message.getBillId();
        BOSObjectType bosType = BaseMetaDataUtil.getBOSObjectTypeById(billId);
        try {
            Context ctx = SHRApiUtils.getContext(message.getContext());
            String assignId = message.getSourceID();
            //飞书应用默认配置
            FeishuConfigInfo defaultConfig = FeishuApiUtils.getDefaultConfig();
            //PC 端的三方审批实例跳转链接。
            String pcLink = getPcLink(defaultConfig, message, assignId);
            //移动端的三方审批实例跳转链接。
            String mobileLink = getMobileLink(defaultConfig, message, assignId);
            //发起人
            IUser iUser = UserFactory.getLocalInstance(ctx);
            UserInfo sender = iUser.getUserInfo("where number='" + message.getSenderNumber() + "'");

            IFeishuApprovalInstance iFeishuApprovalInstance = FeishuApprovalInstanceFactory.getLocalInstance(ctx);

            FeishuApprovalInstanceCollection instanceCol = getFeishuApprovalInstanceCollection(procInstId, iFeishuApprovalInstance);
            FeishuApprovalInstanceInfo instanceInfo = null;
            //发送时间
            Time sendTime = new Time(message.getSendTime().getTime());
            if (instanceCol.isEmpty()) {
                //没有,新增
                instanceInfo = new FeishuApprovalInstanceInfo();
                instanceInfo.setInstanceId(procInstId);
                instanceInfo.setHrOrgUnit(HRORGUNITINFO);
                FeishuApprovalsInfo feishuApprovalsInfo = getFeishuApprovalsInfo(ctx, bosType);
                //审批定义
                instanceInfo.setApproval(feishuApprovalsInfo);
                //审批实例状态
                instanceInfo.setStatus(InstanceStatusEnum.PENDING);
                //审批实例扩展参数，JSON 格式
                Map<String, String> extraMap = new HashMap<String, String>();
                extraMap.put("business_key", message.getBillNumber());
                String extra = JSONObject.toJSONString(extraMap).replace("\"", "\\\"");
                instanceInfo.setExtra(extra);
                //PC 端的三方审批实例跳转链接。
                instanceInfo.setPcLink(pcLink);
                //移动端的三方审批实例跳转链接。
                instanceInfo.setMobileLink(mobileLink);
                //审批展示名称
                String title = message.getTitle().replace("\r", "\\r").replace("\n", "\\n");
                FeishuI18nResourcesInfo titleI18nResources = getI18nResources(ctx, title, BizTypeEnum.TITLE);
                instanceInfo.setTitle(titleI18nResources);
                //表单
                instanceInfo.getForms().addCollection(getFormCollection(ctx, sender, message));
                //发起人
                instanceInfo.setUser(sender);
                //审批发起时间
                instanceInfo.setStartTime(sendTime);
                //审批发起时间
                instanceInfo.setEndTime(null);
                //审批实例最近更新时间
                instanceInfo.setUpdateTime(sendTime);
                //列表页打开审批实例的方式
                instanceInfo.setDisplayMethod(DisplayMethodEnum.NORMAL);
                //更新方式      全量替换
                instanceInfo.setUpdateMode(InstanceUpdateModeEnum.REPLACE);
                try {
                    //保存
                    iFeishuApprovalInstance.save(instanceInfo);
                } catch (SQLAccessException e) {
                    logger.error("addApproveMessage 保存审批实例报错: " + e.getMessage(), e);
                    if (!e.getMessage().contains("IX_CT_FEI_FeishuApprovalInstance")) {
                        throw e;
                    }
                    //并发场景下 实例id重复,说明已经存在实例数据.
                    instanceCol = getFeishuApprovalInstanceCollection(procInstId, iFeishuApprovalInstance);
                    if (instanceCol.isEmpty()) {
                        throw new RuntimeException("并发情况下未能获取审批实例: " + procInstId);
                    }
                    //审批实例
                    instanceInfo = instanceCol.get(0);
                } catch (Exception e) {
                    logger.error("addApproveMessage 保存审批实例报错: " + e.getMessage(), e);
                    throw e;
                }
            } else {
                //审批实例
                instanceInfo = instanceCol.get(0);
            }
            //保存任务列表
            IFeishuApprovalInstanceTaskList iFeishuApprovalInstanceTaskList = FeishuApprovalInstanceTaskListFactory.getLocalInstance(ctx);
            FeishuApprovalInstanceTaskListInfo task = createTask(ctx, message, pcLink, mobileLink);
            task.setParent(instanceInfo);
            iFeishuApprovalInstanceTaskList.save(task);
            //更新审批实例最近更新时间
            instanceInfo.setUpdateTime(sendTime);
            SelectorItemCollection updateSic = new SelectorItemCollection();
            updateSic.add("updateTime");
            iFeishuApprovalInstance.updatePartial(instanceInfo, updateSic);
            //同步
            iFeishuApprovalInstance.syncApprovalInstance(instanceInfo.getId().toString());
            //发送提醒消息
            sendMessageByApprove(message, pcLink, mobileLink);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据实例id获取飞书审批实例
     *
     * @param procInstId
     * @param iFeishuApprovalInstance
     * @return
     * @throws BOSException
     */
    private static FeishuApprovalInstanceCollection getFeishuApprovalInstanceCollection(String procInstId, IFeishuApprovalInstance iFeishuApprovalInstance) throws BOSException {
        FilterInfo filterInfo = new FilterInfo();
        filterInfo.getFilterItems().add(new FilterItemInfo("instanceId", procInstId));
        EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, null, null);
        FeishuApprovalInstanceCollection instanceCol = iFeishuApprovalInstance.getFeishuApprovalInstanceCollection(viewInfo);
        return instanceCol;
    }


    /**
     * 发送消息
     *
     * @param message
     * @return
     */
    public static boolean sendMessageByApprove(WSMessage message, String pcLink, String mobileLink) {
        logger.info("sendMessageByApprove 发送飞书消息: " + message.getTitle());
        try {
            Context ctx = SHRApiUtils.getContext(message.getContext());
            //获取单据对象
            EntityObjectInfo entityObjectInfo = BaseMetaDataUtil.getEntityObjectInfo(ctx, BOSUuid.read(message.getBillId()).getType());
            //发送消息
            IFeishuMessage iFeishuMessage = FeishuMessageFactory.getLocalInstance(ctx);
            FeishuMessageInfo feishuMessageInfo = new FeishuMessageInfo();
            //卡片消息
            feishuMessageInfo.setMsgType(MsgTypeEnum.interactive);
            //消息接收人
            IUser iUser = UserFactory.getLocalInstance(ctx);
            UserInfo reciever = iUser.getUserByID(new ObjectUuidPK(message.getRecieverID()));
            //接收人
            feishuMessageInfo.setReceive(reciever);
            //消息内容
            String messageTemplate = getMessageTemplate();
            Timestamp sendTime = message.getSendTime();
            String sendTimeStr = DateTimeUtils.format(new Date(sendTime.getTime()), "yyyy-MM-dd HH:mm:ss");
            String context = messageTemplate.replace("${sender}", message.getSender())
                    .replace("${sendTime}", sendTimeStr)
                    .replace("${titie}", message.getTitle())
                    .replace("${billType}", entityObjectInfo.getDisplayName());
            //.replace("${pcLink}", pcLink)
            //.replace("${mobileLink}", mobileLink);
            feishuMessageInfo.setContent(context);
            iFeishuMessage.save(feishuMessageInfo);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }


    /**
     * 获取表单
     *
     * @param ctx
     * @param sender
     * @throws BOSException
     * @throws EASBizException
     */
    private static FeishuApprovalInstanceFormCollection getFormCollection(
            Context ctx,
            UserInfo sender,
            WSMessage message
    ) throws BOSException, EASBizException {
        //审批表单
        FeishuApprovalInstanceFormCollection forms = new FeishuApprovalInstanceFormCollection();
        //1、单据编码
        FeishuI18nResourcesInfo numberTitle = getI18nResources(ctx, "单据编码", BizTypeEnum.TITLE);
        FeishuI18nResourcesInfo numberValue = getI18nResources(ctx, message.getBillNumber(), BizTypeEnum.REMARK);
        FeishuApprovalInstanceFormInfo numberFormInfo = new FeishuApprovalInstanceFormInfo();
        //表单字段名称。
        numberFormInfo.setName(numberTitle);
        //表单值。
        numberFormInfo.setValue(numberValue);
        forms.add(numberFormInfo);
        //2、单据类型
        FeishuI18nResourcesInfo billTypeTitle = getI18nResources(ctx, "单据类型", BizTypeEnum.TITLE);
        //获取单据对象
        EntityObjectInfo entityObjectInfo = BaseMetaDataUtil.getEntityObjectInfo(ctx, BOSUuid.read(message.getBillId()).getType());
        FeishuI18nResourcesInfo billTypeValue = getI18nResources(ctx, entityObjectInfo.getDisplayName(), BizTypeEnum.TITLE);
        FeishuApprovalInstanceFormInfo billTypeFormInfo = new FeishuApprovalInstanceFormInfo();
        //表单字段名称。
        billTypeFormInfo.setName(billTypeTitle);
        //表单值。
        billTypeFormInfo.setValue(billTypeValue);
        forms.add(billTypeFormInfo);
        //3、发起人
        FeishuI18nResourcesInfo senderitle = getI18nResources(ctx, "发起人", BizTypeEnum.TITLE);
        FeishuI18nResourcesInfo senderName = getI18nResources(ctx, sender.getName(), BizTypeEnum.REMARK);
        FeishuApprovalInstanceFormInfo senderFormInfo = new FeishuApprovalInstanceFormInfo();
        //表单字段名称。
        senderFormInfo.setName(senderitle);
        //表单值。
        senderFormInfo.setValue(senderName);
        forms.add(senderFormInfo);
        return forms;
    }

    /**
     * 创建任务数据
     *
     * @param ctx
     * @param message
     * @param pcLink
     * @param mobileLink
     * @return
     * @throws BOSException
     * @throws EASBizException
     */
    private static FeishuApprovalInstanceTaskListInfo createTask(
            Context ctx,
            WSMessage message,
            String pcLink,
            String mobileLink
    ) throws BOSException, EASBizException {
        IUser iUser = UserFactory.getLocalInstance(ctx);
        Time time = new Time(System.currentTimeMillis());
        String assignId = message.getSourceID();
        String title = message.getTitle().replace("\r", "\\r").replace("\n", "\\n");
        FeishuI18nResourcesInfo titleI18nResources = getI18nResources(ctx, title, BizTypeEnum.TITLE);

        FeishuApprovalInstanceTaskListInfo taskListInfo = new FeishuApprovalInstanceTaskListInfo();
        //审批实例内，审批任务的唯一标识，用于更新审批任务时定位数据。
        taskListInfo.setTaskId(assignId);
        //审批人
        UserInfo reciever = iUser.getUserByID(new ObjectUuidPK(message.getRecieverID()));
        taskListInfo.setUser(reciever);
        //审批任务名称
        taskListInfo.setTitle(titleI18nResources);
        //PC 端的跳转链接。
        taskListInfo.setPcLink(pcLink);
        //移动端的跳转链接
        taskListInfo.setMobileLink(mobileLink);
        //任务状态
        taskListInfo.setStatus(TaskStatusEnum.PENDING);
        //扩展字段。JSON 格式
        //taskListInfo.setExtra();
        //审批发起时间
        taskListInfo.setCreateTime(time);
        //审批发起时间
        taskListInfo.setEndTime(null);
        //审批实例最近更新时间
        taskListInfo.setUpdateTime(time);
        //列表页打开审批实例的方式
        taskListInfo.setDisplayMethod(DisplayMethodEnum.NORMAL);
        //审批节点 ID
        taskListInfo.setNodeId("");
        //
        taskListInfo.setNodeName(null);
        //任务生成类型
        taskListInfo.setGenerateType("DEFAULT");
        return taskListInfo;
    }

    /**
     * 根据文本获取多语言资源
     * 没有就新增
     *
     * @param ctx
     * @param title
     * @return
     * @throws BOSException
     * @throws EASBizException
     */
    private static FeishuI18nResourcesInfo getI18nResources(
            Context ctx,
            String title,
            BizTypeEnum bizType
    ) throws BOSException, EASBizException {
        IFeishuI18nResources iFeishuI18nResources = FeishuI18nResourcesFactory.getLocalInstance(ctx);
        FeishuI18nResourcesInfo feishuI18nResourcesInfo = null;
        FeishuI18nResourcesCollection feishuI18nResourcesCol = iFeishuI18nResources.getFeishuI18nResourcesCollection("where name='" + title + "'");
        if (feishuI18nResourcesCol.isEmpty()) {
            //新增多语言资源
            feishuI18nResourcesInfo = new FeishuI18nResourcesInfo();
            //HR组织
            feishuI18nResourcesInfo.setHrOrgUnit(HRORGUNITINFO);
            //共享策略:全局共享
            feishuI18nResourcesInfo.setUsePolicy(UsePolicy.GLOBALLY_SHARED);
            feishuI18nResourcesInfo.setName(title);
            //业务类型:标题
            feishuI18nResourcesInfo.setBizType(bizType);
            FeishuI18nResourcesEntryInfo entryInfo = new FeishuI18nResourcesEntryInfo();
            //语言
            entryInfo.setLocale(LocaleEnum.ZHCN);
            //文本
            entryInfo.setValue(title);
            feishuI18nResourcesInfo.getEntrys().add(entryInfo);
            iFeishuI18nResources.save(feishuI18nResourcesInfo);
        } else {
            feishuI18nResourcesInfo = feishuI18nResourcesCol.get(0);
        }
        return feishuI18nResourcesInfo;
    }

    /**
     * 根据bosType获取审批定义
     *
     * @param ctx
     * @param bosType
     * @return
     * @throws BOSException
     */
    private static FeishuApprovalsInfo getFeishuApprovalsInfo(
            Context ctx,
            BOSObjectType bosType
    ) throws BOSException {
        EntityObjectInfo entityObjectInfo = BaseMetaDataUtil.getEntityObjectInfo(ctx, bosType);
        String fullName = entityObjectInfo.getFullName();

        IFeishuApprovals iFeishuApprovals = FeishuApprovalsFactory.getLocalInstance(ctx);
        FeishuApprovalsCollection approvalsCollection = iFeishuApprovals.getFeishuApprovalsCollection("where model='" + fullName + "'");
        if (approvalsCollection.isEmpty()) {
            //没配置对应的审批定义,走默认的审批定义
            logger.error("实体[" + fullName + "]没配置对应的审批定义,走默认的审批定义");
            approvalsCollection = iFeishuApprovals.getFeishuApprovalsCollection("where model = 'default'");
        }
        FeishuApprovalsInfo feishuApprovalsInfo = approvalsCollection.get(0);
        return feishuApprovalsInfo;
    }


    /**
     * 获取pc待办地址
     *
     * @param message
     * @return
     * @throws UnsupportedEncodingException
     * @throws BOSException
     * @throws EASBizException
     */
    private static String getPcLink(
            FeishuConfigInfo defaultConfig,
            WSMessage message,
            String assignId
    ) throws BOSException {
        try {
            String shrAddr = defaultConfig.getSHRAddr();
            String billId = message.getBillId();
            StringBuilder pcLink = new StringBuilder();
            pcLink.append(shrAddr).append("/api/feishu/?redirect=");
            StringBuilder redirectUrl = new StringBuilder();
            String serverName = shrAddr;
            if (serverName.endsWith("/shr")) {
                serverName = serverName.replace("/shr", "");
            }
            redirectUrl.append(serverName).append("/easweb/webviews/workflow/transferApprove.jsp?AssignmentId=")
                    .append(URLEncoder.encode(assignId, "UTF-8")).append("&billID=").append(URLEncoder.encode(billId, "UTF-8"));
            pcLink.append(URLEncoder.encode(redirectUrl.toString(), "UTF-8"));
            logger.error("pcLink: " + pcLink);
            return pcLink.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new BOSException(e.getMessage());
        }
    }

    /**
     * 获取移动端待办地址
     *
     * @param message
     * @return
     * @throws BOSException
     * @throws EASBizException
     * @throws UnsupportedEncodingException
     */
    private static String getMobileLink(
            FeishuConfigInfo defaultConfig,
            WSMessage message,
            String assignId
    ) throws BOSException {
        try {
            String shrAddr = defaultConfig.getSHRAddr();
            String billId = message.getBillId();
            //mbos企业id
            String mEid = defaultConfig.getMEid();
            StringBuilder mobileLink = new StringBuilder();
            mobileLink.append(shrAddr).append("/api/feishu/?redirect=");

            String redirectUrl = MessageFormat.format(MBOS_URL,
                    URLEncoder.encode(assignId, "UTF-8"),
                    mEid,
                    "21231",
                    URLEncoder.encode(billId, "UTF-8"));
            mobileLink.append(URLEncoder.encode(redirectUrl.toString(), "UTF-8"));
            logger.error("mobileLink: " + mobileLink);
            return mobileLink.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new BOSException(e.getMessage());
        }
    }

    private static String getMessageTemplate() {
        return "{\n" +
                "    \"schema\": \"2.0\",\n" +
                "    \"config\": {\n" +
                "        \"update_multi\": true,\n" +
                "        \"locales\": [\n" +
                "            \"en_us\"\n" +
                "        ]\n" +
                "    },\n" +
                "    \"body\": {\n" +
                "        \"direction\": \"vertical\",\n" +
                "        \"padding\": \"12px 12px 12px 12px\",\n" +
                "        \"elements\": [\n" +
                "            {\n" +
                "                \"tag\": \"div\",\n" +
                "                \"text\": {\n" +
                "                    \"tag\": \"plain_text\",\n" +
                "                    \"content\": \"${titie}\",\n" +
                "                    \"text_size\": \"heading\",\n" +
                "                    \"text_align\": \"left\",\n" +
                "                    \"text_color\": \"red\"\n" +
                "                },\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"div\",\n" +
                "                \"text\": {\n" +
                "                    \"tag\": \"plain_text\",\n" +
                "                    \"content\": \"发起时间：${sendTime}\",\n" +
                "                    \"text_size\": \"notation\",\n" +
                "                    \"text_align\": \"left\",\n" +
                "                    \"text_color\": \"grey\"\n" +
                "                },\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"div\",\n" +
                "                \"text\": {\n" +
                "                    \"tag\": \"plain_text\",\n" +
                "                    \"content\": \"单据类型：${billType}\",\n" +
                "                    \"text_size\": \"notation\",\n" +
                "                    \"text_align\": \"left\",\n" +
                "                    \"text_color\": \"grey\"\n" +
                "                },\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"div\",\n" +
                "                \"text\": {\n" +
                "                    \"tag\": \"plain_text\",\n" +
                "                    \"content\": \"发起人：${sender}\",\n" +
                "                    \"text_size\": \"notation\",\n" +
                "                    \"text_align\": \"left\",\n" +
                "                    \"text_color\": \"grey\"\n" +
                "                },\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"hr\",\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"button\",\n" +
                "                \"text\": {\n" +
                "                    \"tag\": \"plain_text\",\n" +
                "                    \"content\": \"查看详情\"\n" +
                "                },\n" +
                "                \"type\": \"primary_filled\",\n" +
                "                \"width\": \"fill\",\n" +
                "                \"size\": \"medium\",\n" +
                "                \"behaviors\": [\n" +
                "                    {\n" +
                "                        \"type\": \"open_url\",\n" +
                "                        \"default_url\": \"https://applink.feishu.cn/client/mini_program/open?mode=appCenter&appId=cli_9cb844403dbb9108&path=pc%2fpages%2fin-process%2findex\",\n" +
                "                        \"pc_url\": \"https://applink.feishu.cn/client/mini_program/open?mode=appCenter&appId=cli_9cb844403dbb9108&path=pc%2fpages%2fin-process%2findex\",\n" +
                "                        \"ios_url\": \"https://applink.feishu.cn/client/mini_program/open?appId=cli_9cb844403dbb9108&path=pages%2fapproval-list%2findex%3fselectIndex%3d0\",\n" +
                "                        \"android_url\": \"https://applink.feishu.cn/client/mini_program/open?appId=cli_9cb844403dbb9108&path=pages%2fapproval-list%2findex%3fselectIndex%3d0\"\n" +
                "                    }\n" +
                "                ],\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            }\n" +
                "        ]\n" +
                "    },\n" +
                "    \"header\": {\n" +
                "        \"title\": {\n" +
                "            \"tag\": \"plain_text\",\n" +
                "            \"content\": \"你有一条hr流程待处理，请到审批中心处理！\",\n" +
                "            \"i18n_content\": {\n" +
                "                \"en_us\": \"\uD83D\uDC4B Dive into Bot Card Interactions: A Hands-on Tutorial\"\n" +
                "            }\n" +
                "        },\n" +
                "        \"subtitle\": {\n" +
                "            \"tag\": \"plain_text\",\n" +
                "            \"content\": \"\"\n" +
                "        },\n" +
                "        \"template\": \"blue\",\n" +
                "        \"icon\": {\n" +
                "            \"tag\": \"standard_icon\",\n" +
                "            \"token\": \"effects_outlined\"\n" +
                "        },\n" +
                "        \"padding\": \"12px 12px 12px 12px\"\n" +
                "    }\n" +
                "}";
    }

    /**
     * 绩效通知模板
     *
     * @return
     */
    private static String getPerwebMessageTemplate() {
        return "{\n" +
                "    \"schema\": \"2.0\",\n" +
                "    \"config\": {\n" +
                "        \"update_multi\": true,\n" +
                "        \"locales\": [\n" +
                "            \"en_us\"\n" +
                "        ],\n" +
                "        \"style\": {\n" +
                "            \"text_size\": {\n" +
                "                \"normal_v2\": {\n" +
                "                    \"default\": \"normal\",\n" +
                "                    \"pc\": \"normal\",\n" +
                "                    \"mobile\": \"heading\"\n" +
                "                }\n" +
                "            }\n" +
                "        }\n" +
                "    },\n" +
                "    \"body\": {\n" +
                "        \"direction\": \"vertical\",\n" +
                "        \"padding\": \"12px 12px 12px 12px\",\n" +
                "        \"elements\": [\n" +
                "            {\n" +
                "                \"tag\": \"markdown\",\n" +
                "                \"content\": \"${body}\",\n" +
                "                \"i18n_content\": {\n" +
                "                    \"en_us\": \"\uD83D\uDC4B <at id=\\\"${open_id}\\\"></at> Hello, ready to explore our **Bot Card Interaction Guide**? \uD83E\uDD16\\n\\nThis tutorial uses \\\"Alert System\\\" as an example to help you understand:\\n- Creating your first interactive card\\n- Setting up interactive buttons on your card\\n- Customizing your bot's menu options\"\n" +
                "                },\n" +
                "                \"text_align\": \"left\",\n" +
                "                \"text_size\": \"normal_v2\",\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"div\",\n" +
                "                \"text\": {\n" +
                "                    \"tag\": \"plain_text\",\n" +
                "                    \"content\": \"发送时间：${sendTime}\",\n" +
                "                    \"text_size\": \"notation\",\n" +
                "                    \"text_align\": \"left\",\n" +
                "                    \"text_color\": \"grey\"\n" +
                "                },\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"hr\",\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"tag\": \"column_set\",\n" +
                "                \"horizontal_align\": \"left\",\n" +
                "                \"columns\": [\n" +
                "                    {\n" +
                "                        \"tag\": \"column\",\n" +
                "                        \"width\": \"weighted\",\n" +
                "                        \"elements\": [\n" +
                "                            {\n" +
                "                                \"tag\": \"button\",\n" +
                "                                \"text\": {\n" +
                "                                    \"tag\": \"plain_text\",\n" +
                "                                    \"content\": \"去处理\",\n" +
                "                                    \"i18n_content\": {\n" +
                "                                        \"en_us\": \"Try It: Initiate Alert\"\n" +
                "                                    }\n" +
                "                                },\n" +
                "                                \"type\": \"primary_filled\",\n" +
                "                                \"width\": \"fill\",\n" +
                "                                \"size\": \"medium\",\n" +
                "                                \"behaviors\": [\n" +
                "                                    {\n" +
                "                                        \"type\": \"open_url\",\n" +
                "                                        \"default_url\": \"${url}\",\n" +
                "                                        \"pc_url\": \"${pc_url}\",\n" +
                "                                        \"ios_url\": \"${ios_url}\",\n" +
                "                                        \"android_url\": \"${android_url}\"\n" +
                "                                    }\n" +
                "                                ]\n" +
                "                            }\n" +
                "                        ],\n" +
                "                        \"direction\": \"horizontal\",\n" +
                "                        \"vertical_spacing\": \"8px\",\n" +
                "                        \"horizontal_align\": \"left\",\n" +
                "                        \"vertical_align\": \"top\",\n" +
                "                        \"weight\": 1\n" +
                "                    }\n" +
                "                ],\n" +
                "                \"margin\": \"0px 0px 0px 0px\"\n" +
                "            }\n" +
                "        ]\n" +
                "    },\n" +
                "    \"header\": {\n" +
                "        \"title\": {\n" +
                "            \"tag\": \"plain_text\",\n" +
                "            \"content\": \"${title}\",\n" +
                "            \"i18n_content\": {\n" +
                "                \"en_us\": \"\uD83D\uDC4B Dive into Bot Card Interactions: A Hands-on Tutorial\"\n" +
                "            }\n" +
                "        },\n" +
                "        \"subtitle\": {\n" +
                "            \"tag\": \"plain_text\",\n" +
                "            \"content\": \"\"\n" +
                "        },\n" +
                "        \"template\": \"blue\",\n" +
                "        \"icon\": {\n" +
                "            \"tag\": \"standard_icon\",\n" +
                "            \"token\": \"vote_colorful\"\n" +
                "        },\n" +
                "        \"padding\": \"12px 12px 12px 12px\"\n" +
                "    }\n" +
                "}";
    }
}
