This commit is contained in:
Jane
2023-12-22 10:59:10 +08:00
parent 751c43e199
commit d1ede2d4aa
2774 changed files with 291509 additions and 0 deletions

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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 {
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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 {
}
}

View File

@@ -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> {
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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 +
'}';
}
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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