暂存一下

This commit is contained in:
yanglin 2024-01-12 19:14:38 +08:00
parent 77112a8315
commit 31f413f7e6
17 changed files with 931 additions and 2 deletions

View File

@ -0,0 +1,29 @@
package cn.axzo.msg.center.inside.notices.controller.msg;
import cn.axzo.msg.center.api.MessageAPIV3;
import cn.axzo.msg.center.api.request.MessagePushReqV3;
import cn.axzo.msg.center.api.response.MessageRespV3;
import cn.axzo.msg.center.inside.notices.service.MessageServiceV3;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author yanglin
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class MessageAPIV3Controller implements MessageAPIV3 {
private final MessageServiceV3 messageServiceV3;
@Override
public CommonResponse<List<MessageRespV3>> send(MessagePushReqV3 req) {
return CommonResponse.success(messageServiceV3.send(req));
}
}

View File

@ -0,0 +1,13 @@
package cn.axzo.msg.center.inside.notices.service;
import cn.axzo.msg.center.api.request.MessagePushReqV3;
import cn.axzo.msg.center.api.response.MessageRespV3;
import java.util.List;
/**
* @author yanglin
*/
public interface MessageServiceV3 {
List<MessageRespV3> send(MessagePushReqV3 req);
}

View File

@ -0,0 +1,39 @@
package cn.axzo.msg.center.inside.notices.service.component;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Component
public class TerminalAppTypeMapping {
private final ImmutableMap<PushTerminalEnum, AppTypeEnum> terminal2AppType = ImmutableMap.of(
PushTerminalEnum.B_ENTERPRISE_APP, AppTypeEnum.CMP,
PushTerminalEnum.C_WORKER_APP, AppTypeEnum.CM
);
public List<AppTypeEnum> toImTypes(List<PushTerminalEnum> terminals) {
if (CollectionUtils.isEmpty(terminals)) {
return Collections.emptyList();
}
return terminals.stream()
.map(this::toImType)
.distinct()
.collect(toList());
}
public AppTypeEnum toImType(PushTerminalEnum terminal) {
return terminal2AppType.get(terminal);
}
}

View File

