Merge branch 'feature/REQ-3045' into dev

This commit is contained in:
yanglin 2024-10-10 16:19:12 +08:00
commit 9075739bb7
19 changed files with 847 additions and 30 deletions

View File

@ -1,29 +1,27 @@
package cn.axzo.msg.center.message.controller; package cn.axzo.msg.center.message.controller;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.msg.center.message.domain.param.MessageTemplateV3SaveOrUpdateParam; import cn.axzo.msg.center.message.domain.param.MessageTemplateV3SaveOrUpdateParam;
import cn.axzo.msg.center.message.domain.param.RelationTemplateMapInitParam; import cn.axzo.msg.center.message.domain.param.RelationTemplateMapInitParam;
import cn.axzo.msg.center.message.domain.vo.RelationTemplateMapInitRequest; import cn.axzo.msg.center.message.domain.vo.RelationTemplateMapInitRequest;
import cn.axzo.msg.center.message.service.MessageTemplateNewService; import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.MessageTemplateNewSyncService;
import cn.axzo.msg.center.message.service.MessageTemplateV3Service; import cn.axzo.msg.center.message.service.MessageTemplateV3Service;
import cn.axzo.msg.center.message.service.RelationTemplateMapService; import cn.axzo.msg.center.message.service.RelationTemplateMapService;
import cn.axzo.msg.center.message.service.impl.MessageTemplateV3SyncService;
import cn.axzo.msg.center.service.template.client.MessageTemplateV3Client; import cn.axzo.msg.center.service.template.client.MessageTemplateV3Client;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3CreateRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateSyncDto;
import cn.axzo.msg.center.service.template.request.MessageTemplateSyncQueryRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateSyncQueryRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3CreateRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3DeleteRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3DeleteRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3PageRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3PageRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3SyncRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3SyncResponse;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateStatusRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateStatusRequest;
import cn.axzo.msg.center.service.template.response.MessageDetailStyle; import cn.axzo.msg.center.service.template.response.MessageDetailStyle;
import cn.axzo.msg.center.service.template.response.MessageTemplateDetailResponse;
import cn.axzo.msg.center.service.template.response.MessageTemplatePageResponse; import cn.axzo.msg.center.service.template.response.MessageTemplatePageResponse;
import cn.axzo.msg.center.service.template.response.MessageTemplateV3DetailResponse; import cn.axzo.msg.center.service.template.response.MessageTemplateV3DetailResponse;
import cn.axzo.msg.center.service.template.response.MessageTemplateV3PageResponse; import cn.axzo.msg.center.service.template.response.MessageTemplateV3PageResponse;
import cn.azxo.framework.common.model.CommonResponse; import cn.azxo.framework.common.model.CommonResponse;
import cn.azxo.framework.common.model.Page; import cn.azxo.framework.common.model.Page;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -51,7 +49,7 @@ public class MessageTemplateV3Controller implements MessageTemplateV3Client {
private final MessageTemplateNewService messageTemplateNewService; private final MessageTemplateNewService messageTemplateNewService;
private final RelationTemplateMapService relationTemplateMapService; private final RelationTemplateMapService relationTemplateMapService;
private final MessageTemplateNewSyncService messageTemplateNewSyncService; private final MessageTemplateV3SyncService messageTemplateV3SyncService;
private final MessageTemplateV3Service messageTemplateV3Service; private final MessageTemplateV3Service messageTemplateV3Service;
@Override @Override
@ -98,17 +96,6 @@ public class MessageTemplateV3Controller implements MessageTemplateV3Client {
return CommonResponse.success(); return CommonResponse.success();
} }
@Override
public CommonResponse<MessageTemplateSyncDto> getSyncMessageTemplate(
MessageTemplateSyncQueryRequest request) {
MessageTemplateDetailResponse detail = messageTemplateNewService.querySyncMessageTemplate(request.getTemplateCode());
AssertUtil.notNull(detail, "消息模板不存在");
MessageTemplateSyncDto result = BeanUtil.copyProperties(detail, MessageTemplateSyncDto.class);
result.setTemplateCode(request.getTemplateCode());
result.setEnv(request.getEnv());
return CommonResponse.success(result);
}
@Override @Override
public CommonResponse<List<MessageDetailStyle>> listTemplateDetailStyles() { public CommonResponse<List<MessageDetailStyle>> listTemplateDetailStyles() {
List<MessageDetailStyle> styles = messageTemplateNewService.listTemplateDetailStyles(); List<MessageDetailStyle> styles = messageTemplateNewService.listTemplateDetailStyles();
@ -116,10 +103,16 @@ public class MessageTemplateV3Controller implements MessageTemplateV3Client {
} }
@Override @Override
public CommonResponse<Void> syncTemplate(MessageTemplateSyncDto param) { public CommonResponse<MessageTemplateV3SyncResponse>
log.info("syncTemplate param= [{}]", JSONUtil.toJsonStr(param)); getSyncMessageTemplate(MessageTemplateSyncQueryRequest request) {
messageTemplateNewSyncService.syncTemplate(param); log.info("getSyncMessageTemplate param= [{}]", JSONUtil.toJsonStr(request));
return CommonResponse.success(); return CommonResponse.success(messageTemplateV3SyncService.getSyncMessageTemplate(request));
}
@Override
public CommonResponse<String> syncTemplate(MessageTemplateV3SyncRequest request) {
log.info("syncTemplate param= [{}]", JSONUtil.toJsonStr(request));
return CommonResponse.success(messageTemplateV3SyncService.syncTemplate(request));
} }
@PostMapping(value = "/message/template/relation/init/v3", produces = {MediaType.APPLICATION_JSON_VALUE}) @PostMapping(value = "/message/template/relation/init/v3", produces = {MediaType.APPLICATION_JSON_VALUE})

View File

@ -0,0 +1,26 @@
package cn.axzo.msg.center.message.domain.dto;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
public class MessageTemplateV3Model {
private MessageTemplateV3 template;
private List<MessageTemplateGroupV3> groups;
private List<MessageTemplateButtonV3> buttons;
public String getTemplateCode() {
return template.getCode();
}
}

View File

@ -0,0 +1,92 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.msg.center.dal.MessageTemplateButtonV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateGroupV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateV3Dao;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateV3Model;
import cn.axzo.msg.center.service.enums.StatusEnum;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Service
@RequiredArgsConstructor
public class MessageTemplateV3ModelService {
private final MessageTemplateV3Dao messageTemplateV3Dao;
private final MessageTemplateGroupV3Dao messageTemplateGroupV3Dao;
private final MessageTemplateButtonV3Dao messageTemplateButtonV3Dao;
public Optional<MessageTemplateV3Model> findEnabledByCode(String templateCode) {
MessageTemplateV3 template = messageTemplateV3Dao.lambdaQuery()
.eq(MessageTemplateV3::getCode, templateCode)
.eq(MessageTemplateV3::getIsDelete, 0)
.eq(MessageTemplateV3::getStatus, StatusEnum.ENABLE)
.one();
if (template == null)
return Optional.empty();
ModelBuilder builder = new ModelBuilder(templateCode);
return Optional.of(builder.build(template));
}
public List<MessageTemplateV3Model> getByCodes(List<String> templateCodes) {
if (CollectionUtils.isEmpty(templateCodes))
return Collections.emptyList();
List<MessageTemplateV3> templates = messageTemplateV3Dao.lambdaQuery()
.in(MessageTemplateV3::getCode, templateCodes)
.eq(MessageTemplateV3::getIsDelete, 0)
.list();
ModelBuilder builder = new ModelBuilder(templateCodes.toArray(new String[0]));
return templates.stream()
.map(builder::build)
.collect(toList());
}
@RequiredArgsConstructor
private class ModelBuilder {
final Map<String, List<MessageTemplateGroupV3>> templateCode2Groups;
final Map<String, List<MessageTemplateButtonV3>> templateCode2Buttons;
ModelBuilder(String... templateCodes) {
if (templateCodes.length > 0) {
templateCode2Groups = messageTemplateGroupV3Dao.lambdaQuery()
.in(MessageTemplateGroupV3::getTemplateCode, Arrays.asList(templateCodes))
.list().stream()
.collect(groupingBy(MessageTemplateGroupV3::getTemplateCode));
templateCode2Buttons = messageTemplateButtonV3Dao.lambdaQuery()
.in(MessageTemplateButtonV3::getTemplateCode, Arrays.asList(templateCodes))
.list().stream()
.collect(groupingBy(MessageTemplateButtonV3::getTemplateCode));
} else {
templateCode2Groups = Collections.emptyMap();
templateCode2Buttons = Collections.emptyMap();
}
}
MessageTemplateV3Model build(MessageTemplateV3 template) {
MessageTemplateV3Model model = new MessageTemplateV3Model();
model.setTemplate(template);
model.setGroups(templateCode2Groups.getOrDefault(template.getCode(), Collections.emptyList()));
model.setButtons(templateCode2Buttons.getOrDefault(template.getCode(), Collections.emptyList()));
return model;
}
}
}

View File

@ -0,0 +1,91 @@
package cn.axzo.msg.center.message.service.impl;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.dal.MessageTemplateButtonV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateGroupV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateV3Dao;
import cn.axzo.msg.center.domain.entity.BaseEntityWithOperator;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.service.template.request.MessageTemplateSyncQueryRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3SyncRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3SyncResponse;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author yanglin
*/
@Service
@RequiredArgsConstructor
public class MessageTemplateV3SyncService {
private final MessageTemplateV3Dao messageTemplateV3Dao;
private final MessageTemplateGroupV3Dao messageTemplateGroupV3Dao;
private final MessageTemplateButtonV3Dao messageTemplateButtonV3Dao;
public MessageTemplateV3SyncResponse getSyncMessageTemplate(MessageTemplateSyncQueryRequest request) {
MessageTemplateV3 template = findTemplate(request.getTemplateCode());
BizAssertions.assertNotNull(template, "未找到需要同步的模版: {}", request.getTemplateCode());
List<MessageTemplateGroupV3> groups = messageTemplateGroupV3Dao.lambdaQuery()
.eq(MessageTemplateGroupV3::getTemplateCode, template.getCode())
.list();
List<MessageTemplateButtonV3> buttons = messageTemplateButtonV3Dao.lambdaQuery()
.eq(MessageTemplateButtonV3::getTemplateCode, template.getCode())
.list();
MessageTemplateV3SyncResponse resp = new MessageTemplateV3SyncResponse();
JSONObject respJson = new JSONObject();
respJson.put("template", template);
respJson.put("groups", groups);
respJson.put("buttons", buttons);
resp.setTemplate(respJson);
return resp;
}
/**
* @return error
*/
public String syncTemplate(MessageTemplateV3SyncRequest request) {
MessageTemplateV3 template = request.getTemplate()
.getObject("template", MessageTemplateV3.class);
MessageTemplateV3 savedTemplate = findTemplate(template.getCode());
if (savedTemplate != null) return "模板已存在";
List<MessageTemplateGroupV3> groups = request.getTemplate()
.getObject("groups", new TypeReference<List<MessageTemplateGroupV3>>() {});
List<MessageTemplateButtonV3> buttons = request.getTemplate()
.getObject("groups", new TypeReference<List<MessageTemplateButtonV3>>() {});
setBasicProps(template, request.getOperatorId(), request.getOperatorName());
groups.forEach(group -> setBasicProps(group, request.getOperatorId(), request.getOperatorName()));
buttons.forEach(button -> setBasicProps(button, request.getOperatorId(), request.getOperatorName()));
messageTemplateV3Dao.save(template);
if (CollectionUtils.isNotEmpty(groups))
messageTemplateGroupV3Dao.saveBatch(groups);
if (CollectionUtils.isNotEmpty(buttons))
messageTemplateButtonV3Dao.saveBatch(buttons);
return null;
}
private MessageTemplateV3 findTemplate(String templateCode) {
return messageTemplateV3Dao.lambdaQuery()
.eq(MessageTemplateV3::getCode, templateCode)
.one();
}
private void setBasicProps(BaseEntityWithOperator<?> entity, Long operatorId, String operatorName) {
entity.setCreateAt(null);
entity.setUpdateAt(null);
entity.setCreatePersonId(operatorId);
entity.setCreatePersonName(operatorName);
entity.setUpdatePersonId(operatorId);
entity.setUpdatePersonName(operatorName);
}
}

View File

@ -0,0 +1,378 @@
package cn.axzo.msg.center.message.xxl;
import cn.axzo.maokai.api.util.Ref;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.msg.center.dal.MessageBaseTemplateDao;
import cn.axzo.msg.center.dal.MessageTemplateButtonV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateGroupDao;
import cn.axzo.msg.center.dal.MessageTemplateGroupV3Dao;
import cn.axzo.msg.center.dal.MessageTemplateV3Dao;
import cn.axzo.msg.center.domain.entity.BaseEntityWithOperator;
import cn.axzo.msg.center.domain.entity.MessageBaseTemplate;
import cn.axzo.msg.center.domain.entity.MessageTemplateButtonV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroup;
import cn.axzo.msg.center.domain.entity.MessageTemplateGroupV3;
import cn.axzo.msg.center.domain.entity.MessageTemplateV3;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO.MessageRouteButtonDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO.MessageRouteDetailDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO.MessageRouterConfigDTO;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.group.GroupTemplateService;
import cn.axzo.msg.center.message.service.group.NodeWrapper;
import cn.axzo.msg.center.message.service.group.RootNodeWrapper;
import cn.axzo.msg.center.service.domain.CardUrlConfig;
import cn.axzo.msg.center.service.domain.DetailConfig;
import cn.axzo.msg.center.service.domain.GroupConfig;
import cn.axzo.msg.center.service.domain.KVConfig;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.domain.url.AppUrl;
import cn.axzo.msg.center.service.domain.url.WebUrl;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import cn.axzo.msg.center.service.enums.BizDetailShowStrategyEnum;
import cn.axzo.msg.center.service.enums.CardUrlOpenStrategy;
import cn.axzo.msg.center.service.enums.GroupType;
import cn.axzo.msg.center.service.enums.KVContentType;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
import cn.axzo.msg.center.service.enums.PushTerminalEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.service.enums.WebPageOpenStrategy;
import cn.axzo.msg.center.service.enums.YesOrNo;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import com.alibaba.fastjson.JSON;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MigrateMessageTemplateV3Job {
private final TransactionTemplate transactionTemplate;
private final MessageBaseTemplateDao messageBaseTemplateDao;
private final MessageTemplateNewService messageTemplateNewService;
private final MessageTemplateV3Dao messageTemplateV3Dao;
private final GroupTemplateService groupTemplateService;
private final MessageTemplateGroupV3Dao messageTemplateGroupV3Dao;
private final MessageTemplateButtonV3Dao messageTemplateButtonV3Dao;
private final MessageTemplateGroupDao messageTemplateGroupDao;
private final PendingMessageBizConfig pendingMessageBizConfig;
@SuppressWarnings("unused")
@XxlJob("migrateMessageTemplateV3Job")
public ReturnT<String> execute(String jsonStr) {
try {
Param param = StringUtils.isBlank(jsonStr)
? new Param()
: JSONObjectUtil.parseObject(jsonStr, Param.class);
return executeImpl(param);
} catch (Exception e) {
log.warn("job failed", e);
throw e;
}
}
private ReturnT<String> executeImpl(Param param) {
RootNodeWrapper groupRoot = groupTemplateService.getGroupRoot();
List<MessageBaseTemplate> baseTemplates = messageBaseTemplateDao.lambdaQuery()
.eq(MessageBaseTemplate::getIsDelete, 0)
.in(CollectionUtils.isNotEmpty(param.getTemplateCodes()),
MessageBaseTemplate::getCode, param.getTemplateCodes())
.list();
for (int i = 0; i < baseTemplates.size(); i++) {
MessageBaseTemplate baseTemplate = baseTemplates.get(i);
log.info("migrating template: {}/{}", i + 1, baseTemplates.size());
transactionTemplate.executeWithoutResult(unused -> migrateTemplate(groupRoot, baseTemplate));
}
return ReturnT.SUCCESS;
}
private void migrateTemplate(RootNodeWrapper groupRoot, MessageBaseTemplate baseTemplate) {
MessageTemplateDTO baseTemplateModel = messageTemplateNewService
.listByTemplateCodes(Collections.singletonList(baseTemplate.getCode()))
.get(0);
MessageTemplateV3 templateV3 = new MessageTemplateV3();
templateV3.setName(baseTemplate.getName());
templateV3.setCode(baseTemplate.getCode());
templateV3.setMsgCategory(baseTemplate.getMsgCategory());
templateV3.setTitle(baseTemplate.getTitle());
templateV3.setContent(baseTemplate.getContent());
templateV3.setDetailStyleCode(baseTemplate.getDetailStyleCode());
templateV3.setIcon(baseTemplate.getIcon());
templateV3.setMinAppVersion(baseTemplate.getMinAppVersion());
templateV3.setStatus(baseTemplate.getStatus());
templateV3.setPushTerminal(baseTemplate.getPushTerminal());
templateV3.setPushData(baseTemplate.getPushData());
templateV3.setImSendPriority(baseTemplate.getImSendPriority());
templateV3.setDisplayOnList(YesOrNo.YES);
templateV3.setIsDelete(0L);
templateV3.setCreateAt(baseTemplate.getCreateAt());
templateV3.getOrCreateRecordExt().setIsMigrated(true);
setOperatorAsSystem(templateV3);
Runnable defaultCardUrlSetter = () -> templateV3.setCardUrlOpenStrategy(
baseTemplate.getMsgCategory() == MessageCategoryEnum.GENERAL_MESSAGE
? CardUrlOpenStrategy.NONE
: CardUrlOpenStrategy.OPEN_TODO_DETAIL);
List<MessageTemplateButtonV3> buttons = null;
MessageTemplateRouterDTO baseTemplateRouter = baseTemplateModel.getMsgTemplateRouter();
if (baseTemplateRouter != null) {
if (baseTemplateRouter.getRouteDetail() != null)
configureCardUrl(groupRoot, baseTemplate, baseTemplateRouter.getRouteDetail(), templateV3);
else
defaultCardUrlSetter.run();
if (baseTemplateRouter.getRouteButtons() != null)
buttons = migrateButtons(groupRoot, baseTemplate, baseTemplateRouter.getRouteButtons());
} else {
defaultCardUrlSetter.run();
}
MessageTemplateGroupV3 group = migrateGroups(groupRoot, baseTemplate, baseTemplateModel);
messageTemplateV3Dao.save(templateV3);
if (CollectionUtils.isNotEmpty(buttons))
messageTemplateButtonV3Dao.saveBatch(buttons);
if (group != null)
messageTemplateGroupV3Dao.save(group);
}
private void configureCardUrl(RootNodeWrapper groupRoot,
MessageBaseTemplate baseTemplate,
MessageRouteDetailDTO routeDetail,
MessageTemplateV3 templateV3) {
if (baseTemplate.getMsgCategory() == MessageCategoryEnum.GENERAL_MESSAGE) {
templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_CUSTOM_PAGE);
} else {
if (routeDetail.getShowStrategy() == BizDetailShowStrategyEnum.INLINE)
templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_TODO_DETAIL);
else
templateV3.setCardUrlOpenStrategy(CardUrlOpenStrategy.OPEN_CUSTOM_PAGE);
}
CardUrlConfig cardUrConfig = new CardUrlConfig();
cardUrConfig.setUrlConfig(buildUrlConfig(groupRoot, baseTemplate, routeDetail.getRouterConfigs()));
templateV3.setCardUrlConfig(cardUrConfig);
}
private List<MessageTemplateButtonV3> migrateButtons(RootNodeWrapper groupRoot,
MessageBaseTemplate baseTemplate,
List<MessageRouteButtonDTO> routeButtons) {
if (CollectionUtils.isEmpty(routeButtons)) return Collections.emptyList();
ArrayList<MessageTemplateButtonV3> buttons = new ArrayList<>();
for (MessageRouteButtonDTO routeButton : routeButtons) {
MessageTemplateButtonV3 button = new MessageTemplateButtonV3();
button.setName(routeButton.getName());
button.setTemplateCode(baseTemplate.getCode());
button.setPresetBtnType(routeButton.getPresetButtonType());
button.setSource(routeButton.getSource());
button.setCategory(routeButton.getCategory());
button.setApiUrl(routeButton.getApiUrl());
button.setUrlConfig(buildUrlConfig(groupRoot, baseTemplate, routeButton.getRouterConfigs()));
button.setStyle(routeButton.getStyle());
button.setExecutorShow(routeButton.getExecutorShow());
button.setPendingShow(routeButton.getPendingShow());
button.setPriority(routeButton.getPriority());
button.getOrCreateRecordExt().setBtnCode(routeButton.getBtnCode());
button.getOrCreateRecordExt().setIsMigrated(true);
setOperatorAsSystem(button);
buttons.add(button);
}
return buttons;
}
private MessageTemplateGroupV3 migrateGroups(RootNodeWrapper groupRoot,
MessageBaseTemplate baseTemplate,
MessageTemplateDTO baseTemplateModel) {
if (StringUtils.isBlank(baseTemplate.getCardContent())) return null;
List<MessageCardContentItemDTO> cardItems = JSONObjectUtil
.parseArray(baseTemplate.getCardContent(), MessageCardContentItemDTO.class);
cardItems = cardItems.stream()
// 工人卡片的去掉产品说他去配置
.filter(cardItem -> !cardItem.getLabel().equals("customerPersonId"))
.collect(toList());
if (CollectionUtils.isEmpty(cardItems)) return null;
GroupConfig groupConfig = new GroupConfig();
groupConfig.setKeyValues(new ArrayList<>());
for (int i = 0; i < cardItems.size(); i++) {
MessageCardContentItemDTO cardItem = cardItems.get(i);
KVConfig kv = new KVConfig();
kv.setContentType(KVContentType.TEXT);
kv.setKey(cardItem.getLabel());
kv.setValue(cardItem.getValue());
kv.setDisplayOnCard(i < 3);
groupConfig.getKeyValues().add(kv);
}
Supplier<MessageRouteDetailDTO> detailFactory = () -> {
MessageTemplateRouterDTO templateRouter = baseTemplateModel.getMsgTemplateRouter();
if (templateRouter == null) return null;
return templateRouter.getRouteDetail();
};
if (detailFactory.get() != null
&& detailFactory.get().getShowStrategy() == BizDetailShowStrategyEnum.JUMP_TO) {
DetailConfig detailConfig = new DetailConfig();
detailConfig.setName("查看详情");
detailConfig.setUrlConfig(buildUrlConfig(
groupRoot, baseTemplate, detailFactory.get().getRouterConfigs()));
groupConfig.setDetail(detailConfig);
}
MessageTemplateGroupV3 group = new MessageTemplateGroupV3();
group.setName("基本信息");
group.setTemplateCode(baseTemplate.getCode());
group.setSortOrder(1);
group.setGroupType(GroupType.KV_VALUES);
group.setGroupConfig(groupConfig);
group.getOrCreateRecordExt().setIsMigrated(true);
setOperatorAsSystem(group);
return group;
}
private UrlConfig buildUrlConfig(RootNodeWrapper groupRoot,
MessageBaseTemplate baseTemplate,
List<MessageRouterConfigDTO> routers) {
UrlConfig urlConfig = new UrlConfig();
Ref<Boolean> propsSet = Ref.create(false);
searchRouter(routers, TerminalTypeEnum.WEB).ifPresent(router -> {
WebUrl pcCms = new WebUrl();
pcCms.setOpenStrategy(WebPageOpenStrategy.NEW_PAGE);
pcCms.setUrl(router.getUrl());
urlConfig.setPcCms(pcCms);
propsSet.set(true);
});
List<PushTerminalEnum> terminals = determineTerminalTypes(groupRoot, baseTemplate);
searchRouter(routers, TerminalTypeEnum.IOS).ifPresent(router -> {
AppUrl ios = new AppUrl();
ios.setUrl(router.getUrl());
if (terminals.contains(PushTerminalEnum.B_ENTERPRISE_APP)) {
urlConfig.getOrCreateAppManager().setIos(ios);
propsSet.set(true);
}
if (terminals.contains(PushTerminalEnum.C_WORKER_APP)) {
urlConfig.getOrCrateAppWorker().setIos(ios);
propsSet.set(true);
}
});
searchRouter(routers, TerminalTypeEnum.ANDROID).ifPresent(router -> {
AppUrl android = new AppUrl();
android.setUrl(router.getUrl());
if (terminals.contains(PushTerminalEnum.B_ENTERPRISE_APP)) {
urlConfig.getOrCreateAppManager().setAndroid(android);
propsSet.set(true);
}
if (terminals.contains(PushTerminalEnum.C_WORKER_APP)) {
urlConfig.getOrCrateAppWorker().setAndroid(android);
propsSet.set(true);
}
});
searchRouter(routers,
TerminalTypeEnum.WEB_VIEW,
TerminalTypeEnum.MINI_PROGRAM,
TerminalTypeEnum.WECHAT_MINI_PROGRAM).ifPresent(router -> {
AppUrl appUrl = new AppUrl();
appUrl.setUrl(router.getUrl());
if (terminals.contains(PushTerminalEnum.B_ENTERPRISE_APP)) {
urlConfig.getOrCreateAppManager().setIos(appUrl);
urlConfig.getOrCreateAppManager().setAndroid(appUrl);
propsSet.set(true);
}
if (terminals.contains(PushTerminalEnum.C_WORKER_APP)) {
urlConfig.getOrCrateAppWorker().setIos(appUrl);
urlConfig.getOrCrateAppWorker().setAndroid(appUrl);
propsSet.set(true);
}
});
return propsSet.get() ? urlConfig : null;
}
private Optional<MessageRouterConfigDTO> searchRouter(List<MessageRouterConfigDTO> routers,
TerminalTypeEnum... terminalTypes) {
List<TerminalTypeEnum> terminalTypesList = Arrays.asList(terminalTypes);
return routers.stream()
.filter(r -> terminalTypesList.contains(r.getTerminalType()))
.findFirst();
}
private List<PushTerminalEnum> determineTerminalTypes(RootNodeWrapper groupRoot,
MessageBaseTemplate baseTemplate) {
if (baseTemplate.getMsgCategory() == MessageCategoryEnum.GENERAL_MESSAGE) {
return JSON.parseArray(baseTemplate.getPushTerminal(), PushTerminalEnum.class);
}
Set<String> paths = messageTemplateGroupDao.lambdaQuery()
.eq(MessageTemplateGroup::getTemplateCode, baseTemplate.getCode())
.list().stream()
.map(MessageTemplateGroup::getPath)
.collect(Collectors.toSet());
Map<AppTerminalTypeEnum, List<Long>> appType2NodeIds = pendingMessageBizConfig.getMsgGroupConfig();
Function<List<AppTerminalTypeEnum>, Boolean> appSelector = appTypes -> {
for (Map.Entry<AppTerminalTypeEnum, List<Long>> e : appType2NodeIds.entrySet()) {
if (!appTypes.contains(e.getKey()))
continue;
for (Long parentNodeId : e.getValue()) {
ValueNode<NodeWrapper> parentNode = groupRoot
.findValueNode(parentNodeId).orElse(null);
if (parentNode == null) continue;
String code = parentNode.getValue().unwrap().getCode();
if (paths.stream().anyMatch(path -> path.contains(code))) return true;
}
}
return false;
};
ArrayList<PushTerminalEnum> terminals = new ArrayList<>();
if (appSelector.apply(Collections.singletonList(
AppTerminalTypeEnum.C_WORKER_APP)))
terminals.add(PushTerminalEnum.C_WORKER_APP);
if (appSelector.apply(Arrays.asList(
AppTerminalTypeEnum.B_ENTERPRISE_APP, AppTerminalTypeEnum.CMS_WEB_PC)))
terminals.add(PushTerminalEnum.B_ENTERPRISE_APP);
return terminals;
}
private void setOperatorAsSystem(BaseEntityWithOperator<?> entity) {
entity.setCreatePersonId(0L);
entity.setCreatePersonName("system");
entity.setUpdatePersonId(0L);
entity.setUpdatePersonName("system");
}
@Setter
@Getter
public static class Param {
private Set<String> templateCodes;
}
}

