REQ-3284: 推送

This commit is contained in:
yanglin 2024-11-19 11:28:05 +08:00
parent 91e52e462b
commit 771a1ac4e2
35 changed files with 672 additions and 170 deletions

View File

@ -5,15 +5,17 @@ import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.msg.center.api.request.MsgBody4Guest;
import cn.axzo.msg.center.dal.MessageRecordV3Dao;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
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.config.PendingMessageBizConfig;
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.MessageTemplateDTO;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
import cn.axzo.msg.center.nimpush.PushChannel;
import cn.axzo.msg.center.push.PushData;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
@ -44,6 +46,8 @@ public class PushYouMengMessageHandler implements EventHandler, InitializingBean
private MessageTemplateNewService messageTemplateNewService;
@Autowired
private IYouMengMessageService youMengMessageService;
@Autowired
private PendingMessageBizConfig cfg;
/**
* 按照这个顺序解析routerType
@ -76,6 +80,7 @@ public class PushYouMengMessageHandler implements EventHandler, InitializingBean
@Override
public void onEvent(Event event, EventConsumer.Context context) {
if (cfg.getPushChannel() != PushChannel.YOU_MENG) return;
log.info("begin push-handler rocketmq event: {}", event);
MessageHistoryUpdatedPayload payload = event.normalizedData(MessageHistoryUpdatedPayload.class);
if (Objects.isNull(payload) || StringUtils.isBlank(payload.getNewMessageHistory().getBizId())) {
@ -115,7 +120,7 @@ public class PushYouMengMessageHandler implements EventHandler, InitializingBean
return;
}
MessageBaseTemplate.PushData pushData = messageTemplateDTO.get().getPushData().toJavaObject(MessageBaseTemplate.PushData.class);
PushData pushData = messageTemplateDTO.get().getPushData().toJavaObject(PushData.class);
if (BooleanUtils.isNotTrue(pushData.isSwitchOn())) {
log.info("push-handler, 模板code:{},push开关未打开", messageRecordV3.getTemplateCode());
@ -140,7 +145,7 @@ public class PushYouMengMessageHandler implements EventHandler, InitializingBean
}
private String resolveM2(MessageHistoryUpdatedPayload.MessageBody messageBody,
MessageBaseTemplate.PushData pushData) {
PushData pushData) {
JSONObject jsonObject = new JSONObject()
.fluentPut("t", messageBody.getMsgHeader())
.fluentPut("type", 0);

View File

@ -3,6 +3,7 @@ package cn.axzo.msg.center.inside.notices.config;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.msg.center.api.enums.MsgTempBizCategoryEnum;
import cn.axzo.msg.center.api.response.PendingMessageTemporarilyTypeRes;
import cn.axzo.msg.center.nimpush.PushChannel;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import cn.axzo.msg.center.service.template.response.MessageDetailStyle;
import com.alibaba.fastjson.JSON;
@ -163,6 +164,12 @@ public class PendingMessageBizConfig {
@Getter
private String todoDetailUrl = "__UNI__57713166#/todoDetail";
/**
* 推送渠道
*/
@Getter
private PushChannel pushChannel = PushChannel.NIM;
public boolean determineOldMsgStatCacheOn() {
return isOldMsgStatCacheOn();
}

View File

