Merge branch 'feature/REQ-2010' into feature/20240306

# Conflicts:
#	inside-notices/src/main/java/cn/axzo/msg/center/message/controller/PrivateMessageController.java
This commit is contained in:
yanglin 2024-03-05 20:06:56 +08:00
commit d31d54bfbe
27 changed files with 689 additions and 128 deletions

View File

@ -0,0 +1,41 @@
package cn.axzo.msg.center.http;
import cn.axzo.basics.common.exception.ServiceException;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
* @author yanglin
*/
@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ControllerExceptionAdvice {
@ExceptionHandler(value = ServiceException.class)
public CommonResponse<?> bizException(ServiceException e) {
log.warn("ControllerExceptionHandler.bizException Exception", e);
return CommonResponse.error(e.getErrorCode(), e.getMessage());
}
@ExceptionHandler(BindException.class)
public CommonResponse<Void> bindExceptionHandler(BindException e) {
log.warn("业务异常", e);
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
if (CollectionUtils.isEmpty(allErrors)) {
return CommonResponse.fail("操作失败 请联系系统管理员");
}
ObjectError objectError = allErrors.get(0);
String objectErrorDefaultMessage = objectError.getDefaultMessage();
return CommonResponse.fail(objectErrorDefaultMessage);
}
}

View File

