WeaverTodoSupport.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. package com.kingdee.eas.custom.weaverTodo.util;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.kingdee.bos.BOSException;
  4. import com.kingdee.bos.Context;
  5. import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
  6. import com.kingdee.bos.metadata.IMetaDataLoader;
  7. import com.kingdee.bos.metadata.MetaDataLoaderFactory;
  8. import com.kingdee.bos.metadata.entity.EntityObjectInfo;
  9. import com.kingdee.bos.util.BOSUuid;
  10. import com.kingdee.eas.base.message.AssignReadFactory;
  11. import com.kingdee.eas.base.message.AssignReadInfo;
  12. import com.kingdee.eas.base.message.IAssignRead;
  13. import com.kingdee.eas.base.message.URLInfo;
  14. import com.kingdee.eas.base.message.util.ProcessCenterUtil;
  15. import com.kingdee.eas.base.permission.IUser;
  16. import com.kingdee.eas.base.permission.UserFactory;
  17. import com.kingdee.eas.base.permission.UserInfo;
  18. import com.kingdee.eas.common.EASBizException;
  19. import com.kingdee.eas.custom.weaverTodo.config.WeaverTodoConfig;
  20. import com.kingdee.eas.custom.weaverTodo.enums.WeaverCallOperTypeEnum;
  21. import com.kingdee.eas.custom.weaverTodo.enums.WeaverIsRemarkEnum;
  22. import com.kingdee.eas.custom.weaverTodo.enums.WeaverRequestStatusEnum;
  23. import com.kingdee.eas.custom.weaverTodo.enums.WeaverViewTypeEnum;
  24. import com.kingdee.eas.util.app.DbUtil;
  25. import com.kingdee.jdbc.rowset.IRowSet;
  26. import org.apache.commons.lang3.StringUtils;
  27. import org.apache.log4j.Logger;
  28. import java.net.URLEncoder;
  29. import java.nio.charset.StandardCharsets;
  30. import java.sql.Timestamp;
  31. import java.text.SimpleDateFormat;
  32. import java.util.Date;
  33. /**
  34. * 统一待办字段拼装、相对链接、人员工号、本地日志表。
  35. */
  36. public class WeaverTodoSupport {
  37. private static final Logger logger = Logger.getLogger(WeaverTodoSupport.class);
  38. private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  39. private final WeaverTodoConfig config;
  40. public WeaverTodoSupport(WeaverTodoConfig config) {
  41. this.config = config;
  42. }
  43. public void delayForWf() {
  44. try {
  45. Thread.sleep(config.getWfDelayMs());
  46. } catch (InterruptedException e) {
  47. Thread.currentThread().interrupt();
  48. }
  49. }
  50. public String sanitize(String text) {
  51. if (text == null) {
  52. return "";
  53. }
  54. String s = text.replace("'", "")
  55. .replace(" ", " ")
  56. .replace("<", "")
  57. .replace(">", "")
  58. .replace("&", "")
  59. .replace("\"", "");
  60. if (s.length() > 2000) {
  61. s = s.substring(0, 2000);
  62. }
  63. return s.trim();
  64. }
  65. public String nowDatetime() {
  66. return SDF.format(new Date());
  67. }
  68. public String workflowCodeOf(String workflowname) {
  69. String name = sanitize(workflowname);
  70. if (StringUtils.isBlank(name)) {
  71. name = "SHR流程";
  72. }
  73. return String.valueOf(name.hashCode());
  74. }
  75. public String getAssignIdByMsgId(Context ctx, String msgId) throws BOSException, EASBizException {
  76. if (StringUtils.isBlank(msgId)) {
  77. return "";
  78. }
  79. IAssignRead assignReadIns = AssignReadFactory.getLocalInstance(ctx);
  80. AssignReadInfo assignReadInfo = assignReadIns.getAssignReadInfo(new ObjectUuidPK(msgId));
  81. if (assignReadInfo == null || assignReadInfo.getAssignID() == null) {
  82. return "";
  83. }
  84. return assignReadInfo.getAssignID().toString();
  85. }
  86. public String resolveBillId(Context ctx, String billId, String assignId) throws Exception {
  87. if (StringUtils.isNotBlank(billId)) {
  88. return billId;
  89. }
  90. if (StringUtils.isBlank(assignId)) {
  91. return "";
  92. }
  93. URLInfo info = ProcessCenterUtil.getUrlInfos(assignId, ctx);
  94. return info == null ? "" : StringUtils.defaultString(info.getBillID());
  95. }
  96. public String resolveWorkflowName(Context ctx, String msgId, String billId, String assignId) {
  97. String flowTypeName = "SHR流程";
  98. try {
  99. String resolvedBill = resolveBillId(ctx, billId, assignId);
  100. if (StringUtils.isBlank(resolvedBill) && StringUtils.isNotBlank(msgId)) {
  101. assignId = getAssignIdByMsgId(ctx, msgId);
  102. resolvedBill = resolveBillId(ctx, billId, assignId);
  103. }
  104. if (StringUtils.isBlank(resolvedBill)) {
  105. return flowTypeName;
  106. }
  107. IMetaDataLoader loader = MetaDataLoaderFactory.getRemoteMetaDataLoader();
  108. EntityObjectInfo vo = loader.getEntity(BOSUuid.read(resolvedBill).getType());
  109. if (vo != null && StringUtils.isNotBlank(vo.getDisplayName())) {
  110. flowTypeName = vo.getDisplayName();
  111. }
  112. } catch (Exception e) {
  113. logger.warn("解析流程类型名失败,使用默认", e);
  114. }
  115. return sanitize(flowTypeName);
  116. }
  117. /**
  118. * 报业返回人员工号 pp.FNumber(与 SSO PERSON_NUMBER 一致)
  119. */
  120. public String resolveReceiverWorkcode(Context ctx, String userIdOrNumber) {
  121. if (StringUtils.isBlank(userIdOrNumber)) {
  122. return "";
  123. }
  124. String key = userIdOrNumber.trim();
  125. try {
  126. String sql = "select uu.FNumber userNumber from T_PM_User uu "
  127. + "where uu.FForbidden = 0 and (uu.FNumber = ? or uu.FID = ?) "
  128. + "order by uu.FIsDelete";
  129. IRowSet rs = DbUtil.executeQuery(ctx, sql, new Object[]{key, key});
  130. if (rs.next()) {
  131. String userNumber = rs.getString("userNumber");
  132. if (StringUtils.isNotBlank(userNumber)) {
  133. return userNumber.trim();
  134. }
  135. }
  136. } catch (Exception e) {
  137. logger.warn("SQL 解析工号失败 key=" + key + ": " + e.getMessage());
  138. }
  139. return "";
  140. }
  141. /**
  142. * 按用户 id 取 UserInfo(BMC 发送人等)。
  143. */
  144. public UserInfo getUserById(Context ctx, String userId) {
  145. if (StringUtils.isBlank(userId)) {
  146. return null;
  147. }
  148. try {
  149. IUser iUser = UserFactory.getLocalInstance(ctx);
  150. return iUser.getUserInfo(new ObjectUuidPK(userId.trim()));
  151. } catch (Exception e) {
  152. logger.warn("按 id 取用户失败 userId=" + userId + ": " + e.getMessage());
  153. return null;
  154. }
  155. }
  156. /**
  157. * 泛微要求相对路径。落地走 SSO:entryPath?appId&redirect=审批页绝对地址(编码)。
  158. * entryPath 来自 weaver.sso.entryPath,默认 /qy/auth/weaver/app.do。
  159. */
  160. public String buildRelativePcUrl(Context ctx, String billId, String assignId) throws Exception {
  161. String resolvedBill = resolveBillId(ctx, billId, assignId);
  162. String approveAbs;
  163. if (StringUtils.isNotBlank(assignId)) {
  164. approveAbs = config.getShrServerName() + config.getApprovePcPath()
  165. + "?AssignmentId=" + URLEncoder.encode(assignId, StandardCharsets.UTF_8.name())
  166. + "&billID=" + URLEncoder.encode(StringUtils.defaultString(resolvedBill), StandardCharsets.UTF_8.name());
  167. } else {
  168. approveAbs = config.getShrServerName() + "/shr/dynamic.do?uipk=shr.workflow.view&inFrame=true";
  169. }
  170. String entryPath = StringUtils.defaultIfBlank(config.getSsoEntryPath(), "/qy/auth/weaver/app.do");
  171. String relative = entryPath + "?appId=" + URLEncoder.encode(config.getSsoAppId(), StandardCharsets.UTF_8.name())
  172. + "&redirect=" + URLEncoder.encode(approveAbs, StandardCharsets.UTF_8.name());
  173. if (relative.startsWith("http://") || relative.startsWith("https://")) {
  174. throw new IllegalStateException("pcurl 不允许绝对地址");
  175. }
  176. return relative;
  177. }
  178. public String buildRelativeAppUrl(Context ctx, String billId, String assignId) throws Exception {
  179. // 移动端暂与 PC 同路径,由泛微外链前缀区分端;后续可接 MBOS
  180. return buildRelativePcUrl(ctx, billId, assignId);
  181. }
  182. public JSONObject buildPayload(String flowid,
  183. String requestname,
  184. String workflowname,
  185. String nodename,
  186. String receiver,
  187. String creator,
  188. String pcurl,
  189. String appurl,
  190. WeaverIsRemarkEnum isRemark,
  191. WeaverViewTypeEnum viewType,
  192. WeaverRequestStatusEnum requestStatus) {
  193. JSONObject body = new JSONObject();
  194. body.put("syscode", config.getSyscode());
  195. body.put("flowid", StringUtils.defaultString(flowid));
  196. body.put("requestname", sanitize(requestname));
  197. String typeName = sanitize(workflowname);
  198. if (StringUtils.isBlank(typeName)) {
  199. typeName = "SHR流程";
  200. }
  201. body.put("workflowname", typeName);
  202. body.put("workflowcode", workflowCodeOf(typeName));
  203. body.put("nodename", sanitize(StringUtils.defaultIfBlank(nodename, "审批节点")));
  204. body.put("createdatetime", nowDatetime());
  205. body.put("receiver", sanitize(receiver));
  206. body.put("receivedatetime", nowDatetime());
  207. body.put("isremark", isRemark.getCode());
  208. body.put("viewtype", viewType.getCode());
  209. body.put("receivets", String.valueOf(System.currentTimeMillis()));
  210. if (StringUtils.isNotBlank(creator)) {
  211. body.put("creator", sanitize(creator));
  212. }
  213. if (StringUtils.isNotBlank(pcurl)) {
  214. body.put("pcurl", pcurl);
  215. }
  216. if (StringUtils.isNotBlank(appurl)) {
  217. body.put("appurl", appurl);
  218. }
  219. if (requestStatus != null) {
  220. body.put("requestStatus", requestStatus.getCode());
  221. }
  222. return body;
  223. }
  224. public void saveAssignRecord(Context ctx, String msgId, String assignId, String billId,
  225. String workflowname, String procInstId, String receiver) {
  226. // 优先带 receiver 列;老表没有该列则回退
  227. try {
  228. String sql = "insert into assignReadRecord(MsgId,assignID,billId,workflowname,procInstId,receiver) values(?,?,?,?,?,?)";
  229. Object[] params = new Object[]{
  230. nvl(msgId), nvl(assignId), nvl(billId), nvl(workflowname), nvl(procInstId), nvl(receiver)
  231. };
  232. DbUtil.execute(ctx, sql, params);
  233. return;
  234. } catch (Exception e) {
  235. logger.warn("带 receiver 写入 assignReadRecord 失败,尝试无 receiver 列: " + e.getMessage());
  236. }
  237. try {
  238. String sql = "insert into assignReadRecord(MsgId,assignID,billId,workflowname,procInstId) values(?,?,?,?,?)";
  239. Object[] params = new Object[]{
  240. nvl(msgId), nvl(assignId), nvl(billId), nvl(workflowname), nvl(procInstId)
  241. };
  242. DbUtil.execute(ctx, sql, params);
  243. } catch (Exception e) {
  244. logger.warn("写入 assignReadRecord 失败(表可能已存在同键或未建表): " + e.getMessage());
  245. }
  246. }
  247. /** 统一待办接口 URL(不含 access_token,便于落库)。 */
  248. public String buildReceiveTodoUrl() {
  249. return StringUtils.defaultString(config.getOpenapiHost())
  250. + StringUtils.defaultString(config.getReceivePath());
  251. }
  252. /** 消息中心 sendMsg URL(不含 access_token)。 */
  253. public String buildSendMsgUrl() {
  254. return StringUtils.defaultString(config.getOpenapiHost())
  255. + StringUtils.defaultString(config.getSendMsgPath());
  256. }
  257. public void addCallLog(Context ctx, String msgId, String assignId, String billId,
  258. WeaverCallOperTypeEnum operType, String requestUrl,
  259. String requestParams, String responseResult,
  260. int statusCode, String message) {
  261. try {
  262. logger.info("[WEAVER-TODO] CallLog write OperType=" + (operType == null ? "" : operType.getLabel())
  263. + " msgId=" + msgId + " assignId=" + assignId + " billId=" + billId
  264. + " requestUrl=" + requestUrl
  265. + " statusCode=" + statusCode + " message=" + message);
  266. String sql = "insert into OAMsgCallLog(MsgId,assignID,billId,OperType,RequestUrl,RequestParams,ResponseResult,StatusCode,Message,CreateTime) "
  267. + "values(?,?,?,?,?,?,?,?,?,?)";
  268. String operLabel = operType == null ? "" : operType.getLabel();
  269. Object[] params = new Object[]{
  270. msgId, assignId, billId, operLabel,
  271. StringUtils.defaultString(requestUrl),
  272. requestParams, responseResult,
  273. statusCode, message, new Timestamp(System.currentTimeMillis())
  274. };
  275. DbUtil.execute(ctx, sql, params);
  276. } catch (Exception e) {
  277. logger.warn("写入 OAMsgCallLog 失败: " + e.getMessage());
  278. }
  279. }
  280. /**
  281. * 截断字符串
  282. * @param s
  283. * @param maxLen
  284. * @return
  285. */
  286. private static String cutStr(String s, int maxLen) {
  287. if (s == null) {
  288. return "";
  289. }
  290. if (s.length() <= maxLen) {
  291. return s;
  292. }
  293. return s.substring(0, maxLen);
  294. }
  295. public String getMsgIdByAssignId(Context ctx, String assignId) {
  296. try {
  297. IRowSet rs = DbUtil.executeQuery(ctx, "select MsgId from assignReadRecord where assignID = ?",
  298. new Object[]{assignId});
  299. if (rs.next()) {
  300. return rs.getString("MsgId");
  301. }
  302. } catch (Exception e) {
  303. logger.warn("按 assignId 查 msgId 失败", e);
  304. }
  305. return "";
  306. }
  307. public String getProcInstIdByAssignId(Context ctx, String assignId) {
  308. try {
  309. IRowSet rs = DbUtil.executeQuery(ctx, "select procInstId from assignReadRecord where assignID = ?",
  310. new Object[]{assignId});
  311. if (rs.next()) {
  312. return rs.getString("procInstId");
  313. }
  314. } catch (Exception e) {
  315. logger.warn("按 assignId 查 procInstId 失败", e);
  316. }
  317. return "";
  318. }
  319. public String getBillIdByAssignId(Context ctx, String assignId) {
  320. try {
  321. IRowSet rs = DbUtil.executeQuery(ctx, "select billId from assignReadRecord where assignID = ?",
  322. new Object[]{assignId});
  323. if (rs.next()) {
  324. return StringUtils.defaultString(rs.getString("billId"));
  325. }
  326. } catch (Exception e) {
  327. logger.warn("get billId by assignId failed", e);
  328. }
  329. return "";
  330. }
  331. /**
  332. * 删除/撤销必须用当初推待办的 workflowname,否则泛微 upsert 键对不上。
  333. */
  334. public String getWorkflowNameByAssignId(Context ctx, String assignId) {
  335. try {
  336. IRowSet rs = DbUtil.executeQuery(ctx, "select workflowname from assignReadRecord where assignID = ?",
  337. new Object[]{assignId});
  338. if (rs.next()) {
  339. String name = rs.getString("workflowname");
  340. if (StringUtils.isNotBlank(name)) {
  341. return sanitize(name);
  342. }
  343. }
  344. } catch (Exception e) {
  345. logger.warn("get workflowname by assignId failed: " + e.getMessage());
  346. }
  347. try {
  348. IRowSet rs = DbUtil.executeQuery(ctx,
  349. "select RequestParams from OAMsgCallLog where assignID = ? and OperType = ? order by CreateTime desc",
  350. new Object[]{assignId, WeaverCallOperTypeEnum.TODO_ADD.getLabel()});
  351. if (rs.next()) {
  352. String raw = rs.getString("RequestParams");
  353. if (StringUtils.isNotBlank(raw)) {
  354. JSONObject json = JSONObject.parseObject(raw);
  355. String name = json.getString("workflowname");
  356. if (StringUtils.isNotBlank(name)) {
  357. return sanitize(name);
  358. }
  359. }
  360. }
  361. } catch (Exception e) {
  362. logger.warn("parse workflowname from CallLog failed", e);
  363. }
  364. return "";
  365. }
  366. public String getReceiverByAssignId(Context ctx, String assignId) {
  367. try {
  368. IRowSet rs = DbUtil.executeQuery(ctx, "select receiver from assignReadRecord where assignID = ?",
  369. new Object[]{assignId});
  370. if (rs.next()) {
  371. String r = rs.getString("receiver");
  372. if (StringUtils.isNotBlank(r)) {
  373. return r;
  374. }
  375. }
  376. } catch (Exception e) {
  377. logger.warn("按 assignId 查 receiver 列失败,回退 CallLog: " + e.getMessage());
  378. }
  379. try {
  380. IRowSet rs = DbUtil.executeQuery(ctx,
  381. "select RequestParams from OAMsgCallLog where assignID = ? and OperType = ? order by CreateTime desc",
  382. new Object[]{assignId, WeaverCallOperTypeEnum.TODO_ADD.getLabel()});
  383. if (rs.next()) {
  384. String raw = rs.getString("RequestParams");
  385. if (StringUtils.isNotBlank(raw)) {
  386. JSONObject json = JSONObject.parseObject(raw);
  387. String r = json.getString("receiver");
  388. if (StringUtils.isNotBlank(r)) {
  389. return r;
  390. }
  391. }
  392. }
  393. } catch (Exception e) {
  394. logger.warn("从 OAMsgCallLog 解析 receiver 失败", e);
  395. }
  396. return "";
  397. }
  398. private static String nvl(String s) {
  399. return s == null ? "" : s;
  400. }
  401. }