feat: 集群数据同步初步完成
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PortalDto {
|
||||
/**
|
||||
* 数据适配器id
|
||||
*/
|
||||
@ApiModelProperty("数据适配器id 为-1 代表需要创建数据适配器")
|
||||
@ApiModelProperty("数据适配器id")
|
||||
private Long adapterId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user