@ -0,0 +1,98 @@
package cn.axzo.msg.center.inside.notices.service.impl.v3;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.MessageInfo;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
import com.alibaba.fastjson.JSON;
import com.taobao.api.internal.util.NamedThreadFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
class ImClient {
private final MessageApi messageApi;
private final ScheduledExecutorService scheduleExecutor =
Executors.newScheduledThreadPool(10, new NamedThreadFactory("MessageServiceV3-send"));
// 强行做成有界队列, 避免内存爆了
private final Semaphore semaphore = new Semaphore(200);
void send(MessageInfo req, BiConsumer<Exception, List<MessageDispatchResp>> callback) {
scheduleExecutor.execute(() -> {
acquireAndSend(req, callback, 0);
});
}
private void acquireAndSend(
MessageInfo req, BiConsumer<Exception, List<MessageDispatchResp>> callback, int retryCount) {
boolean acquired = false;
try {
if (semaphore.tryAcquire(20, TimeUnit.SECONDS)) {
acquired = true;
doSend(req, callback, retryCount);
} else {
String error = String.format("发送消息超载了. templateId=%s, header=%s, receiverPersonIds=%s",
req.getMsgTemplateId(), req.getMsgHeader(), JSON.toJSONString(req.getToPersonIdList()));
callback.accept(new RuntimeException(error), null);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (acquired) {
semaphore.release();
}
}
}
/**
* 本地简单重试
*/
private void doSend(
MessageInfo req, BiConsumer<Exception, List<MessageDispatchResp>> callback, int retryCount) {
if (retryCount > 0) {
log.warn("重试发送消息 templateId={}, header={}, receiverPersonIds={}",
req.getMsgTemplateId(), req.getMsgHeader(), JSON.toJSONString(req.getToPersonIdList()));
}
Consumer<Exception> retryFun = e -> {
if (retryCount > 3) {
callback.accept(e, null);
} else {
// 尽量在消息端进行重试, 因为是按template的纬度调用IM进行批量发送的
// 如果直接让上游调用端进行重试的话, 有重复发送的风险
scheduleExecutor.schedule(
() -> acquireAndSend(req, callback, retryCount + 1),
2L * (retryCount + 1), TimeUnit.SECONDS);
}
};
try {
ApiResult<List<MessageDispatchResp>> apiResult = messageApi.sendMessage(req);
if (apiResult.isSuccess()) {
callback.accept(null, apiResult.getData());
} else {
String error = String.format("发送消息失败. respCode=%s, respMsg=%s, templateId=%s, header=%s, receiverPersonIds=%s, retryCount=%s",
apiResult.getCode(), apiResult.getMsg(), req.getMsgTemplateId(),
req.getMsgHeader(), JSON.toJSONString(req.getToPersonIdList()), retryCount);
log.error(error);
// FIXME(yl): 区分业务异常和IM系统异常?
retryFun.accept(new RuntimeException(error));
}
} catch (Exception e) {
retryFun.accept(e);
}
}
}

View File

@ -0,0 +1,94 @@
package cn.axzo.msg.center.inside.notices.service.impl.v3;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.api.request.MessagePushReqV3;
import cn.axzo.msg.center.api.response.MessageRespV3;
import cn.axzo.msg.center.dal.MessageRecordV3Dao;
import cn.axzo.msg.center.domain.entity.BizEventMapping;
import cn.axzo.msg.center.domain.entity.MessageRecordV3;
import cn.axzo.msg.center.domain.enums.BizActionCategory;
import cn.axzo.msg.center.inside.notices.service.MessageServiceV3;
import cn.axzo.msg.center.inside.notices.service.component.TerminalAppTypeMapping;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.service.BizEventMappingService;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.service.bizevent.request.ReachDto;
import cn.axzo.msg.center.utils.UUIDUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageServiceV3Impl implements MessageServiceV3 {
private final BizEventMappingService bizEventMappingService;
private final MessageTemplateNewService messageTemplateNewService;
private final TerminalAppTypeMapping terminalAppTypeMapping;
private final ImClient imClient;
private final MessageRecordV3Dao messageRecordV3Dao;
@Override
@Transactional(rollbackFor = Exception.class)
public List<MessageRespV3> send(MessagePushReqV3 req) {
req.validate();
BizEventMapping mapping = bizEventMappingService
.getByBizCode(req.getBizEventMappingCode())
.orElse(null);
AssertUtil.notNull(mapping, String.format("找不到对应的事件映射: %s", req.getBizEventMappingCode()));
//noinspection DataFlowIssue
AssertUtil.notEmpty(mapping.getReachConfig(), String.format(
"业务事件映射%s无业务动作配置", req.getBizEventMappingCode()));
String batchNo = UUIDUtil.uuidString();
List<TemplateBatch> batches = new ArrayList<>(mapping.getReachConfig().size());
for (ReachDto cfg : mapping.getReachConfig()) {
AssertUtil.isTrue(BizActionCategory.NOTIFICATION.is(cfg.getCategory()), "目前只支持通知");
MessageTemplateDTO template = messageTemplateNewService
.queryEnableTemplateByCode(cfg.getTemplateCode())
.orElseThrow(() -> new ServiceException(String.format(
"未查询到对应的模板, templateCode=%s", cfg.getTemplateCode())));
batches.add(new TemplateBatch(req, batchNo, template));
}
List<MessageRecordV3> records = batches.stream()
.flatMap(b -> b.getMessageRecords().stream())
.collect(toList());
messageRecordV3Dao.saveBatch(records);
for (TemplateBatch batch : batches) {
sendBatch(batch);
}
// TODO(yl): WHAT
return Collections.emptyList();
}
private void sendBatch(TemplateBatch batch) {
List<AppTypeEnum> appTypes = terminalAppTypeMapping
.toImTypes(batch.getTemplate().getPushTerminals());
imClient.send(batch.buildIMRequest(appTypes), (e, respList) -> {
if (e != null) {
messageRecordV3Dao.setSendFailed(batch.getRecordIds(), e.getMessage());
} else {
for (int i = 0; i < respList.size(); i++) {
MessageDispatchResp resp = respList.get(i);
MessageRecordV3 record = batch.getMessageRecords().get(i);
// 把im端的id也存起来
messageRecordV3Dao.setSendSuccess(record.getId(), resp.getMsgid());
}
}
});
}
}

View File

@ -0,0 +1,148 @@
package cn.axzo.msg.center.inside.notices.service.impl.v3;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.im.center.api.vo.req.MessageInfo;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.msg.center.api.enums.MsgStateV3Enum;
import cn.axzo.msg.center.api.request.MessagePushReqV3;
import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.common.utils.PlaceholderResolver;
import cn.axzo.msg.center.domain.entity.MessageRecordV3;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.service.dto.PersonV3DTO;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
/**
* @author yanglin
*/
@RequiredArgsConstructor
class TemplateBatch {
private final MessagePushReqV3 req;
private final String batchNo;
@Getter
private final MessageTemplateDTO template;
private List<MessageRecordV3> records;
private String title;
private String content;
private Map<String, Object> msgExtInfo;
Collection<Long> getRecordIds() {
return getMessageRecords().stream()
.map(MessageRecordV3::getId)
.collect(toSet());
}
List<MessageRecordV3> getMessageRecords() {
if (records != null) {
return records;
}
records = new ArrayList<>(req.getReceivers().size());
for (PersonV3DTO receiver : req.getReceivers()) {
MessageRecordV3 record = new MessageRecordV3();
record.setBatchNo(batchNo);
record.setIdentityCode(UUIDUtil.uuidString());
record.setBizCategory(req.getBizCategory());
record.setSenderPersonId(req.getSender().getId());
record.setSenderOuId(req.getSender().getId());
record.setSenderWorkspaceId(req.getSender().getWorkspaceId());
record.setReceiverPersonId(receiver.getId());
record.setReceiverOuId(receiver.getOuId());
record.setReceiverWorkspaceId(receiver.getWorkspaceId());
record.setBizEventMappingCode(req.getBizEventMappingCode());
record.setTemplateCode(template.getCode());
record.setTitle(getTitle());
record.setContent(getContent());
record.setOrgType(req.getOrgType());
record.setState(MsgStateV3Enum.UNSENT);
record.setBizCode(req.getBizCode());
record.setRouterParams(JSON.toJSONString(req.getRouterParams()));
record.setBizExtParams(record.getBizExtParams());
record.setMsgExtInfo(JSON.toJSONString(getMsgExtInfo()));
record.setFailCause(null);
record.setSendTime(new Date());
record.setCreateAt(new Date());
record.setUpdateAt(new Date());
record.setIsDelete(TableIsDeleteEnum.NORMAL.value);
}
return records;
}
MessageInfo buildIMRequest(List<AppTypeEnum> apps) {
MessageInfo imReq = new MessageInfo();
imReq.setAppTypeList(apps);
imReq.setToPersonIdList(req.stringReceiverIds());
imReq.setMsgHeader(getTitle());
imReq.setMsgContent(getContent());
imReq.setMsgTemplateId(template.getCode());
imReq.setMsgTemplateContent(JSON.toJSONString(getMsgExtInfo()));
// 扩展信息
Map<String, String> ext = new HashMap<>();
ext.put("minAppVersion", template.getMinAppVersion());
ext.put("workspaceId", req.stringWorkspaceId());
imReq.setExtendsInfo(ext);
return imReq;
}
// ------------------------------- 辅助方法
String getTitle() {
if (title != null) {
return title;
}
title = req.getBizExtParams() == null
? template.getTitle()
: PlaceholderResolver.resolve(template.getTitle(), req.getBizExtParams());
return title;
}
String getContent() {
if (content != null) {
return content;
}
content = req.getBizExtParams() == null
? template.getContent()
: PlaceholderResolver.resolve(template.getContent(), req.getBizExtParams());
return content;
}
Map<String, Object> getMsgExtInfo() {
if (msgExtInfo == null) {
msgExtInfo = ImmutableMap.of(
"bizExtParams", req.getBizExtParams(),
"routerParams", req.getRouterParams());
}
return msgExtInfo;
}
Collection<Long> getReceiverPersonIds() {
return req.getReceivers().stream()
.map(PersonV3DTO::getId)
.collect(toList());
}
@Override
public String toString() {
HashMap<String, Object> values = new HashMap<>();
values.put("batchNo", batchNo);
values.put("bizCode", req.getBizCode());
values.put("templateCode", template.getCode());
values.put("bizEventMappingCode", req.getBizEventMappingCode());
values.put("receiverIds", getRecordIds());
values.put("receiverPersonIds", getReceiverPersonIds());
return JSON.toJSONString(values);
}
}

View File

@ -0,0 +1,34 @@
package cn.axzo.msg.center.api;
import cn.axzo.msg.center.api.fallback.LoggingFallbackFactory;
import cn.axzo.msg.center.api.request.MessagePushReqV3;
import cn.axzo.msg.center.api.response.MessageRespV3;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* @author yanglin
*/
@Component
@FeignClient(
value = "msg-center",
url = "${server.serviceUrl:http://msg-center:8080}",
fallbackFactory = MessageAPIV3.FallbackFactory.class)
public interface MessageAPIV3 {
@RequestMapping(value = "api/message/v3/send", method = RequestMethod.POST)
CommonResponse<List<MessageRespV3>> send(MessagePushReqV3 req);
class FallbackFactory extends LoggingFallbackFactory {
public FallbackFactory() {
super(MessageAPIV3.class);
}
}
}

View File

@ -0,0 +1,33 @@
package cn.axzo.msg.center.api.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
/**
* @author: yanglin
*/
@Getter
public enum MsgStateV3Enum {
UNSENT("UNSENT", "未发送"),
SEND_FAILED("SEND_FAILED", "发送失败"),
SEND_SUCCESS("SEND_SUCCESS", "已发送");
@EnumValue
private final String code;
private final String message;
MsgStateV3Enum(String code, String message) {
this.code = code;
this.message = message;
}
public String code() {
return this.code;
}
public String message() {
return this.message;
}
}

View File

@ -0,0 +1,77 @@
package cn.axzo.msg.center.api.fallback;
import cn.axzo.basics.common.exception.ServiceException;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author yanglin
*/
@Slf4j
public abstract class LoggingFallbackFactory implements FallbackFactory<Object> {
private final Class<?> type;
public LoggingFallbackFactory(Class<?> apiInterfaceType) {
if (!apiInterfaceType.isInterface()) {
throw new RuntimeException(String.format("目标类型必须是接口, 实际上是: %s", apiInterfaceType.getName()));
}
this.type = apiInterfaceType;
}
@Override
public Object create(Throwable cause) {
return Proxy.newProxyInstance(
getClass().getClassLoader(), new Class[]{type}, new Logging(type, cause));
}
@RequiredArgsConstructor
private static class Logging implements InvocationHandler {
private final Class<?> type;
private final Throwable cause;
@Override
@SuppressWarnings({"SuspiciousInvocationHandlerImplementation", "代码逻辑做了检查"})
public Object invoke(Object proxy, Method method, Object[] args) {
if (ReflectionUtils.isObjectMethod(method)) {
log.error("method invoking error {}#{}", type.getName(), method.getName(), cause);
return CommonResponse.error(String.format("调用异常: %s#%s", type.getName(), method.getName()));
}
if (method.getReturnType() != CommonResponse.class) {
throw new RuntimeException(String.format(
"调用类型不匹配, 期望: CommonResponse, 实际上是:%s", method.getReturnType().getName()));
}
if (cause instanceof ServiceException) {
Object errorData = getErrorData((ServiceException) cause);
// 大概率是业务约束不满足, 不打印成error
log.warn("method invoking exception {}#{}, message={}, errorData={}",
type.getName(), method.getName(), cause.getMessage(), errorData, cause);
return CommonResponse.error(cause.getMessage() == null ? "调用异常" : cause.getMessage());
} else {
log.error("method invoking error {}#{}", type.getName(), method.getName(), cause);
return CommonResponse.error(String.format("调用异常: %s#%s", type.getName(), method.getName()));
}
}
private Object getErrorData(ServiceException e) {
try {
Field field = ServiceException.class.getDeclaredField("dataObject");
field.setAccessible(true);
return field.get(e);
} catch (Exception ex) {
log.error("获取ServiceException中的dataObject出错", ex);
return null;
}
}
}
}

View File

@ -0,0 +1,110 @@
package cn.axzo.msg.center.api.request;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.msg.center.service.dto.PersonV3DTO;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static java.util.stream.Collectors.toSet;
/**
* @author yanglin
*/
@Data
public class MessagePushReqV3 implements Serializable {
/**
* 发起者
*/
@NotNull(message = "发起者不能为空")
private PersonV3DTO sender;
/**
* 接收者列表
*/
@NotNull(message = "接收者列表不能为空")
private List<PersonV3DTO> receivers;
/**
* 业务事件code
* oms->消息模板 配置
*/
@NotBlank(message = "业务事件code不能为空")
private String bizEventMappingCode;
/**
* 关联业务唯一标识
* 例如: 请假申请的编号
*/
@NotBlank(message = "关联业务唯一标识不能为空")
private String bizCode;
/**
* 消息所属组织类型
*/
@NotNull(message = "工作台类型不能为空")
private OrganizationTypeEnum orgType;
/**
* 消息所属工作台ID
*/
@NotNull(message = "消息所属工作台ID不能为空")
private Long workspaceId;
/**
* 消息所属企业ID
* 备注如果是工人则所在企业可以为空其它均必传
*/
private Long ouId;
/**
* 业务扩展参数-JSON字符串格式
*/
private JSONObject bizExtParams;
/**
* 路由参数-JSON字符串格式
*/
private JSONObject routerParams;
/**
* 业务类型- 待办使用
*/
private BizCategoryEnum bizCategory;
// ------------------------------- 辅助方法
public String stringWorkspaceId() {
if (workspaceId == null) {
return null;
}
return String.valueOf(workspaceId);
}
public Set<String> stringReceiverIds() {
if (receivers == null) {
return Collections.emptySet();
}
return receivers.stream()
.map(String::valueOf)
.collect(toSet());
}
public void validate() {
// TODO(yl): 确认如此是必传
AssertUtil.notNull(sender, "sender不能为空");
AssertUtil.notEmpty(receivers, "receivers不能为空");
AssertUtil.notEmpty(bizEventMappingCode, "bizEventCode不能为空");
AssertUtil.notEmpty(bizCode, "bizCode不能为空");
AssertUtil.notNull(orgType, "orgType不能为空");
AssertUtil.notNull(workspaceId, "workspaceId不能为空");
}
}

View File

@ -0,0 +1,12 @@
package cn.axzo.msg.center.api.response;
import lombok.Data;
import java.io.Serializable;
/**
* @author yanglin
*/
@Data
public class MessageRespV3 implements Serializable {
}

View File

@ -0,0 +1,38 @@
package cn.axzo.msg.center.service.dto;
import lombok.Data;
import java.io.Serializable;
/**
* @author yanglin
*/
@Data
public class PersonV3DTO implements Serializable {
private static final long serialVersionUID = 1231840051925115741L;
/**
* 自然人id
*/
private Long id;
/**
* 身份信息
*/
private IdentityDTO identity;
/**
* 姓名
*/
private String name;
/**
* 发送者项目部ID
*/
private Long workspaceId;
/**
* 发送者企业ID
*/
private Long ouId;
}

View File

@ -46,6 +46,10 @@ public class PlaceholderResolver {
this.placeholderSuffix = placeholderSuffix;
}
public static String resolve(String template, Map<String, Object> values) {
return getDefaultResolver().resolveByMap(template, values);
}
/**
* 获取默认的占位符解析器即占位符前缀为"${", 后缀为"}"
*

View File

@ -0,0 +1,39 @@
package cn.axzo.msg.center.dal;
import cn.axzo.msg.center.api.enums.MsgStateV3Enum;
import cn.axzo.msg.center.dal.mapper.MessageRecordV3Mapper;
import cn.axzo.msg.center.domain.entity.MessageRecordV3;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Date;
/**
* @author yanglin
*/
@Slf4j
@Component
public class MessageRecordV3Dao extends ServiceImpl<MessageRecordV3Mapper, MessageRecordV3> {
public void setSendSuccess(Long msgId, String imMsgId) {
lambdaUpdate()
.eq(MessageRecordV3::getState, MsgStateV3Enum.UNSENT)
.eq(MessageRecordV3::getId, msgId)
.set(MessageRecordV3::getState, MsgStateV3Enum.SEND_SUCCESS)
.set(MessageRecordV3::getUpdateAt, new Date())
.set(MessageRecordV3::getImMsgId, imMsgId)
.update();
}
public void setSendFailed(Collection<Long> messageIds, String cause) {
lambdaUpdate()
.eq(MessageRecordV3::getState, MsgStateV3Enum.UNSENT)
.in(MessageRecordV3::getId, messageIds)
.set(MessageRecordV3::getState, MsgStateV3Enum.SEND_FAILED)
.set(MessageRecordV3::getUpdateAt, new Date())
.set(MessageRecordV3::getFailCause, cause)
.update();
}
}

View File

@ -0,0 +1,12 @@
package cn.axzo.msg.center.dal.mapper;
import cn.axzo.msg.center.domain.entity.MessageRecordV3;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* @author yanglin
*/
@Mapper
public interface MessageRecordV3Mapper extends BaseMapper<MessageRecordV3> {
}

View File

@ -0,0 +1,144 @@
package cn.axzo.msg.center.domain.entity;
import cn.axzo.msg.center.api.enums.MsgStateV3Enum;
import cn.axzo.msg.center.domain.persistence.BaseEntityExt;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* @author cold_blade
* @version 1.0
* @description 消息基础模板数据模型
* @date 2023/9/20
*/
@Setter
@Getter
@TableName("message_record_v3")
public class MessageRecordV3 extends BaseEntityExt<MessageRecordV3> implements Serializable {
private static final long serialVersionUID = -880409106378455813L;
/**
* 批次号
*/
private String batchNo;
/**
* IM端的消息ID
*/
private String imMsgId;
/**
* 消息唯一标识
*/
private String identityCode;
/**
* 业务类型
* <p>FLOW:流程,OTHER:其它
*/
private BizCategoryEnum bizCategory;
/**
* 发送自然人ID
*/
private Long senderPersonId;
/**
* 接收自然人ID
*/
private Long receiverPersonId;
/**
* 业务事件映射code
*/
private String bizEventMappingCode;
/**
* 模版code
*/
private String templateCode;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 消息所属组织类型 PROJECT:项目,ENT:企业,UNKNOWN:未知
*/
private OrganizationTypeEnum orgType;
/**
* 发送者项目部ID
*/
private Long senderWorkspaceId;
/**
* 发送者企业ID
*/
private Long senderOuId;
/**
* 接收者项目部ID
*/
private Long receiverWorkspaceId;
/**
* 接收者企业ID
*/
private Long receiverOuId;
/**
* 消息状态 UNSENT:未发送,SEND_SUCCESS:已发送,SEND_FAILED:发送失败
*/
private MsgStateV3Enum state;
/**
* 关联业务编码
* <p>业务上的唯一标识, 用于向上回述
*/
private String bizCode;
/**
* 路由参数
*/
private String routerParams;
/**
* 业务扩展参数
*/
private String bizExtParams;
/**
* 消息的其它信息, 备查
*/
private String msgExtInfo;
/**
* 失败原因
*/
private String failCause;
/**
* 发送时间
*/
private Date sendTime;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -2,11 +2,12 @@ package cn.axzo.msg.center.domain.enums;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 业务动作分类
@ -41,4 +42,8 @@ public enum BizActionCategory {
.findFirst()
.orElseThrow(() -> new ServiceException("BizEvent Action is invalid"));
}
public boolean is(String code) {
return this.code.equals(code);
}
}