feat: 集群数据同步初步完成

This commit is contained in:
clay
2024-04-19 21:11:12 +08:00
parent ab8e2f7b87
commit 1e6b62e7cc
21 changed files with 582 additions and 79 deletions

View File

@@ -1,5 +1,6 @@
package cn.fateverse.common.security.handle;
import cn.fateverse.common.security.utils.ResponseRender;
import cn.hutool.core.text.StrFormatter;
import cn.fateverse.common.core.result.Result;
import com.alibaba.fastjson2.JSON;
@@ -35,24 +36,9 @@ public class AuthenticationEntryPointImpl implements AccessDeniedHandler, Authen
public void accessDenied(HttpServletRequest request, HttpServletResponse response) {
String msg = StrFormatter.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
renderString(response,Result.unauthorized(msg));
ResponseRender.renderString(response,Result.unauthorized(msg));
}
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param result 返回的错误对象
*/
public static void renderString(HttpServletResponse response, Result<String> result) {
try {
response.setStatus(result.getStatus().value());
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(JSON.toJSONString(result));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,30 @@
package cn.fateverse.common.security.utils;
import cn.fateverse.common.core.result.Result;
import com.alibaba.fastjson2.JSON;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Clay
* @date 2024/4/19 9:09
*/
public class ResponseRender {
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param result 返回的错误对象
*/
public static void renderString(HttpServletResponse response, Result<Object> result) {
try {
response.setStatus(result.getStatus().value());
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(JSON.toJSONString(result));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,17 @@
package cn.fateverse.query.dubbo;
/**
* @author Clay
* @date 2024/4/19 10:56
*/
public interface DubboDispatchServletPublish {
Boolean publish(String path, String requestMethod);
Boolean unpublish(String path, String requestMethod);
}

View File

@@ -7,6 +7,7 @@ import cn.fateverse.common.decrypt.annotation.Encrypt;
import cn.fateverse.common.decrypt.annotation.EncryptField;
import cn.fateverse.query.entity.dto.PortalDto;
import cn.fateverse.query.entity.query.PortalQuery;
import cn.fateverse.query.entity.vo.PortalIdWrapper;
import cn.fateverse.query.entity.vo.PortalVo;
import cn.fateverse.query.entity.vo.SimplePortalVo;
import cn.fateverse.query.service.PortalService;
@@ -58,18 +59,18 @@ public class PortalController {
@ApiOperation("新增接口")
@PostMapping
@PreAuthorize("@ss.hasPermission('query:portal:add')")
public Result<Void> add(@RequestBody @Validated PortalDto portalDto) {
portalService.save(portalDto);
return Result.ok();
public Result<PortalIdWrapper> add(@RequestBody @Validated PortalDto portalDto) {
PortalIdWrapper wrapper = portalService.save(portalDto);
return Result.ok(wrapper);
}
@ApiOperation("修改接口")
@PutMapping
@PreAuthorize("@ss.hasPermission('query:portal:edit')")
public Result<Void> edit(@RequestBody @Validated PortalDto portalDto) {
public Result<PortalIdWrapper> edit(@RequestBody @Validated PortalDto portalDto) {
ObjectUtils.checkPk(portalDto.getPortalId());
portalService.edit(portalDto);
return Result.ok();
PortalIdWrapper wrapper = portalService.edit(portalDto);
return Result.ok(wrapper);
}

View File

@@ -0,0 +1,55 @@
package cn.fateverse.query.dubbo;
import cn.fateverse.query.constant.QueryConstant;
import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.portal.service.HandlerMethodService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.annotation.Resource;
/**
* @author Clay
* @date 2024/4/19 11:02
*/
@Slf4j
@DubboService(scope = "remote")
public class DubboDispatchServletPublishImpl implements DubboDispatchServletPublish {
@Resource
private HandlerMethodService methodService;
@Resource
private RedisTemplate<String, PortalBo> redisTemplate;
@Override
public Boolean publish(String path, String requestMethod) {
PortalBo portalBo = redisTemplate.opsForValue().get(QueryConstant.PORTAL_KEY + path + ":" + requestMethod);
if (portalBo == null) {
return Boolean.FALSE;
}
try {
log.info("registerMapping, path:{}, requestMethod:{}", path, requestMethod);
methodService.registerMapping(path, RequestMethod.valueOf(requestMethod), Boolean.FALSE);
} catch (Exception e) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
@Override
public Boolean unpublish(String path, String requestMethod) {
try {
log.info("unregisterMapping, path:{}, requestMethod:{}", path, requestMethod);
methodService.unregisterMapping(path, RequestMethod.valueOf(requestMethod), Boolean.FALSE);
} catch (Exception e) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}

View File

@@ -0,0 +1,50 @@
package cn.fateverse.query.entity.bo;
import cn.fateverse.query.entity.Portal;
import cn.fateverse.query.entity.PortalMapping;
import cn.fateverse.query.enums.PortalEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* @author Clay
* @date 2024/4/19 10:04
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PortalBo implements Serializable {
private Long portalId;
private Long queryId;
private Long adapterId;
private PortalEnum type;
private String requestMethod;
private String path;
private String url;
private Integer state;
private List<PortalMapping> mappings;
public static PortalBo toPortalBo(Portal portal, List<PortalMapping> mappings) {
return PortalBo.builder()
.portalId(portal.getPortalId())
.queryId(portal.getQueryId())
.adapterId(portal.getAdapterId())
.type(portal.getType())
.requestMethod(portal.getRequestMethod())
.path(portal.getPath())
.url(portal.getUrl())
.state(portal.getState())
.mappings(mappings)
.build();
}
}

View File

@@ -38,7 +38,7 @@ public class PortalDto {
/**
* 数据适配器id
*/
@ApiModelProperty("数据适配器id 为-1 代表需要创建数据适配器")
@ApiModelProperty("数据适配器id")
private Long adapterId;
/**

View File

@@ -0,0 +1,20 @@
package cn.fateverse.query.entity.vo;
import cn.fateverse.common.decrypt.annotation.EncryptField;
import lombok.Builder;
import lombok.Data;
/**
* @author Clay
* @date 2024/4/19 15:43
*/
@Data
@Builder
public class PortalIdWrapper {
@EncryptField
private String portalId;
@EncryptField
private String adapterId;
}

View File

@@ -34,6 +34,7 @@ public class PortalVo extends SimplePortalVo {
SimplePortalVo simplePortalVo = SimplePortalVo.toPortalVo(portal);
PortalVo portalVo = new PortalVo();
BeanUtils.copyProperties(simplePortalVo, portalVo);
portalVo.setCreateDataAdapter(portal.getCreateDataAdapter());
if (!ObjectUtils.isEmpty(mappings)) {
portalVo.setMappings(mappings);
}

View File

@@ -1,7 +1,7 @@
package cn.fateverse.query.handler.adapter;
import cn.fateverse.query.entity.DataAdapter;
import cn.fateverse.query.entity.Portal;
import cn.fateverse.query.entity.bo.PortalBo;
/**
* @author Clay
@@ -16,7 +16,7 @@ public interface DataAdapterHandler {
* @param portal
* @return 执行结果
*/
Object mockExecute(DataAdapter dataAdapter, Portal portal, Object param);
Object mockExecute(DataAdapter dataAdapter, PortalBo portal, Object param);
/**
* 真实执行
@@ -25,6 +25,6 @@ public interface DataAdapterHandler {
* @param portal
* @return 执行结果
*/
Object execute(DataAdapter dataAdapter, Portal portal, Object param);
Object execute(DataAdapter dataAdapter, PortalBo portal, Object param);
}

View File

@@ -3,8 +3,8 @@ package cn.fateverse.query.handler.adapter.impl;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.query.entity.DataAdapter;
import cn.fateverse.query.entity.Portal;
import cn.fateverse.query.entity.UniQuery;
import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.entity.dto.SearchInfo;
import cn.fateverse.query.enums.PortalEnum;
import cn.fateverse.query.handler.adapter.DataAdapterHandler;
@@ -44,7 +44,7 @@ public class LocalDataAdapterHandler implements DataAdapterHandler {
@Override
public Object mockExecute(DataAdapter dataAdapter, Portal portal, Object param) {
public Object mockExecute(DataAdapter dataAdapter, PortalBo portal, Object param) {
if (portal.getType() != PortalEnum.LOCAL) {
return null;
}
@@ -67,7 +67,7 @@ public class LocalDataAdapterHandler implements DataAdapterHandler {
@Override
public Object execute(DataAdapter dataAdapter, Portal portal, Object param) {
public Object execute(DataAdapter dataAdapter, PortalBo portal, Object param) {
if (portal.getType() != PortalEnum.LOCAL) {
return null;
}

View File

@@ -3,7 +3,7 @@ package cn.fateverse.query.handler.reader;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.query.entity.DataAdapter;
import cn.fateverse.query.entity.Portal;
import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.handler.adapter.DataAdapterHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -28,9 +28,9 @@ public class DataAdapterHandlerReader {
}
public Object mockExecute(DataAdapter dataAdapter, Portal portal, Object params) {
public Object mockExecute(DataAdapter dataAdapter, PortalBo portal, Object params) {
for (DataAdapterHandler dataAdapterHandler : handlerList) {
Object result = dataAdapterHandler.mockExecute(dataAdapter,portal , params);
Object result = dataAdapterHandler.mockExecute(dataAdapter, portal, params);
if (result != null) {
return result;
}
@@ -39,7 +39,7 @@ public class DataAdapterHandlerReader {
}
public Object execute(DataAdapter dataAdapter,Portal portal, Object params) {
public Object execute(DataAdapter dataAdapter, PortalBo portal, Object params) {
for (DataAdapterHandler dataAdapterHandler : handlerList) {
Object result = dataAdapterHandler.execute(dataAdapter, portal, params);
if (result != null) {

View File

@@ -1,8 +1,10 @@
package cn.fateverse.query.mapper;
import cn.fateverse.query.entity.PortalMapping;
import org.apache.ibatis.annotations.MapKey;
import java.util.List;
import java.util.Map;
/**
* @author Clay
@@ -35,4 +37,5 @@ public interface PortalMappingMapper {
Integer deleteByPortalId(Long portalId);
List<PortalMapping> selectByPortalIds(List<Long> portalIds);
}

View File

@@ -1,17 +1,23 @@
package cn.fateverse.query.portal;
import cn.fateverse.common.core.result.Result;
import cn.fateverse.common.security.utils.ResponseRender;
import cn.fateverse.query.constant.QueryConstant;
import cn.fateverse.query.entity.DataAdapter;
import cn.fateverse.query.entity.Portal;
import cn.fateverse.query.entity.PortalMapping;
import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.entity.dto.SearchInfo;
import cn.fateverse.query.entity.dto.UniConDto;
import cn.fateverse.query.handler.reader.DataAdapterHandlerReader;
import cn.fateverse.query.mapper.DataAdapterMapper;
import cn.fateverse.query.mapper.PortalMapper;
import cn.fateverse.query.mapper.PortalMappingMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
@@ -24,6 +30,9 @@ import java.util.List;
@Component
public class PortalDispatchServlet {
@Resource
private RedisTemplate<String, PortalBo> redisTemplate;
private final PortalMapper portalMapper;
private final DataAdapterMapper dataAdapterMapper;
@@ -46,37 +55,46 @@ public class PortalDispatchServlet {
private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
String path = request.getServletPath();
String method = request.getMethod();
Portal portal = portalMapper.selectByPath(path, method);
if (portal == null) {
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
PortalBo portalBo = redisTemplate.opsForValue().get(QueryConstant.PORTAL_KEY + path + ":" + method);
if (portalBo == null) {
Portal portal = portalMapper.selectByPath(path, method);
if (portal == null) {
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
List<PortalMapping> portalMappings = portalMappingMapper.selectByPortalId(portal.getPortalId());
portalBo = PortalBo.toPortalBo(portal, portalMappings);
redisTemplate.opsForValue().set(QueryConstant.PORTAL_KEY + portalBo.getPath() + ":" + portalBo.getRequestMethod(), portalBo);
}
List<PortalMapping> portalMappings = portalMappingMapper.selectByPortalId(portal.getPortalId());
SearchInfo searchInfo = new SearchInfo();
List<UniConDto> uniConList = new ArrayList<>();
for (PortalMapping portalMapping : portalMappings) {
for (PortalMapping portalMapping : portalBo.getMappings()) {
UniConDto uniCon = new UniConDto();
String mappingValue = portalMapping.getMappingValue();
String mappingKey = portalMapping.getMappingKey();
uniCon.setUcId(Long.parseLong(mappingValue));
if (portalMapping.getMappingType() == 0) {
uniCon.setQuery(request.getParameter(mappingValue));
uniCon.setQuery(request.getParameter(mappingKey));
} else if (portalMapping.getMappingType() == 1) {
uniCon.setQuery(request.getHeaders(mappingValue));
uniCon.setQuery(request.getHeaders(mappingKey));
} else {
uniCon.setQuery(request.getParameter(mappingValue));
uniCon.setQuery(request.getParameter(mappingKey));
}
uniConList.add(uniCon);
}
searchInfo.setList(uniConList);
DataAdapter dataAdapter = dataAdapterMapper.selectById(portal.getAdapterId());
if (portal.getState() == 1 || portal.getState() == 2) {
Object execute = dataAdapterHandler.execute(dataAdapter, portal, searchInfo);
DataAdapter dataAdapter = dataAdapterMapper.selectById(portalBo.getAdapterId());
Object result = null;
if (portalBo.getState() == 1 || portalBo.getState() == 2) {
result = dataAdapterHandler.execute(dataAdapter, portalBo, searchInfo);
} else {
Object mockExecute = dataAdapterHandler.mockExecute(dataAdapter, portal, searchInfo);
result = dataAdapterHandler.mockExecute(dataAdapter, portalBo, searchInfo);
}
ResponseRender.renderString(response, Result.ok(result));
}

View File

@@ -0,0 +1,31 @@
package cn.fateverse.query.portal.config;
import org.apache.dubbo.config.spring.ServiceBean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author Clay
* @date 2024/4/19 17:24
*/
@Component
public class DubboServiceBean {
@Resource
private ServiceBean serviceBean;
private volatile int port;
public int getPort() {
if (port == 0) {
synchronized (this) {
if (port == 0) {
port = serviceBean.getExportedUrls().get(0).getPort();
}
}
}
return port;
}
}

View File

@@ -0,0 +1,23 @@
package cn.fateverse.query.portal.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* @author Clay
* @date 2024/4/19 17:42
*/
@Getter
public class DispatchSyncEvent extends ApplicationEvent {
private final String path;
private final Boolean publish;
private final String requestMethod;
public DispatchSyncEvent(String path, String requestMethod, Boolean publish) {
super(path);
this.path = path;
this.publish = publish;
this.requestMethod = requestMethod;
}
}

View File

@@ -0,0 +1,153 @@
package cn.fateverse.query.portal.service;
import cn.fateverse.query.dubbo.DubboDispatchServletPublish;
import cn.fateverse.query.portal.config.DubboServiceBean;
import cn.fateverse.query.portal.event.DispatchSyncEvent;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.rpc.cluster.specifyaddress.Address;
import org.apache.dubbo.rpc.cluster.specifyaddress.UserSpecifiedAddressUtil;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author Clay
* @date 2024/4/19 17:43
*/
@Slf4j
@Component
public class DispatchSyncService {
private final String serviceName;
private final String host;
private final NamingService namingService;
private final DubboServiceBean serviceBean;
@DubboReference
private DubboDispatchServletPublish dubboDispatchServletPublish;
private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(64);
public DispatchSyncService(NacosDiscoveryProperties nacosDiscoveryProperties, Environment environment, NacosRegistration nacosRegistration, ThreadPoolTaskExecutor taskExecuteExecutor, DubboServiceBean serviceBean) {
this.host = nacosRegistration.getHost();
this.serviceBean = serviceBean;
NacosServiceManager nacosServiceManager = new NacosServiceManager();
Properties nacosProperties = nacosDiscoveryProperties.getNacosProperties();
String namespace = environment.getProperty("dubbo.registry.parameters.namespace");
this.serviceName = environment.getProperty("dubbo.application.name");
nacosProperties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
this.namingService = nacosServiceManager.getNamingService(nacosProperties);
new Thread(() -> {
while (true) {
try {
Runnable take = queue.take();
taskExecuteExecutor.execute(take);
} catch (InterruptedException e) {
log.info("", e);
}
}
}).start();
}
@EventListener(DispatchSyncEvent.class)
public void syncDispatch(DispatchSyncEvent event) {
syncDispatch(event.getPath(), event.getRequestMethod(), event.getPublish());
}
private void syncDispatch(String path, String requestMethod, Boolean publish) {
try {
List<Instance> allInstances = namingService.getAllInstances(serviceName);
for (Instance instance : allInstances) {
if (!(instance.getIp().equals(host) && instance.getPort() == serviceBean.getPort())) {
Task task = new Task(instance.getIp(), instance.getPort(), path, publish, requestMethod) {
@Override
public void run() {
try {
List<Instance> allInstances = namingService.getAllInstances(serviceName);
if (ObjectUtils.isEmpty(allInstances)) {
return;
}
Optional<Instance> optional = allInstances.stream().filter(instance -> this.ip.equals(instance.getIp()) && this.port == instance.getPort()).findAny();
if (optional.isEmpty()) {
return;
}
Instance instance = optional.get();
UserSpecifiedAddressUtil.setAddress(new Address(instance.getIp(), instance.getPort(), true));
Boolean state = null;
if (publish) {
log.info("dubboDispatchServletPublish.publish({}, {})", path, requestMethod);
state = dubboDispatchServletPublish.publish(path, requestMethod);
} else {
log.info("dubboDispatchServletPublish.unpublish({}, {})", path, requestMethod);
state = dubboDispatchServletPublish.unpublish(path, requestMethod);
}
if (state == null || !state) {
queue.add(this);
}
} catch (NacosException e) {
log.error("NacosException: {}", e.getMessage());
queue.add(this);
} finally {
UserSpecifiedAddressUtil.setAddress(null);
}
}
};
queue.add(task);
}
}
} catch (NacosException e) {
throw new RuntimeException(e);
}
}
private static abstract class Task implements Runnable {
protected final String ip;
protected final int port;
protected final String path;
protected final Boolean publish;
protected final String requestMethod;
private Task(String ip, int port, String path, Boolean publish, String requestMethod) {
this.ip = ip;
this.port = port;
this.path = path;
this.publish = publish;
this.requestMethod = requestMethod;
}
}
}

View File

@@ -1,11 +1,29 @@
package cn.fateverse.query.portal.config;
package cn.fateverse.query.portal.service;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.core.utils.SpringContextHolder;
import cn.fateverse.query.dubbo.DubboDispatchServletPublish;
import cn.fateverse.query.portal.PortalDispatchServlet;
import cn.fateverse.query.portal.config.DubboServiceBean;
import cn.fateverse.query.portal.event.DispatchSyncEvent;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcContextAttachment;
import org.apache.dubbo.rpc.cluster.specifyaddress.Address;
import org.apache.dubbo.rpc.cluster.specifyaddress.UserSpecifiedAddressUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -14,7 +32,12 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* @author Clay
@@ -24,9 +47,10 @@ import java.util.Map;
@Component
public class HandlerMethodService implements ApplicationContextAware {
public static final String CUSTOM_INTERFACE = "customInterface:";
private RequestMappingHandlerMapping mapping;
private Method mappingMethod;
@@ -47,7 +71,6 @@ public class HandlerMethodService implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
mapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
registerMapping("/anonymity/mapping",RequestMethod.GET);
}
/**
@@ -56,7 +79,7 @@ public class HandlerMethodService implements ApplicationContextAware {
* @param path 接口路径
* @param requestMethod 请求方法
*/
public void registerMapping(String path, RequestMethod requestMethod) {
public void registerMapping(String path, RequestMethod requestMethod, Boolean sync) {
String[] empty = {};
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(path)
@@ -72,6 +95,9 @@ public class HandlerMethodService implements ApplicationContextAware {
throw new CustomException("path is exist");
}
mapping.registerMapping(requestMappingInfo, "portalDispatchServlet", mappingMethod);
if (sync) {
SpringContextHolder.publishEvent(new DispatchSyncEvent(path, requestMethod.name(), Boolean.TRUE));
}
}
/**
@@ -80,13 +106,16 @@ public class HandlerMethodService implements ApplicationContextAware {
* @param path 接口路径
* @param requestMethod 请求类型
*/
public void unregisterMapping(String path, RequestMethod requestMethod) {
public void unregisterMapping(String path, RequestMethod requestMethod, Boolean sync) {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
for (RequestMappingInfo mappingInfo : handlerMethods.keySet()) {
if (!ObjectUtils.isEmpty(mappingInfo.getName())
&& (CUSTOM_INTERFACE + path).equals(mappingInfo.getName())
&& mappingInfo.getMethodsCondition().getMethods().contains(requestMethod)) {
mapping.unregisterMapping(mappingInfo);
if (sync) {
SpringContextHolder.publishEvent(new DispatchSyncEvent(path, requestMethod.name(), Boolean.FALSE));
}
}
}
}

View File

@@ -3,6 +3,7 @@ package cn.fateverse.query.service;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.query.entity.dto.PortalDto;
import cn.fateverse.query.entity.query.PortalQuery;
import cn.fateverse.query.entity.vo.PortalIdWrapper;
import cn.fateverse.query.entity.vo.PortalVo;
import cn.fateverse.query.entity.vo.SimplePortalVo;
@@ -23,7 +24,7 @@ public interface PortalService {
TableDataInfo<SimplePortalVo> searchList(PortalQuery query);
void save(PortalDto portalDto);
PortalIdWrapper save(PortalDto portalDto);
void edit(PortalDto portalDto);
PortalIdWrapper edit(PortalDto portalDto);
}

View File

@@ -3,7 +3,9 @@ package cn.fateverse.query.service.impl;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.mybatis.utils.PageUtils;
import cn.fateverse.query.portal.config.HandlerMethodService;
import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.entity.vo.PortalIdWrapper;
import cn.fateverse.query.portal.service.HandlerMethodService;
import cn.fateverse.query.constant.QueryConstant;
import cn.fateverse.query.entity.DataAdapter;
import cn.fateverse.query.entity.Portal;
@@ -22,16 +24,16 @@ import cn.fateverse.query.mapper.UniQueryMapper;
import cn.fateverse.query.service.PortalService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author Clay
@@ -43,10 +45,9 @@ public class PortalServiceImpl implements PortalService {
@Resource
private RedisTemplate<String, Portal> redisTemplate;
private RedisTemplate<String, PortalBo> redisTemplate;
@Resource
private HandlerMethodService methodService;
private final HandlerMethodService methodService;
private final PortalMapper portalMapper;
@@ -59,11 +60,44 @@ public class PortalServiceImpl implements PortalService {
public PortalServiceImpl(PortalMapper portalMapper,
UniQueryMapper queryMapper,
DataAdapterMapper adapterMapper,
PortalMappingMapper portalMappingMapper) {
PortalMappingMapper portalMappingMapper,
ThreadPoolTaskExecutor taskExecuteExecutor,
HandlerMethodService methodService) {
this.portalMapper = portalMapper;
this.queryMapper = queryMapper;
this.adapterMapper = adapterMapper;
this.portalMappingMapper = portalMappingMapper;
this.methodService = methodService;
taskExecuteExecutor.execute(() -> {
List<Portal> portalList = portalMapper.selectList(new PortalQuery());
if (ObjectUtils.isEmpty(portalList)) {
log.info("portal is empty!");
return;
}
List<Long> portalIds = portalList.stream().map(Portal::getPortalId).collect(Collectors.toList());
if (ObjectUtils.isEmpty(portalIds)) {
log.info("portalIds is empty!");
return;
}
List<PortalMapping> portalMappingList = portalMappingMapper.selectByPortalIds(portalIds);
Map<Long, List<PortalMapping>> portalMappingMap = new HashMap<>();
if (!ObjectUtils.isEmpty(portalMappingList)) {
portalMappingMap = portalMappingList.stream().collect(Collectors.groupingBy(PortalMapping::getPortalId));
}
List<PortalMapping> emptyPortalMapping = new ArrayList<>();
for (Portal portal : portalList) {
try {
publishPortal(portal, portalMappingMap.getOrDefault(portal.getPortalId(), emptyPortalMapping), Boolean.FALSE);
} catch (Exception e) {
if (e instanceof CustomException) {
log.error(e.getMessage());
} else {
log.error("portalId:{} , portalName:{} publish error", portal.getPortalId(), portal.getPortalName(), e);
}
redisTemplate.delete(QueryConstant.PORTAL_KEY + portal.getPath() + ":" + portal.getRequestMethod());
}
}
});
}
@Override
@@ -128,7 +162,7 @@ public class PortalServiceImpl implements PortalService {
@Override
@Transactional(rollbackFor = Exception.class)
public void save(PortalDto portalDto) {
public PortalIdWrapper save(PortalDto portalDto) {
Portal portal = portalDto.toPortal();
checkPortalType(portal);
Portal old = portalMapper.selectByPath(portal.getPath(), portal.getRequestMethod());
@@ -157,21 +191,22 @@ public class PortalServiceImpl implements PortalService {
}
portalMappingMapper.insertBatch(mappings);
}
publishPortal(portal);
publishPortal(portal, mappings, Boolean.TRUE);
return PortalIdWrapper.builder()
.portalId(String.valueOf(portal.getPortalId()))
.adapterId(String.valueOf(portal.getAdapterId()))
.build();
}
@Override
public void edit(PortalDto portalDto) {
public PortalIdWrapper edit(PortalDto portalDto) {
Portal portal = portalDto.toPortal();
checkPortalType(portal);
Portal old = portalMapper.selectByPath(portal.getPath(), portal.getRequestMethod());
if (!ObjectUtils.isEmpty(old) && !old.getPortalId().equals(portal.getPortalId())) {
throw new CustomException("系统中存在当前请求路径");
}
if (!old.getPath().equals(portal.getPath())) {
unpublishPortal(old);
publishPortal(portal);
}
if (portal.getCreateDataAdapter() != old.getCreateDataAdapter()) {
if (portal.getCreateDataAdapter()) {
createDataAdapter(portalDto, portal);
@@ -179,18 +214,57 @@ public class PortalServiceImpl implements PortalService {
adapterMapper.deleteById(old.getAdapterId());
}
}
PortalBo portalBo = PortalBo.toPortalBo(portal, portalDto.getMappings());
if (!old.getPath().equals(portal.getPath())
|| !old.getRequestMethod().equals(portal.getRequestMethod())) {
unpublishPortal(old, true);
methodService.registerMapping(portalBo.getPath(), RequestMethod.valueOf(portalBo.getRequestMethod()), true);
}
redisTemplate.opsForValue().set(QueryConstant.PORTAL_KEY + portalBo.getPath() + ":" + portalBo.getRequestMethod(), portalBo);
return PortalIdWrapper.builder()
.portalId(String.valueOf(portal.getPortalId()))
.adapterId(String.valueOf(portal.getAdapterId()))
.build();
}
private boolean mappingCheckEquals(List<PortalMapping> oldPortalMappingList, List<PortalMapping> mappings) {
if (ObjectUtils.isEmpty(oldPortalMappingList) && ObjectUtils.isEmpty(mappings)) {
return true;
}
if (ObjectUtils.isEmpty(oldPortalMappingList) || ObjectUtils.isEmpty(mappings)) {
return false;
}
Optional<PortalMapping> optional = mappings.stream().filter(ObjectUtils::isEmpty).findAny();
if (optional.isEmpty()) {
return false;
}
if (oldPortalMappingList.size() == mappings.size()) {
Map<Long, PortalMapping> oldPortalMappingMap = oldPortalMappingList.stream()
.collect(Collectors.toMap(PortalMapping::getMappingId, Function.identity()));
Map<Long, PortalMapping> portalMappingMap = mappings.stream()
.collect(Collectors.toMap(PortalMapping::getMappingId, Function.identity()));
for (Long mappingId : oldPortalMappingMap.keySet()) {
PortalMapping portalMapping = portalMappingMap.get(mappingId);
if (ObjectUtils.isEmpty(portalMapping)) {
return false;
}
}
}
return false;
}
private void publishPortal(Portal portal) {
methodService.registerMapping(portal.getPath(), RequestMethod.valueOf(portal.getRequestMethod()));
redisTemplate.opsForValue().set(QueryConstant.PORTAL_KEY + portal.getPortalId(), portal);
private void publishPortal(Portal portal, List<PortalMapping> mappings, Boolean sync) {
PortalBo portalBo = PortalBo.toPortalBo(portal, mappings);
redisTemplate.opsForValue().set(QueryConstant.PORTAL_KEY + portalBo.getPath() + ":" + portalBo.getRequestMethod(), portalBo);
methodService.registerMapping(portalBo.getPath(), RequestMethod.valueOf(portalBo.getRequestMethod()), sync);
}
private void unpublishPortal(Portal portal) {
methodService.unregisterMapping(portal.getPath(), RequestMethod.valueOf(portal.getRequestMethod()));
redisTemplate.delete(QueryConstant.PORTAL_KEY + portal.getPortalId());
private void unpublishPortal(Portal portal, Boolean sync) {
methodService.unregisterMapping(portal.getPath(), RequestMethod.valueOf(portal.getRequestMethod()), sync);
redisTemplate.delete(QueryConstant.PORTAL_KEY + portal.getPath() + ":" + portal.getRequestMethod());
}
private void createDataAdapter(PortalDto portalDto, Portal portal) {

View File

@@ -3,13 +3,24 @@
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fateverse.query.mapper.PortalMappingMapper">
<select id="selectByPortalId" resultType="cn.fateverse.query.entity.PortalMapping">
<sql id="selectPortalMapping">
select mapping_id, portal_id, mapping_key, mapping_type, mapping_value
from portal_mapping
</sql>
<select id="selectByPortalId" resultType="cn.fateverse.query.entity.PortalMapping">
<include refid="selectPortalMapping"/>
where portal_id = #{portalId}
</select>
<select id="selectByPortalIds" resultType="cn.fateverse.query.entity.PortalMapping">
<include refid="selectPortalMapping"/>
where portal_id in
<foreach collection="list" item="portalId" open="(" separator="," close=")">
#{portalId}
</foreach>
</select>
<insert id="insertBatch">
insert into portal_mapping (portal_id, mapping_key, mapping_type, mapping_value)