feat(2227-syncFeature): feature同步实现

This commit is contained in:
zhansihu 2024-04-07 14:40:23 +08:00
parent 6cf46dba73
commit bffda7fbc0
12 changed files with 165 additions and 61 deletions

View File

@ -1,12 +1,14 @@
package cn.axzo.tyr.client.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.tyr.client.model.req.ResourceSyncReq;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import java.util.List;
/**
@ -27,5 +29,5 @@ public interface FeatureResourceApi {
/** 从基准环境同步接口功能资源 **/
@PostMapping("/api/featureResource/sync/fromBase")
ApiResult<Void> syncFromBase(@RequestBody List<Long> ids);
ApiResult<Void> syncFromBase(@RequestBody @Valid ResourceSyncReq req);
}

View File

@ -0,0 +1,32 @@
package cn.axzo.tyr.client.model.req;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 功能资源同步请求
*
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/7 11:31
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResourceSyncReq {
/** 同步资源ID **/
@NotEmpty(message = "要同步的资源ID不能为空")
private List<Long> ids;
/** 操作人personId **/
@NotNull(message = "操作人ID不能为空")
private Long operatorId;
}

View File

@ -17,4 +17,19 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
public class CommonDictResp {
/**
* 业务域
*/
private String scope;
/**
* 字典编码
*/
private String dictCode;
/**
* 字典值
*/
private String dictValue;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.tyr.client.model.res;
import cn.axzo.basics.common.model.IBaseTree;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -23,16 +24,19 @@ public class FeatureResourceTreeNode extends FeatureResourceDTO implements IBase
private List<FeatureResourceTreeNode> children;
@JsonIgnore
@Override
public Long getNodeCode() {
return super.getId();
}
@JsonIgnore
@Override
public Long getParentNodeCode() {
return super.getParentId();
}
@JsonIgnore
@Override
public List<FeatureResourceTreeNode> getNodeChildren() {
return this.children;

View File

@ -1,10 +1,7 @@
package cn.axzo.tyr.server;
import cn.axzo.tyr.server.job.CMSRoleJobHandler;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import cn.axzo.tyr.server.job.OMSRoleJobHandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@ -15,7 +12,6 @@ import org.springframework.scheduling.annotation.EnableAsync;
@Slf4j
@EnableAsync
@EnableDiscoveryClient
//@EnableFeignClients
@MapperScan(value = {"cn.axzo.tyr.server.repository.mapper"})
@SpringBootApplication(scanBasePackages = "cn.axzo")
public class TyrApplication {
@ -23,33 +19,22 @@ public class TyrApplication {
ConfigurableApplicationContext run = SpringApplication.run(TyrApplication.class, args);
Environment env = run.getEnvironment();
log.info(
"--------------------------------------------------------------------------------------------------------------------\n" +
"Application 【{}】 is running on 【{}】 environment!\n" +
"Api Local: \thttp://127.0.0.1:{}\n" +
"Mysql: \t{}\t username:{}\n" +
"Redis: \t{}:{}\t database:{}\n" +
"RabbitMQ: \t{}\t username:{}",
env.getProperty("spring.application.name"),
env.getProperty("spring.profiles.active"),
env.getProperty("server.port"),
env.getProperty("spring.datasource.url"),
env.getProperty("spring.datasource.username"),
env.getProperty("spring.redis.host"),
env.getProperty("spring.redis.port"),
env.getProperty("spring.redis.database"),
env.getProperty("spring.rabbitmq.addresses"),
env.getProperty("spring.rabbitmq.username") +
"\n----------------------------------------------------------");
// try {
// test();
// } catch (Exception e) {
// e.printStackTrace();
// }
"--------------------------------------------------------------------------------------------------------------------\n" +
"Application 【{}】 is running on 【{}】 environment!\n" +
"Api Local: \thttp://127.0.0.1:{}\n" +
"Mysql: \t{}\t username:{}\n" +
"Redis: \t{}:{}\t database:{}\n" +
"RabbitMQ: \t{}\t username:{}",
env.getProperty("spring.application.name"),
env.getProperty("spring.profiles.active"),
env.getProperty("server.port"),
env.getProperty("spring.datasource.url"),
env.getProperty("spring.datasource.username"),
env.getProperty("spring.redis.host"),
env.getProperty("spring.redis.port"),
env.getProperty("spring.redis.database"),
env.getProperty("spring.rabbitmq.addresses"),
env.getProperty("spring.rabbitmq.username") +
"\n----------------------------------------------------------");
}
// public static void test() throws Exception {
// CMSRoleJobHandler executor = SpringUtil.getBean(CMSRoleJobHandler.class);
// executor.execute(null);
// }
}

View File

@ -17,15 +17,18 @@ import static cn.axzo.tyr.server.config.GlobalConfig.FeignClientConstant.*;
*/
@Configuration
@EnableFeignClients(basePackages = {
WORKFLOW_ENGINE
WORKFLOW_ENGINE,
INNER_FEIGN
})
public class GlobalConfig {
/**
* 第三方Feign
*/
public class FeignClientConstant {
public static class FeignClientConstant {
public static final String WORKFLOW_ENGINE = "cn.axzo.workflow.client.feign.bpmn";
public static final String INNER_FEIGN = "cn.axzo.tyr.server.inner.feign";
}
/**

View File

@ -2,6 +2,7 @@ package cn.axzo.tyr.server.controller.permission;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.tyr.client.feign.FeatureResourceApi;
import cn.axzo.tyr.client.model.req.ResourceSyncReq;
import cn.axzo.tyr.client.model.res.FeatureResourceDTO;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
import cn.axzo.tyr.server.service.SaasFeatureResourceService;
@ -26,7 +27,7 @@ import java.util.List;
@RequiredArgsConstructor
public class FeatureResourceController implements FeatureResourceApi {
private SaasFeatureResourceService featureResourceService;
private final SaasFeatureResourceService featureResourceService;
@Override
public ApiResult<List<FeatureResourceTreeNode>> getSyncTreeById(Long id) {
@ -34,13 +35,13 @@ public class FeatureResourceController implements FeatureResourceApi {
}
@Override
public ApiResult<Void> syncFromBase(List<Long> ids) {
public ApiResult<Void> syncFromBase(ResourceSyncReq req) {
if (CollectionUtil.isEmpty(ids)) {
if (CollectionUtil.isEmpty(req.getIds())) {
log.warn("no ids to sync from base env");
return ApiResult.ok();
}
featureResourceService.syncFromBase(ids);
featureResourceService.syncFromBase(req);
return ApiResult.ok();
}
}

View File

@ -15,7 +15,8 @@ import java.util.List;
* @author: ZhanSiHu
* @date: 2024/4/3 10:25
*/
@FeignClient(name = "tyr", url = "${axzo.service.base.tyr:https://pre-api.axzo.cn/tyr}")
//@FeignClient(name = "tyr", url = "${axzo.service.base.tyr:https://pre-api.axzo.cn/tyr}")
@FeignClient(name = "tyr", url = "${axzo.service.base.tyr:http://127.0.0.1:8080}")
public interface BaseFeatureResourceApi {

View File

@ -19,4 +19,8 @@ public class SaasFeatureResourceDao extends ServiceImpl<SaasFeatureResourceMappe
public SaasFeatureResource getByCode(String featureCode) {
return this.lambdaQuery().eq(SaasFeatureResource::getFeatureCode, featureCode).one();
}
public void replacePath(String oldPath, String newPath) {
this.baseMapper.replacePath(oldPath, newPath);
}
}

View File

@ -2,6 +2,8 @@ package cn.axzo.tyr.server.repository.mapper;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* <p>
@ -13,4 +15,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface SaasFeatureResourceMapper extends BaseMapper<SaasFeatureResource> {
@Update("UPDATE saas_feature_resource" +
" SET path = REPLACE(path, #{oldPath}, #{newPath})" +
" WHERE is_delete = 0 AND path LIKE CONCAT(#{oldPath}, '%')")
void replacePath(@Param("oldPath") String oldPath, @Param("newPath") String newPath);
}

View File

@ -1,5 +1,6 @@
package cn.axzo.tyr.server.service;
import cn.axzo.tyr.client.model.req.ResourceSyncReq;
import cn.axzo.tyr.client.model.res.FeatureResourceDTO;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
@ -16,5 +17,5 @@ public interface SaasFeatureResourceService {
List<FeatureResourceTreeNode> getSyncTreeById(Long id);
void syncFromBase(List<Long> ids);
void syncFromBase(ResourceSyncReq req);
}

View File

@ -4,6 +4,7 @@ import cn.axzo.basics.common.BeanMapper;
import cn.axzo.basics.common.util.TreeUtil;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.tyr.client.common.enums.FeatureResourceType;
import cn.axzo.tyr.client.model.req.ResourceSyncReq;
import cn.axzo.tyr.client.model.res.FeatureResourceDTO;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
import cn.axzo.tyr.server.inner.feign.BaseFeatureResourceApi;
@ -12,6 +13,7 @@ import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import cn.axzo.tyr.server.service.SaasFeatureResourceService;
import cn.axzo.tyr.server.util.RpcInternalUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -56,7 +58,7 @@ public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceServic
//如果是页面或应用入口-同时返回所有页面组件
if (FeatureResourceType.applyPage(resource.getFeatureType())) {
List<SaasFeatureResource> componentList = featureResourceDao.lambdaQuery()
.eq(SaasFeatureResource::getFeatureCode, FeatureResourceType.COMPONENT.getCode())
.eq(SaasFeatureResource::getFeatureType, FeatureResourceType.COMPONENT.getCode())
.apply("FIND_IN_SET(" + id + ", path)")
.list();
resourceList.addAll(componentList);
@ -66,40 +68,88 @@ public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceServic
}
@Override
public void syncFromBase(List<Long> ids) {
final Map<String, Long> idCache = new HashMap<>();
public void syncFromBase(ResourceSyncReq req) {
final Map<Long, String> codeCache = new ConcurrentHashMap<>();
for (Long id : ids) {
doSyncFromBase(id, idCache, codeCache);
for (Long id : req.getIds()) {
//TODO:@Zhan 并发处理同一个parent下同批查询
//获取基准环境配置数据:同步某个ID的数据 需要同步处理它所有上级及下级组件
List<FeatureResourceTreeNode> syncList = RpcInternalUtil.rpcProcessor(() -> baseFeatureResourceApi.getSyncTreeById(id),
"get base sync tree by id", id).getData();
doSyncFromBase(syncList, codeCache, req.getOperatorId());
}
}
private void doSyncFromBase(Long id, Map<String, Long> idCache, Map<Long, String> codeCache) {
if (codeCache.containsKey(id)) {
//已处理过
log.info("already sync resource:{}", id);
return;
}
List<FeatureResourceTreeNode> syncList = RpcInternalUtil.rpcProcessor(() -> baseFeatureResourceApi.getSyncTreeById(id),
"get sync tree by id", id).getData();
private void doSyncFromBase(List<FeatureResourceTreeNode> syncList, Map<Long, String> codeCache, Long operatorId) {
for (FeatureResourceTreeNode treeNode : syncList) {
if (codeCache.containsKey(treeNode.getId())) {
//已处理过
log.info("already sync resource:{}", treeNode.getId());
//递归子节点
if (CollectionUtil.isNotEmpty(treeNode.getChildren())) {
doSyncFromBase(treeNode.getChildren(), codeCache, operatorId);
}
continue;
}
//缓存code
codeCache.put(treeNode.getId(), treeNode.getFeatureCode());
SaasFeatureResource baseResource = BeanMapper.copyBean(treeNode, SaasFeatureResource.class);
//修正数据
String parentCode = codeCache.get(baseResource.getParentId());
fixData(baseResource, parentCode);
SaasFeatureResource resource = featureResourceDao.getByCode(treeNode.getFeatureCode());
if (resource == null) {
syncInsert(treeNode, codeCache);
//新增
baseResource.setCreateBy(operatorId);
baseResource.setUpdateBy(operatorId);
newResource(baseResource);
} else {
syncUpdate(resource, treeNode);
//更新
baseResource.setId(resource.getId());
baseResource.setPath(baseResource.getPath() + "," + resource.getId());
baseResource.setUpdateBy(operatorId);
featureResourceDao.updateById(baseResource);
if (!StrUtil.equals(baseResource.getPath(), resource.getPath())) {
//层级变化
log.info("replace path from old:{} to new:{}", resource.getPath(), baseResource.getPath());
featureResourceDao.replacePath(resource.getPath(), baseResource.getPath());
}
}
//递归子节点
if (CollectionUtil.isNotEmpty(treeNode.getChildren())) {
doSyncFromBase(treeNode.getChildren(), codeCache, operatorId);
}
}
}
private void syncUpdate(SaasFeatureResource resource, FeatureResourceTreeNode treeNode) {
/** 修正当前环境的数据 parentId path **/
private void fixData(SaasFeatureResource resource, String parentCode) {
if (StrUtil.isBlank(parentCode)) {
resource.setParentId(0L);
resource.setPath("0");
} else {
//找当前环境的parent
SaasFeatureResource parent = featureResourceDao.getByCode(parentCode);
if (parent == null) {
resource.setParentId(0L);
resource.setPath("0");
} else {
resource.setParentId(parent.getId());
resource.setPath(parent.getPath());
}
}
}
private void syncInsert(FeatureResourceTreeNode treeNode, Map<Long, String> codeCache) {
SaasFeatureResource resource = BeanMapper.copyBean(treeNode, SaasFeatureResource.class);
//修正parent id 和path
String parentCode = codeCache.get(resource.getParentId());
private void newResource(SaasFeatureResource resource) {
featureResourceDao.save(resource);
//path追加自身ID
resource.setPath(resource.getPath() + "," + resource.getId());
featureResourceDao.updateById(resource);
}
}