View File

@ -1,10 +1,18 @@
package cn.axzo.msg.center.service.domain; package cn.axzo.msg.center.service.domain;
import cn.axzo.msg.center.service.domain.parse.TerminalUrl;
import cn.axzo.msg.center.service.domain.parse.TerminalUrlParser;
import cn.axzo.msg.center.service.domain.url.WebUrl; import cn.axzo.msg.center.service.domain.url.WebUrl;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
/** /**
* <a href="https://docs.dingtalk.com/i/nodes/14lgGw3P8v9Yk4yaF0aAo9NbJ5daZ90D">REQ-3045</a>
*
* @author yanglin * @author yanglin
*/ */
@Setter @Setter
@ -36,4 +44,22 @@ public class UrlConfig {
*/ */
private MobileUrlConfig appManager; private MobileUrlConfig appManager;
@JSONField(serialize = false)
public MobileUrlConfig getOrCrateAppWorker() {
if (appWorker == null)
appWorker = new MobileUrlConfig();
return appWorker;
}
@JSONField(serialize = false)
public MobileUrlConfig getOrCreateAppManager() {
if (appManager == null)
appManager = new MobileUrlConfig();
return appManager;
}
public List<TerminalUrl> parse(AppTerminalTypeEnum appType) {
return new TerminalUrlParser(this, appType).parse();
}
} }