@ -16,7 +16,7 @@ import javax.servlet.http.HttpServletResponse;
@RequiredArgsConstructor
public class PrivateHandlerInterceptor implements HandlerInterceptor {
@Value("${msg.center.server.private-token:340-H}")
@Value("${private.token:340-H}")
private final String privateToken;
@Override

View File

@ -0,0 +1,67 @@
package cn.axzo.msg.center.inside.notices.service.impl;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.basics.profiles.api.UserProfileServiceApi;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.msg.center.api.request.v3.SearchPendingMessageReq;
import cn.axzo.msg.center.api.response.v3.SearchPendingMessageResp;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.PendingMessageRecordDao;
import cn.axzo.msg.center.domain.entity.PendingMessageRecord;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author yanglin
*/
@Component
@RequiredArgsConstructor
public class PendingMessageSearchService {
private final PendingMessageRecordDao pendingMessageRecordDao;
private final UserProfileServiceApi userProfileServiceApi;
public List<SearchPendingMessageResp> search(SearchPendingMessageReq req) {
if (req.getLimit() >= 100) {
req.setLimit(100);
}
if (req.getPromoterPersonId() == null && StringUtils.isNotBlank(req.getPromoterPhone())) {
req.setPromoterPersonId(getPersonIdByPhone(req.getPromoterPhone()));
}
if (req.getExecutorPersonId() == null && StringUtils.isNotBlank(req.getExecutorPhone())) {
req.setExecutorPersonId(getPersonIdByPhone(req.getExecutorPhone()));
}
List<PendingMessageRecord> records = pendingMessageRecordDao.lambdaQuery()
.in(CollectionUtils.isNotEmpty(req.getIdentityCodes()), PendingMessageRecord::getIdentityCode, req.getIdentityCodes())
.eq(req.getPromoterPersonId() != null, PendingMessageRecord::getPromoterPersonId, req.getPromoterPersonId())
.eq(req.getExecutorPersonId() != null, PendingMessageRecord::getExecutorPersonId, req.getExecutorPersonId())
.eq(req.getOuId() != null, PendingMessageRecord::getOuId, req.getOuId())
.eq(req.getState() != null, PendingMessageRecord::getState, req.getState())
.eq(req.getBizFinalState() != null, PendingMessageRecord::getBizFinalState, req.getBizFinalState())
.eq(StringUtils.isNotBlank(req.getBizCode()), PendingMessageRecord::getBizCode, req.getBizCode())
.eq(StringUtils.isNotBlank(req.getSubBizCode()), PendingMessageRecord::getSubBizCode, req.getSubBizCode())
.eq(StringUtils.isNotBlank(req.getTemplateCode()), PendingMessageRecord::getTemplateCode, req.getTemplateCode())
.like(StringUtils.isNotBlank(req.getTitle()), PendingMessageRecord::getTitle, req.getTitle())
.like(StringUtils.isNotBlank(req.getContent()), PendingMessageRecord::getContent, req.getContent())
.ge(req.getStartTime() != null, PendingMessageRecord::getCreateAt, req.getStartTime())
.le(req.getEndTime() != null, PendingMessageRecord::getCreateAt, req.getEndTime())
.orderByDesc(PendingMessageRecord::getId)
.last("LIMIT " + req.getLimit())
.list();
return BeanMapper.copyList(records, SearchPendingMessageResp.class);
}
private Long getPersonIdByPhone(String phone) {
CommonResponse<PersonProfileDto> profileResp = userProfileServiceApi
.getUnionPersonProfile(null, phone);
PersonProfileDto personProfile = BizAssertions.assertResponse(profileResp, "未找根据手机找到人员");
BizAssertions.assertNotNull(personProfile, "未找根据手机找到人员");
return personProfile.getId();
}
}

View File

@ -32,8 +32,8 @@ public class MessageRecordServiceV3 {
if (req.getReceiverPersonId() == null && StringUtils.isNotBlank(req.getReceiverPhone())) {
CommonResponse<PersonProfileDto> profileResp = userProfileServiceApi
.getUnionPersonProfile(null, req.getReceiverPhone());
PersonProfileDto personProfile = BizAssertions.assertResponse(profileResp, "未找根据手机找到接受人员");
BizAssertions.assertNotNull(personProfile, "未找根据手机找到接受人员");
PersonProfileDto personProfile = BizAssertions.assertResponse(profileResp, "未找根据手机找到人员");
BizAssertions.assertNotNull(personProfile, "未找根据手机找到人员");
req.setReceiverPersonId(personProfile.getId());
}
List<MessageRecordV3> records = messageRecordV3Dao.lambdaQuery()

View File

@ -13,6 +13,7 @@ import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.domain.vo.GeneralMessagePushVO;
import cn.axzo.msg.center.service.bizevent.request.ReachDto;
import cn.axzo.msg.center.service.dto.PersonV3DTO;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import cn.axzo.msg.center.utils.UUIDUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
@ -95,7 +96,11 @@ public class TemplateMessage {
message.setTemplateCode(template.getCode());
message.setTitle(parseTitle());
message.setContent(parseContent());
message.setReceiverOrgType(req.getReceiversOrgType());
OrganizationTypeEnum orgType = req.getReceiversOrgType();
if (orgType == null) {
orgType = OrganizationTypeEnum.UNKNOWN;
}
message.setReceiverOrgType(orgType.stringCode());
message.setSubtitle(req.getSubtitle());
message.setState(MsgStateV3Enum.UNSENT);
message.setBizCode(req.getBizCode());

View File

@ -1,35 +1,24 @@
package cn.axzo.msg.center.message.controller;
import cn.axzo.msg.center.dal.BizEventMappingDao;
import cn.axzo.msg.center.dal.MessageBaseTemplateDao;
import cn.axzo.msg.center.dal.RelationTemplateMapDao;
import cn.axzo.msg.center.domain.entity.BizEventMapping;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
import cn.axzo.msg.center.domain.entity.RelationTemplateMap;
import cn.axzo.msg.center.domain.enums.BizActionCategory;
import cn.axzo.msg.center.message.migrate.OuIdMigrateService;
import cn.axzo.msg.center.service.bizevent.request.ReachDto;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import cn.axzo.msg.center.api.MessageAPIV3;
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.inside.notices.service.impl.PendingMessageSearchService;
import cn.axzo.msg.center.inside.notices.service.impl.v3.MessageRecordServiceV3;
import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
import cn.axzo.msg.center.message.service.PendingMessageNewService;
import cn.axzo.msg.center.service.pending.request.PendingMessagePageRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List;
/**
* @author syl
* @date 2023/12/18
@ -40,96 +29,38 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/private/message")
public class PrivateMessageController {
private final OuIdMigrateService ouIdMigrateService;
private final BizEventMappingDao bizEventMappingDao;
private final RelationTemplateMapDao relationTemplateMapDao;
private final MessageBaseTemplateDao messageBaseTemplateDao;
private final PendingMessageNewService pendingMessageNewService;
private final MessageRecordServiceV3 messageRecordServiceV3;
private final MessageAPIV3 messageAPIV3;
private final PendingMessageSearchService pendingMessageSearchService;
@PostMapping("/determinePageQueryTemplateCode")
public List<String> determinePageQueryTemplateCode(
@RequestBody @Valid PendingMessagePageRequest request) {
return pendingMessageNewService.determinePageQueryTemplateCodes(request);
}
@PostMapping(value = "/biz-event-mapping/init", produces = {
MediaType.APPLICATION_JSON_VALUE})
public CommonResponse<Void> initBizEventMapping(
@RequestBody @Valid BizEventInitRequest request) {
List<RelationTemplateMap> relationTemplateMaps = relationTemplateMapDao.lambdaQuery()
.in(CollUtil.isNotEmpty(request.getOriginalRelationIds()),
RelationTemplateMap::getOriginalRelationId,
request.getOriginalRelationIds())
.eq(RelationTemplateMap::getIsDelete, 0L)
.list();
if (CollUtil.isEmpty(relationTemplateMaps)) {
return CommonResponse.success();
}
@PostMapping("/sendPendingMessage")
public Object sendPendingMessage(
@RequestBody @Valid PendingMessagePushParam request) {
return pendingMessageNewService.push(request);
}
Map<Long, RelationTemplateMap> relationMap = relationTemplateMaps.stream()
.collect(Collectors.toMap(RelationTemplateMap::getOriginalRelationId,
Function.identity(), (pre, next) -> pre));
@PostMapping("/sendImMessage")
public Object sendImMessage(
@RequestBody @Valid MessageSendReqV3 request) {
return messageAPIV3.send(request);
}
Map<String, BizEventMapping> existBizEventMap = bizEventMappingDao.lambdaQuery()
.in(BizEventMapping::getBizCode, relationMap.keySet()
.stream().map(String::valueOf).collect(Collectors.toList()))
.eq(BizEventMapping::getIsDelete, 0L)
.list()
.stream()
.collect(Collectors.toMap(BizEventMapping::getBizCode, f -> f));
// 同步数据
List<RelationTemplateMap> needSyncList = relationTemplateMaps.stream()
.filter(r -> !existBizEventMap.containsKey(String.valueOf(r.getOriginalRelationId())))
.collect(Collectors.toList());
if (CollUtil.isEmpty(needSyncList)) {
return CommonResponse.success();
}
Map<String, MessageBaseTemplate> templateMap = messageBaseTemplateDao.lambdaQuery()
.in(MessageBaseTemplate::getCode, needSyncList.stream()
.map(RelationTemplateMap::getTemplateCode)
.collect(Collectors.toList()))
.eq(MessageBaseTemplate::getIsDelete, 0L)
.list()
.stream()
.collect(Collectors.toMap(MessageBaseTemplate::getCode, f -> f));
List<BizEventMapping> bizEvents = needSyncList.stream()
.filter(m -> templateMap.containsKey(m.getTemplateCode()))
.map(m ->
BizEventMapping.builder()
.bizCode(String.valueOf(m.getOriginalRelationId()))
.bizName(templateMap.get(m.getTemplateCode()).getTitle())
.reachConfig(Lists.newArrayList(
ReachDto.builder()
.templateCode(m.getTemplateCode())
// 历史数据只有通知
.category(BizActionCategory.NOTIFICATION.getCode())
.build()
))
.creatorId(0L)
.build())
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(bizEvents)) {
bizEventMappingDao.saveBatch(bizEvents);
log.info("sync biz event num= [{}]", bizEvents.size());
}
return CommonResponse.success();
@PostMapping("/searchImRecord")
public Object searchImRecord(@RequestBody @Valid SearchMessageReqV3 req) {
return messageRecordServiceV3.search(req);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class BizEventInitRequest {
private List<Long> originalRelationIds;
@PostMapping("/searchPendingRecords")
public Object searchPendingRecords(@RequestBody @Valid SearchPendingMessageReq req) {
return pendingMessageSearchService.search(req);
}
@PostMapping("/migrateOuId")
public String migrateOuId(
@RequestParam("token") String token,
@RequestBody OuIdMigrateService.Param param) {
if (!"13145".equals(token))
return "invalid token";
return ouIdMigrateService.execute(param);
}
}
}

View File

@ -130,6 +130,11 @@ public class PendingMessageDTO implements Serializable {
*/
private Date hideUntil;
/**
* 模版类型
*/
private MessageCategoryEnum templateCategory;
public static PendingMessageDTO from(PendingMessageRecord pendingMessageRecord) {
// 代办发起者信息
PersonDTO promoter = PersonDTO.builder()
@ -179,6 +184,7 @@ public class PendingMessageDTO implements Serializable {
public PendingMessageResponse toResponse(TerminalTypeEnum terminalType) {
return PendingMessageResponse.builder()
.templateCategory(templateCategory)
.identityCode(this.identityCode)
.templateCode(this.templateCode)
.title(this.title)

View File

@ -13,6 +13,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

View File

@ -119,6 +119,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -218,6 +219,7 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
// 构建人维度的查询条件
buildPersonCondition(query, request.getWithIdentify(), request.getRoleCategory(), operator);
List<String> templateCodes = determinePageQueryTemplateCodes(request);
List<String> templateCodesCopy = new ArrayList<>(templateCodes);
if (CollectionUtils.isEmpty(templateCodes)) {
// 如果该分类未关联任何模板直接返回空集合
log.info("there is not any template matched... appTerminal:[{}]", request.getAppTerminalType());
@ -250,7 +252,7 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
if (resultPage.isEnableAnalysis()) {
resultPage.addAnalysis("terminalConfig", getTerminalConfigInfo());
}
resultPage.addAnalysis("templateCodes", new HashSet<>(templateCodes));
resultPage.addAnalysis("templateCodes", new HashSet<>(templateCodesCopy));
resultPage.addAnalysis("personId", request.getPersonId());
resultPage.addAnalysis("operator", operator);
resultPage.addAnalysis("ouId", request.getOuId());
@ -455,6 +457,7 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
@Override
public List<PushPendingMessageDTO> push(PendingMessagePushParam param) {
BizAssertions.assertNotEmpty(param.getExecutor(), "接受者不能为空");
MessageTemplateDTO msgTemplate = messageTemplateNewService
.queryEnableTemplateByCode(param.getTemplateCode())
.orElseThrow(() -> new ServiceException("not found message template."));
@ -691,7 +694,10 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
List<PendingMessageRecord> value = entry.getValue();
List<PendingMessageRecord> personRecords = new ArrayList<>(value.size());
// 存在操作人
if (null != param.getOperatorIdentityId()) {
if (param.getOperatorPersonId() != null) {
personRecords = ListUtils.emptyIfNull(value).stream().filter(e -> e.getExecutorPersonId().equals(param.getOperatorPersonId())).collect(Collectors.toList());
}
else if (null != param.getOperatorIdentityId()) {
personRecords = ListUtils.emptyIfNull(value).stream().filter(e -> e.getExecutorId().equals(param.getOperatorIdentityId())).collect(Collectors.toList());
}
// 流程最终的记录
@ -721,6 +727,12 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
private PendingMessageDTO convert(PendingMessageRecord pendingMessageRecord, List<MessageTemplateDTO> messageTemplates) {
PendingMessageDTO pendingMessage = PendingMessageDTO.from(pendingMessageRecord);
Map<String, MessageTemplateDTO> templateCode2Template = messageTemplates.stream()
.collect(toMap(MessageTemplateDTO::getCode, identity()));
MessageTemplateDTO template = templateCode2Template.get(pendingMessageRecord.getTemplateCode());
if (template != null) {
pendingMessage.setTemplateCategory(template.getMsgCategory());
}
// 对应模板的路由策略
MessageTemplateRouterDTO msgTemplateRouter = messageTemplates.stream()
.filter(e -> Objects.equals(e.getCode(), pendingMessageRecord.getTemplateCode()))
@ -993,7 +1005,7 @@ public class PendingMessageNewServiceImpl implements PendingMessageNewService {
private Long getPractitionOuId(PersonDTO executor, Long workspaceId, Long ouId) {
Supplier<Long> executorOuIdFun = () -> ouId == null ? 0L : ouId;
// 从业人员单位处理
if (IdentityTypeEnum.PRACTITIONER == executor.getIdentity().getType()) {
if (executor.getIdentity() != null && IdentityTypeEnum.PRACTITIONER == executor.getIdentity().getType()) {
if (null == ouId) {
OrganizationalNodePractitionerWideReqParams param = new OrganizationalNodePractitionerWideReqParams();
param.setWorkspaceId(workspaceId);

View File

@ -143,6 +143,7 @@ public class MessageSendReqV3 implements Serializable {
for (PersonV3DTO receiver : receivers) {
AssertUtil.notNull(receiver.getId(), "接收者ID不能为空");
AssertUtil.isFalse(receiver.getId() <= 0, "接收者ID必须>=0");
}
}

View File

@ -0,0 +1,72 @@
package cn.axzo.msg.center.api.request.v3;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.BizFinalStateEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.util.Date;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
public class SearchPendingMessageReq {
private String promoterPhone;
private String executorPhone;
/**
* 消息的唯一标识
*/
private List<String> identityCodes;
/**
* 发起者的自然人ID
*/
private Long promoterPersonId;
/**
* 执行者的自然人ID
*/
private Long executorPersonId;
/**
* 模板编码
*/
private String templateCode;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 消息所属企业ID
*/
private Long ouId;
/**
* 待办状态
*/
private PendingMessageStateEnum state;
/**
* 关联业务主键
*/
private String bizCode;
/**
* 流程类代办的流程结点编码
*/
private String subBizCode;
/**
* 业务终态可为空
*/
private BizFinalStateEnum bizFinalState;
@JsonFormat(pattern = "yyyy-MM-hh HH:mm")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-hh HH:mm")
private Date endTime;
private int limit = 10;
}

View File

@ -77,6 +77,10 @@ public class MessageNewRes {
*/
private String router;
public void setRouter(String router) {
this.router = router;
}
/**
* 路由参数 JsonString
*/

View File

@ -0,0 +1,144 @@
package cn.axzo.msg.center.api.response.v3;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.BizFinalStateEnum;
import cn.axzo.msg.center.service.enums.IdentityTypeEnum;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @author yanglin
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SearchPendingMessageResp {
/**
* 消息的唯一标识
*/
private String identityCode;
/**
* 请求批次号
*/
private String requestNo;
/**
* 发起者ID
*/
private Long promoterId;
/**
* 发起者的自然人ID
*/
private Long promoterPersonId;
/**
* 发起者姓名
*/
private String promoterName;
/**
* 发起者身份
*/
private IdentityTypeEnum promoterType;
/**
* 执行者ID
*/
private Long executorId;
/**
* 执行者的自然人ID
*/
private Long executorPersonId;
/**
* 执行者姓名
*/
private String executorName;
/**
* 执行者身份
*/
private IdentityTypeEnum executorType;
/**
* 模板编码
*/
private String templateCode;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 消息所属组织类型
*/
private OrganizationTypeEnum orgType;
/**
* 消息所属组织Id
*/
private Long orgId;
/**
* 消息所属组织名称
*/
private String orgName;
/**
* 消息所属企业ID
*/
private Long ouId;
/**
* 待办状态
*/
private PendingMessageStateEnum state;
/**
* 业务类型
*/
private BizCategoryEnum bizCategory;
/**
* 关联业务主键
*/
private String bizCode;
/**
* 流程类代办的流程结点编码
*/
private String subBizCode;
/**
* 业务描述eg:流程结点描述
*/
private String bizDesc;
/**
* 业务标签
*/
private String bizFlag;
/**
* 业务扩展参数
*/
private String bizExtParam;
/**
* 路由参数留存
*/
private String routerParams;
/**
* 业务终态可为空
*/
private BizFinalStateEnum bizFinalState;
/**
* 待办的截止时间
*/
@JsonFormat(pattern = "yyyy-MM-hh HH:mm:ss")
private Date deadline;
/**
* 重试次数
*/
private Integer retryCount;
/**
* 最终失败原因
*/
private String failCause;
/**
* 在这个时间之前不显示
*/
@JsonFormat(pattern = "yyyy-MM-hh HH:mm:ss")
private Date hideUntil;
}

View File

@ -24,4 +24,7 @@ public enum OrganizationTypeEnum {
private final Integer code;
private final String message;
public String stringCode() {
return String.valueOf(code);
}
}

View File

@ -25,7 +25,14 @@ public class PendingMessageByBizCodeRequest {
/**
* 操作人ID
* <p>operatorPersonId和operatorIdentityId使用其中一个进行查询, operatorPersonId的优先级更高
*/
private Long operatorIdentityId;
/**
* 操作自然人id
* <p>operatorPersonId和operatorIdentityId使用其中一个进行查询, operatorPersonId的优先级更高
*/
private Long operatorPersonId;
}

View File

@ -9,6 +9,7 @@ import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -40,7 +41,7 @@ public class PendingMessagePushRequest implements Serializable {
/**
* 执行者
*/
@NotNull(message = "executor is required")
@NotEmpty(message = "executor can't be empty")
private List<PersonDTO> executor;
/**
* 模板编码

View File

@ -5,6 +5,7 @@ import cn.axzo.msg.center.service.dto.DetailRouterDTO;
import cn.axzo.msg.center.service.dto.IdentityDTO;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import com.alibaba.fastjson.JSON;
import lombok.*;
@ -138,6 +139,11 @@ public class PendingMessageResponse implements Serializable {
*/
private Date hideUntil;
/**
* 模版类型
*/
private MessageCategoryEnum templateCategory;
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -79,6 +79,11 @@ public class MessageRecord extends BaseEntity<MessageRecord> {
* 路由参数留存
*/
private String routerParams;
public String getRouterParams() {
return routerParams;
}
/**
* JSON 格式扩展信息
*/

View File

@ -74,8 +74,9 @@ public class MessageRecordV3 extends BaseEntityExt<MessageRecordV3> implements S
/**
* 消息所属组织类型 PROJECT:项目,ENT:企业,UNKNOWN:未知
* <p>这里使用string是为了兼容一些数据库数据没有值的情况
*/
private OrganizationTypeEnum receiverOrgType;
private String receiverOrgType;
/**
* 副标题

View File

@ -28,6 +28,10 @@
<groupId>cn.axzo.mns</groupId>
<artifactId>mns-http-api</artifactId>
</dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -0,0 +1,65 @@
package cn.axzo.msg.center.notices.client.config;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.visitor.SQLASTVisitor;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.core.Ordered;
import java.sql.Connection;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author yanglin
*/
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})})
public class BeautifulPaginationInterceptor implements Interceptor, Ordered {
private final PaginationInterceptor delegate = new PaginationInterceptor();
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()
|| StatementType.CALLABLE == mappedStatement.getStatementType()) {
return invocation.proceed();
}
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
SQLStatement selectStmt = new MySqlStatementParser(boundSql.getSql()).parseSelect();
AtomicBoolean limitPresent = new AtomicBoolean(false);
selectStmt.accept(new SQLASTVisitor() {
@Override
public boolean visit(SQLLimit x) {
limitPresent.set(true);
return false;
}
});
if (limitPresent.get())
return invocation.proceed();
else
return delegate.intercept(invocation);
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}

View File

@ -0,0 +1,82 @@
package cn.axzo.msg.center.notices.client.config;
import cn.axzo.trade.datasecurity.mybatisplus.interceptor.MybatisPlusCryptInterceptor;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.InterceptorChain;
import org.apache.ibatis.session.SqlSessionFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Set;
/**
* @author yanglin
*/
@Slf4j
@Component
public class MybatisInterceptorAnalyzeProcessor implements BeanPostProcessor {
private static final Set<Class<?>> EXPECTED_INTERCEPTORS = Sets.newHashSet(
MybatisPlusCryptInterceptor.class,
BeautifulPaginationInterceptor.class);
@Override
public Object postProcessBeforeInitialization(
@NotNull Object bean, @NotNull String beanName) throws BeansException {
if (bean instanceof SqlSessionFactory) {
try {
analyze((SqlSessionFactory) bean);
} catch (Exception e) {
log.warn("SqlSessionFactory analyze error", e);
}
}
return bean;
}
private void analyze(SqlSessionFactory sessionFactory) {
org.apache.ibatis.session.Configuration configuration = sessionFactory.getConfiguration();
InterceptorChain oldChain = getFieldValue(configuration, "interceptorChain");
List<Interceptor> oldInterceptors = getFieldValue(oldChain, "interceptors");
AnalyzeInterceptorChain newChain = new AnalyzeInterceptorChain();
for (Interceptor interceptor : oldInterceptors)
newChain.addInterceptor(interceptor);
setFieldValue(configuration, "interceptorChain", newChain);
}
@SuppressWarnings({"unchecked", "DataFlowIssue"})
private static <T> T getFieldValue(Object obj, String fieldName) {
Field field = ReflectionUtils.findField(obj.getClass(), fieldName);
field.setAccessible(true);
return (T)ReflectionUtils.getField(field, obj);
}
@SuppressWarnings("DataFlowIssue")
private static void setFieldValue(Object obj, String fieldName, Object value) {
Field field = ReflectionUtils.findField(obj.getClass(), fieldName);
Field modifiers = ReflectionUtils.findField(Field.class, "modifiers");
field.setAccessible(true);
modifiers.setAccessible(true);
ReflectionUtils.setField(modifiers, field, field.getModifiers() & ~Modifier.FINAL);
ReflectionUtils.setField(field, obj, value);
}
private static class AnalyzeInterceptorChain extends InterceptorChain {
@Override
public void addInterceptor(Interceptor interceptor) {
super.addInterceptor(interceptor);
if (EXPECTED_INTERCEPTORS.contains(interceptor.getClass()))
log.info("Adding interceptor={}", interceptor.getClass().getName());
else
log.warn("Unexpected interceptor={}", interceptor.getClass().getName(), new RuntimeException());
}
}
}

View File

@ -1,6 +1,5 @@
package cn.axzo.msg.center.notices.client.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -14,16 +13,9 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 默认不配置分页插件的会使用人RowBound进行分页实际上是逻辑分页物理上不会分页也就是查询出来
* 缓存中再分页这样对于数据量比较多的情况不合适因此需要配置这个分页拦截器来实现物理的分页
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置分页的最大条数
return paginationInterceptor;
public BeautifulPaginationInterceptor paginationInterceptor2() {
return new BeautifulPaginationInterceptor();
}
@Bean
@ -31,4 +23,4 @@ public class MybatisPlusConfig {
return new EntityMetaObjectHandler();
}
}
}

View File

@ -0,0 +1,73 @@
package cn.axzo.msg.center.notices.client.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.InterceptorChain;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PaginationConfig implements ApplicationListener<ContextRefreshedEvent> {
private final SqlSessionFactory sessionFactory;
private final ConfigurableListableBeanFactory beanFactory;
@Override
public void onApplicationEvent(@NotNull ContextRefreshedEvent event) {
try {
repair();
analyze();
} catch (Exception e) {
log.warn("failed to handle mybatis plugins", e);
}
}
@SuppressWarnings("unchecked")
private void repair() {
Configuration configuration = sessionFactory.getConfiguration();
Field chainField = ReflectionUtils.findField(configuration.getClass(), "interceptorChain");
if (chainField == null) return;
chainField.setAccessible(true);
InterceptorChain chain = (InterceptorChain) ReflectionUtils.getField(chainField, configuration);
Field interceptorsField = ReflectionUtils.findField(InterceptorChain.class, "interceptors");
if (interceptorsField == null) return;
interceptorsField.setAccessible(true);
List<Interceptor> interceptors = (List<Interceptor>) ReflectionUtils.getField(interceptorsField, chain);
if (interceptors == null) return;
for (Interceptor interceptor : new ArrayList<>(interceptors)) {
if (interceptor.getClass() == MybatisPlusInterceptor.class) {
log.info("prepare removing MybatisPlusInterceptor");
boolean removed = interceptors.remove(interceptor);
log.info("removed MybatisPlusInterceptor={}", removed);
}
}
}
private void analyze() {
Map<String, MybatisPlusInterceptor> name2Bean = beanFactory.getBeansOfType(MybatisPlusInterceptor.class);
for (String name : name2Bean.keySet()) {
BeanDefinition definition = beanFactory.getBeanDefinition(name);
log.warn("analyze mybatis interceptor bean resource: {}", definition.getResourceDescription());
}
}
}

View File

@ -0,0 +1,25 @@
package cn.axzo.msg.center.notices.client.util;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Layout;
import com.xxl.job.core.log.XxlJobFileAppender;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
public class XxlAppender<E> extends AppenderBase<E> {
private Layout<E> layout;
@Override
protected void append(E event) {
String logFileName = XxlJobFileAppender.contextHolder.get();
if (logFileName == null) return;
if (event == null) throw new RuntimeException("Can't find layout");
String encodedMessage = layout.doLayout(event);
XxlJobFileAppender.appendLog(logFileName, encodedMessage);
}
}

View File

@ -7,4 +7,15 @@
<springProfile name="local,dev">
<logger name="cn.axzo" level="DEBUG" />
</springProfile>
<appender name="XXL-JOB" class="cn.axzo.msg.center.notices.client.util.XxlAppender">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_FILE}</pattern>
</layout>
</appender>
<root level="INFO">
<appender-ref ref="XXL-JOB"/>
</root>
</configuration>

View File

@ -20,7 +20,7 @@ import java.util.Objects;
*/
@Slf4j
@Component
@Profile({"dev", "test", "local"})
@Profile({"dev", "test", "local", "pre"})
public class FeignConfig implements RequestInterceptor, EnvironmentAware {
private Environment environment;
@ -44,6 +44,8 @@ public class FeignConfig implements RequestInterceptor, EnvironmentAware {
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());
}
}
}