init
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>data-market-service-parent</artifactId>
|
||||
<groupId>com.platform</groupId>
|
||||
<version>0.4.x</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>0.4.x</version>
|
||||
<artifactId>data-market-service-mapping</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!--web 模块-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
<!--配置中心客户端 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>common-mybatis</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>common-security</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>common-database</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>common-log</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>data-market-service-api</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>data-metadata-service-api</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.platform</groupId>
|
||||
<artifactId>common-rabbitmq</artifactId>
|
||||
<version>0.4.x</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.datax.service.data.market.mapping;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@EnableFeignClients(basePackages = {"cn.datax.service.system.api.feign", "cn.datax.service.data.metadata.api.feign", "cn.datax.service.data.market.api.feign"})
|
||||
@SpringBootApplication
|
||||
public class DataxMappingApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DataxMappingApplication.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.datax.service.data.market.mapping.async;
|
||||
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
import cn.datax.service.data.market.mapping.service.ApiLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 异步处理
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AsyncTask {
|
||||
|
||||
@Autowired
|
||||
private ApiLogService apiLogService;
|
||||
|
||||
@Async("taskExecutor")
|
||||
public void doTask(ApiLogDto apiLogDto) {
|
||||
apiLogService.saveApiLog(apiLogDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import cn.datax.service.data.market.mapping.handler.MappingHandlerMapping;
|
||||
import cn.datax.service.data.market.mapping.handler.RequestHandler;
|
||||
import cn.datax.service.data.market.mapping.handler.RequestInterceptor;
|
||||
import cn.datax.service.data.market.mapping.service.impl.ApiMappingEngine;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
@Configuration
|
||||
public class ApiMappingConfig {
|
||||
|
||||
@Bean
|
||||
public MappingHandlerMapping mappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping,
|
||||
ApiMappingEngine apiMappingEngine,
|
||||
RedisTemplate redisTemplate,
|
||||
ObjectMapper objectMapper) {
|
||||
MappingHandlerMapping mappingHandlerMapping = new MappingHandlerMapping();
|
||||
mappingHandlerMapping.setHandler(requestHandler(apiMappingEngine, redisTemplate, objectMapper));
|
||||
mappingHandlerMapping.setRequestMappingHandlerMapping(requestMappingHandlerMapping);
|
||||
return mappingHandlerMapping;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestHandler requestHandler(ApiMappingEngine apiMappingEngine, RedisTemplate redisTemplate, ObjectMapper objectMapper) {
|
||||
RequestHandler handler = new RequestHandler();
|
||||
handler.setApiMappingEngine(apiMappingEngine);
|
||||
handler.setObjectMapper(objectMapper);
|
||||
handler.setRequestInterceptor(new RequestInterceptor(redisTemplate));
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean("taskExecutor")
|
||||
public Executor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setKeepAliveSeconds(30);
|
||||
executor.setThreadNamePrefix("async-service-");
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
|
||||
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsProcessor;
|
||||
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
|
||||
import com.baomidou.dynamic.datasource.provider.YmlDynamicDataSourceProvider;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceCreatorAutoConfiguration;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.druid.DruidDynamicDataSourceConfiguration;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Role;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源核心自动配置类
|
||||
* @author AllDataDC
|
||||
* @date 2023/03/17
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@AllArgsConstructor
|
||||
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
|
||||
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
|
||||
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
|
||||
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class DynamicDSConfiguration {
|
||||
|
||||
private final DynamicDataSourceProperties properties;
|
||||
|
||||
//读取多数据源配置,注入到spring容器中
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DynamicDataSourceProvider dynamicDataSourceProvider() {
|
||||
Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
|
||||
return new YmlDynamicDataSourceProvider(datasourceMap);
|
||||
}
|
||||
|
||||
//注册自己的动态多数据源DataSource
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
|
||||
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
|
||||
dataSource.setPrimary(properties.getPrimary());
|
||||
dataSource.setStrict(properties.getStrict());
|
||||
dataSource.setStrategy(properties.getStrategy());
|
||||
dataSource.setProvider(dynamicDataSourceProvider);
|
||||
dataSource.setP6spy(properties.getP6spy());
|
||||
dataSource.setSeata(properties.getSeata());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
//AOP切面,对DS注解过的方法进行增强,达到切换数据源的目的
|
||||
@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
|
||||
DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);
|
||||
DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
|
||||
advisor.setOrder(properties.getOrder());
|
||||
return advisor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import cn.datax.common.rabbitmq.config.RabbitMqConstant;
|
||||
import cn.datax.common.utils.ThrowableUtil;
|
||||
import cn.datax.service.data.market.api.entity.DataApiEntity;
|
||||
import cn.datax.service.data.market.api.feign.DataApiServiceFeign;
|
||||
import cn.datax.service.data.market.mapping.handler.MappingHandlerMapping;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.Exchange;
|
||||
import org.springframework.amqp.rabbit.annotation.Queue;
|
||||
import org.springframework.amqp.rabbit.annotation.QueueBinding;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class RabbitMqListenerConfig {
|
||||
|
||||
private static String HANDLER_RELEASE = "1";
|
||||
private static String HANDLER_CANCEL = "2";
|
||||
|
||||
@Autowired
|
||||
private DataApiServiceFeign dataApiServiceFeign;
|
||||
|
||||
@Autowired
|
||||
private MappingHandlerMapping mappingHandlerMapping;
|
||||
|
||||
/**
|
||||
* api发布与撤销
|
||||
* @param map type 1:发布 2:撤销
|
||||
* @param channel
|
||||
* @param message
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@RabbitListener(bindings = @QueueBinding(exchange = @Exchange(name = RabbitMqConstant.FANOUT_EXCHANGE_API, type = "fanout", durable = "true", autoDelete = "false"),
|
||||
value = @Queue(value = RabbitMqConstant.FANOUT_API_QUEUE, durable = "true", exclusive = "false", autoDelete = "false")))
|
||||
public void fanoutQueueRelease(Map map, Channel channel, Message message) throws Exception {
|
||||
try {
|
||||
String id = (String) map.get("id");
|
||||
String type = (String) map.get("type");
|
||||
log.info("fanoutQueueRelease接收到了:{},{}", id, type);
|
||||
DataApiEntity dataApiEntity = dataApiServiceFeign.getDataApiById(id);
|
||||
if (dataApiEntity != null) {
|
||||
if (HANDLER_RELEASE.equals(type)) {
|
||||
mappingHandlerMapping.registerMapping(dataApiEntity);
|
||||
} else if (HANDLER_CANCEL.equals(type)) {
|
||||
mappingHandlerMapping.unregisterMapping(dataApiEntity);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
|
||||
if (message.getMessageProperties().getRedelivered()){
|
||||
log.error("消息已处理,请勿重复处理!");
|
||||
// 拒绝消息
|
||||
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
|
||||
}else {
|
||||
//记录日志
|
||||
log.error("消息消费失败处理:{}", e.getMessage());
|
||||
//第一个参数为消息的index,第二个参数是是否批量处理,第三个参数为是否让被拒绝的消息重新入队列
|
||||
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
|
||||
}
|
||||
} finally {
|
||||
// 手动确认
|
||||
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import cn.datax.service.data.market.api.entity.DataApiEntity;
|
||||
import cn.datax.service.data.market.api.feign.DataApiServiceFeign;
|
||||
import cn.datax.service.data.market.mapping.handler.MappingHandlerMapping;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class StartedUpRunner implements ApplicationRunner {
|
||||
|
||||
private final ConfigurableApplicationContext context;
|
||||
private final Environment environment;
|
||||
|
||||
@Autowired
|
||||
private DataApiServiceFeign dataApiServiceFeign;
|
||||
|
||||
@Autowired
|
||||
private MappingHandlerMapping mappingHandlerMapping;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
if (context.isActive()) {
|
||||
String banner = "-----------------------------------------\n" +
|
||||
"服务启动成功,时间:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "\n" +
|
||||
"服务名称:" + environment.getProperty("spring.application.name") + "\n" +
|
||||
"端口号:" + environment.getProperty("server.port") + "\n" +
|
||||
"-----------------------------------------";
|
||||
System.out.println(banner);
|
||||
|
||||
// 项目启动时,初始化已发布的接口
|
||||
List<DataApiEntity> releaseDataApiList = dataApiServiceFeign.getReleaseDataApiList();
|
||||
if (CollUtil.isNotEmpty(releaseDataApiList)) {
|
||||
releaseDataApiList.forEach(api -> mappingHandlerMapping.registerMapping(api));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
|
||||
import springfox.documentation.builders.*;
|
||||
import springfox.documentation.schema.ModelRef;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "swagger", name = "enable", havingValue = "true")
|
||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
||||
@EnableSwagger2
|
||||
@EnableKnife4j
|
||||
@Import(BeanValidatorPluginsConfiguration.class)
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Autowired
|
||||
private SwaggerProperties swaggerProperties;
|
||||
|
||||
/**
|
||||
* 创建API应用
|
||||
* apiInfo() 增加API相关信息
|
||||
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
|
||||
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public Docket createRestApi(){
|
||||
//版本类型是swagger2
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
//通过调用自定义方法apiInfo,获得文档的主要信息
|
||||
.apiInfo(apiInfo())
|
||||
//设置全局参数
|
||||
.globalOperationParameters(globalParamBuilder())
|
||||
//设置全局响应参数
|
||||
.globalResponseMessage(RequestMethod.GET,responseBuilder())
|
||||
.globalResponseMessage(RequestMethod.POST,responseBuilder())
|
||||
.globalResponseMessage(RequestMethod.PUT,responseBuilder())
|
||||
.globalResponseMessage(RequestMethod.DELETE,responseBuilder())
|
||||
.select()
|
||||
//扫描该包下面的API注解
|
||||
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
//设置安全认证
|
||||
;
|
||||
}
|
||||
/**
|
||||
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
|
||||
* 访问地址:http://项目实际地址/swagger-ui.html
|
||||
* @return
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title(swaggerProperties.getTitle())
|
||||
.description(swaggerProperties.getDescription())
|
||||
.termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
|
||||
.version(swaggerProperties.getVersion())
|
||||
.contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全认证参数
|
||||
* @return
|
||||
*/
|
||||
private List<ApiKey> security() {
|
||||
List<ApiKey> apiKeys = new ArrayList<>();
|
||||
apiKeys.add(new ApiKey("Authorization", "Authorization", "header"));
|
||||
return apiKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建全局参数列表
|
||||
* @return
|
||||
*/
|
||||
private List<Parameter> globalParamBuilder(){
|
||||
List<Parameter> pars = new ArrayList<>();
|
||||
pars.add(parameterBuilder("Authorization","令牌","string","header",false).build());
|
||||
return pars;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建参数
|
||||
* @return
|
||||
*/
|
||||
private ParameterBuilder parameterBuilder(String name, String desc, String type, String parameterType, boolean required) {
|
||||
ParameterBuilder tokenPar = new ParameterBuilder();
|
||||
tokenPar.name(name).description(desc).modelRef(new ModelRef(type)).parameterType(parameterType).required(required).build();
|
||||
return tokenPar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建全局响应值
|
||||
* @return
|
||||
*/
|
||||
private List<ResponseMessage> responseBuilder() {
|
||||
List<ResponseMessage> responseMessageList = new ArrayList<>();
|
||||
responseMessageList.add(new ResponseMessageBuilder().code(200).message("响应成功").build());
|
||||
responseMessageList.add(new ResponseMessageBuilder().code(500).message("服务器内部错误").build());
|
||||
return responseMessageList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(ignoreUnknownFields = false, prefix = "swagger")
|
||||
public class SwaggerProperties {
|
||||
|
||||
private Boolean enable;
|
||||
private String title;
|
||||
private String description;
|
||||
private String version;
|
||||
private String termsOfServiceUrl;
|
||||
private String basePackage;
|
||||
private Contact contact;
|
||||
|
||||
public Boolean getEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
public void setEnable(Boolean enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getTermsOfServiceUrl() {
|
||||
return termsOfServiceUrl;
|
||||
}
|
||||
|
||||
public void setTermsOfServiceUrl(String termsOfServiceUrl) {
|
||||
this.termsOfServiceUrl = termsOfServiceUrl;
|
||||
}
|
||||
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
public void setBasePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public void setContact(Contact contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
|
||||
public static class Contact {
|
||||
private String name;
|
||||
private String url;
|
||||
private String email;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.datax.service.data.market.mapping.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity(debug = false)
|
||||
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable().authorizeRequests().anyRequest().permitAll().and().logout().permitAll();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package cn.datax.service.data.market.mapping.controller;
|
||||
|
||||
import cn.datax.common.core.JsonPage;
|
||||
import cn.datax.common.core.R;
|
||||
import cn.datax.common.validate.ValidationGroups;
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
import cn.datax.service.data.market.api.entity.ApiLogEntity;
|
||||
import cn.datax.service.data.market.api.query.ApiLogQuery;
|
||||
import cn.datax.service.data.market.api.vo.ApiLogVo;
|
||||
import cn.datax.service.data.market.mapping.mapstruct.ApiLogMapper;
|
||||
import cn.datax.service.data.market.mapping.service.ApiLogService;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import cn.datax.common.base.BaseController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* api调用日志信息表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author AllDataDC
|
||||
* @date 2022-11-21
|
||||
*/
|
||||
@Api(tags = {"api调用日志信息表"})
|
||||
@RestController
|
||||
@RequestMapping("/apiLogs")
|
||||
public class ApiLogController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiLogService apiLogService;
|
||||
|
||||
@Autowired
|
||||
private ApiLogMapper apiLogMapper;
|
||||
|
||||
/**
|
||||
* 通过ID查询信息
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "获取详细信息", notes = "根据url的id来获取详细信息")
|
||||
@ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "String", paramType = "path")
|
||||
@GetMapping("/{id}")
|
||||
public R getApiLogById(@PathVariable String id) {
|
||||
ApiLogEntity apiLogEntity = apiLogService.getApiLogById(id);
|
||||
return R.ok().setData(apiLogMapper.toVO(apiLogEntity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询信息
|
||||
*
|
||||
* @param apiLogQuery
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "分页查询", notes = "")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "apiLogQuery", value = "查询实体apiLogQuery", required = true, dataTypeClass = ApiLogQuery.class)
|
||||
})
|
||||
@GetMapping("/page")
|
||||
public R getApiLogPage(ApiLogQuery apiLogQuery) {
|
||||
QueryWrapper<ApiLogEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.like(StrUtil.isNotBlank(apiLogQuery.getApiName()), "api.api_name", apiLogQuery.getApiName());
|
||||
IPage<ApiLogEntity> page = apiLogService.page(new Page<>(apiLogQuery.getPageNum(), apiLogQuery.getPageSize()), queryWrapper.orderByDesc("id"));
|
||||
List<ApiLogVo> collect = page.getRecords().stream().map(apiLogMapper::toVO).collect(Collectors.toList());
|
||||
JsonPage<ApiLogVo> jsonPage = new JsonPage<>(page.getCurrent(), page.getSize(), page.getTotal(), collect);
|
||||
return R.ok().setData(jsonPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @param apiLog
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "添加信息", notes = "根据apiLog对象添加信息")
|
||||
@ApiImplicitParam(name = "apiLog", value = "详细实体apiLog", required = true, dataType = "ApiLogDto")
|
||||
@PostMapping()
|
||||
public R saveApiLog(@RequestBody @Validated({ValidationGroups.Insert.class}) ApiLogDto apiLog) {
|
||||
ApiLogEntity apiLogEntity = apiLogService.saveApiLog(apiLog);
|
||||
return R.ok().setData(apiLogMapper.toVO(apiLogEntity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
* @param apiLog
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "修改信息", notes = "根据url的id来指定修改对象,并根据传过来的信息来修改详细信息")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "String", paramType = "path"),
|
||||
@ApiImplicitParam(name = "apiLog", value = "详细实体apiLog", required = true, dataType = "ApiLogDto")
|
||||
})
|
||||
@PutMapping("/{id}")
|
||||
public R updateApiLog(@PathVariable String id, @RequestBody @Validated({ValidationGroups.Update.class}) ApiLogDto apiLog) {
|
||||
ApiLogEntity apiLogEntity = apiLogService.updateApiLog(apiLog);
|
||||
return R.ok().setData(apiLogMapper.toVO(apiLogEntity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "删除", notes = "根据url的id来指定删除对象")
|
||||
@ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "String", paramType = "path")
|
||||
@DeleteMapping("/{id}")
|
||||
public R deleteApiLogById(@PathVariable String id) {
|
||||
apiLogService.deleteApiLogById(id);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param ids
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "批量删除角色", notes = "根据url的ids来批量删除对象")
|
||||
@ApiImplicitParam(name = "ids", value = "ID集合", required = true, dataType = "List", paramType = "path")
|
||||
@DeleteMapping("/batch/{ids}")
|
||||
public R deleteApiLogBatch(@PathVariable List<String> ids) {
|
||||
apiLogService.deleteApiLogBatch(ids);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.datax.service.data.market.mapping.controller;
|
||||
|
||||
import cn.datax.common.base.BaseController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/inner")
|
||||
public class InnerController extends BaseController {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.datax.service.data.market.mapping.dao;
|
||||
|
||||
import cn.datax.common.base.BaseDao;
|
||||
import cn.datax.service.data.market.api.entity.ApiLogEntity;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Mapper
|
||||
public interface ApiLogDao extends BaseDao<ApiLogEntity> {
|
||||
|
||||
@Override
|
||||
ApiLogEntity selectById(Serializable id);
|
||||
|
||||
@Override
|
||||
<E extends IPage<ApiLogEntity>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<ApiLogEntity> queryWrapper);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package cn.datax.service.data.market.mapping.factory;
|
||||
|
||||
import cn.datax.service.data.market.mapping.factory.crypto.Crypto;
|
||||
|
||||
public abstract class AbstractFactory {
|
||||
|
||||
public abstract Crypto getCrypto(String type);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.datax.service.data.market.mapping.factory;
|
||||
|
||||
import cn.datax.service.data.market.api.enums.AlgorithmCrypto;
|
||||
import cn.datax.service.data.market.mapping.factory.crypto.AlgorithmRegistry;
|
||||
import cn.datax.service.data.market.mapping.factory.crypto.Crypto;
|
||||
|
||||
public class AlgorithmFactory extends AbstractFactory {
|
||||
|
||||
private static final AlgorithmRegistry ALGORITHM_REGISTRY = new AlgorithmRegistry();
|
||||
|
||||
@Override
|
||||
public Crypto getCrypto(String type) {
|
||||
AlgorithmCrypto crypto = AlgorithmCrypto.getAlgorithmCrypto(type);
|
||||
return ALGORITHM_REGISTRY.getAlgorithm(crypto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.datax.service.data.market.mapping.factory;
|
||||
|
||||
import cn.datax.service.data.market.api.enums.CipherType;
|
||||
|
||||
public class FactoryProducer {
|
||||
|
||||
public static AbstractFactory getFactory(String type){
|
||||
CipherType cipherType = CipherType.getCipherType(type);
|
||||
switch (cipherType) {
|
||||
case REGEX:
|
||||
return new RegexFactory();
|
||||
case ALGORITHM:
|
||||
return new AlgorithmFactory();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.datax.service.data.market.mapping.factory;
|
||||
|
||||
import cn.datax.service.data.market.api.enums.RegexCrypto;
|
||||
import cn.datax.service.data.market.mapping.factory.crypto.Crypto;
|
||||
import cn.datax.service.data.market.mapping.factory.crypto.RegexRegistry;
|
||||
|
||||
public class RegexFactory extends AbstractFactory {
|
||||
|
||||
private static final RegexRegistry REGEX_REGISTRY = new RegexRegistry();
|
||||
|
||||
@Override
|
||||
public Crypto getCrypto(String type) {
|
||||
RegexCrypto crypto = RegexCrypto.getRegexCrypto(type);
|
||||
return REGEX_REGISTRY.getRegex(crypto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [地址] 只显示前六位,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>
|
||||
*/
|
||||
public class ADDRESSCrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.rightPad(StringUtils.left(content, 6), StringUtils.length(content), "*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class AESCrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StrUtil.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
//1.构造密钥生成器,指定为AES算法,不区分大小写
|
||||
KeyGenerator kGen = KeyGenerator.getInstance("AES");
|
||||
//2.根据 RULES 规则初始化密钥生成器,根据传入的字节数组生成一个128位的随机源
|
||||
kGen.init(128, new SecureRandom(SLAT.getBytes(CHARSET_UTF8)));
|
||||
//3.产生原始对称密钥
|
||||
SecretKey secretKey = kGen.generateKey();
|
||||
//4.获得原始对称密钥的字节数组
|
||||
byte[] enCodeFormat = secretKey.getEncoded();
|
||||
//5.根据字节数组生成AES密钥
|
||||
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
|
||||
//6.根据指定算法AES生成密码器
|
||||
Cipher cipher = Cipher.getInstance("AES");
|
||||
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
//8.根据密码器的初始化方式--加密:将数据加密
|
||||
byte[] AES_encrypt = cipher.doFinal(content.getBytes(CHARSET_UTF8));
|
||||
//9.将字符串返回
|
||||
return Base64.getEncoder().encodeToString(AES_encrypt);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
//1.构造密钥生成器,指定为AES算法,不区分大小写
|
||||
KeyGenerator kGen = KeyGenerator.getInstance("AES");
|
||||
//2.根据 RULES 规则初始化密钥生成器,根据传入的字节数组生成一个128位的随机源
|
||||
kGen.init(128, new SecureRandom(SLAT.getBytes(CHARSET_UTF8)));
|
||||
//3.产生原始对称密钥
|
||||
SecretKey secretKey = kGen.generateKey();
|
||||
//4.获得原始对称密钥的字节数组
|
||||
byte[] enCodeFormat = secretKey.getEncoded();
|
||||
//5.根据字节数组生成AES密钥
|
||||
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
|
||||
//6.根据指定算法AES生成密码器
|
||||
Cipher cipher = Cipher.getInstance("AES");
|
||||
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
|
||||
//8.根据密码器的初始化方式--加密:将数据加密
|
||||
byte[] AES_decode = cipher.doFinal(Base64.getDecoder().decode(content));
|
||||
return new String(AES_decode, CHARSET_UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import cn.datax.service.data.market.api.enums.AlgorithmCrypto;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AlgorithmRegistry {
|
||||
|
||||
private final Map<AlgorithmCrypto, Crypto> algorithm_enum_map = new EnumMap<>(AlgorithmCrypto.class);
|
||||
|
||||
public AlgorithmRegistry() {
|
||||
algorithm_enum_map.put(AlgorithmCrypto.BASE64, new BASE64Crypto());
|
||||
algorithm_enum_map.put(AlgorithmCrypto.AES, new AESCrypto());
|
||||
algorithm_enum_map.put(AlgorithmCrypto.DES, new DESCrypto());
|
||||
algorithm_enum_map.put(AlgorithmCrypto.MD5, new MD5Crypto());
|
||||
algorithm_enum_map.put(AlgorithmCrypto.SHA_1, new SHA1Crypto());
|
||||
algorithm_enum_map.put(AlgorithmCrypto.SHA_256, new SHA256Crypto());
|
||||
}
|
||||
|
||||
public Crypto getAlgorithm(AlgorithmCrypto crypto) {
|
||||
return algorithm_enum_map.get(crypto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>
|
||||
*/
|
||||
public class BANKCARDCrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.left(content, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(content, 4), StringUtils.length(content), "*"), "******"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class BASE64Crypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] encode = Base64.getEncoder().encode(content.getBytes(CHARSET_UTF8));
|
||||
return new String(encode, CHARSET_UTF8);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] decode = Base64.getDecoder().decode(content.getBytes(CHARSET_UTF8));
|
||||
return new String(decode, CHARSET_UTF8);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [中文姓名] 只显示第一个汉字,其他隐藏为星号<例子:李**>
|
||||
*/
|
||||
public class CHINESENAMECrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.rightPad(StringUtils.left(content, 1), StringUtils.length(content), "*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [公司开户银行联号] 公司开户银行联行号,显示前四位,其他用星号隐藏,每位1个星号<例子:1234********>
|
||||
*/
|
||||
public class CNAPSCODECrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.rightPad(StringUtils.left(content, 4), StringUtils.length(content), "*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
public interface Crypto {
|
||||
|
||||
/**
|
||||
* 字符编码
|
||||
*/
|
||||
String CHARSET_UTF8 = "UTF-8";
|
||||
/**
|
||||
* 密码盐
|
||||
*/
|
||||
String SLAT = "DATAX:20200101";
|
||||
|
||||
String encrypt(String content);
|
||||
|
||||
String decrypt(String content);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class DESCrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
KeyGenerator kGen = KeyGenerator.getInstance("DES");
|
||||
kGen.init(56, new SecureRandom(SLAT.getBytes(CHARSET_UTF8)));
|
||||
SecretKey secretKey = kGen.generateKey();
|
||||
byte[] enCodeFormat = secretKey.getEncoded();
|
||||
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "DES");
|
||||
Cipher cipher = Cipher.getInstance("DES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] DES_encrypt = cipher.doFinal(content.getBytes(CHARSET_UTF8));
|
||||
return Base64.getEncoder().encodeToString(DES_encrypt);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
KeyGenerator kGen = KeyGenerator.getInstance("DES");
|
||||
kGen.init(56, new SecureRandom(SLAT.getBytes(CHARSET_UTF8)));
|
||||
SecretKey secretKey = kGen.generateKey();
|
||||
byte[] enCodeFormat = secretKey.getEncoded();
|
||||
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "DES");
|
||||
Cipher cipher = Cipher.getInstance("DES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
byte[] DES_decrypt = cipher.doFinal(Base64.getDecoder().decode(content));
|
||||
return new String(DES_decrypt, CHARSET_UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [电子邮箱] 只显示前三后显示邮箱后缀,其他隐藏为星号<例子:312****@qq.com>
|
||||
*/
|
||||
public class EMAILCrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return content.replaceAll("(\\w{3}).*@(\\w+)", "$1****@$2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [固定电话] 后四位,其他隐藏<例子:****1234>
|
||||
*/
|
||||
public class FIXEDPHONECrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.leftPad(StringUtils.right(content, 4), StringUtils.length(content), "*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
|
||||
*/
|
||||
public class IDCARDCrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.leftPad(StringUtils.right(content, 4), StringUtils.length(content), "*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
public class MD5Crypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
MessageDigest md5 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("MD5");
|
||||
md5.update(content.getBytes(CHARSET_UTF8));
|
||||
md5.update(SLAT.getBytes(CHARSET_UTF8));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(md5.digest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
|
||||
*/
|
||||
public class MOBILEPHONECrypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.left(content, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(content, 4), StringUtils.length(content), "*"), "***"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import cn.datax.service.data.market.api.enums.RegexCrypto;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RegexRegistry {
|
||||
|
||||
private final Map<RegexCrypto, Crypto> regex_enum_map = new EnumMap<>(RegexCrypto.class);
|
||||
|
||||
public RegexRegistry() {
|
||||
regex_enum_map.put(RegexCrypto.CHINESE_NAME, new CHINESENAMECrypto());
|
||||
regex_enum_map.put(RegexCrypto.ID_CARD, new IDCARDCrypto());
|
||||
regex_enum_map.put(RegexCrypto.FIXED_PHONE, new FIXEDPHONECrypto());
|
||||
regex_enum_map.put(RegexCrypto.MOBILE_PHONE, new MOBILEPHONECrypto());
|
||||
regex_enum_map.put(RegexCrypto.ADDRESS, new ADDRESSCrypto());
|
||||
regex_enum_map.put(RegexCrypto.EMAIL, new EMAILCrypto());
|
||||
regex_enum_map.put(RegexCrypto.BANK_CARD, new BANKCARDCrypto());
|
||||
regex_enum_map.put(RegexCrypto.CNAPS_CODE, new CNAPSCODECrypto());
|
||||
}
|
||||
|
||||
public Crypto getRegex(RegexCrypto crypto) {
|
||||
return regex_enum_map.get(crypto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SHA1Crypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
MessageDigest md5 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("SHA-1");
|
||||
md5.update(content.getBytes(CHARSET_UTF8));
|
||||
md5.update(SLAT.getBytes(CHARSET_UTF8));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(md5.digest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.datax.service.data.market.mapping.factory.crypto;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SHA256Crypto implements Crypto {
|
||||
|
||||
@Override
|
||||
public String encrypt(String content) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
MessageDigest md5 = null;
|
||||
try {
|
||||
md5 = MessageDigest.getInstance("SHA-256");
|
||||
md5.update(content.getBytes(CHARSET_UTF8));
|
||||
md5.update(SLAT.getBytes(CHARSET_UTF8));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(md5.digest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String content) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package cn.datax.service.data.market.mapping.handler;
|
||||
|
||||
import cn.datax.service.data.market.api.entity.DataApiEntity;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
public class MappingHandlerMapping {
|
||||
|
||||
private static Map<String, DataApiEntity> mappings = new ConcurrentHashMap<>();
|
||||
private RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||
private RequestHandler handler;
|
||||
private Method method;
|
||||
|
||||
{
|
||||
try {
|
||||
method = RequestHandler.class.getDeclaredMethod("invoke", HttpServletRequest.class, HttpServletResponse.class, Map.class, Map.class, Map.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private String ignore = "services";
|
||||
private String prefix = "v1.0.0";
|
||||
private String separator = "/";
|
||||
|
||||
public MappingHandlerMapping() {}
|
||||
|
||||
public void setRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping) {
|
||||
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
|
||||
}
|
||||
|
||||
public void setHandler(RequestHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public static DataApiEntity getMappingApiInfo(HttpServletRequest request) {
|
||||
NativeWebRequest webRequest = new ServletWebRequest(request);
|
||||
String requestMapping = (String) webRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
return getMappingApiInfo(buildMappingKey(request.getMethod(), requestMapping));
|
||||
}
|
||||
|
||||
public static DataApiEntity getMappingApiInfo(String key) {
|
||||
return mappings.get(key);
|
||||
}
|
||||
|
||||
public static String buildMappingKey(String requestMethod, String requestMapping) {
|
||||
return requestMethod.toUpperCase() + ":" + requestMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册请求映射
|
||||
*
|
||||
* @param api
|
||||
*/
|
||||
public void registerMapping(DataApiEntity api) {
|
||||
String mappingKey = getMappingKey(api);
|
||||
if (mappings.containsKey(mappingKey)) {
|
||||
// 取消注册
|
||||
mappings.remove(mappingKey);
|
||||
requestMappingHandlerMapping.unregisterMapping(getRequestMapping(api));
|
||||
}
|
||||
log.info("注册接口:{}", api.getApiName());
|
||||
RequestMappingInfo requestMapping = getRequestMapping(api);
|
||||
mappings.put(mappingKey, api);
|
||||
requestMappingHandlerMapping.registerMapping(requestMapping, handler, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册请求映射
|
||||
*
|
||||
* @param api
|
||||
*/
|
||||
public void unregisterMapping(DataApiEntity api) {
|
||||
log.info("取消注册接口:{}", api.getApiName());
|
||||
String mappingKey = getMappingKey(api);
|
||||
if (mappings.containsKey(mappingKey)) {
|
||||
// 取消注册
|
||||
mappings.remove(mappingKey);
|
||||
requestMappingHandlerMapping.unregisterMapping(getRequestMapping(api));
|
||||
}
|
||||
}
|
||||
|
||||
private String getMappingKey(DataApiEntity api) {
|
||||
return buildMappingKey(api.getReqMethod().toUpperCase(), getRequestPath(api.getApiVersion(), api.getApiUrl()));
|
||||
}
|
||||
|
||||
private RequestMappingInfo getRequestMapping(DataApiEntity api) {
|
||||
return RequestMappingInfo.paths(getRequestPath(api.getApiVersion(), api.getApiUrl())).methods(RequestMethod.valueOf(api.getReqMethod().toUpperCase())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用接口 /services/v1.0.0/user/1
|
||||
* @param version
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
private String getRequestPath(String version, String path) {
|
||||
if (version != null) {
|
||||
prefix = version;
|
||||
}
|
||||
return separator + ignore + separator + prefix + (path.startsWith(separator) ? path : (separator + path));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.datax.service.data.market.mapping.handler;
|
||||
|
||||
import cn.datax.common.core.R;
|
||||
import cn.datax.common.database.core.PageResult;
|
||||
import cn.datax.service.data.market.api.entity.DataApiEntity;
|
||||
import cn.datax.service.data.market.mapping.service.impl.ApiMappingEngine;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class RequestHandler {
|
||||
|
||||
private RequestInterceptor requestInterceptor;
|
||||
|
||||
private ApiMappingEngine apiMappingEngine;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public void setRequestInterceptor(RequestInterceptor requestInterceptor) {
|
||||
this.requestInterceptor = requestInterceptor;
|
||||
}
|
||||
|
||||
public void setApiMappingEngine(ApiMappingEngine apiMappingEngine) {
|
||||
this.apiMappingEngine = apiMappingEngine;
|
||||
}
|
||||
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ResponseBody
|
||||
public Object invoke(HttpServletRequest request, HttpServletResponse response,
|
||||
@PathVariable(required = false) Map<String, Object> pathVariables,
|
||||
@RequestParam(required = false) Map<String, Object> requestParams,
|
||||
@RequestBody(required = false) Map<String, Object> requestBodys) {
|
||||
DataApiEntity api;
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (MapUtil.isNotEmpty(pathVariables)) {
|
||||
log.info("pathVariables:{}", pathVariables.toString());
|
||||
params.putAll(pathVariables);
|
||||
}
|
||||
if (MapUtil.isNotEmpty(requestParams)) {
|
||||
log.info("requestParams:{}", requestParams.toString());
|
||||
params.putAll(requestParams);
|
||||
}
|
||||
if (MapUtil.isNotEmpty(requestBodys)) {
|
||||
log.info("requestBodys:{}", requestBodys.toString());
|
||||
params.putAll(requestBodys);
|
||||
}
|
||||
api = MappingHandlerMapping.getMappingApiInfo(request);
|
||||
// 序列化
|
||||
api = objectMapper.readValue(objectMapper.writeValueAsString(api), DataApiEntity.class);
|
||||
// 执行前置拦截器
|
||||
requestInterceptor.preHandle(request, response, api, params);
|
||||
PageResult<Map<String, Object>> value = apiMappingEngine.execute(api, params);
|
||||
// 执行后置拦截器
|
||||
requestInterceptor.postHandle(request, response, api, params, value);
|
||||
return R.ok().setData(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package cn.datax.service.data.market.mapping.handler;
|
||||
|
||||
import cn.datax.common.core.DataConstant;
|
||||
import cn.datax.common.exception.DataException;
|
||||
import cn.datax.common.utils.IPUtil;
|
||||
import cn.datax.common.utils.MD5Util;
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
import cn.datax.service.data.market.api.dto.RateLimit;
|
||||
import cn.datax.service.data.market.api.entity.DataApiEntity;
|
||||
import cn.datax.service.data.market.api.enums.ParamType;
|
||||
import cn.datax.service.data.market.mapping.utils.ThreadUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class RequestInterceptor {
|
||||
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
public RequestInterceptor(RedisTemplate<String, Object> redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求之前执行
|
||||
*
|
||||
* @return 当返回对象时,直接将此对象返回到页面,返回null时,继续执行后续操作
|
||||
* @throws Exception
|
||||
*/
|
||||
public void preHandle(HttpServletRequest request, HttpServletResponse response, DataApiEntity api, Map<String, Object> params) throws Exception {
|
||||
System.out.println("************ApiInterceptor preHandle executed**********");
|
||||
String uri = request.getRequestURI();
|
||||
log.info("getRequestURI的值:" + uri);
|
||||
String ipAddr = IPUtil.getIpAddr(request);
|
||||
log.info("ipAddr的值:" + ipAddr);
|
||||
|
||||
// 密钥校验
|
||||
String apiKey = request.getHeader("api_key");
|
||||
String secretKey = request.getHeader("secret_key");
|
||||
if (StrUtil.isBlank(apiKey) || StrUtil.isBlank(secretKey)) {
|
||||
throw new DataException("api_key或secret_key空");
|
||||
}
|
||||
MD5Util mt = MD5Util.getInstance();
|
||||
String apiId = mt.decode(apiKey);
|
||||
String userId = mt.decode(secretKey);
|
||||
|
||||
// 黑名单校验
|
||||
String deny = api.getDeny();
|
||||
if (StrUtil.isNotBlank(deny)) {
|
||||
List<String> denyList = Arrays.asList(deny.split(","));
|
||||
if (CollUtil.isNotEmpty(denyList)) {
|
||||
for (String ip : denyList) {
|
||||
if(ip.equals(ipAddr)){
|
||||
throw new DataException(ip + "已被加入IP黑名单");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 参数校验
|
||||
if (MapUtil.isNotEmpty(params)) {
|
||||
api.getReqParams().forEach(param -> {
|
||||
if (params.containsKey(param.getParamName())) {
|
||||
// 参数类型是否正确
|
||||
ParamType.parse(ParamType.getParamType(param.getParamType()), params.get(param.getParamName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 限流校验
|
||||
RateLimit rateLimit = api.getRateLimit();
|
||||
if (DataConstant.TrueOrFalse.TRUE.getKey().equals(rateLimit.getEnable())) {
|
||||
Integer times = rateLimit.getTimes();
|
||||
Integer seconds = rateLimit.getSeconds();
|
||||
// 请求次数
|
||||
times = Optional.ofNullable(times).orElse(5);
|
||||
// 请求时间范围60秒
|
||||
seconds = Optional.ofNullable(seconds).orElse(60);
|
||||
// 根据 USER + API 限流
|
||||
String key = "user:" + userId + ":api:" + apiId;
|
||||
// 根据key获取已请求次数
|
||||
Integer maxTimes = (Integer) redisTemplate.opsForValue().get(key);
|
||||
if (maxTimes == null) {
|
||||
// set时一定要加过期时间
|
||||
redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);
|
||||
} else if (maxTimes < times) {
|
||||
redisTemplate.opsForValue().set(key, maxTimes + 1, seconds, TimeUnit.SECONDS);
|
||||
} else {
|
||||
throw new DataException("API调用过于频繁");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行完毕之后执行
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, DataApiEntity api, Map<String, Object> params, Object value) throws Exception {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.datax.service.data.market.mapping.mapstruct;
|
||||
|
||||
import cn.datax.common.mapstruct.EntityMapper;
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
import cn.datax.service.data.market.api.entity.ApiLogEntity;
|
||||
import cn.datax.service.data.market.api.vo.ApiLogVo;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface ApiLogMapper extends EntityMapper<ApiLogDto, ApiLogEntity, ApiLogVo> {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.datax.service.data.market.mapping.service;
|
||||
|
||||
import cn.datax.common.base.BaseService;
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
import cn.datax.service.data.market.api.entity.ApiLogEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiLogService extends BaseService<ApiLogEntity> {
|
||||
|
||||
ApiLogEntity saveApiLog(ApiLogDto apiLog);
|
||||
|
||||
ApiLogEntity updateApiLog(ApiLogDto apiLog);
|
||||
|
||||
ApiLogEntity getApiLogById(String id);
|
||||
|
||||
void deleteApiLogById(String id);
|
||||
|
||||
void deleteApiLogBatch(List<String> ids);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.datax.service.data.market.mapping.service.impl;
|
||||
|
||||
import cn.datax.common.base.BaseServiceImpl;
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
import cn.datax.service.data.market.api.entity.ApiLogEntity;
|
||||
import cn.datax.service.data.market.mapping.dao.ApiLogDao;
|
||||
import cn.datax.service.data.market.mapping.mapstruct.ApiLogMapper;
|
||||
import cn.datax.service.data.market.mapping.service.ApiLogService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
|
||||
public class ApiLogServiceImpl extends BaseServiceImpl<ApiLogDao, ApiLogEntity> implements ApiLogService {
|
||||
|
||||
@Autowired
|
||||
private ApiLogDao apiLogDao;
|
||||
|
||||
@Autowired
|
||||
private ApiLogMapper apiLogMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ApiLogEntity saveApiLog(ApiLogDto apiLogDto) {
|
||||
ApiLogEntity apiLog = apiLogMapper.toEntity(apiLogDto);
|
||||
apiLogDao.insert(apiLog);
|
||||
return apiLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ApiLogEntity updateApiLog(ApiLogDto apiLogDto) {
|
||||
ApiLogEntity apiLog = apiLogMapper.toEntity(apiLogDto);
|
||||
apiLogDao.updateById(apiLog);
|
||||
return apiLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiLogEntity getApiLogById(String id) {
|
||||
ApiLogEntity apiLogEntity = super.getById(id);
|
||||
return apiLogEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteApiLogById(String id) {
|
||||
apiLogDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteApiLogBatch(List<String> ids) {
|
||||
apiLogDao.deleteBatchIds(ids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package cn.datax.service.data.market.mapping.service.impl;
|
||||
|
||||
import cn.datax.common.database.DataSourceFactory;
|
||||
import cn.datax.common.database.DbQuery;
|
||||
import cn.datax.common.database.constants.DbQueryProperty;
|
||||
import cn.datax.common.database.core.PageResult;
|
||||
import cn.datax.common.exception.DataException;
|
||||
import cn.datax.common.utils.PageUtil;
|
||||
import cn.datax.common.utils.ThrowableUtil;
|
||||
import cn.datax.service.data.market.api.dto.FieldRule;
|
||||
import cn.datax.service.data.market.api.entity.ApiMaskEntity;
|
||||
import cn.datax.service.data.market.api.entity.DataApiEntity;
|
||||
import cn.datax.service.data.market.api.feign.ApiMaskServiceFeign;
|
||||
import cn.datax.service.data.market.mapping.factory.AbstractFactory;
|
||||
import cn.datax.service.data.market.mapping.factory.FactoryProducer;
|
||||
import cn.datax.service.data.market.mapping.factory.crypto.Crypto;
|
||||
import cn.datax.service.data.market.mapping.utils.SqlBuilderUtil;
|
||||
import cn.datax.service.data.metadata.api.dto.DbSchema;
|
||||
import cn.datax.service.data.metadata.api.entity.MetadataSourceEntity;
|
||||
import cn.datax.service.data.metadata.api.feign.MetadataSourceServiceFeign;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApiMappingEngine {
|
||||
|
||||
@Autowired
|
||||
private DataSourceFactory dataSourceFactory;
|
||||
|
||||
@Autowired
|
||||
private MetadataSourceServiceFeign metadataSourceServiceFeign;
|
||||
|
||||
@Autowired
|
||||
private ApiMaskServiceFeign apiMaskServiceFeign;
|
||||
|
||||
public PageResult<Map<String, Object>> execute(DataApiEntity dataApi, Map<String, Object> params) {
|
||||
MetadataSourceEntity dataSource = Optional.ofNullable(metadataSourceServiceFeign.getMetadataSourceById(dataApi.getExecuteConfig().getSourceId())).orElseThrow(() -> new DataException("API调用查询数据源出错"));
|
||||
DbSchema dbSchema = dataSource.getDbSchema();
|
||||
DbQueryProperty dbQueryProperty = new DbQueryProperty(dataSource.getDbType(), dbSchema.getHost(),
|
||||
dbSchema.getUsername(), dbSchema.getPassword(), dbSchema.getPort(), dbSchema.getDbName(), dbSchema.getSid());
|
||||
DbQuery dbQuery = Optional.ofNullable(dataSourceFactory.createDbQuery(dbQueryProperty)).orElseThrow(() -> new DataException("创建数据查询接口出错"));
|
||||
// 参数
|
||||
Integer pageNum = Integer.parseInt((String) params.getOrDefault("pageNum", 1));
|
||||
Integer pageSize = Integer.parseInt((String) params.getOrDefault("pageSize", 20));
|
||||
PageUtil pageUtil = new PageUtil(pageNum, pageSize);
|
||||
Integer offset = pageUtil.getOffset();
|
||||
SqlBuilderUtil.SqlFilterResult sqlFilterResult;
|
||||
try {
|
||||
sqlFilterResult = SqlBuilderUtil.getInstance().applyFilters(dataApi.getExecuteConfig().getSqlText(), params);
|
||||
} catch (Exception e) {
|
||||
log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
|
||||
throw new DataException("API调用动态构造SQL语句出错");
|
||||
}
|
||||
Map<String, Object> acceptedFilters = sqlFilterResult.getAcceptedFilters();
|
||||
// 数据脱敏
|
||||
List<FieldRule> rules = null;
|
||||
ApiMaskEntity apiMaskEntity = apiMaskServiceFeign.getApiMaskByApiId(dataApi.getId());
|
||||
if (apiMaskEntity != null) {
|
||||
rules = apiMaskEntity.getRules();
|
||||
}
|
||||
PageResult<Map<String, Object>> pageResult;
|
||||
try {
|
||||
pageResult = dbQuery.queryByPage(sqlFilterResult.getSql(), acceptedFilters, offset, pageSize);
|
||||
} catch (Exception e) {
|
||||
log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
|
||||
throw new DataException("API调用查询结果集出错");
|
||||
}
|
||||
try {
|
||||
if (CollUtil.isNotEmpty(rules)){
|
||||
// 并行流处理脱敏
|
||||
List<FieldRule> finalRules = rules;
|
||||
pageResult.getData().parallelStream().forEach(m -> {
|
||||
finalRules.forEach(r -> {
|
||||
if (m.containsKey(r.getFieldName())) {
|
||||
Object obj = m.get(r.getFieldName());
|
||||
if (null != obj){
|
||||
AbstractFactory factory = FactoryProducer.getFactory(r.getCipherType());
|
||||
Crypto crypto = factory.getCrypto(r.getCryptType());
|
||||
String encrypt = crypto.encrypt(String.valueOf(obj));
|
||||
m.put(r.getFieldName(), encrypt);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
|
||||
throw new DataException("API调用数据脱敏出错");
|
||||
}
|
||||
pageResult.setPageNum(pageNum).setPageSize(pageSize);
|
||||
return pageResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package cn.datax.service.data.market.mapping.utils;
|
||||
|
||||
import cn.datax.common.exception.DataException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 带参数sql处理工具类
|
||||
*/
|
||||
public class NamedParameterUtil {
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// String sql = "select * from user where 1 = 1 ${ and id = :id } ${and name = :name}";
|
||||
// int start = sql.indexOf("${");
|
||||
// int end = sql.indexOf("}", start);
|
||||
// String key = sql.substring(start + 2, end);
|
||||
// System.out.println(key);
|
||||
// Map<String, Object> params = new HashMap<>();
|
||||
// params.put("name", "yuwei");
|
||||
// params.put("id", "123");
|
||||
// params.put("age", 12);
|
||||
// ParsedSql parsedSql = NamedParameterUtil.parseSqlStatement(key);
|
||||
// System.out.println(parsedSql);
|
||||
// String actualSql = NamedParameterUtil.substituteNamedParams(parsedSql, params);
|
||||
// Map<String, Object> acceptedFilters = NamedParameterUtil.buildValueArray(parsedSql, params);
|
||||
// System.out.println(actualSql);
|
||||
// System.out.println(acceptedFilters);
|
||||
// SqlBuilderUtil.SqlFilterResult sqlFilterResult = SqlBuilderUtil.getInstance().applyFilters(sql, params);
|
||||
// System.out.println(sqlFilterResult.getSql());
|
||||
// Object[] array = new Object[] {};
|
||||
// array = sqlFilterResult.getAcceptedFilters().values().toArray();
|
||||
// Arrays.stream(array).forEach(s -> System.out.println(s));
|
||||
// }
|
||||
|
||||
private NamedParameterUtil() {}
|
||||
|
||||
/**
|
||||
* 定义特殊字符(增加最后的自定义的'}')
|
||||
*/
|
||||
private static final char[] PARAMETER_SEPARATORS =
|
||||
new char[] {'"', '\'', ':', '&', ',', ';', '(', ')', '|', '=', '+', '-', '*', '%', '/', '\\', '<', '>', '^', '}'};
|
||||
|
||||
/**
|
||||
* 对带参数sql的统计式封装,便于后续肢解拼装
|
||||
* @param originalSql
|
||||
* @return
|
||||
*/
|
||||
public static ParsedSql parseSqlStatement(String originalSql) {
|
||||
Assert.notNull(originalSql, "SQL must not be null");
|
||||
ParsedSql parsedSql = new ParsedSql(originalSql);
|
||||
Set<String> namedParameters = new HashSet();
|
||||
char[] sqlchars = originalSql.toCharArray();
|
||||
int namedParamCount = 0;
|
||||
int unNamedParamCount = 0;
|
||||
int totalParamCount = 0;
|
||||
int i = 0;
|
||||
while (i < sqlchars.length) {
|
||||
char statement = sqlchars[i];
|
||||
if (statement == ':') {
|
||||
int j = i + 1;
|
||||
while (j < sqlchars.length && !isSeparatorsChar(sqlchars[j])) {
|
||||
j++;
|
||||
}
|
||||
if (j - i > 1) {
|
||||
String paramName = originalSql.substring(i + 1, j);
|
||||
if (!namedParameters.contains(paramName)) {
|
||||
namedParameters.add(paramName);
|
||||
namedParamCount++;
|
||||
}
|
||||
parsedSql.addParamNames(paramName, i, j);
|
||||
totalParamCount++;
|
||||
}
|
||||
i = j - 1;
|
||||
} else if (statement == '?') {
|
||||
unNamedParamCount++;
|
||||
totalParamCount++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
parsedSql.setNamedParamCount(namedParamCount);
|
||||
parsedSql.setUnnamedParamCount(unNamedParamCount);
|
||||
parsedSql.setTotalParamCount(totalParamCount);
|
||||
return parsedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得不带参数的sql,即替换参数为?
|
||||
* @param parsedSql
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public static String substituteNamedParams(ParsedSql parsedSql, Map<String, Object> params){
|
||||
String original = parsedSql.getOriginalSql();
|
||||
StringBuffer actual = new StringBuffer("");
|
||||
int lastIndex = 0;
|
||||
List<String> paramNames = parsedSql.getParamNames();
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
int[] indexs = parsedSql.getParamIndexs(i);
|
||||
int startIndex = indexs[0];
|
||||
int endIndex = indexs[1];
|
||||
String paramName = paramNames.get(i);
|
||||
actual.append(original.substring(lastIndex, startIndex));
|
||||
if (params != null && params.containsKey(paramName)) {
|
||||
actual.append("?");
|
||||
} else{
|
||||
actual.append("?");
|
||||
}
|
||||
lastIndex = endIndex;
|
||||
}
|
||||
actual.append(original.subSequence(lastIndex, original.length()));
|
||||
return actual.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得sql所需参数K,V
|
||||
* @param parsedSql
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public static LinkedHashMap<String, Object> buildValueArray(ParsedSql parsedSql, Map<String, Object> params){
|
||||
List<String> paramNames = parsedSql.getParamNames();
|
||||
LinkedHashMap<String, Object> acceptedFilters = new LinkedHashMap<>(parsedSql.getTotalParamCount());
|
||||
if (parsedSql.getNamedParamCount() > 0 && parsedSql.getUnnamedParamCount() > 0) {
|
||||
throw new DataException("parameter方式与?方式不能混合!");
|
||||
}
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
String keyName = paramNames.get(i);
|
||||
if (params.containsKey(keyName)) {
|
||||
acceptedFilters.put(keyName, params.get(keyName));
|
||||
}
|
||||
}
|
||||
return acceptedFilters;
|
||||
}
|
||||
|
||||
private static boolean isSeparatorsChar(char statement){
|
||||
if (Character.isWhitespace(statement)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < PARAMETER_SEPARATORS.length; i++) {
|
||||
if (statement == PARAMETER_SEPARATORS[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cn.datax.service.data.market.mapping.utils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 此类封装NamedParameterSql
|
||||
*/
|
||||
public class ParsedSql implements Serializable {
|
||||
|
||||
private static final long serialVersionUID=1L;
|
||||
|
||||
private String originalSql;
|
||||
//参数名
|
||||
private List<String> paramNames = new ArrayList<>();
|
||||
//参数在sql中对应的位置
|
||||
private List<int[]> paramIndexs = new ArrayList<>();
|
||||
//统计参数个数(不包含重复)
|
||||
private int namedParamCount;
|
||||
//统计sql中?的个数
|
||||
private int unnamedParamCount;
|
||||
|
||||
private int totalParamCount;
|
||||
|
||||
public ParsedSql(String originalSql){
|
||||
this.originalSql = originalSql;
|
||||
}
|
||||
|
||||
public List<String> getParamNames() {
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
public void addParamNames(String paramName,int startIndex,int endIndex) {
|
||||
paramNames.add(paramName);
|
||||
paramIndexs.add(new int[]{startIndex,endIndex});
|
||||
}
|
||||
|
||||
public int[] getParamIndexs(int position) {
|
||||
return paramIndexs.get(position);
|
||||
}
|
||||
|
||||
public String getOriginalSql() {
|
||||
return originalSql;
|
||||
}
|
||||
|
||||
public int getNamedParamCount() {
|
||||
return namedParamCount;
|
||||
}
|
||||
|
||||
public void setNamedParamCount(int namedParamCount) {
|
||||
this.namedParamCount = namedParamCount;
|
||||
}
|
||||
|
||||
public int getUnnamedParamCount() {
|
||||
return unnamedParamCount;
|
||||
}
|
||||
|
||||
public void setUnnamedParamCount(int unnamedParamCount) {
|
||||
this.unnamedParamCount = unnamedParamCount;
|
||||
}
|
||||
|
||||
public int getTotalParamCount() {
|
||||
return totalParamCount;
|
||||
}
|
||||
|
||||
public void setTotalParamCount(int totalParamCount) {
|
||||
this.totalParamCount = totalParamCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ParsedSql{" +
|
||||
"originalSql='" + originalSql + '\'' +
|
||||
", paramNames=" + paramNames +
|
||||
", paramIndexs=" + paramIndexs +
|
||||
", namedParamCount=" + namedParamCount +
|
||||
", unnamedParamCount=" + unnamedParamCount +
|
||||
", totalParamCount=" + totalParamCount +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package cn.datax.service.data.market.mapping.utils;
|
||||
|
||||
import cn.datax.service.data.market.api.dto.ReqParam;
|
||||
import cn.datax.service.data.market.api.enums.WhereType;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用于动态构造sql语句
|
||||
* ${ segment... } 为一个条件代码块
|
||||
*
|
||||
* String sql = "select * from user where 1=1
|
||||
* ${ and username = :username }
|
||||
* ${ and password = :password }
|
||||
* ${ and age = :age }"
|
||||
*
|
||||
* Map filters = new HashMap();
|
||||
* filters.put("username", "yuwei");
|
||||
* filters.put("age", "12");
|
||||
* filters.put("id", "123");
|
||||
*
|
||||
* SqlFilterResult result = SqlBuilderUtil.applyFilters(sql, filters);
|
||||
*
|
||||
* result.getSql()结果
|
||||
* select * from user where 1=1 and username=:username and age=:age
|
||||
*
|
||||
* result.getAcceptedFilters()结果
|
||||
* {username=yuwei}
|
||||
* {age=12}
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlBuilderUtil {
|
||||
|
||||
private SqlBuilderUtil() {}
|
||||
|
||||
private static volatile SqlBuilderUtil instance;
|
||||
|
||||
public static SqlBuilderUtil getInstance() {
|
||||
if(instance == null) {
|
||||
synchronized (SqlBuilderUtil.class) {
|
||||
if(instance == null) {
|
||||
instance = new SqlBuilderUtil();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空格
|
||||
*/
|
||||
private final String SPACE = " ";
|
||||
/**
|
||||
* 冒号占位符
|
||||
*/
|
||||
private final String COLON = ":";
|
||||
/**
|
||||
* 问号占位符
|
||||
*/
|
||||
private final String MARK = "?";
|
||||
/**
|
||||
* where关键字
|
||||
*/
|
||||
private final String WHERE_SQL = "WHERE";
|
||||
/**
|
||||
* AND连接符
|
||||
*/
|
||||
private final String WHERE_AND = "AND";
|
||||
/**
|
||||
* where 1=1条件
|
||||
*/
|
||||
private final String WHERE_INIT = WHERE_SQL + " 1 = 1";
|
||||
/**
|
||||
* 左括号
|
||||
*/
|
||||
private final String LEFT_BRACKET = "(";
|
||||
/**
|
||||
* 右括号
|
||||
*/
|
||||
private final String RIGHT_BRACKET = ")";
|
||||
/**
|
||||
* 百分号%
|
||||
*/
|
||||
private final String PERCENT_SIGN = "%";
|
||||
/**
|
||||
* 单引号 '
|
||||
*/
|
||||
private final String SINGLE_QUOTE = "'";
|
||||
/**
|
||||
* 条件代码块标记开始
|
||||
*/
|
||||
public final String MARK_KEY_START = "${";
|
||||
/**
|
||||
* 条件代码块标记结束
|
||||
*/
|
||||
public final String MARK_KEY_END = "}";
|
||||
|
||||
/**
|
||||
* 拼接命名参数sql
|
||||
* @param sql
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public String buildHql(String sql, List<ReqParam> params){
|
||||
Assert.notNull(sql, "SQL must not be null");
|
||||
return buildHql(new StringBuffer(sql), params);
|
||||
}
|
||||
|
||||
private String buildHql(StringBuffer sql, List<ReqParam> params){
|
||||
if(CollUtil.isEmpty(params)){
|
||||
return sql.toString();
|
||||
}
|
||||
sql.append(SPACE).append(WHERE_INIT);
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
ReqParam reqParam = params.get(i);
|
||||
sql.append(SPACE).append(MARK_KEY_START).append(WHERE_AND).append(SPACE).append(reqParam.getParamName());
|
||||
if (WhereType.LIKE.getType() == reqParam.getWhereType()) {
|
||||
// LIKE '%' :username '%' ,:username 两边一定要有空格,如果没有空格,是查询不到数据的
|
||||
sql.append(SPACE).append(WhereType.getWhereType(reqParam.getWhereType()).getKey())
|
||||
.append(SPACE).append(SINGLE_QUOTE).append(PERCENT_SIGN).append(SINGLE_QUOTE).append(SPACE)
|
||||
.append(COLON).append(reqParam.getParamName())
|
||||
.append(SPACE).append(SINGLE_QUOTE).append(PERCENT_SIGN).append(SINGLE_QUOTE).append(MARK_KEY_END);
|
||||
} else if(WhereType.LIKE_LEFT.getType() == reqParam.getWhereType()) {
|
||||
sql.append(SPACE).append(WhereType.getWhereType(reqParam.getWhereType()).getKey())
|
||||
.append(SPACE).append(SINGLE_QUOTE).append(PERCENT_SIGN).append(SINGLE_QUOTE).append(SPACE)
|
||||
.append(COLON).append(reqParam.getParamName()).append(MARK_KEY_END);
|
||||
} else if(WhereType.LIKE_RIGHT.getType() == reqParam.getWhereType()) {
|
||||
sql.append(SPACE).append(WhereType.getWhereType(reqParam.getWhereType()).getKey())
|
||||
.append(SPACE).append(COLON).append(reqParam.getParamName())
|
||||
.append(SPACE).append(SINGLE_QUOTE).append(PERCENT_SIGN).append(SINGLE_QUOTE).append(MARK_KEY_END);
|
||||
} else if(WhereType.NULL.getType() == reqParam.getWhereType() || WhereType.NOT_NULL.getType() == reqParam.getWhereType()){
|
||||
// is null或is not null不需要参数值
|
||||
sql.append(SPACE).append(WhereType.getWhereType(reqParam.getWhereType()).getKey()).append(MARK_KEY_END);
|
||||
} else if(WhereType.IN.getType() == reqParam.getWhereType()){
|
||||
// in (:ids)
|
||||
sql.append(SPACE).append(WhereType.getWhereType(reqParam.getWhereType()).getKey())
|
||||
.append(SPACE).append(LEFT_BRACKET)
|
||||
.append(COLON).append(reqParam.getParamName())
|
||||
.append(RIGHT_BRACKET).append(MARK_KEY_END);
|
||||
} else {
|
||||
sql.append(SPACE).append(WhereType.getWhereType(reqParam.getWhereType()).getKey())
|
||||
.append(SPACE).append(COLON).append(reqParam.getParamName()).append(MARK_KEY_END);
|
||||
}
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据入参动态构造sql语句
|
||||
* @param sql
|
||||
* @param filters
|
||||
* @return
|
||||
*/
|
||||
public SqlFilterResult applyFilters(String sql, Map<String, Object> filters){
|
||||
Assert.notNull(sql, "SQL must not be null");
|
||||
return applyFilters(new StringBuffer(sql), filters);
|
||||
}
|
||||
|
||||
private SqlFilterResult applyFilters(StringBuffer sql, Map<String, Object> filters){
|
||||
LinkedHashMap<String, Object> acceptedFilters = new LinkedHashMap<>();
|
||||
for (int i = 0, end = 0, start = sql.indexOf(MARK_KEY_START); ((start = sql.indexOf(MARK_KEY_START, end)) >= 0); i++) {
|
||||
end = sql.indexOf(MARK_KEY_END, start);
|
||||
// 封装该条件代码块中的NamedParameterSql
|
||||
ParsedSql parsedSql = getSegmentParsedSql(sql, start, end);
|
||||
if (CollUtil.isEmpty(parsedSql.getParamNames())){
|
||||
throw new IllegalArgumentException("Not key found in segment=" + sql.substring(start, end + MARK_KEY_END.length()));
|
||||
}
|
||||
// 判断输入参数filters中是否存在查询参数
|
||||
if (isAcceptedKeys(filters, parsedSql.getParamNames())) {
|
||||
// 动态构造可执行的sql语句,去掉条件代码块两边的${ }标记符
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("The filter namedParameters=" + parsedSql.getParamNames() + " is accepted on segment=" + sql.substring(start, end + MARK_KEY_END.length()));
|
||||
}
|
||||
// 下面方法2选1可以获取条件代码块
|
||||
// select id, name from user where 1 = 1 and id = :id
|
||||
// String segment = sql.substring(start + MARK_KEY_START.length(), end);
|
||||
String segment = parsedSql.getOriginalSql();
|
||||
// 转换命名参数:为?
|
||||
// select id, name from user where 1 = 1 and id = ?
|
||||
// String segment = NamedParameterUtil.substituteNamedParams(parsedSql, filters);
|
||||
// 获取传参中包含命名参数的数据
|
||||
LinkedHashMap<String, Object> linkAcceptedFilters = NamedParameterUtil.buildValueArray(parsedSql, filters);
|
||||
acceptedFilters.putAll(linkAcceptedFilters);
|
||||
sql.replace(start, end + MARK_KEY_END.length(), segment);
|
||||
end = start + segment.length();
|
||||
} else {
|
||||
// 抛弃该条件代码块
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("The filter namedParameters=" + parsedSql.getParamNames() + " is removed from the query on segment=" + sql.substring(start, end + MARK_KEY_END.length()));
|
||||
}
|
||||
sql.replace(start, end + MARK_KEY_END.length(), "");
|
||||
end = start;
|
||||
}
|
||||
}
|
||||
return new SqlFilterResult(sql.toString(), acceptedFilters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证入参,并过滤值为空的入参
|
||||
*/
|
||||
private boolean isAcceptedKeys(Map<String, Object> filters, List<String> keys) {
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
String key = keys.get(i);
|
||||
Object value = getProperty(filters, key);
|
||||
if (!isValuePopulated(value, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装该条件代码块中的NamedParameterSql
|
||||
*/
|
||||
private ParsedSql getSegmentParsedSql(StringBuffer sql, int start, int end) {
|
||||
String segment = sql.substring(start + MARK_KEY_START.length(), end);
|
||||
ParsedSql parsedSql = NamedParameterUtil.parseSqlStatement(segment);
|
||||
return parsedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数值
|
||||
* @param filters
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
private Object getProperty(Map<String, Object> filters, String key) {
|
||||
if (MapUtil.isEmpty(filters))
|
||||
return null;
|
||||
return filters.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证参数值是否空
|
||||
* @param value
|
||||
* @param isRemoveEmpty
|
||||
* @return
|
||||
*/
|
||||
private boolean isValuePopulated(Object value, boolean isRemoveEmpty) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (isRemoveEmpty) {
|
||||
return ObjectUtil.isNotEmpty(value);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SqlFilterResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID=1L;
|
||||
|
||||
private String sql;
|
||||
|
||||
private Map<String, Object> acceptedFilters;
|
||||
|
||||
public SqlFilterResult(String sql, Map<String, Object> acceptedFilters) {
|
||||
this.setSql(sql);
|
||||
this.setAcceptedFilters(acceptedFilters);
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public void setSql(String sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
public Map<String, Object> getAcceptedFilters() {
|
||||
return acceptedFilters;
|
||||
}
|
||||
|
||||
public void setAcceptedFilters(Map<String, Object> acceptedFilters) {
|
||||
this.acceptedFilters = acceptedFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SqlFilterResult{" +
|
||||
"sql='" + sql + '\'' +
|
||||
", acceptedFilters=" + acceptedFilters +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.datax.service.data.market.mapping.utils;
|
||||
|
||||
import cn.datax.service.data.market.api.dto.ApiLogDto;
|
||||
|
||||
public class ThreadUtil {
|
||||
|
||||
private ThreadUtil() {}
|
||||
|
||||
private static volatile ThreadUtil instance;
|
||||
|
||||
public static ThreadUtil getInstance() {
|
||||
if(instance == null) {
|
||||
synchronized (ThreadUtil.class) {
|
||||
if(instance == null) {
|
||||
instance = new ThreadUtil();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final static ThreadLocal<ApiLogDto> logHolder = new ThreadLocal<>();
|
||||
|
||||
public void set(ApiLogDto log){
|
||||
logHolder.set(log);
|
||||
}
|
||||
|
||||
public void remove(){
|
||||
logHolder.remove();
|
||||
}
|
||||
|
||||
public ApiLogDto get(){
|
||||
return logHolder.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
server:
|
||||
port: 8823
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: service-data-mapping
|
||||
profiles:
|
||||
active: dev
|
||||
cloud:
|
||||
config:
|
||||
label: master
|
||||
name: ${spring.application.name}
|
||||
profile: ${spring.profiles.active}
|
||||
discovery:
|
||||
enabled: true
|
||||
service-id: config
|
||||
|
||||
# 注册中心配置
|
||||
eureka:
|
||||
instance:
|
||||
lease-renewal-interval-in-seconds: 20
|
||||
prefer-ip-address: true
|
||||
ip-address: 192.168.1.169
|
||||
client:
|
||||
register-with-eureka: true
|
||||
fetch-registry: true
|
||||
instance-info-replication-interval-seconds: 30
|
||||
registry-fetch-interval-seconds: 3
|
||||
service-url:
|
||||
defaultZone: http://192.168.1.169:8610/eureka
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
|
||||
<property name="log.path" value="logs/service-data-api-mapping"/>
|
||||
<property name="log.maxHistory" value="15"/>
|
||||
<property name="log.totalSizeCap" value="500MB"/>
|
||||
<property name="log.maxFileSize" value="10MB"/>
|
||||
<property name="log.colorPattern"
|
||||
value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %boldCyan(${springAppName:-}) %yellow(%thread) %green(%logger) %msg%n"/>
|
||||
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level ${springAppName:-} %thread %logger %msg%n"/>
|
||||
|
||||
<!--输出到控制台-->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.colorPattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--输出到文件-->
|
||||
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
|
||||
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
|
||||
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
|
||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--日志文件路径和名称-->
|
||||
<File>${log.path}/info/info.log</File>
|
||||
<!--是否追加到文件末尾,默认为true-->
|
||||
<append>true</append>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
|
||||
<!-- 文件名:logs/project_info.2017-12-05.0.log -->
|
||||
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
|
||||
<fileNamePattern>${log.path}/info/info.%d.%i.log</fileNamePattern>
|
||||
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
|
||||
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
|
||||
<MaxHistory>${log.maxHistory}</MaxHistory>
|
||||
<!-- 每个日志文件到2mb的时候开始切分,最多保留30天,但最大到500MB,哪怕没到30天也要删除多余的日志 -->
|
||||
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
|
||||
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
|
||||
<maxFileSize>${log.maxFileSize}</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<File>${log.path}/error/error.log</File>
|
||||
<append>true</append>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/error/error.%d.%i.log</fileNamePattern>
|
||||
<MaxHistory>${log.maxHistory}</MaxHistory>
|
||||
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
|
||||
<maxFileSize>${log.maxFileSize}</maxFileSize>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="file_info"/>
|
||||
<appender-ref ref="file_error"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.datax.service.data.market.mapping.dao.ApiLogDao">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="cn.datax.service.data.market.api.entity.ApiLogEntity">
|
||||
<result column="id" property="id" />
|
||||
<result column="status" property="status" />
|
||||
<result column="api_id" property="apiId" />
|
||||
<result column="api_name" property="apiName" />
|
||||
<result column="caller_id" property="callerId" />
|
||||
<result column="caller_ip" property="callerIp" />
|
||||
<result column="caller_url" property="callerUrl" />
|
||||
<result column="caller_params" property="callerParams" />
|
||||
<result column="caller_date" property="callerDate" />
|
||||
<result column="caller_size" property="callerSize" />
|
||||
<result column="time" property="time" />
|
||||
<result column="msg" property="msg" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id,
|
||||
status,
|
||||
api_id, caller_id, caller_ip, caller_url, caller_params, caller_date, caller_size, time, msg
|
||||
</sql>
|
||||
|
||||
<sql id="Log_Column_List">
|
||||
${alias}.id,
|
||||
${alias}.status,
|
||||
${alias}.api_id, ${alias}.caller_id, ${alias}.caller_ip, ${alias}.caller_url, ${alias}.caller_params,
|
||||
${alias}.caller_date, ${alias}.caller_size, ${alias}.time, ${alias}.msg
|
||||
</sql>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT api.api_name,
|
||||
<include refid="Log_Column_List"><property name="alias" value="log"/></include>
|
||||
FROM market_api_log log
|
||||
LEFT JOIN market_api api ON api.id = log.api_id
|
||||
WHERE 1 = 1 AND log.id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectPage" resultMap="BaseResultMap">
|
||||
SELECT api.api_name,
|
||||
<include refid="Log_Column_List"><property name="alias" value="log"/></include>
|
||||
FROM market_api_log log
|
||||
LEFT JOIN market_api api ON api.id = log.api_id
|
||||
${ew.customSqlSegment}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,25 @@
|
||||
module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
|
||||
# 自定义日志打印
|
||||
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
|
||||
#日志输出到控制台
|
||||
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
|
||||
# 使用日志系统记录 sql
|
||||
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||
# 设置 p6spy driver 代理
|
||||
deregisterdrivers=true
|
||||
# 取消JDBC URL前缀
|
||||
useprefix=true
|
||||
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
|
||||
excludecategories=info,debug,result,batch,resultset
|
||||
# 日期格式
|
||||
dateformat=yyyy-MM-dd HH:mm:ss
|
||||
# 实际驱动可多个
|
||||
#driverlist=org.h2.Driver
|
||||
# 是否开启慢SQL记录
|
||||
outagedetection=true
|
||||
# 慢SQL记录标准 2 秒
|
||||
outagedetectioninterval=2
|
||||
# 开启过滤
|
||||
filter=true
|
||||
# 配置不打印的内容
|
||||
exclude=select 1
|
||||
Reference in New Issue
Block a user