SyncSignedFilesUtil.java 16 KB

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