diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/inside/notices/config/PendingMessageBizConfig.java b/inside-notices/src/main/java/cn/axzo/msg/center/inside/notices/config/PendingMessageBizConfig.java index 050335c2..ceab3700 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/inside/notices/config/PendingMessageBizConfig.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/inside/notices/config/PendingMessageBizConfig.java @@ -173,6 +173,15 @@ public class PendingMessageBizConfig { @Getter private int todoTitleSearchMaxSize = 5000; + @Getter + private boolean requestReplayWindowEnabled = true; + + @Getter + private long requestReplayWindowExpireTimeInSeconds = 3600L; + + @Getter + private Set requestReplayWhitelistTemplateCodes = new HashSet<>(); + public boolean determineOldMsgStatCacheOn() { return isOldMsgStatCacheOn(); } diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/controller/PendingMessageNewController.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/controller/PendingMessageNewController.java index 31fb59bd..7e4d95d5 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/controller/PendingMessageNewController.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/controller/PendingMessageNewController.java @@ -16,6 +16,7 @@ import cn.axzo.msg.center.service.pending.request.CompletePendingBySubCodeReques import cn.axzo.msg.center.service.pending.request.CompletePendingMessageByCodesRequest; import cn.axzo.msg.center.service.pending.request.CompletePendingMessageByIdRequest; import cn.axzo.msg.center.service.pending.request.CompletePendingMessageRequest; +import cn.axzo.msg.center.service.pending.request.FetchTodoByBizRequest; import cn.axzo.msg.center.service.pending.request.GetPendingTodosRequest; import cn.axzo.msg.center.service.pending.request.GetTodoCountRequest; import cn.axzo.msg.center.service.pending.request.GetTodoPageRequest; @@ -283,6 +284,12 @@ public class PendingMessageNewController implements PendingMessageClient { return CommonResponse.success(todoSimpleQueryService.getTodosSimple(request)); } + @Override + public CommonResponse> fetchTodosByBiz(FetchTodoByBizRequest request) { + log.info("fetchTodosByBiz, request={}", JSON.toJSONString(request)); + return CommonResponse.success(todoSimpleQueryService.fetchTodosByBiz(request)); + } + @Override public CommonResponse> getTodosSimpleCount(GetTodoCountRequest request) { return CommonResponse.success(todoSimpleQueryService.getTodosSimpleCount(request)); diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/domain/dto/PendingMessageDTO.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/domain/dto/PendingMessageDTO.java index 6973cf45..efc6377b 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/domain/dto/PendingMessageDTO.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/domain/dto/PendingMessageDTO.java @@ -14,12 +14,10 @@ import cn.axzo.msg.center.service.enums.OrganizationTypeEnum; import cn.axzo.msg.center.service.enums.PendingMessageStateEnum; import cn.axzo.msg.center.service.enums.TerminalTypeEnum; import cn.axzo.msg.center.service.enums.TodoType; -import cn.axzo.msg.center.service.pending.ClientRequest; import cn.axzo.msg.center.service.pending.response.PendingMessageResponse; import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3; import cn.axzo.msg.center.service.template.response.MessageDetailStyle; import cn.axzo.msg.center.utils.DateFormatUtil; -import cn.hutool.core.date.DateUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.AllArgsConstructor; @@ -33,7 +31,6 @@ import java.io.Serializable; import java.time.LocalDateTime; import java.util.Date; import java.util.List; -import java.util.Optional; /** * @description @@ -285,64 +282,6 @@ public class PendingMessageDTO implements Serializable { return null; } - public PendingMessageResponse toResponse(ClientRequest request) { - boolean isCreatedToday = false; - if (createTime != null) { - Date createAt = DateFormatUtil.toDate(createTime); - isCreatedToday = DateUtil.isSameDay(createAt, new Date()); - } - return PendingMessageResponse.builder() - // 发起人单位、项目信息 - .promoterOuId(promoterOuId) - .promoterWorkspaceId(promoterWorkspaceId) - .promoterWorkspaceName(promoterWorkspaceName) - .promoterWorkspaceType(promoterWorkspaceType) - // 接收者单位、项目信息 - .executorOuId(executorOuId) - .executorWorkspaceId(executorWorkspaceId) - .executorWorkspaceName(executorWorkspaceName) - .executorWorkspaceType(executorWorkspaceType) - // 其它信息 - .bizExtParamObj(bizExtParamObj) - .routerExtParamObj(routerExtParamObj) - //页面展示 - .ouId(this.ouId) - .workspaceId(workspaceId) - .workspaceName(workspaceName) - // 样式 - .detailStyle(detailStyle) - .isCreatedToday(isCreatedToday) - .isRead(isRead) - .todoType(todoType) - .proposedButtons(proposedButtons) - .templateCategory(templateCategory) - .identityCode(this.identityCode) - .templateCode(this.templateCode) - .title(this.title) - .content(this.content) - .promoterPersonId(Optional.ofNullable(this.promoter).map(PersonDTO::getId).orElse(null)) - .promoterName(Optional.ofNullable(this.promoter).map(PersonDTO::getName).orElse(null)) - .promoterIdentity(Optional.ofNullable(this.promoter).map(PersonDTO::getIdentity).orElse(null)) - .executorPersonId(Optional.ofNullable(this.executor).map(PersonDTO::getId).orElse(null)) - .executorName(Optional.ofNullable(this.executor).map(PersonDTO::getName).orElse(null)) - .executorIdentity(Optional.ofNullable(this.executor).map(PersonDTO::getIdentity).orElse(null)) - .bizCode(this.bizCode) - .subBizCode(this.subBizCode) - .bizDesc(this.bizDesc) - .bizFlag(this.bizFlag) - .state(this.getState()) - .bizCategory(this.bizCategory) - .createTimestamp(DateFormatUtil.toTimestamp(this.createTime)) - .updateTimestamp(DateFormatUtil.toTimestamp(this.updateTime)) - .deadlineTimestamp(DateFormatUtil.toTimestamp(this.deadline)) - .bizFinalStateIcon(Optional.ofNullable(bizFinalState).map(BizFinalStateEnum::getIcon).orElse(null)) - .bizFinalState(bizFinalState) - .modelV3(modelV3) - .displayOnCardKeyValues(modelV3.determineDisplayOnCardKeyValues()) - .cardUrlOpenStrategy(cardUrlOpenStrategy) - .build(); - } - @Override public String toString() { return JSON.toJSONString(this); diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardManager.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardManager.java index 1a97dfaf..df6bea78 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardManager.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardManager.java @@ -11,6 +11,7 @@ import cn.axzo.msg.center.common.utils.MiscUtils; import cn.axzo.msg.center.dal.CardDao; import cn.axzo.msg.center.domain.entity.Card; import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3; +import cn.axzo.msg.center.domain.enums.RequestReplayType; 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.broadcast.CardBroadcaster; @@ -18,6 +19,8 @@ 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.log.CardLogger; import cn.axzo.msg.center.message.service.card.log.CardLoggers; +import cn.axzo.msg.center.message.service.replay.RequestInfo; +import cn.axzo.msg.center.message.service.replay.RequestReplayService; import cn.axzo.msg.center.nimpush.device.PushDeviceService; import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots; import cn.axzo.msg.center.service.ButtonV3; @@ -69,6 +72,7 @@ public class CardManager { private final CardParser cardParser; private final CardBroadcaster cardBroadcaster; private final CardProps cardProps; + private final RequestReplayService requestReplayService; private final ExecutorService executor = new ThreadPoolExecutor( 5, 15, 5L, TimeUnit.MINUTES, @@ -78,6 +82,16 @@ public class CardManager { // 校验参数 BizAssertions.assertNotNull(request.getSender(), "发送人不能为空"); BizAssertions.assertNotEmpty(request.getReceivers(), "接收人不能为空"); + RequestInfo requestInfo = new RequestInfo(); + requestInfo.setTemplateCode(request.getTemplateCode()); + requestInfo.setBizCode(request.getBizCode()); + requestInfo.setSubBizCode(request.getSubBizCode()); + requestInfo.setReplayType(RequestReplayType.IM_CARD_SEND); + requestInfo.setRequest(request); + return requestReplayService.run(requestInfo, () -> sendImpl(request)); + } + + private CardSendResponse sendImpl(CardSendRequest request) { CardTemplate cardTemplate = cardSupport.parseCardTemplate(cardSupport .ensureImChannelPresent(request.getTemplateCode()), request); // 主要逻辑 diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardSupport.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardSupport.java index 0dd97357..14004a75 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardSupport.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/card/CardSupport.java @@ -82,7 +82,7 @@ public class CardSupport { cardIdempotentDao.save(idempotent); } catch (DuplicateKeyException e) { log.warn("重复创建卡片, request={}", request); - throw new ServiceException(String.format("重复创建卡片: %s", idempotent)); + throw new ServiceException(403, String.format("重复创建卡片: %s", idempotent)); } } diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/impl/v3/ModelV3Parser.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/impl/v3/ModelV3Parser.java index 7f04e7a2..790f3e36 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/impl/v3/ModelV3Parser.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/impl/v3/ModelV3Parser.java @@ -79,6 +79,7 @@ public class ModelV3Parser { @Override public void visitGroupKeyValue(ParsedGroupV3 group, ParsedKV kv) { + kv.setValueVariable(kv.getValue()); kv.setValue(resolveBizParam(kv.getValue())); if (kv.getContentType() == KVContentType.PERSON_ID diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestAcquireResult.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestAcquireResult.java new file mode 100644 index 00000000..4772e148 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestAcquireResult.java @@ -0,0 +1,9 @@ +package cn.axzo.msg.center.message.service.replay; + +/** + * @author yanglin + */ +enum RequestAcquireResult { + NEW_REQUEST, + DUPLICATE_REQUEST, +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestInfo.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestInfo.java new file mode 100644 index 00000000..6e7042f4 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestInfo.java @@ -0,0 +1,24 @@ +package cn.axzo.msg.center.message.service.replay; + +import cn.axzo.msg.center.domain.enums.RequestReplayType; +import com.alibaba.fastjson.JSON; +import lombok.Getter; +import lombok.Setter; + +/** + * @author yanglin + */ +@Setter +@Getter +public class RequestInfo { + private String templateCode; + private String bizCode; + private String subBizCode; + private RequestReplayType replayType; + private Object request; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestReplayService.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestReplayService.java new file mode 100644 index 00000000..1167f3a7 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/replay/RequestReplayService.java @@ -0,0 +1,106 @@ +package cn.axzo.msg.center.message.service.replay; + +import cn.axzo.basics.common.exception.ServiceException; +import cn.axzo.msg.center.common.utils.BizAssertions; +import cn.axzo.msg.center.common.utils.MD5; +import cn.axzo.msg.center.dal.RequestReplayDao; +import cn.axzo.msg.center.domain.entity.RequestReplay; +import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig; +import com.alibaba.fastjson.JSON; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.function.Supplier; + +/** + * @author yanglin + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class RequestReplayService { + + private final RequestReplayDao requestReplayDao; + private final PendingMessageBizConfig cfg; + private final RedisTemplate redisTemplate; + + public T run(RequestInfo requestInfo, Supplier supplier) { + if (!isWindowEnabled(requestInfo)) + return supplier.get(); + if (acquire(requestInfo) == RequestAcquireResult.DUPLICATE_REQUEST) + throw new ServiceException(403, "重复请求: " + requestInfo); + try { + return supplier.get(); + } catch (Exception e) { + release(requestInfo); + log.warn("run request error, requestInfo={}", requestInfo, e); + throw e; + } + } + + private RequestAcquireResult acquire(RequestInfo info) { + try { + return acquireRequestImpl(info); + } catch (Exception e) { + log.warn("acquire request error, requestInfo={}", info, e); + return RequestAcquireResult.NEW_REQUEST; + } + } + + private void release(RequestInfo requestInfo) { + if (!isWindowEnabled(requestInfo)) + return; + try { + BizAssertions.assertNotNull(requestInfo, "requestInfo is null"); + redisTemplate.delete(buildKey(hash(requestInfo))); + } catch (Exception e) { + log.warn("release request error, requestInfo={}", requestInfo, e); + } + } + + private RequestAcquireResult acquireRequestImpl(RequestInfo requestInfo) { + BizAssertions.assertNotNull(requestInfo, "info is null"); + BizAssertions.assertNotNull(requestInfo.getRequest(), "request is null"); + String hash = hash(requestInfo); + RequestReplay replay = new RequestReplay(); + replay.setTemplateCode(requestInfo.getTemplateCode()); + replay.setBizCode(requestInfo.getBizCode()); + String subBizCode = requestInfo.getSubBizCode(); + replay.setSubBizCode(subBizCode == null ? "" : subBizCode); + replay.setType(requestInfo.getReplayType()); + replay.setHash(hash); + replay.setRequest(JSON.parseObject(JSON.toJSONString(requestInfo.getRequest()))); + try { + requestReplayDao.save(replay); + } catch (Exception e) { + log.warn("save request error, requestInfo={}", requestInfo, e); + } + Duration timeout = Duration.ofSeconds(cfg.getRequestReplayWindowExpireTimeInSeconds()); + Boolean success = redisTemplate.opsForValue().setIfAbsent(buildKey(hash), "", timeout); + RequestAcquireResult acquireResult = Boolean.TRUE.equals(success) + ? RequestAcquireResult.NEW_REQUEST + : RequestAcquireResult.DUPLICATE_REQUEST; + if (acquireResult == RequestAcquireResult.DUPLICATE_REQUEST) + log.warn("duplicate request, requestInfo={}", requestInfo); + return acquireResult; + } + + private boolean isWindowEnabled(RequestInfo requestInfo) { + return cfg.isRequestReplayWindowEnabled() + && !cfg.getRequestReplayWhitelistTemplateCodes() + .contains(requestInfo.getTemplateCode()); + } + + private String hash(RequestInfo requestInfo) { + String jsonString = JSON.toJSONString(requestInfo.getRequest()); + return MD5.getMD5Code(jsonString); + } + + private static String buildKey(String hash) { + return String.format("msg-center:request-replay:%s", hash); + } + +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java index e1f26b7c..b8646d17 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java @@ -122,10 +122,12 @@ public class TodoRangeQueryService { if (respResult.getList() != null) { ArrayList keyValues = new ArrayList<>(); for (PendingMessageResponse response : respResult.getList()) { - response.determineButtonRouters() - .removeIf(btn -> !btn.determineStyles().contains(ButtonStyleEnum.OVER_CARD)); + if (request.isButtonDisplayOnCardAware()) + response.determineButtonRouters() + .removeIf(btn -> !btn.determineStyles().contains(ButtonStyleEnum.OVER_CARD)); if (response.getModelV3() != null) { response.getModelV3().getButtons().removeIf(btn -> { + if (!request.isButtonDisplayOnCardAware()) return false; YesOrNo supportBatchProcess = request.getSupportBatchProcess(); boolean notBatchProcessQuery = supportBatchProcess == null || supportBatchProcess == YesOrNo.NO; return notBatchProcessQuery && !btn.hasStyle(ButtonStyleEnum.OVER_CARD); @@ -151,6 +153,7 @@ public class TodoRangeQueryService { PendingMessagePageRequest request, List templateCodes) { Date startingAt = DateFormatUtil.toDate(LocalDateTime.now().minusDays(90)); LambdaQueryWrapper query = businessQuery(request.getTitle(), true) + .in(CollectionUtils.isNotEmpty(request.getTemplateCodes()), TodoBusiness::getTemplateCode, request.getTemplateCodes()) .eq(TodoBusiness::getPromoterPersonId, request.getPersonId()) .eq(TodoBusiness::getIsDelete, TableIsDeleteEnum.NORMAL.value) .gt(TodoBusiness::getCreateAt, startingAt) @@ -186,6 +189,7 @@ public class TodoRangeQueryService { LambdaQueryWrapper query = todoQuery(query(Todo.class), ouInfo, ouCollector) // 查询的待办类型: COPIED_TO_ME, EXECUTABLE .eq(Todo::getType, request.determineToDoType()) + .in(CollectionUtils.isNotEmpty(request.getTemplateCodes()), Todo::getTemplateCode, request.getTemplateCodes()) .eq(Todo::getExecutorPersonId, request.getPersonId()) //判断是否支持批量过滤 .eq(request.getSupportBatchProcess() != null, Todo::getSupportBatchProcess, request.getSupportBatchProcess()) diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRespBuilder.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRespBuilder.java index 3de0cf8e..27be4dbd 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRespBuilder.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRespBuilder.java @@ -34,6 +34,7 @@ import cn.axzo.msg.center.service.pending.response.v3.model.ParsedButtonV3; import cn.axzo.msg.center.service.pending.response.v3.model.ParsedCardUrl; import cn.axzo.msg.center.service.pending.response.v3.model.ParsedGroupDetail; 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.ParsedModelV3; import cn.axzo.msg.center.service.pending.response.v3.model.SessionUrlInfo; import cn.axzo.msg.center.service.pending.response.v3.model.SessionUrlSelector; @@ -262,7 +263,13 @@ public class TodoRespBuilder { Runnable modelV3Builder = () -> { response.setModelV3(parsedModel); - response.setDisplayOnCardKeyValues(parsedModel.determineDisplayOnCardKeyValues()); + List keyValues = parsedModel.collectKeyValues(); + boolean isKeyValueDisplayOnCardAware = request == null || request.isKeyValueDisplayOnCardAware(); + if (isKeyValueDisplayOnCardAware) + keyValues = keyValues.stream() + .filter(ParsedKV::isDisplayOnCard) + .collect(toList()); + response.setDisplayOnCardKeyValues(keyValues); response.setCardUrlOpenStrategy(templateModel.getTemplate().getCardUrlOpenStrategy()); ParsedModelV3Walker.walkDown(parsedModel, new ParsedModel3Visitor() { @Override diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoSimpleQueryService.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoSimpleQueryService.java index cacaf043..0da5b76f 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoSimpleQueryService.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoSimpleQueryService.java @@ -17,6 +17,7 @@ import cn.axzo.msg.center.service.enums.BizCategoryEnum; import cn.axzo.msg.center.service.enums.PendingMessageStateEnum; import cn.axzo.msg.center.service.enums.TodoQueryType; import cn.axzo.msg.center.service.enums.TodoType; +import cn.axzo.msg.center.service.pending.request.FetchTodoByBizRequest; import cn.axzo.msg.center.service.pending.request.GetTodoCountRequest; import cn.axzo.msg.center.service.pending.request.GetTodoPageRequest; import cn.axzo.msg.center.service.pending.request.GetTodoRequest; @@ -26,6 +27,7 @@ import cn.axzo.msg.center.service.pending.request.PendingMessageQueryRequest; import cn.axzo.msg.center.service.pending.response.PendingMessageCountDTO; import cn.axzo.msg.center.service.pending.response.PendingMessageResponse; import cn.axzo.msg.center.service.pending.response.PendingMessageSimpleDTO; +import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3; import cn.azxo.framework.common.model.Page; import cn.hutool.core.date.StopWatch; import cn.hutool.core.lang.Assert; @@ -94,7 +96,9 @@ public class TodoSimpleQueryService { if (request.getQueryType() == TodoQueryType.SEND_BY_ME) { TodoBusiness business = todoBusinessDao.getById(todo.getTodoBusinessId()); PendingRecordAdapter adapter = todoRespBuilder.buildBusinessAdapter(business); - return todoRespBuilder.toResponse(adapter, request); + PendingMessageResponse response = todoRespBuilder.toResponse(adapter, request); + v3ExtPopulator.populate(response.getModelV3()); + return response; } PendingMessageResponse response = todoRespBuilder.toResponse(todo, request); v3ExtPopulator.populate(response.getModelV3()); @@ -143,6 +147,27 @@ public class TodoSimpleQueryService { return todoRespBuilder.buildTodosSimple(latestTodos); } + public List fetchTodosByBiz(FetchTodoByBizRequest request) { + List todos = todoDao.lambdaQuery() + .and(CollectionUtils.isNotEmpty(request.getTodos()), wrapper -> { + for (FetchTodoByBizRequest.TodoInfo todo : request.getTodos()) { + wrapper.or() + .eq(StringUtils.isNotBlank(todo.getTemplateCode()), Todo::getTemplateCode, todo.getTemplateCode()) + .eq(StringUtils.isNotBlank(todo.getBizCode()), Todo::getBizCode, todo.getBizCode()) + .eq(StringUtils.isNotBlank(todo.getSubBizCode()), Todo::getSubBizCode, todo.getSubBizCode()); + } + }) + .last("LIMIT " + cfg.getGetTodoSimpleSize()) + .list(); + List response = todoRespBuilder.toResponse( + todoRespBuilder.buildTodoAdapters(todos), request); + ParsedModelV3[] models = response.stream() + .map(PendingMessageResponse::getModelV3) + .toArray(ParsedModelV3[]::new); + v3ExtPopulator.populate(models); + return response; + } + public List getTodosSimple(GetTodoRequest request) { List todos = todoDao.lambdaQuery() .in(CollectionUtils.isNotEmpty(request.getIdentityCodes()), Todo::getIdentityCode, request.getIdentityCodes()) diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/manage/TodoManager.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/manage/TodoManager.java index c586a6f4..8e3b527f 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/manage/TodoManager.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/manage/TodoManager.java @@ -13,11 +13,14 @@ 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.domain.entity.TodoHandoverMapping; +import cn.axzo.msg.center.domain.enums.RequestReplayType; import cn.axzo.msg.center.domain.persistence.BaseEntityExt; 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.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; @@ -101,6 +104,7 @@ public class TodoManager { private final TodoBroadcaster todoBroadcaster; private final TransactionTemplate transactionTemplate; private final TodoWithCardWrapper todoWithCardWrapper; + private final RequestReplayService requestReplayService; public List send(PendingMessagePushParam request) { TodoRequestContext ctx = TodoRequestContext.create("send", request.normalize()); @@ -109,6 +113,16 @@ public class TodoManager { } public List send(TodoRequestContext ctx, PendingMessagePushParam request) { + RequestInfo requestInfo = new RequestInfo(); + requestInfo.setTemplateCode(request.getTemplateCode()); + requestInfo.setBizCode(request.getBizCode()); + requestInfo.setSubBizCode(request.getSubBizCode()); + requestInfo.setReplayType(RequestReplayType.TODO_SEND); + requestInfo.setRequest(request); + return requestReplayService.run(requestInfo, () -> sendOrRetry(ctx, request)); + } + + private List sendOrRetry(TodoRequestContext ctx, PendingMessagePushParam request) { // 10 seconds at most for (int i = 0; i < 20; i++) { try { @@ -621,7 +635,7 @@ public class TodoManager { if (isAdvancedOrCompleted) { sendMqMessageOnPresetButtonPressed(ctx, request, todo); todoBroadcaster.fireTodoUpdates("presetButtonPressed", todo); - todoWithCardWrapper.fireCardWhenPresetButtonPressedByTodo(request, todo,isSyncCard); + todoWithCardWrapper.fireCardWhenPresetButtonPressedByTodo(request, todo, isSyncCard); // 如果不是重复发送, 就只记一条日志. 如果是重复发送, 就单独记录一条日志 if (!advanceResult.isAdvanced()) diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/ClientRequest.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/ClientRequest.java index 286604f4..2d7939bb 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/ClientRequest.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/ClientRequest.java @@ -16,4 +16,12 @@ public interface ClientRequest { return ModelVersion.V3.getVersionNumber(); } + default boolean isKeyValueDisplayOnCardAware() { + return false; + } + + default boolean isButtonDisplayOnCardAware() { + return false; + } + } \ No newline at end of file diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/client/PendingMessageClient.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/client/PendingMessageClient.java index 830f2d13..968dcb9e 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/client/PendingMessageClient.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/client/PendingMessageClient.java @@ -7,6 +7,7 @@ import cn.axzo.msg.center.service.pending.request.CompletePendingBySubCodeReques import cn.axzo.msg.center.service.pending.request.CompletePendingMessageByCodesRequest; import cn.axzo.msg.center.service.pending.request.CompletePendingMessageByIdRequest; import cn.axzo.msg.center.service.pending.request.CompletePendingMessageRequest; +import cn.axzo.msg.center.service.pending.request.FetchTodoByBizRequest; import cn.axzo.msg.center.service.pending.request.GetPendingTodosRequest; import cn.axzo.msg.center.service.pending.request.GetTodoCountRequest; import cn.axzo.msg.center.service.pending.request.GetTodoPageRequest; @@ -347,6 +348,9 @@ public interface PendingMessageClient { @PostMapping(value = "/pending-message/record/get-todos-simple", produces = {MediaType.APPLICATION_JSON_VALUE}) CommonResponse> getTodosSimple(@RequestBody @Valid GetTodoRequest request); + @PostMapping(value = "/pending-message/record/fetch-todos-by-biz", produces = {MediaType.APPLICATION_JSON_VALUE}) + CommonResponse> fetchTodosByBiz(@RequestBody @Valid FetchTodoByBizRequest request); + @PostMapping(value = "/pending-message/record/get-todos-simple/count", produces = {MediaType.APPLICATION_JSON_VALUE}) CommonResponse> getTodosSimpleCount(@RequestBody @Valid GetTodoCountRequest request); diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/CardSendRequest.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/CardSendRequest.java index 8e4e85b3..a026bc59 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/CardSendRequest.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/CardSendRequest.java @@ -47,6 +47,7 @@ public class CardSendRequest implements CardContent { /** * 用于幂等的唯一编码. 可选, 需要做幂等时传入. 强烈建议传, 不然可能会产生重复的IM消息 + *

最大长度250 */ private String idempotentCode; diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/FetchTodoByBizRequest.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/FetchTodoByBizRequest.java new file mode 100644 index 00000000..0140f61b --- /dev/null +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/FetchTodoByBizRequest.java @@ -0,0 +1,44 @@ +package cn.axzo.msg.center.service.pending.request; + +import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum; +import cn.axzo.msg.center.service.enums.TerminalTypeEnum; +import cn.axzo.msg.center.service.pending.ClientRequest; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; +import java.util.ArrayList; +import java.util.List; + +/** + * @author yanglin + */ +@Setter @Getter +public class FetchTodoByBizRequest implements ClientRequest { + + @NotEmpty(message = "rows不能为空") + private List todos = new ArrayList<>(); + + public void addTodo(TodoInfo todo) { + if (todos == null) + todos = new ArrayList<>(); + todos.add(todo); + } + + @Setter @Getter + public static class TodoInfo { + private String templateCode; + private String bizCode; + private String subBizCode; + } + + @Override + public AppTerminalTypeEnum getAppTerminalType() { + return null; + } + + @Override + public TerminalTypeEnum getTerminalType() { + return null; + } +} \ No newline at end of file diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java index 3bb1ac2c..ab722f88 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java @@ -23,6 +23,7 @@ import javax.validation.constraints.NotNull; import java.io.Serializable; import java.util.Collection; import java.util.Collections; +import java.util.Set; /** * @description @@ -51,6 +52,10 @@ public class PendingMessagePageRequest extends PageRequest implements ClientRequ * 应用终端 */ private AppTerminalTypeEnum appTerminalType; + /** + * 模板编码集合 + */ + private Set templateCodes; /** * 当前登录账户的自然id(前端不care) */ @@ -153,6 +158,16 @@ public class PendingMessagePageRequest extends PageRequest implements ClientRequ */ private Integer modelVersion; + /** + * 是否根据配置'是否显示在卡片上'过滤key/value + */ + private boolean isKeyValueDisplayOnCardAware = true; + + /** + * 是否根据配置'是否显示在卡片上'过滤按钮 + */ + private boolean isButtonDisplayOnCardAware = true; + // !! 用于排查问题 private String analysisToken; private AnalysisInfo a = new AnalysisInfo(); diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessageQueryRequest.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessageQueryRequest.java index ab0951f9..4cd7f43c 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessageQueryRequest.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessageQueryRequest.java @@ -78,6 +78,16 @@ public class PendingMessageQueryRequest implements ClientRequest, Serializable { private Integer modelVersion; + /** + * 是否根据配置'是否显示在卡片上'过滤key/value + */ + private boolean isKeyValueDisplayOnCardAware = false; + + /** + * 是否根据配置'是否显示在卡片上'过滤按钮 + */ + private boolean isButtonDisplayOnCardAware = false; + @Override public String toString() { return JSON.toJSONString(this); diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedKV.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedKV.java index a35bfd77..39d9511b 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedKV.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedKV.java @@ -37,6 +37,11 @@ public class ParsedKV { @JsonIgnore private boolean displayOnCard; + /** + * 原始变量 + */ + private String valueVariable; + @JsonIgnore public boolean isValueAbsent() { return ValueUtils.isValueAbsent(value); diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedModelV3.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedModelV3.java index df5b9fa1..d0d52d90 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedModelV3.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/v3/model/ParsedModelV3.java @@ -67,14 +67,13 @@ public class ParsedModelV3 implements MessageButtonProvider, Pus return template == null ? null : template.getCode(); } - public List determineDisplayOnCardKeyValues() { + public List collectKeyValues() { return determineGroups().stream() .filter(group -> group.getGroupType() == GroupType.KV_VALUES) .map(ParsedGroupV3::getGroupInfo) .filter(Objects::nonNull) .map(ParsedGroupInfo::getKeyValues) .flatMap(Collection::stream) - .filter(ParsedKV::isDisplayOnCard) .collect(toList()); } diff --git a/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/RequestReplayDao.java b/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/RequestReplayDao.java new file mode 100644 index 00000000..c8e54a48 --- /dev/null +++ b/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/RequestReplayDao.java @@ -0,0 +1,15 @@ +package cn.axzo.msg.center.dal; + +import cn.axzo.msg.center.dal.mapper.RequestReplayMapper; +import cn.axzo.msg.center.domain.entity.RequestReplay; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * @author yanglin + */ +@Slf4j +@Component +public class RequestReplayDao extends ServiceImpl { +} diff --git a/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/mapper/RequestReplayMapper.java b/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/mapper/RequestReplayMapper.java new file mode 100644 index 00000000..c111e43f --- /dev/null +++ b/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/mapper/RequestReplayMapper.java @@ -0,0 +1,10 @@ +package cn.axzo.msg.center.dal.mapper; + +import cn.axzo.msg.center.domain.entity.RequestReplay; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * @author yanglin + */ +public interface RequestReplayMapper extends BaseMapper { +} diff --git a/msg-center-domain/src/main/java/cn/axzo/msg/center/domain/entity/RequestReplay.java b/msg-center-domain/src/main/java/cn/axzo/msg/center/domain/entity/RequestReplay.java new file mode 100644 index 00000000..62b57d2e --- /dev/null +++ b/msg-center-domain/src/main/java/cn/axzo/msg/center/domain/entity/RequestReplay.java @@ -0,0 +1,51 @@ +package cn.axzo.msg.center.domain.entity; + +import cn.axzo.msg.center.domain.enums.RequestReplayType; +import cn.axzo.msg.center.domain.persistence.BaseEntityExt; +import cn.axzo.msg.center.domain.utils.IgnorePropsJsonTypeHandler; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +/** + * @author yanglin + */ +@Setter +@Getter +@TableName(value = "request_replay", autoResultMap = true) +public class RequestReplay extends BaseEntityExt { + + /** + * 模版编码 + */ + private String templateCode; + + /** + * 业务编码 + */ + private String bizCode; + + /** + * 子业务编码 + */ + private String subBizCode; + + /** + * 请求类型. TODO_SEND: 发送待办, IM_CARD_SEND: 发送IM卡片 + */ + private RequestReplayType type; + + /** + * 请求的hash值, 具体hash算法未指定 + */ + private String hash; + + /** + * 请求 + */ + @TableField(typeHandler = IgnorePropsJsonTypeHandler.class) + private JSONObject request; + +} \ No newline at end of file diff --git a/msg-center-domain/src/main/java/cn/axzo/msg/center/domain/enums/RequestReplayType.java b/msg-center-domain/src/main/java/cn/axzo/msg/center/domain/enums/RequestReplayType.java new file mode 100644 index 00000000..9e867e77 --- /dev/null +++ b/msg-center-domain/src/main/java/cn/axzo/msg/center/domain/enums/RequestReplayType.java @@ -0,0 +1,9 @@ +package cn.axzo.msg.center.domain.enums; + +/** + * @author yanglin + */ +public enum RequestReplayType { + TODO_SEND, + IM_CARD_SEND +}