View File

@ -0,0 +1,36 @@
package cn.axzo.msg.center.service.domain.parse;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
@NoArgsConstructor
public class TerminalUrl {
public TerminalUrl(TerminalTypeEnum terminalType, String url) {
this.terminalType = terminalType;
this.url = url;
}
/**
* 路由编码
*/
private String routerCode;
/**
* 路由的系统类型
*/
private TerminalTypeEnum terminalType;
/**
* 路由地址
*/
private String url;
}

View File

@ -0,0 +1,73 @@
package cn.axzo.msg.center.service.domain.parse;
import cn.axzo.msg.center.service.domain.MobileUrlConfig;
import cn.axzo.msg.center.service.domain.UrlConfig;
import cn.axzo.msg.center.service.domain.url.WebUrl;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static cn.axzo.msg.center.service.enums.AppTerminalTypeEnum.B_ENTERPRISE_APP;
import static cn.axzo.msg.center.service.enums.AppTerminalTypeEnum.CMS_WEB_PC;
import static cn.axzo.msg.center.service.enums.AppTerminalTypeEnum.C_WORKER_APP;
import static cn.axzo.msg.center.service.enums.AppTerminalTypeEnum.OMS_WEB_PC;
import static cn.axzo.msg.center.service.enums.AppTerminalTypeEnum.PC_GA_GENERAL;
/**
* @author yanglin
*/
@RequiredArgsConstructor
public class TerminalUrlParser {
private final UrlConfig config;
private final AppTerminalTypeEnum requestType;
public List<TerminalUrl> parse() {
ArrayList<TerminalUrl> urls = new ArrayList<>();
// !! 只会命中其一
// PC(CMS)
selectWeb(CMS_WEB_PC, config.getPcCms()).ifPresent(urls::add);
// PC(OMS)
selectWeb(OMS_WEB_PC, config.getPcOms()).ifPresent(urls::add);
// PC(政务系统)
selectWeb(PC_GA_GENERAL, config.getPcGaGeneral()).ifPresent(urls::add);
// !! 只会命中其一
// APP工人端
urls.addAll(selectApp(C_WORKER_APP, config.getAppWorker()));
// APP管理端
urls.addAll(selectApp(B_ENTERPRISE_APP, config.getAppManager()));
return urls;
}
private Optional<TerminalUrl> selectWeb(
AppTerminalTypeEnum selectType, WebUrl webUrl) {
if (requestType != selectType)
return Optional.empty();
if (webUrl == null)
return Optional.empty();
return Optional.of(new TerminalUrl(TerminalTypeEnum.WEB, webUrl.getUrl()));
}
private List<TerminalUrl> selectApp(
AppTerminalTypeEnum selectType, MobileUrlConfig mobileUrl) {
if (requestType != selectType)
return Collections.emptyList();
if (mobileUrl == null)
return Collections.emptyList();
ArrayList<TerminalUrl> urls = new ArrayList<>();
urls.add(new TerminalUrl(TerminalTypeEnum.IOS, mobileUrl.getIos().getUrl()));
urls.add(new TerminalUrl(TerminalTypeEnum.ANDROID, mobileUrl.getAndroid().getUrl()));
return urls;
}
}

