diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/IMkProcessClient.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/IMkProcessClient.java new file mode 100644 index 0000000..e19dbb8 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/IMkProcessClient.java @@ -0,0 +1,20 @@ +package org.springblade.transport.feign; + +import org.springblade.core.tool.api.FR; +import org.springblade.transport.pojo.dto.MkProcessSyncDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * MK 业务流程同步 Feign + */ +@FeignClient(value = "blade-transport") +public interface IMkProcessClient { + + String API_PREFIX = "/feign/client/mkProcess"; + String APPLY = API_PREFIX + "/apply"; + + @PostMapping(APPLY) + FR apply(@RequestBody MkProcessSyncDTO param); +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/MkProcessSyncDTO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/MkProcessSyncDTO.java new file mode 100644 index 0000000..2493d1b --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/MkProcessSyncDTO.java @@ -0,0 +1,33 @@ +package org.springblade.transport.pojo.dto; + +import io.swagger.v3.oas.annotations.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * MK 流程节点同步参数 + */ +@Data +@Schema(description = "MK 流程节点同步参数") +public class MkProcessSyncDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "业务类型,如 project-apply") + private String bizType; + + @Schema(description = "业务主键") + private Long id; + + @Schema(description = "动作:sync / approve / reject") + private String action; + + @Schema(description = "处理人姓名") + private String processorName; + + @Schema(description = "MK 流程实例详情") + private Object processInfo; +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java index 8241b21..6f83dd9 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java @@ -60,6 +60,12 @@ public class BusinessProcessController extends BladeController { return R.data(businessProcessService.processSubmit(param)); } + @PostMapping("/processDelete") + @ApiOperationSupport(order = 7) + public R processDelete(@RequestBody MKProcessCreateDTO param) { + return R.data(businessProcessService.processDelete(param == null ? null : param.getFormInstanceId())); + } + @GetMapping("/isEditView") @ApiOperationSupport(order = 3) @Operation(summary = "是否编辑页", description = "传入业务id") diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java index 2575abd..e3b7834 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java @@ -34,6 +34,14 @@ public interface IBusinessProcessService extends IService { */ String processSubmit(MKProcessCreateDTO param); + /** + * 按业务表单实例 id 删除 MK 流程(不删除本地业务流程记录,供驳回后重新提交使用) + * + * @param formInstanceId 业务表单实例 id + * @return 是否成功 + */ + boolean processDelete(String formInstanceId); + /** * 获取流程当前节点详情 * diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index 1b42e38..088c83d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -43,7 +43,9 @@ import org.springblade.thirdparty.mk.pojo.dto.sort.*; import org.springblade.thirdparty.mk.pojo.vo.*; import org.springblade.thirdparty.mk.service.IMKService; import org.springblade.transport.feign.ICustomerArchiveClient; +import org.springblade.transport.feign.IMkProcessClient; import org.springblade.transport.pojo.dto.CustomerProcessNodeSyncDTO; +import org.springblade.transport.pojo.dto.MkProcessSyncDTO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -69,6 +71,7 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery() + .eq(BusinessProcess::getBizId, bizId) + ); + if (businessProcess == null || StringUtil.isBlank(businessProcess.getProcessInstanceId())) { + log.warn("客商驳回后删除流程跳过,未找到流程实例 formInstanceId={}", formInstanceId); + return true; + } + String loginName = businessProcess.getPromoterLoginName(); + if (StringUtil.isBlank(loginName)) { + loginName = resolveCurrentUserPhone(); + } + AssertUtils.notBlank(loginName, "当前用户手机号为空,无法删除审核流"); + log.info("调用mk processDelete processInstanceId={} loginName={} formInstanceId={}", + businessProcess.getProcessInstanceId(), loginName, formInstanceId); + boolean deleted = mkService.processDelete(businessProcess.getProcessInstanceId(), loginName); + if (!deleted) { + throw new ServiceException("删除MK流程失败"); + } + return true; + } + @Override public List getCurrentNodes(String processInstanceId, String loginName) { if (StringUtils.isBlank(processInstanceId)) { @@ -226,6 +269,37 @@ public class BusinessProcessServiceImpl extends ServiceImpl result = mkProcessClient.apply(param); + log.info("同步业务当前节点完成 bizType={} bizId={} result={}", + resolvedBizType, bizId, JSON.toJSONString(result)); + } catch (Exception e) { + log.error("同步业务当前节点异常 bizType={} bizId={}", resolvedBizType, bizId, e); + } + } + private void syncCustomerArchiveFromProcessInfo(String formInstanceId, Object processInfo) { if (StringUtils.isBlank(formInstanceId) || processInfo == null) { log.warn("同步客商当前节点跳过,formInstanceId或流程实例详情为空 formInstanceId={}", formInstanceId); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java new file mode 100644 index 0000000..d8f0c35 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/MkProcessPublicController.java @@ -0,0 +1,55 @@ +package org.springblade.transport.controller; + +import com.alibaba.fastjson2.JSON; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.constant.AuthConstant; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.R; +import org.springblade.transport.mk.MkProcessMessageService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * MK 业务流程公开查看 + */ +@Slf4j +@RestController +@AllArgsConstructor +@TenantIgnore +@PreAuth(AuthConstant.PERMIT_ALL) +@RequestMapping("/mk-process/public") +@Tag(name = "MK业务流程公开查看", description = "MK业务流程公开查看") +public class MkProcessPublicController { + + private final MkProcessMessageService mkProcessMessageService; + + @GetMapping("/{bizType}/detail") + @ApiOperationSupport(order = 1) + @Operation(summary = "公开详情", description = "无需登录") + public R detail(@PathVariable String bizType, + @Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.data(mkProcessMessageService.handler(bizType).publicDetail(id)); + } + + @PostMapping("/{bizType}/process-message") + @ApiOperationSupport(order = 2) + @Operation(summary = "公开接收流程消息", description = "无需登录,立即查询当前节点,5秒后查询流程实例详情并同步业务") + public R processMessage(@PathVariable String bizType, @RequestBody Map body) { + log.info("公开页收到流程消息 bizType={} body={}", bizType, JSON.toJSONString(body)); + mkProcessMessageService.handleProcessMessage(bizType, body); + return R.success("ok"); + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java index 4cb6918..29b3962 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java @@ -105,7 +105,7 @@ public class ProjectApplyController extends BladeController { @ApiOperationSupport(order = 4) @Operation(summary = "新增或修改", description = "传入projectApply") public R submit(@RequestBody ProjectApply projectApply) { - return R.status(projectApplyService.submit(projectApply)); + return projectApplyService.submit(projectApply) ? R.data(projectApply) : R.fail("保存失败"); } @PostMapping("/submit-approval") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java index 68b83b1..3de3408 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java @@ -213,7 +213,7 @@ public class WaybillController extends BladeController { @ApiOperationSupport(order = 9) @Operation(summary = "新增或修改", description = "传入waybill") public R submit(@RequestBody Waybill waybill) { - return R.status(waybillService.submit(waybill)); + return waybillService.submit(waybill) ? R.data(waybill) : R.fail("保存失败"); } @PostMapping("/save-draft") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/MkProcessClient.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/MkProcessClient.java new file mode 100644 index 0000000..f99fa14 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/MkProcessClient.java @@ -0,0 +1,42 @@ +package org.springblade.transport.feign; + +import io.swagger.v3.oas.annotations.Hidden; +import lombok.AllArgsConstructor; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.constant.AuthConstant; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.FR; +import org.springblade.transport.mk.IMkProcessBizHandler; +import org.springblade.transport.mk.MkProcessMessageService; +import org.springblade.transport.pojo.dto.MkProcessSyncDTO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * MK 业务流程同步 Feign 实现 + */ +@Hidden +@RestController +@AllArgsConstructor +public class MkProcessClient implements IMkProcessClient { + + private final MkProcessMessageService mkProcessMessageService; + + @TenantIgnore + @PreAuth(AuthConstant.PERMIT_ALL) + @PostMapping(APPLY) + @Override + public FR apply(@RequestBody MkProcessSyncDTO param) { + if (param == null || param.getId() == null) { + return FR.data(false); + } + IMkProcessBizHandler handler = mkProcessMessageService.handler(param.getBizType()); + String action = param.getAction() == null ? "sync" : param.getAction(); + return switch (action) { + case "approve" -> FR.data(handler.approveFromProcess(param.getId(), param.getProcessorName())); + case "reject" -> FR.data(handler.rejectFromProcess(param.getId(), param.getProcessorName())); + default -> FR.data(handler.syncFromProcessInfo(param.getId(), param.getProcessInfo())); + }; + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/IMkProcessBizHandler.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/IMkProcessBizHandler.java new file mode 100644 index 0000000..3c8fc50 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/IMkProcessBizHandler.java @@ -0,0 +1,17 @@ +package org.springblade.transport.mk; + +/** + * MK 业务流程处理器 + */ +public interface IMkProcessBizHandler { + + String bizType(); + + Object publicDetail(Long id); + + boolean syncFromProcessInfo(Long id, Object processInfo); + + boolean approveFromProcess(Long id, String processorName); + + boolean rejectFromProcess(Long id, String processorName); +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java new file mode 100644 index 0000000..3ee3d92 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java @@ -0,0 +1,439 @@ +package org.springblade.transport.mk; + +import lombok.RequiredArgsConstructor; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.utils.Func; +import org.springblade.transport.pojo.entity.ContractManage; +import org.springblade.transport.pojo.entity.FormalSettlement; +import org.springblade.transport.pojo.entity.PaymentApplication; +import org.springblade.transport.pojo.entity.PreSettlement; +import org.springblade.transport.pojo.entity.ProjectApply; +import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.service.IContractManageService; +import org.springblade.transport.service.ICustomerArchiveService; +import org.springblade.transport.service.IFormalSettlementService; +import org.springblade.transport.service.IPaymentApplicationService; +import org.springblade.transport.service.IPreSettlementService; +import org.springblade.transport.service.IProjectApplyService; +import org.springblade.transport.service.IWaybillService; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 各业务 MK 流程处理器 + */ +public final class MkProcessBizHandlers { + + private MkProcessBizHandlers() { + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class CustomerHandler implements IMkProcessBizHandler { + private final ICustomerArchiveService service; + + @Override + public String bizType() { + return "customer-archive"; + } + + @Override + public Object publicDetail(Long id) { + return service.publicDetail(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + return service.syncProcessNodeFromProcessInfo(id, processInfo); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + return service.approveFromProcess(id, processorName); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + return service.rejectFromProcess(id, processorName); + } + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class ProjectHandler implements IMkProcessBizHandler { + private final IProjectApplyService service; + + @Override + public String bizType() { + return "project-apply"; + } + + @Override + public Object publicDetail(Long id) { + return service.detail(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + ProjectApply entity = service.getById(id); + if (entity == null) { + return false; + } + String node = MkProcessNodeHelper.currentNode(processInfo); + String processor = MkProcessNodeHelper.currentProcessor(processInfo); + if (Func.isEmpty(node) && Func.isEmpty(processor)) { + return false; + } + if (Func.isNotEmpty(node)) { + entity.setCurrentNode(node); + } + if (Func.isNotEmpty(processor)) { + entity.setCurrentProcessor(processor); + } + if (!"change_reviewing".equals(entity.getApprovalStatus())) { + entity.setApprovalStatus("reviewing"); + } + return service.updateById(entity); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + ProjectApply entity = service.getById(id); + if (entity == null || "approved".equals(entity.getApprovalStatus()) + || "change_approved".equals(entity.getApprovalStatus())) { + return entity != null; + } + boolean change = "change_reviewing".equals(entity.getApprovalStatus()); + entity.setApprovalStatus(change ? "change_approved" : "approved"); + entity.setEffectiveType("formal"); + entity.setCurrentNode("审核通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + entity.setApprovedTime(LocalDateTime.now()); + return service.updateById(entity); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + ProjectApply entity = service.getById(id); + if (entity == null || "rejected".equals(entity.getApprovalStatus()) + || "change_rejected".equals(entity.getApprovalStatus())) { + return entity != null; + } + boolean change = "change_reviewing".equals(entity.getApprovalStatus()); + entity.setApprovalStatus(change ? "change_rejected" : "rejected"); + entity.setCurrentNode("审核不通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class ContractHandler implements IMkProcessBizHandler { + private final IContractManageService service; + + @Override + public String bizType() { + return "contract-manage"; + } + + @Override + public Object publicDetail(Long id) { + return service.detail(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + ContractManage entity = service.getById(id); + if (entity == null) { + return false; + } + String node = MkProcessNodeHelper.currentNode(processInfo); + String processor = MkProcessNodeHelper.currentProcessor(processInfo); + if (Func.isEmpty(node) && Func.isEmpty(processor)) { + return false; + } + if (Func.isNotEmpty(node)) { + entity.setCurrentNode(node); + } + if (Func.isNotEmpty(processor)) { + entity.setCurrentProcessor(processor); + } + if (!"change_reviewing".equals(entity.getApprovalStatus())) { + entity.setApprovalStatus("reviewing"); + } + return service.updateById(entity); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + ContractManage entity = service.getById(id); + if (entity == null || "approved".equals(entity.getApprovalStatus()) + || "change_approved".equals(entity.getApprovalStatus())) { + return entity != null; + } + boolean change = "change_reviewing".equals(entity.getApprovalStatus()); + entity.setApprovalStatus(change ? "change_approved" : "approved"); + entity.setCurrentNode("审核通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + ContractManage entity = service.getById(id); + if (entity == null || "rejected".equals(entity.getApprovalStatus()) + || "change_rejected".equals(entity.getApprovalStatus())) { + return entity != null; + } + boolean change = "change_reviewing".equals(entity.getApprovalStatus()); + entity.setApprovalStatus(change ? "change_rejected" : "rejected"); + entity.setCurrentNode("审核不通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class WaybillHandler implements IMkProcessBizHandler { + private final IWaybillService service; + + @Override + public String bizType() { + return "waybill-manage"; + } + + @Override + public Object publicDetail(Long id) { + return service.getById(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + Waybill entity = service.getById(id); + if (entity == null) { + return false; + } + String node = MkProcessNodeHelper.currentNode(processInfo); + if (Func.isEmpty(node)) { + return false; + } + entity.setCurrentProcessNode(node); + return service.updateById(entity); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + Waybill entity = service.getById(id); + if (entity == null) { + return false; + } + entity.setCurrentProcessNode("审核通过"); + return service.updateById(entity); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + Waybill entity = service.getById(id); + if (entity == null) { + return false; + } + entity.setCurrentProcessNode("审核不通过"); + return service.updateById(entity); + } + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class PreSettlementHandler implements IMkProcessBizHandler { + private final IPreSettlementService service; + + @Override + public String bizType() { + return "pre-settlement"; + } + + @Override + public Object publicDetail(Long id) { + return service.detail(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + PreSettlement entity = service.getById(id); + if (entity == null) { + return false; + } + String node = MkProcessNodeHelper.currentNode(processInfo); + String processor = MkProcessNodeHelper.currentProcessor(processInfo); + if (Func.isEmpty(node) && Func.isEmpty(processor)) { + return false; + } + if (Func.isNotEmpty(node)) { + entity.setCurrentNode(node); + } + if (Func.isNotEmpty(processor)) { + entity.setCurrentProcessor(processor); + } + entity.setApprovalStatus("reviewing"); + return service.updateById(entity); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + PreSettlement entity = service.getById(id); + if (entity == null || "approved".equals(entity.getApprovalStatus())) { + return entity != null; + } + entity.setApprovalStatus("approved"); + entity.setCurrentNode("审核通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + entity.setApprovedTime(LocalDateTime.now()); + return service.updateById(entity); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + PreSettlement entity = service.getById(id); + if (entity == null || "returned".equals(entity.getApprovalStatus())) { + return entity != null; + } + entity.setApprovalStatus("returned"); + entity.setCurrentNode("审核不通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class FormalSettlementHandler implements IMkProcessBizHandler { + private final IFormalSettlementService service; + + @Override + public String bizType() { + return "formal-settlement"; + } + + @Override + public Object publicDetail(Long id) { + return service.detail(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + FormalSettlement entity = service.getById(id); + if (entity == null) { + return false; + } + String node = MkProcessNodeHelper.currentNode(processInfo); + String processor = MkProcessNodeHelper.currentProcessor(processInfo); + if (Func.isEmpty(node) && Func.isEmpty(processor)) { + return false; + } + if (Func.isNotEmpty(node)) { + entity.setCurrentNode(node); + } + if (Func.isNotEmpty(processor)) { + entity.setCurrentProcessor(processor); + } + entity.setApprovalStatus("reviewing"); + return service.updateById(entity); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + FormalSettlement entity = service.getById(id); + if (entity == null || "approved".equals(entity.getApprovalStatus())) { + return entity != null; + } + entity.setApprovalStatus("approved"); + entity.setCurrentNode("审核通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + entity.setApprovedTime(LocalDateTime.now()); + return service.updateById(entity); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + FormalSettlement entity = service.getById(id); + if (entity == null || "returned".equals(entity.getApprovalStatus())) { + return entity != null; + } + entity.setApprovalStatus("returned"); + entity.setCurrentNode("审核不通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + } + + @Component + @TenantIgnore + @RequiredArgsConstructor + public static class PaymentHandler implements IMkProcessBizHandler { + private final IPaymentApplicationService service; + + @Override + public String bizType() { + return "payment-application"; + } + + @Override + public Object publicDetail(Long id) { + return service.detail(id); + } + + @Override + public boolean syncFromProcessInfo(Long id, Object processInfo) { + PaymentApplication entity = service.getById(id); + if (entity == null) { + return false; + } + String node = MkProcessNodeHelper.currentNode(processInfo); + String processor = MkProcessNodeHelper.currentProcessor(processInfo); + if (Func.isEmpty(node) && Func.isEmpty(processor)) { + return false; + } + if (Func.isNotEmpty(node)) { + entity.setCurrentNode(node); + } + if (Func.isNotEmpty(processor)) { + entity.setCurrentProcessor(processor); + } + entity.setApprovalStatus("reviewing"); + return service.updateById(entity); + } + + @Override + public boolean approveFromProcess(Long id, String processorName) { + PaymentApplication entity = service.getById(id); + if (entity == null || "approved".equals(entity.getApprovalStatus())) { + return entity != null; + } + entity.setApprovalStatus("approved"); + entity.setCurrentNode("审核通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + + @Override + public boolean rejectFromProcess(Long id, String processorName) { + PaymentApplication entity = service.getById(id); + if (entity == null || "returned".equals(entity.getApprovalStatus())) { + return entity != null; + } + entity.setApprovalStatus("returned"); + entity.setCurrentNode("审核不通过"); + entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统")); + return service.updateById(entity); + } + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java new file mode 100644 index 0000000..7ca1ad3 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessMessageService.java @@ -0,0 +1,191 @@ +package org.springblade.transport.mk; + +import com.alibaba.fastjson2.JSON; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.FR; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.process.feign.IBusinessProcessClient; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 公开页流程消息:当前节点立即查询,流程实例详情延迟 5 秒查询后同步业务状态。 + */ +@Slf4j +@Service +@TenantIgnore +@RequiredArgsConstructor +public class MkProcessMessageService { + + private static final long PROCESS_INFO_DELAY_SECONDS = 5L; + + private final IBusinessProcessClient businessProcessClient; + private final List handlers; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2, runnable -> { + Thread thread = new Thread(runnable, "mk-process-info-" + THREAD_INDEX.incrementAndGet()); + thread.setDaemon(true); + return thread; + }); + + private static final AtomicInteger THREAD_INDEX = new AtomicInteger(); + + @PreDestroy + public void shutdown() { + scheduler.shutdown(); + } + + public IMkProcessBizHandler handler(String bizType) { + Map mapping = handlers.stream() + .collect(Collectors.toMap(IMkProcessBizHandler::bizType, Function.identity(), (a, b) -> a)); + IMkProcessBizHandler handler = mapping.get(bizType); + if (handler == null) { + throw new IllegalArgumentException("不支持的MK业务类型:" + bizType); + } + return handler; + } + + public void handleProcessMessage(String bizType, Map body) { + IMkProcessBizHandler handler = handler(bizType); + Map formValues = asMap(body == null ? null : body.get("formValues")); + String processId = firstText(formValues, "processId"); + if (StringUtil.isBlank(processId) && body != null) { + processId = firstText(body, "processId"); + } + String loginName = firstText(formValues, "mkLoginName", "loginName"); + if (StringUtil.isBlank(processId)) { + log.warn("公开页流程消息未找到 processId,跳过查询当前节点 bizType={}", bizType); + return; + } + String bizIdText = firstText(asMap(body == null ? null : body.get("formData")), "id", "formInstanceId"); + queryCurrentNodes(bizType, processId, loginName); + scheduleProcessInfo(handler, bizType, processId, loginName, bizIdText, formValues); + } + + private void queryCurrentNodes(String bizType, String processId, String loginName) { + try { + FR result = businessProcessClient.getCurrentNodes(processId, loginName); + Object nodeData = result == null ? null : result.getData(); + log.info("公开页流程消息当前节点详情 bizType={} processId={} loginName={} result={}", + bizType, processId, loginName, JSON.toJSONString(nodeData)); + } catch (Exception e) { + log.error("公开页查询当前节点失败 bizType={} processId={} loginName={}", bizType, processId, loginName, e); + } + } + + private void scheduleProcessInfo(IMkProcessBizHandler handler, String bizType, String processId, + String loginName, String bizIdText, Map formValues) { + Map formValuesCopy = new HashMap<>(formValues == null ? Map.of() : formValues); + log.info("公开页将在{}秒后查询流程实例详情 bizType={} processId={}", PROCESS_INFO_DELAY_SECONDS, bizType, processId); + scheduler.schedule( + () -> queryProcessInfo(handler, bizType, processId, loginName, bizIdText, formValuesCopy), + PROCESS_INFO_DELAY_SECONDS, + TimeUnit.SECONDS + ); + } + + private void queryProcessInfo(IMkProcessBizHandler handler, String bizType, String processId, + String loginName, String bizIdText, Map formValues) { + try { + FR processInfoResult = businessProcessClient.getProcessInfo(processId, loginName); + Object processInfo = processInfoResult == null ? null : processInfoResult.getData(); + log.info("公开页流程消息流程实例详情 bizType={} processId={} loginName={} result={}", + bizType, processId, loginName, JSON.toJSONString(processInfo)); + if (isProcessFinished(processInfo)) { + applyProcessResult(handler, bizType, bizIdText, processId, formValues, true); + } else if (isProcessRejected(processInfo)) { + applyProcessResult(handler, bizType, bizIdText, processId, formValues, false); + } else if (StringUtil.isNotBlank(bizIdText)) { + try { + handler.syncFromProcessInfo(Long.valueOf(bizIdText), processInfo); + } catch (NumberFormatException e) { + log.warn("公开页流程消息业务id格式不正确 bizType={} id={}", bizType, bizIdText); + } + } + } catch (Exception e) { + log.error("公开页查询流程实例详情失败 bizType={} processId={} loginName={}", bizType, processId, loginName, e); + } + } + + private static boolean isProcessFinished(Object processInfo) { + return "30".equals(firstText(asMap(processInfo), "fdProcessStatus")); + } + + private static boolean isProcessRejected(Object processInfo) { + Map info = asMap(processInfo); + if (!"20".equals(firstText(info, "fdProcessStatus"))) { + return false; + } + if (hasItems(info.get("currentHandlers"))) { + return false; + } + return !hasItems(asMap(info.get("fdTaskInfo")).get("handlerInfos")); + } + + private static boolean hasItems(Object value) { + return value instanceof Collection collection && !collection.isEmpty(); + } + + private void applyProcessResult(IMkProcessBizHandler handler, String bizType, String bizIdText, + String processId, Map formValues, boolean approved) { + String action = approved ? "审核通过" : "审核驳回"; + if (StringUtil.isBlank(bizIdText)) { + log.warn("流程{}但未找到业务id,跳过同步 bizType={} processId={}", action, bizType, processId); + return; + } + try { + String processorName = firstText(formValues, "mkUserName", "mkLoginName"); + Long bizId = Long.valueOf(bizIdText); + if (approved) { + handler.approveFromProcess(bizId, processorName); + } else { + handler.rejectFromProcess(bizId, processorName); + } + } catch (NumberFormatException e) { + log.warn("流程{}但业务id格式不正确 bizType={} id={}", action, bizType, bizIdText); + } + } + + private static Map asMap(Object value) { + if (!(value instanceof Map map)) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + map.forEach((key, nested) -> { + if (key != null) { + result.put(String.valueOf(key), nested); + } + }); + return result; + } + + private static String firstText(Map source, String... keys) { + if (source == null || keys == null) { + return null; + } + for (String key : keys) { + Object value = source.get(key); + if (value == null) { + continue; + } + String text = String.valueOf(value).trim(); + if (StringUtil.isNotBlank(text) && !"null".equalsIgnoreCase(text)) { + return text; + } + } + return null; + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessNodeHelper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessNodeHelper.java new file mode 100644 index 0000000..c665e58 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessNodeHelper.java @@ -0,0 +1,76 @@ +package org.springblade.transport.mk; + +import org.springblade.core.tool.jackson.JsonUtil; +import org.springblade.core.tool.utils.Func; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 解析 MK 流程实例详情中的当前节点、当前处理人 + */ +public final class MkProcessNodeHelper { + + private MkProcessNodeHelper() { + } + + public static String currentProcessor(Object processInfo) { + return joinDistinct(extractTexts(asMap(processInfo).get("currentHandlers"), "fdName", "name"), "、"); + } + + public static String currentNode(Object processInfo) { + Map taskInfo = asMap(asMap(processInfo).get("fdTaskInfo")); + return joinDistinct(extractTexts(taskInfo.get("handlerInfos"), "nodeName"), "、"); + } + + public static String text(String value, String fallback) { + return Func.isEmpty(value) ? fallback : value.trim(); + } + + public static Map asMap(Object value) { + if (value instanceof Map map) { + Map result = new LinkedHashMap<>(); + map.forEach((key, nested) -> { + if (key != null) { + result.put(String.valueOf(key), nested); + } + }); + return result; + } + if (value == null) { + return Map.of(); + } + try { + Map parsed = JsonUtil.toMap(JsonUtil.toJson(value)); + return parsed == null ? Map.of() : parsed; + } catch (Exception ignored) { + return Map.of(); + } + } + + private static List extractTexts(Object listObj, String... keys) { + List result = new ArrayList<>(); + if (!(listObj instanceof Collection collection) || keys == null) { + return result; + } + for (Object item : collection) { + Map map = asMap(item); + for (String key : keys) { + String text = Func.toStr(map.get(key), "").trim(); + if (Func.isNotEmpty(text)) { + result.add(text); + break; + } + } + } + return result; + } + + private static String joinDistinct(List values, String delimiter) { + return values.stream().filter(Func::isNotEmpty).distinct().collect(Collectors.joining(delimiter)); + } +} diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java index 8f7b9c1..f0a254d 100644 --- a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/pojo/dto/MKProcessCreateDTO.java @@ -1,6 +1,7 @@ package org.springblade.thirdparty.mk.pojo.dto; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.io.Serial; @@ -44,4 +45,9 @@ public class MKProcessCreateDTO implements Serializable { */ @JsonInclude(JsonInclude.Include.NON_NULL) private Object tempVarData; + /** + * 业务类型,仅 ERP 内部使用,不传给 MK + */ + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String bizType; } diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index f3052f2..81bcee2 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -218,11 +218,14 @@ blade: - /blade-auth/oauth/logout/** - /blade-transport/customer-archive/public/** - /customer-archive/public/** + - /blade-transport/mk-process/public/** + - /mk-process/public/** - /blade-openapi/openApi/mk/process/commonCallback - /openApi/mk/process/commonCallback - /feign/client/businessProcess/getCurrentNodes - /feign/client/businessProcess/getProcessInfo - /feign/client/customerArchive/syncProcessNode + - /feign/client/mkProcess/** #授权认证配置 auth: - method: ALL