diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..60253e46ccbf518908c31d266b7f4fc7fb4d45c2 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Component + +> 公共组件包,对应COLA框架中的公共组件层,为快速搭建项目抽离的通用部分,以Spring-starter的形式存在,可无缝集成到以SpringBoot为主框架的项目中。 + +## 依赖说明 + +该组件整体基于SpringBoot的`2.3.6.RELEASE`版本 + +```xml + + org.springframework.boot + spring-boot-starter-parent + 2.3.6.RELEASE + + +``` + +其他依赖仅包含通用的hutool,使用版本为:`5.7.8` + +```xml + + cn.hutool + hutool-all + 5.7.8 + +``` + +## 内容说明 + +该组件内部以构件为独立单元,项目components仅作为公共父pom进行组件依赖管理。 + +components内涵构件有: + +| 构件 | 说明 | 版本号 | +| ---------------------- | --------------- | ----- | +| es-component | ElasticSearch封装 | 1.0.0 | +| redis-component | Redis数据操作封装 | 1.0.0 | +| rest-component | REST风格的服务端封装 | 1.0.0 | +| task-component | 定时任务构件 | 1.0.0 | +| initdatabase-component | 数据库初始化构件 | 1.0.0 | + +## 使用方式 + +下载对应版本的components项目源码,本地进行编译安装,install到本地的私仓中即可单独使用包内的构件 diff --git a/es-component/README.md b/es-component/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bc80fd2cacbf62d6a9e0f9ba2f9bd4e5b4bcecde --- /dev/null +++ b/es-component/README.md @@ -0,0 +1,252 @@ +# 使用说明 + +## 1、引入依赖 + +使用maven引入依赖: + +```xml + + com.gcc.aribase + ElasticSearchServer + ${相关版本号} + +``` + + + +## 2、新增yml文件配置项 + +在yml配置文件中新增es所需的参数项: + +```yml +spring: + elasticsearch: + host: 127.0.0.1 + port: 9500 + tcpport: 9300 #若不需要es-sql功能则可忽略该属性 + user: elastic + password: ABCDEF + scheme: http + log-enable: false +``` + +参数说明: + +| 参数 | 作用 | 是否必填 | +| ---------- | ---------------------------------------- | ----- | +| host | es服务的ip地址 | 是 | +| port | es服务的端口 | 是 | +| tcpport | es的tcp服务端口,若追加该属性并进行配置,则可使用`es-sql`的功能,默认无此属性 | **否** | +| user | 如果es服务设置了安全认证,则填写用户名,否则可不填 | 否 | +| password | 如果es服务设置了安全认证,则填写密码,否则可不填 | 否 | +| scheme | 默认http方式 | 否 | +| log-enable | 是否开启dsl语句的执行记录日志,无此字段则默认是false | 否 | + + + +## 3、注入ElasticSearchActuator + +完成前两步后,则可在项目中直接使用 ElasticSearchActuator,如下: + +```java +@Service +public class ElasticSearchTest { + + @Autowired + private ElasticSearchActuator elasticSearchActuator; + +} +``` + + + +ElasticSearchActuator推荐使用官方的SearchSourceBuilder对象进行Es数据查询,如下: + +```java +@Service +public class ElasticSearchTest { + + + private final static String INDEX_NAME = "tb_user"; + + @Autowired + private ElasticSearchActuator elasticSearchActuator; + + + public Object excuteSearchData(int pageNo,String asset_type){ + Long startTime = DateUtil.beginOfDay(new Date()).getTime(); + Long endTime = DateUtil.endOfDay(new Date()).getTime(); + BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); + boolQueryBuilder.filter().add(QueryBuilders.rangeQuery("update_time").gte(startTime).lte(endTime)); + if(!StringUtils.isEmpty(asset_type)){ + boolQueryBuilder.filter().add(QueryBuilders.termQuery("asset_type",asset_type)); + } + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder() + .size(10) + .query(boolQueryBuilder) + .aggregation(AggregationBuilders.terms("uniqueKey").field("unique_key")); + SearchResult scrollret = elasticSearchActuator.scrollSearchElasticSearchDatas(INDEX_NAME,searchSourceBuilder,pageNo, JKEntity.class); + return scrollret; + } +} + +``` + + + +也可支持直接使用字符串拼写的DSL,如下: + + + +```java +@Service +public class ElasticSearchTest { + + + private final static String INDEX_NAME = "tb_user"; + + @Autowired + private ElasticSearchActuator elasticSearchActuator; + + + public Object searchUserInfo(){ + String dsl = "{\n" + + " \"size\": 0,\n" + + " \"query\": {\n" + + " \"bool\": {\n" + + " \"filter\": [\n" + + " {\n" + + " \"range\": {\n" + + " \"update_time\": {\n" + + " \"from\": 1684684800000,\n" + + " \"to\": 1684771199999\n" + + " }\n" + + " }\n" + + " },\n" + + " {\n" + + " \"term\": {\n" + + " \"asset_type\": \"server\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"aggregations\": {\n" + + " \"userName\": {\n" + + " \"cardinality\": {\n" + + " \"field\": \"userName\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; + return elasticSearchActuator.executeSearchDslStr(INDEX_NAME,dsl); + } +} +``` + + + +## 4、特殊方法使用说明 + +### method: executeSearchDslStr(String indexName,String dslBodyStr) + +> ```java +> /** +> * 执行Es的dsl(默认文档类型为_doc) +> * @param indexName 索引名称 +> * @param dslBodyStr dsl +> * @return JSONObject +> */ +> JSONObject executeSearchDslStr(String indexName,String dslBodyStr); +> ``` + + 该方法直接执行DSL,可用字符串拼接的DSL,返回值为`JSON对象`,可自行拆解 + + + +### method:executeSearchSearchSourceBuilder(String indexName, SearchSourceBuilder searchSourceBuilder,Class resultObj) + +> ```java +> /** +> * 执行Es的查询对象 SearchSourceBuilder 默认文档为_doc +> * @param indexName 索引名称 +> * @param searchSourceBuilder 查询对象 +> * @return JSONObject +> */ +> SearchResult executeSearchSearchSourceBuilder(String indexName, SearchSourceBuilder searchSourceBuilder,Class resultObj); +> ``` + + 基于`SearchSourceBuilder` 对象的执行方法,可直接执行构建好的Es查询对象,返回值为 `SearchResult` 对象;`T` 可由参数 `resultObj` 指定,可根据文档结构定义实体传入,或直接使用Map结构 + + + +### method:searchElasticSearchDatas(String indexName, SearchSourceBuilder searchSourceBuilder,Class resultObj) + +> ```java +> /** +> * 查询es +> * @param indexName 索引名称 +> * @param searchSourceBuilder 自定义查询内容 +> * @param resultObj 目标实体对象 +> * @return SearchResult +> */ +> SearchResult searchElasticSearchDatas(String indexName, SearchSourceBuilder searchSourceBuilder,Class resultObj); +> ``` + + 基于`SearchSourceBuilder` 对象的查询方法,可直接执行构建好的Es查询对象,返回值为 `SearchResult` 对象;与 ` method:executeSearchSearchSourceBuilder` 不同的地方在于,此方法仅返回查询内容,不处理聚合结果,如`SearchSourceBuilder` 对象中包含聚合语法,则建议使用 ` method:executeSearchSearchSourceBuilder` + + + +### method:scrollSearchElasticSearchDatas(String indexName,SearchSourceBuilder searchSourceBuilder,int pageNo,Class resultObj) + +> ```java +> /** +> * 分页滚动查询 +> * @param indexName 索引名称 +> * @param searchSourceBuilder 查询条件 +> * @param pageNo 页码 +> * @param resultObj 具体目标对象 +> * @return SearchResult +> */ +> SearchResult scrollSearchElasticSearchDatas(String indexName,SearchSourceBuilder searchSourceBuilder,int pageNo,Class resultObj); +> ``` + +Es的Scroll滚动分页查询,除索引和条件外,只需要传入对应的页码即可; 如不进行大范围跳页操作,使用该分页方式效果更好,如存在大范围跳页,则不建议使用该分页方式, + + + +### method:aggregateDatas(String indexName, Object searchDslStr) + +> ```java +> /** +> * 聚合es数据 +> * @param indexName 索引名称 +> * @param searchDslStr 自定义查询内容 +> * @return +> */ +> SearchResult aggregateDatas(String indexName, String searchDslStr); +> /** +> * 聚合es数据 +> * @param indexName 索引名称 +> * @param searchSourceBuilder 自定义查询内容 +> * @return +> */ +> SearchResult aggregateDatas(String indexName, SearchSourceBuilder searchSourceBuilder); +> ``` + +与 `method:searchElasticSearchDatas` 相对,专门进行聚合查询操作所建立的方法,只返回聚合结果内容,为优化聚合的性能,默认会将查询DSL对象中的 size 改为0 + + + +### method: translateSQL(String sql) + +> ```java +> /** +> * 将sql语句转换为dsl语句 +> * @param sql sql 语句 +> * @return String +> */ +> String translateSQL(String sql); +> ``` + +可将sql语句翻译为Es的DSL语句,若DSL学习不精可用此方法作为补充,使用该方法需要在 `yml配置文件` 中追加 tcpport 属性,es默认的tcp通信端口为9300,详细见 第二节`新增yml文件配置项` \ No newline at end of file diff --git a/es-component/pom.xml b/es-component/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..9cae2756372b52001741503d5d14c51b9b532c3f --- /dev/null +++ b/es-component/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + com.gcc.container + components + 1.0.0 + + com.gcc.container.components + es-component + ${component.version} + jar + + + + + 7.6.2 + + + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${es.version} + + + + + org.elasticsearch.client + transport + 7.6.1 + + + org.nlpcn + elasticsearch-sql + 7.8.0.1 + + + + + + + + diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/ApplictionConfiguration.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/ApplictionConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..5575b3e0031c577d36c6f58efa93fdbd16db9217 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/ApplictionConfiguration.java @@ -0,0 +1,9 @@ +package com.gcc.container.components.elasticsearch; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = "com.gcc.container.components.elasticsearch.*") +public class ApplictionConfiguration { +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/actuator/ElasticSearchActuator.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/actuator/ElasticSearchActuator.java new file mode 100644 index 0000000000000000000000000000000000000000..5bf53e2572f5610ca035d23eb06e15669f83c7a9 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/actuator/ElasticSearchActuator.java @@ -0,0 +1,194 @@ +package com.gcc.container.components.elasticsearch.actuator; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.gcc.container.components.elasticsearch.model.SearchResult; +import com.gcc.container.components.elasticsearch.model.entity.FieldEntity; +import org.elasticsearch.search.builder.SearchSourceBuilder; + +import java.util.List; + +/** + * es执行器 + * @author GCC + */ +public interface ElasticSearchActuator { + + + /** + * 判断索引是否存在 + * @param indexName 索引名称 + * @return boolean + */ + boolean checkIndex(String indexName); + + + + /** + * 创建索引 + * @param indexName 索引名称 + * @param mapping 创建索引的mapping + * @return boolean + */ + boolean createIndex(String indexName, JSONObject mapping); + + + + /** + * 删除索引 + * @param indexName 所有名称 + * @return boolean + */ + boolean deleteIndex(String indexName); + + + + + /** + * 向指定索引内追加单条数据 + * @param indexName 索引名称 + * @param data 数据 + * @return boolean + */ + boolean addIndexData(String indexName, JSONObject data); + + + + + /** + * 向指定索引中批量追加数据 + * @param indexName 索引名称 + * @param datas 数据 + * @return boolean + */ + boolean addIndexDatas(String indexName, JSONArray datas); + + + + /** + * 清空指定索引内数据 + * @param indexName 索引名称 + * @return boolean + */ + boolean clearIndexData(String indexName); + + + + /** + * 根据id删除指定索引内的数据 + * @param indexName 索引名称 + * @param id 待删除数据的id + * @return boolean + */ + boolean deleteIndexDataById(String indexName, String id); + + + + /** + * 获取所有索引名称 + * @return List + */ + List getAllIndexName(); + + + + + /** + * 根据索引名称获取对应的字段集合 + * @param indexName 索引名称 + * @return List + */ + List getFieldsByIndexName(String indexName); + + + /** + * 根据文档id(_id)查询具体数据 + * @param indexName 索引名称 + * @param docType 文档类型 + * @param id _id + * @return JSONObject + */ + JSONObject getElasticSearchDataById(String indexName,String docType,String id); + + + + /** + * 根据文档id(_id)查询具体数据,默认文档为_doc类型 + * @param indexName 索引名称 + * @param id _id + * @return JSONObject + */ + JSONObject getElasticSearchDataById(String indexName,String id); + + + + + /** + * 执行Es的dsl(默认文档类型为_doc) + * @param indexName 索引名称 + * @param dslBodyStr dsl + * @return JSONObject + */ + JSONObject executeSearchDslStr(String indexName,String dslBodyStr); + + + + /** + * 执行Es的查询对象 SearchSourceBuilder 默认文档为_doc + * @param indexName 索引名称 + * @param searchSourceBuilder 查询对象 + * @return JSONObject + */ + SearchResult executeSearchSearchSourceBuilder(String indexName, SearchSourceBuilder searchSourceBuilder, Class resultObj); + + + + + /** + * 查询es + * @param indexName 索引名称 + * @param searchSourceBuilder 自定义查询内容 + * @param resultObj 目标实体对象 + * @return SearchResult + */ + SearchResult searchElasticSearchDatas(String indexName, SearchSourceBuilder searchSourceBuilder,Class resultObj); + + /** + * 分页滚动查询 + * @param indexName 索引名称 + * @param searchSourceBuilder 查询条件 + * @param pageNo 页码 + * @param resultObj 具体目标对象 + * @return SearchResult + */ + SearchResult scrollSearchElasticSearchDatas(String indexName,SearchSourceBuilder searchSourceBuilder,int pageNo,Class resultObj); + + /** + * 聚合es数据 + * @param indexName 索引名称 + * @param searchDslStr 自定义查询内容 + * @return + */ + SearchResult aggregateDatas(String indexName, String searchDslStr); + + + + /** + * 聚合es数据 + * @param indexName 索引名称 + * @param searchSourceBuilder 自定义查询内容 + * @return + */ + SearchResult aggregateDatas(String indexName, SearchSourceBuilder searchSourceBuilder); + + + /** + * 将sql语句转换为dsl语句 + * @param sql sql 语句 + * @return String + */ + String translateSQL(String sql); + + + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/actuator/impl/ElasticSearchActuatorImpl.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/actuator/impl/ElasticSearchActuatorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e9ddf925477161d87a6e41a1d07fb3f03b87c725 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/actuator/impl/ElasticSearchActuatorImpl.java @@ -0,0 +1,420 @@ +package com.gcc.container.components.elasticsearch.actuator.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.gcc.container.components.elasticsearch.actuator.ElasticSearchActuator; +import com.gcc.container.components.elasticsearch.configure.ElasticSearchCondition; +import com.gcc.container.components.elasticsearch.configure.ElasticSearchSqlCondition; +import com.gcc.container.components.elasticsearch.log.annotation.ExecuteDslLog; +import com.gcc.container.components.elasticsearch.model.SearchResult; +import com.gcc.container.components.elasticsearch.model.entity.DocBaseEntity; +import com.gcc.container.components.elasticsearch.model.entity.FieldEntity; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpException; +import org.apache.http.util.EntityUtils; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.search.*; +import org.elasticsearch.client.*; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.client.indices.GetMappingsRequest; +import org.elasticsearch.client.indices.GetMappingsResponse; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.cluster.metadata.AliasMetaData; +import org.elasticsearch.cluster.metadata.MappingMetaData; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.search.Scroll; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.nlpcn.es4sql.SearchDao; +import org.nlpcn.es4sql.query.QueryAction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.*; + +@Conditional(ElasticSearchCondition.class) +@Component +@Slf4j +public class ElasticSearchActuatorImpl implements ElasticSearchActuator { + + private final static String DEFAULT_DOC_TYPE = "_doc"; + + private final static String CREATE_OPERATE_TYPE = "create"; + + private final static String HITS_STR = "hits"; + + private final static String SOURCE_STR = "_source"; + + private final static String AGGREGATE_KEY = "aggregations"; + + private RestHighLevelClient restHighLevelClient; + + private TransportClient transportClient; + + + @Conditional(ElasticSearchCondition.class) + @Qualifier("EsHighLevelClient") + @Autowired + public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) { + this.restHighLevelClient = restHighLevelClient; + } + + + @Conditional(ElasticSearchSqlCondition.class) + @Qualifier("TransportClient") + @Autowired + public void setTransportClient(TransportClient transportClient) { + this.transportClient = transportClient; + } + + + @Override + public boolean checkIndex(String indexName) { + if(!StringUtils.isEmpty(indexName)) { + try { + return restHighLevelClient.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT); + } catch (Exception e) { + log.error("Es检测索引 {} 失败!." + e,indexName); + e.printStackTrace(); + } + } + return false; + } + + @Override + public boolean createIndex(String indexName, JSONObject mapping) { + if(checkIndex(indexName)){ + log.warn("索引{}已经存在,创建索引失败.",indexName); + }else if(null == mapping || mapping.isEmpty() || StrUtil.isBlankIfStr(indexName)){ + log.error("构建mapping或索引名称为空,创建索引失败."); + }else { + CreateIndexRequest request = new CreateIndexRequest(indexName); + Map source = new HashMap<>(); + source.put("properties",mapping); + request.mapping(source); + try { + restHighLevelClient.indices().create(request,RequestOptions.DEFAULT); + return true; + }catch (Exception e){ + log.error("构建索引{}失败,es错误 "+e,indexName); + e.printStackTrace(); + } + } + return false; + } + + @Override + public boolean deleteIndex(String indexName) { + DeleteIndexRequest request = new DeleteIndexRequest(indexName); + try { + restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT); + return true; + }catch (Exception e){ + log.error("删除索引 {} 失败"+e,indexName); + e.printStackTrace(); + } + return false; + } + + @Override + public boolean addIndexData(String indexName, JSONObject data) { + BulkRequest request = new BulkRequest(); + request.add(new IndexRequest().index(indexName).opType(CREATE_OPERATE_TYPE).source(data, XContentType.JSON).create(true)); + try { + restHighLevelClient.bulk(request, RequestOptions.DEFAULT); + return true; + }catch (Exception e){ + log.error("插入数据失败"+e); + e.printStackTrace(); + } + return false; + } + + @Override + public boolean addIndexDatas(String indexName, JSONArray datas) { + BulkRequest request = new BulkRequest(); + for (Object obj:datas){ + JSONObject dataMap = (JSONObject)obj; + request.add(new IndexRequest().index(indexName).opType(CREATE_OPERATE_TYPE).source(dataMap,XContentType.JSON).create(true)); + } + try { + restHighLevelClient.bulk(request, RequestOptions.DEFAULT); + return true; + }catch (Exception e){ + log.error("索引{}批量插入数据失败"+e,indexName); + e.printStackTrace(); + } + return false; + } + + @Override + public boolean clearIndexData(String indexName) { + try { + DeleteRequest deleteRequest = new DeleteRequest(indexName, DEFAULT_DOC_TYPE); + restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); + return true; + }catch (Exception e){ + log.error("清除索引{}的数据失败!"+e,indexName); + e.printStackTrace(); + } + return false; + } + + @Override + public boolean deleteIndexDataById(String indexName, String id) { + try { + DeleteRequest deleteRequest = new DeleteRequest(indexName, DEFAULT_DOC_TYPE, id); + restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); + return true; + }catch (Exception e){ + log.error("删除索引{}中id为{}的数据失败!"+e,indexName,id); + e.printStackTrace(); + } + return false; + } + + @Override + public List getAllIndexName() { + List result = new ArrayList<>(); + GetAliasesRequest request = new GetAliasesRequest(); + GetAliasesResponse response = null; + try { + response = restHighLevelClient.indices().getAlias(request, RequestOptions.DEFAULT); + }catch (Exception e){ + log.error("es获取所有索引错误,"+e); + e.printStackTrace(); + } + if(null != response){ + Map> indexmap = response.getAliases(); + for(String key:indexmap.keySet()){ + result.add(key); + } + } + return result; + } + + @Override + public List getFieldsByIndexName(String indexName) { + List result = new ArrayList<>(); + GetMappingsRequest getMappings=new GetMappingsRequest().indices(indexName); + try { + GetMappingsResponse getMappingResponse = restHighLevelClient.indices().getMapping(getMappings, RequestOptions.DEFAULT); + Map allMappings = getMappingResponse.mappings(); + MappingMetaData indexMapping = allMappings.get(indexName); + Map mapping = indexMapping.sourceAsMap(); + Map fields = (Map)mapping.get("properties"); + Iterator> entries=fields.entrySet().iterator(); + while(entries.hasNext()){ + Map.Entry entry = entries.next(); + String key = entry.getKey(); + Map value = (Map)entry.getValue(); + String type = value.get("type"); + if(null == type || "null".equals(type)){ + continue; + } + FieldEntity temp = new FieldEntity(key,type); + result.add(temp); + } + }catch (Exception e){ + log.error("es查询索引{}的字段集合错误"+e,indexName); + e.printStackTrace(); + } + return result; + } + + @Override + public JSONObject getElasticSearchDataById(String indexName, String docType, String id) { + Request request = new Request("get","/"+indexName+"/"+docType+"/"+id); + try{ + return excuteHttpRequest(request); + }catch (Exception e){ + log.error("获取索引{}中id为{}的数据失败!"+e,indexName,id); + e.printStackTrace(); + } + return null; + } + + @Override + public JSONObject getElasticSearchDataById(String indexName, String id) { + return getElasticSearchDataById(indexName,DEFAULT_DOC_TYPE,id); + } + + + @Override + @ExecuteDslLog + public JSONObject executeSearchDslStr(String indexName, String dslBodyStr) { + Request req = new Request("post","/"+indexName+"/_search"); + req.setJsonEntity(dslBodyStr); + try{ + return excuteHttpRequest(req); + }catch (Exception e){ + log.error("executeSearchDslStr Error \nindexName:{} \n DSL:\n {}",indexName,dslBodyStr); + e.printStackTrace(); + } + return new JSONObject(); + } + + + @Override + public SearchResult executeSearchSearchSourceBuilder(String indexName, SearchSourceBuilder searchSourceBuilder, Class resultObj) { + JSONObject retJSon = executeSearchDslStr(indexName,searchSourceBuilder.toString()); + SearchResult result = new SearchResult<>(); + if(!retJSon.isEmpty()) { + result.setTotal(retJSon.getJSONObject(HITS_STR).getJSONObject("total").getInt("value")); + JSONArray arra = retJSon.getJSONObject(HITS_STR).getJSONArray(HITS_STR); + for (int i = 0; i < arra.size(); i++) { + JSONObject obj = arra.getJSONObject(i); + DocBaseEntity tem = new DocBaseEntity<>(obj); + tem.setDatas(JSONUtil.toBean(obj.getJSONObject(SOURCE_STR), resultObj)); + result.addData(tem); + } + if (retJSon.containsKey(AGGREGATE_KEY)) { + result.setAggregations(retJSon.getJSONObject(AGGREGATE_KEY)); + } + } + return result; + } + + + @Override + @ExecuteDslLog + public SearchResult searchElasticSearchDatas(String indexName, SearchSourceBuilder searchSourceBuilder, Class resultObj) { + SearchResult result = new SearchResult<>(); + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.source(searchSourceBuilder); + try { + SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); + result = createSearchResult(response,resultObj); + }catch (Exception e){ + log.error("searchElasticSearchDatas Error \nindexName:{}\nDSL:\n {}",indexName,searchSourceBuilder.toString()); + e.printStackTrace(); + } + return result; + } + + + @Override + @ExecuteDslLog + public SearchResult scrollSearchElasticSearchDatas(String indexName, SearchSourceBuilder searchSourceBuilder, int pageNo, Class resultObj) { + SearchResult resultMap = new SearchResult(); + SearchRequest searchRequest = new SearchRequest(indexName); + searchRequest.source(searchSourceBuilder); + try{ + SearchResponse searchResp = scrollSearch(pageNo,searchRequest); + resultMap = createSearchResult(searchResp,resultObj); + }catch (Exception e){ + log.error("scrollSearch Error!\nindexName:{}\n dls:\n {}",indexName,searchSourceBuilder.toString()); + e.printStackTrace(); + } + return resultMap; + } + + @Override + public SearchResult aggregateDatas(String indexName, String searchDslStr) { + JSONObject retJSon = executeSearchDslStr(indexName,searchDslStr); + if(null == retJSon || !retJSon.containsKey(AGGREGATE_KEY)){ + return new SearchResult<>(); + }else { + SearchResult ret = new SearchResult<>(); + ret.setAggregations(retJSon.getJSONObject(AGGREGATE_KEY)); + return ret; + } + } + + @Override + public SearchResult aggregateDatas(String indexName, SearchSourceBuilder searchSourceBuilder) { + return aggregateDatas(indexName,searchSourceBuilder.size(0).toString()); + } + + + @Override + public String translateSQL(String sql) { + if(null != transportClient){ + try { + SearchDao searchDao = new SearchDao(transportClient); + QueryAction queryAction = searchDao.explain(sql); + return queryAction.explain().explain(); + } catch (Exception e) { + log.error("translateSQL Failed!sql:{}"+e,sql); + e.printStackTrace(); + } + }else { + log.warn("Confirm whether the configuration of es-sql is enabled !!! spring.elasticsearch.tcpport: xxx"); + } + return null; + } + + + + /** + * 执行ES的HTTP的API查询 + * @param request 请求 + * @return JSONObject + * @throws Exception + */ + private JSONObject excuteHttpRequest(Request request) throws Exception{ + Response ret = restHighLevelClient.getLowLevelClient().performRequest(request); + if(200 == ret.getStatusLine().getStatusCode()){ + return JSONUtil.parseObj(EntityUtils.toString(ret.getEntity()),false,false); + }else { + log.error("ElasticSearch HTTP API status:{} Error!!!!!",ret.getStatusLine().getStatusCode()); + throw new HttpException(); + } + } + + private SearchResponse scrollSearch(int pageNo,SearchRequest searchRequest) { + Scroll scroll = new Scroll(TimeValue.timeValueMinutes(3)); + searchRequest.scroll(scroll); + SearchResponse searchResponse = null; + try { + searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); + String scrollId = searchResponse.getScrollId(); + SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId); + searchScrollRequest.scroll(scroll); + for (int i = 0; i < (pageNo -1); i++) { + searchResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT); + } + if (scrollId != null) { + ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); + clearScrollRequest.addScrollId(scrollId); + ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT); + clearScrollResponse.isSucceeded(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return searchResponse; + } + + /** + * 构建目标结果 + * @param response 返回参数 + * @param resultObj 类对象 + * @param + * @return + */ + private SearchResult createSearchResult(SearchResponse response,Class resultObj){ + SearchResult resultMap = new SearchResult<>(); + SearchHit[] datas = response.getHits().getHits(); + for(SearchHit data:datas){ + DocBaseEntity temp = new DocBaseEntity<>(data); + temp.setDatas(JSONUtil.toBean(JSONUtil.parseObj(data.getSourceAsMap()),resultObj)); + resultMap.addData(temp); + } + resultMap.setTotal(response.getHits().getTotalHits().value); + return resultMap; + } + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchCondition.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..c3317ddf641c29a9e79ea724359b2e25287daadc --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchCondition.java @@ -0,0 +1,23 @@ +package com.gcc.container.components.elasticsearch.configure; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + + +public class ElasticSearchCondition implements Condition { + + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + String host = conditionContext.getEnvironment().getProperty("spring.elasticsearch.host"); + String port = conditionContext.getEnvironment().getProperty("spring.elasticsearch.port"); + + if(null != host && null != port){ + return true; + } + + return false; + + } + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchLogCondition.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchLogCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..91ee7d03818bd3e26328f427f26de8c3cede10a8 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchLogCondition.java @@ -0,0 +1,21 @@ +package com.gcc.container.components.elasticsearch.configure; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.StringUtils; + +public class ElasticSearchLogCondition implements Condition { + + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + String logEnable = conditionContext.getEnvironment().getProperty("spring.elasticsearch.log-enable"); + + if(!StringUtils.isEmpty(logEnable)){ + return Boolean.parseBoolean(logEnable); + } + return false; + + } + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchSqlCondition.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchSqlCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..e9908f5edde62fc151f34f74637c7031ccfc6dfc --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/ElasticSearchSqlCondition.java @@ -0,0 +1,21 @@ +package com.gcc.container.components.elasticsearch.configure; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.StringUtils; + +public class ElasticSearchSqlCondition implements Condition { + + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + String tcpport = conditionContext.getEnvironment().getProperty("spring.elasticsearch.tcpport"); + + if(!StringUtils.isEmpty(tcpport)){ + return true; + } + return false; + + } + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/LoadEsClient.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/LoadEsClient.java new file mode 100644 index 0000000000000000000000000000000000000000..31a4cd6a01c366a299880ee39f372d8acd4f5fbf --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/configure/LoadEsClient.java @@ -0,0 +1,93 @@ +package com.gcc.container.components.elasticsearch.configure; + +import com.gcc.container.components.elasticsearch.property.ElasticSearchProperties; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.transport.client.PreBuiltTransportClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class LoadEsClient { + + + private ElasticSearchProperties properties; + + + @Autowired + public void setProperties(ElasticSearchProperties properties) { + this.properties = properties; + } + + + @Conditional(ElasticSearchCondition.class) + @Bean("EsHighLevelClient") + public RestHighLevelClient getClient() { + RestHighLevelClient highLevelClient = null ; + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(properties.getUser(), properties.getPassword())); + highLevelClient = new RestHighLevelClient( + RestClient.builder(getESHosts()).setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { + @Override + public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) { + // 设置连接超时和套接字超时时长 + return requestConfigBuilder.setConnectTimeout(60000).setSocketTimeout(60000*3); + } + }).setHttpClientConfigCallback( + new RestClientBuilder.HttpClientConfigCallback() { + @Override + public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) { + return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + } + ) + ); + return highLevelClient; + } + + + @Conditional(ElasticSearchSqlCondition.class) + @Bean("TransportClient") + public TransportClient getTransportClient() throws UnknownHostException { + Settings settings = Settings.builder() + .put("client.transport.sniff", true) + .put("client.transport.ignore_cluster_name",true) + .build(); + TransportClient client = new PreBuiltTransportClient(settings) + .addTransportAddress(new TransportAddress(InetAddress.getByName(properties.getHost()),properties.getTcpPort())); + + return client; + } + + + private HttpHost[] getESHosts() { + String[] esHosts = properties.getHost().trim().split(","); + List host = new ArrayList(); + for(String it:esHosts){ + host.add(new HttpHost(it,properties.getPort(), properties.getScheme())); + } + HttpHost[] hostArray = host.toArray(new HttpHost[esHosts.length]); + return hostArray; + } + + + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/log/annotation/ExecuteDslLog.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/log/annotation/ExecuteDslLog.java new file mode 100644 index 0000000000000000000000000000000000000000..1f39b4af629100d5bd5026b993c3dfe2e56b90ea --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/log/annotation/ExecuteDslLog.java @@ -0,0 +1,11 @@ +package com.gcc.container.components.elasticsearch.log.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExecuteDslLog { +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/log/aspect/ElasticSearchMonitorAspect.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/log/aspect/ElasticSearchMonitorAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..0446ffe76c2d3d04fb7d830eb0545b0bf530deb6 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/log/aspect/ElasticSearchMonitorAspect.java @@ -0,0 +1,30 @@ +package com.gcc.container.components.elasticsearch.log.aspect; + +import com.gcc.container.components.elasticsearch.configure.ElasticSearchLogCondition; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +@Conditional(ElasticSearchLogCondition.class) +@Component +@Aspect +@Slf4j(topic = "ES-Monitor") +public class ElasticSearchMonitorAspect { + + + @Before("@annotation(com.gcc.container.components.elasticsearch.log.annotation.ExecuteDslLog)") + public void beforeAdvice(JoinPoint point) throws Throwable{ + Object[] args = point.getArgs(); + if(args.length >= 2){ + String index = String.valueOf(args[0]); + String dsl = args[1].toString(); + MethodSignature signature = (MethodSignature) point.getSignature(); + log.info("\nmethod: {} , indexName: {} \nDSL: {}",signature.getMethod().getName(),index,dsl); + } + + } +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/SearchResult.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/SearchResult.java new file mode 100644 index 0000000000000000000000000000000000000000..661b995c4e78cb2e50c79397f2757c7eeb660140 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/SearchResult.java @@ -0,0 +1,50 @@ +package com.gcc.container.components.elasticsearch.model; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.gcc.container.components.elasticsearch.model.entity.DocBaseEntity; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +@Data +public class SearchResult implements Serializable { + + + private int total; + + + private List> source = new ArrayList<>(); + + + private JSONObject aggregations; + + + public void addData(DocBaseEntity obj){ + source.add(obj); + } + + public List getDatas(){ + return source.stream().map(DocBaseEntity::getDatas).collect(Collectors.toList()); + } + + public void addDatas(List> objs){ + source.addAll(objs); + } + + + public void setTotal(Object total){ + this.total = Integer.parseInt(String.valueOf(total)); + } + + + public JSONObject toJSONObject(){ + return JSONUtil.parseObj(this,true); + } + + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/entity/DocBaseEntity.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/entity/DocBaseEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..a759a0bcd15176552ebb02188891c8980a0bfd94 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/entity/DocBaseEntity.java @@ -0,0 +1,36 @@ +package com.gcc.container.components.elasticsearch.model.entity; + +import cn.hutool.json.JSONObject; +import lombok.Data; +import org.elasticsearch.search.SearchHit; + +import java.io.Serializable; + +@Data +public class DocBaseEntity implements Serializable { + + private String _index; + + private String _type; + + private String _id; + + private T datas; + + public DocBaseEntity(SearchHit data) { + this._index = data.getIndex(); + this._type = data.getType(); + this._id = data.getId(); + } + + public DocBaseEntity(JSONObject jsonHits){ + this._index = jsonHits.getStr("_index"); + this._type = jsonHits.getStr("_type"); + this._id = jsonHits.getStr("_id"); + } + + public T getDatas(){ + return datas; + } + +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/entity/FieldEntity.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/entity/FieldEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..b38eb87aef9544753034bb0f041ef63a6206a8b0 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/model/entity/FieldEntity.java @@ -0,0 +1,21 @@ +package com.gcc.container.components.elasticsearch.model.entity; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class FieldEntity implements Serializable { + + + private String properties; + + + private String type; + + + public FieldEntity(String properties, String type) { + this.properties = properties; + this.type = type; + } +} diff --git a/es-component/src/main/java/com/gcc/container/components/elasticsearch/property/ElasticSearchProperties.java b/es-component/src/main/java/com/gcc/container/components/elasticsearch/property/ElasticSearchProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..7f2d806d22e1c864af34030cae243bd838f192c2 --- /dev/null +++ b/es-component/src/main/java/com/gcc/container/components/elasticsearch/property/ElasticSearchProperties.java @@ -0,0 +1,41 @@ +package com.gcc.container.components.elasticsearch.property; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@ConfigurationProperties(prefix = "spring.elasticsearch") +@Data +@Configuration +public class ElasticSearchProperties { + + private String host; + + + private String port; + + + private String tcpPort; + + + private String user = ""; + + + private String password = ""; + + + private String Scheme; + + + private Boolean logEnable = false; + + + public Integer getPort(){ + return Integer.valueOf(port); + } + + + public Integer getTcpPort() { + return Integer.valueOf(tcpPort); + } +} diff --git a/es-component/src/main/resources/META-INF/spring.factories b/es-component/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..3798cbb79ba2efeaf22ea15bd89484f8ddbc1074 --- /dev/null +++ b/es-component/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gcc.container.components.elasticsearch.ApplicationConfiguration \ No newline at end of file diff --git a/es-component/src/main/resources/application.yml b/es-component/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..46bb617f53d42678db97ffc2bbd08d3c9ddb9d90 --- /dev/null +++ b/es-component/src/main/resources/application.yml @@ -0,0 +1,9 @@ +spring: + elasticsearch: + host: 10.11.203.7 + port: 9500 + tcpport: 9300 + user: elastic + password: 1234 + scheme: http + log-enable: false \ No newline at end of file diff --git a/initdatabse-component/README.md b/initdatabse-component/README.md new file mode 100644 index 0000000000000000000000000000000000000000..70c822547444439bb51840b1a356dfec095e9e6d --- /dev/null +++ b/initdatabse-component/README.md @@ -0,0 +1,374 @@ +# initdatabse-component + +> initdatabse是基于之前Conscript进行改造的项目,主要是以component的形式统一管理此部分代码, +> +> initdatabse基于Spring-boot-starter的方式提供数据库初始化功能,可以随项目初次启动,自动创建相关数据库,后续系统升级所引发的数据库变化及升级也可由此完成,拆箱即用,随系统程序代码运行,省去额外的数据库升级操作, +> +> `支持数据库种类可扩展,目前支持通用mysql、postgreSQL、 国产数据库达梦和金仓` +> +> `支持执行失败回滚机制` + +## 组件依赖说明 + +组件整体继承自Components包,所依赖Spring相关组件及通用的公共组件依赖跟随Components版本进行调整管控,相关内容可查看Components的README文档 + +```xml + + com.gcc.container + components + 1.0.0 + +``` + +除基本的SpringAOP、autoconfigure外,需要依赖jsqlparser,如下: + +```xml + + com.github.jsqlparser + jsqlparser + ${jsqlparser.version} + +``` + +## 支持数据库类型 + +| 数据库类型 | 是否支持 | db-driver参数 | +| ---------- | ---- | ----------- | +| mysql | 是 | mysql | +| mariadb | 是 | mariadb | +| PostgreSQL | 是 | pgsql | +| 达梦数据库 | 是 | dm | +| 人大金仓8 | 是 | kingbase8 | + +## 实现设计 + +核心代码结构: + +```shell ++---action #基础功能包 +| +---mysql #mysql数据库实现 +----initDataBase #数据库初始化接口 +----DataBaseInitorFactory #数据库初始器工厂 +----AbstractInitDataBase #数据库初始器基类 +``` + +| 类 | 介绍 | +| --------------------- | -------------- | +| initDataBase | 初始化接口 | +| AbstractInitDataBase | 初始化基类,定义核心逻辑 | +| MySqlDataBase | Mysql实现,具体执行方式 | +| DataBaseInitorFactory | 工厂类 | + +```mermaid +classDiagram +direction TD +class initDataBase{ + <> + isInitEd() + startCoreJob() + createConnection() + databaseIsExitd() + getCurrenDbVersion() + excuteSQL(Map sqlcontent) + close() +} +class AbstractInitDataBase { + <> + +void : startCoreJob() + } +class MySqlDataBase { + +void : createConnection() + +boolean:databaseIsExitd() + +Float:getCurrenDbVersion() + +void:excuteSQL(Map sqlcontent) + } +class DataBaseInitorFactory { + +InitDataBase : createInitiator(DbConConfiguration config) + +String:getClassUrlValue(DbType dbType) + } + class DbConConfiguration { + +DbConConfiguration : builder() + +void:set() + +Object:get() + } +initDataBase <-- AbstractInitDataBase : +AbstractInitDataBase <|-- MySqlDataBase +DbConConfiguration <.. DataBaseInitorFactory +initDataBase -- DataBaseInitorFactory +``` + +> **扩展其他类型数据库:在action目录下新建相应的数据库类型目录,然后继承AbstractInitDataBase类,实现对应的几个数据库操作方法,在DatabaseProperties的getDbDeriver()中新增对应的映射** + +## 3.使用方式 + +找到对应版本的Components源码,下载到本地,本地编译安装到私仓中 + +### 1.引入Conscript + +1.1.使用jar引入 + +> 本地编译源码后,install到本地的maven私仓后,直接引入项目 + +```xml + + com.gcc.container.components + initdatabse-component + 1.0 + +``` + +### 2.配置yml文件 + +```yml +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/test?user=root&password=1234 + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver + db-config-file-url: sql/mysql/dbconfig.json + db-driver: mysql + db-name: test +``` + +配置内容遵循标准的`spring.datasource`配置,并进行了增强 + +| 字段 | 含义 | 是否必填 | +| ------------------ | ------------------------------------------------ | ---- | +| url | 必选字段,遵循标准datasource即可,若使用mybaits,则按照mybaits标准填写 | 是 | +| username | 必选字段,标准datasource即可,填写数据库账号 | 是 | +| password | 必选字段,标准datasource即可,填写数据库连接密码 | 是 | +| driver-class-name | 必选字段,标准datasource即可,填写数据库连接驱动类 | 是 | +| db-config-file-url | 必选字段,指定静态SQL文件存放位置 | 是 | +| db-driver | 必选字段,指定数据库类型,扩展时,可填写类路径,默认填写mysql,详细见扩展用法章节 | 是 | +| dbport | 非必选字段,数据库服务端口,若url填写,可省略该字段 | 否 | +| db-name | 非必选字段,需要连接的数据库名称,若url中填写,可省略该字段 | 否 | +| schema | 非必须字段,若url中填写,则可省略,主要用于对部分数据库的模式配置 | 否 | +| root-user | 非必选字段,**若存在对数据库权限分离有要求的需求**,可以在此填写专门用于建库的高权限账号 | 否 | +| root-pass | 非必选字段,**若存在对数据库权限分离有要求的需求**,可以在此填写专门用于建库的高权限账号密码 | 否 | + +此处配置相较 `spring.datasource`的基础的 `url、username、password、driver-class-name`外,增加了独有的`db-driver、db-config-file-url`,原则上只需要满足此6项配置即可使用Conscript,但建议将配置补全使用 + + `使用该组件,必须确保拥有较高的数据库角色权限,例如mysql的root、pgsql的postgres,否则无法使用创建数据库的功能,如需清晰划分权限,则可以通过配置项中的root-user,root-pass来将系统数据库的使用区分开来` + +### 3.建立配置文件及SQL文件 + +以标准maven项目为例 + +​ 1.根据 yml中`spring.datasource.db-config-file-url`配置的值,在resource目录下新建相应路径json文件 + +XX.json文件样例如下: + +```json + [ + { + "version": "1.0", + "sqlfile": "a.sql", + "desc": "基础数据库结构" + }, + { + "version": "1.1", + "sqlfile": "ddd.sql", + "desc": "第一版升级数据库" + } + ] +``` + +| 字段 | 含义 | +| ------- | --------------------------------------------------------------------------- | +| version | 数据库的版本,**数字类型** | +| sqlfile | 数据库升级的sql文件,叠加式追加,**注:这里要写清楚sql文件的路径(基于resource目录为基准),此处的配置决定后续sql文件的存放位置** | +| desc | 维护使用的描述信息 | + +​ 2.根据XX.json配置文件中的sqlfile配置项,新建相应目录追加需要预制的SQL文件 + +### 4.提供一个标准的用例 + +yml配置文件如下: + +``` +spring: + datasource: + url: jdbc:mysql://127.0.0.1:3306/test?user=root&password=1234?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2B8 + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver + db-dirver: mysql + db-config-file-url: sql/mysql-dbconfig.json + db-name: test +``` + +mysql-dbconfig.json配置如下: + +```json + [ + { + "version": "1.0", + "sqlfile": "sql/a.sql", + "desc": "基础数据库结构" + }, + { + "version": "1.1", + "sqlfile": "sql/ddd.sql", + "desc": "第一版升级数据库" + } + ] +``` + +目录结构如下: + +```shell ++-java #java源码 ++-resource #资源文件 +| +---sql #sql初始化配置内容 + |+---mysql-dbconfig.json #sql初始化配置内容 + |+---a.sql #sql初始化配置内容 + |+---ddd.sql #sql初始化配置内容 +``` + +> yml配置中的`db-config-file-url`配置决定着json配置文件的存放,如填写的路径为:sql/mysql/a.json,则需要在resource目录下新建sql文件夹,并在sql文件夹下新建mysql文件夹,然后在mysql文件夹中按照样例创建json文件;json配置文件中的sqlfile属性决定着需要执行的sql文件存放位置; +> +> 为了方便,建议json文件中的sql文件存放位置和json文件在同级目录 + +### 5.其他说明 + +使用过程中获取建库结果,需要在代码中获取数据库的升级结果以便后续操作,可直接获取bean `DbIsExist` 的值来进行判定: + +```java +@Service +public class AfterStartProcess{ + + @Qualifier("DbIsExid") + @Autowired + Boolean dbIsOk; + + + public void test(){ + if(dbIsOk){ + System.out.print("数据库升级完成!") + } + } +} +``` + +默认数据库版本表名称修改,需要新增配置 `conscript.table-name` 配置项: + +```yml +conscript: + table-Name: xxxx +``` + +## 4.扩展 + +若支持的数据库类型不满足要求,需要自行进行扩展,项目引入后,可新建类继承 `AbstractInitDataBase`类,仅实现对应的 `initCommConn() initDbConn() databaseIsExitd() ` + +`createDataBase() `方法,并将新建的类路径配置到db-driver中即可 + +### method:initCommConn() + +> 创建数据库基础JDBC连接, +> +> `AbstractInitDataBase`类中定义了 `commConn` 、` dbConn` 两个**Connection** 变量,该方法主要用于对 `commConn`变量进行初始化,建立基础的数据库连接,用于测试数据库联通以及执行数据库建库语句,例如MySQL中,需要建立连接mysql数据库的JDBC + +```java + @Override + public void initCommConn() { + try { + Class.forName(dataBaseProperties.getDriverClassName()); + String jdbc_url = "jdbc:mysql://" + dataBaseProperties.getHost() + ":" + dataBaseProperties.getDbport() + "/mysql?characterEncoding=utf8&serverTimezone=GMT%2B8"; + commConn = DriverManager.getConnection(jdbc_url, dataBaseProperties.getUsername(), dataBaseProperties.getPassword()); + }catch (Exception e){ + log.error("【Conscript】Database initialization :data base is not connection....."); + } + } +``` + +### method: initDbConn() + +> 创建数据库基础JDBC连接, +> +> `AbstractInitDataBase`类中定义了 `commConn` 、` dbConn` 两个**Connection** 变量,该方法主要用于对 `dbConn`变量进行初始化,建立对目标数据库(配置中指定的数据库)实例的JDBC连接,用于执行SQL语句 + +```java + @Override + public void initDbConn() { + try{ + String url = "jdbc:mysql://"+ dataBaseProperties.getHost()+":"+ dataBaseProperties.getDbport()+"/"+ dataBaseProperties.getDbName()+"?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2B8"; + dbConn = DriverManager.getConnection(url, dataBaseProperties.getUsername(), dataBaseProperties.getPassword()); + }catch (Exception e){ + log.warn(""); + } + } +``` + +### method:databaseIsExitd(Connection connection) + +> 确定需要连接的目标数据库实例((配置中指定的数据库))是否存在,使用`commConn`进行确认,不同的数据库确认实例的方式不同,可根据具体要扩展的数据库类型自行进行定义 + +```java +@Override + public boolean databaseIsExitd(Connection connection){ + try { + Statement stmt = connection.createStatement(); + ResultSet res = stmt.executeQuery("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name= \""+ dataBaseProperties.getDbName()+"\""); + if(res.next() && res.getInt(1) == 0){ + stmt.close(); + return false; + } + return true; + }catch (Exception e){ + log.error("【Conscript】Database initialization :database base query is error"); + } + return false; + } +``` + +### method:createDataBaseSQL() + +> 创建指定的数据库的建库语句,仅返回对应的建库语句即可 + +```java +@Override + public String createDataBaseSQL() { + return "CREATE DATABASE IF NOT EXISTS "+ dataBaseProperties.getDbName()+" DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci"; + } +``` + +## 5.回滚保护机制 + +为防止用户自定义的sql文件中存在错误的语法,导致升级建库过程因部分sql错误而失败,但正常SQL却已经执行,污染了原始数据库;Conscript进行了加强,可以将升级过程停止于错误sql文件之前,保证存在错误语法的sql文件不会被执行,从而保证不会污染原始数据库 + +```mermaid +graph LR; + sql文件 --> 语法校验; + 语法校验 --> 数据事务操作保护 +``` + +保护机制为两层防护,第一层为语法保护,保证执行的sql语法都是正确的,能够被数据库执行的,其次进行一次数据操作的回滚,当正确的SQL满足了语法要求,但是如果存在语义错误(主键冲突、字段和值不匹配等造成的数据操作失败),则进行数据 `增删改`的回滚机制,保证数据不被污染 + +> 注:因开启回滚保护机制,需要对sql文件进行语法校验,sql文件数量较大时会有较高的性能损耗,可根据实际情况使用,默认是不开启状态 + +### 使用方法 + +yml文件中新增`conscript.parse-enable`属性,取值范围为布尔,不配置时默认为 false + +```yml +conscript: + parse-enable: true +``` + +## 6.其他问题 + +6.1 与Mybatis-plus一起使用时,会出现Conscript在Mybatis-Plus之后加载的情况,此时会因为未创建数据库,导致Mybatis-plus的SqlSession创建失败,解决方案需要在启动类上使用注解@DependsOn,手动干预starter加载顺序,先加载Conscript的 `DbIsExist` bean,如下: + +```java +@SpringBootApplication +@DependsOn("DbIsExist") +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} +``` \ No newline at end of file diff --git a/initdatabse-component/pom.xml b/initdatabse-component/pom.xml index 952fbbe3794bcec55d3e7a415ddcc35bd1a52b80..cf04c0d3ce5bf531ef908624ff9fb65c9ab6e283 100644 --- a/initdatabse-component/pom.xml +++ b/initdatabse-component/pom.xml @@ -6,7 +6,7 @@ components 1.0.0 - com.gcc.container.components.conscript + com.gcc.container.components initdatabse-component ${component.version} jar diff --git a/initdatabse-component/src/main/resources/META-INF/spring.factories b/initdatabse-component/src/main/resources/META-INF/spring.factories index 812ff55ea6702cb42e80f2c12e02f03e99243427..04a6ae15284201254fa40be97c019c2e18a7b732 100644 --- a/initdatabse-component/src/main/resources/META-INF/spring.factories +++ b/initdatabse-component/src/main/resources/META-INF/spring.factories @@ -1 +1 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gcc.airbase.conscript.ApplicationConfiguration \ No newline at end of file +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gcc.container.components.conscript.ApplicationConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index f1de576a8cb21ca43df3651c7b792a08a2a8bb26..5b300adc2c06f7d92b9c7fbe974a56f5e3b498c8 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,8 @@ rest-component redis-component initdatabse-component + es-component + task-component @@ -54,12 +56,6 @@ org.projectlombok lombok - - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - cn.hutool diff --git a/redis-component/pom.xml b/redis-component/pom.xml index a56ce444ff15afffaea84ef3cf01e018028cca30..c069687ea8e58afee078ff67b26e11f242474182 100644 --- a/redis-component/pom.xml +++ b/redis-component/pom.xml @@ -6,7 +6,7 @@ components 1.0.0 - com.gcc.container.components.redis + com.gcc.container.components redis-component ${component.version} jar diff --git a/redis-component/src/main/resources/META-INF/spring.factories b/redis-component/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..7080fd0de25e377830bbe8cb11b3a177b8c906e3 --- /dev/null +++ b/redis-component/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gcc.container.components.web.ApplicationConfiguration \ No newline at end of file diff --git a/rest-component/pom.xml b/rest-component/pom.xml index 18f9d3838b9bc27519bb5edb4ecbc515193c6371..c3fb0110e265ae176cfca2efeb1e1ce97ea7dca3 100644 --- a/rest-component/pom.xml +++ b/rest-component/pom.xml @@ -8,7 +8,7 @@ 1.0.0 - com.gcc.container.commpont + com.gcc.container.components rest-component ${component.version} jar diff --git a/rest-component/src/main/java/com/gcc/container/components/web/configure/SwaggerConfig.java b/rest-component/src/main/java/com/gcc/container/components/web/configure/SwaggerConfigure.java similarity index 39% rename from rest-component/src/main/java/com/gcc/container/components/web/configure/SwaggerConfig.java rename to rest-component/src/main/java/com/gcc/container/components/web/configure/SwaggerConfigure.java index e01dbd8f0c747d61d3ef8f6a211173e7eecceec2..37ff527ccff66728c64dc3abd0f8e83aea86c718 100644 --- a/rest-component/src/main/java/com/gcc/container/components/web/configure/SwaggerConfig.java +++ b/rest-component/src/main/java/com/gcc/container/components/web/configure/SwaggerConfigure.java @@ -1,8 +1,9 @@ package com.gcc.container.components.web.configure; import com.gcc.container.components.web.property.ApiDocProperties; -import com.gcc.container.components.web.property.ApiInfoProperties; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; @@ -13,40 +14,34 @@ import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; -import java.util.ArrayList; -import java.util.List; - @Configuration -public class SwaggerConfig { +public class SwaggerConfigure { @Autowired private ApiDocProperties properties; + @ConditionalOnWebApplication + @ConditionalOnProperty(name="rest-component.api-doc.enable", havingValue = "true",matchIfMissing = false) @Bean - public List taskApis(){ - if(properties.getEnable()){ - List result = new ArrayList<>(); - List apis = properties.getApiDocs(); - for(ApiInfoProperties apidoc:apis){ - ApiInfo onePage = new ApiInfoBuilder() - .title(apidoc.getTitle()) - .description(apidoc.getTitle()) - .contact(new Contact("GCC", "#", "gcc.com")) - .version(apidoc.getVersion()) - .build(); - result.add(new Docket(DocumentationType.OAS_30) - .apiInfo(onePage) - .select() - .apis(RequestHandlerSelectors.basePackage(apidoc.getPackagePath())) - .paths(PathSelectors.any()) - .build() - .groupName(apidoc.getTitle()) - .enable(true)); - } - return result; - }else { - return null; + public Docket createDockets() { + if (properties.isEnable()) { + ApiInfo apiInfo = new ApiInfoBuilder() + .title(properties.getTitle()) + .description(properties.getDescription()) + .contact(new Contact("GCC", "#", "gcc.com")) + .version(properties.getVersion()) + .build(); + Docket docket = new Docket(DocumentationType.OAS_30) + .apiInfo(apiInfo) + .select() + .apis(RequestHandlerSelectors.basePackage(properties.getPackagePath())) + .paths(PathSelectors.any()) + .build() + .groupName(properties.getTitle()) + .enable(true); + return docket; } + return null; } diff --git a/rest-component/src/main/java/com/gcc/container/components/web/configure/WebMvcRegistrationsConfig.java b/rest-component/src/main/java/com/gcc/container/components/web/configure/WebMvcRegistrationsConfigure.java similarity index 87% rename from rest-component/src/main/java/com/gcc/container/components/web/configure/WebMvcRegistrationsConfig.java rename to rest-component/src/main/java/com/gcc/container/components/web/configure/WebMvcRegistrationsConfigure.java index 93b99334893a1167d73ac10ba5944363609fe745..c78e3eada972f099b3c72e8be0e512573f252437 100644 --- a/rest-component/src/main/java/com/gcc/container/components/web/configure/WebMvcRegistrationsConfig.java +++ b/rest-component/src/main/java/com/gcc/container/components/web/configure/WebMvcRegistrationsConfigure.java @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @Configuration -public class WebMvcRegistrationsConfig implements WebMvcRegistrations { +public class WebMvcRegistrationsConfigure implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiRequestMappingHandlerMapping(); diff --git a/rest-component/src/main/java/com/gcc/container/components/web/exception/ControllerExceptionAdvice.java b/rest-component/src/main/java/com/gcc/container/components/web/exception/ControllerExceptionAdvice.java index e80fae0757a6681bebc50e22bc159b192c94b861..ac801d02eb143577e987757d1cb0112252b0ba98 100644 --- a/rest-component/src/main/java/com/gcc/container/components/web/exception/ControllerExceptionAdvice.java +++ b/rest-component/src/main/java/com/gcc/container/components/web/exception/ControllerExceptionAdvice.java @@ -12,11 +12,13 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.LinkedHashMap; - -@RestControllerAdvice +//仅支持自定义编写的API接口全局拦截 +@RestControllerAdvice(annotations = {GetMapping.class, PostMapping.class}) public class ControllerExceptionAdvice implements ResponseBodyAdvice { @Override diff --git a/rest-component/src/main/java/com/gcc/container/components/web/property/ApiDocProperties.java b/rest-component/src/main/java/com/gcc/container/components/web/property/ApiDocProperties.java index 89edf0b99b26731cb94095c76fe72655aae09be4..5f56f7cd494bb38d7cc3966550d0dee3c35407ca 100644 --- a/rest-component/src/main/java/com/gcc/container/components/web/property/ApiDocProperties.java +++ b/rest-component/src/main/java/com/gcc/container/components/web/property/ApiDocProperties.java @@ -1,25 +1,57 @@ package com.gcc.container.components.web.property; -import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; -import java.util.List; - -@ConfigurationProperties(prefix = "rest-component") -@Data +@ConfigurationProperties(prefix = "rest-component.api-doc") @Configuration public class ApiDocProperties { - /** - * 是否启用 - */ - private Boolean enable = false; + private boolean enable = false; + private String packagePath; + private String title; + private String description; + private String version; + + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public String getPackagePath() { + return packagePath; + } + + public void setPackagePath(String packagePath) { + this.packagePath = packagePath; + } + + 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; + } - /** - * swagger页面 - */ - private List apiDocs; + public String getVersion() { + return version; + } + public void setVersion(String version) { + this.version = version; + } -} +} \ No newline at end of file diff --git a/rest-component/src/main/java/com/gcc/container/components/web/property/ApiInfoProperties.java b/rest-component/src/main/java/com/gcc/container/components/web/property/ApiInfoProperties.java deleted file mode 100644 index 02166216fdb1e4d5133ab5ff9dc4a5fb0a0855d4..0000000000000000000000000000000000000000 --- a/rest-component/src/main/java/com/gcc/container/components/web/property/ApiInfoProperties.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.gcc.container.components.web.property; - -import lombok.Data; - -@Data -public class ApiInfoProperties { - private String packagePath; - private String title; - private String description; - private String version; - - public String getDescription() { - if(null == description || "".equals(description) ) { - return this.title; - } - return description; - } - - public String getVersion() { - if(null == version || "".equals(version)){ - return "1.0.0"; - } - return version; - } -} diff --git a/rest-component/src/main/resources/META-INF/spring.factories b/rest-component/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..7080fd0de25e377830bbe8cb11b3a177b8c906e3 --- /dev/null +++ b/rest-component/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gcc.container.components.web.ApplicationConfiguration \ No newline at end of file diff --git a/rest-component/src/main/resources/application.yml b/rest-component/src/main/resources/application.yml index 773fc744d7e9fc23b9a848df6a6dd39196d39d21..26c9e5c36c0fb3375330e36003b53433b055f1a5 100644 --- a/rest-component/src/main/resources/application.yml +++ b/rest-component/src/main/resources/application.yml @@ -1,9 +1,7 @@ rest-component: - apiDoc: + api-doc: enable: true - - packagePath: com.gcc.container.client.* - title: 客户端接口 - description: 客户端服务接口 - - packagePath: com.gcc.container.web.* - title: web页面接口 - description: B/S架构下web页面接口 + packagePath: com.gcc.container.taskschedule.taskscheduling.controller.* + title: 任务调度 + description: 定时任务相关数据查看 + version: 1.0.0 \ No newline at end of file diff --git a/task-component/README.md b/task-component/README.md new file mode 100644 index 0000000000000000000000000000000000000000..eb4143d9abd2493095ede6e62959695c16037a00 --- /dev/null +++ b/task-component/README.md @@ -0,0 +1,333 @@ +# task-component + +> 相比于SpringBoot本身自带的定时任务注解的死板(程序一旦运行,无法更改定时任务执行周期、相关配置等问题)和xxl-Job的笨重,该组件从代码级提供了轻量级的定时任务管理解决方案 +> +> 引入该组件,只需要实现定时任务基类,即可完成自动注册定时任务,通过定时任务接口来动态控制任务的状态,使用接口配合页面,可扩展到页面功能,做到随时启停、修改定时任务 + +## 组件依赖说明 + +组件整体继承自Components包,所依赖Spring相关组件及通用的公共组件依赖跟随Components版本进行调整管控,相关内容可查看Components的README文档 + +```xml + + com.gcc.container + components + 1.0.0 + +``` + +除基本的SpringAOP、autoconfigure外无其他依赖 + +## 使用说明 + +找到对应版本的Components源码,下载到本地,本地编译安装到私仓中,在你的项目中引入: + +```xml + + + com.gcc.container.components + task-component + 1.0.0 + +``` + +### yaml配置 + +因该组件是基于包扫描去识别定时任务类和默认以JSON文件的形式去存储项目的定时任务信息,需要通过yaml配置文件来指定包路径和文件的存放路径,引入组件后,可在自己的yml文件中追加: + +```yml +gcc-task: + #指定定时任务存放的包路径,默认不填写则扫描全项目 + task-class-package : * + #定时任务管理的数据存放文件及其路径,默认不填写则存放项目运行目录下,如自行扩展实现,则该配置自动失效 + data-file-path: /opt/myproject/task_info.json +``` + +此处的两个配置项都包含默认值,所以不进行yml的`gcc-task`仍旧可以使用该组件 + +### 新建定时任务 + +引入依赖后,需要新增一个定时任务逻辑,则需要实现基类`AbstractBaseCronTask` ,并加入注解 `@TaskJob` + +**TaskJob的参数如下**: + +| 参数 | 说明 | 样例 | +| ------ | --------------------------------------------- | -------------- | +| cron | 定时任务默认的执行周期,仅在首次初始化该任务使用(**必填**) | 10 0/2 * * * ? | +| desc | 任务描述,非必填 | 这是测试任务 | +| bootup | 是否开机自启动,**缺省值为 false** | false | + +*`cron` 属性仅在第一次新增该任务时提供一个默认的执行周期,必须填写,后续任务加载后,定时任务相关数据会被存放在文件或数据库中,此时则以文件或数据库中该任务的cron为主* + +完整一个定时任务demo如下: + +```java +@TaskJob(cron = "10 0/2 * * * ?" ,desc = "这是一个测试任务",bootup = true) +public class TaskMysqlOne extends AbstractBaseCronTask { + + public TaskMysqlOne(TaskEntity taskEntity) { + super(taskEntity); + } + + @Override + public void beforeJob() { + } + + @Override + public void startJob() { + } + + @Override + public void afterJob() { + } +} +``` + +继承`AbstractBaseCronTask` 必须要实现携带TaskEntity参数的`构造函数`、`beforeJob()`、`startJob()` 、`afterJob()` 三个方法即可。**原则上这三个方法是规范定时任务的执行,实际使用,只需要把核心逻辑放在三个方法中任何一个即可**。 + + + +因定时任务类是非SpringBean管理的类,所以**在自定义的定时任务类内无法使用任何Spring相关的注解(如@Autowired)**,**但是却可以通过自带的**`getServer(Class className)`**方法来获取任何Spring上下文中的Bean** + +例如,你有一个UserService的接口及其Impl的实现类 + +```java +@TaskJob(cron = "10 0/2 * * * ?" ,desc = "这是一个测试任务",bootup = true) +public class TaskMysqlOne extends AbstractBaseCronTask { + + public TaskMysqlOne(TaskEntity taskEntity) { + super(taskEntity); + } + + @Override + public void beforeJob() { + } + + @Override + public void startJob() { + List names = getServer(UserService.class).searchAllUserName(); + //后续逻辑…… + //其他逻辑 + } + + @Override + public void afterJob() { + + } +} +``` + +### 服务接口说明 + +定时任务相关的管理操作均封装在`TaskScheduleManagerService`接口中,可直接在外部项目中注入使用即可: + +```java +@RestController +@RequestMapping("/myself/manage") +public class TaskSchedulingController { + + @Autowired + TaskScheduleManagerService taskScheduleManagerService; + + @GetMapping("/detail") + @Operation(summary = "具体任务对象") + public Response searchDetail(String taskId){ + return Response.success(taskScheduleManagerService.searchTaskDetail(taskId)); + } + + @GetMapping("/shutdown") + @Operation(summary = "关闭指定任务") + public Response shutdownTask(String taskId){ + return Response.success(taskScheduleManagerService.shutdownTask(taskId)); + } + + @GetMapping("/open") + @Operation(summary = "开启指定任务") + public Response openTask(String taskId){ + return Response.success(taskScheduleManagerService.openTask(taskId)); + } + +``` + +`TaskScheduleManagerService`的接口定义如下: + +```java +public interface TaskScheduleManagerService { + /** + * 查询在用任务 + * @return + */ + List searchTask(SearchTaskDto dto); + /** + * 查询任务详情 + * @param taskId 任务id + * @return TaskEntity + */ + TaskVo searchTaskDetail(String taskId); + /** + * 运行指定任务 + * @param taskId 任务id + * @return TaskRunRetDto + */ + TaskRunRetVo runTask(String taskId); + /** + * 关停任务 + * @param taskId 任务id + * @return TaskRunRetDto + */ + TaskRunRetVo shutdownTask(String taskId); + /** + * 开启任务 + * @param taskId 任务id + * @return TaskRunRetDto + */ + TaskRunRetVo openTask(String taskId); + /** + * 更新任务信息 + * @param entity 实体 + * @return TaskRunRetDto + */ + TaskRunRetVo updateTaskBusinessInfo(TaskEntity entity); +} +``` + +## 扩展说明 + +定时任务类首次新增被扫描后,会以TaskEntity的形式存在于内存中,TaskEntity如下: + +```java +@Data +public class TaskEntity implements Serializable { + + private String taskId; + + private String taskName; + + private String taskDesc; + + private String taskCron; + + private String taskClass; + + /** + * 任务相关配置 + */ + private String taskOutConfig; + + /** + * 任务注册时间 + */ + private String taskCreateTime; + + /** + * 是否启用 + */ + private Integer taskIsUse; + + /** + * 是否开启自启动 + */ + private Integer taskBootUp; + + /** + * 上次运行状态 + */ + private Integer taskLastRun; + + /** + * 任务是否在内存中 + */ + private Integer taskRamStatus; +``` + +task-component会将内存中的TaskEntity进行持久化,下次再启动项目时,则直接使用持久化的TaskEntity来加载定时任务,task-component默认是以JSON文件进行持久化,实际使用中为了能带来更好的体验,建议扩展为数据库存储,扩展方式如下: + +1、新建数据库表(以Mysql为例) + +```SQL +DROP TABLE IF EXISTS `tb_task_info`; +CREATE TABLE `tb_task_info` ( + `task_id` varchar(100) NOT NULL PRIMARY KEY , + `task_name` varchar(255) DEFAULT NULL, + `task_desc` text, + `task_cron` varchar(20) DEFAULT NULL, + `task_class` varchar(100) DEFAULT NULL COMMENT '定时任务类路径', + `task_is_use` tinyint DEFAULT NULL COMMENT '是否启用该任务,1:启用,0禁用', + `task_boot_up` tinyint DEFAULT NULL COMMENT '是否为开机即运行,1:初始化即运行,0,初始化不运行', + `task_out_config` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci COMMENT '定时任务额外配置项,采用json结构存放', + `task_create_time` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '定时任务追加时间', + `task_last_run` tinyint DEFAULT NULL COMMENT '任务上次执行状态;1正常,0执行失败,null未知', + `task_ram_status` tinyint DEFAULT NULL COMMENT '任务当前状态;1内存运行中,0内存移除', + PRIMARY KEY (`task_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC; +``` + +2、项目中实现`TaskRepository`接口,并使用@Primary注解: + +```java +@Repository +@Primary +public class TaskRepositoryImpl implements TaskRepository { + + @Autowired + private TaskDataMapper taskDataMapper; + + @Override + public int save(TaskEntity entity) { + entity.setTaskCreateTime(DateUtil.now()); + return taskDataMapper.insert(entity); + } + + @Override + public int update(TaskEntity entity) { + UpdateWrapper update = new UpdateWrapper<>(); + update.eq("task_id",entity.getTaskId()); + return taskDataMapper.update(entity,update); + } + + + @Override + public int removeByTaskId(String id) { + QueryWrapper query = new QueryWrapper<>(); + query.eq("task_id",id); + return taskDataMapper.delete(query); + } + + @Override + public List queryAllTask() { + return taskDataMapper.selectList(new QueryWrapper<>()); + } + + @Override + public TaskEntity queryData(String id) { + QueryWrapper query = new QueryWrapper<>(); + query.eq("task_id",id); + return taskDataMapper.selectOne(query); + } + + @Override + public List queryData(TaskEntity entity) { + QueryWrapper query = new QueryWrapper<>(); + query.orderByAsc("task_create_time"); + if(null != entity) { + if (null != entity.getTaskIsUse()) { + query.eq("task_is_use", entity.getTaskIsUse()); + } + if (null != entity.getTaskBootUp()) { + query.eq("task_boot_up", entity.getTaskBootUp()); + } + if (!StringUtils.isEmpty(entity.getTaskDesc())) { + query.like("task_desc", entity.getTaskDesc()); + } + } + return taskDataMapper.selectList(query); + } +} + +``` + + + +只需要实现其中指定的几个持久化和查询方法即可替换component中的JSON文件持久化 + + diff --git a/task-component/pom.xml b/task-component/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..79591f2501b40245941411df692cc14d8e9ff7be --- /dev/null +++ b/task-component/pom.xml @@ -0,0 +1,13 @@ + + 4.0.0 + + com.gcc.container + components + 1.0.0 + + com.gcc.container.components + task-component + ${component.version} + jar + diff --git a/task-component/src/main/java/com/gcc/container/components/task/AfterAppStarted.java b/task-component/src/main/java/com/gcc/container/components/task/AfterAppStarted.java new file mode 100644 index 0000000000000000000000000000000000000000..cc51b48ba3122cca257cea7cf9f327b7c53e374c --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/AfterAppStarted.java @@ -0,0 +1,28 @@ +package com.gcc.container.components.task; + + + +import com.gcc.container.components.task.compont.TaskSchedule; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AfterAppStarted { + + TaskSchedule taskSchedule; + + @Autowired + public void setTaskScheduleServer(TaskSchedule taskSchedule) { + this.taskSchedule = taskSchedule; + } + + @EventListener + public void onApplicationEvent(ApplicationReadyEvent event){ + //运行随系统启动的定时任务 + taskSchedule.runBootUpTask(); + } +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/ApplicationConfiguration.java b/task-component/src/main/java/com/gcc/container/components/task/ApplicationConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..0697cbcabba82efb6b04dcc38ca9d131909f94f8 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/ApplicationConfiguration.java @@ -0,0 +1,9 @@ +package com.gcc.container.components.task; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = "com.gcc.container.components.task.*") +public class ApplicationConfiguration { +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/annotation/TaskJob.java b/task-component/src/main/java/com/gcc/container/components/task/annotation/TaskJob.java new file mode 100644 index 0000000000000000000000000000000000000000..39a90f8c98affd1e0db025a2bf1fe713a52e2a40 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/annotation/TaskJob.java @@ -0,0 +1,19 @@ +package com.gcc.container.components.task.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TaskJob { + + String desc() default "暂无描述"; + + String cron() default "00 00 00 * * ?"; + + boolean bootup() default false; + +} \ No newline at end of file diff --git a/task-component/src/main/java/com/gcc/container/components/task/compont/AbstractBaseCronTask.java b/task-component/src/main/java/com/gcc/container/components/task/compont/AbstractBaseCronTask.java new file mode 100644 index 0000000000000000000000000000000000000000..7264f39f76578f7a295def55e34939ec4b2d043b --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/compont/AbstractBaseCronTask.java @@ -0,0 +1,111 @@ +package com.gcc.container.components.task.compont; + + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.gcc.container.components.task.dao.TaskRepository; +import com.gcc.container.components.task.model.entity.TaskEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; + +/** + * 抽象定时任务类 + * 实现定时任务需要继承该类 + * 子类实现时,不要使用Spring的任何注解 + * 若依赖外部的对象,使用 getServer()方法进行手动注入 + * @author GCC + */ +public abstract class AbstractBaseCronTask implements Runnable{ + + public Logger log; + + + private TaskEntity taskEntity; + + + private Double consumptionTime; + + private ApplicationContext applicationContext; + + + public AbstractBaseCronTask(TaskEntity taskEntity){ + log = LoggerFactory.getLogger(taskEntity.getTaskName()); + this.taskEntity = taskEntity; + } + + + //前置操作 + public abstract void beforeJob(); + + + //任务操作 + public abstract void startJob(); + + + //后置操作 + public abstract void afterJob(); + + + @Override + public void run() { + String id = this.taskEntity.getTaskId(); + this.taskEntity = getServer(TaskRepository.class).queryData(id); + log.info("---------------------任务 {} 开始执行-----------------------",taskEntity.getTaskName()); + log.info("任务描述:"+taskEntity.getTaskDesc()); + consumptionTime = 0d; + try { + Long start = System.currentTimeMillis(); + beforeJob(); + startJob(); + afterJob(); + Long end = System.currentTimeMillis(); + consumptionTime = NumberUtil.div((end - start),1000); + log.info("任务耗时:约 {} s",consumptionTime); + taskEntity.setTaskLastRun(1); + }catch (Exception e){ + log.error("任务{}计算出错!请及时排查问题!", taskEntity.getTaskName(),e); + taskEntity.setTaskLastRun(0); + } + getServer(TaskRepository.class).update(taskEntity); + log.info("---------------------任务 {} 结束执行-----------------------\n",taskEntity.getTaskName()); + } + + + public Object getSelfConfig(String key){ + try{ + TaskEntity entity = getServer(TaskRepository.class).queryData(taskEntity.getTaskId()); + JSONObject config = JSONUtil.parseObj(entity.getTaskOutConfig()); + return config.get(key); + }catch (Exception e){ + log.error("获取任务 {} 的配置参数失败,请确认参数配置格式是否正确!",taskEntity.getTaskName()); + } + return new JSONObject(); + } + + + public double getRunTime(){ return this.consumptionTime;} + + public TaskEntity getThisTaskInfo(){ + return this.taskEntity; + } + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + + /** + * 为适配反射生成的对象不受Spring容器管辖导致对象不共用问题 + * 提供手动获取系统上下文容器完成依赖注入 + * 通过类名使用Spring容器中的对象 + * @param className 类 + * @param 返回类型 + * @return T + */ + public T getServer(Class className){ + return applicationContext.getBean(className); + } + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/compont/InitTaskSchedulingApplication.java b/task-component/src/main/java/com/gcc/container/components/task/compont/InitTaskSchedulingApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..6085c3ad9cf69822452d2ff48b44d3387af88d1c --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/compont/InitTaskSchedulingApplication.java @@ -0,0 +1,48 @@ +package com.gcc.container.components.task.compont; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * 初始化任务调度机制 + * @author GCC + */ +@Order(1) +@Slf4j +@Component +public class InitTaskSchedulingApplication implements ApplicationRunner { + + private TaskSchedule taskSchedule; + + + private TaskTempFactory taskTempFactory; + + + @Autowired + public void setTaskScheduleServer(TaskSchedule taskSchedule) { + this.taskSchedule = taskSchedule; + } + + @Autowired + public void setTaskTempFactory(TaskTempFactory taskTempFactory) { + this.taskTempFactory = taskTempFactory; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + //开启扫描 + taskTempFactory.scanTaskInfo(); + log.info("【定时任务初始化】 容器初始化"); + taskSchedule.initScheduling(); + log.info("【定时任务初始化】定时任务初始化任务开始"); + taskSchedule.loadAllTask(); + log.info("【定时任务初始化】定时任务初始化任务完成"); + + } + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/compont/TaskSchedule.java b/task-component/src/main/java/com/gcc/container/components/task/compont/TaskSchedule.java new file mode 100644 index 0000000000000000000000000000000000000000..c2d2949050cc0d93a8d18e6e92b400b73c97649a --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/compont/TaskSchedule.java @@ -0,0 +1,62 @@ +package com.gcc.container.components.task.compont; + + + + +import com.gcc.container.components.task.model.entity.TaskEntity; + +import java.util.concurrent.ConcurrentHashMap; + +public interface TaskSchedule { + + + + ConcurrentHashMap getTaskSchedulingRam(); + + + /** + * 初始化任务调度 + */ + void initScheduling(); + + /** + * 添加任务至内存及容器 + * @param taskEntity 任务实体 + * @return boolean + */ + boolean addTaskToScheduling(TaskEntity taskEntity); + + /** + * 从任务调度器中移除任务 + * @param id 任务id + * @return Boolean + */ + boolean removeTaskFromScheduling(String id); + + + /** + * 执行指定任务 + * @param id 任务id + * @return double 耗时 + */ + double runTaskById(String id); + + + /** + * 清空任务 + */ + void claearAllTask(); + + + + /** + * 加载所有任务 + */ + void loadAllTask(); + + /** + * 运行开机自启任务 + */ + void runBootUpTask(); + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/compont/TaskScheduleImpl.java b/task-component/src/main/java/com/gcc/container/components/task/compont/TaskScheduleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d9f58a75f65f8451169f782308d56d07deed942d --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/compont/TaskScheduleImpl.java @@ -0,0 +1,160 @@ +package com.gcc.container.components.task.compont; + + +import cn.hutool.core.util.NumberUtil; +import com.gcc.container.components.task.dao.TaskRepository; +import com.gcc.container.components.task.model.entity.TaskEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +@Slf4j +@Component +public class TaskScheduleImpl implements TaskSchedule { + + //正在运行的任务 + private static ConcurrentHashMap runningTasks = new ConcurrentHashMap<>(); + + //线程池任务调度 + private ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); + + //内存中任务对象 + private static ConcurrentHashMap ramTasks = new ConcurrentHashMap<>(); + + //Spring容器池,用于注入Spring的bean + private ApplicationContext context; + + + private TaskRepository taskRepository; + + + private TaskTempFactory taskFactory; + + + @Autowired + public void setTaskRepository(TaskRepository taskRepository) { + this.taskRepository = taskRepository; + } + + @Autowired + public void setContext(ApplicationContext context) { + this.context = context; + } + + @Autowired + public void setTaskFactory(TaskTempFactory taskFactory) { + this.taskFactory = taskFactory; + } + + @Autowired + + + @Override + public ConcurrentHashMap getTaskSchedulingRam() { + return ramTasks; + } + + @Override + public void initScheduling() { + int num = taskFactory.mergeTaskEntities().size(); + num = num > 0 ? num:1; + this.threadPoolTaskScheduler.setPoolSize(num); + this.threadPoolTaskScheduler.setThreadNamePrefix("task-thread-"); + this.threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true); + this.threadPoolTaskScheduler.initialize(); + } + + + @Override + public boolean addTaskToScheduling(TaskEntity task) { + if(!runningTasks.containsKey(task.getTaskId())){ + try{ + Class clazz = Class.forName(task.getTaskClass()); + Constructor c = clazz.getConstructor(TaskEntity.class); + AbstractBaseCronTask runnable = (AbstractBaseCronTask) c.newInstance(task); + //反射方式生成对象不属于Spring容器管控,对于Spring的bean使用需要手动注入 + runnable.setApplicationContext(context); + CronTrigger cron = new CronTrigger(task.getTaskCron()); + //put到runTasks + runningTasks.put(task.getTaskId(), Objects.requireNonNull(this.threadPoolTaskScheduler.schedule(runnable, cron))); + //存入内存中,便于外部调用 + ramTasks.put(task.getTaskId(),runnable); + task.setTaskRamStatus(1); + taskRepository.update(task); + return true; + }catch (Exception e){ + log.error("定时任务加载失败..."+e); + } + } + return false; + } + + + @Override + public boolean removeTaskFromScheduling(String id) { + if(runningTasks.containsKey(id)){ + runningTasks.get(id).cancel(true); + runningTasks.remove(id); + ramTasks.remove(id); + TaskEntity entity = taskRepository.queryData(id); + entity.setTaskRamStatus(0); + entity.setTaskLastRun(null); + if( 1 == taskRepository.update(entity) && !runningTasks.containsKey(id)){ + log.info("【定时任务控制器】任务"+id+"从内存进程池中移除,被终止!"); + return true; + } + } + return false; + } + + @Override + public double runTaskById(String id) { + TaskEntity task = taskRepository.queryData(id); + if(null!=task) { + if (runningTasks.containsKey(task.getTaskId())){ + ramTasks.get(task.getTaskId()).run(); + return ramTasks.get(task.getTaskId()).getRunTime(); + } + } + return 0d; + } + + @Override + public void claearAllTask() { + ramTasks.clear(); + log.info("【定时任务控制器】清除内存任务 完成"); + runningTasks.clear(); + log.info("【定时任务控制器】清除线程任务 完成"); + threadPoolTaskScheduler.shutdown(); + } + + @Override + public void loadAllTask() { + List allTask = taskFactory.mergeTaskEntities(); + for (TaskEntity task : allTask) { + if(addTaskToScheduling(task)){ + log.info("【定时任务初始化】装填任务:{} [ 任务执行周期:{} ] [ bootup:{}]",task.getTaskName(),task.getTaskCron(),task.getTaskBootUp()); + } + } + } + + + @Override + public void runBootUpTask() { + List list = taskRepository.queryAllTask(); + for(TaskEntity task:list){ + if(1 == task.getTaskIsUse() && 1 == task.getTaskBootUp()) { + runTaskById(task.getTaskId()); + } + } + } +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/compont/TaskTempFactory.java b/task-component/src/main/java/com/gcc/container/components/task/compont/TaskTempFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..1f0eca59f23bde5d74ff036a0789c0d178033d6f --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/compont/TaskTempFactory.java @@ -0,0 +1,99 @@ +package com.gcc.container.components.task.compont; + + +import cn.hutool.core.date.DateUtil; +import cn.hutool.crypto.digest.DigestUtil; + +import com.gcc.container.components.task.annotation.TaskJob; +import com.gcc.container.components.task.dao.TaskRepository; +import com.gcc.container.components.task.model.entity.TaskEntity; +import com.gcc.container.components.task.property.TaskDataProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +class TaskTempFactory { + + @Autowired + private TaskRepository taskRepository; + + @Autowired + TaskDataProperties taskDataProperties; + + private List tempContainer = new ArrayList<>(); + + + public void scanTaskInfo() throws Exception { + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); + provider.addIncludeFilter(new AnnotationTypeFilter(TaskJob.class)); + Set beanDefinitions = provider.findCandidateComponents(taskDataProperties.getTaskClassPackage()); + for (BeanDefinition beanDefinition : beanDefinitions) { + Class clazz = Class.forName(beanDefinition.getBeanClassName()); + TaskJob taskJob = clazz.getAnnotation(TaskJob.class); + if (AbstractBaseCronTask.class.isAssignableFrom(clazz)) { + tempContainer.add(builderTaskEntity(clazz,taskJob)); + } else { + throw new IllegalArgumentException("Class " + clazz.getName() + " must extend AbstractBaseCronTask"); + } + } + } + + + public List mergeTaskEntities() { + List db = getIsUseTask(); + Map map = db.stream() + .collect(Collectors.toMap( + TaskEntity::getTaskClass, + task -> task + )); + for(TaskEntity entity:tempContainer){ + if(!map.containsKey(entity.getTaskClass())){ + taskRepository.save(entity); + db.add(entity); + } + } + return db; + } + + + private TaskEntity builderTaskEntity(Class cronTaskClass, TaskJob taskJob) { + String path = cronTaskClass.getName(); TaskEntity entity = new TaskEntity(); + entity.setTaskId(DigestUtil.md5Hex(path)); + entity.setTaskClass(path); + entity.setTaskIsUse(1); + entity.setTaskName(cronTaskClass.getSimpleName()); + entity.setTaskDesc(taskJob.desc()); + entity.setTaskBootUp(converBootUp(taskJob.bootup())); + entity.setTaskCron(taskJob.cron()); + entity.setTaskCreateTime(DateUtil.now()); + //todo 外部配置 + return entity; + } + + + private List getIsUseTask(){ + List list = taskRepository.queryAllTask(); + List result = new ArrayList<>(); + for(TaskEntity entity:list){ + if(entity.isUse()){ + result.add(entity); + } + } + return result; + } + + private Integer converBootUp(Boolean flag){ + if(flag){ + return 1; + } + return 0; + } +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/dao/TaskRepository.java b/task-component/src/main/java/com/gcc/container/components/task/dao/TaskRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..c05ea46ce023d5ae982b67a7d2c2ecd08061fc9b --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/dao/TaskRepository.java @@ -0,0 +1,57 @@ +package com.gcc.container.components.task.dao; +import com.gcc.container.components.task.model.entity.TaskEntity; +import java.util.List; +/** + * 任务数据操作 + */ +public interface TaskRepository { + + /** + * 新增数据 + * @param entity + * @return int + */ + int save(TaskEntity entity); + + + /** + * 删除任务ID + * @param id id + * @return int + */ + int removeByTaskId(String id); + + + /** + * 更新整个任务 + * @param entity 实体 + * @return int + */ + int update(TaskEntity entity); + + + /** + * 查询全部任务 + * @return List + */ + List queryAllTask(); + + + + /** + * 查询数据 + * @param entity 查询 + * @return TaskEntity + */ + List queryData(TaskEntity entity); + + + + + /** + * 查询具体的任务实体 + * @param taskId 任务id + * @return TaskEntity + */ + TaskEntity queryData(String taskId); +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/dao/impl/FileOpTaskRepositoryImpl.java b/task-component/src/main/java/com/gcc/container/components/task/dao/impl/FileOpTaskRepositoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..50fefe69b605be1a9b0f311beab9bf5d889c7b16 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/dao/impl/FileOpTaskRepositoryImpl.java @@ -0,0 +1,154 @@ +package com.gcc.container.components.task.dao.impl; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONUtil; +import com.gcc.container.components.task.dao.TaskRepository; +import com.gcc.container.components.task.model.entity.TaskEntity; +import com.gcc.container.components.task.property.TaskDataProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Repository +@Slf4j +public class FileOpTaskRepositoryImpl implements TaskRepository { + + + @Autowired + private TaskDataProperties properties; + + + + @Override + public int save(TaskEntity entity) { + List list = readFileInfo(); + list.add(entity); + writeFileInfo(list); + return 1; + } + + @Override + public int removeByTaskId(String id) { + List list = readFileInfo(); + List data = new ArrayList<>(); + for(TaskEntity entity:list){ + if(!entity.getTaskId().equals(id)){ + data.add(entity); + } + } + writeFileInfo(data); + return list.size() - data.size(); + } + + + @Override + public int update(TaskEntity entity) { + List list = readFileInfo(); + List data = new ArrayList<>(); + for(TaskEntity one:list){ + if(!one.getTaskId().equals(entity.getTaskId())){ + data.add(one); + } + } + data.add(entity); + writeFileInfo(data); + return 1; + } + + @Override + public List queryAllTask() { + return readFileInfo(); + } + + @Override + public List queryData(TaskEntity entity) { + List list = readFileInfo(); + // 获取不为空的属性 + Map nonNullProperties = entity.getNonNullProperties(); + // 进行 AND 查询 + List result = new ArrayList<>(); + for (TaskEntity t : list) { + boolean match = true; + for (Map.Entry entry : nonNullProperties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if ("taskDesc".equals(key) && !StrUtil.equals(t.getTaskDesc(), (String) value)) { + match = false; + break; + } + if ("taskIsUse".equals(key) && t.getTaskIsUse() != (Integer) value) { + match = false; + break; + } + if ("taskBootUp".equals(key) && t.getTaskBootUp() != (Integer) value) { + match = false; + break; + } + } + if (match) { + result.add(t); + } + } + return result; + } + + + + + @Override + public TaskEntity queryData(String id) { + List list = readFileInfo(); + for(TaskEntity entity:list){ + if(1 == entity.getTaskIsUse() && id.equals(entity.getTaskId())){ + return entity; + } + } + return null; + } + + + private List readFileInfo(){ + // 确保文件存在 + File file = FileUtil.file(getDefaultPath()); + if (!file.exists()) { + log.error("FILE is not exists"); + return new ArrayList<>(); + } + // 读取 JSON 文件内容 + String jsonContent = FileUtil.readUtf8String(getDefaultPath()); + // 将 JSON 字符串转换为 JSON 对象 + JSONArray data = JSONUtil.parseArray(jsonContent); + List result = new ArrayList<>(); + for(int i = 0; i < data.size(); i++){ + result.add(JSONUtil.toBean(data.getJSONObject(i),TaskEntity.class)); + } + return result; + } + + + + /** + * 覆盖写入 JSONArray 文件 + */ + private void writeFileInfo(List data) { + JSONArray array = new JSONArray(data); + // 确保文件存在或创建文件 + File file = FileUtil.touch(getDefaultPath()); + FileUtil.writeUtf8String(array.toJSONString(0), file); + } + + + private String getDefaultPath(){ + if(StrUtil.isNotBlank(properties.getDataFilePath())){ + return properties.getDataFilePath(); + } + return "db/task_info.json"; + } + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/model/TaskVoConvertor.java b/task-component/src/main/java/com/gcc/container/components/task/model/TaskVoConvertor.java new file mode 100644 index 0000000000000000000000000000000000000000..fe9089ce57c7d38e7c95e67d3f303be76306cda6 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/model/TaskVoConvertor.java @@ -0,0 +1,40 @@ +package com.gcc.container.components.task.model; + + +import com.gcc.container.components.task.model.entity.TaskEntity; +import com.gcc.container.components.task.model.vo.TaskVo; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class TaskVoConvertor { + + + public TaskVo toTaskVo(TaskEntity entity){ + return TaskVo.builder() + .taskId(entity.getTaskId()) + .taskName(entity.getTaskName()) + .taskDesc(entity.getTaskDesc()) + .taskCron(entity.getTaskCron()) + .taskOutConfig(entity.getTaskOutConfig()) + .taskBootUp(entity.getTaskBootUp()) + .taskCreateTime(entity.getTaskCreateTime()) + .status(ramStatusToRunStatus(entity.getTaskRamStatus())) + .build(); + } + + public List toTaskVos(List entities){ + return entities.stream().map(this::toTaskVo).collect(Collectors.toList()); + } + + private TaskVo.TaskRunStatus ramStatusToRunStatus(Integer ramStatus){ + if(1 == ramStatus){ + return TaskVo.TaskRunStatus.open; + }else { + return TaskVo.TaskRunStatus.shutdown; + } + } + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/model/dto/SearchTaskDto.java b/task-component/src/main/java/com/gcc/container/components/task/model/dto/SearchTaskDto.java new file mode 100644 index 0000000000000000000000000000000000000000..d4f352c0d6dc502e0fff795aca066124151bfcbc --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/model/dto/SearchTaskDto.java @@ -0,0 +1,28 @@ +package com.gcc.container.components.task.model.dto; + +import lombok.Data; +import java.io.Serializable; + + +/** + * 查询参数 + */ +@Data +public class SearchTaskDto implements Serializable { + + + private String filterKey; + + + private Boolean isBootUp; + + + private Integer lastRunStatus; + + + private Integer pageNo; + + + private Integer pageSize; + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/model/entity/TaskEntity.java b/task-component/src/main/java/com/gcc/container/components/task/model/entity/TaskEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..cfb9bcc78015fd744d8dd208fe577e46995c25b1 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/model/entity/TaskEntity.java @@ -0,0 +1,115 @@ +package com.gcc.container.components.task.model.entity; + + +import lombok.Data; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * 任务实体类 + * @author GCC + */ +@Data +public class TaskEntity implements Serializable { + + //todo 解决外部创建表的操作 + + private String taskId; + + private String taskName; + + private String taskDesc; + + private String taskCron; + + private String taskClass; + + /** + * 任务相关配置 + */ + private String taskOutConfig; + + /** + * 任务注册时间 + */ + private String taskCreateTime; + + /** + * 是否启用 + */ + private Integer taskIsUse; + + /** + * 是否开启自启动 + */ + private Integer taskBootUp; + + /** + * 上次运行状态 + */ + private Integer taskLastRun; + + /** + * 任务是否在内存中 + */ + private Integer taskRamStatus; + + + public TaskEntity taskIsUse(Boolean isUse) { + if(isUse) { + this.taskIsUse = 1; + }else { + this.taskIsUse = 0; + } + return this; + } + + public TaskEntity taskBootUp(Boolean isBootUp) { + if(isBootUp) { + this.taskBootUp = 1; + }else { + this.taskBootUp = 0; + } + return this; + } + + public TaskEntity taskLastRun(Integer taskLastRun) { + this.taskLastRun = taskLastRun; + return this; + } + + public TaskEntity taskRamStatus(Integer taskRamStatus) { + this.taskRamStatus = taskRamStatus; + return this; + } + + + + public Boolean isUse(){ + if(null != this.taskIsUse && 1 == this.taskIsUse){ + return true; + } + return false; + } + + + public Boolean isBootUp(){ + if(null != this.taskBootUp && 1 == this.taskBootUp){ + return true; + } + return false; + } + + + + public Map getNonNullProperties() { + Map properties = new HashMap<>(); + if (taskDesc != null) properties.put("taskDesc", taskDesc); + if (taskIsUse != null) properties.put("taskIsUse", taskIsUse); + if (taskBootUp != null) properties.put("taskBootUp", taskBootUp); + return properties; + } + + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/model/vo/TaskRunRetVo.java b/task-component/src/main/java/com/gcc/container/components/task/model/vo/TaskRunRetVo.java new file mode 100644 index 0000000000000000000000000000000000000000..300d93572b19af6090d10c439695dfdfefd03bdf --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/model/vo/TaskRunRetVo.java @@ -0,0 +1,70 @@ +package com.gcc.container.components.task.model.vo; + + +import com.gcc.container.components.task.model.entity.TaskEntity; +import lombok.Data; + +import java.io.Serializable; + +/** + * 任务运行状态 + */ +@Data +public class TaskRunRetVo implements Serializable { + + + private String taskId; + + + private String taskName; + + + private TaskOperation taskOperation; + + /** + * 执行状态 + * 1:成功 + * 0:失败 + */ + private Integer result; + + + private String extend = "successful"; + + + public TaskRunRetVo(TaskOperation taskOperation, int result) { + this.taskOperation = taskOperation; + this.result = result; + if(result == 0){ + this.extend = "fail"; + } + } + + public TaskRunRetVo taskInfo(TaskEntity taskEntity){ + this.taskId = taskEntity.getTaskId(); + this.taskName = taskEntity.getTaskName(); + return this; + } + + public TaskRunRetVo extend(Object extend){ + this.extend =String.valueOf(extend); + return this; + } + + public enum TaskOperation{ + + //开启 + open, + + //关闭/停止 + shutdown, + + //更新任务 + update, + + //立即执行 + run; + } + + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/model/vo/TaskVo.java b/task-component/src/main/java/com/gcc/container/components/task/model/vo/TaskVo.java new file mode 100644 index 0000000000000000000000000000000000000000..433c27a1534e282537d90a08177cbbebecdfc164 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/model/vo/TaskVo.java @@ -0,0 +1,55 @@ +package com.gcc.container.components.task.model.vo; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +@Data +@Builder +public class TaskVo implements Serializable { + + private String taskId; + + private String taskName; + + private String taskDesc; + + private String taskCron; + + /** + * 任务相关配置 + */ + private String taskOutConfig; + + /** + * 任务注册时间 + */ + private String taskCreateTime; + + + /** + * 是否开启自启动 + */ + private Integer taskBootUp; + + /** + * 启停状态 + */ + private TaskRunStatus status; + + + public enum TaskRunStatus{ + //开启 + open(1), + //关闭 + shutdown(0); + + Integer code; + + TaskRunStatus(Integer code){ + this.code = code; + } + } + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/property/TaskDataProperties.java b/task-component/src/main/java/com/gcc/container/components/task/property/TaskDataProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..20f6cb17c17f35fe7a7c0a0a38cff6aa7628f7fb --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/property/TaskDataProperties.java @@ -0,0 +1,25 @@ +package com.gcc.container.components.task.property; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties("gcc-task") +@Data +public class TaskDataProperties { + + + private String dataFilePath; + + + private String taskClassPackage; + + public String getTaskClassPackage() { + if(StrUtil.isBlankIfStr(this.taskClassPackage)){ + return "*"; + } + return taskClassPackage; + } +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/service/TaskScheduleManagerService.java b/task-component/src/main/java/com/gcc/container/components/task/service/TaskScheduleManagerService.java new file mode 100644 index 0000000000000000000000000000000000000000..ec07f9c66076c217c706ac4ef9cefe8ee65399d0 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/service/TaskScheduleManagerService.java @@ -0,0 +1,55 @@ +package com.gcc.container.components.task.service; + +import com.gcc.container.components.task.model.dto.SearchTaskDto; +import com.gcc.container.components.task.model.entity.TaskEntity; +import com.gcc.container.components.task.model.vo.TaskRunRetVo; +import com.gcc.container.components.task.model.vo.TaskVo; +import java.util.List; +public interface TaskScheduleManagerService { + + /** + * 查询在用任务 + * @return + */ + List searchTask(SearchTaskDto dto); + + /** + * 查询任务详情 + * @param taskId 任务id + * @return TaskEntity + */ + TaskVo searchTaskDetail(String taskId); + + + /** + * 运行指定任务 + * @param taskId 任务id + * @return TaskRunRetDto + */ + TaskRunRetVo runTask(String taskId); + + + /** + * 关停任务 + * @param taskId 任务id + * @return TaskRunRetDto + */ + TaskRunRetVo shutdownTask(String taskId); + + + /** + * 开启任务 + * @param taskId 任务id + * @return TaskRunRetDto + */ + TaskRunRetVo openTask(String taskId); + + + /** + * 更新任务信息 + * @param entity 实体 + * @return TaskRunRetDto + */ + TaskRunRetVo updateTaskBusinessInfo(TaskEntity entity); + +} diff --git a/task-component/src/main/java/com/gcc/container/components/task/service/impl/TaskScheduleManagerServiceImpl.java b/task-component/src/main/java/com/gcc/container/components/task/service/impl/TaskScheduleManagerServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e0c0ba5c1cf8f5e556204483a9e5b89f9e41a281 --- /dev/null +++ b/task-component/src/main/java/com/gcc/container/components/task/service/impl/TaskScheduleManagerServiceImpl.java @@ -0,0 +1,142 @@ +package com.gcc.container.components.task.service.impl; + +import cn.hutool.json.JSONUtil; +import com.gcc.container.components.task.compont.AbstractBaseCronTask; +import com.gcc.container.components.task.compont.TaskSchedule; +import com.gcc.container.components.task.dao.TaskRepository; +import com.gcc.container.components.task.model.TaskVoConvertor; +import com.gcc.container.components.task.model.dto.SearchTaskDto; +import com.gcc.container.components.task.model.entity.TaskEntity; +import com.gcc.container.components.task.model.vo.TaskRunRetVo; +import com.gcc.container.components.task.model.vo.TaskVo; +import com.gcc.container.components.task.service.TaskScheduleManagerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.List; + + +@Component +public class TaskScheduleManagerServiceImpl implements TaskScheduleManagerService { + + + private TaskSchedule taskSchedule; + + private TaskRepository taskRepository; + + private TaskVoConvertor taskVoConvertor; + + @Autowired + public void setTaskScheduleServer(TaskSchedule taskSchedule) { + this.taskSchedule = taskSchedule; + } + + @Autowired + public void setTaskRepository(TaskRepository taskRepository) { + this.taskRepository = taskRepository; + } + + @Autowired + public void setTaskVoConvertor(TaskVoConvertor taskVoConvertor) { + this.taskVoConvertor = taskVoConvertor; + } + + @Override + public List searchTask(SearchTaskDto dto) { + TaskEntity entity = new TaskEntity() + .taskIsUse(true) + .taskBootUp(dto.getIsBootUp()) + .taskLastRun(dto.getLastRunStatus()); + entity.setTaskDesc(dto.getFilterKey()); + return taskVoConvertor.toTaskVos(taskRepository.queryData(entity)); + } + + + @Override + public TaskVo searchTaskDetail(String taskId) { + if(!StringUtils.isEmpty(taskId)){ + return taskVoConvertor.toTaskVo(taskRepository.queryData(taskId)); + } + return null; + } + + + @Override + public TaskRunRetVo runTask(String taskId) { + AbstractBaseCronTask task = taskSchedule.getTaskSchedulingRam().get(taskId); + TaskRunRetVo result = new TaskRunRetVo(TaskRunRetVo.TaskOperation.run, 0); + if(null != task) { + double time = taskSchedule.runTaskById(taskId); + result.setResult(1); + return result.extend(time).taskInfo(task.getThisTaskInfo()); + } else { + return result.extend("任务未启用"); + } + } + + @Override + public TaskRunRetVo shutdownTask(String taskId) { + AbstractBaseCronTask task = taskSchedule.getTaskSchedulingRam().get(taskId); + TaskRunRetVo result = new TaskRunRetVo(TaskRunRetVo.TaskOperation.shutdown, 0); + if(null != task) { + boolean flag = taskSchedule.removeTaskFromScheduling(taskId); + if(flag) { + result.setResult(1); + } + return result.extend("任务成功关闭").taskInfo(task.getThisTaskInfo()); + } else { + return result.extend("任务未启用"); + } + } + + @Override + public TaskRunRetVo openTask(String taskId) { + TaskEntity task = taskRepository.queryData(taskId); + TaskRunRetVo result = new TaskRunRetVo(TaskRunRetVo.TaskOperation.open, 0); + if(null != task) { + if (!taskSchedule.getTaskSchedulingRam().containsKey(taskId)) { + boolean flag = taskSchedule.addTaskToScheduling(task); + if(flag) { + result.setResult(1); + } + return result.extend("任务开启成功").taskInfo(task); + } else { + return result.extend("任务处于启动状态").taskInfo(task); + } + }else { + return result.extend("任务不存在!"); + } + } + + @Override + public TaskRunRetVo updateTaskBusinessInfo(TaskEntity entity) { + TaskEntity task = taskRepository.queryData(entity.getTaskId()); + TaskRunRetVo result = new TaskRunRetVo(TaskRunRetVo.TaskOperation.update, 0).taskInfo(entity); + String config = entity.getTaskOutConfig(); + if(null != config && !JSONUtil.isJson(config) && !JSONUtil.isJsonArray(config)){ + result.setResult(0); + result.extend("更新任务失败,任务配置必须为JSON或空"); + result.taskInfo(entity); + return result; + } + task.setTaskCron(entity.getTaskCron()); + task.setTaskOutConfig(entity.getTaskOutConfig()); + task.setTaskName(entity.getTaskName()); + task.setTaskDesc(entity.getTaskDesc()); + int num = taskRepository.update(task); + if (num == 1) { + result.setResult(1); + result.extend("成功更新任务"); + result.taskInfo(entity); + //重新刷新任务 + taskSchedule.removeTaskFromScheduling(entity.getTaskId()); + taskSchedule.addTaskToScheduling(task); + } + + return result; + } + + + +} diff --git a/task-component/src/main/resources/META-INF/spring.factories b/task-component/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..0be5d40b2678b60992b6f3eb3f5668525a54d3b1 --- /dev/null +++ b/task-component/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gcc.container.components.task.ApplicationConfiguration \ No newline at end of file diff --git a/task-component/src/main/resources/application.yml b/task-component/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..78cba490151d5218a727318e4e30508c2a1e616a --- /dev/null +++ b/task-component/src/main/resources/application.yml @@ -0,0 +1,3 @@ +gcc-task: + task-class-package : * + data-file-path: E:\JavaProject\task_info.json