View File

@ -1,7 +1,9 @@
package cn.axzo.msg.center.service.domain.url; package cn.axzo.msg.center.service.domain.url;
import cn.axzo.msg.center.service.enums.WebPageOpenStrategy; import cn.axzo.msg.center.service.enums.WebPageOpenStrategy;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
/** /**

View File

@ -5,6 +5,8 @@ import cn.axzo.msg.center.service.template.request.MessageTemplateSyncDto;
import cn.axzo.msg.center.service.template.request.MessageTemplateSyncQueryRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateSyncQueryRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3DeleteRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3DeleteRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3PageRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3PageRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3SyncRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3SyncResponse;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateRequest;
import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateStatusRequest; import cn.axzo.msg.center.service.template.request.MessageTemplateV3UpdateStatusRequest;
import cn.axzo.msg.center.service.template.response.MessageDetailStyle; import cn.axzo.msg.center.service.template.response.MessageDetailStyle;
@ -96,14 +98,14 @@ public interface MessageTemplateV3Client {
* 查询消息模板 * 查询消息模板
*/ */
@PostMapping(value = "/message/template/v3/sync/get", produces = {MediaType.APPLICATION_JSON_VALUE}) @PostMapping(value = "/message/template/v3/sync/get", produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<MessageTemplateSyncDto> getSyncMessageTemplate(@RequestBody @Valid MessageTemplateSyncQueryRequest request); CommonResponse<MessageTemplateV3SyncResponse> getSyncMessageTemplate(@RequestBody @Valid MessageTemplateSyncQueryRequest request);
/** /**
* 同步消息模板 * 同步消息模板
* req-1896 说明仅pre环境能同步 * req-1896 说明仅pre环境能同步
*/ */
@PostMapping(value = "/message/template/v3/sync", produces = {MediaType.APPLICATION_JSON_VALUE}) @PostMapping(value = "/message/template/v3/sync", produces = {MediaType.APPLICATION_JSON_VALUE})
CommonResponse<Void> syncTemplate(@RequestBody @Valid MessageTemplateSyncDto request); CommonResponse<String> syncTemplate(@RequestBody @Valid MessageTemplateV3SyncRequest request);
/** /**
* 获取所有消息样式 * 获取所有消息样式

View File

@ -0,0 +1,16 @@
package cn.axzo.msg.center.service.template.request;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class MessageTemplateV3SyncRequest {
private JSONObject template;
private Long operatorId;
private String operatorName;
}

View File

@ -0,0 +1,14 @@
package cn.axzo.msg.center.service.template.request;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class MessageTemplateV3SyncResponse {
private JSONObject template;
}

View File

@ -23,7 +23,6 @@ public class MessageTemplateV3UpdateStatusRequest implements Serializable {
/** /**
* 操作者的自然人id * 操作者的自然人id
*/ */
@NotNull(message = "operatorId is required")
private Long operatorId; private Long operatorId;
/** /**
* 模板编码 * 模板编码

View File

@ -77,7 +77,7 @@ public class MessageTemplateButtonV3 extends BaseEntityWithOperator<MessageTempl
*/ */
private Integer priority; private Integer priority;
@TableField(typeHandler = FastjsonTypeHandler.class) @TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private RecordExt recordExt; private RecordExt recordExt;
public RecordExt getOrCreateRecordExt() { public RecordExt getOrCreateRecordExt() {
@ -89,6 +89,7 @@ public class MessageTemplateButtonV3 extends BaseEntityWithOperator<MessageTempl
@Getter @Getter
public static class RecordExt { public static class RecordExt {
private String btnCode; private String btnCode;
private Boolean isMigrated;
} }
} }

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.domain.entity; package cn.axzo.msg.center.domain.entity;
import cn.axzo.msg.center.domain.utils.IgnorePropsJsonTypeHandler;
import cn.axzo.msg.center.service.domain.GroupConfig; import cn.axzo.msg.center.service.domain.GroupConfig;
import cn.axzo.msg.center.service.enums.GroupType; import cn.axzo.msg.center.service.enums.GroupType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
@ -39,7 +40,26 @@ public class MessageTemplateGroupV3 extends BaseEntityWithOperator<MessageTempla
/** /**
* 分组配置 * 分组配置
*/ */
@TableField(typeHandler = FastjsonTypeHandler.class) @TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private GroupConfig groupConfig; private GroupConfig groupConfig;
@TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private RecordExt recordExt;
public RecordExt getOrCreateRecordExt() {
if (recordExt == null) recordExt = new RecordExt();
return recordExt;
}
public GroupConfig getOrCreateGroupConfig() {
if (groupConfig == null)
groupConfig = new GroupConfig();
return groupConfig;
}
@Setter
@Getter
public static class RecordExt {
private Boolean isMigrated;
}
} }

