Merge branch 'feature/REQ-3502'

This commit is contained in:
yanglin 2025-02-20 13:33:40 +08:00
commit 40754a7bd0
65 changed files with 1425 additions and 670 deletions

View File

@ -21,6 +21,10 @@
</properties>
<dependencies>
<dependency>
<groupId>cn.axzo.workflow</groupId>
<artifactId>workflow-engine-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.org</groupId>
<artifactId>org-api</artifactId>

View File

@ -1,78 +0,0 @@
package cn.axzo.msg.center.event.card;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.msg.center.api.mq.CardPresetButtonPressedMessage;
import cn.axzo.msg.center.message.service.todo.TodoWithCardWrapper;
import cn.axzo.msg.center.message.service.todo.manage.TodoManager;
import cn.axzo.msg.center.notices.common.constans.CommonConstants;
import cn.axzo.msg.center.service.enums.MqMessageType;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @author xudawei@axzo.cn
* @date 2024/11/07
* @desc 卡片预设MQ消费
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CardChangeStateSyncTodoEventHandler implements EventHandler, InitializingBean {
private final EventConsumer eventConsumer;
private final TodoWithCardWrapper todoWithCardWrapper;
private final TodoManager todoManager;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
if (!MqMessageType.CARD_PRESET_BUTTON_PRESSED.getEventName().equalsIgnoreCase(event.getEventCode().getName())) {
return;
}
log.info("CardChangeStateSyncTodoEventHandler-start - handle mq event, event={}", JSON.toJSONString(event));
try {
long start = System.currentTimeMillis();
handleMqMessage(event);
long end = System.currentTimeMillis();
log.warn("CardChangeStateSyncTodoEventHandler-handle mq event, used={}ms, event={}", end - start, JSON.toJSONString(event));
} catch (Exception e) {
log.warn("CardChangeStateSyncTodoEventHandler-error - handle mq event, event={}", JSON.toJSONString(event), e);
}
}
/**
* 业务逻辑
*/
private void handleMqMessage(Event event) {
//解析数据
CardPresetButtonPressedMessage payload = event.normalizedData(CardPresetButtonPressedMessage.class);
if (Objects.isNull(payload)) {
return;
}
if (Objects.isNull(payload.getCardInfo())) {
return;
}
if (!CommonConstants.TODO_SYSN_CARD_APP_CODE.equalsIgnoreCase(payload.getCardInfo().getAppCode())) {
return;
}
Long operatorId = Objects.nonNull(payload.getOperatorId()) ? payload.getOperatorId() : 0L;
todoWithCardWrapper.fireTodoWhenPresetButtonPressedByCard(todoManager, payload.getPresetButtonType(), payload.getCardInfo(), operatorId);
}
@Override
public void afterPropertiesSet() {
Event.EventCode eventCode = new Event.EventCode(MqMessageType.CARD_PRESET_BUTTON_PRESSED.getEventModel(), MqMessageType.CARD_PRESET_BUTTON_PRESSED.getEventName());
eventConsumer.registerHandler(eventCode, this);
}
}

View File