@ -1,5 +1,6 @@
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;
@ -52,17 +53,33 @@ public class MessageMappingProcessor implements EventMappingProcessor {
.toImTypes(getTemplate().determinePushTerminals());
BizAssertions.assertNotEmpty(appTypes, "发送IM消息, 消息模版没有配置IM发送终端, templateCode={}, templateName={}",
getTemplate().getCode(), getTemplate().getName());
SendTemplateMessageParam request = template.buildImRequest(templateParser, appTypes);
String cmTaskId = null;
String cmpTaskId = null;
try {
for (AppTypeEnum appType : appTypes) {
String taskId = sendImpl(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(AppTypeEnum appType) {
SendTemplateMessageParam request = template.buildImRequest(templateParser, appType);
ApiResult<MessageTaskResp> apiResult = messageApi.sendTemplateMessageAsync(request);
log.info("sending im message result, req={}, resp={}",
JSON.toJSONString(request), JSON.toJSONString(apiResult));
if (apiResult.isSuccess()) {
MessageTaskResp resp = apiResult.getData();
messageRecordV3Dao.setSendSuccess(template.getMessageIds(), resp.getId() + "");
return apiResult.getData().getId() + "";
} else {
messageRecordV3Dao.batchSetSendFailed(template.getMessageIds(), apiResult.getMsg());
log.warn("sending im message fail, req={}, resp={}",
JSON.toJSONString(request), JSON.toJSONString(apiResult));
throw new ServiceException(apiResult.getMsg());
}
}

View File

@ -12,14 +12,18 @@ 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.impl.v3.ModelV3Parser;
import cn.axzo.msg.center.nimpush.NimPushService;
import cn.axzo.msg.center.service.dto.PersonV3DTO;
import cn.axzo.msg.center.service.enums.Channel;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import java.util.ArrayList;
import java.util.Collection;
@ -46,6 +50,7 @@ public class TemplateMessage {
private final Channel channel;
@Getter
private final TemplateModelV3 templateModel;
private final ApplicationContext beanFactory;
private List<MessageRecordV3> records;
private String title;
@ -97,8 +102,8 @@ public class TemplateMessage {
message.setSubtitle(sendBasicInfo.getSubtitle());
message.setState(MsgStateV3Enum.UNSENT);
message.setBizCode(sendBasicInfo.determineBizCode());
message.setRouterParams(sendBasicInfo.getRouterParams());
message.setBizExtParams(sendBasicInfo.getBizExtParams());
message.setRouterParams(sendBasicInfo.determineRouterParams());
message.setBizExtParams(sendBasicInfo.determineBizExtParams());
message.setMsgExtInfo(getMsgExtInfo());
message.setFailCause(null);
message.setCreateAt(new Date());
@ -113,7 +118,7 @@ public class TemplateMessage {
return String.format("%s:%s", BIZ_ID_PREFIX, templateCode);
}
SendTemplateMessageParam buildImRequest(MessageTemplateParserV3 templateParser, List<AppTypeEnum> apps) {
SendTemplateMessageParam buildImRequest(MessageTemplateParserV3 templateParser, AppTypeEnum appType) {
MessageRecordV3 sample = getMessageRecords().get(0);
GeneralMessagePushVO sendVo = templateParser.parse(sample, templateModel);
@ -132,15 +137,17 @@ public class TemplateMessage {
for (PersonV3DTO receiver : sendBasicInfo.receivers()) {
PersonV3DTO.ReceiveModel imReceiveModel = receiver.getImReceiveModel();
Long ouId = imReceiveModel == null ? sendBasicInfo.determineReceiversOuId() : imReceiveModel.getOuId();
for (AppTypeEnum app : apps) {
if (app == AppTypeEnum.CM && !cmUnique.add(receiver.getId())) {
continue;
}
if (app == AppTypeEnum.CMP && !cmpUnique.add(new OuAndPerson(ouId, receiver.getId()))) {
continue;
}
receivers.add(new ReceivePerson(String.valueOf(receiver.getId()), ouId, app));
if (appType == AppTypeEnum.CM && !cmUnique.add(receiver.getId())) {
continue;
}
if (appType == AppTypeEnum.CMP && !cmpUnique.add(new OuAndPerson(ouId, receiver.getId()))) {
continue;
}
receivers.add(new ReceivePerson(String.valueOf(receiver.getId()), ouId, appType));
if (appType == AppTypeEnum.CM)
cmUnique.add(receiver.getId());
else if (appType == AppTypeEnum.CMP)
cmpUnique.add(new OuAndPerson(ouId, receiver.getId()));
}
imReq.setReceivePersons(receivers);
// 扩展信息
@ -153,9 +160,17 @@ public class TemplateMessage {
ext.put("ouId", String.valueOf(sample.getReceiverOuId()));
}
imReq.setExt(ext);
imReq.setPayload(buildPayload(sample, appType));
return imReq;
}
private String buildPayload(MessageRecordV3 sample, AppTypeEnum appType) {
ModelV3Parser modelV3Parser = beanFactory.getBean(ModelV3Parser.class);
ParsedModelV3 parsedModelV3 = modelV3Parser.parseModelNoRequestContext(templateModel, sample);
NimPushService nimPushService = beanFactory.getBean(NimPushService.class);
return nimPushService.buildPayloadString(parsedModelV3, appType);
}
// ------------------------------- 辅助方法
String parseTitle() {

View File

@ -56,7 +56,8 @@ public class MessageServiceV4Impl implements MessageServiceV4 {
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));
imProcessor.setTemplate(new TemplateMessage(
request, sendRequestNo, info.getChannel(), templateModel, beanFactory));
processors.add(imProcessor);
} else if (info.getChannel() == Channel.PENDING) {
// @Scope("prototype") -> we're good

View File

@ -1,33 +0,0 @@
package cn.axzo.msg.center.message.controller;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.msg.center.event.outer.PushYouMengMessageHandler;
import cn.axzo.msg.center.message.xxl.UpdateOuIdJob;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class PrivateJobController {
@Autowired
private UpdateOuIdJob updateOuIdJob;
@Autowired
private PushYouMengMessageHandler pushYouMengMessageHandler;
@PostMapping("private/ou/update")
public Object updateOuIdJob(@RequestBody @Valid UpdateOuIdJob.UpdateOuIdParam req) throws Exception {
return updateOuIdJob.execute(JSONObject.toJSONString(req));
}
@PostMapping("private/you-meng/push")
public Object pushYouMeng(@RequestBody @Valid Event event) throws Exception {
pushYouMengMessageHandler.onEvent(event,null);
return null;
}
}

View File

@ -1,17 +1,17 @@
package cn.axzo.msg.center.message.controller;
import cn.axzo.msg.center.api.MNSNoticesApi;
import cn.axzo.msg.center.api.MessageAPIV3;
import cn.axzo.msg.center.api.MessageAPIV4;
import cn.axzo.msg.center.api.request.AddMnsChannelRequest;
import cn.axzo.msg.center.api.request.MnsSendCodeV2Req;
import cn.axzo.msg.center.api.request.SendMessageRequestDto;
import cn.axzo.msg.center.api.request.UpdateMnsTemplateContentRequest;
import cn.axzo.msg.center.api.request.v3.MessageSendReqV3;
import cn.axzo.msg.center.api.request.v3.SearchMessageReqV3;
import cn.axzo.msg.center.api.request.v3.SearchPendingMessageReq;
import cn.axzo.msg.center.api.request.v3.SearchTodoLogReq;
import cn.axzo.msg.center.api.request.v3.SetImSendPriorityRequest;
import cn.axzo.msg.center.api.request.v3.UpdateMnsChannelTemplateRequest;
import cn.axzo.msg.center.api.request.v4.MessageSendRequestV4;
import cn.axzo.msg.center.dal.MNSMessageTemplateDao;
import cn.axzo.msg.center.domain.entity.MNSMessageTemplate;
import cn.axzo.msg.center.im.service.IMService;
@ -60,7 +60,7 @@ import static java.util.stream.Collectors.toMap;
public class PrivateMessageController {
private final MessageRecordServiceV3 messageRecordServiceV3;
private final MessageAPIV3 messageAPIV3;
private final MessageAPIV4 messageAPIV4;
private final TodoSearchService todoSearchService;
private final TodoManager todoManager;
private final GroupTemplateService groupTemplateService;
@ -88,8 +88,8 @@ public class PrivateMessageController {
@PostMapping("/sendByEventMapping")
@EnableResponseAdvice(enable = false)
public Object sendByEventMapping(@RequestBody @Valid MessageSendReqV3 request) {
return messageAPIV3.send(request);
public Object sendByEventMapping(@RequestBody @Valid MessageSendRequestV4 request) {
return messageAPIV4.send(request);
}
@PostMapping("/searchImRecord")

View File

@ -64,6 +64,13 @@ public class ModelV3Parser {
return copy;
}
public ParsedModelV3 parseModelNoRequestContext(
TemplateModelV3 templateModel, MessageEntity entity) {
return parseModel(templateModel, entity,
entity.bizParam(), entity.routerParam(),
null, null);
}
public ParsedModelV3 parseModel(TemplateModelV3 templateModel,
MessageEntity entity,
JSONObject bizParam,
@ -74,6 +81,12 @@ public class ModelV3Parser {
UrlParser urlParser = new UrlParser(entity, routerParam, appendRouterParam);
ParsedModelV3Walker.walkDown(parsedModel, new ParsedModel3Visitor() {
@Override
public void visitTemplate(ParsedTemplateV3 template) {
template.setTitle(getDefaultResolver().resolveByMap(template.getTitle(), bizParam));
template.setContent(getDefaultResolver().resolveByMap(template.getContent(), bizParam));
}
@Override
public void visitTemplateCardUrlConfig(UrlConfig urlConfig) {
if (!urlConfig.hasUrl()) return;

View File

@ -253,6 +253,16 @@ public class TodoRecordAdapter implements PendingRecordAdapter {
return business;
}
@Override
public JSONObject routerParam() {
return todo.routerParam();
}
@Override
public JSONObject bizParam() {
return todo.bizParam();
}
@Override
public ProposedButtons getCustomButtons() {
return forBusiness ? business.getProposedButtons() : todo.getProposedButtons();

View File

@ -4,6 +4,7 @@ import cn.axzo.msg.center.dal.TodoBusinessDao;
import cn.axzo.msg.center.dal.TodoBusinesses;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.domain.entity.TodoBusiness;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.message.domain.dto.TemplateModelV3;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
import cn.axzo.msg.center.message.service.impl.v3.NativeAppLinkUrlConfigVisitor;
@ -11,6 +12,7 @@ import cn.axzo.msg.center.message.service.impl.v3.ModelV3Parser;
import cn.axzo.msg.center.message.service.todo.manage.TodoExt;
import cn.axzo.msg.center.message.service.youmeng.YoumengPush;
import cn.axzo.msg.center.message.service.youmeng.YoumengTemplateClient;
import cn.axzo.msg.center.nimpush.PushChannel;
import cn.axzo.msg.center.service.domain.CardUrlConfig;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.domain.UrlConfigWalker;
@ -39,9 +41,11 @@ class TodoPushSender implements ApplicationListener<NewTodoEvent> {
private final YoumengTemplateClient youmengTemplateClient;
private final TodoBusinessDao todoBusinessDao;
private final ModelV3Parser modelV3Parser;
private final PendingMessageBizConfig cfg;
@Override
public void onApplicationEvent(@NotNull NewTodoEvent event) {
if (cfg.getPushChannel() != PushChannel.YOU_MENG) return;
log.info("Prepare sending todo push. event={}", event);
ArrayList<YoumengPush> pushes = new ArrayList<>();
for (Todo todo : event.getTodos()) {

View File

@ -0,0 +1,83 @@
package cn.axzo.msg.center.message.service.todo.manage.event;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.framework.web.redirect.RedirectSetting;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
import cn.axzo.im.center.api.vo.resp.MessageCustomResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.domain.entity.Todo;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.inside.notices.service.impl.v3.msg.TerminalAppMapping;
import cn.axzo.msg.center.message.service.impl.v3.ModelV3Parser;
import cn.axzo.msg.center.nimpush.NimPushService;
import cn.axzo.msg.center.nimpush.PushChannel;
import cn.axzo.msg.center.nimpush.PushExecutorConfig;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.service.pending.response.v3.model.ParsedModelV3;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TodoPushSenderV3 implements ApplicationListener<NewTodoEvent> {
private final PendingMessageBizConfig cfg;
private final NimPushService nimPushService;
private final MessageApi messageApi;
private final ModelV3Parser modelV3Parser;
private final TerminalAppMapping terminalAppMapping;
@Qualifier(PushExecutorConfig.PUSH_EXECUTOR)
private final ExecutorService executor;
@Override
public void onApplicationEvent(NewTodoEvent event) {
if (cfg.getPushChannel() != PushChannel.NIM) return;
List<PushTerminalEnum> pushTerminalEnums = event
.getTemplateModel().getTemplate().determinePushTerminals();
List<AppTypeEnum> appTypes = terminalAppMapping.toImTypes(pushTerminalEnums);
if (CollectionUtils.isEmpty(appTypes)) return;
ParsedModelV3 parsedModelV3 = modelV3Parser
.parseModelNoRequestContext(
event.getTemplateModel(), event.getTodos().get(0));
for (Todo todo : event.getTodos()) {
RedirectSetting redirectSetting = RedirectSetting.find().orElse(null);
executor.submit(() -> {
RedirectSetting.setIfAbsent(redirectSetting);
for (AppTypeEnum appType : appTypes) {
String payload = nimPushService.buildPayloadString(parsedModelV3, appType);
CustomMessageInfo pushRequest = new CustomMessageInfo();
pushRequest.setAppTypeList(Collections.singletonList(appType));
pushRequest.setToPersonId(todo.getExecutorPersonId() + "");
pushRequest.setOuId(todo.getOuId());
pushRequest.setPayload(payload);
pushRequest.setPush(true);
ApiResult<List<MessageCustomResp>> pushResponse = null;
try {
pushResponse = messageApi.sendCustomMessage(pushRequest);
} catch (Exception e) {
log.warn("Nim push error", e);
} finally {
log.info("Nim push request={}, response={}",
JSON.toJSONString(pushRequest), JSON.toJSONString(pushResponse));
}
}
RedirectSetting.remove();
});
}
}
}

View File

@ -3,27 +3,25 @@ package cn.axzo.msg.center.message.service.youmeng;
import cn.axzo.msg.center.api.request.MsgBody4Guest;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.mapper.MessageTemplateV3Mapper;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate.PushData;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.event.outer.PushYouMengMessageHandler;
import cn.axzo.msg.center.inside.notices.service.IYouMengMessageService;
import cn.axzo.msg.center.message.service.impl.v3.AppLink;
import cn.axzo.msg.center.nimpush.PushExecutorConfig;
import cn.axzo.msg.center.push.PushData;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.taobao.api.internal.util.NamedThreadFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static java.util.stream.Collectors.toList;
@ -38,12 +36,8 @@ public class YoumengTemplateClient {
private final IYouMengMessageService youMengMessageService;
private final PushYouMengMessageHandler pushYouMengMessageHandler;
private final MessageTemplateV3Mapper messageTemplateV3Mapper;
private final ExecutorService executor = new ThreadPoolExecutor(
30, 50,
10, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(10000),
new NamedThreadFactory(getClass().getSimpleName()));
@Qualifier(PushExecutorConfig.PUSH_EXECUTOR)
private final ExecutorService executor;
public void asyncSend(Long templateId, List<YoumengPush> pushes) {
try {

View File

@ -0,0 +1,94 @@
package cn.axzo.msg.center.nimpush;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.nimpush.payload.PushPayloadBuilder;
import cn.axzo.msg.center.push.PushData;
import cn.axzo.msg.center.push.PushMessage;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.domain.UrlConfigVisitor;
import cn.axzo.msg.center.service.domain.UrlConfigWalker;
import cn.axzo.msg.center.service.domain.url.AppUrl;
import cn.axzo.msg.center.service.enums.CodeDefinition;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
import javax.annotation.Nullable;
/**
* @author yanglin
*/
@Service
@RequiredArgsConstructor
public class NimPushService {
private final ObjectProvider<PushPayloadBuilder[]> payloadBuilderProvider;
private final PendingMessageBizConfig cfg;
public String buildPayloadString(PushMessage message, AppTypeEnum appType) {
JSONObject payload = buildPayload(message, appType);
return payload == null ? null : payload.toJSONString();
}
@Nullable
public JSONObject buildPayload(PushMessage message, AppTypeEnum appType) {
if (cfg.getPushChannel() != PushChannel.NIM) return null;
PushData pushData = message.getPushData();
if (pushData == null || !pushData.isSwitchOn()) return null;
PushMessageContent content = new PushMessageContent();
content.setTitle(message.getPushTitle());
content.setSubtitle(message.getPushSubtitle());
content.setCustomSoundFile(pushData.getVoiceFile());
content.setMessageTye(CodeDefinition
.findByCode(PushMessageTye.class, pushData.getType())
.orElse(null));
content.setCustomSoundFile(pushData.getVoiceFile());
populateUrl(content, message.getPushUrl(), appType);
return buildPayload(content);
}
private void populateUrl(PushMessageContent content,
UrlConfig url,
AppTypeEnum appType) {
if (url == null || appType == null) return;
UrlConfigWalker.walkDown(url, new UrlConfigVisitor() {
@Override
public void visitAppManagerAndroid(AppUrl android) {
if (android.hasUrl() && appType == AppTypeEnum.CMP)
content.setAndroidPushUrl(android.getUrl());
}
@Override
public void visitAppManagerIos(AppUrl ios) {
if (ios.hasUrl() && appType == AppTypeEnum.CMP)
content.setIosPushUrl(ios.getUrl());
}
@Override
public void visitAppWorkerAndroid(AppUrl android) {
if (android.hasUrl() && appType == AppTypeEnum.CM)
content.setAndroidPushUrl(android.getUrl());
}
@Override
public void visitAppWorkerIos(AppUrl ios) {
if (ios.hasUrl() && appType == AppTypeEnum.CM)
content.setIosPushUrl(ios.getUrl());
}
});
}
private JSONObject buildPayload(PushMessageContent content) {
JSONObject payload = new JSONObject();
payload.put("pushTitle", content.getTitle());
PushPayloadBuilder[] builders = payloadBuilderProvider.getIfAvailable();
if (builders != null) {
for (PushPayloadBuilder builder : builders)
builder.build(content, payload);
}
return payload;
}
}

View File

@ -0,0 +1,11 @@
package cn.axzo.msg.center.nimpush;
/**
* @author yanglin
*/
public enum PushChannel {
YOU_MENG,
NIM
}

View File

@ -0,0 +1,29 @@
package cn.axzo.msg.center.nimpush;
import com.taobao.api.internal.util.NamedThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author yanglin
*/
@Configuration
public class PushExecutorConfig {
public static final String PUSH_EXECUTOR = "pushExecutor";
@Bean(PUSH_EXECUTOR)
public ExecutorService pushExecutor() {
return new ThreadPoolExecutor(
30, 50,
10, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(10000),
new NamedThreadFactory(getClass().getSimpleName()));
}
}

View File

@ -0,0 +1,52 @@
package cn.axzo.msg.center.nimpush;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
/**
* @author yanglin
*/
@Setter
@Getter
public class PushMessageContent {
/**
* 主标题
*/
private String title;
/**
* 副标题
*/
private String subtitle;
/**
* android点击push的跳转链接
*/
private String androidPushUrl;
/**
* ios点击push的跳转链接
*/
private String iosPushUrl;
/**
* push消息类型. SYSTEM: 系统消息, OP: 运营消息
*/
private PushMessageTye messageTye;
/**
* 自定义提示音
*/
private String customSoundFile;
public boolean hasAndroidPushUrl() {
return StringUtils.isNotBlank(androidPushUrl);
}
public boolean hasIosPushUrl() {
return StringUtils.isNotBlank(iosPushUrl);
}
}

View File

@ -0,0 +1,24 @@
package cn.axzo.msg.center.nimpush;
import cn.axzo.msg.center.service.enums.CodeDefinition;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@RequiredArgsConstructor
public enum PushMessageTye implements CodeDefinition<String> {
SYSTEM("system", "系统消息"),
OP("op", "运营消息"),
;
private final String code;
private final String description;
@Override
public String getCode() {
return code;
}
}

View File

@ -0,0 +1,30 @@
package cn.axzo.msg.center.nimpush.payload;
import cn.axzo.msg.center.nimpush.PushMessageContent;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
/**
* 华为
*
* @author yanglin
*/
@Component
public class HWPushPayloadBuilder implements PushPayloadBuilder {
@Override
public void build(PushMessageContent message, JSONObject payload) {
if (!message.hasAndroidPushUrl()) return;
// 点击事件的内容
JSONObject clickAction = new JSONObject();
clickAction.put("type", 1);
clickAction.put("intent", message.getAndroidPushUrl());
// 通知的内容
JSONObject notification = new JSONObject();
notification.put("click_action", clickAction);
payload.put("hwField", notification);
}
}

View File

@ -0,0 +1,19 @@
package cn.axzo.msg.center.nimpush.payload;
import cn.axzo.msg.center.nimpush.PushMessageContent;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
/**
* 苹果
*
* @author yanglin
*/
@Component
public class PGPushPayloadBuilder implements PushPayloadBuilder {
@Override
public void build(PushMessageContent message, JSONObject payload) {
}
}

View File

@ -0,0 +1,16 @@
package cn.axzo.msg.center.nimpush.payload;
import cn.axzo.msg.center.nimpush.PushMessageContent;
import com.alibaba.fastjson.JSONObject;
/**
* <a href="https://doc.yunxin.163.com/messaging/server-apis/DQyNjc5NjE?platform=server#apns%E6%8E%A8%E9%80%81%E6%B6%88%E6%81%AF">云信推送</a>
* <a href="https://doc.yunxin.163.com/messaging/guide/TY4MzU5MDc?platform=android#%E8%AE%BE%E7%BD%AE%E6%8E%A8%E9%80%81%E9%80%9A%E7%9F%A5%E6%A0%8F%E8%B7%B3%E8%BD%AC%E6%96%B9%E5%BC%8F">各端推送配置</a>
*
* @author yanglin
*/
public interface PushPayloadBuilder {
void build(PushMessageContent message, JSONObject payload);
}

View File

@ -0,0 +1,31 @@
package cn.axzo.msg.center.nimpush.payload;
import cn.axzo.msg.center.nimpush.PushMessageContent;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
/**
* 荣耀
*
* @author yanglin
*/
@Component
public class RYPushPayloadBuilder implements PushPayloadBuilder {
@Override
public void build(PushMessageContent message, JSONObject payload) {
if (!message.hasAndroidPushUrl()) return;
// 点击事件的内容
JSONObject clickAction = new JSONObject();
clickAction.put("type", 1);
clickAction.put("intent", message.getAndroidPushUrl());
// 通知的内容
JSONObject notification = new JSONObject();
notification.put("clickAction", clickAction);
payload.put("honorField", notification);
}
}

View File

@ -0,0 +1,19 @@
package cn.axzo.msg.center.nimpush.payload;
import cn.axzo.msg.center.nimpush.PushMessageContent;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
/**
* 小米
*
* @author yanglin
*/
@Component
public class XMPushPayloadBuilder implements PushPayloadBuilder {
@Override
public void build(PushMessageContent message, JSONObject payload) {
}
}

View File

@ -1 +1,2 @@
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

View File

@ -114,6 +114,14 @@ public class MessageSendBasicInfoV4 implements Serializable {
return bizCode == null ? "" : bizCode;
}
public JSONObject determineBizExtParams() {
return bizExtParams == null ? new JSONObject() : bizExtParams;
}
public JSONObject determineRouterParams() {
return routerParams == null ? new JSONObject() : routerParams;
}
public void validate() {
AssertUtil.notEmpty(receivers, "receivers不能为空");

View File

@ -0,0 +1,28 @@
package cn.axzo.msg.center.push;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author yanglin
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PushData {
private boolean switchOn;
/**
* 声音文件
*/
private String voiceFile;
/**
* push类型system系统消息op(运营消息)
*/
private String type;
}

View File

@ -0,0 +1,22 @@
package cn.axzo.msg.center.push;
import cn.axzo.msg.center.service.domain.UrlConfig;
import javax.annotation.Nullable;
/**
* @author yanglin
*/
public interface PushMessage {
String getPushTitle();
String getPushSubtitle();
@Nullable
UrlConfig getPushUrl();
@Nullable
PushData getPushData();
}

View File

@ -1,8 +1,13 @@
package cn.axzo.msg.center.service.pending.response.v3.model;
import cn.axzo.msg.center.push.PushData;
import cn.axzo.msg.center.push.PushMessage;
import cn.axzo.msg.center.service.domain.CardUrlConfig;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.enums.GroupType;
import cn.axzo.msg.center.service.pending.response.MessageButtonProvider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
@ -23,7 +28,7 @@ import static java.util.stream.Collectors.toList;
*/
@Setter
@Getter
public class ParsedModelV3 implements MessageButtonProvider<ParsedButtonV3> {
public class ParsedModelV3 implements MessageButtonProvider<ParsedButtonV3>, PushMessage {
/**
* 前端忽略
@ -110,4 +115,26 @@ public class ParsedModelV3 implements MessageButtonProvider<ParsedButtonV3> {
public String toString() {
return JSON.toJSONString(this);
}
@Override
public String getPushTitle() {
return template.getTitle();
}
@Override
public String getPushSubtitle() {
return template.getContent();
}
@Override
public UrlConfig getPushUrl() {
CardUrlConfig cardUrl = template.getCardUrlConfig();
return cardUrl == null ? null : cardUrl.getUrlConfig();
}
@Override
public PushData getPushData() {
JSONObject pushData = template.getPushData();
return pushData == null ? null : pushData.toJavaObject(PushData.class);
}
}

View File

@ -20,7 +20,9 @@ import java.util.Date;
@Component
public class MessageRecordV3Dao extends ServiceImpl<MessageRecordV3Mapper, MessageRecordV3> {
public void setSendSuccess(Collection<Long> messageIds, @Nullable String imMsgId) {
public void setSendSuccess(Collection<Long> messageIds,
@Nullable String cmTaskId,
@Nullable String cmpTaskId) {
if (CollectionUtils.isEmpty(messageIds))
return;
lambdaUpdate()
@ -29,18 +31,8 @@ public class MessageRecordV3Dao extends ServiceImpl<MessageRecordV3Mapper, Messa
.set(MessageRecordV3::getState, MsgStateV3Enum.SEND_SUCCESS)
.set(MessageRecordV3::getUpdateAt, new Date())
.set(MessageRecordV3::getSendTime, new Date())
.set(StringUtils.isNotBlank(imMsgId), MessageRecordV3::getImMsgId, imMsgId)
.update();
}
public void setSendSuccess(Long messageId, @Nullable String imMsgId) {
lambdaUpdate()
.eq(MessageRecordV3::getState, MsgStateV3Enum.UNSENT)
.eq(MessageRecordV3::getId, messageId)
.set(MessageRecordV3::getState, MsgStateV3Enum.SEND_SUCCESS)
.set(MessageRecordV3::getUpdateAt, new Date())
.set(MessageRecordV3::getSendTime, new Date())
.set(StringUtils.isNotBlank(imMsgId), MessageRecordV3::getImMsgId, imMsgId)
.set(StringUtils.isNotBlank(cmTaskId), MessageRecordV3::getCmTaskId, cmTaskId)
.set(StringUtils.isNotBlank(cmpTaskId), MessageRecordV3::getCmpTaskId, cmpTaskId)
.update();
}

View File

@ -11,9 +11,7 @@ import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
@ -117,32 +115,4 @@ public class MessageBaseTemplate extends BaseEntityExt<MessageBaseTemplate> impl
return JSON.toJSONString(this);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PushData {
private boolean switchOn;
/**
* 声音文件
*/
private String voiceFile;
/**
* 提醒方式voice声音vibrate(震动)
*/
private String ability;
/**
* push类型system系统消息op(运营消息)
*/
private String type;
/**
* 声音类型custom自定义system系统
*/
private String voiceType;
}
}

View File

@ -1,5 +1,7 @@
package cn.axzo.msg.center.domain.entity;
import com.alibaba.fastjson.JSONObject;
/**
* @author yanglin
*/
@ -9,4 +11,11 @@ public interface MessageEntity {
Long getReceiverWorkspaceId();
default JSONObject bizParam() {
return new JSONObject();
}
default JSONObject routerParam() {
return new JSONObject();
}
}

View File

@ -37,6 +37,16 @@ public class MessageRecordV3 extends BaseEntityExt<MessageRecordV3>
*/
private String imMsgId;
/**
* 工人端im的task id
*/
private String cmTaskId;
/**
* 管理端im的task id
*/
private String cmpTaskId;
/**
* 消息唯一标识
*/
@ -142,6 +152,16 @@ public class MessageRecordV3 extends BaseEntityExt<MessageRecordV3>
*/
private Date sendTime;
@Override
public JSONObject bizParam() {
return bizExtParams == null ? new JSONObject() : bizExtParams;
}
@Override
public JSONObject routerParam() {
return routerParams == null ? new JSONObject() : routerParams;
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -127,6 +127,15 @@ public interface PendingRecordAdapter extends MessageEntity {
return null;
}
@Override
default JSONObject bizParam() {
return getBizExtParamObj();
}
@Override default JSONObject routerParam() {
return getRouterParamObj();
}
default JSONObject getBizExtParamObj() {
return parseJsonObj(getBizExtParam());
}

View File

@ -188,4 +188,14 @@ public class Todo extends BaseEntityExt<Todo> implements MessageEntity {
public boolean isExecCompleted() {
return type == TodoType.EXECUTABLE && state == PendingMessageStateEnum.COMPLETED;
}
@Override
public JSONObject bizParam() {
return bizExtParam == null ? new JSONObject() : bizExtParam;
}
@Override
public JSONObject routerParam() {
return routerParams == null ? new JSONObject() : routerParams;
}
}

View File

@ -1,57 +0,0 @@
package cn.axzo.msg.center;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
/**
* @author cold_blade
* @date 2023/10/19
* @version 1.0
*/
@Slf4j
@Component
@Profile({"dev", "test", "local", "pre"})
public class FeignConfig implements RequestInterceptor, EnvironmentAware {
private Environment environment;
private static String POD_NAMESPACE;
static {
Map<String, String> env = System.getenv();
if (env != null) {
POD_NAMESPACE = env.get("MY_POD_NAMESPACE");
}
log.info("init FeignConfig, POD_NAMESPACE value is {}", POD_NAMESPACE);
}
@SneakyThrows
@Override
public void apply(RequestTemplate requestTemplate) {
if (POD_NAMESPACE == null) {
Target<?> target = requestTemplate.feignTarget();
String profile = environment.getProperty("spring.profiles.active");
if (Objects.equals(profile, "dev")) {
requestTemplate.target("http://dev-app.axzo.cn/" + target.name());
} else if (Objects.equals(profile, "test")) {
requestTemplate.target("http://test-api.axzo.cn/" + target.name());
} else if (Objects.equals(profile, "pre")) {
requestTemplate.target("http://pre-api.axzo.cn/" + target.name());
}
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@ -0,0 +1,22 @@
package cn.axzo.msg.center.nimpush;
import cn.axzo.msg.center.MsgCenterApplication;
import lombok.RequiredArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author yanglin
*/
@SpringBootTest(classes = MsgCenterApplication.class)
@RequiredArgsConstructor(onConstructor_ = @Autowired)
class NimPushServiceTest {
private final NimPushService nimPushService;
@Test
void exec() {
}
}