diff --git a/antflow-base/pom.xml b/antflow-base/pom.xml index 7a03c58..10e38cb 100644 --- a/antflow-base/pom.xml +++ b/antflow-base/pom.xml @@ -200,6 +200,12 @@ 3.1.4 provided + + org.aspectj + aspectjweaver + 1.9.22 + provided + diff --git a/antflow-base/src/main/java/org/openoa/base/aspect/ExceptionLoggingAspect.java b/antflow-base/src/main/java/org/openoa/base/aspect/ExceptionLoggingAspect.java new file mode 100644 index 0000000..d711796 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/aspect/ExceptionLoggingAspect.java @@ -0,0 +1,86 @@ +package org.openoa.base.aspect; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.openoa.base.constant.StringConstants; +import org.openoa.base.entity.MethodReplayEntity; +import org.openoa.base.interf.MethodReplay; +import org.openoa.base.mapper.MethodReplayMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; + +@Aspect +@Component +@Slf4j +public class ExceptionLoggingAspect { + + @Autowired + private MethodReplayMapper methodReplayMapper; + @Value("${methodreplay.on:true}") + private boolean methodReplayOn; + + @Around("@annotation(methodReplay)") + public Object aroundMethodExecution(ProceedingJoinPoint joinPoint, MethodReplay methodReplay) throws Throwable { + Object result; + try { + result = joinPoint.proceed(); + } catch (Exception e) { + + if(methodReplayOn){ + // 如果抓取到异常就记录 类全路径 方法名 参数 异常信息 当前时间 全都存表 + + // 获取类全路径 + String className = joinPoint.getTarget().getClass().getName(); + if(ClassUtils.isCglibProxy(joinPoint.getTarget())){ + className=ClassUtils.getUserClass(joinPoint.getTarget()).getName(); + } + // 获取方法名 + String methodName = joinPoint.getSignature().getName(); + // 获取参数类型 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Class[] parameterTypes = method.getParameterTypes(); + // 获取参数 + Object[] args = joinPoint.getArgs(); + // 打印类全路径、方法名和参数 + log.info("MethodReplay接受到异常,Class name:{},Method name:{},parameterTypes:{}, Arguments:{}", className, methodName, JSON.toJSONString(parameterTypes), JSON.toJSONString(args)); + // 入库 + MethodReplayEntity methodReplayEntity = new MethodReplayEntity(); + methodReplayEntity.setProjectName(StringConstants.PROJECT_NAME); + methodReplayEntity.setClassName(className); + methodReplayEntity.setMethodName(methodName); + methodReplayEntity.setParamType(JSON.toJSONString(parameterTypes)); + methodReplayEntity.setArgs(JSON.toJSONString(args)); + String message=e.getMessage(); + if(message.length()>100){ + message=message.substring(0,100); + } + methodReplayEntity.setErrorMsg(message); + methodReplayEntity.setAlreadyReplayTimes(0); + methodReplayEntity.setMaxReplayTimes(methodReplay.maxReplayTimes()); + String sum=StringConstants.PROJECT_NAME + className + methodName + methodReplayEntity.getParamType() + methodReplayEntity.getArgs(); + CRC32 crc32=new CRC32(); + crc32.update(sum.getBytes(StandardCharsets.UTF_8)); + methodReplayEntity.setId(crc32.getValue()); + MethodReplayEntity exist = methodReplayMapper.selectById(methodReplayEntity); + if (exist == null) { + methodReplayMapper.insert(methodReplayEntity); + } + } + throw e; + } + return result; + } + + +} diff --git a/antflow-base/src/main/java/org/openoa/base/constant/MsgTopics.java b/antflow-base/src/main/java/org/openoa/base/constant/MsgTopics.java new file mode 100644 index 0000000..6dfaa99 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/constant/MsgTopics.java @@ -0,0 +1,10 @@ +package org.openoa.base.constant; + +public interface MsgTopics { + + /** + * 工作流状态变化发送消息topic + */ + String WORKFLOW_EVENT_PUSH="oa_workflow_event_push"; + +} diff --git a/antflow-base/src/main/java/org/openoa/base/constant/StringConstants.java b/antflow-base/src/main/java/org/openoa/base/constant/StringConstants.java index 90064f7..9914ba4 100644 --- a/antflow-base/src/main/java/org/openoa/base/constant/StringConstants.java +++ b/antflow-base/src/main/java/org/openoa/base/constant/StringConstants.java @@ -1,5 +1,7 @@ package org.openoa.base.constant; +import org.activiti.engine.impl.pvm.runtime.StartingExecution; + /** * @Classname StringConstant * @Description TODO @@ -24,4 +26,5 @@ public class StringConstants { public static final String ADAPTOR_FACTORY_BEANNAME="jimuAdaptorFactory"; public static final String TASK_ASSIGNEE_NAME="assigneeName"; + public static final String PROJECT_NAME="antFlow"; } diff --git a/antflow-base/src/main/java/org/openoa/base/constant/enums/BusinessCallbackEnum.java b/antflow-base/src/main/java/org/openoa/base/constant/enums/BusinessCallbackEnum.java new file mode 100644 index 0000000..2525de5 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/constant/enums/BusinessCallbackEnum.java @@ -0,0 +1,53 @@ +package org.openoa.base.constant.enums; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.EnumUtils; +import org.openoa.base.interf.BusinessCallBackAdaptor; +import org.openoa.base.service.BusinessCallBackFace; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * @Author tylerzhou + */ +@Slf4j +public enum BusinessCallbackEnum { + PROCESS_EVENT_CALLBACK(1, ProcessBusinessCallBackTypeEnum.class, "流程类回调枚举"), + ; + @Getter + private final Integer code; + @Getter + private final Class clsz; + @Getter + private final String desc; + + BusinessCallbackEnum(Integer code, Class clsz, String desc) { + this.code = code; + this.clsz = clsz; + this.desc = desc; + } + public static Map>getAllAdaptorsByType(){ + + Map> allAdaptorsOfType=new HashMap<>(); + for (BusinessCallbackEnum businessCallbackEnum : BusinessCallbackEnum.values()) { + Class clsz = businessCallbackEnum.getClsz(); + + BusinessCallBackFace businessCallBackFace = null; + try { + businessCallBackFace= clsz.getEnumConstants()[0]; + Set> allAdaptors = businessCallBackFace.getAllAdaptors(); + ListbusinessCallBackAdaptors=new ArrayList<>(); + for (Class adaptor : allAdaptors) { + BusinessCallBackAdaptor businessCallBackAdaptor = adaptor.newInstance(); + businessCallBackAdaptors.add(businessCallBackAdaptor); + } + allAdaptorsOfType.put(businessCallbackEnum,businessCallBackAdaptors); + }catch (Exception ex){ + log.error("error occur while creating instance by reflection"); + } + } + return allAdaptorsOfType; + } +} diff --git a/antflow-base/src/main/java/org/openoa/base/constant/enums/ProcessBusinessCallBackTypeEnum.java b/antflow-base/src/main/java/org/openoa/base/constant/enums/ProcessBusinessCallBackTypeEnum.java new file mode 100644 index 0000000..c6db680 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/constant/enums/ProcessBusinessCallBackTypeEnum.java @@ -0,0 +1,49 @@ +package org.openoa.base.constant.enums; + + +import lombok.Getter; +import org.openoa.base.interf.BusinessCallBackAdaptor; +import org.openoa.base.service.BusinessCallBackFace; +import org.openoa.base.service.ProcessEventSendMessageAdaptor; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * @Author tylerzhou + */ +public enum ProcessBusinessCallBackTypeEnum implements BusinessCallBackFace { + Send_MQ_Message(1, ProcessEventSendMessageAdaptor.class,"发送事件消息到mq队列") + ,; + @Getter + private Integer code; + + private Class clsz; + @Getter + private String desc; + + ProcessBusinessCallBackTypeEnum(Integer code, Classclsz, String desc){ + this.code = code; + this.clsz = clsz; + this.desc = desc; + } + + @Override + public Class getClsz() { + return this.clsz; + } + + public static ProcessBusinessCallBackTypeEnum getEnumByCode(Integer code){ + for (ProcessBusinessCallBackTypeEnum callBackTypeEnum : ProcessBusinessCallBackTypeEnum.values()) { + if(Objects.equals(callBackTypeEnum.getCode(), code)){ + return callBackTypeEnum; + } + } + return null; + } + @Override + public BusinessCallBackAdaptor getAdaptorByCode(Integer code) { + return null; + } +} diff --git a/antflow-base/src/main/java/org/openoa/base/entity/MethodReplayEntity.java b/antflow-base/src/main/java/org/openoa/base/entity/MethodReplayEntity.java new file mode 100644 index 0000000..fedd233 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/entity/MethodReplayEntity.java @@ -0,0 +1,19 @@ +package org.openoa.base.entity; + +import lombok.Data; + +@Data +public class MethodReplayEntity { + + private Long id; + private String projectName; + private String className; + private String methodName; + private String paramType; + private String args; + private String nowTime; + private String errorMsg; + private Integer alreadyReplayTimes; + private Integer maxReplayTimes; + +} diff --git a/antflow-base/src/main/java/org/openoa/base/interf/BpmBusinessProcessService.java b/antflow-base/src/main/java/org/openoa/base/interf/BpmBusinessProcessService.java new file mode 100644 index 0000000..c1c3d27 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/interf/BpmBusinessProcessService.java @@ -0,0 +1,7 @@ +package org.openoa.base.interf; + +import org.openoa.base.entity.BpmBusinessProcess; + +public interface BpmBusinessProcessService { + BpmBusinessProcess getBpmBusinessProcess(String processCode); +} diff --git a/antflow-base/src/main/java/org/openoa/base/interf/BusinessCallBackAdaptor.java b/antflow-base/src/main/java/org/openoa/base/interf/BusinessCallBackAdaptor.java new file mode 100644 index 0000000..ec63689 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/interf/BusinessCallBackAdaptor.java @@ -0,0 +1,6 @@ +package org.openoa.base.interf; + +public interface BusinessCallBackAdaptor { + void doCallBack(P param); + R formattedValue(P value); +} diff --git a/antflow-base/src/main/java/org/openoa/base/interf/MethodReplay.java b/antflow-base/src/main/java/org/openoa/base/interf/MethodReplay.java new file mode 100644 index 0000000..bdb3e9b --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/interf/MethodReplay.java @@ -0,0 +1,15 @@ +package org.openoa.base.interf; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MethodReplay { + + // 最大重放次数 + int maxReplayTimes() default 3; + +} diff --git a/antflow-base/src/main/java/org/openoa/base/mapper/MethodReplayMapper.java b/antflow-base/src/main/java/org/openoa/base/mapper/MethodReplayMapper.java new file mode 100644 index 0000000..88615f8 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/mapper/MethodReplayMapper.java @@ -0,0 +1,26 @@ +package org.openoa.base.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.openoa.base.entity.MethodReplayEntity; +import org.openoa.base.vo.MethodReplayDTO; + +import java.util.List; + +/** + * @author duggle.du + */ +@Mapper +public interface MethodReplayMapper { + + int insert(MethodReplayEntity methodReplayEntity); + + List select(MethodReplayDTO methodReplayDTO); + + int addAlreadyReplayTimes(MethodReplayEntity methodReplayEntity); + + int delete(MethodReplayEntity methodReplayEntity); + + MethodReplayEntity selectById(MethodReplayEntity methodReplayEntity); + + void deleteByIds(MethodReplayDTO methodReplayDTO); +} diff --git a/antflow-base/src/main/java/org/openoa/base/service/BaseSendMqMsgAdaptor.java b/antflow-base/src/main/java/org/openoa/base/service/BaseSendMqMsgAdaptor.java new file mode 100644 index 0000000..c960999 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/service/BaseSendMqMsgAdaptor.java @@ -0,0 +1,38 @@ +package org.openoa.base.service; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.NotImplementedException; +import org.openoa.base.interf.BusinessCallBackAdaptor; +import org.openoa.base.util.SpringBeanUtils; + +/** + * @Author tylerzhou + * Date on 2021/8/20 + */ +public abstract class BaseSendMqMsgAdaptor implements BusinessCallBackAdaptor { + + @Override + public void doCallBack(P param) { + R r = formattedValue(param); + if (r==null) { + return; + } + sendMqMessage(r); + } + + private void sendMqMessage(R value) { + if (value==null) { + return; + } + String message = JSON.toJSONString(value); + //todo + throw new NotImplementedException("send mq message method not implemented yet at the moment"); + } + + protected abstract String getTopicName(); + + protected String getUserName() { + //todo + throw new NotImplementedException("not implemented yet"); + } +} diff --git a/antflow-base/src/main/java/org/openoa/base/service/BusinessCallBackFace.java b/antflow-base/src/main/java/org/openoa/base/service/BusinessCallBackFace.java new file mode 100644 index 0000000..27cfe7a --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/service/BusinessCallBackFace.java @@ -0,0 +1,73 @@ +package org.openoa.base.service; + +import org.apache.commons.lang3.EnumUtils; +import org.openoa.base.interf.BusinessCallBackAdaptor; +import org.openoa.base.util.SpringBeanUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * @Author tylerzhou + */ +public interface BusinessCallBackFace { + default Set> getAllAdaptors(){ + Enum anEnum = (Enum) this; + List enumList = EnumUtils.getEnumList(anEnum.getClass()); + Set>resultSet=new HashSet<>(); + for (Object o : enumList) { + BusinessCallBackFace callBackFace = (BusinessCallBackFace)o; + Class clsz = (Class)callBackFace.getClsz(); + resultSet.add(clsz); + } + return resultSet; + } + Integer getCode(); + Class getClsz(); + default BusinessCallBackAdaptor getAdaptorByCode(Integer code){ + Enum anEnum = (Enum) this; + List enumList = EnumUtils.getEnumList(anEnum.getClass()); + for (Object en :enumList){ + BusinessCallBackFace callBackFace = (BusinessCallBackFace) en; + Integer adaptorCode = callBackFace.getCode(); + if(Objects.equals(adaptorCode,code)){ + Class clsz = callBackFace.getClsz(); + BusinessCallBackAdaptor callBackAdaptor=null; + try { + callBackAdaptor=clsz.newInstance(); + return callBackAdaptor; + }catch (Exception e){ + + } + } + } + return null; + } + default BusinessCallBackAdaptor getClszAdaptorInstance(){ + BusinessCallBackAdaptor adaptor=null; + try { + try { + + Component annotation = AnnotationUtils.findAnnotation(this.getClsz(),Component.class); + if(annotation!=null){ + adaptor= SpringBeanUtils.getBean(this.getClsz()); + } + }catch (Exception e){ + + + } + if(adaptor==null){ + adaptor= this.getClsz().newInstance(); + } + }catch (Exception e){ + + } + return adaptor; + } +} diff --git a/antflow-base/src/main/java/org/openoa/base/service/ConfigUtil.java b/antflow-base/src/main/java/org/openoa/base/service/ConfigUtil.java new file mode 100644 index 0000000..7d3b73d --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/service/ConfigUtil.java @@ -0,0 +1,19 @@ +package org.openoa.base.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class ConfigUtil { + + private static Boolean outsideCallbackOn; + + @Value("${outside.callback.switch:false}") + public void setOutsideCallbackOn(boolean outsideCallbackOn) { + ConfigUtil.outsideCallbackOn = outsideCallbackOn; + } + + public static boolean getOutsideCallbackOn() { + return Boolean.TRUE.equals(outsideCallbackOn); + } +} \ No newline at end of file diff --git a/antflow-base/src/main/java/org/openoa/base/service/ProcessEventSendMessageAdaptor.java b/antflow-base/src/main/java/org/openoa/base/service/ProcessEventSendMessageAdaptor.java new file mode 100644 index 0000000..59708fe --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/service/ProcessEventSendMessageAdaptor.java @@ -0,0 +1,62 @@ +package org.openoa.base.service; + + + +import org.apache.commons.lang3.StringUtils; +import org.openoa.base.constant.MsgTopics; +import org.openoa.base.entity.BpmBusinessProcess; +import org.openoa.base.exception.JiMuBizException; +import org.openoa.base.interf.BpmBusinessProcessService; +import org.openoa.base.util.SecurityUtils; +import org.openoa.base.util.SpringBeanUtils; +import org.openoa.base.vo.BusinessDataVo; +import org.openoa.base.vo.MqProcessEventVo; + +import java.util.Date; +import java.util.Optional; + +/** + * 流程提交,同意,撤销,打回,完成等触发发送消息事件 + * + * @Author tylerzhou + * Date on 2021/8/19 + */ +public class ProcessEventSendMessageAdaptor extends BaseSendMqMsgAdaptor { + @Override + public void doCallBack(BusinessDataVo param) { + if (param==null) { + return; + } + super.doCallBack(param); + } + + @Override + protected String getTopicName() { + return MsgTopics.WORKFLOW_EVENT_PUSH; + } + + @Override + public MqProcessEventVo formattedValue(BusinessDataVo businessDataVo) { + if (businessDataVo==null) { + return null; + } + String processNumber = businessDataVo.getProcessNumber(); + if (StringUtils.isEmpty(processNumber)) { + throw new JiMuBizException("未获取到流程编号!"); + } + BpmBusinessProcessService bpmBusinessProcessService = SpringBeanUtils.getBean(BpmBusinessProcessService.class); + BpmBusinessProcess bpmBusinessProcess = bpmBusinessProcessService.getBpmBusinessProcess(processNumber); + if (bpmBusinessProcess==null) { + return null; + } + MqProcessEventVo mqProcessEventVo = new MqProcessEventVo(); + mqProcessEventVo.setProcessCode(processNumber); + mqProcessEventVo.setBusinessId(bpmBusinessProcess.getBusinessId()); + mqProcessEventVo.setProcInstId(bpmBusinessProcess.getProcInstId()); + mqProcessEventVo.setButtonOperationType(businessDataVo.getMsgProcessEventEnum().getCode()); + mqProcessEventVo.setTaskId(businessDataVo.getTaskId()); + mqProcessEventVo.setOpTime(new Date()); + mqProcessEventVo.setOperationUserId(SecurityUtils.getLogInEmpIdSafe()); + return mqProcessEventVo; + } +} diff --git a/antflow-base/src/main/java/org/openoa/base/util/Retryer.java b/antflow-base/src/main/java/org/openoa/base/util/Retryer.java new file mode 100644 index 0000000..166978f --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/util/Retryer.java @@ -0,0 +1,51 @@ +package org.openoa.base.util; + +import static java.util.concurrent.TimeUnit.SECONDS; + +public interface Retryer extends Cloneable { + + void continueOrPropagate(RuntimeException e); + + class Default implements Retryer { + private long period; + private long maxPeriod; + private int attempt; + private int maxAttempts; + + public Default() { + this(SECONDS.toMillis(1), 3); + } + + public Default(long period, int maxAttempts){ + this.period = period; + this.maxPeriod = SECONDS.toMillis(10); + this.maxAttempts = maxAttempts; + this.attempt = 1; + } + + public Default(long period, long maxPeriod, int maxAttempts){ + this.period = period; + this.maxPeriod = maxPeriod; + this.maxAttempts = maxAttempts; + this.attempt = 1; + } + + @Override + public void continueOrPropagate(RuntimeException e) { + if (attempt++ >= maxAttempts) { + throw e; + } + try { + Thread.sleep(nextMaxInterval()); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + + long nextMaxInterval() { + long interval = (long) (period * Math.pow(1.5, attempt - 1.0)); + return interval > maxPeriod ? maxPeriod : interval; + } + + } +} diff --git a/antflow-base/src/main/java/org/openoa/base/vo/BusinessDataVo.java b/antflow-base/src/main/java/org/openoa/base/vo/BusinessDataVo.java index f12956e..edb9582 100644 --- a/antflow-base/src/main/java/org/openoa/base/vo/BusinessDataVo.java +++ b/antflow-base/src/main/java/org/openoa/base/vo/BusinessDataVo.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.openoa.base.constant.enums.MsgProcessEventEnum; import org.openoa.base.dto.PageDto; import java.util.List; @@ -229,4 +230,6 @@ public class BusinessDataVo extends PageDto { * level nodes */ private ListoutSideLevelNodes; + + private MsgProcessEventEnum msgProcessEventEnum; } diff --git a/antflow-base/src/main/java/org/openoa/base/vo/MethodReplayDTO.java b/antflow-base/src/main/java/org/openoa/base/vo/MethodReplayDTO.java new file mode 100644 index 0000000..f40afe0 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/vo/MethodReplayDTO.java @@ -0,0 +1,19 @@ +package org.openoa.base.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class MethodReplayDTO { + + private List delIds; + private Long id; + private String projectName; + private String className; + private String methodName; + private String args; + private String nowTime; + private String errorMsg; + +} diff --git a/antflow-base/src/main/java/org/openoa/base/vo/MqProcessEventVo.java b/antflow-base/src/main/java/org/openoa/base/vo/MqProcessEventVo.java new file mode 100644 index 0000000..7f63e94 --- /dev/null +++ b/antflow-base/src/main/java/org/openoa/base/vo/MqProcessEventVo.java @@ -0,0 +1,50 @@ +package org.openoa.base.vo; + +import lombok.Getter; +import lombok.Setter; +import org.openoa.base.constant.enums.MsgProcessEventEnum; + +import java.util.Date; + +/** + * 工作流 + * @Author tylerzhou + */ +@Getter +@Setter +public class MqProcessEventVo { + /** + * 流程编号(必填) + * + */ + private String processCode; + /** + * 流程formCode + */ + private String formCode; + /** + * 流程实例id + */ + private String procInstId; + /** + * 业务表businessId + */ + private String businessId; + /** + * 流程任务节点的id + */ + private String taskId; + /** + * 操作时间 + */ + private Date opTime; + /** + * 按钮事件类型 (必填) + * @see MsgProcessEventEnum + */ + private Integer buttonOperationType; + /** + * 触发当前按钮事件人员id + */ + private String operationUserId; +} diff --git a/antflow-engine/pom.xml b/antflow-engine/pom.xml index b74190c..fbf56c7 100644 --- a/antflow-engine/pom.xml +++ b/antflow-engine/pom.xml @@ -172,6 +172,12 @@ 8.0.27 provided + + org.apache.httpcomponents + httpclient + 4.5.14 + provided + org.aspectj aspectjweaver diff --git a/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/BpmBusinessProcessServiceImpl.java b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/BpmBusinessProcessServiceImpl.java index d66e158..521ac6f 100644 --- a/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/BpmBusinessProcessServiceImpl.java +++ b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/BpmBusinessProcessServiceImpl.java @@ -6,6 +6,7 @@ import org.openoa.base.constant.enums.ProcessEnum; import org.openoa.base.constant.enums.ProcessStateEnum; import org.openoa.base.entity.BpmBusinessProcess; +import org.openoa.base.interf.BpmBusinessProcessService; import org.openoa.engine.bpmnconf.mapper.BpmBusinessProcessMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -22,7 +23,7 @@ */ @Slf4j @Service -public class BpmBusinessProcessServiceImpl extends ServiceImpl { +public class BpmBusinessProcessServiceImpl extends ServiceImpl implements BpmBusinessProcessService { @Autowired diff --git a/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/callback/BusinessCallBackFactory.java b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/callback/BusinessCallBackFactory.java new file mode 100644 index 0000000..99da677 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/service/biz/callback/BusinessCallBackFactory.java @@ -0,0 +1,87 @@ +package org.openoa.engine.bpmnconf.service.biz.callback; + +import lombok.extern.slf4j.Slf4j; +import org.openoa.base.constant.enums.BusinessCallbackEnum; +import org.openoa.base.interf.BusinessCallBackAdaptor; +import org.openoa.base.service.BusinessCallBackFace; +import org.openoa.base.util.Retryer; +import org.springframework.util.CollectionUtils; + +import java.util.*; + +/** + * @Author tylerzhou + */ +@Slf4j +public class BusinessCallBackFactory { + private static Map> allAdaptorsOfType = null; + + static { + allAdaptorsOfType = BusinessCallbackEnum.getAllAdaptorsByType(); + } + + private BusinessCallBackFactory() { + + } + + private static final BusinessCallBackFactory instance = new BusinessCallBackFactory(); + + public static BusinessCallBackFactory build() { + return instance; + } + + /** + * 对单个adaptor的回调 + * + * @param params + * @param callbackEnum + * @param code {@link BusinessCallBackFace#getCode()} + */ + public void doCallBack(T params, BusinessCallbackEnum callbackEnum, Integer code) { + List callBackAdaptorsOfType = getCallBackAdaptorsOfType(callbackEnum); + if (CollectionUtils.isEmpty(callBackAdaptorsOfType)) { + return; + } + Class clsz = callbackEnum.getClsz(); + + BusinessCallBackFace[] enumFaces = clsz.getEnumConstants(); + for (BusinessCallBackFace callBackFace : enumFaces) { + if(Objects.equals(callBackFace.getCode(),code)){ + BusinessCallBackAdaptor businessCallBackAdaptor = callBackFace.getClszAdaptorInstance(); + Retryer retryer=new Retryer.Default(); + while (true){ + try { + businessCallBackAdaptor.doCallBack(params); + break; + }catch (RuntimeException e){ + retryer.continueOrPropagate(e); + } + } + + } + } + + } + + /** + * 对某一业务类型下的所有adaptor回调 + * + * @param params + * @param callbackEnum + */ + public void doCallBacks(T params, BusinessCallbackEnum callbackEnum) { + List callBackAdaptors = this.getCallBackAdaptorsOfType(callbackEnum); + for (BusinessCallBackAdaptor callBackAdaptor : callBackAdaptors) { + try { + callBackAdaptor.doCallBack(params); + } catch (Exception e) { + log.error("error while executing callback method,className:{}", callBackAdaptor.getClass().getName(), e); + } + } + } + + private List getCallBackAdaptorsOfType(BusinessCallbackEnum businessCallbackEnum) { + List businessCallBackAdaptors = allAdaptorsOfType.get(businessCallbackEnum); + return businessCallBackAdaptors; + } +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/util/JsonUtils.java b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/util/JsonUtils.java new file mode 100644 index 0000000..492a3a7 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/util/JsonUtils.java @@ -0,0 +1,41 @@ +package org.openoa.engine.bpmnconf.util; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.StringWriter; + +/** + * @Author tylerzhou + */ +public class JsonUtils { + private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); + private static final ObjectMapper mapper; + + static { + mapper=new ObjectMapper(); + mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + //mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + } + public static String transfer2JsonString(T value) { + + StringWriter sw = new StringWriter(); + + try { + JsonGenerator gen = (new JsonFactory()).createGenerator(sw); + mapper.writeValue(gen, value); + gen.close(); + } catch (IOException var5) { + logger.error(var5.getMessage(), "value=[" + value + "]"); + } + + return sw.toString(); + } +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/util/MsgSendMqUtil.java b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/util/MsgSendMqUtil.java new file mode 100644 index 0000000..14b3225 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/bpmnconf/util/MsgSendMqUtil.java @@ -0,0 +1,30 @@ +package org.openoa.engine.bpmnconf.util; + +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.openoa.base.constant.enums.BusinessCallbackEnum; +import org.openoa.base.constant.enums.ProcessBusinessCallBackTypeEnum; +import org.openoa.base.vo.BusinessDataVo; +import org.openoa.engine.bpmnconf.service.biz.callback.BusinessCallBackFactory; + +import java.util.Collections; +import java.util.List; + +/** + * @Author tylerzhou + */ +@Slf4j +public class MsgSendMqUtil { + + + public static void sendProcessEventChangeMessageCallBack(BusinessDataVo vo){ + if(vo==null){ + return; + } + try { + BusinessCallBackFactory.build().doCallBack(vo, BusinessCallbackEnum.PROCESS_EVENT_CALLBACK, ProcessBusinessCallBackTypeEnum.Send_MQ_Message.getCode()); + }catch (Exception e){ + log.error("流程消息发送失败,{}", JsonUtils.transfer2JsonString(vo),e); + } + } +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/conf/config/RestInterceptor.java b/antflow-engine/src/main/java/org/openoa/engine/conf/config/RestInterceptor.java new file mode 100644 index 0000000..8283ce5 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/conf/config/RestInterceptor.java @@ -0,0 +1,90 @@ +package org.openoa.engine.conf.config; + +import lombok.extern.slf4j.Slf4j; +import org.openoa.base.exception.JiMuBizException; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +/** + * + * @className: RestInterceptor + * @author: Tyler Zhou + **/ +@Slf4j +@Component +public class RestInterceptor implements ClientHttpRequestInterceptor { + + + + @Override + public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { + + StopWatch watch = new StopWatch(); + watch.start(); + ClientHttpResponse execute = clientHttpRequestExecution.execute(httpRequest, bytes); + watch.stop(); + long totalTimeMillis = watch.getTotalTimeMillis(); + intermediateProcessRequest(httpRequest, bytes); + intermediateProcessResponse(execute, totalTimeMillis); + return execute; + } + + private String getRequestBody(byte[] body) throws UnsupportedEncodingException { + if (body != null && body.length > 0) { + return (new String(body, StandardCharsets.UTF_8)); + } else { + return null; + } + } + + private void intermediateProcessRequest(HttpRequest request, byte[] body) throws IOException { + log.info("request method : " + request.getMethod() + " request body : " + getRequestBody(body)); + } + + private void intermediateProcessResponse(ClientHttpResponse response, long totalMils) throws IOException { + String body = getBodyString(response); + HttpStatus statusCode = response.getStatusCode(); + double totalSeconds = totalMils / 1000d; + log.info("response status code:{} response status text:{},请求耗时:{}, response body :{}", + response.getStatusCode(), response.getStatusText(),totalSeconds, body); + + if(HttpStatus.TOO_MANY_REQUESTS==statusCode){ + throw new JiMuBizException("请求限流"); + } + } + + private String getBodyString(ClientHttpResponse response) throws IOException { + try { + if (response != null && response.getBody() != null) // && + { + StringBuilder inputStringBuilder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8)); + String line = bufferedReader.readLine(); + while (line != null) { + inputStringBuilder.append(line); + inputStringBuilder.append('\n'); + line = bufferedReader.readLine(); + } + return inputStringBuilder.toString(); + } else { + return null; + } + } catch (IOException e) { + log.error(e.getMessage(), e); + return null; + } + } +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/conf/config/RestTemplateConfig.java b/antflow-engine/src/main/java/org/openoa/engine/conf/config/RestTemplateConfig.java new file mode 100644 index 0000000..ce2f491 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/conf/config/RestTemplateConfig.java @@ -0,0 +1,130 @@ +package org.openoa.engine.conf.config; + +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.openoa.engine.vo.HttpClientProperties; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Configuration +public class RestTemplateConfig { + + @Bean + RestTemplate restTemplate(@Qualifier("clientHttpRequestFactory") ClientHttpRequestFactory requestFactory,RestInterceptor restInterceptor) { + requestFactory = requestFactory instanceof BufferingClientHttpRequestFactory ? requestFactory : new BufferingClientHttpRequestFactory(requestFactory); + RestTemplate restTemplate = new RestTemplate(requestFactory); + List> messageConverters = restTemplate.getMessageConverters(); + for (HttpMessageConverter c : messageConverters) { + if (c instanceof StringHttpMessageConverter) { + ((StringHttpMessageConverter) c).setDefaultCharset(StandardCharsets.UTF_8); + } + } + restTemplate.getInterceptors().add(restInterceptor); + restTemplate.getMessageConverters().add(0,this.getFastJsonConverter()); + return restTemplate; + + } + + @Bean + @ConfigurationProperties(prefix = "spring.resttemplate") + HttpClientProperties httpClientProperties() { + return new HttpClientProperties(); + } + + @Bean(name = "clientHttpRequestFactory") + ClientHttpRequestFactory clientHttpRequestFactory(HttpClientProperties httpClientProperties) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + //如果不使用HttpClient的连接池,则使用restTemplate默认的SimpleClientHttpRequestFactory,底层基于HttpURLConnection + + if (!httpClientProperties.isUseHttpClientPool()) { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(httpClientProperties.getConnectTimeout()); + factory.setReadTimeout(httpClientProperties.getReadTimeout()); + return factory; + } + + TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true; + SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); + SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); + + ConnectionKeepAliveStrategy myStrategy = (response, context) -> { + HeaderElementIterator it = new BasicHeaderElementIterator + (response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String param = he.getName(); + String value = he.getValue(); + if (value != null && param.equalsIgnoreCase + ("timeout")) { + return Long.parseLong(value) * 1000; + } + } + return 5 * 1000; + }; + //HttpClient4.3及以上版本不手动设置HttpClientConnectionManager,默认就会使用连接池PoolingHttpClientConnectionManager + HttpClient httpClient = HttpClientBuilder + .create() + .setMaxConnTotal(httpClientProperties.getMaxTotalConnect()) + .setMaxConnPerRoute(httpClientProperties.getMaxConnectPerRoute()) + .evictExpiredConnections() + .evictIdleConnections(5000, TimeUnit.MILLISECONDS) + .setSSLSocketFactory(csf) + .setKeepAliveStrategy(myStrategy) + .build(); + + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + factory.setConnectTimeout(httpClientProperties.getConnectTimeout()); + factory.setReadTimeout(httpClientProperties.getReadTimeout()); + factory.setConnectionRequestTimeout(httpClientProperties.getConnectionRequestTimeout()); + return factory; + } + //自定义报文转换器 + FastJsonHttpMessageConverter getFastJsonConverter(){ + + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + // 设置feature + fastJsonConfig.setFeatures(Feature.AllowISO8601DateFormat); + // 设置日期格式、关闭循环引用检测 + fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat, + SerializerFeature.DisableCircularReferenceDetect); + //指定全局日期格式 + fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); + fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); + + //设定MediaType + fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,MediaType.APPLICATION_JSON_UTF8)); + return fastJsonHttpMessageConverter; + } + +} \ No newline at end of file diff --git a/antflow-engine/src/main/java/org/openoa/engine/factory/CallbackAdaptor.java b/antflow-engine/src/main/java/org/openoa/engine/factory/CallbackAdaptor.java new file mode 100644 index 0000000..08844b1 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/factory/CallbackAdaptor.java @@ -0,0 +1,36 @@ +package org.openoa.engine.factory; + + +import org.openoa.base.vo.BpmnConfVo; +import org.openoa.engine.vo.CallbackReqVo; +import org.openoa.engine.vo.CallbackRespVo; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public interface CallbackAdaptor { + + req formatRequest(BpmnConfVo bpmnConfVo); + + resp formatResponce(String resultJson); + + /** + * 获得创建的空的回调返回对象 + * + * @return + * @throws IllegalAccessException + * @throws InstantiationException + */ + default resp getNewRespObj() throws IllegalAccessException, InstantiationException { + Type[] genericInterfaces = this.getClass().getGenericInterfaces(); + + Type type = genericInterfaces[0]; + + ParameterizedType p = (ParameterizedType) type; + + Class cls = (Class) p.getActualTypeArguments()[1]; + + return (resp) cls.newInstance(); + } + +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/factory/ThirdPartyCallbackFactory.java b/antflow-engine/src/main/java/org/openoa/engine/factory/ThirdPartyCallbackFactory.java new file mode 100644 index 0000000..92d6c46 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/factory/ThirdPartyCallbackFactory.java @@ -0,0 +1,342 @@ +package org.openoa.engine.factory; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Maps; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.EntityUtils; +import org.openoa.base.constant.enums.CallbackTypeEnum; +import org.openoa.base.exception.JiMuBizException; +import org.openoa.base.service.ConfigUtil; +import org.openoa.base.util.DateUtil; +import org.openoa.base.util.SecurityUtils; +import org.openoa.base.util.SpringBeanUtils; +import org.openoa.base.vo.BaseIdTranStruVo; +import org.openoa.base.vo.BpmVerifyInfoVo; +import org.openoa.base.vo.BpmnConfVo; +import org.openoa.engine.bpmnconf.confentity.OutSideBpmCallbackUrlConf; +import org.openoa.engine.bpmnconf.service.biz.BpmVerifyInfoBizServiceImpl; +import org.openoa.engine.bpmnconf.service.impl.OutSideBpmBusinessPartyServiceImpl; +import org.openoa.engine.bpmnconf.service.impl.OutSideBpmCallbackUrlConfServiceImpl; +import org.openoa.engine.bpmnconf.util.JsonUtils; +import org.openoa.engine.vo.CallbackReqVo; +import org.openoa.engine.vo.CallbackRespVo; +import org.openoa.engine.vo.OutSideBpmAccessProcessRecordVo; +import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.net.URLEncoder; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +import static org.openoa.base.constant.enums.OpLogFlagEnum.BusinessException; + +@Slf4j +@Data +public class ThirdPartyCallbackFactory { + + private static volatile ThirdPartyCallbackFactory thirdPartyCallbackFactory; + + private static final ThreadLocalRandom random = ThreadLocalRandom.current(); + + private ThirdPartyCallbackFactory() { + + } + + /** + * CallbackFactory构建方法 + * + * @return + */ + public static ThirdPartyCallbackFactory build() { + + if (thirdPartyCallbackFactory==null) { + synchronized (ThirdPartyCallbackFactory.class){ + if(thirdPartyCallbackFactory==null){ + thirdPartyCallbackFactory = new ThirdPartyCallbackFactory(); + } + } + } + return thirdPartyCallbackFactory; + } + + /** + * 执行回调 + * + * @param url + * @param callbackTypeEnum + * @param bpmnConfVo + * @param + * @return + */ + public T doCallback(String url, CallbackTypeEnum callbackTypeEnum, BpmnConfVo bpmnConfVo, + String processNum, String businessId) { + + boolean callBackSwitch = ConfigUtil.getOutsideCallbackOn(); + + CallbackReqVo callbackReqVo = null; + + String resultJson = StringUtils.EMPTY; + + //设置请求头 + Map heads = Maps.newHashMap(); + + try { + + if (ObjectUtils.isEmpty(bpmnConfVo.getBusinessPartyId())) { + throw new JiMuBizException("业务方缺失,操作失败!"); + } + + CallbackAdaptor callbackAdaptor = getCallbackAdaptor(callbackTypeEnum.getBeanId()); + + if (callbackAdaptor==null) { + return null; + } + + //关闭回调逻辑 + if (!callBackSwitch) { + CallbackRespVo newRespObj = callbackAdaptor.getNewRespObj(); + if (!StringUtils.isEmpty(businessId)) { + Integer rendomNum = Math.abs(random.nextInt(Integer.MAX_VALUE)); + newRespObj.setBusinessId(String.valueOf(rendomNum)); + } + return (T) newRespObj; + } + + callbackReqVo = callbackAdaptor.formatRequest(bpmnConfVo); + + //事件类型 + callbackReqVo.setEventType(callbackTypeEnum.getMark()); + + //查询业务方标识 + OutSideBpmBusinessPartyServiceImpl bean = SpringBeanUtils.getBean(OutSideBpmBusinessPartyServiceImpl.class); + String businessPartyMarkById = bean.getBusinessPartyMarkById(bpmnConfVo.getBusinessPartyId()); + + //设置入参业务方标识 + callbackReqVo.setBusinessPartyMark(businessPartyMarkById); + + //表单编号 + callbackReqVo.setFormCode(formatFormCode(businessPartyMarkById, bpmnConfVo.getFormCode())); + + //流程编号 + callbackReqVo.setProcessNum(processNum); + + //如果流程编号不为空则根据流程编号查询流程路径 + if (!StringUtils.isEmpty(processNum)) { + BpmVerifyInfoBizServiceImpl bpmVerifyInfoNewService = SpringBeanUtils.getBean(BpmVerifyInfoBizServiceImpl.class); + boolean finishFlag = callbackTypeEnum.equals(CallbackTypeEnum.PROC_FINISH_CALL_BACK); + List bpmVerifyInfoVos = bpmVerifyInfoNewService.getBpmVerifyInfoVos(processNum,finishFlag); + callbackReqVo.setProcessRecord(bpmVerifyInfoVos + .stream() + .map(o -> OutSideBpmAccessProcessRecordVo + .builder() + .nodeName(o.getTaskName()) + .approvalTime(Optional.ofNullable(o.getVerifyDate()) + .map(od -> DateUtil.SDF_DATETIME_PATTERN.format(od)) + .orElse(StringUtils.EMPTY)) + .approvalStatusName(o.getVerifyStatusName()) + .approvalUserName(o.getVerifyUserName()) + .build()) + .collect(Collectors.toList())); + } + + //对接方返回业务编号 + callbackReqVo.setBusinessId(businessId); + + //获取当前登录人信息 + BaseIdTranStruVo loginedEmployee = SecurityUtils.getLogInEmpInfo(); + if (ObjectUtils.isEmpty(loginedEmployee)){ + loginedEmployee = new BaseIdTranStruVo(); + } + //查询外部流程url配置获取"用户标识"及"api-key" + OutSideBpmCallbackUrlConfServiceImpl outSideBpmCallbackUrlConfService = SpringBeanUtils.getBean(OutSideBpmCallbackUrlConfServiceImpl.class); + OutSideBpmCallbackUrlConf outSideBpmCallbackUrlConf = outSideBpmCallbackUrlConfService.getOutSideBpmCallbackUrlConf(bpmnConfVo.getId(), bpmnConfVo.getBusinessPartyId()); + + heads.put("sso-service", getCurrentSysDomain());//域名 + if (outSideBpmCallbackUrlConf!=null) { + heads.put("api-client-id", outSideBpmCallbackUrlConf.getApiClientId());//用户标识 + heads.put("api-client-secret", outSideBpmCallbackUrlConf.getApiClientSecrent());//api-key + } + heads.put("sso-uid", loginedEmployee.getId());//当前登录人username + heads.put("sso-name", URLEncoder.encode(loginedEmployee.getName(), "UTF-8"));//当前登录人真实姓名 + log.info("执行外部工作流回调,request:{} , processNumber:{} , callBackUrl:{} , 操作人:{} ,请求参数:{}",callbackTypeEnum.getDesc() , processNum, url, loginedEmployee.getName(), JsonUtils.transfer2JsonString(callbackReqVo)); + + resultJson = doPost(url, heads, callbackReqVo); + log.info("执行外部工作流回调,response:{} , processNumber:{} , callBackUrl:{} , 操作人:{} ,请求参数:{} , response:{}",callbackTypeEnum.getDesc() , processNum, url, loginedEmployee.getName(), JsonUtils.transfer2JsonString(callbackReqVo), resultJson); + + if (StringUtils.isEmpty(resultJson)) { + return null; + } + + JSONObject resultObject = JSON.parseObject(resultJson); + + Object status = resultObject.get("status"); + + String successMark = "000000"; + + if (!ObjectUtils.isEmpty(status) && successMark.equals(status.toString())) { + CallbackRespVo callbackRespVo = callbackAdaptor.formatResponce(resultJson); + if (callbackRespVo!=null) { + + //设置出参业务方标识 + callbackRespVo.setBusinessPartyMark(businessPartyMarkById); + + //返回结果 + return (T) callbackRespVo; + } + } else { + + Object message = resultObject.get("message"); + + String messageStr = StringUtils.EMPTY; + if (!ObjectUtils.isEmpty(message)) { + messageStr = message.toString(); + } + + if (!StringUtils.isEmpty(messageStr)) { + throw new JiMuBizException(messageStr); + } else { + throw new JiMuBizException("工作流对外服务回调失败"); + } + + } + + } catch (JiMuBizException e) { + log.error("工作流对外服务回调失败,回调类型:{},请求头信息{},入参:{},出参:{}", + callbackTypeEnum.getMark(), + JSON.toJSONString(heads), + JSON.toJSONString(Optional.ofNullable(callbackReqVo).orElse(new CallbackReqVo())), + resultJson, e); + throw new JiMuBizException(e.getMessage()); + } catch (Exception e) { + log.error("工作流对外服务回调失败,回调类型:{},请求头信息{},入参:{},出参:{}", + callbackTypeEnum.getMark(), + JSON.toJSONString(heads), + JSON.toJSONString(Optional.ofNullable(callbackReqVo).orElse(new CallbackReqVo())), + resultJson, e); + return null; + } + + return null; + } + + /** + * 格式化表单编号 + * + * @param businessPartyMarkById + * @param formCode + * @return + */ + public String formatFormCode(String businessPartyMarkById, String formCode) { + if (formCode.startsWith(businessPartyMarkById)) { + return formCode; + } + return StringUtils.join(businessPartyMarkById, "_", formCode); + } + + /** + * 获得回调适配 + * + * @param eventType + * @return + */ + private CallbackAdaptor getCallbackAdaptor(String eventType) { + return SpringBeanUtils.getBean(eventType, CallbackAdaptor.class); + } + + /** + * 获得OA域名 + * + * @return + */ + private String getCurrentSysDomain() { + + HttpServletRequest request = getHttpServletRequest(); + + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath(); + + return basePath; + } + + /** + * 获得HttpServletRequest对象 + * + * @return + */ + private HttpServletRequest getHttpServletRequest() { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return requestAttributes.getRequest(); + } + + /** + * 调用POST接口调用 + * + * @param url + * @param heads + * @param object + * @return + * @throws Exception + */ + private String doPost(String url, Map heads, Object object) throws Exception { + String resultStr; + + CloseableHttpClient httpclient = HttpClients.createDefault(); + + // 创建http POST请求 + HttpPost httpPost = new HttpPost(url); + + CloseableHttpResponse response = null; + + try { + + //头部配置Map不为空则向头部追加参数 + if (!CollectionUtils.isEmpty(heads)) { + heads.forEach((key, val) -> { + httpPost.addHeader(key, val); + }); + } + + httpPost.addHeader(HTTP.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE); + + String input = JSON.toJSONString(object); + + StringEntity stringEntity = new StringEntity(input, "UTF-8"); + stringEntity.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + + //设置参数到请求对象中 + httpPost.setEntity(stringEntity); + + response = httpclient.execute(httpPost); + + resultStr = EntityUtils.toString(response.getEntity(), "UTF-8"); + + } catch (Exception e) { + log.error("工作流对外服务回调POST接口失败:", e); + throw new Exception(e); + } finally { + if (response!=null) { + response.close(); + } + httpclient.close(); + } + + return resultStr; + } + +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/utils/HttpClientUtil.java b/antflow-engine/src/main/java/org/openoa/engine/utils/HttpClientUtil.java new file mode 100644 index 0000000..cd95192 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/utils/HttpClientUtil.java @@ -0,0 +1,100 @@ +package org.openoa.engine.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.utils.URIBuilder; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +/** + * 请不要在应用未启动之前使用此此中静态方法 + * @className: HttpClientUtil + * @author: Tyler Zhou + **/ +@Component +@Slf4j +public class HttpClientUtil implements ApplicationContextAware { + private static RestTemplate restTemplate; + + public static String doGet(String url) { + return doGet(url,null,null,String.class); + } + public static String doGet(String url, HttpHeaders headers) { + return doGet(url,headers, null,String.class); + } + public static T doGet(String url,HttpHeaders headers, Map param,Class cls){ + + try { + // 创建uri + URIBuilder builder = new URIBuilder(url); + if (param != null) { + for (String key : param.keySet()) { + builder.addParameter(key, param.get(key)); + } + } + URI uri = builder.build(); + HttpEntity requestEntity = new HttpEntity<>(null,headers); + + ResponseEntity exchange = restTemplate.exchange(uri,HttpMethod.GET,requestEntity,cls); + T body = exchange.getBody(); + return body; + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static String doPostForm(String url, MultiValueMapparams){ + return doPostForm(url,params,null,String.class); + } + public static String doPostForm(String url, MultiValueMap params, HttpHeaders headers){ + return doPostForm(url,params,headers,String.class); + } + public static T doPostForm(String url, MultiValueMap params, HttpHeaders headers, Class cls){ + if(headers==null){ + headers=new HttpHeaders(); + } + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + HttpEntity> requestEntity = new HttpEntity<>(params, headers); + ResponseEntity exchange = restTemplate.exchange(url, HttpMethod.POST, requestEntity, cls); + T body = exchange.getBody(); + return body; + } + public static String doPostJson(String url, Object body){ + return doPostJson(url,body,null,String.class); + } + public static String doPostJson(String url, Object body, HttpHeaders headers){ + return doPostJson(url,body,headers,String.class); + } + public static String doPostXml(String url,Object body){ + return doPostContent(url,body,null,MediaType.APPLICATION_XML,String.class); + } + public static T doPostJson(String url, Object body, HttpHeaders headers, Class tClass){ + return doPostContent(url,body,headers,MediaType.APPLICATION_JSON,tClass); + } + + public static T doPostContent(String url,Object body,HttpHeaders headers,MediaType mediaType,Class tClass){ + if(headers==null){ + headers=new HttpHeaders(); + } + headers.setContentType(mediaType); + HttpEntity requestEntity= new HttpEntity<>(body, headers); + ResponseEntity exchange = restTemplate.exchange(url, HttpMethod.POST, requestEntity, tClass); + T result = exchange.getBody(); + return result; + } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + restTemplate=applicationContext.getBean(RestTemplate.class); + } +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/vo/CallbackReqVo.java b/antflow-engine/src/main/java/org/openoa/engine/vo/CallbackReqVo.java new file mode 100644 index 0000000..c393468 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/vo/CallbackReqVo.java @@ -0,0 +1,41 @@ +package org.openoa.engine.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class CallbackReqVo implements Serializable { + + /** + * 事件类型 + */ + private String eventType; + + /** + * 业务方标识 + */ + private String businessPartyMark; + + /** + * 表单编号 + */ + private String formCode; + + /** + * 流程编号 + */ + private String processNum; + + /** + * 对接方业务编号 + */ + private String businessId; + + /** + * 流程记录列表 + */ + private List processRecord; + +} \ No newline at end of file diff --git a/antflow-engine/src/main/java/org/openoa/engine/vo/CallbackRespVo.java b/antflow-engine/src/main/java/org/openoa/engine/vo/CallbackRespVo.java new file mode 100644 index 0000000..4b04537 --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/vo/CallbackRespVo.java @@ -0,0 +1,30 @@ +package org.openoa.engine.vo; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class CallbackRespVo implements Serializable { + + /** + * 返回状态 + */ + private String status; + + /** + * 对接方返回业务编号 + */ + private String businessId; + + /** + * 业务方标识 + */ + private String businessPartyMark; + + /** + * 对接方返回业务扩展信息 + */ + private String extend; + +} diff --git a/antflow-engine/src/main/java/org/openoa/engine/vo/HttpClientProperties.java b/antflow-engine/src/main/java/org/openoa/engine/vo/HttpClientProperties.java new file mode 100644 index 0000000..dc770db --- /dev/null +++ b/antflow-engine/src/main/java/org/openoa/engine/vo/HttpClientProperties.java @@ -0,0 +1,46 @@ +package org.openoa.engine.vo; + +import lombok.Data; + +@Data +public class HttpClientProperties { + + /** + * 是否使用httpclient连接池 + */ + + private boolean useHttpClientPool = true; + + /** + * 从连接池中获得一个connection的超时时间 + */ + + private int connectionRequestTimeout = 3000; + + /** + * 建立连接超时时间 + */ + + private int connectTimeout = 3000; + + /** + * 建立连接后读取返回数据的超时时间 + */ + + private int readTimeout = 10000; + + /** + * 连接池的最大连接数,0代表不限 + */ + + private int maxTotalConnect = 128; + + /** + * 每个路由的最大连接数 + * the maximum number of concurrent connections per route + */ + + private int maxConnectPerRoute = 32; + + +} \ No newline at end of file diff --git a/antflow-engine/src/main/resources/mapper/MethodReplayMapper.xml b/antflow-engine/src/main/resources/mapper/MethodReplayMapper.xml new file mode 100644 index 0000000..e411f46 --- /dev/null +++ b/antflow-engine/src/main/resources/mapper/MethodReplayMapper.xml @@ -0,0 +1,61 @@ + + + + + + INSERT INTO OPS_METHOD_REPLAY ( + ID, + PROJECT_NAME, + CLASS_NAME, + METHOD_NAME, + PARAM_TYPE, + ARGS, + NOW_TIME, + ERROR_MSG, + ALREADY_REPLAY_TIMES, + MAX_REPLAY_TIMES + ) VALUES ( + #{id,jdbcType=BIGINT}, + #{projectName,jdbcType=VARCHAR}, + #{className,jdbcType=VARCHAR}, + #{methodName,jdbcType=VARCHAR}, + #{paramType,jdbcType=VARCHAR}, + #{args,jdbcType=VARCHAR}, + sysdate, + #{errorMsg,jdbcType=VARCHAR}, + #{alreadyReplayTimes,jdbcType=INTEGER}, + #{maxReplayTimes,jdbcType=INTEGER} + ) + + + + + + + + UPDATE OPS_METHOD_REPLAY SET ALREADY_REPLAY_TIMES = ALREADY_REPLAY_TIMES + 1 WHERE ID = #{id,jdbcType=BIGINT} + + + + DELETE FROM OPS_METHOD_REPLAY WHERE ID = #{id,jdbcType=BIGINT} + + + + DELETE FROM OPS_METHOD_REPLAY WHERE ID IN + + #{delId,jdbcType=BIGINT} + + + + diff --git a/pom.xml b/pom.xml index 281b3f9..e89a988 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,14 @@ org.springframework spring-webmvc + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpmime + diff --git a/web/src/main/resources/script/bpm_init_db.sql b/web/src/main/resources/script/bpm_init_db.sql index 9586c4c..d19b052 100644 --- a/web/src/main/resources/script/bpm_init_db.sql +++ b/web/src/main/resources/script/bpm_init_db.sql @@ -686,6 +686,26 @@ CREATE TABLE if not exists `t_user_email_send` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='user email send'; +create table if not exists t_method_replay +( + id int auto_increment + primary key, + PROJECT_NAME varchar(100) null comment 'project name', + CLASS_NAME varchar(255) null, + METHOD_NAME varchar(255) null, + PARAM_TYPE varchar(255) null, + ARGS text null, + NOW_TIME timestamp null, + ERROR_MSG text null, + ALREADY_REPLAY_TIMES int null, + MAX_REPLAY_TIMES int null +)ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 comment 'method replay records'; + +create index t_method_replay_NOW_TIME_index + on t_method_replay (NOW_TIME); + + CREATE TABLE if not exists `t_user_entrust` ( `id` int(11) NOT NULL AUTO_INCREMENT,