@ -135,6 +135,12 @@ public class PushYouMengMessageHandler implements EventHandler, InitializingBean
return;
}
MessageHistoryUpdatedPayload.MsgBody msgBody = messageBody.resolveMsgBody();
if (msgBody == null) {
log.warn("push-handler, 模板code:{}, msgBody为空 [不是普通消息], event: {}", card.getTemplateCode(), event);
return;
}
youMengMessageService.sendPushMessage(MsgBody4Guest.builder()
.ty(0)
.f("0")

View File

@ -34,9 +34,9 @@ public class DiagnosisProps implements EnvironmentAware {
@Setter
@Getter
public static class RowCount {
private int warningThreshold = 6000;
private int warningThreshold = 1500;
private boolean enable = true;
private int periodMaxWarnTimes = 3;
private int periodMaxWarnTimes = 2;
/**
* For test purpose

View File

@ -173,6 +173,15 @@ public class PendingMessageBizConfig {
@Getter
private int todoTitleSearchMaxSize = 5000;
@Getter
private boolean enableTodoLog = true;
@Getter
private String workflowIMChannelAppMinVersionCmp = "";
@Getter
private String workflowIMChannelAppMinVersionCm = "";
@Getter
private boolean requestReplayWindowEnabled = true;

View File

@ -302,15 +302,9 @@ public class PendingMessageNewController implements PendingMessageClient {
}
@Override
@Deprecated
public CommonResponse<Boolean> setHide(SetHideRequest req) {
log.info("setHide, request={}", JSON.toJSONString(req));
Boolean response = null;
try {
response = todoManager.setHide(req);
return CommonResponse.success(response);
} finally {
log.info("setHide. request={}, response={}", req, response);
}
return CommonResponse.success(true);
}
@Override

View File

@ -8,6 +8,7 @@ import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.WebPageOpenStrategy;
import cn.axzo.msg.center.service.pending.client.WorkflowButtonSyncClient;
import cn.axzo.msg.center.service.pending.request.WorkflowSyncButtonsRequest;
import cn.axzo.msg.center.service.pending.request.WorkflowSyncButtonsRequest.WorkflowButton;
@ -96,12 +97,14 @@ public class WorkflowButtonSyncClientController implements WorkflowButtonSyncCli
if (button.getCategory() == RouterCategoryEnum.JUMP) {
UrlConfig urlConfig = new UrlConfig();
urlConfig.setDefaultUrl(workflowButton.getUrl());
urlConfig.setWebOpenStrategy(WebPageOpenStrategy.DRAWER);
update.setUrlConfig(urlConfig);
update.setApiUrl("");
} else {
String apiUrl = workflowButton.getUrl();
if (apiUrl == null) apiUrl = "";
update.setApiUrl(apiUrl);
update.setUrlConfig(new UrlConfig());
}
messageTemplateButtonV3Dao.updateById(update);
}

View File

@ -175,18 +175,6 @@ public class MessageTemplateV3SaveOrUpdateParam implements Serializable {
* 校验
*/
public void checkCreate() {
//1 流程待办暂不能使用IM通道
switch (this.getMsgCategory()) {
case GENERAL_MESSAGE:
break;
case IM_MESSAGE_CARD:
break;
case BIZ_PENDING_MESSAGE:
break;
case APPROVAL_PENDING_MESSAGE:
boolean contains = CollectionUtils.isNotEmpty(this.getChannels()) && this.getChannels().contains(MessageChannel.IM);
BizAssertions.assertFalse(contains, "审批待办暂不能使用IM通道");
}
//2 同一个预设按钮类型只能有一个
if (CollectionUtils.isNotEmpty(this.getButtons())) {
Map<String, Set<PresetButtonType>> collect = this.getButtons().stream()

View File

@ -7,6 +7,7 @@ import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.enums.ButtonStyleEnum;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.service.pending.card.domain.CardElementConfig;
@ -89,6 +90,12 @@ public class GeneralMessagePushVO implements Serializable {
* 业务编码
*/
private String bizCode;
/**
* 子业务编码
*/
private String subBizCode;
/**
* 消息发送时间戳
*/
@ -111,6 +118,8 @@ public class GeneralMessagePushVO implements Serializable {
private Map<String, Object> debugInfo;
private Map<String, Object> extInfo;
public void addDebugInfo(String name, Object value) {
if (debugInfo == null)
debugInfo = new HashMap<>();
@ -236,6 +245,11 @@ public class GeneralMessagePushVO implements Serializable {
*/
private UrlConfig urlConfig;
/**
* 按钮来源
*/
private RouterButtonSourceEnum source;
/**
* 调用api的地址. action=ACTION时, 消费这个字段
*/

View File

@ -0,0 +1,32 @@
package cn.axzo.msg.center.message.service.card;
import com.alibaba.fastjson.JSONObject;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
public class CardExtInfo {
private final JSONObject extInfo;
public CardExtInfo(JSONObject bizParam) {
JSONObject extInfo = bizParam.getJSONObject("extInfo");
if (extInfo == null) {
extInfo = new JSONObject();
bizParam.put("extInfo", extInfo);
}
this.extInfo = extInfo;
}
public void addExtInfo(String key, Object value) {
extInfo.put(key, value);
}
@NotNull
public JSONObject getJsonObject() {
return extInfo;
}
}

View File

@ -135,7 +135,7 @@ public class CardManager {
if (cardProps.isDeleteCardsWhenSendFail()) {
cardSupport.deleteCardIdempotent(request);
cardDao.deleteCards(sendModel.getCards());
sendLogger.reloadAndLogCards("send:fail:deleteCards");
sendLogger.reloadAndLogCards("send:fail:deleteCards", e);
}
});
throw MiscUtils.wrapException(e);
@ -152,11 +152,9 @@ public class CardManager {
public void updateState(CardUpdateStateRequest request) {
request.validate();
TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
boolean updated = false;
// 不要放到for里面去了
CardRequestContext<CardUpdateStateRequest> requestContext = CardRequestContext.create(request);
for (List<Card> cards : cardsCursor(request)) {
updated = true;
UpdateExecutor executor = new UpdateExecutor(requestContext, "updateState", templateModel, cards);
executor.update(card -> {
Card update = executor.createUpdate(card);
@ -165,10 +163,9 @@ public class CardManager {
update.setBizState(request.getBizState());
});
}
BizAssertions.assertTrue(updated, "未找到任何需要更新的卡片, request={}", request);
}
void setActionPerformed(SetActionPerformedRequest request) {
public void setActionPerformed(SetActionPerformedRequest request) {
TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
MessageTemplateButtonV3 button = templateModel.findButton(request.getButtonCode()).orElse(null);
BizAssertions.assertNotNull(button, "找不到对应的按钮. buttonCode={}", request.getButtonCode());
@ -194,11 +191,15 @@ public class CardManager {
"卡片已是终状态, 无法'{}'", request.getPresetButtonType().getDesc());
}
public void firePresetButtonPressed(CardUpdatePresetButtonRequest request) {
public boolean firePresetButtonPressed(CardUpdatePresetButtonRequest request) {
TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent(request.getTemplateCode());
CardRequestContext<CardUpdatePresetButtonRequest> requestContext = CardRequestContext.create(request);
for (List<Card> cards : cardsCursor(request))
firePresetButtonPressedImpl(request, requestContext, templateModel, cards);
boolean updated = false;
for (List<Card> cards : cardsCursor(request)) {
UpdateStateResult result = firePresetButtonPressedImpl(request, requestContext, templateModel, cards);
updated = result == UpdateStateResult.UPDATED || updated;
}
return updated;
}
private UpdateStateResult firePresetButtonPressedImpl(
@ -214,8 +215,10 @@ public class CardManager {
UpdateStateResult result = executor.update(new SetActionPerformedBuilder(
executor, button, CardBizState.fromPresetButton(request.getPresetButtonType())));
if (result == UpdateStateResult.UPDATED) {
cardBroadcaster.firePresetButtonPressed(executor.updatedCards, request);
cardBroadcaster.firePresetButtonPressed(executor.updatedCards, request, requestContext);
executor.updateCardLogger.reloadAndLogCards("presetButtonPressed:mq:success");
} else {
executor.updateCardLogger.reloadAndLogCards("presetButtonPressed:cardNoUpdate");
}
return result;
}
@ -251,7 +254,7 @@ public class CardManager {
}));
}
private void rebuildCardContent(TemplateModelV3 templateModel, List<Card> cards) {
void rebuildCardContent(TemplateModelV3 templateModel, List<Card> cards) {
ArrayList<Card> updates = new ArrayList<>();
for (Card card : cardDao.reloadCards(cards)) {
Card update = new Card();
@ -261,9 +264,6 @@ public class CardManager {
BizAssertions.assertTrue(cardTemplate.isUpdatable(),
"模板不支持更新, templateCode={}", card.getTemplateCode());
GeneralMessagePushVO cardContent = cardParser.parseCardContent(cardTemplate, card);
// 保留原来的title和content, 避免模版变了但缺少bizParam导致数据不完整
cardContent.setCardTitle(card.getTitle());
cardContent.setCardContent(card.getContent());
cardContent.addDebugInfo("bizCode", card.getBizCode());
cardContent.addDebugInfo("subBizCode", card.getSubBizCode());
update.setCardContent(cardContent);
@ -301,6 +301,7 @@ public class CardManager {
private class UpdateExecutor {
final CardLogger updateCardLogger;
final CardRequestContext<?> requestContext;
final TemplateModelV3 templateModel;
final String operation;
final List<Card> cards;
@ -310,6 +311,7 @@ public class CardManager {
UpdateExecutor(CardRequestContext<?> requestContext, String operation,
TemplateModelV3 templateModel, List<Card> cards) {
this.updateCardLogger = cardLoggers.createLogger(requestContext);
this.requestContext = requestContext;
this.templateModel = templateModel;
this.operation = operation;
this.cards = cards;

View File

@ -4,6 +4,8 @@ import cn.axzo.msg.center.inside.notices.config.MessageSystemConfig;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO.CardButton;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO.CardExtensionItem;
import cn.axzo.msg.center.message.service.card.interceptor.CardButtonInterceptor;
import cn.axzo.msg.center.message.service.card.interceptor.CardButtonInterceptorFactory;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
import cn.axzo.msg.center.message.service.impl.v3.NativeAppLinkUrlConfigVisitor;
import cn.axzo.msg.center.message.service.impl.v3.V3ExtPopulator;
@ -12,8 +14,8 @@ import cn.axzo.msg.center.service.domain.UrlConfigWalker;
import cn.axzo.msg.center.service.enums.ButtonStyleEnum;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.KVContentType;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.service.pending.card.domain.CardButtonStates;
import cn.axzo.msg.center.service.pending.card.domain.CardElementConfig;
import cn.axzo.msg.center.service.pending.request.CardContent;
@ -25,8 +27,9 @@ import cn.axzo.msg.center.service.pending.response.v3.model.ParsedGroupV3;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedKV;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedTemplateV3;
import cn.axzo.msg.center.service.pending.response.v3.model.PersonInfo;
import lombok.RequiredArgsConstructor;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@ -38,12 +41,23 @@ import java.util.List;
* @author yanglin
*/
@Component
@RequiredArgsConstructor
class CardParser {
private final MessageSystemConfig messageSystemConfig;
private final V3ExtPopulator v3ExtPopulator;
private final CardProps cardProps;
private final CardButtonInterceptorFactory[] interceptorFactories;
CardParser(MessageSystemConfig messageSystemConfig,
V3ExtPopulator v3ExtPopulator,
CardProps cardProps,
ObjectProvider<CardButtonInterceptorFactory[]> interceptorFactoryProvider) {
this.messageSystemConfig = messageSystemConfig;
this.v3ExtPopulator = v3ExtPopulator;
this.cardProps = cardProps;
this.interceptorFactories = interceptorFactoryProvider
.getIfAvailable(() -> new CardButtonInterceptorFactory[0]);
}
GeneralMessagePushVO parseCardContent(CardTemplate cardTemplate, CardContent card) {
ParsedTemplateV3 template = cardTemplate.getTemplate();
@ -55,6 +69,8 @@ class CardParser {
super.addDebugInfo(name, value);
}
};
bizBody.setBizCode(card.getBizCode());
bizBody.setSubBizCode(card.getSubBizCode());
bizBody.setCardStyleCode(template.getCardStyleCode());
bizBody.setTemplateCode(template.getCode());
bizBody.setCardBannerUrl(template.getIcon());
@ -76,6 +92,7 @@ class CardParser {
bizBody.addDebugInfo("bizState", stateInfo.getBizState());
bizBody.addDebugInfo("cardState", stateInfo.getCardState());
bizBody.addDebugInfo("isUpdatable", cardTemplate.isUpdatable());
CardButtonInterceptor buttonInterceptor = getButtonInterceptor(card);
ParsedModelV3Walker.walkDown(cardTemplate.getParsedModel(), new ParsedModel3Visitor() {
@Override
public void visitTemplateCardUrlConfig(UrlConfig urlConfig) {
@ -113,16 +130,25 @@ class CardParser {
*/
@Override
public void visitButton(ParsedButtonV3 button) {
List<ButtonStyleEnum> styles = button.parseStyle();
if (!styles.contains(ButtonStyleEnum.OVER_CARD))
if (bizBody.getCardButtons() != null
&& bizBody.getCardButtons().size() >= cardProps.getMaxSendingCardButtonsCount())
return;
boolean isActionPerformed = CardButtonStates
.create(card.getButtonStates())
.isButtonActionPerformed(button);
Boolean isVisibleOnCard = buttonInterceptor
.isVisibleOnCard(button)
.orElse(() -> button.getStyles().contains(ButtonStyleEnum.OVER_CARD));
if (!isVisibleOnCard) return;
boolean isPerformActionAvailable = buttonInterceptor
.isPerformActionAvailable(button)
.orElse(button.isPerformActionAvailable());
Boolean isActionPerformed = buttonInterceptor
.isActionPerformed(button)
.orElse(CardButtonStates
.create(card.getButtonStates())
.isButtonActionPerformed(button));
if (card.getStateInfo().getCardState() == CardState.COMPLETED
&& button.isPerformActionAvailable()
&& isPerformActionAvailable
&& !isActionPerformed)
return;
@ -131,21 +157,23 @@ class CardParser {
CardButton imButton = new CardButton();
bizBody.getCardButtons().add(imButton);
imButton.setTitle(isActionPerformed ? button.getActionPerformedName() : button.getName());
String actionPerformed = buttonInterceptor
.getActionPerformedName(button)
.orElse(button.getActionPerformedName());
imButton.setSource(button.getSource());
imButton.setTitle(isActionPerformed ? actionPerformed : button.getName());
imButton.setAction(button.getCategory().name());
imButton.setIsHighlight(styles.contains(ButtonStyleEnum.HIGH_LIGHT));
imButton.setExecutorShow(button.getExecutorShow());
imButton.setIsHighlight(button.getStyles().contains(ButtonStyleEnum.HIGH_LIGHT));
imButton.setSenderShow(buttonInterceptor.isSenderShow(button).orElse(false));
imButton.setExecutorShow(buttonInterceptor.isExecutorShow(button).orElse(button.getExecutorShow()));
imButton.setActionPerformed(isActionPerformed);
boolean isSystemButton = button.getSource() == RouterButtonSourceEnum.SYSTEM;
if (button.getUrlConfig().hasUrl())
imButton.setUrlConfig(button.getUrlConfig());
imButton.setButtonCode(button.getCode());
imButton.setPresetButtonType(button.getPresetButtonType());
imButton.setApiUrl(button.getApiUrl());
if (RouterCategoryEnum.ACTION.equals(button.getCategory())) {
AppLink appLink = new AppLink(TerminalTypeEnum.WEB_VIEW.name(), button.getApiUrl());
imButton.setActionPaths(Collections.singletonList(appLink));
} else if (button.getUrlConfig().hasUrl()) {
if (button.getUrlConfig().hasUrl() && !isSystemButton) {
imButton.setActionPaths(getNativeAppLinks(button.getUrlConfig()));
}
}
@ -160,6 +188,9 @@ class CardParser {
}
});
JSONObject extInfo = new CardExtInfo(card.getBizParam()).getJsonObject();
if (!extInfo.isEmpty())
bizBody.setExtInfo(extInfo);
return bizBody;
}
@ -170,4 +201,13 @@ class CardParser {
return visitor.getLinks();
}
private CardButtonInterceptor getButtonInterceptor(CardContent card) {
for (CardButtonInterceptorFactory factory : interceptorFactories) {
CardButtonInterceptor interceptor = factory.create(card);
if (interceptor != null)
return interceptor;
}
return CardButtonInterceptor.NOT_SURE;
}
}

View File

@ -22,6 +22,8 @@ public class CardProps {
private boolean deleteCardsWhenSendFail = true;
private boolean enableCardIdempotent = true;
private int updateCardBatchSize = 200;
// IM最多显示3个, 可能会存在仅发起人可见的按钮1个, 3 + 1 = 4, 避免消息超长
private int maxSendingCardButtonsCount = 4;
private Set<String> idempotentFreeTemplateCodes = new HashSet<>();
boolean isIdempotentFree(String templateCode) {

View File

@ -1,6 +1,5 @@
package cn.axzo.msg.center.message.service.card;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
@ -11,10 +10,12 @@ import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.CardIdempotentDao;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.domain.entity.CardIdempotent;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import cn.axzo.msg.center.message.service.card.domain.CardGroup;
import cn.axzo.msg.center.message.service.card.domain.CardSendModel;
import cn.axzo.msg.center.message.service.card.exception.CardIdempotentException;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Parser;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Service;
import cn.axzo.msg.center.message.service.impl.v3.UrlParser;
@ -23,6 +24,7 @@ import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots;
import cn.axzo.msg.center.push.PushData;
import cn.axzo.msg.center.service.domain.card.AppVersionConfig;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.MessageChannel;
import cn.axzo.msg.center.service.pending.request.CardContent;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
@ -60,6 +62,7 @@ public class CardSupport {
private final NimPushService nimPushService;
private final CardIdempotentDao cardIdempotentDao;
private final CardProps cardProps;
private final PendingMessageBizConfig cfg;
public static String getBizIdPrefix(String templateCode) {
return IdBuilder.builder()
@ -82,7 +85,7 @@ public class CardSupport {
cardIdempotentDao.save(idempotent);
} catch (DuplicateKeyException e) {
log.warn("重复创建卡片, request={}", request);
throw new ServiceException(403, String.format("重复创建卡片: %s", idempotent));
throw new CardIdempotentException(403, String.format("重复创建卡片: %s", idempotent));
}
}
@ -178,15 +181,22 @@ public class CardSupport {
private String determineMinAppVersion(ParsedModelV3 model,
AppTypeEnum appType) {
Ref<String> minVersion = Ref.create("");
Ref<String> ref = Ref.create("");
ParsedModelV3Walker.walkDown(model, new ParsedModel3Visitor() {
@Override
public void visitAppVersionConfig(AppVersionConfig cfg) {
if (cfg.getAppType() == appType)
minVersion.set(cfg.getMinVersion());
ref.set(cfg.getMinVersion());
}
});
return minVersion.get();
String minVersion = ref.get();
if (StringUtils.isBlank(minVersion)
&& model.getTemplate().getMsgCategory() == MessageCategoryEnum.APPROVAL_PENDING_MESSAGE) {
return appType == AppTypeEnum.CMP
? cfg.getWorkflowIMChannelAppMinVersionCmp()
: cfg.getWorkflowIMChannelAppMinVersionCm();
}
return minVersion;
}
TemplateModelV3 ensureImChannelPresent(String templateCode) {

View File

@ -5,10 +5,12 @@ import cn.axzo.msg.center.api.mq.CardInfo;
import cn.axzo.msg.center.api.mq.CardPresetButtonPressedMessage;
import cn.axzo.msg.center.dal.CardDao;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.message.service.card.CardRequestContext;
import cn.axzo.msg.center.mq.MqMessageRecord;
import cn.axzo.msg.center.mq.MqProducer;
import cn.axzo.msg.center.service.enums.MqMessageType;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonRequest;
import cn.axzo.msg.center.service.util.IdBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@ -24,18 +26,25 @@ public class CardBroadcaster {
private final CardDao cardDao;
private final MqProducer mqProducer;
public void firePresetButtonPressed(List<Card> cards, CardPresetButtonRequest request) {
public void firePresetButtonPressed(List<Card> cards,
CardPresetButtonRequest request,
CardRequestContext<?> requestContext) {
for (Card card : cardDao.reloadCards(cards)) {
CardPresetButtonPressedMessage message = new CardPresetButtonPressedMessage();
message.setPresetButtonType(request.getPresetButtonType());
message.setOperatorId(request.getOperatorId());
message.setOperatorName(request.getOperatorName());
message.setCardInfo(BeanMapper.copyBean(card, CardInfo.class));
message.setBatchNo(requestContext.getBatchNo());
mqProducer.send(MqMessageRecord
.builder(MqMessageType.CARD_PRESET_BUTTON_PRESSED, message)
.messageKey(card.getId())
.messageKey(card.getIdentityCode())
.operatorId(request.getOperatorId())
.shardingKey(card.getTemplateCode())
.shardingKey(IdBuilder.builder()
.append(card.getTemplateCode())
.append(card.getBizCode())
.append(card.getSubBizCode())
.build())
.build());
}
}

View File

@ -0,0 +1,18 @@
package cn.axzo.msg.center.message.service.card.exception;
import cn.axzo.basics.common.exception.ServiceException;
/**
* @author yanglin
*/
public class CardIdempotentException extends ServiceException {
public CardIdempotentException(Integer code, String msg) {
super(code, msg);
}
public CardIdempotentException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,37 @@
package cn.axzo.msg.center.message.service.card.interceptor;
import cn.axzo.msg.center.service.ButtonV3;
import cn.axzo.msg.center.utils.desision.DecisionValue;
/**
* @author yanglin
*/
public interface CardButtonInterceptor {
default DecisionValue<Boolean> isPerformActionAvailable(ButtonV3 button) {
return DecisionValue.notSure();
}
default DecisionValue<Boolean> isActionPerformed(ButtonV3 button) {
return DecisionValue.notSure();
}
default DecisionValue<String> getActionPerformedName(ButtonV3 button) {
return DecisionValue.notSure();
}
default DecisionValue<Boolean> isVisibleOnCard(ButtonV3 button) {
return DecisionValue.notSure();
}
default DecisionValue<Boolean> isSenderShow(ButtonV3 button) {
return DecisionValue.notSure();
}
default DecisionValue<Boolean> isExecutorShow(ButtonV3 button) {
return DecisionValue.notSure();
}
CardButtonInterceptor NOT_SURE = new CardButtonInterceptor() {};
}

View File

@ -0,0 +1,15 @@
package cn.axzo.msg.center.message.service.card.interceptor;
import cn.axzo.msg.center.service.pending.request.CardContent;
import javax.annotation.Nullable;
/**
* @author yanglin
*/
public interface CardButtonInterceptorFactory {
@Nullable
CardButtonInterceptor create(CardContent card);
}

View File

@ -6,7 +6,9 @@ import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import lombok.Getter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author yanglin
@ -15,6 +17,7 @@ import java.util.List;
public class NativeAppLinkUrlConfigVisitor implements UrlConfigVisitor {
private final List<AppLink> links = new ArrayList<>();
private final Set<TerminalTypeEnum> terminals = new HashSet<>();
@Override
public void visitAppManagerAndroid(AppUrl android) {
@ -41,7 +44,9 @@ public class NativeAppLinkUrlConfigVisitor implements UrlConfigVisitor {
}
private void addLink(AppLink link) {
if (!links.contains(link))
if (!terminals.contains(link.getPlatform())) {
links.add(link);
terminals.add(link.getPlatform());
}
}
}

View File

@ -89,7 +89,7 @@ public class ModelV2PropsPopulator implements ParsedModel3Visitor {
buttonV2.setDesc(buttonV3.getName());
buttonV2.setCategory(buttonV3.getCategory());
buttonV2.setPresetButtonType(buttonV3.getPresetButtonType());
buttonV2.setStyle(buttonV3.parseStyle());
buttonV2.setStyle(buttonV3.getStyles());
buttonV2.setExecutorShow(buttonV3.getExecutorShow());
buttonV2.setPendingShow(buttonV3.getPendingShow());
buttonV2.setKey(buttonV3.getCode());

View File

@ -1,324 +0,0 @@
package cn.axzo.msg.center.message.service.todo;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.msg.center.api.mq.CardInfo;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
import cn.axzo.msg.center.message.service.card.CardManager;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Service;
import cn.axzo.msg.center.message.service.todo.manage.TodoLogger;
import cn.axzo.msg.center.message.service.todo.manage.TodoManager;
import cn.axzo.msg.center.message.service.todo.manage.TodoRequestContext;
import cn.axzo.msg.center.notices.common.constans.CommonConstants;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.dto.PersonDTO;
import cn.axzo.msg.center.service.enums.CardBizState;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdatePresetButtonRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.request.PresetButtonPressedRequest;
import cn.axzo.msg.center.service.util.IdBuilder;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author xudawei@axzo.cn
* @date 2024/12/12
* @desc 待办与卡片关联包装
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TodoWithCardWrapper {
private final CardManager cardManager;
private final TodoLogger todoLogger;
private final TodoDao todoDao;
private final ModelV3Service modelV3Service;
/**
* 卡片预设按钮,同步待办
* 卡片预设同步待办状态
* @param presetButtonType 预设类型
* @param cardInfo 卡片
*/
public void fireTodoWhenPresetButtonPressedByCard(TodoManager todoManager, PresetButtonType presetButtonType, CardInfo cardInfo, Long operatorId) {
if (Objects.isNull(presetButtonType) || Objects.isNull(cardInfo)) {
return;
}
if (StringUtils.isBlank(cardInfo.getTemplateCode()) || StringUtils.isBlank(cardInfo.getBizCode())) {
return;
}
log.info("TodoWithCardWrapper#fireTodoWhenPresetButtonPressedByCard start,presetButtonType:{},cardInfo:{}", presetButtonType, JSON.toJSONString(cardInfo));
TodoRequestContext ctx = TodoRequestContext.create("cardPresetTodo", cardInfo);
ctx.addLogContent("presetButtonType", presetButtonType);
ctx.addLogContent("operatorId", operatorId);
//1 查询待办(Todo对象)根据templateCode/bizCode/subBizCode/receiverPresonId/receiverOuId/receiverWorkspaceId 理论上只能查询一条[待办]记录
List<Todo> todoList = todoDao.findByCondition(cardInfo.getTemplateCode(), cardInfo.getBizCode(), cardInfo.getSubBizCode(), cardInfo.getReceiverPersonId(), cardInfo.getReceiverOuId(), cardInfo.getReceiverWorkspaceId());
try {
if (CollectionUtils.isEmpty(todoList)) {
return;
}
//2 同步待办预设状态
for (Todo todo : todoList) {
PresetButtonPressedRequest presetButtonPressedRequest = this.buildPresetButtonPressedRequest(todo.getIdentityCode(), presetButtonType, operatorId);
todoManager.firePresetButtonPressed( presetButtonPressedRequest, false);
ctx.addLogContent("fireTodoWhenPresetButtonPressedByCard", "success");
todoLogger.logTodosUpdated(ctx, Collections.singletonList(todo));
}
} catch (Exception e) {
log.warn("TodoWithCardWrapper#fireTodoWhenPresetButtonPressedByCard exception,presetButtonType:{},cardInfo:{}", presetButtonType, JSON.toJSONString(cardInfo),e);
ctx.addLogContent("exception", Throwables.getStackTraceAsString(e));
todoLogger.logTodosUpdated(ctx, todoList);
}
}
/**
* 构建预设按钮对象
*/
private PresetButtonPressedRequest buildPresetButtonPressedRequest(String identityCode, PresetButtonType presetButtonType, Long operatorId) {
return PresetButtonPressedRequest.builder()
.identityCode(identityCode)
.presetButtonType(presetButtonType)
.operatorId(operatorId)
.build();
}
/**
* 点击预设按钮-同步卡片
* 待办预设同步卡片状态
*/
public void fireCardWhenPresetButtonPressedByTodo(PresetButtonPressedRequest request, Todo todo, boolean isSyncCard) {
//是否同步卡片信息true:同步;false:不同步
if (!isSyncCard) {
return;
}
log.info("TodoWithCardWrapper#fireCardWhenPresetButtonPressedByTodo start,request:{},todo:{}", JSON.toJSONString(request), JSON.toJSONString(todo));
TodoRequestContext ctx = TodoRequestContext.create("todoPresetCard", request);
if (!this.isContainImChannel(todo.getTemplateCode())) {
log.info("fireCardWhenPresetButtonPressedByTodo#notContainImChannel,templateCode:{}", todo.getTemplateCode());
return;
}
log.info("fireCardWhenPresetButtonPressedByTodo#isContainImChannel,templateCode:{}", todo.getTemplateCode());
try {
//1 构建对象
CardUpdatePresetButtonRequest cardRequest = this.buildCardUpdatePresetButtonRequest(request, todo);
//2 同步卡片
cardManager.firePresetButtonPressed(cardRequest);
ctx.addLogContent("fireCardWhenPresetButtonPressedByTodo", "success");
todoLogger.logTodosUpdated(ctx, Collections.singletonList(todo));
} catch (Exception e) {
log.warn("TodoWithCardWrapper#fireCardWhenPresetButtonPressedByTodo exception,request:{},todo:{}", JSON.toJSONString(request), JSON.toJSONString(todo),e);
ctx.addLogContent("exception", Throwables.getStackTraceAsString(e));
todoLogger.logTodosUpdated(ctx, Collections.singletonList(todo));
}
}
/**
* 构建对象
*/
private CardUpdatePresetButtonRequest buildCardUpdatePresetButtonRequest(PresetButtonPressedRequest request, Todo todo) {
CardUpdatePresetButtonRequest cardRequest = new CardUpdatePresetButtonRequest();
cardRequest.setAppCode(CommonConstants.TODO_SYSN_CARD_APP_CODE);
cardRequest.setTemplateCode(todo.getTemplateCode());
cardRequest.setBizCode(todo.getBizCode());
cardRequest.setSubBizCode(todo.getSubBizCode());
cardRequest.setReceivers(Sets.newHashSet(
PeerPerson.newPeerPerson(todo.getExecutorPersonId()
, todo.getOuId()
, todo.getReceiverWorkspaceId())));
cardRequest.setPresetButtonType(request.getPresetButtonType());
cardRequest.setOperatorId(request.getOperatorId());
return cardRequest;
}
/**
* 发送卡片信息
*/
public void send(PendingMessagePushParam param,List<Todo> todos) {
log.info("TodoWithCardWrapper#send start,param:{}", JSON.toJSONString(param));
TodoRequestContext ctx = TodoRequestContext.create("todoSyncCardSend", param);
if (!this.isContainImChannel(param.getTemplateCode())) {
log.info("send#notContainImChannel,templateCode:{}", param.getTemplateCode());
return;
}
log.info("send#isContainImChannel,templateCode:{}", param.getTemplateCode());
try {
//1 构建对象
CardSendRequest cardSendRequest = this.buildCardSendRequest(param);
ctx.addLogContent(JSON.toJSONString(cardSendRequest));
//2 发送
cardManager.send(cardSendRequest);
ctx.addLogContent("sendCard", "success");
todoLogger.logTodosUpdated(ctx, todos);
} catch (Exception e) {
log.warn("TodoWithCardWrapper#send,param:{}", JSON.toJSONString(param), e);
ctx.addLogContent("exception", Throwables.getStackTraceAsString(e));
todoLogger.logTodosUpdated(ctx, todos);
}
}
/**
* 是否包含IM通道
*/
private boolean isContainImChannel(String templateCode) {
TemplateModelV3 templateModel = modelV3Service.findEnabledByCode(templateCode)
.orElseThrow(() -> new ServiceException(
String.format("Can't find template. templateCode=%s", templateCode)));
return templateModel.getTemplate().isContainImChannel();
}
/**
* 卡片更新状态-完成
*/
public void cardCompleteStateByTodoList(List<Todo> todoList) {
this.cardUpdateStateByTodoList(todoList,CardBizState.END, true);
}
/**
* 卡片更新状态-回滚
* 暂时不需要
*/
public void cardRollbackStateByTodoList(List<Todo> todoList) {
this.cardUpdateStateByTodoList(todoList, CardBizState.ABORTED, false);
}
/**
* 卡片更新状态-撤销
*/
public void cardRevokeStateByTodoList(List<Todo> todoList) {
this.cardUpdateStateByTodoList(todoList, CardBizState.REVOKED, false);
}
/**
* 卡片更新状态-执行中
*/
public void cardProcessingStateByTodoList(List<Todo> todoList) {
this.cardUpdateStateByTodoList(todoList, CardBizState.PENDING, false);
}
/**
* 卡片更新完成状态
*/
private void cardUpdateStateByTodoList(List<Todo> todoList, CardBizState bizState, boolean setCardCompleted) {
log.info("TodoWithCardWrapper#cardUpdateStateByTodoList start,todoList:{},bizState:{},setCardCompleted:{}", JSON.toJSONString(todoList), bizState, setCardCompleted);
if (CollectionUtils.isEmpty(todoList)) {
return;
}
TodoRequestContext ctx = TodoRequestContext.create("todoSyncCardState", todoList);
ctx.addLogContent("bizState", bizState);
ctx.addLogContent("cardState", bizState);
ctx.addLogContent("setCardCompleted", setCardCompleted);
try {
Set<String> codes = todoList.stream().map(Todo::getTemplateCode).collect(Collectors.toSet());
List<TemplateModelV3> byCodes = modelV3Service.getByCodes(new ArrayList<>(codes));
Map<String, TemplateModelV3> modelV3Map = byCodes.stream().collect(Collectors.toMap(TemplateModelV3::getTemplateCode, Function.identity()));
for (Todo todo : todoList) {
TemplateModelV3 templateModelV3 = modelV3Map.get(todo.getTemplateCode());
if (Objects.isNull(templateModelV3) || Objects.isNull(templateModelV3.getTemplate())
|| StringUtils.isBlank(templateModelV3.getTemplate().getCode()) ||!templateModelV3.getTemplate().isContainImChannel()) {
log.info("cardUpdateStateByTodoList#notContainImChannel,templateModelV3:{}", JSON.toJSONString(templateModelV3));
continue;
}
log.info("cardUpdateStateByTodoList#isContainImChannel,templateModelV3:{}", JSON.toJSONString(templateModelV3));
//1 构建对象
CardUpdateStateRequest updateStateRequest = this.buildCardUpdateStateRequest(todo, bizState, setCardCompleted);
//2 更新状态
cardManager.updateState(updateStateRequest);
ctx.addLogContent("cardUpdateStateByTodoList", "success");
todoLogger.logTodosUpdated(ctx, Collections.singletonList(todo));
}
} catch (Exception e) {
log.warn("TodoWithCardWrapper#cardCompleteState,todoList:{}", JSON.toJSONString(todoList), e);
ctx.addLogContent("exception", Throwables.getStackTraceAsString(e));
todoLogger.logTodosUpdated(ctx, todoList);
}
}
/**
* 构建卡片状态更新对象
*/
private CardUpdateStateRequest buildCardUpdateStateRequest(Todo todo, CardBizState bizState, boolean setCardCompleted) {
CardUpdateStateRequest updateStateRequest = new CardUpdateStateRequest();
updateStateRequest.setBizState(bizState);
updateStateRequest.setCardCompleted(setCardCompleted);
updateStateRequest.setAppCode(CommonConstants.TODO_SYSN_CARD_APP_CODE);
updateStateRequest.setTemplateCode(todo.getTemplateCode());
updateStateRequest.setBizCode(todo.getBizCode());
updateStateRequest.setSubBizCode(todo.getSubBizCode());
updateStateRequest.setReceivers(Sets.newHashSet(PeerPerson.newPeerPerson(todo.getExecutorPersonId(), todo.getOuId(), todo.getReceiverWorkspaceId())));//TODO
return updateStateRequest;
}
/**
* 构建对象
*/
private CardSendRequest buildCardSendRequest(PendingMessagePushParam param) {
CardSendRequest sendRequest = new CardSendRequest();
sendRequest.setAppCode(CommonConstants.TODO_SYSN_CARD_APP_CODE);
sendRequest.setTemplateCode(param.getTemplateCode());
sendRequest.setBizCode(param.getBizCode());
sendRequest.setSubBizCode(param.getSubBizCode());
if (Objects.nonNull(param.getPromoter()) && Objects.nonNull(param.getPromoter().getId())) {
sendRequest.setSender(PeerPerson.newPeerPerson(param.getPromoter().getId()
, Objects.nonNull(param.getPromoterOuId()) ? param.getPromoterOuId() : null
, Objects.nonNull(param.getPromoterWorkspaceId()) ? param.getPromoterWorkspaceId() : null));
}
sendRequest.setReceivers(this.buildReceivers(param.getExecutor(), param.getOuId(), param.getWorkspaceId()));
if (StringUtils.isNotBlank(param.getBizExtParams())) {
sendRequest.setBizParam(JSON.parseObject(param.getBizExtParams()));
}
if (StringUtils.isNotBlank(param.getRouterParams())) {
sendRequest.setRouterParam(JSON.parseObject(param.getRouterParams()));
}
sendRequest.setIdempotentCode(IdBuilder.builder()
.append(param.getBizCode())
.append(param.getSubBizCode())
.build());
return sendRequest;
}
private Set<PeerPerson> buildReceivers(List<PersonDTO> personDTOS, Long ouId, Long workspaceId) {
if (CollectionUtils.isEmpty(personDTOS)) {
return Sets.newHashSet();
}
return personDTOS.stream().map(item -> PeerPerson.newPeerPerson(item.getId(), ouId, workspaceId)).collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,45 @@
package cn.axzo.msg.center.message.service.todo.card;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.msg.center.api.mq.CardPresetButtonPressedMessage;
import cn.axzo.msg.center.mq.ConsumerIsolation;
import cn.axzo.msg.center.mq.IsolationMQListener;
import cn.axzo.msg.center.mq.RocketMQConfig;
import cn.axzo.msg.center.service.enums.MqMessageType;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.stereotype.Component;
/**
* @author yanglin
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumeThreadMax = 2,
maxReconsumeTimes = 3,
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}",
topic = RocketMQConfig.MSG_CENTER_TOPIC,
consumerGroup = "GID_topic_card_preset_button_sync_todo_${spring.profiles.active}"
)
public class CardPresetButtonSyncTodoListener extends IsolationMQListener {
private final TodoSyncCardService todoSyncCardService;
CardPresetButtonSyncTodoListener(TodoSyncCardService todoSyncCardService) {
super(ConsumerIsolation.CARD_PRESET_BUTTON_CLICKED_SYNC_TODO,
MqMessageType.CARD_PRESET_BUTTON_PRESSED);
this.todoSyncCardService = todoSyncCardService;
}
@Override
public void onEventImpl(Event event, EventConsumer.Context context) {
CardPresetButtonPressedMessage message = event.normalizedData(CardPresetButtonPressedMessage.class);
log.info("received CardPresetButtonPressedMessage: {}", message);
todoSyncCardService.syncCardPresetButtonPressed(message);
}
}

View File

@ -0,0 +1,45 @@
package cn.axzo.msg.center.message.service.todo.card;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.msg.center.api.mq.PresetButtonPressedMessage;
import cn.axzo.msg.center.mq.ConsumerIsolation;
import cn.axzo.msg.center.mq.IsolationMQListener;
import cn.axzo.msg.center.mq.RocketMQConfig;
import cn.axzo.msg.center.service.enums.MqMessageType;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.stereotype.Component;
/**
* @author yanglin
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumeThreadMax = 2,
maxReconsumeTimes = 3,
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}",
topic = RocketMQConfig.MSG_CENTER_TOPIC,
consumerGroup = "GID_topic_todo_preset_button_sync_card_${spring.profiles.active}"
)
public class TodoPresetButtonSyncCardListener extends IsolationMQListener {
private final TodoSyncCardService todoSyncCardService;
TodoPresetButtonSyncCardListener(TodoSyncCardService todoSyncCardService) {
super(ConsumerIsolation.TODO_PRESET_BUTTON_CLICKED_SYNC_CARD,
MqMessageType.TODO_PRESET_BUTTON_PRESSED);
this.todoSyncCardService = todoSyncCardService;
}
@Override
public void onEventImpl(Event event, EventConsumer.Context context) {
PresetButtonPressedMessage message = event.normalizedData(PresetButtonPressedMessage.class);
log.info("received PresetButtonPressedMessage: {}", message);
todoSyncCardService.syncTodoPresetButtonPressed(message);
}
}

View File

@ -0,0 +1,51 @@
package cn.axzo.msg.center.message.service.todo.card;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.msg.center.api.mq.TodoUpdateMessage;
import cn.axzo.msg.center.mq.ConsumerIsolation;
import cn.axzo.msg.center.mq.IsolationMQListener;
import cn.axzo.msg.center.mq.RocketMQConfig;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.MqMessageType;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.stereotype.Component;
/**
* @author yanglin
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumeThreadMax = 2,
maxReconsumeTimes = 3,
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}",
topic = RocketMQConfig.MSG_CENTER_TOPIC,
consumerGroup = "GID_topic_todo_sync_card_biz_${spring.profiles.active}"
)
class TodoSyncCardBizListener extends IsolationMQListener {
private final TodoSyncCardService todoSyncCardService;
TodoSyncCardBizListener(TodoSyncCardService todoSyncCardService) {
super(ConsumerIsolation.TODO_SYNC_CARD_BIZ, MqMessageType.TODO_STATE_UPDATE);
this.todoSyncCardService = todoSyncCardService;
}
@Override
public void onEventImpl(Event event, EventConsumer.Context context) {
TodoUpdateMessage message;
try {
message = event.normalizedData(TodoUpdateMessage.class);
} catch (Exception e) {
log.warn("parse message error. event={}", event, e);
return;
}
if (message.getUpdatedTodo().getBizCategory() == BizCategoryEnum.OTHER)
todoSyncCardService.onMessage(event, message);
}
}

View File

@ -0,0 +1,51 @@
package cn.axzo.msg.center.message.service.todo.card;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.msg.center.api.mq.TodoUpdateMessage;
import cn.axzo.msg.center.mq.ConsumerIsolation;
import cn.axzo.msg.center.mq.IsolationMQListener;
import cn.axzo.msg.center.mq.RocketMQConfig;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.MqMessageType;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.springframework.stereotype.Component;
/**
* @author yanglin
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumeThreadMax = 2,
maxReconsumeTimes = 3,
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}",
topic = RocketMQConfig.MSG_CENTER_TOPIC,
consumerGroup = "GID_topic_todo_sync_card_flow_${spring.profiles.active}"
)
class TodoSyncCardFlowListener extends IsolationMQListener {
private final TodoSyncCardService todoSyncCardService;
TodoSyncCardFlowListener(TodoSyncCardService todoSyncCardService) {
super(ConsumerIsolation.TODO_SYNC_CARD_FLOW, MqMessageType.TODO_STATE_UPDATE);
this.todoSyncCardService = todoSyncCardService;
}
@Override
public void onEventImpl(Event event, EventConsumer.Context context) {
TodoUpdateMessage message;
try {
message = event.normalizedData(TodoUpdateMessage.class);
} catch (Exception e) {
log.warn("parse message error. event={}", event, e);
return;
}
if (message.getUpdatedTodo().getBizCategory() == BizCategoryEnum.FLOW)
todoSyncCardService.onMessage(event, message);
}
}

View File

@ -0,0 +1,318 @@
package cn.axzo.msg.center.message.service.todo.card;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.msg.center.api.mq.CardPresetButtonPressedMessage;
import cn.axzo.msg.center.api.mq.PresetButtonPressedMessage;
import cn.axzo.msg.center.api.mq.TodoUpdateMessage;
import cn.axzo.msg.center.dal.MessageTemplateV3Dao;
import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.message.service.card.CardExtInfo;
import cn.axzo.msg.center.message.service.card.CardManager;
import cn.axzo.msg.center.message.service.card.exception.CardIdempotentException;
import cn.axzo.msg.center.message.service.todo.manage.TodoLogger;
import cn.axzo.msg.center.message.service.todo.manage.TodoManager;
import cn.axzo.msg.center.message.service.todo.manage.TodoRequestContext;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.BizFinalStateEnum;
import cn.axzo.msg.center.service.enums.CardBizState;
import cn.axzo.msg.center.service.enums.CodeDefinition;
import cn.axzo.msg.center.service.enums.MessageChannel;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdatePresetButtonRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.request.PresetButtonPressedRequest;
import cn.axzo.msg.center.service.pending.response.CardSendResponse;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Optional;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TodoSyncCardService {
private static final ThreadLocal<TodoUpdateMessage> TODO_MESSAGE = new ThreadLocal<>();
private static final String APP_CODE = "msg-center:todo";
private static final String TODO_IDENTITY_CODE_BIZ_PARAM_KEY = "todoIdentityCode";
private static final String TODO_TYPE_BIZ_PARAM_KEY = "todoType";
private static final String TODO_CATEGORY_BIZ_PARAM_KEY = "todoCategory";
private static final String TODO_EXECUTOR_WORKSPACE_ID_BIZ_PARAM_KEY = "todoExecutorWorkspaceId";
private static final String TODO_PROMOTER_WORKSPACE_ID_BIZ_PARAM_KEY = "todoPromoterWorkspaceId";
private final TodoDao todoDao;
private final TodoBusinessDao todoBusinessDao;
private final CardManager cardManager;
private final TodoLogger todoLogger;
private final MessageTemplateV3Dao messageTemplateV3Dao;
private final TodoManager todoManager;
private final TransactionTemplate transactionTemplate;
void onMessage(Event event, TodoUpdateMessage message) {
log.info("received TodoUpdateMessage: {}", message);
try {
TODO_MESSAGE.set(message);
sync(event, message);
} catch (Exception e) {
log.warn("onMessage error, message={}", message, e);
throw new RuntimeException(e);
} finally {
TODO_MESSAGE.remove();
}
}
public static Optional<TodoUpdateMessage> getTodoUpdateMessage() {
return Optional.ofNullable(TODO_MESSAGE.get());
}
private void sync(Event event, TodoUpdateMessage message) {
if (!message.isUpdateCard()) {
log.warn("not update card, message={}", message);
return;
}
Todo todo = todoDao.findTodoByCode(message.getUpdatedTodo().getIdentityCode())
.orElse(null);
if (todo == null) {
log.warn("[sync] todo not found, message={}", message);
return;
}
MessageTemplateV3 template = messageTemplateV3Dao
.findByCode(todo.getTemplateCode())
.orElse(null);
if (template == null) {
log.warn("template not found, message={}", message);
return;
}
if (!template.determineChannels().contains(MessageChannel.IM)) {
log.warn("template not support IM, message={}", message);
return;
}
TodoBusiness business = todoBusinessDao.getBusinesses(todo).findBusiness(todo).orElse(null);
if (business == null) {
log.warn("business not found, message={}", message);
return;
}
if (todo.getState() == PendingMessageStateEnum.HAS_BEEN_SENT
|| (todo.getState() == PendingMessageStateEnum.CREATED && business.getBizFinalState() == null))
sendCard(event, business, todo);
else if (!TodoManager.OP_FIRE_PRESET_BUTTON_PRESSED.equals(message.getOperation()))
updateCardState(event, business, todo);
}
private void sendCard(Event event, TodoBusiness business, Todo todo) {
CardSendRequest request = new CardSendRequest();
request.setAppCode(APP_CODE);
request.setTemplateCode(todo.getTemplateCode());
request.setBizCode(todo.getBizCode());
request.setIdempotentCode(todo.getIdentityCode());
request.setSubBizCode(todo.getSubBizCode());
Long promoterPersonId = business.getPromoterPersonId();
if (promoterPersonId == null) promoterPersonId = 0L;
Long ouId = business.getOuId();
if (ouId == null) ouId = 0L;
request.setSender(PeerPerson.create(promoterPersonId, ouId, business.getOrgId()));
request.setReceivers(Sets.newHashSet(PeerPerson.create(
todo.getExecutorPersonId(), todo.getOuId(), todo.getOrgId())));
JSONObject bizParam = todo.bizParam();
CardExtInfo cardExtInfo = new CardExtInfo(bizParam);
cardExtInfo.addExtInfo(TODO_IDENTITY_CODE_BIZ_PARAM_KEY, todo.getIdentityCode());
cardExtInfo.addExtInfo(TODO_TYPE_BIZ_PARAM_KEY, todo.getType().getCode());
cardExtInfo.addExtInfo(TODO_CATEGORY_BIZ_PARAM_KEY, business.getBizCategory().getCode());
cardExtInfo.addExtInfo(TODO_EXECUTOR_WORKSPACE_ID_BIZ_PARAM_KEY, todo.getReceiverWorkspaceId());
cardExtInfo.addExtInfo(TODO_PROMOTER_WORKSPACE_ID_BIZ_PARAM_KEY, business.getOrgId());
request.setBizParam(bizParam);
request.setRouterParam(todo.routerParam());
request.setReturnCards(true);
TodoRequestContext ctx = TodoRequestContext
.create("sendCard", event)
.addLogContent("sendCardRequest", request);
try {
log.info("sendCard: todo={}", todo);
CardSendResponse response = cardManager.send(request);
ctx.addLogContent("sendCardResponse", response);
todoLogger.logTodoUpdated(ctx, todo);
log.info("sendCard: success, todo={}", todo);
} catch (CardIdempotentException ignored) {
log.warn("sendCard: idempotent, todo={}", todo);
} catch (Exception e) {
log.warn("sendCard: error, todo={}", todo, e);
todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
}
}
private void updateCardState(Event event, TodoBusiness business, Todo todo) {
TodoCardUpdateStateRequest request = new TodoCardUpdateStateRequest();
request.setTodo(todo);
request.setTodoBusiness(business);
request.setAppCode(APP_CODE);
request.setTemplateCode(todo.getTemplateCode());
request.setBizCode(todo.getBizCode());
request.setSubBizCode(todo.getSubBizCode());
request.setReceivers(Sets.newHashSet(PeerPerson.create(
todo.getExecutorPersonId(), todo.getOuId(), todo.getOrgId())));
if (todo.getState() != PendingMessageStateEnum.PROCESSING) {
request.setBizState(business.getBizCategory() == BizCategoryEnum.FLOW
? determineCardBizStateForFlowTodo(business)
: determineCardBizStateForBizTodo(todo));
}
request.setCardCompleted(todo.getState() == PendingMessageStateEnum.COMPLETED);
TodoRequestContext ctx = TodoRequestContext
.create("updateCardState", event)
.addLogContent("updateCardStateRequest", request)
.addLogContent("todo", todo);
if (request.isValid()) {
try {
cardManager.updateState(request);
todoLogger.logTodoUpdated(ctx.copy().addLogContent("updateCardStateResult", "success"), todo);
} catch (Exception e) {
log.warn("updateCardState: error, todo={}", todo, e);
todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
}
} else {
log.info("updateCardState: invalid request, todo={}", todo);
todoLogger.logTodoUpdated(ctx.copy().addLogContent("updateCardStateResult", "invalid update card state request"), todo);
}
}
void syncCardPresetButtonPressed(CardPresetButtonPressedMessage message) {
log.info("onCardPresetButtonPressed: {}", message);
if (!APP_CODE.equals(message.getCardInfo().getAppCode())) {
log.info("[syncCardPresetButtonPressed] onCardPresetButtonPressed: not msg-center:todo, message={}", message);
return;
}
String identityCode = new CardExtInfo(message.getCardInfo().determineBizParam())
.getJsonObject().getString(TODO_IDENTITY_CODE_BIZ_PARAM_KEY);
if (StringUtils.isBlank(identityCode)) {
log.warn("[syncCardPresetButtonPressed] onCardPresetButtonPressed: identityCode is blank, message={}", message);
return;
}
Todo todo = todoDao.findTodoByCode(identityCode).orElse(null);
if (todo == null) {
log.info("[syncCardPresetButtonPressed] onCardPresetButtonPressed: todo not found, message={}", message);
return;
}
PresetButtonPressedRequest request = new PresetButtonPressedRequest();
request.setIdentityCode(identityCode);
request.setPresetButtonType(message.getPresetButtonType());
request.setOperatorId(message.getOperatorId());
request.setOperatorName(message.getOperatorName());
TodoRequestContext ctx = TodoRequestContext.create(
"syncCardPresetButtonPressed:finished", message.getBatchNo(), message);
try {
execTransactional(() -> {
if (todoManager.firePresetButtonPressed(request, false))
todoLogger.logTodoUpdated(ctx, todo);
});
} catch (Exception e) {
log.warn("[syncCardPresetButtonPressed] onCardPresetButtonPressed: error, message={}", message, e);
todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
}
}
void syncTodoPresetButtonPressed(PresetButtonPressedMessage message) {
Todo todo = todoDao.findTodoByCode(message.getTodoInfo().getIdentityCode()).orElse(null);
if (todo == null) {
log.warn("[syncTodoPresetButtonPressed] todo not found, message={}", message);
return;
}
MessageTemplateV3 template = messageTemplateV3Dao
.findByCode(todo.getTemplateCode())
.orElse(null);
if (template == null) {
log.warn("[syncTodoPresetButtonPressed] template not found, message={}", message);
return;
}
if (!template.determineChannels().contains(MessageChannel.IM)) {
log.warn("[syncTodoPresetButtonPressed] template not support IM, message={}", message);
return;
}
CardUpdatePresetButtonRequest request = new CardUpdatePresetButtonRequest();
request.setAppCode(APP_CODE);
request.setTemplateCode(todo.getTemplateCode());
request.setBizCode(todo.getBizCode());
request.setSubBizCode(todo.getSubBizCode());
request.setReceivers(Sets.newHashSet(PeerPerson.create(
todo.getExecutorPersonId(), todo.getOuId(), todo.getReceiverWorkspaceId())));
request.setPresetButtonType(message.getPresetButtonType());
request.setOperatorId(0L);
request.setOperatorName("");
TodoRequestContext ctx = TodoRequestContext.create("syncTodoPresetButtonPressed:finished", message);
try {
execTransactional(() -> {
if (cardManager.firePresetButtonPressed(request))
todoLogger.logTodoUpdated(ctx, todo);
});
} catch (Exception e) {
log.warn("[syncTodoPresetButtonPressed] error, message={}", message, e);
todoLogger.logTodoUpdated(ctx.copy().addLogContent("exception", e), todo);
}
}
private static CardBizState determineCardBizStateForBizTodo(Todo todo) {
if (todo.getState() == PendingMessageStateEnum.COMPLETED)
return CardBizState.COMPLETED;
if (todo.getState() == PendingMessageStateEnum.PROCESSING)
return CardBizState.PROCESSING;
if (todo.getState() == PendingMessageStateEnum.RETRACT)
return CardBizState.REVOKED;
return null;
}
private static CardBizState determineCardBizStateForFlowTodo(TodoBusiness business) {
if (business.getBizFinalState() == BizFinalStateEnum.COMPLETED)
return CardBizState.COMPLETED;
if (business.getBizFinalState() == BizFinalStateEnum.RETRACT)
return CardBizState.REVOKED;
if (business.getBizFinalState() == BizFinalStateEnum.PASSED)
return CardBizState.AGREED;
if (business.getBizFinalState() == BizFinalStateEnum.REJECTED)
return CardBizState.REJECTED;
if (business.getBizFinalState() == BizFinalStateEnum.ABORTED)
return CardBizState.ABORTED;
return null;
}
public static Optional<BizCategoryEnum> findTodoBizCategory(JSONObject bizParam) {
if (bizParam == null)
return Optional.empty();
String category = new CardExtInfo(bizParam)
.getJsonObject().getString(TODO_CATEGORY_BIZ_PARAM_KEY);
return CodeDefinition.findByCode(BizCategoryEnum.class, category);
}
public static Optional<String> findTodoIdentityCode(JSONObject bizParam) {
if (bizParam == null)
return Optional.empty();
String identityCode = new CardExtInfo(bizParam)
.getJsonObject().getString(TODO_IDENTITY_CODE_BIZ_PARAM_KEY);
return Optional.ofNullable(identityCode);
}
@Setter
@Getter
private static class TodoCardUpdateStateRequest extends CardUpdateStateRequest {
private TodoBusiness todoBusiness;
private Todo todo;
}
private void execTransactional(Runnable runnable) {
transactionTemplate.executeWithoutResult(unused -> runnable.run());
}
}

View File

@ -0,0 +1,147 @@
package cn.axzo.msg.center.message.service.todo.card.workflow;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.message.service.card.interceptor.CardButtonInterceptor;
import cn.axzo.msg.center.service.ButtonV3;
import cn.axzo.msg.center.service.enums.BizFinalStateEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.utils.desision.DecisionValue;
import cn.axzo.workflow.common.enums.BpmnProcessTaskResultEnum;
import cn.axzo.workflow.common.enums.ButtonVisibleScopeEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo.BpmnButtonMetaInfoWithVisibleScope;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static java.util.stream.Collectors.toSet;
/**
* @author yanglin
*/
@Slf4j
@RequiredArgsConstructor
class WorkflowTodoCardButtonInterceptor implements CardButtonInterceptor {
private final static Set<String> SYSTEM_BUTTONS_SENDER_SHOW = Sets.newHashSet(
BizFinalStateEnum.RETRACT.getButtonCode());
private final static Set<String> SYSTEM_BUTTONS_EXECUTOR_SHOW = Sets.newHashSet(
BizFinalStateEnum.PASSED.getButtonCode(),
BizFinalStateEnum.REJECTED.getButtonCode());
private final Todo todo;
private final BpmnTaskButtonVo taskInfo;
@Override
public DecisionValue<Boolean> isPerformActionAvailable(ButtonV3 button) {
BizFinalStateEnum btnBizState = findButtonClickState(button).orElse(null);
return DecisionValue.decide(btnBizState != null && btnBizState.isButtonPerformActionAvailable());
}
@Override
public DecisionValue<Boolean> isActionPerformed(ButtonV3 button) {
return DecisionValue.decide(isSystemButtonActionPerformed(button));
}
private boolean isSystemButtonActionPerformed(ButtonV3 button) {
return isExecutorActionPerformed(button) || isSenderActionPerformed(button);
}
private boolean isExecutorActionPerformed(ButtonV3 button) {
BizFinalStateEnum btnBizState = findButtonClickState(button).orElse(null);
if (btnBizState == null)
return false;
if (btnBizState == BizFinalStateEnum.PASSED
&& taskInfo.getExecutorTaskResult() == BpmnProcessTaskResultEnum.APPROVED)
return true;
return btnBizState == BizFinalStateEnum.REJECTED
&& taskInfo.getExecutorTaskResult() == BpmnProcessTaskResultEnum.REJECTED;
}
private boolean isSenderActionPerformed(ButtonV3 button) {
BizFinalStateEnum btnBizState = findButtonClickState(button).orElse(null);
if (btnBizState == null)
return false;
return btnBizState == BizFinalStateEnum.RETRACT
&& taskInfo.getInitiatorTaskResult() == BpmnProcessTaskResultEnum.CANCELED;
}
@Override
public DecisionValue<String> getActionPerformedName(ButtonV3 button) {
BizFinalStateEnum btnBizState = findButtonClickState(button).orElse(null);
return btnBizState == null
? DecisionValue.notSure()
: DecisionValue.decide(btnBizState.getActionPerformedName());
}
@Override
public DecisionValue<Boolean> isVisibleOnCard(ButtonV3 button) {
// 进行中隐藏所有按钮
if (todo.getState() == PendingMessageStateEnum.PROCESSING)
return DecisionValue.decide(false);
// 显示失效按钮
if (isSystemButtonActionPerformed(button))
return DecisionValue.notSure();
if (getWorkflowHideButtonKeys().contains(button.getCode()))
return DecisionValue.decide(false);
if (button.getSource() == RouterButtonSourceEnum.CUSTOM)
return DecisionValue.notSure();
BpmnButtonMetaInfoWithVisibleScope workflowButton = findWorkflowButton(button).orElse(null);
// 如果找不到流程按钮, 说明不需要显示按钮否则是否显示由待办模版控制
return workflowButton == null ? DecisionValue.decide(false) : DecisionValue.notSure();
}
@Override
public DecisionValue<Boolean> isSenderShow(ButtonV3 button) {
if (button.getSource() == RouterButtonSourceEnum.CUSTOM)
return DecisionValue.decide(false);
return DecisionValue.decide(SYSTEM_BUTTONS_SENDER_SHOW.contains(button.getCode()));
}
@Override
public DecisionValue<Boolean> isExecutorShow(ButtonV3 button) {
if (button.getSource() == RouterButtonSourceEnum.CUSTOM)
return DecisionValue.notSure();
if (SYSTEM_BUTTONS_EXECUTOR_SHOW.contains(button.getCode()))
return DecisionValue.decide(true);
BpmnButtonMetaInfoWithVisibleScope workflowButton = findWorkflowButton(button).orElse(null);
if (workflowButton == null)
return DecisionValue.notSure();
List<ButtonVisibleScopeEnum> scopes = workflowButton.getVisibleScopes();
return DecisionValue.decide(scopes != null
&& scopes.contains(ButtonVisibleScopeEnum.EXECUTOR)
&& !scopes.contains(ButtonVisibleScopeEnum.INITIATOR));
}
private Optional<BizFinalStateEnum> findButtonClickState(ButtonV3 button) {
if (button.getSource() == RouterButtonSourceEnum.CUSTOM)
return Optional.empty();
return BizFinalStateEnum.findButtonClickedState(button.getCode());
}
private Optional<BpmnButtonMetaInfoWithVisibleScope> findWorkflowButton(ButtonV3 button) {
List<BpmnButtonMetaInfoWithVisibleScope> workflowButtons = this.taskInfo.getButtons();
if (workflowButtons == null)
workflowButtons = Collections.emptyList();
return workflowButtons.stream()
.filter(workflowButton -> workflowButton.getBtnKey().equals(button.getCode()))
.findFirst();
}
private Set<String> getWorkflowHideButtonKeys() {
List<BpmnButtonMetaInfo> workflowHiddenButtons = taskInfo.getCustomHiddenButtons();
if (workflowHiddenButtons == null)
workflowHiddenButtons = Collections.emptyList();
return workflowHiddenButtons.stream()
.map(BpmnButtonMetaInfo::getBtnKey)
.collect(toSet());
}
}

View File

@ -0,0 +1,76 @@
package cn.axzo.msg.center.message.service.todo.card.workflow;
import cn.axzo.msg.center.api.mq.TodoUpdateMessage;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.message.service.card.interceptor.CardButtonInterceptor;
import cn.axzo.msg.center.message.service.card.interceptor.CardButtonInterceptorFactory;
import cn.axzo.msg.center.message.service.todo.card.TodoSyncCardService;
import cn.axzo.msg.center.message.service.todo.manage.TodoLogger;
import cn.axzo.msg.center.message.service.todo.manage.TodoRequestContext;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.pending.request.CardContent;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo;
import cn.axzo.workflow.starter.api.WorkflowCoreService;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
class WorkflowTodoCardButtonInterceptorFactory implements CardButtonInterceptorFactory {
private final TodoBusinessDao todoBusinessDao;
private final TodoDao todoDao;
private final WorkflowCoreService workflowCoreService;
private final TodoLogger todoLogger;
@Override @Nullable
public CardButtonInterceptor create(CardContent card) {
BizCategoryEnum category = TodoSyncCardService.findTodoBizCategory(card.getBizParam()).orElse(null);
if (category != BizCategoryEnum.FLOW)
return null;
String identityCode = TodoSyncCardService.findTodoIdentityCode(card.getBizParam()).orElse(null);
if (identityCode == null)
return null;
Todo todo = todoDao.findTodoByCode(identityCode).orElse(null);
if (todo == null) {
log.warn("todo not found. identityCode={}", card.getBizParam());
return null;
}
BpmnTaskButtonVo taskInfo = fetchWorkflowButtons(todo);
log.info("fetchWorkflowButtons, todoIdentityCode={}, workflowTaskInfo: {}",
todo.getIdentityCode(), JSON.toJSONString(taskInfo));
String contextName = "syncWorkflowButtons";
TodoUpdateMessage todoMessage = TodoSyncCardService.getTodoUpdateMessage().orElse(null);
if (todoMessage != null)
contextName += ":" + todoMessage.getOperation();
TodoRequestContext ctx = TodoRequestContext.create(contextName, card)
.addLogContent("workflowTaskInfo", taskInfo);
todoLogger.logTodoUpdated(ctx, todo);
return new WorkflowTodoCardButtonInterceptor(todo, taskInfo);
}
BpmnTaskButtonVo fetchWorkflowButtons(Todo todo) {
TodoBusiness business = todoBusinessDao.getBusinesses(todo).findBusiness(todo).orElse(null);
BizAssertions.assertNotNull(business, "todo business not found. todoIdentityCode={}", todo.getIdentityCode());
BpmnTaskButtonSearchDTO workflowRequest = new BpmnTaskButtonSearchDTO();
workflowRequest.setProcessInstanceId(todo.getBizCode());
workflowRequest.setTaskId(todo.getSubBizCode());
workflowRequest.setInitiatorPersonId(business.getPromoterPersonId());
workflowRequest.setExecutorPersonId(todo.getExecutorPersonId());
return workflowCoreService.findProcessSingleTaskButtons(workflowRequest);
}
}

View File

@ -2,10 +2,12 @@ package cn.axzo.msg.center.message.service.todo.manage;
import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoBusinesses;
import cn.axzo.msg.center.dal.TodoDao;
import cn.axzo.msg.center.dal.TodoLogDao;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.domain.entity.TodoLog;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.enums.TodoLogType;
@ -18,6 +20,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@ -27,8 +31,11 @@ public class TodoLogger {
private final TodoLogDao todoLogDao;
private final TodoBusinessDao todoBusinessDao;
private final TodoDao todoDao;
private final PendingMessageBizConfig cfg;
void logBusinessUpdated(TodoRequestContext ctx, TodoBusiness business) {
if (!cfg.isEnableTodoLog()) return;
TodoLog log = createBusinessLog(ctx, business);
log.setContext(ctx.getName());
log.addLogContents(ctx.getLogContents());
@ -55,16 +62,20 @@ public class TodoLogger {
logTodosUpdated(ctx, todos);
}
void logTodoUpdated(TodoRequestContext ctx, Todo todo) {
public void logTodoUpdated(TodoRequestContext ctx, Todo todo) {
logTodosUpdated(ctx, Collections.singletonList(todo));
}
public void logTodosUpdated(TodoRequestContext ctx, List<Todo> todos) {
if (CollectionUtils.isEmpty(todos))
return;
if (!cfg.isEnableTodoLog()) return;
if (CollectionUtils.isEmpty(todos)) return;
TodoBusinesses businesses = todoBusinessDao.getBusinesses(todos);
ArrayList<TodoLog> logs = new ArrayList<>();
for (Todo todo : todos) {
List<String> todoCodes = todos.stream()
.map(Todo::getIdentityCode)
.distinct().collect(toList());
// reload to get the up-to-date update time
for (Todo todo : todoDao.getByCodes(todoCodes)) {
logs.add(createTodoLog(ctx, businesses, todo)
.addLogContents(ctx.getLogContents()));
}
@ -101,6 +112,8 @@ public class TodoLogger {
log.setSubBizCode(todo.getSubBizCode());
log.setRequestNo(ctx.getRequestNo());
log.setContext(ctx.getName());
log.addLogContent("todoState", todo.getState());
log.addLogContent("todoUpdateTime", todo.getUpdateAt().getTime());
String category = businesses.findBusiness(todo)
.map(TodoBusiness::getBizCategory)
.map(Enum::name)

View File

@ -21,7 +21,6 @@ import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Service;
import cn.axzo.msg.center.message.service.replay.RequestInfo;
import cn.axzo.msg.center.message.service.replay.RequestReplayService;
import cn.axzo.msg.center.message.service.todo.TodoWithCardWrapper;
import cn.axzo.msg.center.message.service.todo.manage.broadcast.TodoBroadcaster;
import cn.axzo.msg.center.message.service.todo.manage.broadcast.TodoMqBroadcaster;
import cn.axzo.msg.center.message.service.todo.manage.event.HandoverEvent;
@ -42,13 +41,12 @@ import cn.axzo.msg.center.service.pending.request.CompletePendingMessageRequest;
import cn.axzo.msg.center.service.pending.request.PresetButtonPressedRequest;
import cn.axzo.msg.center.service.pending.request.RevokeByTemplateCodeRequest;
import cn.axzo.msg.center.service.pending.request.RevokePendingMessageByIdRequest;
import cn.axzo.msg.center.service.pending.request.SetHideRequest;
import cn.axzo.msg.center.service.pending.request.TodoHandoverRequest;
import cn.axzo.msg.center.service.pending.request.UpdateBusinessFinalBizStateRequest;
import cn.axzo.msg.center.service.pending.request.UpdatePendingMessageByIdRequest;
import cn.axzo.msg.center.service.pending.response.PushPendingMessageDTO;
import cn.axzo.msg.center.service.util.IdBuilder;
import cn.axzo.msg.center.service.util.JSONUtils;
import cn.axzo.msg.center.utils.DateFormatUtil;
import cn.axzo.msg.center.utils.QueryFormatter;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.alibaba.fastjson.JSONObject;
@ -62,7 +60,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.DuplicateKeyException;
@ -72,7 +69,6 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -91,6 +87,8 @@ import static java.util.stream.Collectors.toSet;
@RequiredArgsConstructor
public class TodoManager {
public static final String OP_FIRE_PRESET_BUTTON_PRESSED = "firePresetButtonPressed";
private final TodoBusinessDao todoBusinessDao;
private final TodoDao todoDao;
private final TodoRecordBuilder todoRecordBuilder;
@ -103,7 +101,6 @@ public class TodoManager {
private final ApplicationContext applicationContext;
private final TodoBroadcaster todoBroadcaster;
private final TransactionTemplate transactionTemplate;
private final TodoWithCardWrapper todoWithCardWrapper;
private final RequestReplayService requestReplayService;
public List<PushPendingMessageDTO> send(PendingMessagePushParam request) {
@ -184,7 +181,7 @@ public class TodoManager {
if (StringUtils.isBlank(business.getSampleTodoCode())
&& request.determineTodoType() == TodoType.EXECUTABLE)
todoBusinessDao.updateSampleTodCode(business.getId(), sample.getIdentityCode());
todoBroadcaster.fireTodoUpdates("send", todos);
todoBroadcaster.fireTodoUpdates("send", todos, true);
// 记录日志
// @formatter:off
ctx.addLogContent("templateTitle", templateModel.getTemplate().getTitle())
@ -200,7 +197,6 @@ public class TodoManager {
todoLogger.logBusinessUpdated(ctx, business);
todoLogger.logTodosUpdated(ctx, todos);
applicationContext.publishEvent(new NewTodoEvent(this, templateModel, todos));
todoWithCardWrapper.send(request, todos);
return todos.stream()
.map(todo -> new PushPendingMessageDTO(
@ -264,8 +260,8 @@ public class TodoManager {
destTodo.setExecutorName(request.determineToPersonName());
}
todoDao.saveBatch(destTodos);
todoBroadcaster.fireTodoUpdates("handover", srcTodos);
todoBroadcaster.fireTodoUpdates("handover", destTodos);
todoBroadcaster.fireTodoUpdates("handover", srcTodos, true);
todoBroadcaster.fireTodoUpdates("handover", destTodos, true);
// build handover mappings
List<TodoHandoverMapping> mappings = new ArrayList<>();
for (int i = 0; i < srcTodos.size(); i++) {
@ -299,7 +295,6 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoCompleted(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardCompleteStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
@ -310,7 +305,9 @@ public class TodoManager {
public boolean completeById(CompletePendingMessageByIdRequest request) {
Set<Long> ids = request.determineIds();
BizAssertions.assertNotEmpty(ids, "待办id不能为空");
TodoRequestContext ctx = TodoRequestContext.create("completeById", request);
TodoRequestContext ctx = TodoRequestContext
.create("completeById", request)
.delayBroadcast(true);
StateAdvanceResult advanceResult = advanceState(ctx, execAdvanceBuilder()
.in(Todo::getId, ids.toArray(new Object[0]))
.set(Todo::getState, PendingMessageStateEnum.COMPLETED));
@ -324,10 +321,12 @@ public class TodoManager {
"businessUpdated", businessUpdated);
todoLogger.logBusinessUpdated(ctx, advanceResult.getBusiness());
}
if (advanceResult.isAdvanced()) {
if (advanceResult.isAdvanced())
todoLogger.logTodoCompleted(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardCompleteStateByTodoList(advanceResult.getAdvancedTodos());
}
if (businessUpdated)
todoBroadcaster.fireTodoUpdates("completeById", advanceResult.getBusinessId(), true);
else
advanceResult.broadcast();
return advanceResult.isAdvanced() || businessUpdated;
}
@ -354,12 +353,13 @@ public class TodoManager {
"businessUpdated", businessUpdated);
todoLogger.logBusinessUpdated(ctx, advanceResult.getBusiness());
}
if (advanceResult.isAdvanced()) {
if (advanceResult.isAdvanced())
todoLogger.logTodoCompleted(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardCompleteStateByTodoList(advanceResult.getAdvancedTodos());
}
if (businessUpdated)
todoBroadcaster.fireTodoUpdates("completeByBizCode", advanceResult.getBusinessId(), true);
else
advanceResult.broadcast();
return advanceResult.isAdvanced() || businessUpdated;
}
@ -398,8 +398,7 @@ public class TodoManager {
TodoRequestContext ctx = TodoRequestContext.create("updateBusinessFinalBizState", request)
.addLogContent("updated", updated);
todoLogger.logBusinessUpdated(ctx, business);
List<Todo> todos = todoDao.getByBusinessIds(Collections.singletonList(business.getId()));
todoBroadcaster.fireTodoUpdates("updateBusinessFinalBizState", todos);
todoBroadcaster.fireTodoUpdates("updateBusinessFinalBizState", business.getId(), true);
}
return updated;
}
@ -418,7 +417,6 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoCompleted(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardCompleteStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
@ -437,8 +435,7 @@ public class TodoManager {
return false;
}
todoLogger.logTodoRollback(ctx, advanceResult.getAdvancedTodos());
todoBroadcaster.fireTodoUpdates("rollbackBySubBizCode", advanceResult.getAdvancedTodos());
// todoWithCardWrapper.cardRollbackStateByTodoList(advanceResult.getAdvancedTodos());
todoBroadcaster.fireTodoUpdates("rollbackBySubBizCode", advanceResult.getAdvancedTodos(), true);
return true;
}
@ -458,7 +455,6 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoRevoked(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardRevokeStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
@ -474,7 +470,6 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoRevoked(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardRevokeStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
@ -490,7 +485,6 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoRevoked(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardRevokeStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
@ -507,7 +501,6 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoRevoked(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardRevokeStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
@ -520,37 +513,11 @@ public class TodoManager {
if (!advanceResult.isAdvanced())
return false;
todoLogger.logTodoRevoked(ctx, advanceResult.getAdvancedTodos());
todoWithCardWrapper.cardRevokeStateByTodoList(advanceResult.getAdvancedTodos());
return true;
}
// !! update
/**
* 将待办设置为隐藏, 隐藏有时间期限
*/
@Transactional(rollbackFor = Exception.class)
public boolean setHide(SetHideRequest request) {
BizAssertions.assertTrue(StringUtils.isNotBlank(request.getSubBizCode()), "subBizCode不能为空");
List<Todo> todos = todoDao.getBySubBizCode(request.getSubBizCode());
if (todos.isEmpty())
return false;
int seconds = request.getHideSeconds() == null
? cfg.getPendingSetHideSeconds() : request.getHideSeconds();
Date expireTime = DateTime.now().plusSeconds(seconds).toDate();
boolean updated = todoDao.setExecutableHide(request.getSubBizCode(), expireTime);
if (updated) {
List<Todo> updatedTodos = todos.stream()
.filter(todo -> todo.getState() == PendingMessageStateEnum.HAS_BEEN_SENT)
.collect(toList());
TodoRequestContext ctx = TodoRequestContext.create("setHide", request)
.addLogContent("expiredTime", expireTime.getTime())
.addLogContent("readableExpiredTime", DateFormatUtil.toReadableString(expireTime));
todoLogger.logTodosUpdated(ctx, updatedTodos);
}
return updated;
}
/**
* 将待办设置为执行中
*/
@ -571,8 +538,7 @@ public class TodoManager {
request.put("subBizCodes", subBizCodes);
TodoRequestContext ctx = TodoRequestContext.create("batchSetProcessing", request);
todoLogger.logSetTodoProcessing(ctx, todos);
todoBroadcaster.fireTodoUpdates("batchSetProcessing", todos);
todoWithCardWrapper.cardProcessingStateByTodoList(todos);
todoBroadcaster.fireTodoUpdates("batchSetProcessing", todos, true);
}
return updated;
}
@ -609,7 +575,7 @@ public class TodoManager {
.addLogContent("updatedRouterParam", routerParam)
.addLogContent("updatedTemplateCode", request.getTemplateCode());
todoLogger.logBusinessUpdated(ctx, business);
//TODO todoWithCardWrapper.send
todoBroadcaster.fireTodoUpdates("updateBusinessById", business.getId(), true);
}
return updated;
}
@ -620,31 +586,23 @@ public class TodoManager {
* 点击预设按钮
*/
@Transactional(rollbackFor = Exception.class)
public boolean firePresetButtonPressed(PresetButtonPressedRequest request, boolean isSyncCard) {
public boolean firePresetButtonPressed(PresetButtonPressedRequest request, boolean syncCardState) {
Todo todo = todoDao.findTodoByCode(request.getIdentityCode()).orElse(null);
if (todo == null)
return false;
TodoRequestContext ctx = TodoRequestContext.create("firePresetButtonPressed", request)
.addLogContent("presetButtonType", request.getPresetButtonType());
TodoRequestContext ctx = TodoRequestContext.create(OP_FIRE_PRESET_BUTTON_PRESSED, request)
.addLogContent("presetButtonType", request.getPresetButtonType())
.addLogContent("syncCardState", syncCardState);
if (!syncCardState)
ctx.disableUpdateCard();
StateAdvanceResult advanceResult = advanceState(ctx, execAdvanceBuilder()
.eq(Todo::getIdentityCode, request.getIdentityCode())
.set(Todo::getState, PendingMessageStateEnum.COMPLETED));
// isExecCompleted 可以排除是抄送待办的情况
boolean isAdvancedOrCompleted = advanceResult.isAdvanced() || todo.isExecCompleted();
// 支持重复发mq消息
if (isAdvancedOrCompleted) {
if (advanceResult.isAdvanced()) {
sendMqMessageOnPresetButtonPressed(ctx, request, todo);
todoBroadcaster.fireTodoUpdates("presetButtonPressed", todo);
todoWithCardWrapper.fireCardWhenPresetButtonPressedByTodo(request, todo, isSyncCard);
// 如果不是重复发送, 就只记一条日志. 如果是重复发送, 就单独记录一条日志
if (!advanceResult.isAdvanced())
todoLogger.logTodoUpdated(ctx, todo);
}
// 如果不是重复发送, 就只记一条日志. 所以这个记录日志不能提前
if (advanceResult.isAdvanced())
todoLogger.logTodoCompleted(ctx, advanceResult.getAdvancedTodos());
return isAdvancedOrCompleted;
}
return advanceResult.isAdvanced();
}
/**
@ -662,9 +620,13 @@ public class TodoManager {
try {
mqProducer.send(MqMessageRecord
.builder(MqMessageType.TODO_PRESET_BUTTON_PRESSED, message)
.messageKey(todo.getId())
.messageKey(todo.getIdentityCode())
.operatorId(request.getOperatorId())
.shardingKey(todo.getTemplateCode())
.shardingKey(IdBuilder.builder()
.append(todo.getTemplateCode())
.append(todo.getBizCode())
.append(todo.getSubBizCode())
.build())
.build());
ctx.addLogContent("sendMqMessage", ImmutableMap.of("isSuccess", "true"));
} catch (Exception e) {
@ -692,6 +654,7 @@ public class TodoManager {
return false;
ImmutableMap<String, Object> request = ImmutableMap.of("personId", personId, "identityCode", identityCode);
TodoRequestContext ctx = TodoRequestContext.create("setCopiedToMeRead", request)
.disableUpdateCard()
.addLogContent("state", PendingMessageStateEnum.READ)
.addLogContent("isSetAllRead", StringUtils.isBlank(identityCode));
StateAdvanceResult advanceResult = advanceState(ctx, copiedAdvanceBuilder()
@ -724,7 +687,7 @@ public class TodoManager {
"currentStateSample", noStateBusinessTodos.sampleTodo());
// @formatter:on
if (cfg.isLogAdvanceTodoStateFail())
todoLogger.logTodosUpdated(ctx, noStateBusinessTodos.todos);
todoLogger.logTodosUpdated(ctx.copy(String.format("%s:fail", ctx.getName())), noStateBusinessTodos.todos);
// throw an error? dunno
log.warn("尝试推进待办状态, 但是 {}. ctx={}, query={}, currentStateSample={}",
failReason, ctx, QueryFormatter.format(builder.getQuery()), noStateBusinessTodos.sampleTodo());
@ -745,7 +708,7 @@ public class TodoManager {
broadcastHandler = () -> {
if (!updatedTodoIds.isEmpty()) {
List<Todo> updatedTodos = todoDao.listByIds(updatedTodoIds);
todoBroadcaster.fireTodoUpdates(ctx.getName(), updatedTodos);
todoBroadcaster.fireTodoUpdates(ctx.getName(), updatedTodos, ctx.isUpdateCard());
}
};
if (!ctx.isDelayBroadcast())

View File

@ -3,7 +3,9 @@ package cn.axzo.msg.center.message.service.todo.manage;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Throwables;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.LinkedHashMap;
@ -18,6 +20,7 @@ public class TodoRequestContext {
private final String requestNo;
private final Map<String, Object> logContents = new LinkedHashMap<>();
private boolean delayBroadcast = false;
private boolean updateCard = true;
private TodoRequestContext(String name, String requestNo, Object request) {
this.name = name;
@ -35,7 +38,8 @@ public class TodoRequestContext {
}
public static TodoRequestContext create(String name, String requestNo, Object request) {
return new TodoRequestContext(name, requestNo, request);
String finalRequestNo = StringUtils.isBlank(requestNo) ? UUIDUtil.uuidString() : requestNo;
return new TodoRequestContext(name, finalRequestNo, request);
}
public TodoRequestContext addLogContent(String name, Object... fields) {
@ -53,15 +57,28 @@ public class TodoRequestContext {
return this;
}
public TodoRequestContext disableUpdateCard() {
this.updateCard = false;
return this;
}
public TodoRequestContext addLogContent(String name, Object value) {
if (name == null || value == null)
return this;
if (!logContents.containsKey(name))
if (!logContents.containsKey(name)) {
if (value instanceof Throwable)
logContents.put(name, Throwables.getStackTraceAsString((Throwable) value));
else
logContents.put(name, value);
}
return this;
}
public TodoRequestContext copy() {
return copy(this.name);
}
public TodoRequestContext copy(String name) {
TodoRequestContext copy = new TodoRequestContext(name, requestNo);
copy.logContents.putAll(logContents);
return copy;

View File

@ -26,11 +26,16 @@ public class TodoBroadcaster {
private final TodoPullBroadcaster todoPullBroadcaster;
private final TodoDao todoDao;
public void fireTodoUpdates(String operation, Todo todo) {
fireTodoUpdates(operation, Collections.singletonList(todo));
public void fireTodoUpdates(String operation, Long businessId, boolean updateCard) {
List<Todo> todos = todoDao.getByBusinessIds(Collections.singletonList(businessId));
fireTodoUpdates(operation, todos, updateCard);
}
public void fireTodoUpdates(String operation, List<Todo> todos) {
public void fireTodoUpdates(String operation, Todo todo, boolean updateCard) {
fireTodoUpdates(operation, Collections.singletonList(todo), updateCard);
}
public void fireTodoUpdates(String operation, List<Todo> todos, boolean updateCard) {
if (CollectionUtils.isEmpty(todos))
return;
List<Long> todoIds = todos.stream()
@ -42,8 +47,8 @@ public class TodoBroadcaster {
log.warn("发送待办通知时, 查询不到最新的待办信息. todoIds={}", JSON.toJSONString(todoIds));
return;
}
todoMqBroadcaster.fireTodoUpdated(operation, todos);
todoPullBroadcaster.fireTodoChanged(todos);
todoMqBroadcaster.fireTodoUpdated(operation, upToDateTodos, updateCard);
todoPullBroadcaster.fireTodoChanged(upToDateTodos);
}
}

View File

@ -11,6 +11,7 @@ import cn.axzo.msg.center.message.service.todo.manage.TodoExt;
import cn.axzo.msg.center.mq.MqMessageRecord;
import cn.axzo.msg.center.mq.MqProducer;
import cn.axzo.msg.center.service.enums.MqMessageType;
import cn.axzo.msg.center.service.util.IdBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@ -26,20 +27,25 @@ public class TodoMqBroadcaster {
private final MqProducer mqProducer;
private final TodoBusinessDao todoBusinessDao;
public void fireTodoUpdated(String operation, List<Todo> todos) {
public void fireTodoUpdated(String operation, List<Todo> todos, boolean updateCard) {
TodoBusinesses businesses = todoBusinessDao.getBusinesses(todos);
for (Todo todo : todos)
fireTodoUpdated(operation, businesses, todo);
fireTodoUpdated(operation, businesses, todo, updateCard);
}
private void fireTodoUpdated(String operation, TodoBusinesses businesses, Todo todo) {
private void fireTodoUpdated(String operation, TodoBusinesses businesses, Todo todo, boolean updateCard) {
TodoUpdateMessage message = new TodoUpdateMessage();
message.setOperation(operation);
message.setUpdatedTodo(createTodoInfo(businesses, todo));
message.setUpdateCard(updateCard);
mqProducer.send(MqMessageRecord
.builder(MqMessageType.TODO_STATE_UPDATE, message)
.messageKey(todo.getId())
.shardingKey(todo.getTemplateCode())
.messageKey(todo.getIdentityCode())
.shardingKey(IdBuilder.builder()
.append(todo.getTemplateCode())
.append(todo.getBizCode())
.append(todo.getSubBizCode())
.build())
.build());
}

View File

@ -18,6 +18,7 @@ import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.core.Ordered;
@ -36,12 +37,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
args = {Connection.class, Integer.class})})
public class BeautifulPaginationInterceptor implements Interceptor, Ordered {
private static final DefaultReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final PaginationInterceptor delegate = new CustomPaginationInterceptor();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MetaObject metaObject = systemMetaForObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()
|| StatementType.CALLABLE == mappedStatement.getStatementType()) {
@ -70,7 +72,7 @@ public class BeautifulPaginationInterceptor implements Interceptor, Ordered {
private static String tryBuildSql(Invocation invocation) {
Connection connection = (Connection) (invocation.getArgs()[0]);
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MetaObject metaObject = systemMetaForObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
try (PreparedStatement statement = connection.prepareStatement(boundSql.getSql())) {
@ -86,6 +88,13 @@ public class BeautifulPaginationInterceptor implements Interceptor, Ordered {
}
}
public static MetaObject systemMetaForObject(Object object) {
return MetaObject.forObject(object,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
REFLECTOR_FACTORY);
}
private static class CustomPaginationInterceptor extends PaginationInterceptor {
public CustomPaginationInterceptor() {

View File

@ -63,7 +63,7 @@ public class ProxyStatement extends StatementWrapper {
private boolean canWarnPeriodically() {
int maxWarnTimes = props.getRowCount().getPeriodMaxWarnTimes();
return rowCount % 2000 == 0 && periodWarnTimes <= maxWarnTimes;
return rowCount % 100 == 0 && periodWarnTimes <= maxWarnTimes;
}
@Override

View File

@ -0,0 +1,29 @@
package cn.axzo.msg.center.mq;
/**
* @author yanglin
*/
public enum ConsumerIsolation {
TODO_SYNC_CARD_BIZ,
TODO_SYNC_CARD_FLOW,
TODO_PRESET_BUTTON_CLICKED_SYNC_CARD,
CARD_PRESET_BUTTON_CLICKED_SYNC_TODO
;
private static final ThreadLocal<ConsumerIsolation> INSTANCE = new ThreadLocal<>();
public static void setIsolation(ConsumerIsolation isolation) {
INSTANCE.set(isolation);
}
public static ConsumerIsolation getIsolation() {
return INSTANCE.get();
}
public static void clearIsolation() {
INSTANCE.remove();
}
}

View File

@ -0,0 +1,56 @@
package cn.axzo.msg.center.mq;
import cn.axzo.framework.rocketmq.BaseListener;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.msg.center.service.enums.MqMessageType;
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author yanglin
*/
public abstract class IsolationMQListener extends BaseListener
implements RocketMQListener<MessageExt>, EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
private final ConsumerIsolation isolation;
private final Event.EventCode eventCode;
protected IsolationMQListener(ConsumerIsolation isolation, MqMessageType mqMessageType) {
this(isolation, mqMessageType.getEventCode());
}
protected IsolationMQListener(ConsumerIsolation isolation, Event.EventCode eventCode) {
this.isolation = isolation;
this.eventCode = eventCode;
}
@Override
public void onMessage(MessageExt message) {
ConsumerIsolation.setIsolation(isolation);
try {
super.onEvent(message, eventConsumer);
} finally {
ConsumerIsolation.clearIsolation();
}
}
@Override
public final void onEvent(Event event, EventConsumer.Context context) {
if (ConsumerIsolation.getIsolation() == isolation)
onEventImpl(event, context);
}
@Override
public void afterPropertiesSet() {
eventConsumer.registerHandler(eventCode, this);
}
public abstract void onEventImpl(Event event, EventConsumer.Context context);
}

View File

@ -11,7 +11,6 @@ import cn.axzo.framework.rocketmq.RocketMQEventProducer.RocketMQMessageMeta;
import cn.axzo.framework.rocketmq.utils.TraceUtils;
import cn.axzo.msg.center.api.mq.MqMessage;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
@ -41,8 +40,9 @@ import java.util.function.Consumer;
public class RocketMQConfig {
public static final String APP_NAME = "MSG-CENTER";
public static final String MSG_CENTER_TOPIC = "topic_msg_center_${spring.profiles.active}";
@Value("topic_msg_center_${spring.profiles.active}")
@Value(MSG_CENTER_TOPIC)
private String topic;
@Bean
@ -140,26 +140,4 @@ public class RocketMQConfig {
});
}
/**
* 卡片变更,同步状态至待办TODO
*/
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_msg_center_${spring.profiles.active}",
consumerGroup = "GID_topic_card_change_state_sync_todo_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class CardChangeStateSyncTodoListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;
@Override
public void onMessage(MessageExt message) {
log.info("CardChangeStateSyncTodoListener onMessage,message:{}", JSON.toJSONString(message));
super.onEvent(message, eventConsumer);
}
}
}

View File

@ -0,0 +1,8 @@
package cn.axzo.msg.center.utils.desision;
/**
* @author yanglin
*/
public enum Decision {
NOT_SURE, DECIDED
}

View File

@ -0,0 +1,41 @@
package cn.axzo.msg.center.utils.desision;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.function.Supplier;
/**
* @author yanglin
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DecisionValue<T> {
private static final DecisionValue<?> NOT_SURE = new DecisionValue<>(Decision.NOT_SURE, null);
private final Decision decision;
@Getter private final T value;
@SuppressWarnings("unchecked")
public static <T> DecisionValue<T> notSure() {
return (DecisionValue<T>) NOT_SURE;
}
public static <T> DecisionValue<T> decide(T value) {
return new DecisionValue<>(Decision.DECIDED, value);
}
public T orElse(Supplier<T> supplier) {
return isDecided() ? value : supplier.get();
}
public T orElse(T value) {
return isDecided() ? this.value : value;
}
public boolean isDecided() {
return decision == Decision.DECIDED;
}
}

View File

@ -148,4 +148,9 @@ public class CardInfo {
*/
private String subtitle;
public JSONObject determineBizParam() {
if (bizParam == null)
bizParam = new JSONObject();
return bizParam;
}
}

View File

@ -30,4 +30,5 @@ public class CardPresetButtonPressedMessage extends MqMessage implements Seriali
*/
private CardInfo cardInfo;
private String batchNo;
}

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.api.mq;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -9,6 +10,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class TodoUpdateMessage extends MqMessage {
/**
* 什么操作导致这次变化
*/
@ -17,4 +19,15 @@ public class TodoUpdateMessage extends MqMessage {
* 待办信息
*/
private TodoInfo updatedTodo;
/**
* 是否需要更新卡片
*/
private boolean updateCard;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -1,12 +1,17 @@
package cn.axzo.msg.center.service;
import cn.axzo.msg.center.service.enums.ButtonStyleEnum;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.pending.response.MessageButton;
import java.util.List;
/**
* @author yanglin
*/
public interface ButtonV3 {
public interface ButtonV3 extends MessageButton {
String getName();
@ -22,14 +27,14 @@ public interface ButtonV3 {
RouterCategoryEnum getCategory();
RouterButtonSourceEnum getSource();
List<ButtonStyleEnum> getStyles();
default boolean determineIsPendingShow() {
return getPendingShow() != null && getPendingShow();
}
default boolean determineIsExecutorShow() {
return getExecutorShow() != null && getExecutorShow();
}
default boolean isPerformActionAvailable() {
return determineIsPendingShow() || getCategory() == RouterCategoryEnum.PRESET_BUTTON;
}

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.service.domain;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.msg.center.service.domain.url.AppUrl;
import cn.axzo.msg.center.service.domain.url.WebUrl;
import cn.axzo.msg.center.service.enums.WebPageOpenStrategy;
@ -17,6 +18,8 @@ import lombok.Setter;
@Getter
public class UrlConfig {
private String sameUrlForAllPlatforms;
/**
* PC(OMS)
*/
@ -129,4 +132,14 @@ public class UrlConfig {
getOrCreateAppWorker().setIos(appUrl);
}
public void setWebOpenStrategy(WebPageOpenStrategy openStrategy) {
if (pcCms != null) pcCms.setOpenStrategy(openStrategy);
if (pcOms != null) pcOms.setOpenStrategy(openStrategy);
if (pcGaGeneral != null) pcGaGeneral.setOpenStrategy(openStrategy);
}
public UrlConfig copy() {
return JSON.parseObject(JSON.toJSONString(this), UrlConfig.class);
}
}

View File

@ -26,7 +26,7 @@ public class PeerPerson {
return person;
}
public static PeerPerson newPeerPerson(Long personId, Long ouId, Long workspaceId) {
public static PeerPerson create(Long personId, Long ouId, Long workspaceId) {
PeerPerson person = new PeerPerson();
person.setPersonId(personId);
person.setOuId(ouId);

View File

@ -13,10 +13,15 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum BizCategoryEnum {
public enum BizCategoryEnum implements CodeDefinition<String> {
FLOW("流程"), OTHER("其它"),
;
private final String desc;
@Override
public String getCode() {
return name();
}
}

View File

@ -3,14 +3,15 @@ package cn.axzo.msg.center.service.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Optional;
/**
* @description
* 业务终态的状态枚举包含审批流的相关状态
*
* @author cold_blade
* @date 2023/11/7
* @version 1.0
* @description 业务终态的状态枚举包含审批流的相关状态
* @date 2023/11/7
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@ -19,28 +20,42 @@ public enum BizFinalStateEnum implements CodeDefinition<String> {
/**
* 已处理
*/
COMPLETED("https://static.axzo.cn/fe-static/uni-icon/public/seal-1%23_%241699341908382.png"),
COMPLETED("", "", "https://static.axzo.cn/fe-static/uni-icon/public/seal-1%23_%241699341908382.png"),
/**
* 已撤销
*/
RETRACT("https://static.axzo.cn/fe-static/uni-icon/native/%E5%8E%9F%E7%A8%BF%23_%241711338477975.png"),
RETRACT("BPMN_REVOCATION", "已撤销", "https://static.axzo.cn/fe-static/uni-icon/native/%E5%8E%9F%E7%A8%BF%23_%241711338477975.png"),
/**
* 已通过
*/
PASSED("https://static.axzo.cn/fe-static/uni-icon/public/seal-4%23_%241699341908374.png"),
PASSED("BPMN_APPROVE", "已同意", "https://static.axzo.cn/fe-static/uni-icon/public/seal-4%23_%241699341908374.png"),
/**
* 已拒绝
*/
REJECTED("https://static.axzo.cn/fe-static/uni-icon/public/seal-2%23_%241699341908381.png"),
REJECTED("BPMN_REJECT", "已驳回", "https://static.axzo.cn/fe-static/uni-icon/public/seal-2%23_%241699341908381.png"),
/**
* 已终止
*/
ABORTED("https://axzo-public.oss-cn-chengdu.aliyuncs.com/%E5%8D%B0%E7%AB%A0-%E8%AF%A6%E6%83%85end.png");
ABORTED("", "", "https://axzo-public.oss-cn-chengdu.aliyuncs.com/%E5%8D%B0%E7%AB%A0-%E8%AF%A6%E6%83%85end.png");
private final String buttonCode;
private final String actionPerformedName;
private final String icon;
@Override
public String getCode() {
return name();
}
public static Optional<BizFinalStateEnum> findButtonClickedState(String buttonCode) {
for (BizFinalStateEnum value : values()) {
if (value.buttonCode.equals(buttonCode))
return Optional.of(value);
}
return Optional.empty();
}
public boolean isButtonPerformActionAvailable() {
return StringUtils.isNotBlank(actionPerformedName);
}
}

View File

@ -26,7 +26,7 @@ public enum CardBizState implements CodeDefinition<String> {
ABORTED("已中止", true, "https://axzo-public.oss-cn-chengdu.aliyuncs.com/msg-center/todo_card_state/card_biz_state_aborted_20241220.png"),
COMPLETED("已处理", true, "https://axzo-public.oss-cn-chengdu.aliyuncs.com/msg-center/todo_card_state/card_biz_state_completed_20241220.png"),
END("已完结", true, "https://axzo-public.oss-cn-chengdu.aliyuncs.com/msg-center/todo_card_state/card_biz_state_end_20241220.png"),
IN_PROGRESS("进行中", false, "https://axzo-public.oss-cn-chengdu.aliyuncs.com/msg-center/todo_card_state/card_biz_state_inprogress_20241220.png")
PROCESSING("进行中", false, "https://axzo-public.oss-cn-chengdu.aliyuncs.com/msg-center/todo_card_state/card_biz_state_inprogress_20241220.png")
;

View File

@ -11,15 +11,14 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum CardState implements CodeDefinition<String> {
CREATED("待处理"),
SEND_SUCCESS("发送成功"),
COMPLETED("完成"),
CREATED("未发送"),
SEND_SUCCESS("发送"),
COMPLETED("处理"),
;
private final String name;
@Override
public String getCode() {
return name();

View File

@ -19,11 +19,9 @@ import java.util.Objects;
public enum PendingMessageStateEnum implements CodeDefinition<String> {
CREATED(0, "创建"),
UNSENT(1, "未发送"),
HAS_BEEN_SENT(2, "代办"),
COMPLETED(5, "已办"),
RETRACT(6, "已撤回"),
DELETED(7, "已删除"),
READ(8, "已读"),
PROCESSING(9, "处理中")
;

View File

@ -11,6 +11,10 @@ import java.util.List;
*/
public interface CardContent {
String getBizCode();
String getSubBizCode();
String getTemplateCode();
CardStateInfo getStateInfo();

View File

@ -24,16 +24,18 @@ public class CardUpdateStateRequest extends CardUpdateRequest {
private Boolean cardCompleted;
public void validate() {
if (!determineIsCardCompleted() && bizState == null)
throw new ServiceException("cardCompleted 和 bizState 不能同时为空");
if (!isValid()) throw new ServiceException("cardCompleted 和 bizState 不能同时为空");
}
public boolean isValid() {
return determineIsCardCompleted() || bizState != null;
}
public boolean determineIsCardCompleted() {
return cardCompleted != null && cardCompleted;
}
@Override
public String toString() {
@Override public String toString() {
return JSON.toJSONString(this);
}

View File

@ -1,18 +0,0 @@
package cn.axzo.msg.center.service.pending.response;
import cn.axzo.msg.center.api.custombutton.ProposedButtons;
import cn.axzo.msg.center.service.dto.ButtonRouterDTO;
import java.util.List;
/**
* @author yanglin
*/
@Deprecated
public interface TodoButtonProvider {
List<ButtonRouterDTO> getButtonRouters();
ProposedButtons getProposedButtons();
}

View File

@ -15,6 +15,7 @@ import cn.axzo.msg.center.service.pending.response.v3.model.ParsedTemplateV3;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.Comparator;
/**
* @author yanglin
@ -40,7 +41,9 @@ public class ParsedModelV3Walker {
}
for (ParsedGroupV3 group : new ArrayList<>(model.determineGroups()))
visitGroup(visitor, group);
for (ParsedButtonV3 button : new ArrayList<>(model.determineButtons()))
ArrayList<ParsedButtonV3> buttons = new ArrayList<>(model.determineButtons());
buttons.sort(Comparator.comparingInt(ParsedButtonV3::determinePriority));
for (ParsedButtonV3 button : buttons)
visitButton(visitor, button);
if (template != null)
visitor.exitTemplate(template);

View File

@ -103,13 +103,8 @@ public class ParsedButtonV3 implements MessageButton, ButtonV3 {
return priority == null ? Integer.MAX_VALUE : priority;
}
public List<ButtonStyleEnum> parseStyle() {
if (style == null) return Collections.emptyList();
return JSON.parseArray(style.toJSONString(), ButtonStyleEnum.class);
}
public boolean hasStyle(ButtonStyleEnum style) {
return parseStyle().contains(style);
return getStyles().contains(style);
}
@Override
@ -126,4 +121,10 @@ public class ParsedButtonV3 implements MessageButton, ButtonV3 {
public PresetButtonType getPresetBtnType() {
return presetButtonType;
}
@Override
public List<ButtonStyleEnum> getStyles() {
if (style == null) return Collections.emptyList();
return JSON.parseArray(style.toJSONString(), ButtonStyleEnum.class);
}
}

View File

@ -45,4 +45,8 @@ public class IdBuilder {
return buf.stream().map(String::valueOf).collect(joining(":"));
}
@Override
public String toString() {
return build();
}
}

View File

@ -21,6 +21,10 @@ import java.util.Objects;
@Slf4j
public class BizAssertions {
public static ServiceException fail(String message, Object... args) {
return new ServiceException(MessageFormatter.arrayFormat(message, args).getMessage());
}
/**
* 断言为NULL
*/

View File

@ -17,9 +17,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@ -157,24 +155,4 @@ public class TodoDao extends ServiceImpl<TodoMapper, Todo> {
.set(Todo::getState, PendingMessageStateEnum.PROCESSING)
.update();
}
public boolean setExecutableHide(String subBizCode, Date expireTime) {
return lambdaUpdate()
.eq(Todo::getType, TodoType.EXECUTABLE)
.eq(Todo::getSubBizCode, subBizCode)
.eq(Todo::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
.set(Todo::getHideUntil, expireTime)
.update();
}
public List<Todo> findByCondition(String templateCode, String bizCode, String subBizCode, Long executorPersonId, Long ouId, Long workspaceId) {
return lambdaQuery()
.eq(StringUtils.isNotBlank(templateCode), Todo::getTemplateCode, templateCode)
.eq(StringUtils.isNotBlank(bizCode), Todo::getBizCode, bizCode)
.eq(StringUtils.isNotBlank(subBizCode), Todo::getSubBizCode, subBizCode)
.eq(Objects.nonNull(executorPersonId), Todo::getExecutorPersonId, executorPersonId)
.eq(Objects.nonNull(ouId), Todo::getOuId, ouId)
.eq(Objects.nonNull(workspaceId), Todo::getReceiverWorkspaceId, workspaceId)
.list();
}
}

View File

@ -3,6 +3,7 @@ package cn.axzo.msg.center.domain.entity;
import cn.axzo.msg.center.domain.utils.IgnorePropsJsonTypeHandler;
import cn.axzo.msg.center.service.ButtonV3;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.enums.ButtonStyleEnum;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import cn.axzo.msg.center.service.enums.RouterButtonSourceEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
@ -14,6 +15,9 @@ import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import java.util.Collections;
import java.util.List;
/**
* @author yanglin
*/
@ -102,6 +106,17 @@ public class MessageTemplateButtonV3 extends BaseEntityWithOperator<MessageTempl
return recordExt;
}
@Override
public List<ButtonStyleEnum> getStyles() {
if (style == null) return Collections.emptyList();
return JSON.parseArray(style.toJSONString(), ButtonStyleEnum.class);
}
@Override
public String key() {
return code;
}
@Setter
@Getter
public static class RecordExt {

View File

@ -182,20 +182,17 @@ public class Todo extends BaseEntityExt<Todo> implements MessageEntity {
return orgId;
}
/**
* 执行待办是否已经完成(处理)
*/
public boolean isExecCompleted() {
return type == TodoType.EXECUTABLE && state == PendingMessageStateEnum.COMPLETED;
}
@Override
public JSONObject bizParam() {
return bizExtParam == null ? new JSONObject() : bizExtParam;
if (bizExtParam == null)
bizExtParam = new JSONObject();
return bizExtParam;
}
@Override
public JSONObject routerParam() {
return routerParams == null ? new JSONObject() : routerParams;
if (routerParams == null)
routerParams = new JSONObject();
return routerParams;
}
}

View File

@ -60,6 +60,7 @@ public class MnsLimiter {
error = String.format("验证码短信: 每 %s 分钟发送给同一个手机号的重复内容不能超过 %s 条。 请勿重试发送!!",
props.getVerifyCodeLimitWindowMinutes(), limitCount);
}
error += " [" + request.getPhoneNo() + "]";
if (alreadySendCount + 1 > limitCount) {
log.warn("mns rate limited, {}, request={}", error, request);
throw new ServiceException(ReturnCodeEnum.FAIL.getCode(), error);

View File

@ -42,6 +42,7 @@
<feign-httpclient.version>11.8</feign-httpclient.version>
<jetbrains.version>26.0.0</jetbrains.version>
<epic.version>1.0.0-SNAPSHOT</epic.version>
<workflow.version>1.5.2-SNAPSHOT</workflow.version>
</properties>
<dependencyManagement>
@ -106,6 +107,11 @@
<artifactId>msg-center-api-v2</artifactId>
<version>${msg-center-api-v2-version}</version>
</dependency>
<dependency>
<groupId>cn.axzo.workflow</groupId>
<artifactId>workflow-engine-spring-boot-starter</artifactId>
<version>${workflow.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -0,0 +1,34 @@
package cn.axzo.msg.center.message.service.card;
import cn.axzo.msg.center.MsgCenterApplication;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.CardDao;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import lombok.RequiredArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Collections;
/**
* @author yanglin
*/
@SpringBootTest(classes = MsgCenterApplication.class)
@RequiredArgsConstructor(onConstructor_ = @Autowired)
class CardManagerTest {
private final CardManager cardManager;
private final CardSupport cardSupport;
private final CardDao cardDao;
@Test
void rebuildCardContent() {
TemplateModelV3 templateModel = cardSupport.ensureImChannelPresent("8733f93de8db49699a78eda5a342763c");
Card card = cardDao.findCardByBizMessageId("c0d696b2178f442f9488cfc74c518042").orElse(null);
BizAssertions.assertNotNull(card, "card not found");
cardManager.rebuildCardContent(templateModel, Collections.singletonList(card));
}
}

View File

@ -0,0 +1,26 @@
package cn.axzo.msg.center.message.service.todo.card;
import cn.axzo.msg.center.MsgCenterApplication;
import cn.axzo.msg.center.api.mq.CardPresetButtonPressedMessage;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author yanglin
*/
@SpringBootTest(classes = MsgCenterApplication.class)
@RequiredArgsConstructor(onConstructor_ = @Autowired)
class TodoSyncCardServiceTest {
private final TodoSyncCardService todoSyncCardService;
@Test
void syncCardPresetButtonPressed() {
String json = "{\"cardInfo\": {\"appCode\": \"msg-center:todo\", \"batchNo\": \"fd301a68203a430cbc6e6928c0164fe5\", \"bizCode\": \"test-14\", \"bizMessageId\": \"2e1119e4a96345049870558d53939700\", \"bizParam\": {\"assistLogName\": \"测试\", \"extInfo\": {\"todoType\": \"EXECUTABLE\", \"todoCategory\": \"OTHER\", \"todoPromoterWorkspaceId\": 0, \"todoIdentityCode\": \"80978834809443febcf579b0eecf560b\", \"todoExecutorWorkspaceId\": 0}}, \"buttonStates\": [{\"buttonCode\": \"f6a77000fc50487ab9be215201bf233a\", \"isActionPerformed\": true}], \"cardContent\": {\"bizCode\": \"test-14\", \"cardStyleCode\": \"common_style_001\", \"updateTime\": 1737025348622, \"cardContent\": \"系统提示,无需关注\", \"debugInfo\": {\"isUpdatable\": true, \"bizCode\": \"test-14\", \"bizState\": \"AGREED\", \"subBizCode\": \"aaaa\", \"cardState\": \"COMPLETED\"}, \"templateCode\": \"7aceab1f31ec4570a98cdaf7f8940c61\", \"sendTimestamp\": 1737025348622, \"cardTitle\": \"系统提示\", \"extInfo\": {\"todoType\": \"EXECUTABLE\", \"todoCategory\": \"OTHER\", \"todoPromoterWorkspaceId\": 0, \"todoIdentityCode\": \"80978834809443febcf579b0eecf560b\", \"todoExecutorWorkspaceId\": 0}, \"cardBannerUrl\": \"\", \"subBizCode\": \"aaaa\", \"stateImage\": \"https://axzo-public.oss-cn-chengdu.aliyuncs.com/msg-center/todo_card_state/card_biz_state_agreed_20241220.png\", \"cardButtons\": [{\"presetButtonType\": \"AGREE\", \"actionPerformed\": true, \"senderShow\": false, \"buttonCode\": \"f6a77000fc50487ab9be215201bf233a\", \"isHighlight\": true, \"action\": \"PRESET_BUTTON\", \"source\": \"CUSTOM\", \"title\": \"已同意\", \"executorShow\": true}]}, \"content\": \"系统提示,无需关注\", \"id\": 167756, \"identityCode\": \"726de22e936c4f3bb640693e5f2c0968\", \"imTaskId\": 756527, \"isSenderRobot\": \"YES\", \"receiverAppType\": \"CMP\", \"receiverOuId\": 10616, \"receiverPersonId\": 9000399522, \"receiverWorkspaceId\": 0, \"routerParam\": {}, \"senderAppType\": \"SYSTEM\", \"senderOuId\": 0, \"senderPersonId\": 6678911, \"senderWorkspaceId\": 0, \"state\": null, \"subBizCode\": \"aaaa\", \"subtitle\": \"\", \"templateCode\": \"7aceab1f31ec4570a98cdaf7f8940c61\", \"title\": \"系统提示\"}, \"messageId\": \"490ddb1e-5879-4088-bbda-7ca8f15bd4f2\", \"messageSendTime\": 1737025348697, \"messageSendTimeStr\": \"2025-01-16 19:02:28\", \"operatorId\": 9000399522, \"operatorName\": \"罗福\", \"presetButtonType\": \"AGREE\"}";
CardPresetButtonPressedMessage message = JSON.parseObject(json, CardPresetButtonPressedMessage.class);
todoSyncCardService.syncCardPresetButtonPressed(message);
}
}