REQ-3281: 发送卡片消息

This commit is contained in:
yanglin 2024-12-11 19:34:35 +08:00
parent 9da3f9b325
commit 8132bfee23
46 changed files with 1381 additions and 464 deletions

View File

@ -11,8 +11,8 @@ import cn.axzo.msg.center.domain.entity.MessageRecordV3;
import cn.axzo.msg.center.domain.enums.NativeTypeEnum;
import cn.axzo.msg.center.event.payload.MessageHistoryUpdatedPayload;
import cn.axzo.msg.center.inside.notices.service.IYouMengMessageService;
import cn.axzo.msg.center.inside.notices.service.impl.v3.msg.TemplateMessage;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.message.service.card.CardSupport;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Service;
import cn.axzo.msg.center.nimpush.PushChannel;
@ -89,7 +89,7 @@ public class PushYouMengMessageHandler implements EventHandler, InitializingBean
return;
}
if (!payload.getNewMessageHistory().getBizId().startsWith(TemplateMessage.BIZ_ID_PREFIX)) {
if (!payload.getNewMessageHistory().getBizId().startsWith(CardSupport.BIZ_ID_PREFIX)) {
log.info("push-handler 非msg-center的消息");
return;
}

View File

@ -9,7 +9,7 @@ import cn.axzo.msg.center.dal.MessageBaseTemplateDao;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
import cn.axzo.msg.center.im.service.IMService;
import cn.axzo.msg.center.inside.notices.config.ImMessageProps;
import cn.axzo.msg.center.inside.notices.service.impl.v3.msg.TemplateMessage;
import cn.axzo.msg.center.message.service.card.CardSupport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -46,7 +46,7 @@ public class IMServiceImpl implements IMService {
.update();
BizAssertions.assertTrue(templateUpdated, "未找到对应的模版或者模版已经删除");
UpdateTemplateSendPriorityRequest updatePriorityRequest = new UpdateTemplateSendPriorityRequest();
updatePriorityRequest.setBizIdPrefix(TemplateMessage.getBizIdPrefix(request.getTemplateCode()));
updatePriorityRequest.setBizIdPrefix(CardSupport.getBizIdPrefix(request.getTemplateCode()));
updatePriorityRequest.setSendPriority(request.getSendPriority());
updatePriorityRequest.setToken(imProps.getControllerToken());
Boolean imMessageUpdated = messageController.updateSendPriority(updatePriorityRequest).getData();

View File

@ -0,0 +1,92 @@
package cn.axzo.msg.center.inside.notices.service.impl.v3.msg;
import cn.axzo.msg.center.api.request.v4.MessageSendBasicInfoV4;
import cn.axzo.msg.center.api.response.v3.MessageSendResultV3;
import cn.axzo.msg.center.api.response.v3.TemplateSendResultV3;
import cn.axzo.msg.center.inside.notices.service.impl.v3.EventMappingProcessor;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.message.service.card.CardManager;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.dto.PersonV3DTO;
import cn.axzo.msg.center.service.enums.Channel;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* @author yanglin
*/
@Slf4j
@Component
@Scope("prototype")
@RequiredArgsConstructor
public class CardMappingProcessor implements EventMappingProcessor {
private final CardManager cardManager;
@Setter
private MessageSendBasicInfoV4 sendBasicInfo;
@Setter
private Channel channel;
@Setter
private TemplateModelV3 templateModel;
@Override
public void saveRecords() {
// NOP
}
@Override
public void maybeAsyncSend() {
PeerPerson cardSender = new PeerPerson();
cardSender.setOuId(sendBasicInfo.getSenderOuId());
cardSender.setWorkspaceId(sendBasicInfo.getSenderWorkspaceId());
PersonV3DTO eventSender = sendBasicInfo.getSender();
if (eventSender != null)
cardSender.setPersonId(eventSender.getId());
Set<PeerPerson> cardReceivers = new HashSet<>();
for (PersonV3DTO eventReceiver : sendBasicInfo.receivers()) {
PersonV3DTO.ReceiveModel receiveModel = eventReceiver.getImReceiveModel();
PeerPerson cardReceiver = new PeerPerson();
cardReceiver.setPersonId(eventReceiver.getId());
cardReceiver.setOuId(receiveModel == null
? sendBasicInfo.getReceiversOuId()
: receiveModel.getOuId());
cardReceiver.setWorkspaceId(receiveModel == null
? sendBasicInfo.getReceiversWorkspaceId()
: receiveModel.getWorkspaceId());
cardReceivers.add(cardReceiver);
}
CardSendRequest cardRequest = new CardSendRequest();
cardRequest.setAppCode("msg-center");
cardRequest.setTemplateCode(templateModel.getTemplateCode());
cardRequest.setBizCode(sendBasicInfo.getBizCode());
cardRequest.setSubBizCode(null);
cardRequest.setSender(cardSender);
cardRequest.setReceivers(cardReceivers);
cardRequest.setBizParam(sendBasicInfo.getBizExtParams());
cardRequest.setRouterParam(sendBasicInfo.getRouterParams());
cardRequest.setSubtitle(sendBasicInfo.getSubtitle());
cardManager.send(cardRequest);
}
@Override
public TemplateSendResultV3 buildTemplateSendResult() {
TemplateSendResultV3 templateResult = new TemplateSendResultV3(
templateModel.getTemplateCode(), channel.getCode());
for (PersonV3DTO receiver : sendBasicInfo.receivers()) {
templateResult.addResult(new MessageSendResultV3(
receiver.getId(), 0L));
}
return templateResult;
}
}

View File

@ -1,105 +0,0 @@
package cn.axzo.msg.center.inside.notices.service.impl.v3.msg;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.api.response.v3.MessageSendResultV3;
import cn.axzo.msg.center.api.response.v3.TemplateSendResultV3;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.MessageRecordV3Dao;
import cn.axzo.msg.center.domain.entity.MessageRecordV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.inside.notices.service.impl.v3.EventMappingProcessor;
import cn.axzo.msg.center.nimpush.device.PushDeviceService;
import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author yanglin
*/
@Slf4j
@Component
@Scope("prototype")
@RequiredArgsConstructor
public class MessageMappingProcessor implements EventMappingProcessor {
private final MessageRecordV3Dao messageRecordV3Dao;
private final TerminalAppMapping terminalAppMapping;
private final MessageTemplateParserV3 templateParser;
private final MessageApi messageApi;
private final PushDeviceService pushDeviceService;
/**
* Scope("prototype") -> we're good
*/
@Setter
private TemplateMessage template;
@Override
public void saveRecords() {
messageRecordV3Dao.saveBatch(template.getMessageRecords());
}
@Override
public void maybeAsyncSend() {
List<AppTypeEnum> appTypes = terminalAppMapping
.toImTypes(getTemplate().determinePushTerminals());
BizAssertions.assertNotEmpty(appTypes, "发送IM消息, 消息模版没有配置IM发送终端, templateCode={}, templateName={}",
getTemplate().getCode(), getTemplate().getName());
String cmTaskId = null;
String cmpTaskId = null;
PushDeviceSnapshots deviceSnapshots = pushDeviceService.createDeviceSnapshots();
try {
for (AppTypeEnum appType : appTypes) {
String taskId = sendImpl(deviceSnapshots, appType);
if (appType == AppTypeEnum.CM)
cmTaskId = taskId;
else if (appType == AppTypeEnum.CMP)
cmpTaskId = taskId;
}
messageRecordV3Dao.setSendSuccess(template.getMessageIds(), cmTaskId, cmpTaskId);
} catch (Exception e) {
messageRecordV3Dao.batchSetSendFailed(template.getMessageIds(), e.getMessage());
}
}
private String sendImpl(PushDeviceSnapshots deviceCache, AppTypeEnum appType) {
SendTemplateMessageParam request = template.buildImRequest(templateParser, deviceCache, appType);
ApiResult<MessageTaskResp> apiResult = messageApi.sendTemplateMessageAsync(request);
log.info("sending im message result, req={}, resp={}",
JSON.toJSONString(request), JSON.toJSONString(apiResult));
if (apiResult.isSuccess()) {
return apiResult.getData().getId() + "";
} else {
log.warn("sending im message fail, req={}, resp={}",
JSON.toJSONString(request), JSON.toJSONString(apiResult));
throw new ServiceException(apiResult.getMsg());
}
}
@Override
public TemplateSendResultV3 buildTemplateSendResult() {
TemplateSendResultV3 templateResult = new TemplateSendResultV3(
template.getTemplateCode(), template.getChannel().name());
for (MessageRecordV3 message : template.getMessageRecords()) {
templateResult.addResult(new MessageSendResultV3(
message.getReceiverPersonId(), message.getId()));
}
return templateResult;
}
private MessageTemplateV3 getTemplate() {
return template.getTemplateModel().getTemplate();
}
}

View File

@ -141,31 +141,31 @@ public class MessageTemplateParserV3 {
return im;
}
private static ParsedUrlForCms parseUrlForCms(UrlConfig urlConfig) {
if (urlConfig == null) return null;
ParsedUrlForCms urlForCms = new ParsedUrlForCms();
UrlConfigWalker.walkDown(urlConfig, new UrlConfigVisitor() {
@Override
public void visitPcCms(WebUrl pcCms) {
urlForCms.setUrl(pcCms.getUrl());
urlForCms.setOpenStrategy(pcCms.getOpenStrategy());
}
@Override
public void visitAppManager(MobileUrlConfig appManager) {
urlForCms.setHasManagerAppUrl(appManager.hasUrl());
}
});
return urlForCms;
}
private List<AppLink> getNativeAppLinks(UrlConfig urlConfig) {
if (urlConfig == null) return Collections.emptyList();
NativeAppLinkUrlConfigVisitor visitor = new NativeAppLinkUrlConfigVisitor();
UrlConfigWalker.walkDown(urlConfig, visitor);
return visitor.getLinks();
}
//private static ParsedUrlForCms parseUrlForCms(UrlConfig urlConfig) {
// if (urlConfig == null) return null;
// ParsedUrlForCms urlForCms = new ParsedUrlForCms();
// UrlConfigWalker.walkDown(urlConfig, new UrlConfigVisitor() {
//
// @Override
// public void visitPcCms(WebUrl pcCms) {
// urlForCms.setUrl(pcCms.getUrl());
// urlForCms.setOpenStrategy(pcCms.getOpenStrategy());
// }
//
// @Override
// public void visitAppManager(MobileUrlConfig appManager) {
// urlForCms.setHasManagerAppUrl(appManager.hasUrl());
// }
//
// });
// return urlForCms;
//}
//
//private List<AppLink> getNativeAppLinks(UrlConfig urlConfig) {
// if (urlConfig == null) return Collections.emptyList();
// NativeAppLinkUrlConfigVisitor visitor = new NativeAppLinkUrlConfigVisitor();
// UrlConfigWalker.walkDown(urlConfig, visitor);
// return visitor.getLinks();
//}
}

View File

@ -16,6 +16,7 @@ import cn.axzo.msg.center.common.utils.PlaceholderResolver;
import cn.axzo.msg.center.domain.entity.MessageRecordV3;
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.CardSupport;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Parser;
import cn.axzo.msg.center.message.service.impl.v3.UrlParser;
import cn.axzo.msg.center.nimpush.NimPushService;
@ -55,8 +56,6 @@ import static java.util.stream.Collectors.toSet;
@RequiredArgsConstructor
public class TemplateMessage {
public static final String BIZ_ID_PREFIX = "msg-center";
private final MessageSendRequestV4 req;
private final String batchNo;
@Getter
@ -127,25 +126,21 @@ public class TemplateMessage {
return records;
}
public static String getBizIdPrefix(String templateCode) {
return String.format("%s:%s", BIZ_ID_PREFIX, templateCode);
}
SendTemplateMessageParam buildImRequest(MessageTemplateParserV3 templateParser,
PushDeviceSnapshots deviceCache,
PushDeviceSnapshots deviceSnapshots,
AppTypeEnum appType) {
MessageRecordV3 sample = getMessageRecords().get(0);
GeneralMessagePushVO sendVo = templateParser.parse(sample, templateModel);
MessageSendBasicInfoV4 sendBasicInfo = req.getSendBasicInfo();
SendTemplateMessageParam imReq = new SendTemplateMessageParam();
imReq.setBizId(String.format("%s:%s", getBizIdPrefix(getTemplateCode()), sendBasicInfo.determineBizCode()));
imReq.setSendPriority(templateModel.getTemplate().determineImSendPriority());
imReq.setMsgHeader(parseTitle());
imReq.setMsgContent(parseContent());
imReq.setMsgTemplateId(getTemplateCode());
imReq.setMsgTemplateContent(JSON.toJSONString(sendVo));
imReq.setExcludePushPayloads(new ArrayList<>());
SendTemplateMessageParam imRequest = new SendTemplateMessageParam();
imRequest.setBizId(String.format("%s:%s", CardSupport.getBizIdPrefix(getTemplateCode()), sendBasicInfo.determineBizCode()));
imRequest.setSendPriority(templateModel.getTemplate().determineImSendPriority());
imRequest.setMsgHeader(parseTitle());
imRequest.setMsgContent(parseContent());
imRequest.setMsgTemplateId(getTemplateCode());
imRequest.setMsgTemplateContent(JSON.toJSONString(sendVo));
imRequest.setExcludePushPayloads(new ArrayList<>());
// 接收人
ArrayList<PersonAccountAttribute> receivers = new ArrayList<>();
Set<Long> cmUnique = new HashSet<>();
@ -155,7 +150,6 @@ public class TemplateMessage {
boolean pushable = pushData.determinePushable(log, getTemplateCode());
// 扩展信息
JSONObject ext = new JSONObject();
for (PersonV3DTO receiver : sendBasicInfo.receivers()) {
PersonV3DTO.ReceiveModel imReceiveModel = receiver.getImReceiveModel();
Long ouId = imReceiveModel == null ? sendBasicInfo.determineReceiversOuId() : imReceiveModel.getOuId();
@ -167,13 +161,13 @@ public class TemplateMessage {
}
boolean excludePayload = pushable &&
!deviceCache.getDevice(receiver.getId())
!deviceSnapshots.getDevice(receiver.getId())
.shouldPush(appType, PushChannel.NIM);
if (excludePayload) {
ExcludePushPayload excludePush = new ExcludePushPayload();
excludePush.setPersonId(receiver.getId() + "");
excludePush.setAppType(appType);
imReq.getExcludePushPayloads().add(excludePush);
imRequest.getExcludePushPayloads().add(excludePush);
}
PersonAccountAttribute receiverAccount = new PersonAccountAttribute();
@ -186,7 +180,8 @@ public class TemplateMessage {
else if (appType == AppTypeEnum.CMP)
cmpUnique.add(new OuAndPerson(ouId, receiver.getId()));
}
imReq.setReceivePersons(receivers);
imRequest.setReceivePersons(receivers);
JSONObject ext = new JSONObject();
ext.put("minAppVersion", templateModel.getTemplate().getMinAppVersion());
if (sample.getReceiverWorkspaceId() != null) {
ext.put("workspaceId", String.valueOf(sample.getReceiverWorkspaceId()));
@ -194,13 +189,13 @@ public class TemplateMessage {
if (sample.getReceiverOuId() != null) {
ext.put("ouId", String.valueOf(sample.getReceiverOuId()));
}
imReq.setExt(ext);
imRequest.setExt(ext);
if (pushable) {
imReq.setPayload(buildPayload(sample, appType));
imRequest.setPayload(buildPayload(sample, appType));
if (StringUtils.isNotBlank(pushData.getVoiceFile()))
ext.put(Intent.INTENT_SOUND, pushData.getVoiceFile());
}
return imReq;
return imRequest;
}
private String buildPayload(MessageRecordV3 sample, AppTypeEnum appType) {

View File

@ -8,8 +8,7 @@ import cn.axzo.msg.center.api.response.v3.MessageSendRespV3;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.inside.notices.service.MessageServiceV4;
import cn.axzo.msg.center.inside.notices.service.impl.v3.EventMappingProcessor;
import cn.axzo.msg.center.inside.notices.service.impl.v3.msg.MessageMappingProcessor;
import cn.axzo.msg.center.inside.notices.service.impl.v3.msg.TemplateMessage;
import cn.axzo.msg.center.inside.notices.service.impl.v3.msg.CardMappingProcessor;
import cn.axzo.msg.center.inside.notices.service.impl.v3.todo.TodoMappingProcessor;
import cn.axzo.msg.center.inside.notices.utils.FunctionalTransactionTemplate;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
@ -55,9 +54,10 @@ public class MessageServiceV4Impl implements MessageServiceV4 {
"未查询到对应的模板, templateCode=%s", templateCode)));
if (info.getChannel() == Channel.NOTIFICATION) {
// @Scope("prototype") -> we're good
MessageMappingProcessor imProcessor = beanFactory.getBean(MessageMappingProcessor.class);
imProcessor.setTemplate(new TemplateMessage(
request, sendRequestNo, info.getChannel(), templateModel, beanFactory));
CardMappingProcessor imProcessor = beanFactory.getBean(CardMappingProcessor.class);
imProcessor.setSendBasicInfo(sendBasicInfo);
imProcessor.setChannel(info.getChannel());
imProcessor.setTemplateModel(templateModel);
processors.add(imProcessor);
} else if (info.getChannel() == Channel.PENDING) {
// @Scope("prototype") -> we're good

View File

@ -5,6 +5,7 @@ import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import lombok.Getter;
import lombok.Setter;
@ -12,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
/**
* @author yanglin
@ -38,4 +40,10 @@ public class TemplateModelV3 {
.anyMatch(btn -> btn.getCategory() == RouterCategoryEnum.PRESET_BUTTON);
}
public Optional<MessageTemplateButtonV3> findPresetButton(PresetButtonType type) {
return buttons.stream()
.filter(btn -> btn.getPresetBtnType() == type)
.findFirst();
}
}

View File

@ -3,9 +3,11 @@ package cn.axzo.msg.center.message.domain.vo;
import cn.axzo.msg.center.domain.entity.GeneralMessageRecord;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
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.CardState;
import cn.axzo.msg.center.service.enums.PresetButtonType;
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;
@ -21,7 +23,6 @@ import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@ -42,6 +43,11 @@ public class GeneralMessagePushVO implements Serializable {
private static final long serialVersionUID = -9017550674630922381L;
/**
* 样式编码
*/
private String cardStyleCode;
/**
* 消息的唯一标识
*/
@ -102,10 +108,15 @@ public class GeneralMessagePushVO implements Serializable {
*/
private String stateImage;
/**
* 更新时间
*/
private Long updateTime;
/**
* 卡片元素
*/
private List<CardElementConfig> cardElements = new ArrayList<>();
private List<CardElementConfig> cardElements;
public static GeneralMessagePushVO from(GeneralMessageRecord record, String templateIcon, String orgIcon,
MessageTemplateRouterDTO msgTemplateRouter,
@ -225,6 +236,11 @@ public class GeneralMessagePushVO implements Serializable {
*/
private List<AppLink> actionPaths;
/**
* 各个端的url配置
*/
private UrlConfig urlConfig;
/**
* 专门给cms的路由信息
*/
@ -240,6 +256,16 @@ public class GeneralMessagePushVO implements Serializable {
*/
private Boolean actionPerformed;
/**
* 预设按钮类型. AGREE: 同意, REJECT: 驳回, REVOKE: 撤销
*/
private PresetButtonType presetButtonType;
/**
* 按钮编码
*/
private String buttonCode;
static CardButton from(MessageTemplateRouterDTO.MessageRouteDetailDTO routeDetail) {
return CardButton.builder()
.title(routeDetail.getName())

View File

@ -2,9 +2,8 @@ package cn.axzo.msg.center.message.service.card;
import cn.axzo.msg.center.service.pending.card.CardClient;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateRequest;
import cn.axzo.msg.center.service.pending.response.CardSendResponse;
import cn.axzo.msg.center.service.pending.response.CardUpdateResponse;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonPressedRequest;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -21,13 +20,22 @@ public class CardController implements CardClient {
private final CardManager cardManager;
@Override
public CommonResponse<CardSendResponse> send(CardSendRequest request) {
return CommonResponse.success(cardManager.send(request));
public CommonResponse<Void> send(CardSendRequest request) {
cardManager.send(request);
return CommonResponse.success();
}
@Override
public CommonResponse<CardUpdateResponse> update(CardUpdateRequest request) {
return CommonResponse.success(cardManager.update(request));
public CommonResponse<Void> updateState(CardUpdateStateRequest request) {
cardManager.updateState(request);
return CommonResponse.success();
}
@Override
public CommonResponse<Void>
firePresetButtonPressed(CardPresetButtonPressedRequest request) {
cardManager.firePresetButtonPressed(request);
return CommonResponse.success();
}
}

View File

@ -1,10 +1,49 @@
package cn.axzo.msg.center.message.service.card;
import cn.axzo.msg.center.dal.CardDao;
import cn.axzo.msg.center.dal.CardLogDao;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.domain.entity.CardLog;
import com.google.common.base.Throwables;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author yanglin
*/
@Component
@RequiredArgsConstructor
class CardLogger {
private final CardDao cardDao;
private final CardLogDao cardLogDao;
void reloadAndLogCards(String context, Object request, List<Card> cards) {
reloadAndLogCards(context, request, cards, null);
}
void reloadAndLogCards(String context, Object request, List<Card> cards, Exception e) {
ArrayList<CardLog> logs = new ArrayList<>();
for (Card card : cardDao.reloadCards(cards)) {
CardLog log = new CardLog();
logs.add(log);
log.setIdentityCode(card.getIdentityCode());
log.setTemplateCode(card.getTemplateCode());
log.setBizMessageId(card.getBizMessageId());
log.setBizCode(card.getBizCode());
log.setSubBizCode(card.getSubBizCode());
log.setContext(context);
log.setBatchNo(card.getBatchNo());
log.setCardContent(card.getCardContent());
log.addLogContent("request", request);
log.addLogContent("state", card.getState());
if (e != null)
log.addLogContent("exception", Throwables.getStackTraceAsString(e));
}
cardLogDao.saveBatch(logs);
}
}

View File

@ -1,20 +1,40 @@
package cn.axzo.msg.center.message.service.card;
import cn.axzo.msg.center.api.MessageApi;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
import cn.axzo.im.center.api.vo.req.UpdateMessageRequest;
import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
import cn.axzo.maokai.api.util.Ref;
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.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
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;
import cn.axzo.msg.center.message.service.card.broadcast.CardBroadcaster;
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.nimpush.device.PushDeviceService;
import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots;
import cn.axzo.msg.center.service.dto.PeerPerson;
import cn.axzo.msg.center.service.pending.card.domain.CardButtonStates;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonPressedRequest;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonRequest;
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.CardUpdateRequest;
import cn.axzo.msg.center.service.pending.response.CardSendResponse;
import cn.axzo.msg.center.service.pending.response.CardUpdateResponse;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* @author yanglin
@ -24,28 +44,168 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class CardManager {
private final ModelV3Service modelV3Service;
private final ModelV3Parser modelV3Parser;
private final TransactionTemplate transactionTemplate;
private final CardSupport cardSupport;
private final CardDao cardDao;
private final CardLogger cardLogger;
private final MessageApi messageApi;
private final PushDeviceService pushDeviceService;
private final CardParser cardParser;
private final CardBroadcaster cardBroadcaster;
public CardSendResponse send(CardSendRequest request) {
return null;
public void send(CardSendRequest request) {
// 校验参数
BizAssertions.assertNotNull(request.getSender(), "发送人不能为空");
BizAssertions.assertNotEmpty(request.getReceivers(), "接收人不能为空");
ParsedModelV3 parsedModel = cardSupport.parseModel(
cardSupport.getTemplateModelOrThrow(request.getTemplateCode()), request);
// 主要逻辑
CardSendModel sendModel = new CardSendModel(request, parsedModel);
cardSupport.buildCardsForSend(sendModel);
execTransactional(() -> {
cardDao.saveBatch(sendModel.getCards());
cardLogger.reloadAndLogCards("send:enqueue", request, sendModel.getCards());
});
PushDeviceSnapshots deviceSnapshots = pushDeviceService.createDeviceSnapshots();
String imSenderAccount = BizAssertions.assertResponse(
messageApi.findTemplateRobotImAccount(parsedModel.getTemplateCode()));
for (CardGroup group : sendModel.getCardGroups()) {
SendTemplateMessageParam imRequest = cardSupport.buildImSendRequest(
sendModel, group, deviceSnapshots, imSenderAccount);
try {
MessageTaskResp imResponse = BizAssertions.assertResponse(
messageApi.sendTemplateMessageAsync(imRequest));
execTransactional(() -> {
cardDao.setSendSuccess(sendModel.getCards(), imResponse);
cardLogger.reloadAndLogCards("send:success", request, sendModel.getCards());
});
} catch (Exception e) {
log.warn("发送IM消息失败, request={}", request, e);
cardLogger.reloadAndLogCards("send:fail", request, sendModel.getCards(), e);
}
}
}
public CardUpdateResponse update(CardUpdateRequest request) {
return null;
public void updateState(CardUpdateStateRequest request) {
TemplateModelV3 templateModel = cardSupport.getTemplateModelOrThrow(request.getTemplateCode());
Supplier<List<Card>> cursor = cardsCursor(request);
boolean updated = false;
for (List<Card> cards = cursor.get(); !cards.isEmpty(); cards = cursor.get()) {
updated = true;
List<Card> finalCards = cards;
execTransactional(() -> {
cardDao.updateStates(finalCards, request.getCardState());
rebuildCardContent(templateModel, finalCards);
cardLogger.reloadAndLogCards("updateState:enqueue", request, finalCards);
});
updateMessages("updateState", request, cards);
}
BizAssertions.assertTrue(updated, "未找到任何需要更新的卡片, request={}", request);
}
private ParsedModelV3 parseModel(String templateCode,
JSONObject bizParam,
JSONObject routerParam) {
TemplateModelV3 model = modelV3Service
.findEnabledByCode(templateCode)
.orElse(null);
BizAssertions.assertNotNull(model, "模板不存在或已下线 {} ", templateCode);
return modelV3Parser.parseModel(model, bizParam, new UrlParser(routerParam));
void firePresetButtonPressed(CardPresetButtonPressedRequest request) {
Card requestCard = cardDao.findCardByBizMessageId(request.getBizMessageId()).orElse(null);
BizAssertions.assertNotNull(requestCard, "找不到对应的卡片, bizMessageId={}", request.getBizMessageId());
List<Card> cards = cardDao.collectPersonCards(request.getBizMessageId());
TemplateModelV3 templateModel = cardSupport.getTemplateModelOrThrow(requestCard.getTemplateCode());
firePresetButtonPressedImpl(request, templateModel, cards);
}
public void firePresetButtonPressed(CardUpdatePresetButtonRequest request) {
TemplateModelV3 templateModel = cardSupport.getTemplateModelOrThrow(request.getTemplateCode());
Supplier<List<Card>> cursor = cardsCursor(request);
for (List<Card> cards = cursor.get(); !cards.isEmpty(); cards = cursor.get())
firePresetButtonPressedImpl(request, templateModel, cards);
}
private void firePresetButtonPressedImpl(
CardPresetButtonRequest request, TemplateModelV3 templateModel, List<Card> cards) {
MessageTemplateButtonV3 button = templateModel.findPresetButton(request.getPresetButtonType()).orElse(null);
BizAssertions.assertNotNull(button, "找不到对应的预设按钮, request={}", JSON.toJSONString(request));
ArrayList<Card> updates = new ArrayList<>();
for (Card card : cards) {
CardButtonStates buttonStates = CardButtonStates.create(card.getButtonStates());
//noinspection DataFlowIssue
buttonStates.setActionPerformed(button.getCode());
Card update = new Card();
update.setId(card.getId());
update.setButtonStates(buttonStates.getStates());
updates.add(update);
}
execTransactional(() -> {
cardDao.updateBatchById(updates);
rebuildCardContent(templateModel, cards);
cardLogger.reloadAndLogCards("presetButtonPressed:enqueue", request, cards);
});
if (updateMessages("presetButtonPressed", request, cards)) {
cardBroadcaster.firePresetButtonPressed(
cards, request.getOperatorId(), request.getPresetButtonType());
cardLogger.reloadAndLogCards("presetButtonPressed:mq:success", request, cards);
}
}
private Supplier<List<Card>> cardsCursor(CardUpdateRequest request) {
Ref<Long> maxId = Ref.create(0L);
return () -> {
List<Card> cards = cardDao.lambdaQuery()
.eq(Card::getAppCode, request.getAppCode())
.eq(Card::getTemplateCode, request.getTemplateCode())
.eq(Card::getBizCode, request.getBizCode())
.eq(StringUtils.isNotBlank(request.getSubBizCode()), Card::getSubBizCode, request.getSubBizCode())
.nested(CollectionUtils.isNotEmpty(request.getReceivers()), nested -> {
for (PeerPerson receiver : request.getReceivers()) {
nested.or().eq(Card::getReceiverPersonId, receiver.getPersonId())
// 有可能不区分ouId和workspaceId更新人所有ou和workspace的卡片
.eq(receiver.getOuId() != null, Card::getReceiverOuId, receiver.getOuId())
.eq(receiver.getWorkspaceId() != null, Card::getReceiverWorkspaceId, receiver.getWorkspaceId());
}
})
.gt(Card::getId, maxId.get())
.orderByAsc(Card::getId)
.last("LIMIT 500")
.list();
if (!cards.isEmpty())
maxId.set(cards.get(cards.size() - 1).getId());
return cards;
};
}
private void rebuildCardContent(TemplateModelV3 templateModel, List<Card> cards) {
ArrayList<Card> updates = new ArrayList<>();
for (Card card : cardDao.reloadCards(cards)) {
Card update = new Card();
update.setId(card.getId());
// 每个消息独立解析, 因为每个卡片的状态和按钮可能已经存在差异了
CardTemplate cardTemplate = CardTemplate.wrap(cardSupport.parseModel(templateModel, card));
BizAssertions.assertTrue(cardTemplate.isUpdatable(),
"模板不支持更新, templateCode={}", card.getTemplateCode());
update.setCardContent(cardParser.parseCardContent(cardTemplate, card));
updates.add(update);
}
cardDao.updateBatchById(updates);
}
private boolean updateMessages(String operation, Object request, List<Card> cards) {
UpdateMessageRequest imRequest = new UpdateMessageRequest();
for (Card card : cardDao.reloadCards(cards)) {
UpdateMessageRequest.Update update = new UpdateMessageRequest.Update();
update.setBizMessageId(card.getBizMessageId());
update.setMsgTemplateContent(JSON.toJSONString(card.getCardContent()));
imRequest.addUpdate(update);
}
try {
BizAssertions.assertResponse(messageApi.updateMessage(imRequest));
cardLogger.reloadAndLogCards(String.format("%s:success", operation), request, cards);
return true;
} catch (Exception e) {
log.warn("更新IM消息失败, operation={}, request={}", operation, JSON.toJSONString(request), e);
cardLogger.reloadAndLogCards(String.format("%s:fail", operation), request, cards, e);
return false;
}
}
private void execTransactional(Runnable runnable) {
transactionTemplate.executeWithoutResult(unused -> runnable.run());
}
}

View File

@ -3,6 +3,7 @@ package cn.axzo.msg.center.message.service.card;
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.domain.vo.ParsedUrlForCms;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
import cn.axzo.msg.center.message.service.impl.v3.NativeAppLinkUrlConfigVisitor;
@ -17,13 +18,13 @@ import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.KVContentType;
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;
import cn.axzo.msg.center.service.pending.request.CardContent;
import cn.axzo.msg.center.service.pending.response.v3.ParsedModel3Visitor;
import cn.axzo.msg.center.service.pending.response.v3.ParsedModelV3Walker;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedButtonV3;
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.ParsedTemplateV3;
import cn.axzo.msg.center.service.pending.response.v3.model.PersonInfo;
import lombok.RequiredArgsConstructor;
@ -45,28 +46,30 @@ class CardParser {
private final MessageSystemConfig messageSystemConfig;
private final V3ExtPopulator v3ExtPopulator;
GeneralMessagePushVO parseCardContent(ParsedModelV3 model,
CardContent card) {
ParsedTemplateV3 template = model.getTemplate();
GeneralMessagePushVO parseCardContent(CardTemplate cardTemplate, CardContent card) {
ParsedTemplateV3 template = cardTemplate.getTemplate();
GeneralMessagePushVO im = new GeneralMessagePushVO();
// 这个字段好像没有业务含义
im.setIdentityCode("");
im.setCardStyleCode(template.getCardStyleCode());
im.setBizCode("");
im.setTemplateCode(template.getCode());
im.setCardBannerUrl(template.getIcon());
im.setCardTitle(template.getTitle());
im.setCardContent(template.getContent());
im.setSendTimestamp(new Date().getTime());
//im.setCardState();
//im.setStateImage();
//im.setCardElements();
// 取当前时间即可, 数据库存的更新时间肯定晚于等于这个时间, 因此不会存在更新丢失
im.setUpdateTime(System.currentTimeMillis());
if (cardTemplate.isUpdatable())
im.setCardState(card.getState());
im.setStateImage(cardTemplate.determineStateImage(card.getState()));
if (StringUtils.isNotBlank(card.getSubtitle())) {
GeneralMessagePushVO.Subtitle subtitle = new GeneralMessagePushVO.Subtitle();
subtitle.setIconUrl(messageSystemConfig.getOrgIcon());
subtitle.setTitle(card.getSubtitle());
im.setSubtitles(Collections.singletonList(subtitle));
}
ParsedModelV3Walker.walkDown(model, new ParsedModel3Visitor() {
ParsedModelV3Walker.walkDown(cardTemplate.getParsedModel(), new ParsedModel3Visitor() {
@Override
public void visitTemplateCardUrlConfig(UrlConfig urlConfig) {
if (!urlConfig.hasUrl()) return;
@ -75,6 +78,7 @@ class CardParser {
cardDetailButton.setAction(RouterCategoryEnum.JUMP.name());
cardDetailButton.setIsHighlight(false);
cardDetailButton.setActionPaths(getNativeAppLinks(urlConfig));
cardDetailButton.setUrlConfig(urlConfig);
im.setCardDetailButton(cardDetailButton);
im.setCardUrlForCms(parseUrlForCms(urlConfig));
@ -82,15 +86,15 @@ class CardParser {
@Override
public void visitGroupKeyValue(ParsedGroupV3 group, ParsedKV kv) {
if (!kv.isDisplayOnCard())
return;
GeneralMessagePushVO.CardExtensionItem item = new GeneralMessagePushVO.CardExtensionItem(kv.getKey(), kv.getValue());
if (!kv.isDisplayOnCard()) return;
if (StringUtils.isBlank(kv.getValue())) return;
CardExtensionItem item = new CardExtensionItem(kv.getKey(), kv.getValue());
// IM人员: 张三(130****5556)
if (kv.getContentType() == KVContentType.PERSON_ID) {
v3ExtPopulator.populatePersonKV(kv);
PersonInfo person = kv.getPersonInfo();
if (person != null) {
item = new GeneralMessagePushVO.CardExtensionItem(kv.getKey(), person.getPlainPersonInfo());
item = new CardExtensionItem(kv.getKey(), person.getPlainPersonInfo());
}
}
if (im.getCardExtension() == null)
@ -103,7 +107,8 @@ class CardParser {
List<ButtonStyleEnum> styles = button.parseStyle();
if (!styles.contains(ButtonStyleEnum.OVER_CARD))
return;
if (card.getCardState() != CardState.PENDING && button.determinePendingShow())
if (card.getState() != CardState.PENDING
&& button.determinePendingShow())
return;
if (im.getCardButtons() == null)
im.setCardButtons(new ArrayList<>());
@ -117,6 +122,9 @@ class CardParser {
imButton.setIsOverCard(styles.contains(ButtonStyleEnum.OVER_CARD));
imButton.setExecutorShow(button.getExecutorShow());
imButton.setActionPerformed(card.isButtonActionPerformed(button.getCode()));
imButton.setUrlConfig(button.getUrlConfig());
imButton.setButtonCode(button.getCode());
imButton.setPresetButtonType(button.getPresetButtonType());
if (RouterCategoryEnum.ACTION.equals(button.getCategory())) {
AppLink appLink = new AppLink(TerminalTypeEnum.WEB_VIEW.name(), button.getApiUrl());
@ -130,6 +138,25 @@ class CardParser {
imButton.setUrlInfoForCms(parseUrlForCms(button.getUrlConfig()));
}
}
@Override
public void visitCardElement(CardElementConfig cfg) {
addCardElement(cfg);
}
@Override
public void visitCardExtField(CardElementConfig cfg) {
addCardElement(cfg);
}
private void addCardElement(CardElementConfig cfg) {
if (StringUtils.isBlank(cfg.getValue()))
return;
if (im.getCardElements() == null)
im.setCardElements(new ArrayList<>());
im.getCardElements().add(cfg);
}
});
if (im.getCardUrlForCms() == null) {
ParsedUrlForCms cardUrlForCms = new ParsedUrlForCms();

View File

@ -1,74 +1,174 @@
package cn.axzo.msg.center.message.service.card;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.im.center.api.vo.ApiChannel;
import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.YesOrNo;
import cn.axzo.maokai.api.util.Ref;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.domain.entity.Card;
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.CardsForSend;
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.impl.v3.ModelV3Parser;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Service;
import cn.axzo.msg.center.message.service.impl.v3.UrlParser;
import cn.axzo.msg.center.nimpush.NimPushService;
import cn.axzo.msg.center.nimpush.PushPeer;
import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots;
import cn.axzo.msg.center.nimpush.payload.intent.Intent;
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.CardSendState;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.YesOrNo;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.enums.MessageChannel;
import cn.axzo.msg.center.service.pending.request.CardContent;
import cn.axzo.msg.center.service.pending.response.v3.ParsedModel3Visitor;
import cn.axzo.msg.center.service.pending.response.v3.ParsedModelV3Walker;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedTemplateV3;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.Supplier;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
class CardSupport {
public class CardSupport {
public static final String BIZ_ID_PREFIX = "msg-center";
private final ModelV3Service modelV3Service;
private final ModelV3Parser modelV3Parser;
private final CardParser cardParser;
private final NimPushService nimPushService;
CardsForSend buildCards(CardSendRequest request, ParsedModelV3 model) {
ArrayList<Card> cards = new ArrayList<>();
HashSet<PersonAccountAttribute> accounts = new HashSet<>();
public static String getBizIdPrefix(String templateCode) {
return String.format("%s:%s", BIZ_ID_PREFIX, templateCode);
}
void buildCardsForSend(CardSendModel sendModel) {
String batchNo = UUIDUtil.uuidString();
ParsedTemplateV3 template = model.getTemplate();
GeneralMessagePushVO cardContent = cardParser.parseCardContent(model, request);
for (PeerPerson person : request.getReceivers()) {
PersonAccountAttribute account = new PersonAccountAttribute();
if (accounts.contains(account)) continue;
accounts.add(account);
Card card = new Card();
cards.add(card);
card.setBatchNo(batchNo);
//todo
card.setImInitTaskId(0L);
card.setIdentityCode(UUIDUtil.uuidString());
card.setBizCode(request.getBizCode());
card.setSubBizCode(request.getSubBizCode());
card.setTemplateCode(request.getTemplateCode());
card.setSendState(CardSendState.INIT_SEND_QUEUED);
card.setCardState(CardState.PENDING);
card.setTitle(template.getTitle());
card.setContent(template.getContent());
card.setCardContent(cardContent);
card.setBizParam(request.getBizParam());
card.setRouterParam(request.getRouterParam());
card.setSenderAppType(AppTypeEnum.SYSTEM);
card.setIsSenderRobot(YesOrNo.YES);
card.setReceiverAppTypes(template.parsePushAppTypes());
card.setSenderPersonId(request.getSender().getPersonId());
card.setSenderOuId(request.getSender().getOuId());
card.setSenderWorkspaceId(request.getSender().getWorkspaceId());
card.setReceiverPersonId(person.getPersonId());
card.setReceiverOuId(person.getOuId());
card.setReceiverWorkspaceId(person.getWorkspaceId());
//card.setButtonStates();
//card.setStateImage(request.getStateImage());
card.setSubtitle(request.getSubtitle());
//card.setUpdatable();
GeneralMessagePushVO cardContent = cardParser.parseCardContent(
sendModel.cardTemplate(), sendModel.getRequest());
sendModel.setCardContent(cardContent);
for (PeerPerson person : sendModel.getRequest().getReceivers()) {
for (AppTypeEnum appType : sendModel.cardTemplate().ensureAppTypesPresent()) {
Card card = new Card();
sendModel.addCard(card);
card.setBatchNo(batchNo);
card.setAppCode(sendModel.getRequest().getAppCode());
card.setImTaskId(0L);
card.setBizMessageId("");
card.setIdentityCode(UUIDUtil.uuidString());
card.setBizCode(sendModel.getRequest().determineBizCode());
card.setSubBizCode(sendModel.getRequest().getSubBizCode());
card.setTemplateCode(sendModel.getRequest().getTemplateCode());
card.setState(CardState.PENDING);
card.setTitle(sendModel.getParsedModel().getTemplate().getTitle());
card.setContent(sendModel.getParsedModel().getTemplate().getContent());
card.setCardContent(cardContent);
card.setBizParam(sendModel.getRequest().getBizParam());
card.setRouterParam(sendModel.getRequest().getRouterParam());
card.setSenderAppType(AppTypeEnum.SYSTEM);
card.setIsSenderRobot(YesOrNo.YES);
card.setReceiverAppType(appType);
card.setSenderPersonId(sendModel.getRequest().getSender().getPersonId());
card.setSenderOuId(sendModel.getRequest().getSender().getOuId());
card.setSenderWorkspaceId(sendModel.getRequest().getSender().getWorkspaceId());
card.setReceiverPersonId(person.getPersonId());
card.setReceiverOuId(person.getOuId());
card.setReceiverWorkspaceId(person.getWorkspaceId());
card.setSubtitle(sendModel.getRequest().getSubtitle());
card.setButtonStates(new ArrayList<>());
card.setUpdatable(sendModel.cardTemplate().isUpdatable() ? YesOrNo.YES : YesOrNo.NO);
}
}
return new CardsForSend(cards, cardContent);
}
SendTemplateMessageParam buildImSendRequest(CardSendModel sendModel,
CardGroup group,
PushDeviceSnapshots deviceSnapshots,
String imSenderAccount) {
ParsedTemplateV3 template = sendModel.getParsedModel().getTemplate();
Supplier<String> payloadBuilder = () -> {
PushPeer peer = new PushPeer();
peer.setSenderImAccount(imSenderAccount);
peer.setOuId(group.getGroupKey().getOuId());
peer.setWorkspaceId(group.getGroupKey().getWorkspaceId());
peer.setAppType(group.getGroupKey().getAppType());
peer.setApiChannel(ApiChannel.COMMON_MESSAGE);
return nimPushService.buildPayloadString(sendModel.getParsedModel(), peer);
};
Supplier<JSONObject> extBuilder = () -> {
JSONObject ext = new JSONObject();
ext.put("minAppVersion", determineMinAppVersion(
sendModel.getParsedModel(), group.getGroupKey().getAppType()));
if (group.getGroupKey().getOuId() != null)
ext.put("ouId", String.valueOf(group.getGroupKey().getOuId()));
if (group.getGroupKey().getWorkspaceId() != null)
ext.put("workspaceId", String.valueOf(group.getGroupKey().getWorkspaceId()));
return ext;
};
PushData pushData = template.parsePushData();
SendTemplateMessageParam imRequest = new SendTemplateMessageParam();
imRequest.setSender(null);
imRequest.setMsgHeader(template.getTitle());
imRequest.setMsgContent(template.getContent());
imRequest.setMsgTemplateId(template.getCode());
imRequest.setSendPriority(template.determineImSendPriority());
imRequest.setMsgTemplateContent(JSON.toJSONString(sendModel.getCardContent()));
imRequest.setBizId(String.format("%s:%s", getBizIdPrefix(
template.getCode()), sendModel.getRequest().determineBizCode()));
imRequest.setReceivePersons(group.buildReceiverAccounts());
imRequest.setExt(extBuilder.get());
imRequest.setUpdatable(sendModel.cardTemplate().isUpdatable());
if (pushData.determinePushable(log, template.getCode())) {
imRequest.setPayload(payloadBuilder.get());
imRequest.setExcludePushPayloads(group.buildNimPushExcludes(deviceSnapshots));
if (StringUtils.isNotBlank(pushData.getVoiceFile()))
imRequest.getExt().put(Intent.INTENT_SOUND, pushData.getVoiceFile());
}
return imRequest;
}
private String determineMinAppVersion(ParsedModelV3 model,
AppTypeEnum appType) {
Ref<String> minVersion = Ref.create("");
ParsedModelV3Walker.walkDown(model, new ParsedModel3Visitor() {
@Override
public void visitAppVersionConfig(AppVersionConfig cfg) {
if (cfg.getAppType() == appType)
minVersion.set(cfg.getMinVersion());
}
});
return minVersion.get();
}
TemplateModelV3 getTemplateModelOrThrow(String templateCode) {
TemplateModelV3 templateModel = modelV3Service
.findEnabledByCode(templateCode).orElse(null);
BizAssertions.assertNotNull(templateModel, "模板不存在或已下线 {} ", templateCode);
return templateModel;
}
ParsedModelV3 parseModel(TemplateModelV3 model, CardContent card) {
List<MessageChannel> channels = model.getTemplate().determineChannels();
BizAssertions.assertTrue(channels.contains(MessageChannel.IM), "模版未配置IM通道 {} ",
model.getTemplateCode());
return modelV3Parser.parseModel(model, card.getBizParam(), new UrlParser(card.getRouterParam()));
}
}

View File

@ -0,0 +1,53 @@
package cn.axzo.msg.center.message.service.card;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.service.domain.card.StateImageConfig;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedTemplateV3;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.List;
/**
* @author yanglin
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class CardTemplate {
private final ParsedModelV3 parsedModel;
public static CardTemplate wrap(ParsedModelV3 parsedModel) {
return new CardTemplate(parsedModel);
}
public List<AppTypeEnum> ensureAppTypesPresent() {
List<AppTypeEnum> appTypes = parsedModel.getTemplate().parsePushAppTypes();
BizAssertions.assertNotEmpty(appTypes,
"发送IM消息, 消息模版没有配置IM发送终端, templateCode={}", parsedModel.getTemplateCode());
return appTypes;
}
public boolean isUpdatable() {
return getTemplate().getMsgCategory() != MessageCategoryEnum.GENERAL_MESSAGE;
}
public String determineStateImage(CardState state) {
List<StateImageConfig> images = getTemplate().getStateImageConfigs();
if (images == null) return null;
return images.stream()
.filter(config -> config.getCardState() == state)
.findFirst()
.map(StateImageConfig::determineImageUrl)
.orElse(null);
}
public ParsedTemplateV3 getTemplate() {
return parsedModel.getTemplate();
}
}

View File

@ -0,0 +1,42 @@
package cn.axzo.msg.center.message.service.card.broadcast;
import cn.axzo.basics.common.BeanMapper;
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.mq.MqMessageRecord;
import cn.axzo.msg.center.mq.MqProducer;
import cn.axzo.msg.center.service.enums.MqMessageType;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author yanglin
*/
@Component
@RequiredArgsConstructor
public class CardBroadcaster {
private final CardDao cardDao;
private final MqProducer mqProducer;
public void firePresetButtonPressed(
List<Card> cards, Long operatorId, PresetButtonType presetButtonType) {
for (Card card : cardDao.reloadCards(cards)) {
CardPresetButtonPressedMessage message = new CardPresetButtonPressedMessage();
message.setPresetButtonType(presetButtonType);
message.setCardInfo(BeanMapper.copyBean(card, CardInfo.class));
mqProducer.send(MqMessageRecord
.builder(MqMessageType.CARD_PRESET_BUTTON_PRESSED, message)
.messageKey(card.getId())
.operatorId(operatorId)
.shardingKey(card.getTemplateCode())
.build());
}
}
}

View File

@ -0,0 +1,59 @@
package cn.axzo.msg.center.message.service.card.domain;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.api.vo.req.ExcludePushPayload;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.nimpush.PushChannel;
import cn.axzo.msg.center.nimpush.device.PushDevice;
import cn.axzo.msg.center.nimpush.device.PushDeviceSnapshots;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Getter
@RequiredArgsConstructor
public class CardGroup {
private final CardGroupKey groupKey;
private final List<Card> cards = new ArrayList<>();
public void addCard(Card card) {
cards.add(card);
}
public List<PersonAccountAttribute> buildReceiverAccounts() {
return getCards().stream().map(card -> {
PersonAccountAttribute account = new PersonAccountAttribute();
account.setPersonId(card.getReceiverPersonId() + "");
account.setOuId(card.getReceiverOuId());
account.setWorkspaceId(card.getReceiverWorkspaceId());
account.setAppType(groupKey.getAppType());
return account;
}).collect(toList());
}
public List<ExcludePushPayload> buildNimPushExcludes(PushDeviceSnapshots deviceSnapshots) {
AppTypeEnum appType = groupKey.getAppType();
return getCards().stream().map(Card::getReceiverPersonId)
.filter(personId -> {
PushDevice device = deviceSnapshots.getDevice(personId);
return device.shouldPush(appType, PushChannel.NIM);
})
.map(personId -> {
ExcludePushPayload exclude = new ExcludePushPayload();
exclude.setPersonId(personId + "");
exclude.setAppType(appType);
return exclude;
})
.collect(toList());
}
}

View File

@ -0,0 +1,18 @@
package cn.axzo.msg.center.message.service.card.domain;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@Getter
@EqualsAndHashCode
@RequiredArgsConstructor
public class CardGroupKey {
private final Long ouId;
private final Long workspaceId;
private final AppTypeEnum appType;
}

View File

@ -0,0 +1,52 @@
package cn.axzo.msg.center.message.service.card.domain;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import cn.axzo.msg.center.message.service.card.CardTemplate;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
@RequiredArgsConstructor
public class CardSendModel {
private final CardSendRequest request;
private final ParsedModelV3 parsedModel;
private final List<Card> cards = new ArrayList<>();
private GeneralMessagePushVO cardContent;
public CardTemplate cardTemplate() {
return CardTemplate.wrap(parsedModel);
}
public void addCard(Card card) {
cards.add(card);
}
public Collection<CardGroup> getCardGroups() {
HashMap<CardGroupKey, CardGroup> groups = new HashMap<>();
for (Card card : cards) {
CardGroupKey key = new CardGroupKey(
card.getReceiverOuId(),
card.getReceiverWorkspaceId(),
card.getReceiverAppType());
groups.computeIfAbsent(key, CardGroup::new)
.addCard(card);
}
return groups.values();
}
}

View File

@ -1,16 +0,0 @@
package cn.axzo.msg.center.message.service.card.domain;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import lombok.RequiredArgsConstructor;
import java.util.List;
/**
* @author yanglin
*/
@RequiredArgsConstructor
public class CardsForSend {
private final List<Card> cards;
private final GeneralMessagePushVO cardContent;
}

View File

@ -4,6 +4,7 @@ import cn.axzo.msg.center.domain.entity.MessageEntity;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.inside.notices.utils.FunctionalTransactionTemplate;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.domain.UrlConfigVisitor;
@ -13,6 +14,7 @@ import cn.axzo.msg.center.service.domain.url.WebUrl;
import cn.axzo.msg.center.service.enums.GroupType;
import cn.axzo.msg.center.service.enums.KVContentType;
import cn.axzo.msg.center.service.pending.ClientRequest;
import cn.axzo.msg.center.service.pending.card.domain.CardElementConfig;
import cn.axzo.msg.center.service.pending.response.v3.ParsedModel3Visitor;
import cn.axzo.msg.center.service.pending.response.v3.ParsedModelV3Walker;
import cn.axzo.msg.center.service.pending.response.v3.model.ComponentWorkerGroup;
@ -36,6 +38,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@ -54,6 +57,8 @@ public class ModelV3Parser {
private static final String CUSTOMER_PERSON_ID = "customerPersonId";
private final PendingMessageBizConfig cfg;
private final ResourcePatternResolver resourcePatternResolver;
private final FunctionalTransactionTemplate functionalTransactionTemplate;
public ParsedModelV3 parseModelForTodo(TemplateModelV3 templateModel,
MessageEntity entity,
@ -99,8 +104,8 @@ public class ModelV3Parser {
@Override
public void visitTemplate(ParsedTemplateV3 template) {
template.setTitle(getDefaultResolver().resolveByMap(template.getTitle(), bizParam));
template.setContent(getDefaultResolver().resolveByMap(template.getContent(), bizParam));
template.setTitle(resolveBizParam(template.getTitle()));
template.setContent(resolveBizParam(template.getContent()));
}
@Override
@ -126,7 +131,7 @@ public class ModelV3Parser {
@Override
public void visitGroupKeyValue(ParsedGroupV3 group, ParsedKV kv) {
kv.setValue(getDefaultResolver().resolveByMap(kv.getValue(), bizParam));
kv.setValue(resolveBizParam(kv.getValue()));
if (kv.getContentType() == KVContentType.PERSON_ID
&& StringUtils.isNotBlank(kv.getValue())
@ -175,11 +180,25 @@ public class ModelV3Parser {
urlParser.parseUrlConfig(urlConfig);
}
@Override
public void visitCardExtField(CardElementConfig cfg) {
cfg.setValue(resolveBizParam(cfg.getValue()));
}
@Override
public void visitCardElement(CardElementConfig cfg) {
cfg.setValue(resolveBizParam(cfg.getValue()));
}
@Override
public void exitModel(ParsedModelV3 model) {
model.removeEmptyKVGroups();
}
private String resolveBizParam(String template) {
return getDefaultResolver().resolveByMap(template, bizParam);
}
});
return parsedModel;

View File

@ -0,0 +1,152 @@
package cn.axzo.msg.center.api.mq;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.YesOrNo;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.pending.card.domain.CardButtonState;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
public class CardInfo {
private Long id;
/**
* 请求批次号
*/
private String batchNo;
/**
* 调用方标识
*/
private String appCode;
/**
* IM任务id
*/
private Long imTaskId;
/**
* 业务消息ID
*/
private String bizMessageId;
/**
* 卡片编码
*/
private String identityCode;
/**
* 业务编码
*/
private String bizCode;
/**
* 流程类代办的流程结点编码
*/
private String subBizCode;
/**
* 卡片模版编码
*/
private String templateCode;
/**
* 卡片状态. PENDING: 待处理, AGREED: 已同意, REJECTED: 已拒绝, REVOKED: 已撤销, ABORTED: 已中止, COMPLETED: 已处理, END: 已完结, IN_PROGRESS: 进行中
*/
private CardState state;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* IM卡片信息 (解析后)
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private Object cardContent;
/**
* 业务扩展参数
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject bizParam;
/**
* 路由扩展参数
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject routerParam;
/**
* 发送者的端
*/
private AppTypeEnum senderAppType;
/**
* 接收的端
*/
private AppTypeEnum receiverAppType;
/**
* 发送人是否为机器人. YES: , NO:
*/
private YesOrNo isSenderRobot;
/**
* 发起者的自然人ID
*/
private Long senderPersonId;
/**
* 发起人的单位id
*/
private Long senderOuId;
/**
* 发起者项目id
*/
private Long senderWorkspaceId;
/**
* 接收人自然人id
*/
private Long receiverPersonId;
/**
* 接收人单位id
*/
private Long receiverOuId;
/**
* 接收人项目id
*/
private Long receiverWorkspaceId;
/**
* 按钮状态
*/
private List<CardButtonState> buttonStates;
/**
* 副标题
*/
private String subtitle;
}

View File

@ -0,0 +1,29 @@
package cn.axzo.msg.center.api.mq;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* @author yanglin
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CardPresetButtonPressedMessage extends MqMessage implements Serializable {
/**
* 预设按钮类型
* AGREE: 同意
* REJECT: 驳回
* REVOKE: 撤销
*/
private PresetButtonType presetButtonType;
/**
* 待办信息
*/
private CardInfo cardInfo;
}

View File

@ -24,16 +24,16 @@ public class PeerPerson {
/**
* 自然人id
*/
private Long personId;
private Long personId = 0L;
/**
* 单位id
*/
private Long ouId;
private Long ouId = 0L;
/**
* 项目id
*/
private Long workspaceId;
private Long workspaceId = 0L;
}

View File

@ -1,29 +0,0 @@
package cn.axzo.msg.center.service.enums;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum CardSendState implements CodeDefinition<String> {
INIT_SEND_QUEUED("准备发送"),
INIT_SEND_SUCCESS("首次发送成功"),
INIT_SEND_FAIL("首次发送失败"),
UPDATE_SEND_SUCCESS("更新发送成功"),
UPDATE_SEND_FAIL("更新发送失败"),
;
private final String description;
@Override
public String getCode() {
return name();
}
}

View File

@ -14,9 +14,11 @@ import org.apache.commons.lang3.StringUtils;
public enum MqMessageType {
OLD_MSG_SEND("old-msg", "old-msg-send", "发送旧消息"),
TODO_PRESET_BUTTON_PRESSED("todo", "todo-preset-button-pressed", "预设按钮被点击"),
TODO_PRESET_BUTTON_PRESSED("todo", "todo-preset-button-pressed", "待定预设按钮被点击"),
TODO_TEST_MESSAGE("todo", "test-message", "测试消息"),
TODO_STATE_UPDATE("todo", "todo-state-update", "待办状态变更(创建)");
TODO_STATE_UPDATE("todo", "todo-state-update", "待办状态变更(创建)"),
CARD_PRESET_BUTTON_PRESSED("card", "card-preset-button-pressed", "卡片预设按钮被点击"),
;
private final String eventModel;
private final String eventName;

View File

@ -1,9 +1,8 @@
package cn.axzo.msg.center.service.pending.card;
import cn.axzo.msg.center.service.pending.request.CardSendRequest;
import cn.axzo.msg.center.service.pending.request.CardUpdateRequest;
import cn.axzo.msg.center.service.pending.response.CardSendResponse;
import cn.axzo.msg.center.service.pending.response.CardUpdateResponse;
import cn.axzo.msg.center.service.pending.request.CardUpdateStateRequest;
import cn.axzo.msg.center.service.pending.request.CardPresetButtonPressedRequest;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
@ -20,11 +19,19 @@ import javax.validation.Valid;
@FeignClient(value = "msg-center", url = "${server.serviceUrl:http://msg-center:8080}")
public interface CardClient {
@PostMapping(value = "/card/send", produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<CardSendResponse> send(@RequestBody @Valid CardSendRequest request);
@PostMapping(value = "/card/send",
produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<Void> send(
@RequestBody @Valid CardSendRequest request);
@PostMapping(value = "/card/updateState",
produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<Void> updateState(
@RequestBody @Valid CardUpdateStateRequest request);
@PostMapping(value = "/card/update", produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<CardUpdateResponse> update(@RequestBody @Valid CardUpdateRequest request);
@PostMapping(value = "/card/firePresetButtonPressed",
produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<Void> firePresetButtonPressed(
@RequestBody @Valid CardPresetButtonPressedRequest request);
}

View File

@ -1,4 +1,4 @@
package cn.axzo.msg.center.domain.entity;
package cn.axzo.msg.center.service.pending.card.domain;
import lombok.Getter;
import lombok.Setter;

View File

@ -0,0 +1,42 @@
package cn.axzo.msg.center.service.pending.card.domain;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author yanglin
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class CardButtonStates {
private final List<CardButtonState> states;
public static CardButtonStates create(List<CardButtonState> buttonStates) {
return new CardButtonStates(new ArrayList<>(
buttonStates == null ? Collections.emptyList(): buttonStates));
}
public void setActionPerformed(String buttonCode) {
states.removeIf(s -> s.getButtonCode().equals(buttonCode));
CardButtonState state = new CardButtonState();
state.setButtonCode(buttonCode);
state.setIsActionPerformed(true);
states.add(state);
}
public boolean isButtonActionPerformed(String buttonCode) {
return states.stream()
.anyMatch(state -> {
boolean isTheButton = state.getButtonCode().equals(buttonCode);
boolean actionPerformed = state.determineIsActionPerformed();
return isTheButton && actionPerformed;
});
}
}

View File

@ -1,16 +1,27 @@
package cn.axzo.msg.center.service.pending.request;
import cn.axzo.msg.center.service.enums.CardState;
import com.alibaba.fastjson.JSONObject;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
public interface CardContent {
CardState getCardState();
String getTemplateCode();
CardState getState();
boolean isButtonActionPerformed(String buttonCode);
String getSubtitle();
@NotNull
JSONObject getBizParam();
@NotNull
JSONObject getRouterParam();
}

View File

@ -0,0 +1,36 @@
package cn.axzo.msg.center.service.pending.request;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class CardPresetButtonPressedRequest implements CardPresetButtonRequest {
/**
* 业务消息id
*/
@NotBlank
private String bizMessageId;
/**
* 预设按钮类型
* AGREE: 同意
* REJECT: 驳回
* REVOKE: 撤销
*/
@NotNull
private PresetButtonType presetButtonType;
private Long operatorId;
private Long operatorName;
}

View File

@ -0,0 +1,14 @@
package cn.axzo.msg.center.service.pending.request;
import cn.axzo.msg.center.service.enums.PresetButtonType;
/**
* @author yanglin
*/
public interface CardPresetButtonRequest {
PresetButtonType getPresetButtonType();
Long getOperatorId();
}

View File

@ -19,6 +19,12 @@ import java.util.Set;
@Getter
public class CardSendRequest implements CardContent {
/**
* 调用方应用编码或场景编码, 保证这个场景下bizCode唯一
*/
@NotBlank(message = "应用编码不能为空")
private String appCode;
/**
* 模版编码
*/
@ -64,7 +70,7 @@ public class CardSendRequest implements CardContent {
private String subtitle;
@Override
public CardState getCardState() {
public CardState getState() {
return CardState.PENDING;
}
@ -73,4 +79,18 @@ public class CardSendRequest implements CardContent {
return false;
}
@Override
public JSONObject getBizParam() {
return bizParam == null ? new JSONObject() : bizParam;
}
@Override
public JSONObject getRouterParam() {
return routerParam == null ? new JSONObject() : routerParam;
}
public String determineBizCode() {
return bizCode == null ? "" : bizCode;
}
}

View File

@ -0,0 +1,27 @@
package cn.axzo.msg.center.service.pending.request;
import cn.axzo.msg.center.service.enums.PresetButtonType;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class CardUpdatePresetButtonRequest extends CardUpdateRequest implements CardPresetButtonRequest {
/**
* 预设按钮类型
* AGREE: 同意
* REJECT: 驳回
* REVOKE: 撤销
*/
@NotNull
private PresetButtonType presetButtonType;
private Long operatorId;
}

View File

@ -1,12 +1,46 @@
package cn.axzo.msg.center.service.pending.request;
import cn.axzo.msg.center.service.dto.PeerPerson;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.util.HashSet;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class CardUpdateRequest {
}
/**
* 调用方应用编码或场景编码, 保证这个场景下bizCode唯一
*/
@NotBlank(message = "应用编码不能为空")
private String appCode;
/**
* 模版编码
*/
@NotBlank(message = "模版编码不能为空")
private String templateCode;
/**
* 业务编码
*/
@NotBlank(message = "业务编码不能为空")
private String bizCode;
/**
* 子业务编码
*/
private String subBizCode;
/**
* 接收人, 可选
*/
private Set<PeerPerson> receivers = new HashSet<>();
}

View File

@ -0,0 +1,22 @@
package cn.axzo.msg.center.service.pending.request;
import cn.axzo.msg.center.service.enums.CardState;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class CardUpdateStateRequest extends CardUpdateRequest {
/**
* 卡片状态
*/
@NotNull(message = "卡片状态不能为空")
private CardState cardState;
}

View File

@ -1,12 +0,0 @@
package cn.axzo.msg.center.service.pending.response;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class CardSendResponse {
}

View File

@ -8,5 +8,5 @@ import lombok.Setter;
*/
@Setter
@Getter
public class CardUpdateResponse {
public class ImPresetButtonPressedResponse {
}

View File

@ -1,13 +1,14 @@
package cn.axzo.msg.center.service.pending.response.v3.model;
import cn.axzo.im.center.api.feign.SendPriority;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.push.PushData;
import cn.axzo.msg.center.service.domain.CardUrlConfig;
import cn.axzo.msg.center.service.domain.card.AppVersionConfig;
import cn.axzo.msg.center.service.domain.card.StateImageConfig;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.CardUrlOpenStrategy;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.MessageChannel;
import cn.axzo.msg.center.service.enums.StatusEnum;
import cn.axzo.msg.center.service.enums.YesOrNo;
import cn.axzo.msg.center.service.pending.card.domain.CardElementConfig;
@ -129,13 +130,10 @@ public class ParsedTemplateV3 {
*/
private List<StateImageConfig> stateImageConfigs;
public String determineStateImage(CardState state) {
return stateImageConfigs.stream()
.filter(config -> config.getCardState() == state)
.findFirst()
.map(StateImageConfig::determineImageUrl)
.orElse(null);
}
/**
* 通道
*/
private List<MessageChannel> channels;
public List<AppTypeEnum> parsePushAppTypes() {
if (StringUtils.isBlank(pushTerminal))
@ -157,6 +155,10 @@ public class ParsedTemplateV3 {
return cardUrl;
}
public Integer determineImSendPriority() {
return imSendPriority == null ? SendPriority.TEMPLATE_MESSAGE.getPriority() : imSendPriority;
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -1,10 +1,23 @@
package cn.axzo.msg.center.dal;
import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
import cn.axzo.im.center.api.vo.resp.UpdatableMessageSendResult;
import cn.axzo.msg.center.dal.mapper.CardMapper;
import cn.axzo.msg.center.domain.entity.Card;
import cn.axzo.msg.center.domain.persistence.BaseEntityExt;
import cn.axzo.msg.center.service.enums.CardState;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
@ -12,4 +25,58 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class CardDao extends ServiceImpl<CardMapper, Card> {
public void updateStates(List<Card> cards, CardState state) {
List<Long> cardIds = cards.stream()
.map(BaseEntityExt::getId)
.collect(Collectors.toList());
lambdaUpdate()
.in(Card::getId, cardIds)
.set(Card::getState, state)
.update();
}
public List<Card> collectPersonCards(String oneOfBizMessageId) {
Card card = findCardByBizMessageId(oneOfBizMessageId).orElse(null);
if (card == null) return Collections.emptyList();
return lambdaQuery()
.eq(Card::getAppCode, card.getAppCode())
.eq(Card::getTemplateCode, card.getTemplateCode())
.eq(Card::getBizCode, card.getBizCode())
.eq(Card::getSubBizCode, card.getSubBizCode())
.eq(Card::getReceiverOuId, card.getReceiverOuId())
.eq(Card::getReceiverWorkspaceId, card.getReceiverWorkspaceId())
.eq(Card::getReceiverPersonId, card.getReceiverPersonId())
.list();
}
public Optional<Card> findCardByBizMessageId(String bizMessageId) {
return Optional.ofNullable(lambdaQuery()
.eq(Card::getBizMessageId, bizMessageId)
.one());
}
public List<Card> reloadCards(List<Card> cards) {
List<Long> cardIds = cards.stream().map(BaseEntityExt::getId).collect(toList());
return listByIds(cardIds);
}
@Transactional
public void setSendSuccess(List<Card> cards, MessageTaskResp imResponse) {
ArrayList<Card> updates = new ArrayList<>();
List<UpdatableMessageSendResult> sendResults = imResponse.getUpdatableMessageSendResults();
for (int i = 0; i < cards.size(); i++) {
Card card = cards.get(i);
Card update = new Card();
update.setId(card.getId());
update.setImTaskId(imResponse.getId());
if (sendResults != null && i < sendResults.size()) {
UpdatableMessageSendResult sendResult = sendResults.get(i);
update.setBizMessageId(sendResult.getBizMessageId());
}
updates.add(update);
}
updateBatchById(updates);
}
}

View File

@ -1,15 +0,0 @@
package cn.axzo.msg.center.dal;
import cn.axzo.msg.center.dal.mapper.CardMessageMapper;
import cn.axzo.msg.center.domain.entity.CardMessage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author yanglin
*/
@Slf4j
@Component
public class CardMessageDao extends ServiceImpl<CardMessageMapper, CardMessage> {
}

View File

@ -1,10 +0,0 @@
package cn.axzo.msg.center.dal.mapper;
import cn.axzo.msg.center.domain.entity.CardMessage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author yanglin
*/
public interface CardMessageMapper extends BaseMapper<CardMessage> {
}

View File

@ -2,11 +2,12 @@ package cn.axzo.msg.center.domain.entity;
import cn.axzo.foundation.dao.support.mysql.type.BaseListTypeHandler;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.YesOrNo;
import cn.axzo.msg.center.domain.persistence.BaseEntityExt;
import cn.axzo.msg.center.domain.utils.IgnorePropsJsonTypeHandler;
import cn.axzo.msg.center.service.enums.CardSendState;
import cn.axzo.msg.center.service.enums.CardState;
import cn.axzo.msg.center.service.enums.YesOrNo;
import cn.axzo.msg.center.service.pending.card.domain.CardButtonState;
import cn.axzo.msg.center.service.pending.card.domain.CardButtonStates;
import cn.axzo.msg.center.service.pending.request.CardContent;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
@ -32,10 +33,20 @@ public class Card extends BaseEntityExt<Card> implements CardContent {
*/
private String batchNo;
/**
* 调用方标识
*/
private String appCode;
/**
* IM任务id
*/
private Long imInitTaskId;
private Long imTaskId;
/**
* 业务消息ID
*/
private String bizMessageId;
/**
* 卡片编码
@ -57,15 +68,10 @@ public class Card extends BaseEntityExt<Card> implements CardContent {
*/
private String templateCode;
/**
* 卡片发送状态. INIT_SEND_QUEUED: 准备发送; INIT_SEND_SUCCESS: 首次发送成功; INIT_SEND_FAIL: 首次发送成功; UPDATE_SEND_SUCCESS: 更新发送成功; UPDATE_SEND_FAIL: 更新发送失败;
*/
private CardSendState sendState;
/**
* 卡片状态. PENDING: 待处理, AGREED: 已同意, REJECTED: 已拒绝, REVOKED: 已撤销, ABORTED: 已中止, COMPLETED: 已处理, END: 已完结, IN_PROGRESS: 进行中
*/
private CardState cardState;
private CardState state;
/**
* 标题
@ -103,8 +109,7 @@ public class Card extends BaseEntityExt<Card> implements CardContent {
/**
* 接收的端
*/
@TableField(typeHandler = AppTypeHandler.class)
private List<AppTypeEnum> receiverAppTypes;
private AppTypeEnum receiverAppType;
/**
* 发送人是否为机器人. YES: , NO:
@ -147,11 +152,6 @@ public class Card extends BaseEntityExt<Card> implements CardContent {
@TableField(typeHandler = CardButtonStateHandler.class)
private List<CardButtonState> buttonStates;
/**
* 状态戳
*/
private String stateImage;
/**
* 副标题
*/
@ -170,12 +170,17 @@ public class Card extends BaseEntityExt<Card> implements CardContent {
@Override
public boolean isButtonActionPerformed(String buttonCode) {
return buttonStates.stream()
.anyMatch(state -> {
boolean isTheButton = state.getButtonCode().equals(buttonCode);
boolean actionPerformed = state.determineIsActionPerformed();
return isTheButton && actionPerformed;
});
return CardButtonStates.create(buttonStates).isButtonActionPerformed(buttonCode);
}
@Override
public JSONObject getBizParam() {
return bizParam;
}
@Override
public JSONObject getRouterParam() {
return routerParam;
}
@Setter
@ -184,8 +189,6 @@ public class Card extends BaseEntityExt<Card> implements CardContent {
}
// @formatter:off
public static class AppTypeHandler
extends BaseListTypeHandler<AppTypeEnum> {}
public static class CardButtonStateHandler
extends BaseListTypeHandler<CardButtonState> {}
// @formatter:on

View File

@ -2,6 +2,7 @@ package cn.axzo.msg.center.domain.entity;
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 com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
@ -22,6 +23,11 @@ public class CardLog extends BaseEntityExt<CardLog> {
*/
private String identityCode;
/**
* 业务消息ID
*/
private String bizMessageId;
/**
* 模版编码
*/
@ -57,7 +63,7 @@ public class CardLog extends BaseEntityExt<CardLog> {
* 日志内容
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private Object logContent;
private JSONObject logContent;
/**
* 扩展字段
@ -65,6 +71,12 @@ public class CardLog extends BaseEntityExt<CardLog> {
@TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private Card.RecordExt recordExt;
public void addLogContent(String name, Object value) {
if (logContent == null)
logContent = new JSONObject();
logContent.put(name, value);
}
@Setter
@Getter
public static class RecordExt {

View File

@ -1,78 +0,0 @@
package cn.axzo.msg.center.domain.entity;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.domain.persistence.BaseEntityExt;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* @author yanglin
*/
@Setter
@Getter
@Accessors(chain = true)
@TableName(value = "card_message", autoResultMap = true)
public class CardMessage extends BaseEntityExt<CardMessage> {
/**
* 卡片id
*/
private Long cardId;
/**
* 消息业务id
*/
private String bizMessageId;
/**
* 业务编码
*/
private String bizCode;
/**
* 消息卡片id
*/
private String cardIdentityCode;
/**
* 样式模版code
*/
private String templateCode;
/**
* 发起者的自然人ID
*/
private Long senderPersonId;
/**
* 发送者单位id
*/
private Long senderOuId;
/**
* 发送者项目id
*/
private Long senderWorkspaceId;
/**
* 接收人自然人id
*/
private Long receiverPersonId;
/**
* 接收者单位id
*/
private Long receiveOuId;
/**
* 接收者项目id
*/
private Long receiveWorkspaceId;
/**
* 终端类型. CM: 工人端, CMP: 管理端
*/
private AppTypeEnum appType;
}

View File

@ -148,6 +148,10 @@ public class MessageTemplateV3 extends BaseEntityWithOperator<MessageTemplateV3>
@TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private RecordExt recordExt;
public List<MessageChannel> determineChannels() {
return channels == null ? Collections.emptyList() : channels;
}
public PushData parsePushData() {
JSONObject pushData = this.pushData;
if (pushData == null)