package com.kingdee.eas.custom.esign.util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.kingdee.eas.base.permission.UserInfo; import org.apache.commons.lang3.StringUtils; import com.kingdee.bos.BOSException; import com.kingdee.bos.Context; import com.kingdee.bos.dao.ormapping.ObjectUuidPK; import com.kingdee.bos.metadata.entity.EntityViewInfo; import com.kingdee.bos.metadata.entity.FilterInfo; import com.kingdee.bos.metadata.entity.FilterItemInfo; import com.kingdee.bos.metadata.entity.SelectorItemCollection; import com.kingdee.bos.metadata.query.util.CompareType; import com.kingdee.eas.base.attachment.*; import com.kingdee.eas.basedata.person.PersonInfo; import com.kingdee.eas.common.EASBizException; import com.kingdee.eas.custom.esign.*; import com.kingdee.eas.custom.esign.tsign.hz.comm.EsignHttpResponse; import com.kingdee.shr.attachment.*; import com.kingdee.shr.attachment.AttachmentTypeEnum; import com.kingdee.shr.base.syssetting.exception.ShrWebBizException; import com.kingdee.shr.base.syssetting.ml.SHRWebResource; import com.kingdee.shr.ml.util.MutilanUtils; import com.kingdee.util.LocaleUtils; import com.kingdee.util.STConverter; import java.io.IOException; import java.text.MessageFormat; import java.util.HashSet; import java.util.Locale; import java.util.Set; /** * @Description 签署文件同步 * @Date 2025/11/29 14:22 * @Created by heyuan */ public class SyncSignedFilesUtil { // 员工页签的uipk private static final String EMPPAGEUIPK = "com.kingdee.eas.custom.esign.app.FileTab.form"; private static final SelectorItemCollection updateSic = new SelectorItemCollection(); static { updateSic.add("Fivouchered"); } /** * 同步员工页面的电子签附件信息。 *

* 根据传入的签署ID(signId),从电子签全域状态总览表中获取对应的记录, * 并判断是否已同步过附件。如果尚未同步,则通过e签宝接口下载相关文件和附件, * 并保存到系统中。 *

* * @param ctx 上下文对象,用于获取业务服务实例 * @param signId 签署记录的唯一标识(即电子签全域状态总览表的主键) * @throws BOSException 当出现业务异常时抛出,例如:已同步、流程ID为空、员工信息缺失或HTTP请求失败等 */ public static void syncAttachmentsForEmpPage(Context ctx, String signId) throws BOSException { try { IESignGlobalStatusOverview iESignGlobalStatusOverview = ESignGlobalStatusOverviewFactory.getLocalInstance(ctx); SelectorItemCollection sic = new SelectorItemCollection(); sic.add("*"); // 获取电子签全域状态总览表信息 ESignGlobalStatusOverviewInfo info = iESignGlobalStatusOverview.getESignGlobalStatusOverviewInfo(new ObjectUuidPK(signId), sic); // 判断是否已经同步过附件 boolean fivouchered = info.isFivouchered(); if (fivouchered) { throw new BOSException(MessageFormat.format("电子签全域状态总览表 [{0}],签署文件已经同步,请勿重复同步", info.getNumber())); } // 检查e签宝流程ID是否存在 String signFlowId = info.getSignFlowId(); if (StringUtils.isBlank(signFlowId)) { throw new BOSException(MessageFormat.format("电子签全域状态总览表 [{0}],e签宝流程id为空!", info.getNumber())); } // 检查关联的员工信息是否存在 PersonInfo person = info.getPerson(); if (person == null) { throw new BOSException(MessageFormat.format("电子签全域状态总览表 [{0}],员工为空!", info.getNumber())); } IFileTab iFileTab = FileTabFactory.getLocalInstance(ctx); FileTabInfo fileTabInfo = iFileTab.getFileTabInfo("where person.id ='" + person.getId() + "'"); if (fileTabInfo == null) { fileTabInfo = new FileTabInfo(); fileTabInfo.setPerson(person); iFileTab.addnew(fileTabInfo); } // 调用e签宝接口获取文件下载地址 EsignHttpResponse response = EsignHttpUtil.getFile_download_url(ctx, signFlowId, 3600, info.getEfileId()); JSONObject jsonObject = JSON.parseObject(response.getBody()); Integer code = (Integer) jsonObject.get("code"); // 处理返回结果并保存文件和附件 if (code == 0) { JSONObject jsonData = jsonObject.getJSONObject("data"); saveAttachment(ctx, fileTabInfo, jsonData.getJSONArray("files")); saveAttachment(ctx, fileTabInfo, jsonData.getJSONArray("attachments")); } //更新电子签全域状态总览表信息 info.setFivouchered(true); iESignGlobalStatusOverview.updatePartial(info, updateSic); } catch (Exception e) { e.printStackTrace(); throw new BOSException(e); } } /** * 保存附件 * * @param ctx * @param fileTabInfo * @throws IOException * @throws ShrWebBizException * @throws BOSException * @throws EASBizException */ private static void saveAttachment( Context ctx, FileTabInfo fileTabInfo, JSONArray attachments ) throws IOException, ShrWebBizException, BOSException, EASBizException { IESignTemplateFileEntry ieSignTemplateFileEntry = ESignTemplateFileEntryFactory.getLocalInstance(ctx); IAttachment iAttachment = AttachmentFactory.getLocalInstance(ctx); IBoAttchAsso iBoAttchAsso = BoAttchAssoFactory.getLocalInstance(ctx); ISHRAttachmentExt ishrAttachmentExt = SHRAttachmentExtFactory.getLocalInstance(ctx); UserInfo userInfo = (UserInfo) ctx.get("UserInfo"); String userId = userInfo.getId().toString(); // 遍历并保存签署生成的正式文件 for (int i = 0; i < attachments.size(); i++) { JSONObject fileMap = attachments.getJSONObject(i); String fileName = fileMap.getString("fileName"); String downloadUrl = fileMap.getString("downloadUrl"); String fileId = "fileTab" + fileMap.getString("fileId"); // 查询对应模板文件的组件ID String componentId = ieSignTemplateFileEntry.findByFileName(fileName); // 查询附件是否存在,如果存在需要删除 delAttachment(ctx, fileId); byte[] content = DownloaderUtil.downloadFileToByteArray(downloadUrl); // 检查文件名是否包含脚本攻击 fileName = checkScriptAttack(fileName); String mainname = fileName.substring(0, fileName.lastIndexOf(46)); String extname = fileName.substring(fileName.lastIndexOf(46) + 1, fileName.length()); extname = extname.toLowerCase(); //附件对象 AttachmentInfo ai = new AttachmentInfo(); setAttMulNameAndDesc(ctx, ai, mainname, null); ai.setSimpleName(extname); ai.setFile(content); ai.setIsShared(false); //ai.setNumber(); ai.setSharedDesc(SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "false")); int size = content.length; if (size < 1024) { ai.setSize(size + SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "byte")); } else { ai.setSize(size / 1024 + "KB"); } ai.setSizeInByte(size); ai.setAttachID(fileId);//附件id ai.setType(getFileType(fileName)); ai.setBeizhu(EMPPAGEUIPK);//uipk //附件扩展 SHRAttachmentExtInfo attchExt = new SHRAttachmentExtInfo(); attchExt.setAttachment(ai); setAttExtMulNameAndDesc(ctx, attchExt, fileName, ""); attchExt.setPropertyName(componentId); attchExt.setType(AttachmentTypeEnum.FORM); attchExt.setState(AttachmentState.SAVE); String bunding = MessageFormat.format("{0}#{1}", userId, EMPPAGEUIPK); attchExt.setBunding(bunding); attchExt.setBoID(fileTabInfo.getId().toString());//业务对象id try { //保存附件对象 iAttachment.addnew(ai); } catch (Exception var20) { throw new ShrWebBizException(SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "upload_error"), var20); } attchExt.setState(AttachmentState.SAVE); //保存附件扩展 BoAttchAssoInfo boAttchAssoInfo = new BoAttchAssoInfo(); boAttchAssoInfo.setBoID(fileTabInfo.getId().toString());//业务对象id boAttchAssoInfo.setAssoBusObjType(String.valueOf(fileTabInfo.getBOSType())); // boAttchAssoInfo.setAssoType("Added Accessories", LocaleUtils.locale_l1); // boAttchAssoInfo.setAssoType("Added Accessories", LocaleUtils.locale_l2); // boAttchAssoInfo.setAssoType("Added Accessories", LocaleUtils.locale_l3); boAttchAssoInfo.setAttachment(ai); //保存 iBoAttchAsso.addnew(boAttchAssoInfo); ishrAttachmentExt.addnew(attchExt); } } /** * 删除附件及相关关联信息 * * @param ctx 上下文对象,用于获取服务实例 * @param fileId 文件ID,标识要删除的附件 * @throws BOSException 业务对象服务异常 * @throws EASBizException EAS业务异常 */ private static void delAttachment(Context ctx, String fileId) throws BOSException, EASBizException { if (StringUtils.isNotBlank(fileId)) { IAttachment iAttachment = AttachmentFactory.getLocalInstance(ctx); IBoAttchAsso iBoAttchAsso = BoAttchAssoFactory.getLocalInstance(ctx); ISHRAttachmentExt ishrAttachmentExt = SHRAttachmentExtFactory.getLocalInstance(ctx); FilterInfo filterInfo = new FilterInfo(); filterInfo.getFilterItems().add(new FilterItemInfo("attachID", fileId)); EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, null, null); //判断是否存在附件 if (iAttachment.exists(fileId)) { // 获取附件集合 AttachmentCollection attachmentCol = iAttachment.getAttachmentCollection(viewInfo); ObjectUuidPK[] attachMentIds = new ObjectUuidPK[attachmentCol.size()]; Set delIds = new HashSet(); // 收集需要删除的附件ID for (int i = 0; i < attachmentCol.size(); i++) { String id = attachmentCol.get(i).getId().toString(); attachMentIds[i] = new ObjectUuidPK(id); delIds.add(id); } // 构造删除条件并执行删除操作 FilterInfo deleteFilterInfo = new FilterInfo(); deleteFilterInfo.getFilterItems().add(new FilterItemInfo("attachment.id", delIds, CompareType.INCLUDE)); iAttachment.delete(attachMentIds); iBoAttchAsso.delete(deleteFilterInfo); ishrAttachmentExt.delete(deleteFilterInfo); } } } /** * 设置附件扩展信息的多语言名称和描述 * * @param ctx 上下文对象,用于获取当前区域设置 * @param info 附件扩展信息对象,要设置名称和描述的目标对象 * @param name 附件名称 * @param desc 附件描述 */ private static void setAttExtMulNameAndDesc(Context ctx, SHRAttachmentExtInfo info, String name, String desc) { if (info != null && !org.apache.commons.lang3.StringUtils.isEmpty(name)) { Locale locale = ctx.getLocale(); info.setName(name, LocaleUtils.locale_l1); info.setDescription(desc, LocaleUtils.locale_l1); // 根据当前区域设置确定简体/繁体中文的处理方式 if (LocaleUtils.locale_l1.getDisplayName().equals(LocaleUtils.getLocaleString(locale))) { // 当前区域为locale_l1时,将名称和描述同时设置到locale_l2和locale_l3 info.setName(name, LocaleUtils.locale_l2); info.setName(name, LocaleUtils.locale_l3); info.setDescription(desc, LocaleUtils.locale_l2); info.setDescription(desc, LocaleUtils.locale_l3); } else if (LocaleUtils.locale_l2.getDisplayName().equals(LocaleUtils.getLocaleString(locale))) { // 当前区域为locale_l2时,进行简繁转换处理 info.setName(name, LocaleUtils.locale_l2); info.setName(STConverter.sc2tc(name), LocaleUtils.locale_l3); if (!org.apache.commons.lang3.StringUtils.isEmpty(desc)) { info.setDescription(desc, LocaleUtils.locale_l2); info.setDescription(STConverter.sc2tc(desc), LocaleUtils.locale_l3); } } else { // 其他情况,进行反向简繁转换处理 info.setName(STConverter.tc2sc(name), LocaleUtils.locale_l2); info.setName(name, LocaleUtils.locale_l3); if (!org.apache.commons.lang3.StringUtils.isEmpty(desc)) { info.setDescription(STConverter.tc2sc(desc), LocaleUtils.locale_l2); info.setDescription(desc, LocaleUtils.locale_l3); } } } } /** * 根据文件全名获取文件类型描述 * * @param fullname 文件全名(包含扩展名) * @return 文件类型的描述字符串,如果扩展名未知则返回未知类型描述 */ private static String getFileType(String fullname) { // 提取文件扩展名 String extname = fullname.substring(fullname.lastIndexOf(46) + 1, fullname.length()); // 判断是否为Word文档类型 if (!"doc".equalsIgnoreCase(extname) && !"docx".equalsIgnoreCase(extname)) { // 判断是否为Excel文档类型 if (!"xls".equalsIgnoreCase(extname) && !"xlsx".equalsIgnoreCase(extname) && !"xlsm".equalsIgnoreCase(extname) && !"xlsb".equalsIgnoreCase(extname)) { // 判断是否为PowerPoint文档类型 if (!"ppt".equalsIgnoreCase(extname) && !"pptx".equalsIgnoreCase(extname) && !"pptm".equalsIgnoreCase(extname)) { // 判断是否为文本文件或其他未知类型 return "txt".equalsIgnoreCase(extname) ? SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "text") : MessageFormat.format(SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "unknow_type"), extname); } else { return SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "microsoft_ppt"); } } else { return SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "microsoft_excel"); } } else { return SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "microsoft_word"); } } /** * 检查并防止脚本攻击,对输入字符串中的HTML标签符号进行转义处理 * * @param str 待检查和处理的字符串 * @return 处理后的字符串,将HTML标签符号"<"和">"分别替换为"<"和">" */ private static String checkScriptAttack(String str) { // 检查字符串是否非空 if (!org.apache.commons.lang3.StringUtils.isEmpty(str)) { // 如果包含"<"符号,则进行HTML转义替换 if (str.contains("<")) { str.replaceAll("<", "<"); } // 如果包含">"符号,则进行HTML转义替换 if (str.contains(">")) { str.replaceAll(">", ">"); } } return str; } /** * 设置附件的多语言名称和描述信息 * * @param ctx 上下文对象,用于获取多语言环境信息 * @param att 附件信息对象,需要设置名称和描述的目标对象 * @param name 附件名称,如果为空则不进行设置 * @param desc 附件描述信息 */ private static void setAttMulNameAndDesc(Context ctx, AttachmentInfo att, String name, String desc) { // 当附件对象不为空且名称不为空时,设置附件的多语言名称和描述 if (att != null && !org.apache.commons.lang3.StringUtils.isEmpty(name)) { MutilanUtils.setMultiFieldValueToBean(ctx, "name", att, name); //MutilanUtils.setMultiFieldValueToBean(ctx, "description", att, desc); } } }