View File

@ -1,5 +1,6 @@
package cn.axzo.msg.center.domain.entity; package cn.axzo.msg.center.domain.entity;
import cn.axzo.msg.center.domain.utils.IgnorePropsJsonTypeHandler;
import cn.axzo.msg.center.service.domain.CardUrlConfig; import cn.axzo.msg.center.service.domain.CardUrlConfig;
import cn.axzo.msg.center.service.enums.CardUrlOpenStrategy; import cn.axzo.msg.center.service.enums.CardUrlOpenStrategy;
import cn.axzo.msg.center.service.enums.MessageCategoryEnum; import cn.axzo.msg.center.service.enums.MessageCategoryEnum;
@ -80,7 +81,7 @@ public class MessageTemplateV3 extends BaseEntityWithOperator<MessageTemplateV3>
/** /**
* 卡片跳转配置. 在links的基础上再包一层, 避免以后卡片增加自己的配置 * 卡片跳转配置. 在links的基础上再包一层, 避免以后卡片增加自己的配置
*/ */
@TableField(typeHandler = FastjsonTypeHandler.class) @TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private CardUrlConfig cardUrlConfig; private CardUrlConfig cardUrlConfig;
/** /**
@ -98,4 +99,22 @@ public class MessageTemplateV3 extends BaseEntityWithOperator<MessageTemplateV3>
* 是否显示在列表中. YES: , NO: * 是否显示在列表中. YES: , NO:
*/ */
private YesOrNo displayOnList; private YesOrNo displayOnList;
/**
* 扩展字段
*/
@TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private RecordExt recordExt;
public RecordExt getOrCreateRecordExt() {
if (recordExt == null) recordExt = new RecordExt();
return recordExt;
}
@Setter
@Getter
public static class RecordExt {
private Boolean isMigrated;
}
} }

