SyncSignedFilesUtil.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. package com.kingdee.eas.custom.esign.util;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.kingdee.eas.base.permission.UserInfo;
  6. import org.apache.commons.lang3.StringUtils;
  7. import com.kingdee.bos.BOSException;
  8. import com.kingdee.bos.Context;
  9. import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
  10. import com.kingdee.bos.metadata.entity.EntityViewInfo;
  11. import com.kingdee.bos.metadata.entity.FilterInfo;
  12. import com.kingdee.bos.metadata.entity.FilterItemInfo;
  13. import com.kingdee.bos.metadata.entity.SelectorItemCollection;
  14. import com.kingdee.bos.metadata.query.util.CompareType;
  15. import com.kingdee.eas.base.attachment.*;
  16. import com.kingdee.eas.basedata.person.PersonInfo;
  17. import com.kingdee.eas.common.EASBizException;
  18. import com.kingdee.eas.custom.esign.*;
  19. import com.kingdee.eas.custom.esign.tsign.hz.comm.EsignHttpResponse;
  20. import com.kingdee.shr.attachment.*;
  21. import com.kingdee.shr.attachment.AttachmentTypeEnum;
  22. import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
  23. import com.kingdee.shr.base.syssetting.ml.SHRWebResource;
  24. import com.kingdee.shr.ml.util.MutilanUtils;
  25. import com.kingdee.util.LocaleUtils;
  26. import com.kingdee.util.STConverter;
  27. import org.apache.log4j.Logger;
  28. import java.io.IOException;
  29. import java.text.MessageFormat;
  30. import java.util.HashSet;
  31. import java.util.Locale;
  32. import java.util.Set;
  33. /**
  34. * @Description 签署文件同步
  35. * @Date 2025/11/29 14:22
  36. * @Created by heyuan
  37. */
  38. public class SyncSignedFilesUtil {
  39. private static Logger logger = Logger.getLogger(SyncSignedFilesUtil.class);
  40. // 员工页签的uipk
  41. private static final String EMPPAGEUIPK = "com.kingdee.eas.custom.esign.app.FileTab.form";
  42. private static final SelectorItemCollection updateSic = new SelectorItemCollection();
  43. static {
  44. updateSic.add("Fivouchered");
  45. }
  46. /**
  47. * 同步员工页面的电子签附件信息。
  48. * <p>
  49. * 根据传入的签署ID(signId),从电子签全域状态总览表中获取对应的记录,
  50. * 并判断是否已同步过附件。如果尚未同步,则通过e签宝接口下载相关文件和附件,
  51. * 并保存到系统中。
  52. * </p>
  53. *
  54. * @param ctx 上下文对象,用于获取业务服务实例
  55. * @param signId 签署记录的唯一标识(即电子签全域状态总览表的主键)
  56. * @throws BOSException 当出现业务异常时抛出,例如:已同步、流程ID为空、员工信息缺失或HTTP请求失败等
  57. */
  58. public static void syncAttachmentsForEmpPage(Context ctx, String signId) throws BOSException {
  59. try {
  60. IESignGlobalStatusOverview iESignGlobalStatusOverview =
  61. ESignGlobalStatusOverviewFactory.getLocalInstance(ctx);
  62. SelectorItemCollection sic = new SelectorItemCollection();
  63. sic.add("*");
  64. // 获取电子签全域状态总览表信息
  65. ESignGlobalStatusOverviewInfo info = iESignGlobalStatusOverview.getESignGlobalStatusOverviewInfo(new ObjectUuidPK(signId), sic);
  66. // 判断是否已经同步过附件
  67. boolean fivouchered = info.isFivouchered();
  68. if (fivouchered) {
  69. throw new BOSException(MessageFormat.format("电子签全域状态总览表 [{0}],签署文件已经同步,请勿重复同步", info.getNumber()));
  70. }
  71. // 检查e签宝流程ID是否存在
  72. String signFlowId = info.getSignFlowId();
  73. if (StringUtils.isBlank(signFlowId)) {
  74. throw new BOSException(MessageFormat.format("电子签全域状态总览表 [{0}],e签宝流程id为空!", info.getNumber()));
  75. }
  76. // 检查关联的员工信息是否存在
  77. PersonInfo person = info.getPerson();
  78. if (person == null) {
  79. throw new BOSException(MessageFormat.format("电子签全域状态总览表 [{0}],员工为空!", info.getNumber()));
  80. }
  81. IFileTab iFileTab = FileTabFactory.getLocalInstance(ctx);
  82. FileTabCollection fileTabCollection = iFileTab.getFileTabCollection("where person.id ='" + person.getId() + "'");
  83. FileTabInfo fileTabInfo = null;
  84. if (fileTabCollection.isEmpty()) {
  85. fileTabInfo = new FileTabInfo();
  86. fileTabInfo.setPerson(person);
  87. iFileTab.addnew(fileTabInfo);
  88. } else {
  89. fileTabInfo = fileTabCollection.get(0);
  90. }
  91. // 调用e签宝接口获取文件下载地址
  92. EsignHttpResponse response = EsignHttpUtil.getFile_download_url(ctx, signFlowId, 3600, info.getEfileId());
  93. JSONObject jsonObject = JSON.parseObject(response.getBody());
  94. Integer code = (Integer) jsonObject.get("code");
  95. // 处理返回结果并保存文件和附件
  96. if (code == 0) {
  97. JSONObject jsonData = jsonObject.getJSONObject("data");
  98. saveAttachment(ctx, fileTabInfo, jsonData.getJSONArray("files"));
  99. saveAttachment(ctx, fileTabInfo, jsonData.getJSONArray("attachments"));
  100. }
  101. //更新电子签全域状态总览表信息
  102. info.setFivouchered(true);
  103. iESignGlobalStatusOverview.updatePartial(info, updateSic);
  104. } catch (Exception e) {
  105. e.printStackTrace();
  106. throw new BOSException(e);
  107. }
  108. }
  109. /**
  110. * 保存附件
  111. *
  112. * @param ctx
  113. * @param fileTabInfo
  114. * @throws IOException
  115. * @throws ShrWebBizException
  116. * @throws BOSException
  117. * @throws EASBizException
  118. */
  119. private static void saveAttachment(
  120. Context ctx,
  121. FileTabInfo fileTabInfo,
  122. JSONArray attachments
  123. ) throws IOException, BOSException, EASBizException {
  124. IESignTemplateFileEntry ieSignTemplateFileEntry = ESignTemplateFileEntryFactory.getLocalInstance(ctx);
  125. IAttachment iAttachment = AttachmentFactory.getLocalInstance(ctx);
  126. IBoAttchAsso iBoAttchAsso = BoAttchAssoFactory.getLocalInstance(ctx);
  127. ISHRAttachmentExt ishrAttachmentExt = SHRAttachmentExtFactory.getLocalInstance(ctx);
  128. UserInfo userInfo = (UserInfo) ctx.get("UserInfo");
  129. String userId = userInfo.getId().toString();
  130. // 遍历并保存签署生成的正式文件
  131. for (int i = 0; i < attachments.size(); i++) {
  132. JSONObject fileMap = attachments.getJSONObject(i);
  133. String fileName = fileMap.getString("fileName");
  134. String downloadUrl = fileMap.getString("downloadUrl");
  135. String fileId = "fileTab" + fileMap.getString("fileId");
  136. logger.error("fileId: " + fileId);
  137. if (StringUtils.isBlank(downloadUrl)) {
  138. logger.error("下载地址为空!");
  139. continue;
  140. }
  141. // 查询对应模板文件的组件ID
  142. String componentId = ieSignTemplateFileEntry.findByFileName(fileName);
  143. // 查询附件是否存在,如果存在需要删除
  144. delAttachment(ctx, fileId);
  145. byte[] content = DownloaderUtil.downloadFileToByteArray(downloadUrl);
  146. // 检查文件名是否包含脚本攻击
  147. fileName = checkScriptAttack(fileName);
  148. if( fileName.lastIndexOf(46)<=0) {
  149. fileName +=".pdf";
  150. }
  151. String mainname = fileName.substring(0, fileName.lastIndexOf(46));
  152. String extname = fileName.substring(fileName.lastIndexOf(46) + 1, fileName.length());
  153. extname = extname.toLowerCase();
  154. //附件对象
  155. AttachmentInfo ai = new AttachmentInfo();
  156. setAttMulNameAndDesc(ctx, ai, mainname, null);
  157. ai.setSimpleName(extname);
  158. ai.setFile(content);
  159. ai.setIsShared(false);
  160. //ai.setNumber();
  161. ai.setSharedDesc(SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "false"));
  162. int size = content.length;
  163. if (size < 1024) {
  164. ai.setSize(size + SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "byte"));
  165. } else {
  166. ai.setSize(size / 1024 + "KB");
  167. }
  168. ai.setSizeInByte(size);
  169. ai.setAttachID(fileId);//附件id
  170. ai.setType(getFileType(fileName));
  171. ai.setBeizhu(EMPPAGEUIPK);//uipk
  172. //附件扩展
  173. SHRAttachmentExtInfo attchExt = new SHRAttachmentExtInfo();
  174. attchExt.setAttachment(ai);
  175. setAttExtMulNameAndDesc(ctx, attchExt, fileName, "");
  176. attchExt.setPropertyName(componentId);
  177. attchExt.setType(AttachmentTypeEnum.FORM);
  178. attchExt.setState(AttachmentState.SAVE);
  179. String bunding = MessageFormat.format("{0}#{1}", userId, EMPPAGEUIPK);
  180. attchExt.setBunding(bunding);
  181. attchExt.setBoID(fileTabInfo.getId().toString());//业务对象id
  182. try {
  183. //保存附件对象
  184. iAttachment.addnew(ai);
  185. } catch (Exception e) {
  186. e.printStackTrace();
  187. throw e;
  188. }
  189. attchExt.setState(AttachmentState.SAVE);
  190. //保存附件扩展
  191. BoAttchAssoInfo boAttchAssoInfo = new BoAttchAssoInfo();
  192. boAttchAssoInfo.setBoID(fileTabInfo.getId().toString());//业务对象id
  193. boAttchAssoInfo.setAssoBusObjType(String.valueOf(fileTabInfo.getBOSType()));
  194. // boAttchAssoInfo.setAssoType("Added Accessories", LocaleUtils.locale_l1);
  195. // boAttchAssoInfo.setAssoType("Added Accessories", LocaleUtils.locale_l2);
  196. // boAttchAssoInfo.setAssoType("Added Accessories", LocaleUtils.locale_l3);
  197. boAttchAssoInfo.setAttachment(ai);
  198. //保存
  199. iBoAttchAsso.addnew(boAttchAssoInfo);
  200. ishrAttachmentExt.addnew(attchExt);
  201. }
  202. }
  203. /**
  204. * 删除附件及相关关联信息
  205. *
  206. * @param ctx 上下文对象,用于获取服务实例
  207. * @param fileId 文件ID,标识要删除的附件
  208. * @throws BOSException 业务对象服务异常
  209. * @throws EASBizException EAS业务异常
  210. */
  211. private static void delAttachment(Context ctx, String fileId) throws BOSException, EASBizException {
  212. if (StringUtils.isNotBlank(fileId)) {
  213. IAttachment iAttachment = AttachmentFactory.getLocalInstance(ctx);
  214. IBoAttchAsso iBoAttchAsso = BoAttchAssoFactory.getLocalInstance(ctx);
  215. ISHRAttachmentExt ishrAttachmentExt = SHRAttachmentExtFactory.getLocalInstance(ctx);
  216. FilterInfo filterInfo = new FilterInfo();
  217. filterInfo.getFilterItems().add(new FilterItemInfo("attachID", fileId));
  218. EntityViewInfo viewInfo = EntityViewInfo.getInstance(filterInfo, null, null);
  219. //判断是否存在附件
  220. if (iAttachment.exists(filterInfo)) {
  221. // 获取附件集合
  222. AttachmentCollection attachmentCol = iAttachment.getAttachmentCollection(viewInfo);
  223. ObjectUuidPK[] attachMentIds = new ObjectUuidPK[attachmentCol.size()];
  224. Set<String> delIds = new HashSet<String>();
  225. // 收集需要删除的附件ID
  226. for (int i = 0; i < attachmentCol.size(); i++) {
  227. String id = attachmentCol.get(i).getId().toString();
  228. attachMentIds[i] = new ObjectUuidPK(id);
  229. delIds.add(id);
  230. }
  231. // 构造删除条件并执行删除操作
  232. FilterInfo deleteFilterInfo = new FilterInfo();
  233. deleteFilterInfo.getFilterItems().add(new FilterItemInfo("attachment.id", delIds, CompareType.INCLUDE));
  234. iAttachment.delete(attachMentIds);
  235. iBoAttchAsso.delete(deleteFilterInfo);
  236. ishrAttachmentExt.delete(deleteFilterInfo);
  237. }
  238. }
  239. }
  240. /**
  241. * 设置附件扩展信息的多语言名称和描述
  242. *
  243. * @param ctx 上下文对象,用于获取当前区域设置
  244. * @param info 附件扩展信息对象,要设置名称和描述的目标对象
  245. * @param name 附件名称
  246. * @param desc 附件描述
  247. */
  248. private static void setAttExtMulNameAndDesc(Context ctx, SHRAttachmentExtInfo info, String name, String desc) {
  249. if (info != null && !org.apache.commons.lang3.StringUtils.isEmpty(name)) {
  250. Locale locale = ctx.getLocale();
  251. info.setName(name, LocaleUtils.locale_l1);
  252. info.setDescription(desc, LocaleUtils.locale_l1);
  253. // 根据当前区域设置确定简体/繁体中文的处理方式
  254. if (LocaleUtils.locale_l1.getDisplayName().equals(LocaleUtils.getLocaleString(locale))) {
  255. // 当前区域为locale_l1时,将名称和描述同时设置到locale_l2和locale_l3
  256. info.setName(name, LocaleUtils.locale_l2);
  257. info.setName(name, LocaleUtils.locale_l3);
  258. info.setDescription(desc, LocaleUtils.locale_l2);
  259. info.setDescription(desc, LocaleUtils.locale_l3);
  260. } else if (LocaleUtils.locale_l2.getDisplayName().equals(LocaleUtils.getLocaleString(locale))) {
  261. // 当前区域为locale_l2时,进行简繁转换处理
  262. info.setName(name, LocaleUtils.locale_l2);
  263. info.setName(STConverter.sc2tc(name), LocaleUtils.locale_l3);
  264. if (!org.apache.commons.lang3.StringUtils.isEmpty(desc)) {
  265. info.setDescription(desc, LocaleUtils.locale_l2);
  266. info.setDescription(STConverter.sc2tc(desc), LocaleUtils.locale_l3);
  267. }
  268. } else {
  269. // 其他情况,进行反向简繁转换处理
  270. info.setName(STConverter.tc2sc(name), LocaleUtils.locale_l2);
  271. info.setName(name, LocaleUtils.locale_l3);
  272. if (!org.apache.commons.lang3.StringUtils.isEmpty(desc)) {
  273. info.setDescription(STConverter.tc2sc(desc), LocaleUtils.locale_l2);
  274. info.setDescription(desc, LocaleUtils.locale_l3);
  275. }
  276. }
  277. }
  278. }
  279. /**
  280. * 根据文件全名获取文件类型描述
  281. *
  282. * @param fullname 文件全名(包含扩展名)
  283. * @return 文件类型的描述字符串,如果扩展名未知则返回未知类型描述
  284. */
  285. private static String getFileType(String fullname) {
  286. // 提取文件扩展名
  287. String extname = fullname.substring(fullname.lastIndexOf(46) + 1, fullname.length());
  288. // 判断是否为Word文档类型
  289. if (!"doc".equalsIgnoreCase(extname) && !"docx".equalsIgnoreCase(extname)) {
  290. // 判断是否为Excel文档类型
  291. if (!"xls".equalsIgnoreCase(extname) && !"xlsx".equalsIgnoreCase(extname) && !"xlsm".equalsIgnoreCase(extname) && !"xlsb".equalsIgnoreCase(extname)) {
  292. // 判断是否为PowerPoint文档类型
  293. if (!"ppt".equalsIgnoreCase(extname) && !"pptx".equalsIgnoreCase(extname) && !"pptm".equalsIgnoreCase(extname)) {
  294. // 判断是否为文本文件或其他未知类型
  295. 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);
  296. } else {
  297. return SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "microsoft_ppt");
  298. }
  299. } else {
  300. return SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "microsoft_excel");
  301. }
  302. } else {
  303. return SHRWebResource.getString("com.kingdee.shr.base.syssetting.SHRSyssettingResource", "microsoft_word");
  304. }
  305. }
  306. /**
  307. * 检查并防止脚本攻击,对输入字符串中的HTML标签符号进行转义处理
  308. *
  309. * @param str 待检查和处理的字符串
  310. * @return 处理后的字符串,将HTML标签符号"<"和">"分别替换为"&lt;"和"&gt;"
  311. */
  312. private static String checkScriptAttack(String str) {
  313. // 检查字符串是否非空
  314. if (!org.apache.commons.lang3.StringUtils.isEmpty(str)) {
  315. // 如果包含"<"符号,则进行HTML转义替换
  316. if (str.contains("<")) {
  317. str.replaceAll("<", "&lt;");
  318. }
  319. // 如果包含">"符号,则进行HTML转义替换
  320. if (str.contains(">")) {
  321. str.replaceAll(">", "&gt;");
  322. }
  323. }
  324. return str;
  325. }
  326. /**
  327. * 设置附件的多语言名称和描述信息
  328. *
  329. * @param ctx 上下文对象,用于获取多语言环境信息
  330. * @param att 附件信息对象,需要设置名称和描述的目标对象
  331. * @param name 附件名称,如果为空则不进行设置
  332. * @param desc 附件描述信息
  333. */
  334. private static void setAttMulNameAndDesc(Context ctx, AttachmentInfo att, String name, String desc) {
  335. // 当附件对象不为空且名称不为空时,设置附件的多语言名称和描述
  336. if (att != null && !org.apache.commons.lang3.StringUtils.isEmpty(name)) {
  337. MutilanUtils.setMultiFieldValueToBean(ctx, "name", att, name);
  338. //MutilanUtils.setMultiFieldValueToBean(ctx, "description", att, desc);
  339. }
  340. }
  341. }