View File

@ -1,6 +1,7 @@
package cn.axzo.msg.center.domain.utils; package cn.axzo.msg.center.domain.utils;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -29,7 +30,7 @@ public class IgnorePropsJsonTypeHandler extends AbstractJsonTypeHandler<Object>
@Override @Override
protected String toJson(Object obj) { protected String toJson(Object obj) {
return JSON.toJSONString(obj); return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);
} }
} }

View File

@ -26,7 +26,7 @@ public class MybatisPlusConfig {
return new EntityMetaObjectHandler(); return new EntityMetaObjectHandler();
} }
@Component //@Component
public static class JsonObjectTypeHandlerRegister implements BeanPostProcessor { public static class JsonObjectTypeHandlerRegister implements BeanPostProcessor {
@Override @Override

View File

@ -0,0 +1,28 @@
package cn.axzo.msg.center.message.xxl;
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;
import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author yanglin
*/
@SpringBootTest(classes = MsgCenterApplication.class)
@RequiredArgsConstructor(onConstructor_ = @Autowired)
class MigrateMessageTemplateV3JobTest {
private final MigrateMessageTemplateV3Job migrateMessageTemplateV3Job;
@Test @Rollback @Transactional
void exec() {
migrateMessageTemplateV3Job.execute("{\"templateCodes\":[\"1c7effbba987411fbb6719619d2f5f07\"]}");
}
}