From 62682dcb5b169e55352e8f0739a0fe0efef7a4ba Mon Sep 17 00:00:00 2001 From: wht <1309375318@qq.com> Date: Mon, 31 Mar 2025 12:03:06 +0800 Subject: [PATCH] init: plm service --- .gitignore | 31 + .../PROJECT_NAME_IS_UNDEFINED-task.log | 0 .../PROJECT_NAME_IS_UNDEFINED.log | 0 commons/.gitignore | 31 + .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ commons/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes commons/.mvn/wrapper/maven-wrapper.properties | 2 + commons/pom.xml | 286 ++++ .../commons/ant/ControllerLog.java | 40 + .../commons/ant/ServiceLog.java | 15 + .../commons/dto/PlmPageReqVo.java | 13 + .../commons/dto/PlmUtilsException.java | 35 + .../commons/dto/ResEntity.java | 37 + .../commons/dto/WebResponse.java | 132 ++ .../commons/dto/enum4cache/EnumList.java | 31 + .../commons/dto/enum4cache/EnumValue.java | 20 + .../commons/em/C8ImportTypeEnum.java | 14 + .../commons/em/C8NativeExportType.java | 5 + .../centricsoftware/commons/em/ResCode.java | 67 + .../commons/exception/BaseException.java | 52 + .../C8UnsupportedEncodingException.java | 30 + .../commons/exception/ParamException.java | 30 + .../exception/RequestArgsException.java | 31 + .../commons/utils/CSVParser.java | 91 ++ .../commons/utils/CommonUtil.java | 602 +++++++++ .../commons/utils/EncodeUtils.java | 143 ++ .../commons/utils/ExcelUtil.java | 1150 +++++++++++++++++ .../commons/utils/ExportExcel.java | 1057 +++++++++++++++ .../commons/utils/FileUtil.java | 267 ++++ .../commons/utils/FtpUtil.java | 198 +++ .../commons/utils/ImageUtil.java | 121 ++ .../commons/utils/ImportExcel.java | 257 ++++ .../centricsoftware/commons/utils/IpUtil.java | 36 + .../commons/utils/JsonHelper.java | 349 +++++ .../commons/utils/JsonUtil.java | 225 ++++ .../commons/utils/MailUtil.java | 181 +++ .../commons/utils/MarkImageUtil.java | 55 + .../commons/utils/PageUtil.java | 21 + .../commons/utils/SMBFileUtil.java | 197 +++ .../commons/utils/ServicesUtil.java | 326 +++++ .../commons/utils/SpringContextHolder.java | 88 ++ .../commons/utils/SpringUtil.java | 43 + .../commons/utils/StringUtils.java | 616 +++++++++ .../commons/utils/ZIPUtil.java | 393 ++++++ commons/src/main/resources/license.xml | 12 + config/.gitignore | 31 + .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ config/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes config/.mvn/wrapper/maven-wrapper.properties | 2 + config/pom.xml | 94 ++ .../config/config/ExecutorConfig.java | 48 + .../config/cons/Constants.java | 151 +++ .../config/entity/CenterProperties.java | 36 + .../config/entity/CsProperties.java | 60 + .../config/entity/EsProperties.java | 27 + .../src/main/resources/application-common.yml | 17 + config/src/main/resources/application-es.yml | 38 + .../src/main/resources/application-import.yml | 31 + .../resources/application-integration.yml | 63 + .../main/resources/application-mybatis.yml | 60 + config/src/main/resources/application-plm.yml | 118 ++ config/src/main/resources/application-pro.yml | 94 ++ .../main/resources/application-rabbitmq.yml | 38 + .../src/main/resources/application-redis.yml | 73 ++ config/src/main/resources/logback-spring.xml | 82 ++ core/.gitignore | 31 + core/.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ core/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes core/.mvn/wrapper/maven-wrapper.properties | 2 + core/pom.xml | 202 +++ .../centricsoftware/core/CoreApplication.java | 35 + .../core/aspect/AopAspect.java | 203 +++ .../core/aspect/BaseAspect.java | 203 +++ .../core/config/BootConfig.java | 77 ++ .../core/config/WebMvcConfig.java | 54 + .../core/controller/TestController.java | 193 +++ .../com/centricsoftware/core/dto/BaseDto.java | 13 + .../core/dto/CommonExportDto.java | 35 + .../centricsoftware/core/dto/DataPackage.java | 32 + .../core/handler/GlobalExceptionHandler.java | 122 ++ .../core/service/CommonExportService.java | 216 ++++ .../core/task/config/TaskConfig.java | 39 + .../task/job/inter/DeleteLogTask.java | 38 + core/src/main/resources/application.yml | 39 + core/src/main/resources/banner.txt | 7 + core/src/main/resources/bootstrap.yml | 26 + .../resources/jboss-deployment-structure.xml | 8 + core/src/main/resources/template/export1.xlsx | Bin 0 -> 10719 bytes core/src/main/resources/template/export2.xlsx | Bin 0 -> 15024 bytes core/src/main/resources/template/export3.xlsx | Bin 0 -> 13886 bytes core/src/main/resources/template/export4.xlsx | Bin 0 -> 53318 bytes core/src/main/resources/template/export5.xlsx | Bin 0 -> 15175 bytes core/src/main/webapp/index.jsp | 16 + .../core/model/TimeSeries.java | 16 + .../core/service/ElasticsearchClientTest.java | 204 +++ core/src/test/postman/Test.http | 75 ++ core/src/test/postman/http-client.env.json | 11 + enhancement/.gitignore | 31 + enhancement/pom.xml | 71 + .../enhancement/ant/C8Column.java | 33 + .../enhancement/ant/C8ColumnConfig.java | 71 + .../enhancement/ant/C8Entity.java | 45 + .../enhancement/ant/C8EntityConfig.java | 61 + .../enhancement/ant/C8NativeExport.java | 17 + .../enhancement/ant/ExcelAlias.java | 18 + .../enhancement/ant/grid/GridColumn.java | 75 ++ .../enhancement/ant/grid/GridForm.java | 56 + .../enhancement/component/CacheComponent.java | 42 + .../component/ExcelAliasHelper.java | 131 ++ .../enhancement/component/RedisUtils.java | 635 +++++++++ .../component/StandaloneLockComponent.java | 87 ++ .../component/c8/NativeExportComponent.java | 301 +++++ .../controller/ImagesUploadController.java | 53 + .../ImportPropertiesController.java | 215 +++ .../controller/c8/C8TestController.java | 663 ++++++++++ .../c8/FiledPermissionsController.java | 33 + .../controller/log/LogController.java | 134 ++ .../log/LogForRelationshipDBController.java | 89 ++ .../SpecificationDataSheetController.java | 57 + .../enhancement/dto/AnnoImportLineError.java | 22 + .../enhancement/dto/PlmReqDto.java | 19 + .../enhancement/dto/UploadImagesEntity.java | 19 + .../enhancement/dto/cache/OkHttpCache.java | 8 + .../dto/cache/ThreadLocalCache.java | 17 + .../enhancement/dto/excel/ExcelAroundAOP.java | 39 + .../enhancement/dto/log/FeignLogDto.java | 133 ++ .../enhancement/dto/log/FeignLogReqDto.java | 46 + .../enhancement/dto/log/LogDto.java | 11 + .../enhancement/dto/log/PlmLog.java | 43 + .../enhancement/dto/log/QueryPlmLogReq.java | 48 + .../enhancement/dto/table/GridOptions.java | 120 ++ .../enhancement/dto/table/GridResponse.java | 28 + .../dto/table/GridResponseData.java | 20 + .../table/gridconfig/ColumnBaseConfig.java | 21 + .../dto/table/gridconfig/ColumnConfig.java | 102 ++ .../dto/table/gridconfig/ExportConfig.java | 34 + .../dto/table/gridconfig/FilterConfig.java | 32 + .../dto/table/gridconfig/FormConfig.java | 46 + .../dto/table/gridconfig/FormItemConfig.java | 65 + .../dto/table/gridconfig/FromItemRender.java | 74 ++ .../dto/table/gridconfig/PagerConfig.java | 34 + .../dto/table/gridconfig/PrintConfig.java | 26 + .../dto/table/gridconfig/RightBtnConfig.java | 31 + .../dto/table/gridconfig/RowConfig.java | 24 + .../dto/table/gridconfig/SortConfig.java | 29 + .../dto/table/gridconfig/ToolbarConfig.java | 69 + .../dto/table/gridconfig/ToolsConfig.java | 54 + .../es/config/EsAutoConfigure.java | 76 ++ .../enhancement/filter/AddCookieFilter.java | 40 + .../filter/CachingRequestBodyFilter.java | 22 + .../enhancement/mapper/ExtractMapper.java | 20 + .../enhancement/mapper/LogMapper.java | 10 + .../modules/c8/ant/DepPathField.java | 29 + .../modules/c8/component/EnumCache.java | 257 ++++ .../c8/component/dep/CentricResult.java | 41 + .../c8/component/dep/CentricResultCache.java | 9 + .../component/dep/CentricResultForFeign.java | 40 + .../c8/component/dep/DepPathCache.java | 33 + .../c8/component/dep/DepPathParser.java | 445 +++++++ .../c8/component/dep/DepPathResult.java | 187 +++ .../design/chain/IDepPathSearchChain.java | 150 +++ .../impl/DefaultDepPathSearchChainImpl.java | 173 +++ .../factory/ICentricAbstractFactory.java | 15 + .../c8/component/factory/IDepPathFactory.java | 14 + .../factory/INodeServiceFactory.java | 12 + .../factory/impl/DefaultDepPathFactory.java | 34 + .../impl/DefaultICentricAbstractFactory.java | 38 + .../impl/DefaultINodeServiceFactory.java | 26 + .../c8/component/parser/ICentricResult.java | 19 + .../component/parser/ICentricResultCache.java | 16 + .../parser/impl/CentricResultCacheImpl.java | 20 + .../impl/DefaultICentricResultImpl.java | 22 + .../modules/c8/config/FeignConfig.java | 129 ++ .../c8/config/okhttp/CookieJarHandler.java | 42 + .../modules/c8/dto/C8LogReturnEntity.java | 26 + .../modules/c8/dto/ExpResultEntity.java | 42 + .../modules/c8/dto/OperationResultEntity.java | 36 + .../c8/dto/customview/CustomViewConfig.java | 194 +++ .../c8/dto/customview/LambdaParam.java | 16 + .../modules/c8/dto/dep/CentricAttrs.java | 28 + .../modules/c8/dto/dep/DepPath.java | 145 +++ .../c8/dto/dep/DepPathByEntityContext.java | 68 + .../modules/c8/dto/dep/DepPathContext.java | 24 + .../modules/c8/dto/dep/DepPathExp.java | 92 ++ .../modules/c8/dto/dep/DepPathResult0.java | 38 + .../c8/dto/dep/DepPathResultValue.java | 425 ++++++ .../nativeexport/ExtractParameterEntity.java | 23 + .../dto/nativeexport/ExtractResultEntity.java | 24 + .../modules/c8/em/CentricAttributeType.java | 48 + .../modules/c8/em/DepPathEntityType.java | 93 ++ .../modules/c8/em/DepPathType.java | 19 + .../modules/c8/em/EnumTypeForCV.java | 28 + .../modules/c8/em/ParserFieldType.java | 49 + .../enhancement/modules/c8/feign/C8Feign.java | 103 ++ .../modules/c8/feign/C8LoginFeign.java | 42 + .../modules/c8/service/C8LoginService.java | 132 ++ .../modules/c8/service/C8NodeService.java | 450 +++++++ .../modules/c8/service/C8Service.java | 15 + .../service/curd/C8DealExpResultService.java | 128 ++ .../c8/service/curd/C8OperationService.java | 174 +++ .../c8/service/curd/C8SearchService.java | 108 ++ .../customview/ICustomViewHandler.java | 39 + .../customview/ICustomViewParseService.java | 31 + .../impl/DefaultCustomViewHandlerImpl.java | 236 ++++ .../DefaultCustomViewParseServiceImpl.java | 363 ++++++ .../dep/IDepPathEntitySearchService.java | 101 ++ .../DateDepPathEntitySearchServiceImpl.java | 60 + ...DefaultDepPathEntitySearchServiceImpl.java | 57 + .../EnumDepPathEntitySearchServiceImpl.java | 56 + ...numListDepPathEntitySearchServiceImpl.java | 58 + ...oStringDepPathEntitySearchServiceImpl.java | 58 + .../ListDepPathEntitySearchServiceImpl.java | 50 + ...oStringDepPathEntitySearchServiceImpl.java | 51 + .../service/es/LogElasticsearchService.java | 259 ++++ .../c8/util/CentricAttributesUtil.java | 133 ++ .../controller/CustomViewDemoController.java | 62 + .../demo/controller/DDLDemoController.java | 66 + .../controller/DepPathDemoController.java | 151 +++ .../controller/FeignLogDemoController.java | 62 + .../MultipleDataSourceDemoController.java | 33 + .../dto/dep/ColorwayAttributesDemoDTO.java | 18 + .../modules/demo/dto/dep/ColorwayDemoDTO.java | 31 + .../modules/demo/dto/dep/SampleDemoDto.java | 21 + .../demo/dto/dep/StyleAttributesDemoDTO.java | 21 + .../modules/demo/dto/dep/StyleDemoDTO.java | 48 + .../modules/demo/feign/LocalhostFeign.java | 26 + .../mapper/MultipleDataSourceDemoMapper.java | 21 + .../IMultipleDataSourceDemoService.java | 16 + .../MultipleDataSourceDemoServiceImpl.java | 27 + .../modules/dml/dto/node/NodeHelper.java | 58 + .../dml/dto/node/call/C8CallMethod.java | 111 ++ .../dml/dto/node/change/C8OperationNode.java | 516 ++++++++ .../modules/dml/dto/node/delete/C8Delete.java | 19 + .../modules/dml/dto/node/search/C8Search.java | 157 +++ .../dto/node/search/C8SearchAttribute.java | 112 ++ .../enhancement/modules/dml/util/C8Write.java | 141 ++ .../service/ImagesUploadService.java | 119 ++ .../service/TableConfigService.java | 124 ++ .../service/bo/BOAttributesDCLService.java | 193 +++ .../service/bo/BOAttributesService.java | 20 + .../importAnnotation/BaseImportService.java | 136 ++ .../DealWithImportDataService.java | 361 ++++++ .../DefaultImportService.java | 331 +++++ .../ImportCreateNodeService.java | 193 +++ .../importAnnotation/ImportInterface.java | 15 + .../BaseImportPropertiesService.java | 121 ++ .../ImportPropertiesService.java | 235 ++++ .../TransDataAndAppendXmlService.java | 532 ++++++++ .../DoBeforeOrAfterImportInterface.java | 13 + .../plugin/ItemImportInterface.java | 23 + .../service/log/AsyncLogService.java | 73 ++ .../enhancement/service/log/LogService.java | 17 + .../log/impl/CallBackLogServiceImpl.java | 62 + .../service/log/impl/LogServiceImpl.java | 283 ++++ .../service/lui/FieldPermissionsService.java | 116 ++ .../SpecificationDataSheetService.java | 104 ++ .../enhancement/util/C8Constant.java | 12 + .../util/ExportUtil/AnalysisCell.java | 51 + .../util/ExportUtil/ExportFieldTypeEnum.java | 29 + .../util/ExportUtil/ExportTemplateRegion.java | 49 + .../util/ExportUtil/ExportUtil.java | 498 +++++++ .../util/ExportUtil/ImageModifyHandler.java | 182 +++ .../enhancement/util/ImportUtil.java | 107 ++ .../enhancement/util/UploadtProperties.java | 25 + .../enhancement/util/http/C8HttpUtil.java | 53 + .../main/resources/mapper/ExtractMapper.xml | 51 + integration/pom.xml | 24 + .../dingtalk/DingTalkController.java | 34 + .../controller/flybook/FlyBookController.java | 73 ++ .../controller/wechat/WeChatController.java | 32 + .../integration/dto/param/BaseParamDto.java | 8 + .../integration/dto/wechat/WeChatDataDto.java | 115 ++ .../feign/MultipartSupportConfig.java | 21 + .../FlyBookFeignRequestInterceptor.java | 33 + .../feign/flybook/FlyBookMessageFeign.java | 31 + .../feign/flybook/FlyBookTokenFeign.java | 16 + .../integration/feign/wechat/WeChatFeign.java | 26 + .../flybook/FlyBookAppAccessToken.java | 24 + .../flybook/FlyBookAppAuthenAccessToken.java | 16 + .../integration/flybook/FlyBookAppData.java | 15 + .../flybook/FlyBookBatchMessage.java | 30 + .../integration/flybook/FlyBookMessage.java | 25 + .../flybook/FlyBookResultData.java | 15 + .../service/dingtalk/DingTalkService.java | 118 ++ .../service/flybook/FlyBookService.java | 163 +++ .../service/flybook/FlyBookTokenService.java | 28 + .../service/inter/Initializable.java | 8 + .../integration/service/inter/MsgAble.java | 13 + .../service/wechat/WeChatService.java | 62 + log.sql | 40 + maven-setting.xml | 42 + mybatis/.gitignore | 31 + .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ mybatis/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes mybatis/.mvn/wrapper/maven-wrapper.properties | 2 + mybatis/pom.xml | 95 ++ .../mybatis/config/MybatisPlusConfig.java | 77 ++ .../mybatis/entity/PlmStyleEntity.java | 156 +++ .../mybatis/handler/MyMetaObjectHandler.java | 36 + .../mybatis/mapper/PlmStyleMapper.java | 40 + .../mybatis/provider/PlmStyleSqlProvider.java | 41 + .../mybatis/service/StyleService.java | 7 + .../service/impl/StyleServiceImpl.java | 11 + .../resources/mapper/PlmStyleEntityMapper.xml | 5 + pom.xml | 199 +++ rabbitmq/.gitignore | 31 + .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ rabbitmq/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + ...delayed_message_exchange-20171215-3.6.x.ez | Bin 0 -> 31352 bytes ...rabbitmq_delayed_message_exchange-3.8.0.ez | Bin 0 -> 43377 bytes rabbitmq/pom.xml | 62 + .../rabbitmq/config/RabbitMqConfig.java | 95 ++ .../rabbitmq/handler/DelayQueueHandler.java | 44 + .../handler/DirectQueueOneHandler.java | 52 + .../rabbitmq/message/MessageStruct.java | 23 + readme.md | 29 + redis/.gitignore | 31 + .../.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ redis/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes redis/.mvn/wrapper/maven-wrapper.properties | 2 + redis/pom.xml | 56 + .../redis/component/RedisExecutor.java | 197 +++ .../redis/config/RedisConfig.java | 70 + sso/.gitignore | 31 + sso/pom.xml | 37 + .../sso/controller/SamlSSOController.java | 64 + .../sso/controller/TestSAMLController.java | 33 + .../dto/saml2/sp/AuthorizationRequestDto.java | 46 + .../sso/dto/saml2/sp/C8MetaDataDto.java | 94 ++ .../sso/dto/saml2/sp/SPMetaDataDto.java | 103 ++ .../saml2/common/AssertionService.java | 9 + .../sso/service/saml2/common/SAMLService.java | 44 + .../service/saml2/common/SAMLSignature.java | 14 + .../common/impl/AssertionServiceImpl.java | 63 + .../saml2/common/impl/SAMLServiceImpl.java | 181 +++ .../saml2/common/impl/SAMLSignatureImpl.java | 90 ++ .../service/saml2/xml/RequestSAMLService.java | 14 + .../xml/impl/RequestSAMLServiceImpl.java | 203 +++ .../security/AuthenticationService.java | 20 + .../impl/AuthenticationServiceImpl.java | 27 + sso/src/main/resources/SPKeystore.jks | Bin 0 -> 2255 bytes sso/src/main/resources/application-sso.yml | 37 + start.bat | 5 + stop.bat | 4 + task/.gitignore | 31 + task/.mvn/wrapper/MavenWrapperDownloader.java | 118 ++ task/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes task/.mvn/wrapper/maven-wrapper.properties | 2 + task/pom.xml | 77 ++ .../task/config/TaskConfig.java | 38 + .../com/centricsoftware/task/job/TaskJob.java | 46 + .../task/job/inter/CategoryJob.java | 18 + 353 files changed, 30871 insertions(+) create mode 100644 .gitignore create mode 100644 LOG_HOME_IS_UNDEFINED/PROJECT_NAME_IS_UNDEFINED-task.log create mode 100644 LOG_HOME_IS_UNDEFINED/PROJECT_NAME_IS_UNDEFINED.log create mode 100644 commons/.gitignore create mode 100644 commons/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 commons/.mvn/wrapper/maven-wrapper.jar create mode 100644 commons/.mvn/wrapper/maven-wrapper.properties create mode 100644 commons/pom.xml create mode 100644 commons/src/main/java/com/centricsoftware/commons/ant/ControllerLog.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/ant/ServiceLog.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/dto/PlmPageReqVo.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/dto/PlmUtilsException.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/dto/ResEntity.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/dto/WebResponse.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumList.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumValue.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/em/C8ImportTypeEnum.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/em/C8NativeExportType.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/em/ResCode.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/exception/BaseException.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/exception/C8UnsupportedEncodingException.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/exception/ParamException.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/exception/RequestArgsException.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/CSVParser.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/CommonUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/EncodeUtils.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/ExcelUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/ExportExcel.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/FileUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/FtpUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/ImageUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/ImportExcel.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/IpUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/JsonHelper.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/JsonUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/MailUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/MarkImageUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/PageUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/SMBFileUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/ServicesUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/SpringContextHolder.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/SpringUtil.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/StringUtils.java create mode 100644 commons/src/main/java/com/centricsoftware/commons/utils/ZIPUtil.java create mode 100644 commons/src/main/resources/license.xml create mode 100644 config/.gitignore create mode 100644 config/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 config/.mvn/wrapper/maven-wrapper.jar create mode 100644 config/.mvn/wrapper/maven-wrapper.properties create mode 100644 config/pom.xml create mode 100644 config/src/main/java/com/centricsoftware/config/config/ExecutorConfig.java create mode 100644 config/src/main/java/com/centricsoftware/config/cons/Constants.java create mode 100644 config/src/main/java/com/centricsoftware/config/entity/CenterProperties.java create mode 100644 config/src/main/java/com/centricsoftware/config/entity/CsProperties.java create mode 100644 config/src/main/java/com/centricsoftware/config/entity/EsProperties.java create mode 100644 config/src/main/resources/application-common.yml create mode 100644 config/src/main/resources/application-es.yml create mode 100644 config/src/main/resources/application-import.yml create mode 100644 config/src/main/resources/application-integration.yml create mode 100644 config/src/main/resources/application-mybatis.yml create mode 100644 config/src/main/resources/application-plm.yml create mode 100644 config/src/main/resources/application-pro.yml create mode 100644 config/src/main/resources/application-rabbitmq.yml create mode 100644 config/src/main/resources/application-redis.yml create mode 100644 config/src/main/resources/logback-spring.xml create mode 100644 core/.gitignore create mode 100644 core/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 core/.mvn/wrapper/maven-wrapper.jar create mode 100644 core/.mvn/wrapper/maven-wrapper.properties create mode 100644 core/pom.xml create mode 100644 core/src/main/java/com/centricsoftware/core/CoreApplication.java create mode 100644 core/src/main/java/com/centricsoftware/core/aspect/AopAspect.java create mode 100644 core/src/main/java/com/centricsoftware/core/aspect/BaseAspect.java create mode 100644 core/src/main/java/com/centricsoftware/core/config/BootConfig.java create mode 100644 core/src/main/java/com/centricsoftware/core/config/WebMvcConfig.java create mode 100644 core/src/main/java/com/centricsoftware/core/controller/TestController.java create mode 100644 core/src/main/java/com/centricsoftware/core/dto/BaseDto.java create mode 100644 core/src/main/java/com/centricsoftware/core/dto/CommonExportDto.java create mode 100644 core/src/main/java/com/centricsoftware/core/dto/DataPackage.java create mode 100644 core/src/main/java/com/centricsoftware/core/handler/GlobalExceptionHandler.java create mode 100644 core/src/main/java/com/centricsoftware/core/service/CommonExportService.java create mode 100644 core/src/main/java/com/centricsoftware/core/task/config/TaskConfig.java create mode 100644 core/src/main/java/com/centricsoftware/task/job/inter/DeleteLogTask.java create mode 100644 core/src/main/resources/application.yml create mode 100644 core/src/main/resources/banner.txt create mode 100644 core/src/main/resources/bootstrap.yml create mode 100644 core/src/main/resources/jboss-deployment-structure.xml create mode 100644 core/src/main/resources/template/export1.xlsx create mode 100644 core/src/main/resources/template/export2.xlsx create mode 100644 core/src/main/resources/template/export3.xlsx create mode 100644 core/src/main/resources/template/export4.xlsx create mode 100644 core/src/main/resources/template/export5.xlsx create mode 100644 core/src/main/webapp/index.jsp create mode 100644 core/src/test/java/com/centricsoftware/core/model/TimeSeries.java create mode 100644 core/src/test/java/com/centricsoftware/core/service/ElasticsearchClientTest.java create mode 100644 core/src/test/postman/Test.http create mode 100644 core/src/test/postman/http-client.env.json create mode 100644 enhancement/.gitignore create mode 100644 enhancement/pom.xml create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Column.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8ColumnConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Entity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8EntityConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8NativeExport.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/ExcelAlias.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridColumn.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridForm.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/component/CacheComponent.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/component/ExcelAliasHelper.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/component/RedisUtils.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/component/StandaloneLockComponent.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/component/c8/NativeExportComponent.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/ImagesUploadController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/ImportPropertiesController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/C8TestController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/FiledPermissionsController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogForRelationshipDBController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/controller/specification/SpecificationDataSheetController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/AnnoImportLineError.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/PlmReqDto.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/UploadImagesEntity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/OkHttpCache.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/ThreadLocalCache.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/excel/ExcelAroundAOP.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogDto.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogReqDto.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/LogDto.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/PlmLog.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/QueryPlmLogReq.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridOptions.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponse.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponseData.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnBaseConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ExportConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FilterConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormItemConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FromItemRender.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PagerConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PrintConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RightBtnConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RowConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/SortConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolbarConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolsConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/es/config/EsAutoConfigure.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/filter/AddCookieFilter.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/filter/CachingRequestBodyFilter.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/mapper/ExtractMapper.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/mapper/LogMapper.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/ant/DepPathField.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/EnumCache.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResult.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultCache.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultForFeign.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathCache.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathParser.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathResult.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/IDepPathSearchChain.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/impl/DefaultDepPathSearchChainImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/ICentricAbstractFactory.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/IDepPathFactory.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/INodeServiceFactory.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultDepPathFactory.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultICentricAbstractFactory.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultINodeServiceFactory.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResult.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResultCache.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/CentricResultCacheImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/DefaultICentricResultImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/FeignConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/okhttp/CookieJarHandler.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/C8LogReturnEntity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/ExpResultEntity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/OperationResultEntity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/CustomViewConfig.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/LambdaParam.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/CentricAttrs.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPath.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathByEntityContext.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathContext.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathExp.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResult0.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResultValue.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractParameterEntity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractResultEntity.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/CentricAttributeType.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathEntityType.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathType.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/EnumTypeForCV.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/ParserFieldType.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8Feign.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8LoginFeign.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8LoginService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8NodeService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8Service.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8DealExpResultService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8OperationService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8SearchService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewHandler.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewParseService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewHandlerImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewParseServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/IDepPathEntitySearchService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DateDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DefaultDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListToStringDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListToStringDepPathEntitySearchServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/es/LogElasticsearchService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/util/CentricAttributesUtil.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/CustomViewDemoController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DDLDemoController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DepPathDemoController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/FeignLogDemoController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/MultipleDataSourceDemoController.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayAttributesDemoDTO.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayDemoDTO.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/SampleDemoDto.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleAttributesDemoDTO.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleDemoDTO.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/feign/LocalhostFeign.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/mapper/MultipleDataSourceDemoMapper.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/IMultipleDataSourceDemoService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/impl/MultipleDataSourceDemoServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/NodeHelper.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/call/C8CallMethod.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/change/C8OperationNode.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/delete/C8Delete.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8Search.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8SearchAttribute.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/util/C8Write.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/ImagesUploadService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/TableConfigService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesDCLService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/BaseImportService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DealWithImportDataService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DefaultImportService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportCreateNodeService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportInterface.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/BaseImportPropertiesService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/ImportPropertiesService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/TransDataAndAppendXmlService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/DoBeforeOrAfterImportInterface.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/ItemImportInterface.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/log/AsyncLogService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/log/LogService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/CallBackLogServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/LogServiceImpl.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/lui/FieldPermissionsService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/service/specification/SpecificationDataSheetService.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/C8Constant.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/AnalysisCell.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportFieldTypeEnum.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportTemplateRegion.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportUtil.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ImageModifyHandler.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/ImportUtil.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/UploadtProperties.java create mode 100644 enhancement/src/main/java/com/centricsoftware/enhancement/util/http/C8HttpUtil.java create mode 100644 enhancement/src/main/resources/mapper/ExtractMapper.xml create mode 100644 integration/pom.xml create mode 100644 integration/src/main/java/com/centricsoftware/integration/controller/dingtalk/DingTalkController.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/controller/flybook/FlyBookController.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/controller/wechat/WeChatController.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/dto/param/BaseParamDto.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/dto/wechat/WeChatDataDto.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/feign/MultipartSupportConfig.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookFeignRequestInterceptor.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookMessageFeign.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookTokenFeign.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/feign/wechat/WeChatFeign.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAccessToken.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAuthenAccessToken.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppData.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookBatchMessage.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookMessage.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookResultData.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/service/dingtalk/DingTalkService.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookService.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookTokenService.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/service/inter/Initializable.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/service/inter/MsgAble.java create mode 100644 integration/src/main/java/com/centricsoftware/integration/service/wechat/WeChatService.java create mode 100644 log.sql create mode 100644 maven-setting.xml create mode 100644 mybatis/.gitignore create mode 100644 mybatis/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 mybatis/.mvn/wrapper/maven-wrapper.jar create mode 100644 mybatis/.mvn/wrapper/maven-wrapper.properties create mode 100644 mybatis/pom.xml create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/config/MybatisPlusConfig.java create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/entity/PlmStyleEntity.java create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/handler/MyMetaObjectHandler.java create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/mapper/PlmStyleMapper.java create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/provider/PlmStyleSqlProvider.java create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/service/StyleService.java create mode 100644 mybatis/src/main/java/com/centricsoftware/mybatis/service/impl/StyleServiceImpl.java create mode 100644 mybatis/src/main/resources/mapper/PlmStyleEntityMapper.xml create mode 100644 pom.xml create mode 100644 rabbitmq/.gitignore create mode 100644 rabbitmq/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 rabbitmq/.mvn/wrapper/maven-wrapper.jar create mode 100644 rabbitmq/.mvn/wrapper/maven-wrapper.properties create mode 100644 rabbitmq/plugins/rabbitmq_delayed_message_exchange-20171215-3.6.x.ez create mode 100644 rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.8.0.ez create mode 100644 rabbitmq/pom.xml create mode 100644 rabbitmq/src/main/java/com/centricsoftware/rabbitmq/config/RabbitMqConfig.java create mode 100644 rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DelayQueueHandler.java create mode 100644 rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DirectQueueOneHandler.java create mode 100644 rabbitmq/src/main/java/com/centricsoftware/rabbitmq/message/MessageStruct.java create mode 100644 readme.md create mode 100644 redis/.gitignore create mode 100644 redis/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 redis/.mvn/wrapper/maven-wrapper.jar create mode 100644 redis/.mvn/wrapper/maven-wrapper.properties create mode 100644 redis/pom.xml create mode 100644 redis/src/main/java/com/centricsoftware/redis/component/RedisExecutor.java create mode 100644 redis/src/main/java/com/centricsoftware/redis/config/RedisConfig.java create mode 100644 sso/.gitignore create mode 100644 sso/pom.xml create mode 100644 sso/src/main/java/com/centricsoftware/sso/controller/SamlSSOController.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/controller/TestSAMLController.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/AuthorizationRequestDto.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/C8MetaDataDto.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/SPMetaDataDto.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/common/AssertionService.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLService.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLSignature.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/AssertionServiceImpl.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLServiceImpl.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLSignatureImpl.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/RequestSAMLService.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/impl/RequestSAMLServiceImpl.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/security/AuthenticationService.java create mode 100644 sso/src/main/java/com/centricsoftware/sso/service/security/impl/AuthenticationServiceImpl.java create mode 100644 sso/src/main/resources/SPKeystore.jks create mode 100644 sso/src/main/resources/application-sso.yml create mode 100644 start.bat create mode 100644 stop.bat create mode 100644 task/.gitignore create mode 100644 task/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 task/.mvn/wrapper/maven-wrapper.jar create mode 100644 task/.mvn/wrapper/maven-wrapper.properties create mode 100644 task/pom.xml create mode 100644 task/src/main/java/com/centricsoftware/task/config/TaskConfig.java create mode 100644 task/src/main/java/com/centricsoftware/task/job/TaskJob.java create mode 100644 task/src/main/java/com/centricsoftware/task/job/inter/CategoryJob.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/LOG_HOME_IS_UNDEFINED/PROJECT_NAME_IS_UNDEFINED-task.log b/LOG_HOME_IS_UNDEFINED/PROJECT_NAME_IS_UNDEFINED-task.log new file mode 100644 index 0000000..e69de29 diff --git a/LOG_HOME_IS_UNDEFINED/PROJECT_NAME_IS_UNDEFINED.log b/LOG_HOME_IS_UNDEFINED/PROJECT_NAME_IS_UNDEFINED.log new file mode 100644 index 0000000..e69de29 diff --git a/commons/.gitignore b/commons/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/commons/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/commons/.mvn/wrapper/MavenWrapperDownloader.java b/commons/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/commons/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/commons/.mvn/wrapper/maven-wrapper.jar b/commons/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/commons/.mvn/wrapper/maven-wrapper.properties b/commons/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/commons/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/commons/pom.xml b/commons/pom.xml new file mode 100644 index 0000000..5783f05 --- /dev/null +++ b/commons/pom.xml @@ -0,0 +1,286 @@ + + + 4.0.0 + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + commons + 2.1 + jar + Demo project for Spring Boot + + + 1.8 + 4.1 + 7.0.1 + 2.6.12 + 1.0.3 + + + 4.1.2 + 3.6 + 1.4.7 + 2.0.0 + 1.3.17 + 1.56 + 2.0 + 1.1.1 + 5.5.13 + 5.2.0 + 8.5.2 + 3.4.0 + 1.3.8 + 3.3.3 + 2.4.0 + + + + + + org.springframework.data + spring-data-elasticsearch + + + org.apache.commons + commons-lang3 + + + + org.springframework.boot + spring-boot-starter + provided + + + + org.springframework.boot + spring-boot-starter-web + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + commons-io + commons-io + 2.6 + + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + + + + org.apache.velocity + velocity-engine-core + ${velocity-engine-core.version} + + + + javax.ws.rs + jsr311-api + ${jsr311-api.version} + + + com.centricsoftware + config + + + com.centricsoftware + mybatis + + + org.projectlombok + lombok + true + + + + org.antlr + ST4 + ${st4.version} + + + + + + + com.oracle + ojdbc6 + + + + net.sourceforge.jexcelapi + jxl + ${jxl.version} + + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + net.sf.jxls + jxls-core + ${jxl.core.version} + + + + commons-net + commons-net + ${commons-net.version} + + + + org.codehaus.jettison + jettison + ${jettson.version} + + + + javax.mail + mail + ${mail.version} + + + + jcifs + jcifs + ${jcifs.version} + + + javax.servlet + servlet-api + + + + + + com.itextpdf + itextpdf + ${itextpdf.version} + + + + com.itextpdf + itext-asian + ${itext-asian.version} + + + com.alibaba + easyexcel + ${easyexcel.version} + + + com.jayway.jsonpath + json-path + ${json-path.version} + + + + + + + + + + com.microsoft.sqlserver + mssql-jdbc + + + + com.google.zxing + core + ${zxing.core.version} + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + 2.0.0.RELEASE + + + io.github.openfeign + feign-core + + + + + io.github.openfeign + feign-core + 10.1.0 + + + io.github.openfeign + feign-okhttp + 10.1.0 + + + commons-fileupload + commons-fileupload + 1.3.3 + + + io.github.openfeign.form + feign-form + 3.8.0 + + + io.github.openfeign.form + feign-form-spring + 3.8.0 + + + + + org.apache.httpcomponents + httpcore + 4.4.9 + + + + org.springframework + spring-test + 5.2.9.RELEASE + + + + + + + AsposeJavaAPI + Aspose Java API + https://repository.aspose.com/repo/ + + + + plmservice-commons + + + src/main/resources + + license.xml + + true + + + + + diff --git a/commons/src/main/java/com/centricsoftware/commons/ant/ControllerLog.java b/commons/src/main/java/com/centricsoftware/commons/ant/ControllerLog.java new file mode 100644 index 0000000..196eed6 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/ant/ControllerLog.java @@ -0,0 +1,40 @@ +package com.centricsoftware.commons.ant; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +/** + * 自定义注解,拦截Controller + * @author ZhengGong + * @date 2019/6/25 + */ +@Target({ElementType.TYPE,ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface ControllerLog { + String value() default ""; + + /** + * 是否记录响应信息 + * @author liaochangjiang + * @since 2024-05-07 17:11 + */ + boolean response() default false; + + /** + * 是否接口日志 + */ + boolean interfaceLog() default false; + + /** + * 请求报文中key的字段,支持getByPath + */ + String requestId() default ""; + + /** + * 响应报文中成功的字段,支持getByPath + */ + String responseId() default ""; +} diff --git a/commons/src/main/java/com/centricsoftware/commons/ant/ServiceLog.java b/commons/src/main/java/com/centricsoftware/commons/ant/ServiceLog.java new file mode 100644 index 0000000..dea5a98 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/ant/ServiceLog.java @@ -0,0 +1,15 @@ +package com.centricsoftware.commons.ant; + +import java.lang.annotation.*; + +/** + * 自定义注解,拦截service + * @author ZhengGong + * @date 2019/6/25 + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ServiceLog { + String value() default ""; +} diff --git a/commons/src/main/java/com/centricsoftware/commons/dto/PlmPageReqVo.java b/commons/src/main/java/com/centricsoftware/commons/dto/PlmPageReqVo.java new file mode 100644 index 0000000..f171193 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/dto/PlmPageReqVo.java @@ -0,0 +1,13 @@ +package com.centricsoftware.commons.dto; + +import lombok.Data; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Data +@ToString(callSuper = true) +@Accessors(chain = true) +public class PlmPageReqVo { + private Long pageNum = 1L; + private Long pageSize = 10L; +} diff --git a/commons/src/main/java/com/centricsoftware/commons/dto/PlmUtilsException.java b/commons/src/main/java/com/centricsoftware/commons/dto/PlmUtilsException.java new file mode 100644 index 0000000..8c4af1d --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/dto/PlmUtilsException.java @@ -0,0 +1,35 @@ +package com.centricsoftware.commons.dto; + +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; + +/** + * @Description: PLM工具类异常基类 + * @Author: ZhengGong + * @CreateDate: 2019/5/20 15:11 + * @Company: Centric + */ +public class PlmUtilsException extends BaseException { + + public PlmUtilsException(ResCode code) { + super(code); + } + + public PlmUtilsException(ResCode code, Object data) { + super(code,data); + } + + public PlmUtilsException(Integer code, String message) { + super(code,message); + } + + public PlmUtilsException(Integer code, String message, Object data) { + super(code,message,data); + } + + public PlmUtilsException(ResCode code, Throwable e){ + super(code); + } + + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/dto/ResEntity.java b/commons/src/main/java/com/centricsoftware/commons/dto/ResEntity.java new file mode 100644 index 0000000..62d70f8 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/dto/ResEntity.java @@ -0,0 +1,37 @@ +package com.centricsoftware.commons.dto; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 返回信息实体类 + * @author ZhengGong + * @date 2020/4/16 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ResEntity implements Serializable { + /** + * 错误编号 + */ + private Integer code; + /** + * 错误信息 + */ + private String msg; + /** + * 返回对象 + */ + private Object data; + /** + * 是否成功 + */ + private boolean success; +} diff --git a/commons/src/main/java/com/centricsoftware/commons/dto/WebResponse.java b/commons/src/main/java/com/centricsoftware/commons/dto/WebResponse.java new file mode 100644 index 0000000..857bff9 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/dto/WebResponse.java @@ -0,0 +1,132 @@ +package com.centricsoftware.commons.dto; + + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; +import lombok.extern.slf4j.Slf4j; +import org.stringtemplate.v4.ST; + +import java.net.URLEncoder; + +/** + * 接口返回信息 + * @author ZhengGong + * @date 2019/9/16 + */ +@Slf4j +public class WebResponse { + + /** + * 失败返回特定的消息实体 + * @param code 错误代码 + * @param message 错误信息 + * @param data 具体消息实体 + * @return 消息实体ResEntity + */ + public static ResEntity failure(Integer code, String message, Object data) { + return ResEntity.builder().code(code).msg(message).data(data).success(false).build(); + } + + /** + * 失败返回特定的消息实体 + * @param code 错误代码 + * @param message 错误信息 + * @return 消息实体ResEntity + */ + public static ResEntity failure(Integer code, String message) { + return ResEntity.builder().code(code).msg(message).success(false).build(); + } + + /** + * 失败返回特定的消息实体 + * @param respCode 错误代码封装枚举类 + * @param data 具体消息实体 + * @return 消息实体ResEntity + */ + public static ResEntity failure(ResCode respCode, Object data) { + return getStringObjectMap(respCode, data,false); + } + + /** + * 失败返回特定的消息实体 + * @param respCode 错误代码封装枚举类 + * @return 消息实体ResEntity + */ + public static ResEntity failure(ResCode respCode) { + return getStringObjectMap(respCode,false); + } + + /** + * 失败返回特定的消息实体 + * @param e 错误基类 + * @return 消息实体ResEntity + */ + public static ResEntity failure(T e){ + return failure(e.getCode(),e.getMessage(),e.getData()); + } + + + + /** + * 错误302到具体的页面 + * @param stp 模板链接 + * @param msg 错误信息 + * @return 页面链接 + */ + public static String failurePage(String stp,String msg) { + try { + ST st=new ST(stp); + st.add("ERROR", URLEncoder.encode(msg,"UTF-8")); + return "redirect:"+st.render(); + } catch (Exception e) { + log.error("error:",e); + return null; + } + } + + + /** + * 成功返回特定的状态码和信息 + * @param respCode 成功代码封装枚举类 + * @param data 具体消息实体 + * @return 消息实体ResEntity + */ + public static ResEntity success(ResCode respCode, Object data) { + return getStringObjectMap(respCode, data,true); + } + + private static ResEntity getStringObjectMap(ResCode respCode, Object data, Boolean success) { + return ResEntity.builder().code( respCode.getCode()).msg( respCode.getMessage()).data(data).success(success).build(); + } + + /** + * 成功返回特定的状态码和信息 + * @param respCode 成功代码封装枚举类 + * @return 消息实体ResEntity + */ + public static ResEntity success(ResCode respCode) { + return getStringObjectMap(respCode,true); + } + + private static ResEntity getStringObjectMap(ResCode respCode, Boolean success) { + return ResEntity.builder().code(respCode.getCode()).msg(respCode.getMessage()).success(success).build(); + } + + public static ResEntity failure(String message){ + return ResEntity.builder().code(ResCode.ERROR.getCode()).msg(message).success(false).build(); + } + public static ResEntity success(String message){ + return ResEntity.builder().code(ResCode.SUCCESS.getCode()).msg(message).success(true).build(); + } + + public static ResEntity autoResponse(Object obj,String error) { + if(StrUtil.isBlank(error)){ + return ResEntity.builder().code(ResCode.SUCCESS.getCode()).msg(ResCode.SUCCESS.getMessage()).success(true).data(obj).build(); + }else{ + return ResEntity.builder().code(ResCode.ERROR.getCode()).msg(error).success(false).data(obj).build(); + } + + } + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumList.java b/commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumList.java new file mode 100644 index 0000000..9b321d3 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumList.java @@ -0,0 +1,31 @@ +package com.centricsoftware.commons.dto.enum4cache; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Data +public class EnumList { + + private String dependsOn; + + private String description; + + private List enums; + + private String nodeName; + + private List values; + + private List enumValues; + + private Map descCache;//描述:fullname 和 fullname:描述 + + private Map displayEnCache;//翻译值:fullname + + private Map displayZhCache;//翻译值:fullname + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumValue.java b/commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumValue.java new file mode 100644 index 0000000..1d50c7d --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/dto/enum4cache/EnumValue.java @@ -0,0 +1,20 @@ +package com.centricsoftware.commons.dto.enum4cache; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +public class EnumValue { + + private boolean active; + + private String dependsOn; + + private String description; + + private String nodeName; + + private String value; + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/em/C8ImportTypeEnum.java b/commons/src/main/java/com/centricsoftware/commons/em/C8ImportTypeEnum.java new file mode 100644 index 0000000..04d136a --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/em/C8ImportTypeEnum.java @@ -0,0 +1,14 @@ +package com.centricsoftware.commons.em; + +public enum C8ImportTypeEnum { + REF, + STRING, + BOOLEAN, + ENUM, + ENUM_DESC, + ENUM_DISPLAY, + TIME, + INTEGER, + DOUBLE, + REFLIST +} \ No newline at end of file diff --git a/commons/src/main/java/com/centricsoftware/commons/em/C8NativeExportType.java b/commons/src/main/java/com/centricsoftware/commons/em/C8NativeExportType.java new file mode 100644 index 0000000..b32cf5a --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/em/C8NativeExportType.java @@ -0,0 +1,5 @@ +package com.centricsoftware.commons.em; + +public enum C8NativeExportType { + STRING, DOUBLE, INTEGER, BOOLEAN, URL, URL_ID, ENUM, ENUM_KEY, ENUM_DESC, ENUM_DISPLAY, TIME, LONG +} diff --git a/commons/src/main/java/com/centricsoftware/commons/em/ResCode.java b/commons/src/main/java/com/centricsoftware/commons/em/ResCode.java new file mode 100644 index 0000000..21e78bf --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/em/ResCode.java @@ -0,0 +1,67 @@ +package com.centricsoftware.commons.em; + +import lombok.Getter; + +/** + * 错误码枚举类 + * + * @author ZhengGong + * @date 2019/6/12 + */ +@Getter +public enum ResCode { + + SUCCESS(0, "成功!"), + OK(200, "OK"), + ERROR(201, "失败"), + REQUEST_NOT_FOUND(202, "请求不存在!"), + HTTP_BAD_METHOD(203, "请求方式不支持!"), + BAD_REQUEST(204, "请求异常!"), + PARAM_NOT_MATCH(205, "参数不匹配!"), + PARAM_NOT_NULL(206, "参数不能为空!"), + JSON_PARSE_ERROR(207, "JSON转换异常"), + C8_UNSUPPORTED_ENCODING_ERROR(208, "双编码异常"), + AUTHORIZE_ERROR(209, "请求未授权!"), + AUTHORIZE_UP_ERROR(210, "请求未授权,账号或密码错误!"), + CONFIG_NOT_INIT(666, "配置未初始化!"), + + /** + * 业务类 + */ + DMP_DATA_NULL(3001, "找不到对应的数据,或者数据已经同步成功"), + DMP_DATA_TRANSLATE_FAIL(3002, "调用DMP同步数据失败,请重试"), + DMP_FILE_URL_ILLEGAL(3003, "获取到的DMP文件路径不合法"), + DMP_FILE_PUBLISH_ERROR(3004, "DMP发布文件失败"), + SKU_CODE_NOTFOUND(3005, "SKU CODE没有找到相关的SKU数据"), + SKU_CODE_DUPLICATE(3006, "SKU CODE找出重复的数据,请联系管理员"), + SKU_CODE_EMPTY(3007, "SKU CODE为空"), + + + RECORD_LOCK(888, "数据正在执行更新,已被锁定,请稍后再试"), + SYSTEM_RUNTIME_ERROR(999, "系统异常!"); + + private Integer code; + + private String message; + + ResCode(Integer code, String message) { + this.code = code; + this.message = message; + } + + /** + * 通过code返回枚举 + * + * @param code + * @return + */ + public static ResCode parse(Integer code) { + ResCode[] values = values(); + for (ResCode value : values) { + if (value.getCode().equals(code)) { + return value; + } + } + throw new RuntimeException("Unknown code of ResultEnum"); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/exception/BaseException.java b/commons/src/main/java/com/centricsoftware/commons/exception/BaseException.java new file mode 100644 index 0000000..48318ec --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/exception/BaseException.java @@ -0,0 +1,52 @@ +package com.centricsoftware.commons.exception; + +import com.centricsoftware.commons.em.ResCode; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 异常基类 + * @author ZhengGong + * @date 2019/6/18 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class BaseException extends RuntimeException { + private Integer code; + private String message; + private Object data; + + public BaseException(ResCode resCode) { + super(resCode.getMessage()); + this.code = resCode.getCode(); + this.message = resCode.getMessage(); + } + + public BaseException(ResCode resCode, Object data) { + this(resCode); + this.data = data; + } + + public BaseException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public BaseException(Integer code, String message, Object data) { + this(code, message); + this.data = data; + } + + public BaseException(ResCode code, Throwable e){ + this(code); + data = e; + } + + public ResCode getResCode(){ + return ResCode.parse(code); + } + + + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/exception/C8UnsupportedEncodingException.java b/commons/src/main/java/com/centricsoftware/commons/exception/C8UnsupportedEncodingException.java new file mode 100644 index 0000000..f692844 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/exception/C8UnsupportedEncodingException.java @@ -0,0 +1,30 @@ +package com.centricsoftware.commons.exception; + +import com.centricsoftware.commons.em.ResCode; + +/** + * c8 uri双编码异常 + * @author zheng.gong + * @date 2020/5/12 + */ +public class C8UnsupportedEncodingException extends BaseException { + public C8UnsupportedEncodingException(ResCode code) { + super(code); + } + + public C8UnsupportedEncodingException(ResCode code, Object data) { + super(code,data); + } + + public C8UnsupportedEncodingException(Integer code, String message) { + super(code,message); + } + + public C8UnsupportedEncodingException(Integer code, String message, Object data) { + super(code,message,data); + } + + public C8UnsupportedEncodingException(ResCode code, Throwable e){ + super(code); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/exception/ParamException.java b/commons/src/main/java/com/centricsoftware/commons/exception/ParamException.java new file mode 100644 index 0000000..5adcb8c --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/exception/ParamException.java @@ -0,0 +1,30 @@ +package com.centricsoftware.commons.exception; + +import com.centricsoftware.commons.em.ResCode; + +/** + * 参数异常 + * @author ZhengGong + * @date 2019/9/16 + */ +public class ParamException extends BaseException { + public ParamException(ResCode code) { + super(code); + } + + public ParamException(ResCode code, Object data) { + super(code,data); + } + + public ParamException(Integer code, String message) { + super(code,message); + } + + public ParamException(Integer code, String message, Object data) { + super(code,message,data); + } + + public ParamException(ResCode code, Throwable e){ + super(code); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/exception/RequestArgsException.java b/commons/src/main/java/com/centricsoftware/commons/exception/RequestArgsException.java new file mode 100644 index 0000000..5fa9503 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/exception/RequestArgsException.java @@ -0,0 +1,31 @@ +package com.centricsoftware.commons.exception; + +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; + +/** + * 请求参数异常 + * @author ZhengGong + * @date 2019/9/16 + */ +public class RequestArgsException extends BaseException { + public RequestArgsException(ResCode resCode) { + super(resCode); + } + + public RequestArgsException(ResCode resCode, Object data) { + super(resCode, data); + } + + public RequestArgsException(Integer code, String message) { + super(code, message); + } + + public RequestArgsException(Integer code, String message, Object data) { + super(code, message, data); + } + + public RequestArgsException(ResCode code, Throwable e) { + super(code, e); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/CSVParser.java b/commons/src/main/java/com/centricsoftware/commons/utils/CSVParser.java new file mode 100644 index 0000000..b71530f --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/CSVParser.java @@ -0,0 +1,91 @@ +package com.centricsoftware.commons.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +/** + * csv处理工具类 + * 同类方法 {@link cn.hutool.core.text.csv.CsvUtil} + * @author zheng.gong + * @date 2020/4/27 + */ +public class CSVParser { + + public List parse(File file) throws Exception { + List result = new ArrayList(); + BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); + try { + String line = null; + while ((line = in.readLine()) != null) { + result.add(parseLine(line)); + } + } finally { + if (in != null) { + in.close(); + } + } + + return result; + } + + enum State { + Normal, QuoteBegin, Quote, QuoteEnd, + } + + public String[] parseLine(String line) { + ArrayList list = new ArrayList(); + State state = State.Normal; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < line.length(); ++i) { + char ch = line.charAt(i); + switch (state) { + case Normal: + if (ch == ',') { + list.add(sb.toString()); + sb.setLength(0); + } else if (ch == '"') { + state = State.QuoteBegin; + } else { + sb.append(ch); + } + break; + case QuoteBegin: + if (ch == '"') { + state = State.Normal; + sb.append('"'); + } else { + state = State.Quote; + sb.append(ch); + } + break; + case Quote: + if (ch == '"') { + state = State.QuoteEnd; + } else { + sb.append(ch); + } + break; + case QuoteEnd: + if (ch == ',') { + state = State.Normal; + list.add(sb.toString()); + sb.setLength(0); + } else if (ch == '"') { + state = State.Quote; + sb.append('"'); + } else { + state = State.Normal; + sb.append(ch); + } + break; + } + } + list.add(sb.toString()); + return list.toArray(new String[list.size()]); + } + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/CommonUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/CommonUtil.java new file mode 100644 index 0000000..0bb9ef7 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/CommonUtil.java @@ -0,0 +1,602 @@ +package com.centricsoftware.commons.utils; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.*; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; + +@Slf4j +public class CommonUtil { + + public static final String DAY_FORMAT = "yyyy-MM-dd"; + public static final String DAY_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final String YMD = "yyyyMMdd"; + public static final String YYMD = "yyMMdd"; + public static final String YYMD_HMS = "yyMMddHHmmss"; + public static final String YMD_DASH = "yyyy-MM-dd"; + public static final String YMD_SLASH = "yyyy/MM/dd"; + public static final String YMD_HMS = "yyyyMMddHHmmss"; + public static final String YMD_HMSS = "yyyyMMddHHmmssSSS"; + public static final String YMD_HMS_DASH = "yyyy-MM-dd HH:mm:ss"; + public static final String HMS_DASH = "HH:mm:ss"; + + + /** + * 转换timestamp + * + * @param attrvalue + * @param attrformat + * @param CLASSNAME + * @return + * @author GHUANG + * @version 2019年6月10日 上午11:56:16 + */ + public static String parseTimestamp(String attrvalue, String attrformat, String CLASSNAME) { + DateFormat format = new SimpleDateFormat(attrformat); + String svalue = ""; + try { + Timestamp ts = new Timestamp(format.parse(attrvalue).getTime()); + long tsvalue = ts.getTime() / 1000; + svalue = String.valueOf(tsvalue); + } catch (Exception e) { + log.error(CLASSNAME, e); + } + return svalue; + } + + public static String getCurrentDate(String format) { + SimpleDateFormat sm = new SimpleDateFormat(format); + return sm.format(new Date()); + } + + public static String createTimeNo() { + SimpleDateFormat fullDateFormat = new SimpleDateFormat("yyyyMMdd"); + fullDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT+8:00")); + String currentTime = fullDateFormat.format(new Date(System.currentTimeMillis())); + return currentTime; + } + + public static String createTime() { + Timestamp d = new Timestamp(System.currentTimeMillis()); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 定义格式 + return df.format(d); + } + + public static String createCurrentTimestamp(String CLASSNAME) { + String svalue = ""; + try { + Timestamp d = new Timestamp(System.currentTimeMillis()); + long tsvalue = d.getTime() / 1000; + svalue = String.valueOf(tsvalue); + } catch (Exception e) { + log.error(CLASSNAME, e); + } + return svalue; + } + + public static HashMap changeStr2Map(String mapStr) { + HashMap map = new HashMap(); + String cms = mapStr.replace("{", "").replace("}", ""); + String[] mapStrs = cms.split(","); + for (String s : mapStrs) { + String[] ms = s.split("="); + System.out.println(s); + if (ms.length == 2) { + map.put(ms[0], ms[1]); + } + + } + return map; + } + + public static String escapeUrl(String value) { + if (value == null) { + return ""; + } + String s = value.replaceAll("&", "&"); + s = s.replaceAll("<", "<"); + s = s.replaceAll(">", ">"); + s = s.replaceAll("\"", """); + s = s.replaceAll("'", "'"); + + return s; + + } + + /** + * 拆分List + * + * @param list + * @param len + * @return + * @author GHUANG + * @version 2019年11月5日 下午3:35:49 + */ + public static List splitList(List list, int len) { + List result = new ArrayList(); + log.info("input list={},core={}",list,len); + if (list == null || list.size() == 0 || len < 1) { + result.add(list); + } else { + + int size = list.size(); + int count = (size + len - 1) / len; + + for (int i = 0; i < count; i++) { + List subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1))); + result.add(subList); + } + } + return result; + } + + /** + * 拆分List + * + * @param map + * @param len + * @return + * @author GHUANG + * @version 2019年11月5日 下午3:35:49 + */ + public static List splitMap(LinkedHashMap> map, int len) { + List result = new ArrayList(); + if (map == null || map.size() == 0 || len < 1) { + result.add(map); + } else { + + int size = map.size(); + int count = (size + len - 1) / len; + for (int i = 0; i < count; i++) { + int fromIndex = i * len; + int toIndex = ((i + 1) * len > size ? size : len * (i + 1)); + LinkedHashMap> newmap = new LinkedHashMap>(); + int j = 0; + for (Entry> entry : map.entrySet()) { + if (j >= fromIndex && j < toIndex) { + newmap.put(entry.getKey(), entry.getValue()); + } + j++; + } + result.add(newmap); + } + } + return result; + } + + public static String escapeSpecUrl(String value) { + if (value == null) { + return ""; + } + String s = value.replaceAll("&", "&"); + s = s.replaceAll("\"", """); + s = s.replaceAll("'", "'"); + + // s = stripBadChar(s); + return s; + + } + + public static String getExtractXML(ArrayList> list, String spec) { + String extractXML = ""; + extractXML += "\n"; + for (int i = 0; i < list.size(); i++) { + HashMap map = list.get(i); + String path = map.get("path"); + if (path.length() > 0) { + path = " Path=\"" + path + "\""; + } else { + path = ""; + } + extractXML += " \n"; + } + if (extractXML.length() > 0) { + extractXML += spec; + } + extractXML += "\n"; + return extractXML; + } + + public static String getQueryXML(String nodetype, ArrayList> list, String notcondition, + String order) { + // TODO Auto-generated method stub + String queryXML = ""; + queryXML += "\n"; + queryXML += " \n"; + queryXML += " \n"; + if (notcondition.length() > 0) { + queryXML += notcondition; + } + queryXML += " \n"; + for (HashMap map : list) { + String path = map.get("path"); + String attrkey = map.get("key"); + String attrvalue = map.get("value"); + String attrtype = map.get("type"); + if (attrtype.equalsIgnoreCase("string")) { + queryXML += " \n"; + } else if (attrtype.equalsIgnoreCase("ref")) { + queryXML += " \n"; + } else if (attrtype.equalsIgnoreCase("double")) { + queryXML += " \n"; + } + } + queryXML += " \n"; + queryXML += " \n"; + queryXML += order; + queryXML += "\n"; + + return queryXML; + } + + public static Locale getLocaleFromLocaleName(String localeName) { + Locale locale = null; + if (!StringUtils.isEmpty(localeName)) { + locale = Locale.forLanguageTag(localeName.replace('_', '-')); + } + return locale; + } + + public static String stripBadChar(String s) { + StringBuilder out = new StringBuilder(s.length() * 6); + byte[] bytes = s.getBytes(); + + for (int j = 0; j < bytes.length; ++j) { + byte b = bytes[j]; + int i = b; + if (i < 0) { + i = 256 + i; + } + if (i > '~') { + String cc = "&#" + i + ";"; + out.append(cc); + } else { + char current = (char) b; + if ((current == 0x9) || + (current == 0xA) || + (current == 0xD) || + ((current >= 0x20) && (current <= 0xD7FF)) || + ((current >= 0xE000) && (current <= 0xFFFD)) || + ((current >= 0x10000) && (current <= 0x10FFFF))) { + out.append(current); + } + } + } + + s = out.toString(); + return s; + + } + + public static String writeFileToDisk(byte[] img, String filePath, String fileName) { + String path = ""; + try { + File folder = new File(filePath); + if (!folder.exists()) { + folder.mkdirs(); + } + path = filePath + fileName; + File file = new File(path); + FileOutputStream fops = new FileOutputStream(file); + fops.write(img); + fops.flush(); + fops.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return path; + } + + /** + * + * @param json + * @param classname + * @param LOG + * @return + * @author GHUANG + * @version 2019年6月10日 下午1:08:45 + */ +// public static HashMap parseXML(String type, JSONObject json, String classname) throws Exception{ +// +// String keys = CSProperties.getValue("cs." + type + ".import.keys", ""); +// +// List keylist = Arrays.asList(keys.split(",")); +// HashMap objmap = new HashMap(); +// try { +// for (String key : keylist) { +// String xml = ""; +// String attrname = CSProperties.getValue("cs." + type + ".import." + key + ".attrname", ""); +// String attrtype = CSProperties.getValue("cs." + type + ".import." + key + ".attrtype", ""); +// String refobj = CSProperties.getValue("cs." + type + ".import." + key + ".refobject", ""); +// String refkey = CSProperties.getValue("cs." + type + ".import." + key + ".refkey", ""); +// String refpath = CSProperties.getValue("cs." + type + ".import." + key + ".refpath", ""); +// String attrpath = CSProperties.getValue("cs." + type + ".import." + key + ".attrpath", ""); +// if (json.isNull(key)) { +// continue; +// } +// if (json.has(key)) { +// String attrvalue = json.getString(key); +// if (attrtype.equals("enumKEY")) { +// xml += ""; +// ; +// } else if (attrtype.equals("enumNAME")) { +// for (Entry gmap : NodeUtil.zhLocaleMap.entrySet()) { +// if (gmap.getValue().equals(attrvalue) && gmap.getKey().contains(refobj)) { +// xml += ""; +// +// } +// } +// } else if (attrtype.equals("time")) { +// String timestr = CommonUtil.parseTimestamp(attrvalue, refobj, classname); +// xml += ""; +// } else if (attrtype.equals("integer")) { +// xml += ""; +// } else if (attrtype.equals("ref")) { +// String queryxml = "\r\n" + +// ""; +// List refobjlist = NodeUtil.queryBOByXML(queryxml); +// if (refobjlist.size() > 0) { +// xml += ""; +// +// } +// } else if (attrtype.equals("reflist")) { +// List valuelist = Arrays.asList(attrvalue); +// if (valuelist.size() > 0) { +// xml += ""; +// for (String value : valuelist) { +// String queryxml = "\r\n" + +// ""; +// List refobjlist = NodeUtil.queryBOByXML(queryxml); +// xml += "" + refobjlist.get(0) + ""; +// } +// xml += ""; +// } +// } else { +// xml += " "; +// +// } +// } +// if (attrpath == null || attrpath.length() == 0) { +// if (objmap.containsKey("default")) { +// String tempxml = (String) objmap.get("default"); +// objmap.put("default", xml + tempxml); +// } else { +// objmap.put("default", xml); +// } +// } else { +// System.out.println("---" + attrpath); +// if (objmap.containsKey(attrpath)) { +// String tempxml = (String) objmap.get(attrpath); +// objmap.put(attrpath, xml + tempxml); +// } else { +// objmap.put(attrpath, xml); +// } +// } +// } +// } catch (Exception e) { +// log.info(classname, e); +// } +// return objmap; +// } + + /** + * 四舍五入 + * + * @param attrvalue + * @param dot + * @return + * @author GHUANG + * @version 2019年6月18日 下午7:27:30 + */ + public static String changeDot(String attrvalue, int dot) { + BigDecimal db = new BigDecimal(attrvalue); + return String.valueOf(db.setScale(dot, BigDecimal.ROUND_HALF_UP).doubleValue()); + } + + public static void saveToFile(String fileName, InputStream in) throws IOException { + FileOutputStream fos = null; + BufferedInputStream bis = null; + int BUFFER_SIZE = 1024; + byte[] buf = new byte[BUFFER_SIZE]; + int size = 0; + bis = new BufferedInputStream(in); + File f = new File(fileName); + if (!f.exists())// + { + File parentDir = new File(f.getParent()); + if (!parentDir.exists())// + { + parentDir.mkdirs(); + } + f.createNewFile(); + } + + fos = new FileOutputStream(fileName); + while ((size = bis.read(buf)) != -1) { + fos.write(buf, 0, size); + } + fos.close(); + bis.close(); + } + + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toByteArray(); + } + + public static String parseTime(String timeValue, String format) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + String resultValue = ""; + try { + long timeLongValue = Long.parseLong(timeValue); + if (timeLongValue > 0) { + Timestamp ts = new Timestamp(timeLongValue); + resultValue = sdf.format(ts); + } + } catch (Exception e) { + e.printStackTrace(); + } + return resultValue; + } + + public static String inputStream2String(InputStream is) + throws UnsupportedEncodingException { + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + StringBuffer sb = new StringBuffer(); + String line = null; + try { + while ((line = reader.readLine()) != null) { + sb.append(line + "\n"); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return sb.toString(); + } + + /*** + * 删除文件夹 + * + * @param folderPath 文件夹完整绝对路径 + */ + public static void delFolder(String folderPath) { + try { + delAllFile(folderPath); // 删除完里面所有内容 + File myFilePath = new File(folderPath); + boolean b = myFilePath.delete(); // 删除空文件夹 + if(!b){ + log.error("删除文件失败,路径错误!"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /*** + * 删除指定文件夹下所有文件 + * + * @param path + * 文件夹完整绝对路径 + * @return + */ + public static boolean delAllFile(String path) { + boolean flag = false; + File file = new File(path); + if (!file.exists()) { + return flag; + } + if (!file.isDirectory()) { + return flag; + } + String[] tempList = file.list(); + File temp = null; + for (int i = 0; i < tempList.length; i++) { + if (path.endsWith(File.separator)) { + temp = new File(path + tempList[i]); + } else { + temp = new File(path + File.separator + tempList[i]); + } + if (temp.isFile()) { + temp.delete(); + } + if (temp.isDirectory()) { + delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件 + delFolder(path + "/" + tempList[i]);// 再删除空文件夹 + flag = true; + } + } + return flag; + } + + /** + * 查找类及其父类的所有属性 + * @param clazz + * @return + */ + public static List getAllFields(Class clazz) { + List fields = Lists.newArrayList(); + while (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + clazz = clazz.getSuperclass(); + } + return fields; + } + /** + * 四舍五入 + * @param d + * @param bitNum + * @return + */ + public static double getDouble(double d,int bitNum){ + BigDecimal b = BigDecimal.valueOf(d);//增加计算精度的处理,四舍弃,五入 + return b.setScale(bitNum, BigDecimal.ROUND_HALF_UP).doubleValue();//保留3位小数 + } + + /** + * 去掉数值末尾的0 + * @param d + * @param bitNum + * @return + */ + public static String getDoubleString(double d,int bitNum){ + BigDecimal b = BigDecimal.valueOf(d);//增加计算精度的处理,四舍弃,五入 + return b.setScale(bitNum, BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();//保留3位小数 + } + + public static String timestamp2Date(String str_num,String format ) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + if(StrUtil.isBlank(str_num)) str_num = "0"; + if (str_num.length() == 13) { + String date = sdf.format(new Date(Long.parseLong(str_num))); + //LogUtil.debug("timestamp2Date"+ "将13位时间戳:" + str_num + "转化为字符串:", date); + return date; + } else if(!"0".equals(str_num)) { + String date = sdf.format(new Date(Integer.parseInt(str_num) * 1000L)); + //LogUtil.debug("timestamp2Date" + "将10位时间戳:" + str_num + "转化为字符串:", date); + return date; + }else + return ""; + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/EncodeUtils.java b/commons/src/main/java/com/centricsoftware/commons/utils/EncodeUtils.java new file mode 100644 index 0000000..317e829 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/EncodeUtils.java @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2005-2012 springside.org.cn + */ +package com.centricsoftware.commons.utils; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.StringEscapeUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +/** + * 封装各种格式的编码解码工具类. + * 1.Commons-Codec的 hex/base64 编码 + * 2.自制的base62 编码 + * 3.Commons-Lang的xml/html escape + * 4.JDK提供的URLEncoder + * + * @author calvin + * @version 2016-01-15 + */ +public class EncodeUtils { + private static final String DEFAULT_URL_ENCODING = "UTF-8"; + private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); + + /** + * Hex编码. + */ + public static String encodeHex(byte[] input) { + return new String(Hex.encodeHex(input)); + } + + /** + * Hex解码. + */ + public static byte[] decodeHex(String input) throws DecoderException { + return Hex.decodeHex(input.toCharArray()); + } + + /** + * Base64编码. + */ + public static String encodeBase64(byte[] input) { + return new String(Base64.encodeBase64(input)); + } + + /** + * Base64编码. + */ + public static String encodeBase64(String input) { + try { + return new String(Base64.encodeBase64(input.getBytes(DEFAULT_URL_ENCODING))); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + /** + * Base64解码. + */ + public static byte[] decodeBase64(String input) { + return Base64.decodeBase64(input.getBytes()); + } + + /** + * Base64解码. + */ + public static String decodeBase64String(String input) { + try { + return new String(Base64.decodeBase64(input.getBytes()), DEFAULT_URL_ENCODING); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + /** + * Base62编码。 + */ + public static String encodeBase62(byte[] input) { + char[] chars = new char[input.length]; + for (int i = 0; i < input.length; i++) { + chars[i] = BASE62[((input[i] & 0xFF) % BASE62.length)]; + } + return new String(chars); + } + + /** + * Html 转码. + */ + public static String escapeHtml(String html) { + return StringEscapeUtils.escapeHtml4(html); + } + + /** + * Html 解码. + */ + public static String unescapeHtml(String htmlEscaped) { + return StringEscapeUtils.unescapeHtml4(htmlEscaped); + } + + /** + * Xml 转码. + */ + public static String escapeXml(String xml) { + return StringEscapeUtils.escapeXml10(xml); + } + + /** + * Xml 解码. + */ + public static String unescapeXml(String xmlEscaped) { + return StringEscapeUtils.unescapeXml(xmlEscaped); + } + + /** + * URL 编码, Encode默认为UTF-8. + */ + public static String urlEncode(String part) { + try { + return URLEncoder.encode(part, DEFAULT_URL_ENCODING); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + /** + * URL 解码, Encode默认为UTF-8. + */ + public static String urlDecode(String part) { + try { + return URLDecoder.decode(part, DEFAULT_URL_ENCODING); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + public static void main(String[] args){ + System.out.println(EncodeUtils.encodeBase64("plm.2021.666")); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/ExcelUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/ExcelUtil.java new file mode 100644 index 0000000..029dc7e --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/ExcelUtil.java @@ -0,0 +1,1150 @@ +package com.centricsoftware.commons.utils; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.poi.excel.ExcelReader; +import cn.hutool.poi.excel.ExcelWriter; +import jxl.Cell; +import jxl.CellType; +import jxl.Sheet; +import jxl.Workbook; +import jxl.*; +import jxl.format.UnderlineStyle; +import jxl.write.Boolean; +import jxl.write.Number; +import jxl.write.*; +import lombok.extern.slf4j.Slf4j; +import net.sf.jxls.util.Util; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.*; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.channels.FileChannel; +import java.util.*; +import java.util.regex.Pattern; + +//import com.aspose.cells.License; +//import com.aspose.cells.SaveFormat; + +/** + * excel + * 处理工具类,同类方法 + * {@link cn.hutool.poi.excel.ExcelReader} + * {@link cn.hutool.poi.excel.ExcelFileUtil} + * {@link cn.hutool.poi.excel.ExcelUtil} + * {@link cn.hutool.poi.excel.ExcelWriter} + * {@link cn.hutool.poi.excel.ExcelPicUtil} + * @author zheng.gong + * @date 2020/4/27 + */ +@Slf4j +public class ExcelUtil { + public ExcelUtil() { + + } + + /** + * 获取破解excel转换成pdf文件的license文件 + * + * @return + */ +// public static boolean getLicense() { +// // 获取license文件路径 +// String license = "license.xml"; +// boolean result = false; +// try { +// // 获取文件 +// ClassPathResource resource = new ClassPathResource(license); +// // 获取输入流 +// InputStream is = resource.getInputStream(); +// // 通过License的set方法进行破解转换 +// License aposeLic = new License(); +// aposeLic.setLicense(is); +// result = true; +// is.close(); +// } catch (Exception e) { +// log.error("破解excel转换pdf文件的license错误",e); +// } +// return result; +// } + + /** + * @param excelPath + * 需要被转换的excel全路径带文件名 + * @param pdfPath + * 转换之后pdf的全路径带文件名 + */ +// public static void excel2pdf(String excelPath, String pdfPath) { +// if (!getLicense()) { // 验证License 若不验证则转化出的pdf文档会有水印产生 +// return; +// } +// log.info("License文件验证成功!"); +// try { +// log.info("开始转换pdf文件"); +// // 原始excel路径 +// long old = System.currentTimeMillis(); +// // 创建一个工作空间 +// com.aspose.cells.Workbook wb = new com.aspose.cells.Workbook(excelPath); +// // 获取文件输出流 +// FileOutputStream fileOS = new FileOutputStream(new File(pdfPath)); +// // 进行保存,SaveFormat内部有声明可以导出的文件类型,可自由选择 +// wb.save(fileOS, SaveFormat.PDF); +// fileOS.close(); +// long now = System.currentTimeMillis(); +// log.info("共耗时:" + ((now - old) / 1000.0) + "秒"); // 转化用时 +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + + + /** + * + * @param filePath + */ + public static void readExcel(String filePath) { + try { + InputStream is = new FileInputStream(filePath); + Workbook rwb = Workbook.getWorkbook(is); + Sheet st = rwb.getSheet("original"); + Cell c00 = st.getCell(0, 0); + String strc00 = c00.getContents(); + if (c00.getType() == CellType.LABEL) { + LabelCell labelc00 = (LabelCell) c00; + strc00 = labelc00.getString(); + } + System.out.println(strc00); + rwb.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** + * 根据配置文件,导出需要的数据 + * @param templatePath 模板文件路径 + * @param list 根据配置文件生成的对应数据 + * @return ExcelWriter + */ + public static ExcelWriter exportExcelByConfig(String templatePath, List> list){ + ExcelWriter writer = null; + try (InputStream in = ExcelUtil.class.getClassLoader().getResourceAsStream(templatePath)) { + ExcelReader reader = cn.hutool.poi.excel.ExcelUtil.getReader(in); + writer = reader.getWriter(); + writer.setStyleSet(null); + for (Map stringObjectMap : list) { + int x = (int) stringObjectMap.get("x"); + int y = (int) stringObjectMap.get("y"); + Object value = stringObjectMap.get("value"); + writer.writeCellValue(x,y,value); + } + } catch (Exception e) { + log.error("创建excel失败!", e); + } + + return writer; + } + + + /** + * @param sheet sheet + * @param wb workbook + * @param in 图片输入流 + * @param imageType 图片格式类型 + * @param resize 是否按图片原比例缩放 + * @param dx1 图片在起始单元格x轴坐标 + * @param dy1 图片在起始单元格y轴坐标 + * @param dx2 图片在结束单元格x轴坐标 + * @param dy2 图片在结束单元格y轴坐标 + * @param col1 图片左上角所在的cellNum,从0开始 + * @param row1 图片左上角所在的RowNum,从0开始 + * @param col2 图片右下角所在的cellNum,从0开始 + * @param row2 图片右下角所在的RowNum,从0开始 + */ + public static void insertImage(org.apache.poi.ss.usermodel.Sheet sheet, org.apache.poi.ss.usermodel.Workbook wb, InputStream in, int imageType, boolean resize, + int dx1, int dy1, int dx2, int dy2, + int col1, int row1, int col2, int row2) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] b = new byte[1024]; + int len; + while ((len = in.read(b)) != -1) { + baos.write(b, 0, len); + } + Drawing drawing = sheet.createDrawingPatriarch(); + XSSFClientAnchor anchor = new XSSFClientAnchor(dx1, dy1, dx2, dy2, + col1, row1, col2, row2); + byte[] bytes = baos.toByteArray(); + //是否有缩放 + if (resize) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + BufferedImage imgReader = ImageIO.read(inputStream); +// BufferedImage imgReader = ImgUtil.read(inputStream); + Row row = sheet.getRow(row1); + //单元格列宽 + float cellWidth = 0f; + for (int i = col1; i < col2; i++) { + cellWidth += sheet.getColumnWidthInPixels(i); + } + //x轴缩放比例 + double scalx = 0; + //y轴缩放比例 + double scaly = 0; + //缩放后显示在单元格上的图片长度 + double imgScalWidth = 0; + //缩放后显示在单元格上的图片宽度 + double imgScalHeight = 0; + double contextRatio = 0.9; + //图片原始宽度 + int imgOriginalWidth = imgReader.getWidth(); + //图片原始高度 + double imgOriginalHeight = imgReader.getHeight(); + //单元格行高 + float cellHeight = row.getHeightInPoints() / 72 * 96; +// log.debug("图片原始宽度:{},图片原始高度:{},单元格列宽:{},单元格行高:{}",imgOriginalWidth,imgOriginalHeight,cellWidth,cellHeight); + //如果行宽和列高都小于单元格行宽和列高,则图片不做缩放,原始大小 + + //单元格长宽比 + double cellRatio = cellWidth/cellHeight; + //原始图片长宽比 + double imgRatio = imgOriginalWidth/imgOriginalHeight; + if(cellRatio > 0 && imgRatio > 0 ){ + /* + 原始图片和单元格均为长>宽 标准样式 + */ + imgScalWidth = Math.floor(cellWidth*contextRatio); + imgScalHeight = Math.floor(imgOriginalHeight/imgOriginalWidth*cellWidth); + if(imgScalHeight > cellHeight){ + imgScalWidth = Math.floor(cellHeight*imgOriginalWidth/imgOriginalHeight); + imgScalHeight = Math.floor(cellHeight*contextRatio); + + } + }else if(cellRatio > 0 && imgRatio < 0){ + /* + 原始图片长<宽 长图样式 + */ + imgScalWidth = imgOriginalWidth/imgOriginalHeight*cellHeight*contextRatio; + imgScalHeight = cellHeight*contextRatio; + } + //计算缩率 + scalx = imgScalWidth/cellWidth; + scaly = imgScalHeight/cellHeight; + + //图片左边相对excel格的位置(x偏移) + double doubleDx1 = (cellWidth - imgScalWidth) / 2; + //图片上方相对excel格的位置(y偏移) + double doubleDy1 = (cellHeight - imgScalHeight) / 2; + + int rdx1 = NumberUtil.round((doubleDx1*1000)+dx1,0).intValue(); + int rdy1 = NumberUtil.round((doubleDy1*1000)+dy1,0).intValue(); + log.debug("dx1:{},dy1:{}",rdx1,rdx1); + Drawing patriarch = sheet.createDrawingPatriarch(); + XSSFClientAnchor resizeAnchor = new XSSFClientAnchor(rdx1, rdy1, dx2, dy2,(short) col1, row1, (short) col2, row2); + Picture picture = patriarch.createPicture(resizeAnchor, wb.addPicture(bytes, HSSFWorkbook.PICTURE_TYPE_JPEG)); + + log.debug("scalx:{},scaly:{}",scalx,scaly); + picture.resize(scalx,scaly);//等比缩放 +// picture.resize(0.8);//等比缩放 + } else { + drawing.createPicture(anchor, wb.addPicture(bytes, imageType)); + } + } catch (Exception e) { + log.error("图片处理异常!", e); + } + } + + /** + * 读取Excel + * + * @param filePath + */ + public static ArrayList> readExcel(String filePath, int sheetIndex) { + try { + ArrayList> valueList = new ArrayList>(); + InputStream is = new FileInputStream(filePath); + Workbook rwb = Workbook.getWorkbook(is); + // Sheet st = rwb.getSheet("0")这里有两种方法获取sheet�?,1为名字,而为下标,从0�?�? + Sheet st = rwb.getSheet(sheetIndex); + int clumns = st.getColumns(); + System.out.println("colum=" + clumns); + String[] headers = new String[clumns]; + for (int i = 0; i < clumns; i++) { + String header = st.getCell(i, 0).getContents(); + headers[i] = header; + } + + int rows = st.getRows(); + System.out.println("rows=" + rows); + for (int i = 1; i < rows; i++) { + boolean breakFlag = false; + HashMap map = new HashMap(); + for (int j = 0; j < clumns; j++) { + String header = headers[j]; + String strc = st.getCell(j, i).getContents(); + if (header != null && !"".equals(header)) { + map.put(header, strc.trim()); + } + if (j == 0 && "".equals(strc)) { + breakFlag = true; + } + } + if (breakFlag) { + break; + } + map.put("LineNo", (i + 1) + ""); + valueList.add(map); + } + + // 关闭 + rwb.close(); + return valueList; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + public static boolean copyRealFile(String srcName, String destName) { + try { + BufferedInputStream in = new BufferedInputStream(new FileInputStream(srcName)); + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(destName)); + int i = 0; + byte[] buffer = new byte[2048]; + while ((i = in.read(buffer)) != -1) { + out.write(buffer, 0, i); + } + out.close(); + in.close(); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + public static HSSFCellStyle bodyStyle(HSSFWorkbook wb, HorizontalAlignment alignStyle) { + HSSFFont bodyFont = wb.createFont(); + bodyFont.setBold(false); + bodyFont.setFontName("宋体"); + bodyFont.setFontHeightInPoints((short) 9); + HSSFCellStyle bodyStyle = wb.createCellStyle(); + bodyStyle.setFont(bodyFont); + bodyStyle.setBorderTop(BorderStyle.THIN); + bodyStyle.setBorderRight(BorderStyle.THIN); + bodyStyle.setBorderBottom(BorderStyle.THIN); + bodyStyle.setBorderLeft((BorderStyle.THIN)); + bodyStyle.setAlignment(alignStyle); + bodyStyle.setVerticalAlignment(VerticalAlignment.CENTER); + return bodyStyle; + } + + public static HSSFCellStyle bodyWrapStyle(HSSFWorkbook wb, HorizontalAlignment alignStyle) { + HSSFFont bodyFont = wb.createFont(); + bodyFont.setBold(false); + bodyFont.setFontName("宋体"); + bodyFont.setFontHeightInPoints((short) 9); + HSSFCellStyle bodyStyle = wb.createCellStyle(); + bodyStyle.setFont(bodyFont); + bodyStyle.setBorderTop(BorderStyle.THIN); + bodyStyle.setBorderRight(BorderStyle.THIN); + bodyStyle.setBorderBottom(BorderStyle.THIN); + bodyStyle.setBorderLeft((BorderStyle.THIN)); + bodyStyle.setAlignment(alignStyle); + bodyStyle.setWrapText(true); + bodyStyle.setVerticalAlignment(VerticalAlignment.CENTER); + bodyStyle.setWrapText(true); + return bodyStyle; + } + + /** + * + * @param resultWorkbook + * @param sheet + * @param imgStream + * @param nodeName + * @param imageStartCol + * @param imageStartRow + * @param imageEndCol + * @param imageEndRow + * @author GHUANG + * @version 2019年5月16日 下午2:36:39 + */ + public static void writePicture(HSSFWorkbook resultWorkbook, HSSFSheet sheet, InputStream imgStream, + String nodeName, int imageStartCol, int imageStartRow, int imageEndCol, int imageEndRow) throws Exception{ + int imagetype = 0; + ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); + if (imgStream != null) { + if (nodeName.endsWith("png") || nodeName.endsWith("PNG")) { + imagetype = HSSFWorkbook.PICTURE_TYPE_PNG; + } else { + imagetype = HSSFWorkbook.PICTURE_TYPE_JPEG; + } + byte[] buffer = new byte[1024]; + int len = -1; + try { + while ((len = imgStream.read(buffer)) != -1) { + byteArrayOut.write(buffer, 0, len); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 0, 0, + (short) imageStartCol, imageStartRow, + (short) imageEndCol, imageEndRow); + // int dx1, int dy1, int dx2, int dy2, short col1, int row1, short col2, int row2 + patriarch.createPicture(anchor, resultWorkbook.addPicture(byteArrayOut.toByteArray(), imagetype)); + } + } + + /** + * 输出Excel + * + * @param os + */ + public static void writeExcel(OutputStream os) { + try { + WritableWorkbook wwb = Workbook.createWorkbook(os); + WritableSheet ws = wwb.createSheet("Test Sheet 1", 0); + Label label = new Label(0, 0, "this is a label test"); + ws.addCell(label); + + WritableFont wf = new WritableFont(WritableFont.TIMES, 18, + WritableFont.BOLD, true); + WritableCellFormat wcf = new WritableCellFormat(wf); + Label labelcf = new Label(1, 0, "this is a label test", wcf); + ws.addCell(labelcf); + + WritableFont wfc = new WritableFont(WritableFont.ARIAL, 10, + WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, + jxl.format.Colour.RED); + WritableCellFormat wcfFC = new WritableCellFormat(wfc); + Label labelCF = new Label(1, 0, "This is a Label Cell", wcfFC); + ws.addCell(labelCF); + + // 2.添加Number对象 + Number labelN = new Number(0, 1, 3.1415926); + ws.addCell(labelN); + + // 添加带有formatting的Number对象 + NumberFormat nf = new NumberFormat("#.##"); + WritableCellFormat wcfN = new WritableCellFormat(nf); + Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN); + ws.addCell(labelNF); + + // 3.添加Boolean对象 + Boolean labelB = new jxl.write.Boolean(0, 2, false); + ws.addCell(labelB); + + // 4.添加DateTime对象 + jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, + new java.util.Date()); + ws.addCell(labelDT); + + // 添加带有formatting的DateFormat对象 + DateFormat df = new DateFormat("dd MM yyyy hh:mm:ss"); + WritableCellFormat wcfDF = new WritableCellFormat(df); + DateTime labelDTF = new DateTime(1, 3, new java.util.Date(), wcfDF); + ws.addCell(labelDTF); + + // 添加图片对象,jxl只支持png格式图片 + File image = new File("f:\\2.png"); + WritableImage wimage = new WritableImage(0, 1, 2, 2, image); + ws.addImage(wimage); + // 写入工作�? + wwb.write(); + wwb.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void write2Excel(String fileName, List> valueList) { + try { + WritableWorkbook wwb = Workbook.createWorkbook(new FileOutputStream(fileName)); + // 创建Excel工作�? 指定名称和位�? + WritableSheet ws = wwb.createSheet("Custom Attributes", 0); + + // **************�?工作表中添加数据***************** + + // 1.添加Label对象 + if (valueList.size() <= 0) { + return; + } + + Iterator it = valueList.get(0).keySet().iterator(); + String[] headers = new String[valueList.get(0).keySet().size()]; + int i = 0; + while (it.hasNext()) { + String header = (String) it.next(); + headers[i] = header; + Label label = new Label(i++, 0, header); + ws.addCell(label); + } + + int r = 1; + for (HashMap attValues : valueList) { + for (int j = 0; j < headers.length; j++) { + Label label = new Label(j, r, attValues.get(headers[j])); + ws.addCell(label); + } + r++; + } + + wwb.write(); + wwb.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 拷贝文件,进行修改,其中file1为被copy对象,file2为修改后创建的对象 尽单元格原有的格式化修饰是不能去掉的,我们还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现 + * + * @param file1 + * @param file2 + */ + public static void copyFile(File file1, File file2) { + try { + Workbook rwb = Workbook.getWorkbook(file1); + WritableWorkbook wwb = Workbook.createWorkbook(file2, rwb);// copy + wwb.close(); + rwb.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static float getExcelCellAutoHeight(String str, float fontCountInline) { + float defaultRowHeight = 18.00f;// 每一行的高度指定 + float defaultCount = 0.00f; + for (int i = 0; i < str.length(); i++) { + float ff = getregex(str.substring(i, i + 1)); + defaultCount = defaultCount + ff; + } + System.out.println("defaultCount=" + defaultCount); + return ((int) (defaultCount / fontCountInline) + 1) * defaultRowHeight;// 计算 + } + + public static float getregex(String charStr) { + + if (charStr == " ") { + return 0.5f; + } + // 判断是否为字母或字符 + if (Pattern.compile("^[A-Za-z0-9]+$").matcher(charStr).matches()) { + return 0.50f; + } + // 判断是否为全角 + + if (Pattern.compile("[\u4e00-\u9fa5]+$").matcher(charStr).matches()) { + return 1.00f; + } + // 全角符号 及中文 + if (Pattern.compile("[^x00-xff]").matcher(charStr).matches()) { + return 1.00f; + } + return 0.5f; + + } + + /** + * 使用文件通道的方式复制文件 + * + * @param s 源文件 + * @param t 复制到的新文件 + */ + public static void fileCopy(File s, File t) { + FileInputStream fi = null; + FileOutputStream fo = null; + FileChannel in = null; + FileChannel out = null; + try { + fi = new FileInputStream(s); + fo = new FileOutputStream(t); + in = fi.getChannel();// 得到对应的文件通道 + out = fo.getChannel();// 得到对应的文件通道 + in.transferTo(0, in.size(), out);// 连接两个通道,并且从in通道读取,然后写入out通道 + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + fi.close(); + in.close(); + fo.close(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 功能:拷贝sheet 实际调用 copySheet(targetSheet, sourceSheet, targetWork, sourceWork, true) + * + * @param targetSheet + * @param sourceSheet + * @param targetWork + * @param sourceWork + */ + public static void copySheet(HSSFSheet targetSheet, HSSFSheet sourceSheet, + HSSFWorkbook targetWork, HSSFWorkbook sourceWork) throws Exception { + if (targetSheet == null || sourceSheet == null || targetWork == null || sourceWork == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copySheet()方法时,targetSheet、sourceSheet、targetWork、sourceWork都不能为空,故抛出该异常!"); + } + // String pa = sourceWork.getPrintArea(sourceWork.getSheetIndex(sourceSheet)); + // System.out.print("printarea=" + pa + ",source" + sourceSheet.getSheetName()); + // if (pa != null) + // targetWork.setPrintArea(targetWork.getSheetIndex(targetSheet), pa); + + copySheet(targetSheet, sourceSheet, targetWork, sourceWork, true); + Util.copyPageSetup(targetSheet, sourceSheet); + Util.copyPrintSetup(targetSheet, sourceSheet); + + } + + /** + * 功能:拷贝sheet + * + * @param targetSheet + * @param sourceSheet + * @param targetWork + * @param sourceWork + * @param copyStyle + * boolean 是否拷贝样式 + */ + public static void copySheet(HSSFSheet targetSheet, HSSFSheet sourceSheet, + HSSFWorkbook targetWork, HSSFWorkbook sourceWork, boolean copyStyle) throws Exception { + + if (targetSheet == null || sourceSheet == null || targetWork == null || sourceWork == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copySheet()方法时,targetSheet、sourceSheet、targetWork、sourceWork都不能为空,故抛出该异常!"); + } + targetSheet.setMargin(HSSFSheet.TopMargin, sourceSheet.getMargin(HSSFSheet.TopMargin));// 页边距(上) + targetSheet.setMargin(HSSFSheet.BottomMargin, sourceSheet.getMargin(HSSFSheet.BottomMargin));// 页边距(下) + targetSheet.setMargin(HSSFSheet.LeftMargin, sourceSheet.getMargin(HSSFSheet.LeftMargin));// 页边距(左) + targetSheet.setMargin(HSSFSheet.RightMargin, sourceSheet.getMargin(HSSFSheet.RightMargin));// 页边距(右 + targetSheet.setHorizontallyCenter(true); + targetSheet.setRepeatingColumns(sourceSheet.getRepeatingColumns()); + targetSheet.setRepeatingRows(sourceSheet.getRepeatingRows()); + HSSFFooter sfoot = sourceSheet.getFooter(); + HSSFFooter tfoot = targetSheet.getFooter(); + tfoot.setCenter(sfoot.getCenter()); + tfoot.setLeft(sfoot.getLeft()); + tfoot.setRight(sfoot.getRight()); + // 复制源表中的行 + int maxColumnNum = 0; + + Map styleMap = (copyStyle) ? new HashMap() : null; + + HSSFPatriarch patriarch = targetSheet.createDrawingPatriarch(); // 用于复制注释 + for (int i = sourceSheet.getFirstRowNum(); i <= sourceSheet.getLastRowNum(); i++) { + HSSFRow sourceRow = sourceSheet.getRow(i); + HSSFRow targetRow = targetSheet.createRow(i); + + if (sourceRow != null) { + copyRow(targetRow, sourceRow, + targetWork, sourceWork, patriarch, styleMap); + if (sourceRow.getLastCellNum() > maxColumnNum) { + maxColumnNum = sourceRow.getLastCellNum(); + } + } + } + + // 复制源表中的合并单元格 + mergerRegion(targetSheet, sourceSheet); + + // 设置目标sheet的列宽 + for (int i = 0; i <= maxColumnNum; i++) { + targetSheet.setColumnWidth(i, sourceSheet.getColumnWidth(i)); + } + } + + /** + * 功能:拷贝sheet + * + * @param targetSheet + * @param sourceSheet + * @param targetWork + * @param sourceWork + * @param copyStyle + * boolean 是否拷贝样式 + */ + public static void copySheet(XSSFSheet targetSheet, XSSFSheet sourceSheet, + XSSFWorkbook targetWork, XSSFWorkbook sourceWork, boolean copyStyle) throws Exception { + + if (targetSheet == null || sourceSheet == null || targetWork == null || sourceWork == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copySheet()方法时,targetSheet、sourceSheet、targetWork、sourceWork都不能为空,故抛出该异常!"); + } + targetSheet.setMargin(HSSFSheet.TopMargin, sourceSheet.getMargin(HSSFSheet.TopMargin));// 页边距(上) + targetSheet.setMargin(HSSFSheet.BottomMargin, sourceSheet.getMargin(HSSFSheet.BottomMargin));// 页边距(下) + targetSheet.setMargin(HSSFSheet.LeftMargin, sourceSheet.getMargin(HSSFSheet.LeftMargin));// 页边距(左) + targetSheet.setMargin(HSSFSheet.RightMargin, sourceSheet.getMargin(HSSFSheet.RightMargin));// 页边距(右 + targetSheet.setHorizontallyCenter(true); + targetSheet.setRepeatingColumns(sourceSheet.getRepeatingColumns()); + targetSheet.setRepeatingRows(sourceSheet.getRepeatingRows()); + Footer sfoot = sourceSheet.getFooter(); + Footer tfoot = targetSheet.getFooter(); + tfoot.setCenter(sfoot.getCenter()); + tfoot.setLeft(sfoot.getLeft()); + tfoot.setRight(sfoot.getRight()); + // 复制源表中的行 + int maxColumnNum = 0; + + Map styleMap = (copyStyle) ? new HashMap() : null; + + XSSFDrawing patriarch = targetSheet.createDrawingPatriarch(); // 用于复制注释 + for (int i = sourceSheet.getFirstRowNum(); i <= sourceSheet.getLastRowNum(); i++) { + XSSFRow sourceRow = sourceSheet.getRow(i); + XSSFRow targetRow = targetSheet.createRow(i); + + if (sourceRow != null) { + copyRow(targetRow, sourceRow, + targetWork, sourceWork, patriarch, styleMap); + if (sourceRow.getLastCellNum() > maxColumnNum) { + maxColumnNum = sourceRow.getLastCellNum(); + } + } + } + + // 复制源表中的合并单元格 + mergerRegion(targetSheet, sourceSheet); + + // 设置目标sheet的列宽 + for (int i = 0; i <= maxColumnNum; i++) { + targetSheet.setColumnWidth(i, sourceSheet.getColumnWidth(i)); + } + } + + /** + * 功能:拷贝row + * + * @param targetRow + * @param sourceRow + * @param styleMap + * @param targetWork + * @param sourceWork + * @param targetPatriarch + */ + public static void copyRow(HSSFRow targetRow, HSSFRow sourceRow, + HSSFWorkbook targetWork, HSSFWorkbook sourceWork, HSSFPatriarch targetPatriarch, Map styleMap) + throws Exception { + if (targetRow == null || sourceRow == null || targetWork == null || sourceWork == null + || targetPatriarch == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copyRow()方法时,targetRow、sourceRow、targetWork、sourceWork、targetPatriarch都不能为空,故抛出该异常!"); + } + + // 设置行高 + targetRow.setHeight(sourceRow.getHeight()); + + for (int i = sourceRow.getFirstCellNum(); i <= sourceRow.getLastCellNum(); i++) { + HSSFCell sourceCell = sourceRow.getCell(i); + HSSFCell targetCell = targetRow.getCell(i); + + if (sourceCell != null) { + if (targetCell == null) { + targetCell = targetRow.createCell(i); + } + + // 拷贝单元格,包括内容和样式 + copyCell(targetCell, sourceCell, targetWork, sourceWork, styleMap); + + // 拷贝单元格注释 + // copyComment(targetCell, sourceCell, targetPatriarch); + } + } + } + + /** + * 功能:拷贝row + * + * @param targetRow + * @param sourceRow + * @param styleMap + * @param targetWork + * @param sourceWork + * @param targetPatriarch + */ + public static void copyRow(XSSFRow targetRow, XSSFRow sourceRow, + XSSFWorkbook targetWork, XSSFWorkbook sourceWork, XSSFDrawing targetPatriarch, Map styleMap) + throws Exception { + if (targetRow == null || sourceRow == null || targetWork == null || sourceWork == null + || targetPatriarch == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copyRow()方法时,targetRow、sourceRow、targetWork、sourceWork、targetPatriarch都不能为空,故抛出该异常!"); + } + + // 设置行高 + targetRow.setHeight(sourceRow.getHeight()); + + for (int i = sourceRow.getFirstCellNum(); i <= sourceRow.getLastCellNum(); i++) { + XSSFCell sourceCell = sourceRow.getCell(i); + XSSFCell targetCell = targetRow.getCell(i); + + if (sourceCell != null) { + if (targetCell == null) { + targetCell = targetRow.createCell(i); + } + + // 拷贝单元格,包括内容和样式 + copyCell(targetCell, sourceCell, targetWork, sourceWork, styleMap); + + // 拷贝单元格注释 + // copyComment(targetCell, sourceCell, targetPatriarch); + } + } + } + + /** + * 功能:拷贝cell,依据styleMap是否为空判断是否拷贝单元格样式 + * + * @param targetCell + * 不能为空 + * @param sourceCell + * 不能为空 + * @param targetWork + * 不能为空 + * @param sourceWork + * 不能为空 + * @param styleMap + * 可以为空 + */ + public static void copyCell(HSSFCell targetCell, HSSFCell sourceCell, HSSFWorkbook targetWork, + HSSFWorkbook sourceWork, Map styleMap) { + if (targetCell == null || sourceCell == null || targetWork == null || sourceWork == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copyCell()方法时,targetCell、sourceCell、targetWork、sourceWork都不能为空,故抛出该异常!"); + } + + // 处理单元格样式 + if (styleMap != null) { + if (targetWork == sourceWork) { + targetCell.setCellStyle(sourceCell.getCellStyle()); + // targetCell.getCellStyle().cloneStyleFrom(sourceCell.getCellStyle()); + } else { + String stHashCode = "" + sourceCell.getCellStyle().hashCode(); + HSSFCellStyle targetCellStyle = (HSSFCellStyle) styleMap + .get(stHashCode); + if (targetCellStyle == null) { + targetCellStyle = targetWork.createCellStyle(); + targetCellStyle.cloneStyleFrom(sourceCell.getCellStyle()); + styleMap.put(stHashCode, targetCellStyle); + } + + targetCell.setCellStyle(targetCellStyle); + } + } + + org.apache.poi.ss.usermodel.CellType cellTypeEnum = sourceCell.getCellTypeEnum(); + // 处理单元格内容 + switch (cellTypeEnum) { + case STRING: + targetCell.setCellValue(sourceCell.getRichStringCellValue()); + break; + case NUMERIC: + targetCell.setCellValue(sourceCell.getNumericCellValue()); + break; + case BLANK: + targetCell.setCellType(cellTypeEnum); + break; + case BOOLEAN: + targetCell.setCellValue(sourceCell.getBooleanCellValue()); + break; + case ERROR: + targetCell.setCellErrorValue(FormulaError.forInt(sourceCell.getErrorCellValue())); + break; + case FORMULA: + targetCell.setCellFormula(sourceCell.getCellFormula()); + break; + default: + break; + } + + + } + + /** + * 功能:拷贝cell,依据styleMap是否为空判断是否拷贝单元格样式 + * + * @param targetCell + * 不能为空 + * @param sourceCell + * 不能为空 + * @param targetWork + * 不能为空 + * @param sourceWork + * 不能为空 + * @param styleMap + * 可以为空 + */ + public static void copyCell(XSSFCell targetCell, XSSFCell sourceCell, XSSFWorkbook targetWork, + XSSFWorkbook sourceWork, Map styleMap) { + if (targetCell == null || sourceCell == null || targetWork == null || sourceWork == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copyCell()方法时,targetCell、sourceCell、targetWork、sourceWork都不能为空,故抛出该异常!"); + } + + // 处理单元格样式 + if (styleMap != null) { + if (targetWork == sourceWork) { + targetCell.setCellStyle(sourceCell.getCellStyle()); + } else { + String stHashCode = "" + sourceCell.getCellStyle().hashCode(); + XSSFCellStyle targetCellStyle = (XSSFCellStyle) styleMap + .get(stHashCode); + if (targetCellStyle == null) { + targetCellStyle = targetWork.createCellStyle(); + targetCellStyle.cloneStyleFrom(sourceCell.getCellStyle()); + styleMap.put(stHashCode, targetCellStyle); + } + + targetCell.setCellStyle(targetCellStyle); + } + } + + org.apache.poi.ss.usermodel.CellType cellTypeEnum = sourceCell.getCellTypeEnum(); + // 处理单元格内容 + switch (cellTypeEnum) { + case STRING: + targetCell.setCellValue(sourceCell.getRichStringCellValue()); + break; + case NUMERIC: + targetCell.setCellValue(sourceCell.getNumericCellValue()); + break; + case BLANK: + targetCell.setCellType(cellTypeEnum); + break; + case BOOLEAN: + targetCell.setCellValue(sourceCell.getBooleanCellValue()); + break; + case ERROR: + targetCell.setCellErrorValue(sourceCell.getErrorCellValue()); + break; + case FORMULA: + targetCell.setCellFormula(sourceCell.getCellFormula()); + break; + default: + break; + } + } + + /** + * 功能:拷贝comment + * + * @param targetCell + * @param sourceCell + * @param targetPatriarch + */ + public static void copyComment(HSSFCell targetCell, HSSFCell sourceCell, HSSFPatriarch targetPatriarch) + throws Exception { + if (targetCell == null || sourceCell == null || targetPatriarch == null) { + throw new IllegalArgumentException( + "调用PoiUtil.copyCommentr()方法时,targetCell、sourceCell、targetPatriarch都不能为空,故抛出该异常!"); + } + + // 处理单元格注释 + HSSFComment comment = sourceCell.getCellComment(); + if (comment != null) { + HSSFComment newComment = targetPatriarch.createComment(new HSSFClientAnchor()); + newComment.setAuthor(comment.getAuthor()); + newComment.setColumn(comment.getColumn()); + newComment.setFillColor(comment.getFillColor()); + newComment.setHorizontalAlignment(comment.getHorizontalAlignment()); + newComment.setLineStyle(comment.getLineStyle()); + newComment.setLineStyleColor(comment.getLineStyleColor()); + newComment.setLineWidth(comment.getLineWidth()); + newComment.setMarginBottom(comment.getMarginBottom()); + newComment.setMarginLeft(comment.getMarginLeft()); + newComment.setMarginTop(comment.getMarginTop()); + newComment.setMarginRight(comment.getMarginRight()); + newComment.setNoFill(comment.isNoFill()); + newComment.setRow(comment.getRow()); + newComment.setShapeType(comment.getShapeType()); + newComment.setString(comment.getString()); + newComment.setVerticalAlignment(comment.getVerticalAlignment()); + newComment.setVisible(comment.isVisible()); + targetCell.setCellComment(newComment); + } + } + + /** + * 功能:复制原有sheet的合并单元格到新创建的sheet + * + * @param targetSheet + * @param sourceSheet + */ + public static void mergerRegion(XSSFSheet targetSheet, XSSFSheet sourceSheet) throws Exception { + if (targetSheet == null || sourceSheet == null) { + throw new IllegalArgumentException("调用PoiUtil.mergerRegion()方法时,targetSheet或者sourceSheet不能为空,故抛出该异常!"); + } + + for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) { + CellRangeAddress oldRange = sourceSheet.getMergedRegion(i); + CellRangeAddress newRange = new CellRangeAddress( + oldRange.getFirstRow(), oldRange.getLastRow(), + oldRange.getFirstColumn(), oldRange.getLastColumn()); + targetSheet.addMergedRegion(newRange); + } + } + + /** + * 功能:复制原有sheet的合并单元格到新创建的sheet + * + * @param targetSheet + * @param sourceSheet + */ + public static void mergerRegion(HSSFSheet targetSheet, HSSFSheet sourceSheet) throws Exception { + if (targetSheet == null || sourceSheet == null) { + throw new IllegalArgumentException("调用PoiUtil.mergerRegion()方法时,targetSheet或者sourceSheet不能为空,故抛出该异常!"); + } + + for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) { + CellRangeAddress oldRange = sourceSheet.getMergedRegion(i); + CellRangeAddress newRange = new CellRangeAddress( + oldRange.getFirstRow(), oldRange.getLastRow(), + oldRange.getFirstColumn(), oldRange.getLastColumn()); + targetSheet.addMergedRegion(newRange); + } + } + + /** + * 功能:重新定义HSSFColor.PINK的色值 + * + * @param workbook + * @return + */ + public static HSSFColor setMBorderColor(HSSFWorkbook workbook) { + HSSFPalette palette = workbook.getCustomPalette(); + HSSFColor hssfColor = null; + byte[] rgb = { (byte) 0, (byte) 128, (byte) 192 }; + try { + hssfColor = palette.findColor(rgb[0], rgb[1], rgb[2]); + if (hssfColor == null) { + palette.setColorAtIndex(HSSFColor.HSSFColorPredefined.PINK.getIndex(), rgb[0], rgb[1], + rgb[2]); + hssfColor = palette.getColor(HSSFColor.HSSFColorPredefined.PINK.getIndex()); + } + } catch (Exception e) { + e.printStackTrace(); + } + return hssfColor; + } + + /** + * 合并单元格,适用所有Excle版本 + * @param fRow + * @param lRow + * @param fCol + * @param lCol + * @param st + */ + public static void addMergeCell(int fRow, int lRow, int fCol, int lCol,org.apache.poi.ss.usermodel.Sheet st){ + CellRangeAddress region1 = new CellRangeAddress(fRow,lRow,fCol,lCol); + st.addMergedRegion(region1); + } + + /** + * 设置富文本格式的值,适用于Excle 2003 + * @param colNo + * @param rowNo + * @param value + * @param ws + */ + public static void setCellValue(int colNo, int rowNo, HSSFRichTextString value,HSSFSheet ws){ + HSSFRow row = ws.getRow(rowNo); + if(row==null) + row = ws.createRow(rowNo); + HSSFCell cell = row.getCell(colNo); + if(cell==null){ + cell = row.createCell(colNo); + } + HSSFCellStyle st = cell.getCellStyle(); + //NodeUtil.outInfo("st="+st + " value="value, logFile); + cell.setCellValue(value); + cell.setCellStyle(st); + } + + /** + * 设置单元格的值,适用于Excel 2007以上 + * @param colNo + * @param rowNo + * @param value + * @param ws + */ + public static void setCellValue(int colNo, int rowNo, HSSFRichTextString value,XSSFSheet ws){ + XSSFRow row = ws.getRow(rowNo); + if(row==null) + row = ws.createRow(rowNo); + XSSFCell cell = row.getCell(colNo); + if(cell==null){ + cell = row.createCell(colNo); + } + XSSFCellStyle st = cell.getCellStyle(); + //NodeUtil.outInfo("st="+st + " value="value, logFile); + cell.setCellValue(value); + cell.setCellStyle(st); + } + + /** + * 设置单元格的值,适用于Excel 2003 + * @param colNo + * @param rowNo + * @param value + * @param ws + */ + public static void setCellValue(int colNo, int rowNo, String value,HSSFSheet ws){ + if(value==null) value = ""; + HSSFRow row = ws.getRow(rowNo); + if(row==null) + row = ws.createRow(rowNo); + HSSFCell cell = row.getCell(colNo); + if(cell==null){ + cell = row.createCell(colNo); + } + HSSFCellStyle st = cell.getCellStyle(); + //NodeUtil.outInfo("st="+st + " value="value, logFile); + cell.setCellValue(value); + cell.setCellStyle(st); + } + + /** + * 设置单元格的值 + * @param colNo + * @param rowNo + * @param value + * @param ws + */ + public static void setCellValue(int colNo, int rowNo, String value,XSSFSheet ws){ + if(value==null) value = ""; + XSSFRow row = ws.getRow(rowNo); + if(row==null) + row = ws.createRow(rowNo); + XSSFCell cell = row.getCell(colNo); + if(cell==null){ + cell = row.createCell(colNo); + } + XSSFCellStyle st = cell.getCellStyle(); + //NodeUtil.outInfo("st="+st + " value="value, logFile); + cell.setCellValue(value); + cell.setCellStyle(st); + } + + // 测试 + public static void main(String[] args) { + try { + fileCopy(new File("D:\\harry\\project\\bestseller\\Integration\\AD\\StickerTemplate.xls"), + new File("D:\\harry\\project\\bestseller\\Integration\\AD\\Sticker1.xls")); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/ExportExcel.java b/commons/src/main/java/com/centricsoftware/commons/utils/ExportExcel.java new file mode 100644 index 0000000..8c25d46 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/ExportExcel.java @@ -0,0 +1,1057 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.centricsoftware.commons.utils; + +import cn.hutool.core.util.NumberUtil; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.ImageUtils; +import org.apache.poi.ss.util.RegionUtil; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.*; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol; + +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.io.*; +import java.util.List; +import java.util.*; + +/** + * 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) + * + * @author jeeplus + * @version 2016-04-21 + */ +@Slf4j +public class ExportExcel { + public String fileName = "fileName.xlsx";//导出文件名 + + /** + * 工作薄对象 + */ + private SXSSFWorkbook wb; + /** + * 原始工作薄对象 + */ + private XSSFWorkbook srcWb; + /** + * 工作表对象 + */ + private Sheet sheet; + /** + * 模板sheet + */ + private Sheet templateSheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 无任何参数,创建一个空的excel类 + */ + public ExportExcel() { + XSSFWorkbook workbook = new XSSFWorkbook(); + this.wb = new SXSSFWorkbook(workbook); + this.sheet = workbook.createSheet("Export"); + this.styles = createStyles(wb); + this.templateSheet = this.sheet; + } + + /** + * 读取excel模板,创建excel类 + * + * @param wb + * @param sheet + */ + public ExportExcel(XSSFWorkbook wb, Sheet sheet) { + this.wb = new SXSSFWorkbook(wb); + this.srcWb = wb; + this.sheet = sheet; + this.styles = createStyles(this.wb); + this.templateSheet = sheet; + } + + /** + * 构造函数 + * + * @param title 表格标题,传“空值”,表示无标题 + * @param headers 表头数组 + */ + public ExportExcel(String title, String[] headers) { + XSSFWorkbook workbook = new XSSFWorkbook(); + this.wb = new SXSSFWorkbook(workbook); + this.sheet = workbook.createSheet("Export"); + this.styles = createStyles(this.wb); + this.templateSheet = this.sheet; + initialize(title, Lists.newArrayList(headers)); + } + + /** + * 构造函数 + * + * @param title 表格标题,传“空值”,表示无标题 + * @param headerList 表头列表 + */ + public ExportExcel(String title, List headerList) { + XSSFWorkbook workbook = new XSSFWorkbook(); + this.wb = new SXSSFWorkbook(workbook); + this.sheet = workbook.createSheet("Export"); + this.styles = createStyles(this.wb); + this.templateSheet = this.sheet; + initialize(title, headerList); + } + + public Sheet getSheet() { + return this.sheet; + } + + public void setSheet(Sheet sheet) { + this.sheet = sheet; + } + + public void setRowNum(int rownum) { + this.rownum = rownum; + } + + public SXSSFWorkbook getWb() { + return this.wb; + } + + public XSSFWorkbook getSrcWb() { + return srcWb; + } + + public Sheet getTemplateSheet() { + return templateSheet; + } + + /** + * 初始化函数 + * + * @param title 表格标题,传“空值”,表示无标题 + * @param headerList 表头列表 + */ + public void initialize(String title, List headerList) { + this.rownum = 0; + // Create title + if (StringUtils.isNotBlank(title)) { + Row titleRow = sheet.createRow(rownum++); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), + titleRow.getRowNum(), titleRow.getRowNum(), headerList.size() - 1)); + } + // Create header + if (headerList == null) { + throw new RuntimeException("headerList not null!"); + } + Row headerRow = sheet.createRow(rownum++); + headerRow.setHeightInPoints(16); + for (int i = 0; i < headerList.size(); i++) { + Cell cell = headerRow.createCell(i); + cell.setCellStyle(styles.get("header")); + String[] ss = StringUtils.split(headerList.get(i), "**", 2); + if (ss.length == 2) { + cell.setCellValue(ss[0]); + Comment comment = this.sheet.createDrawingPatriarch().createCellComment( + new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6)); + comment.setString(new XSSFRichTextString(ss[1])); + cell.setCellComment(comment); + } else { + cell.setCellValue(headerList.get(i)); + } + sheet.autoSizeColumn(i); + } + for (int i = 0; i < headerList.size(); i++) { + int colWidth = sheet.getColumnWidth(i) * 2; + sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth); + } + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) { + Map styles = new HashMap(); + + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("微软雅黑"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setWrapText(true); //自动换行 + Font dataFont = wb.createFont(); + dataFont.setFontName("微软雅黑"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.LEFT); + styles.put("data1", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + styles.put("data2", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.RIGHT); + styles.put("data3", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); +// style.setWrapText(true); + style.setAlignment(HorizontalAlignment.CENTER); + style.setFillForegroundColor(IndexedColors.LIGHT_TURQUOISE.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("微软雅黑"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(IndexedColors.BLACK.getIndex()); + style.setFont(headerFont); + styles.put("header", style); + + return styles; + } + + + /** + * 添加一行 + * + * @return 行对象 + */ + public Row addRow() { + return sheet.createRow(rownum++); + } + + /** + * 在指定位置插入一行,下方行向下移 + * + * @param rowIndex + * @return + */ + public Row insertRow(int rowIndex) { + Row row; + if (sheet.getRow(rowIndex) != null) { + int lastRowNo = sheet.getLastRowNum(); + sheet.shiftRows(rowIndex, lastRowNo, 1); + } + row = sheet.createRow(rowIndex); + return row; + } + + /** + * 添加一个单元格 + * + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val) { + return this.addCell(row, column, val, 0); + } + + /** + * 添加一个单元格 + * + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @param align 对齐方式(1:靠左;2:居中;3:靠右) + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val, int align) { + CellStyle style = styles.get("data" + (align >= 1 && align <= 3 ? align : "")); + return addCell(row, column, val, style); + } + + /** + * 添加一个单元格 + * + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @param style 单元格样式 + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val, CellStyle style) { + Cell cell = row.createCell(column); + try { + if (val == null) { + cell.setCellValue(""); + } else if (val instanceof Integer) { + cell.setCellValue((Integer) val); + //cell.setCellType(CellType.NUMERIC); + } else if (val instanceof Long) { + cell.setCellValue((Long) val); + } else if (val instanceof Double) { + cell.setCellValue((Double) val); + } else if (val instanceof Float) { + cell.setCellValue(Double.parseDouble(val.toString())); + } else if (val instanceof Date) { + cell.setCellValue((Date) val); + } else { + cell.setCellValue((String) val); + //cell.setCellType(CellType.STRING); + } + } catch (Exception ex) { + cell.setCellValue(val.toString()); + } + if (style != null) + cell.setCellStyle(style); + return cell; + } + + /** + * 输出数据流 + * + * @param os 输出数据流 + */ + public ExportExcel write(OutputStream os) throws IOException { + wb.write(os); + return this; + } + + /** + * 输出到客户端 + * + * @param fileName 输出文件名 + */ + public ExportExcel write(HttpServletResponse response, String fileName) throws IOException { + response.reset(); + response.setContentType("application/octet-stream; charset=utf-8"); + response.setHeader("Content-Disposition", "attachment; filename=" + EncodeUtils.urlEncode(fileName)); + write(response.getOutputStream()); + return this; + } + + /** + * 输出到文件 + * + * @param name 输出文件名 + */ + public ExportExcel writeFile(String name) { + File file = new File(name); + if (!file.getParentFile().exists()) + file.getParentFile().mkdirs(); + try (FileOutputStream os = new FileOutputStream(file)) { + this.write(os); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return this; + } + + /** + * 清理临时文件 + */ + public ExportExcel dispose() { + wb.dispose(); + return this; + } + + /** + * 当导出内容不是entity时,调用此方法 + * + * @param dataList + * @return + */ + public ExportExcel setCustomDataList(List> dataList) { + if (dataList != null && dataList.size() > 0) { + for (List list : dataList) { + Row row = this.addRow(); + for (int i = 0; i < list.size(); i++) { + this.addCell(row, i, list.get(i)); + } + } + } + return this; + } + + public CellStyle getStyle(String name) { + if (styles != null) { + return styles.get(name); + } + return null; + } + + /** + * 合并单元格 + * @param firstRow + * @param lastRow + * @param firstCol + * @param lastCol + * @return + */ + public int addMergedRegion(int firstRow, int lastRow, int firstCol, int lastCol) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRow, lastRow, firstCol, lastCol); + RegionUtil.setBorderBottom(BorderStyle.THIN, cellRangeAddress, this.sheet); + RegionUtil.setBorderLeft(BorderStyle.THIN, cellRangeAddress, this.sheet); + RegionUtil.setBorderRight(BorderStyle.THIN, cellRangeAddress, this.sheet); + RegionUtil.setBorderTop(BorderStyle.THIN, cellRangeAddress, this.sheet); + return this.sheet.addMergedRegion(cellRangeAddress); + } + + + /** + * 行复制功能 + * + * @param fromRow + * @param toRow + */ + public void copyRow(Row fromRow, Row toRow, boolean copyValueFlag) { + toRow.setHeight(fromRow.getHeight()); + for (int i = 0; i < sheet.getNumMergedRegions(); i++) { + CellRangeAddress cellRangeAddress = sheet.getMergedRegion(i); + if (cellRangeAddress.getFirstRow() == fromRow.getRowNum()) { + CellRangeAddress newCellRangeAddress = new CellRangeAddress(toRow.getRowNum(), (toRow.getRowNum() + + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())), cellRangeAddress + .getFirstColumn(), cellRangeAddress.getLastColumn()); + sheet.addMergedRegion(newCellRangeAddress); + } + } + for (Iterator cellIt = fromRow.cellIterator(); cellIt.hasNext(); ) { + Cell tmpCell = (Cell) cellIt.next(); + Cell newCell = toRow.createCell(tmpCell.getColumnIndex()); + copyCell(tmpCell, newCell, copyValueFlag); + } + + } + + public void copyRow(Sheet fromSheet, Row fromRow, Sheet toSheet, Row toRow, boolean copyValueFlag) { + toRow.setHeight(fromRow.getHeight()); + for (int i = 0; i < fromSheet.getNumMergedRegions(); i++) { + CellRangeAddress cellRangeAddress = fromSheet.getMergedRegion(i); + if (cellRangeAddress.getFirstRow() == fromRow.getRowNum()) { + CellRangeAddress newCellRangeAddress = new CellRangeAddress(toRow.getRowNum(), (toRow.getRowNum() + + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())), cellRangeAddress + .getFirstColumn(), cellRangeAddress.getLastColumn()); + toSheet.addMergedRegion(newCellRangeAddress); + } + } + for (Iterator cellIt = fromRow.cellIterator(); cellIt.hasNext(); ) { + Cell tmpCell = (Cell) cellIt.next(); + Cell newCell = toRow.createCell(tmpCell.getColumnIndex()); + copyCell(tmpCell, newCell, copyValueFlag); + } + } + + /** + * 复制单元格 + * + * @param srcCell + * @param distCell + * @param copyValueFlag true则连同cell的内容一起复制 + */ + public void copyCell(Cell srcCell, Cell distCell, boolean copyValueFlag) { + CellStyle newstyle; + CellStyle srcStyle = srcCell.getCellStyle(); + if (this.styles.containsKey(srcStyle.toString())) { + newstyle = this.styles.get(srcStyle.toString()); + } else { + newstyle = wb.createCellStyle(); + newstyle.cloneStyleFrom(srcCell.getCellStyle()); + this.styles.put(srcStyle.toString(), newstyle); + } + //样式 + distCell.setCellStyle(newstyle); + //评论 + if (srcCell.getCellComment() != null) { + distCell.setCellComment(srcCell.getCellComment()); + } + // 不同数据类型处理 + CellType srcCellType = srcCell.getCellType(); + //distCell.setCellType(srcCellType); + if (copyValueFlag) { + if (srcCellType == CellType.NUMERIC) { + if (DateUtil.isCellDateFormatted(srcCell)) { + distCell.setCellValue(srcCell.getDateCellValue()); + } else { + distCell.setCellValue(srcCell.getNumericCellValue()); + } + } else if (srcCellType == CellType.STRING) { + distCell.setCellValue(srcCell.getRichStringCellValue()); + } else if (srcCellType == CellType.BLANK) { + // nothing21 + } else if (srcCellType == CellType.BOOLEAN) { + distCell.setCellValue(srcCell.getBooleanCellValue()); + } else if (srcCellType == CellType.ERROR) { + distCell.setCellErrorValue(srcCell.getErrorCellValue()); + } else if (srcCellType == CellType.FORMULA) { + distCell.setCellFormula(srcCell.getCellFormula()); + } else { // nothing29 + } + } + } + + public void copySheet(Sheet fromSheet, Sheet toSheet) { + // 处理合并单元格 + for (int i = 0; i < fromSheet.getNumMergedRegions(); i++) { + CellRangeAddress cellRangeAddress = fromSheet.getMergedRegion(i); + toSheet.addMergedRegion(cellRangeAddress); + } + for (Iterator rowIt = fromSheet.rowIterator(); rowIt.hasNext(); ) { + Row fromRow = (Row) rowIt.next(); + Row toRow = toSheet.createRow(fromRow.getRowNum()); + // 复制列宽 + if (fromRow.getRowNum() == 0) { + for (Iterator cellIt = fromRow.cellIterator(); cellIt.hasNext(); ) { + Cell tmpCell = (Cell) cellIt.next(); + int colIndex = tmpCell.getColumnIndex(); + toSheet.setColumnWidth(colIndex, fromSheet.getColumnWidth(colIndex)); + } + } + toRow.setHeight(fromRow.getHeight()); + for (Iterator cellIt = fromRow.cellIterator(); cellIt.hasNext(); ) { + Cell tmpCell = (Cell) cellIt.next(); + Cell newCell = toRow.createCell(tmpCell.getColumnIndex()); + copyCell(tmpCell, newCell, true); + } + } + } + + /** + * 删除指定位置的合并单元格 + * + * @author liaochangjiang + * @since 2022-01-11 11:05:50 + */ + public void removeMergedRegion(Sheet sheet, int row, int column) { + for (int i = 0; i < sheet.getNumMergedRegions(); i++) { + CellRangeAddress ca = sheet.getMergedRegion(i); + int firstColumn = ca.getFirstColumn(); + int lastColumn = ca.getLastColumn(); + int firstRow = ca.getFirstRow(); + int lastRow = ca.getLastRow(); + if (row >= firstRow && row <= lastRow) { + if (column >= firstColumn && column <= lastColumn) { + sheet.removeMergedRegion(i); + } + } + } + + } + + /** + * 取消多个合并单元格 + * + * @param sheet + * @param startRow 开始行号 + * @param endRow 结束行号 + * @param startColumn 开始列号 + * @param endColumn 结束列号 + */ + public static void removeMerged(Sheet sheet, Integer startRow, Integer endRow, Integer startColumn, Integer endColumn) { + if (startRow == null) { + startRow = sheet.getFirstRowNum(); + } + if (endRow == null) { + endRow = sheet.getLastRowNum(); + } + //获取所有的单元格 + int sheetMergeCount = sheet.getNumMergedRegions(); + //用于保存要移除的那个合并单元格序号 + List indexList = new ArrayList<>(); + for (int i = 0; i < sheetMergeCount; i++) { + //获取第i个单元格 + CellRangeAddress ca = sheet.getMergedRegion(i); + int firstColumn = ca.getFirstColumn(); + int lastColumn = ca.getLastColumn(); + int firstRow = ca.getFirstRow(); + int lastRow = ca.getLastRow(); + if (startRow <= firstRow && endRow >= lastRow && startColumn <= firstColumn && endColumn >= lastColumn) { + indexList.add(i); + } + } + sheet.removeMergedRegions(indexList); + } + + /** + * startRow开始行startCol开始列colmun跨越的列数 + * excel增加列包括合并列 + * + * @param excel + * @param sheet + * @param startRow + * @param startCol + * @param colmun + */ + public void createNewCols(ExportExcel excel, XSSFSheet sheet, int startRow, + int startCol, int colmun) { + XSSFSheet templateSheet = excel.getSrcWb().getSheetAt(0); + int lastRow = templateSheet.getLastRowNum(); + for (int i = 0; i <= lastRow; i++) { + XSSFRow tmpRow = templateSheet.getRow(i); + XSSFRow toRow = sheet.getRow(startRow + i); + for (int j = 0; j < colmun; j++) { + XSSFCell cell = tmpRow.getCell(j); + if (cell != null) { + XSSFCell distCell = toRow.createCell(startCol + j); + excel.copyCell(cell, distCell, false); + // 设置列宽 + if (i == 1) { + sheet.setColumnWidth(startCol + j, templateSheet.getColumnWidth(j)); + } + } + } + } + int regions = templateSheet.getNumMergedRegions(); + for (int k = 0; k < regions; k++) { + CellRangeAddress region = templateSheet.getMergedRegion(k); + CellRangeAddress newRegion = new CellRangeAddress(region.getFirstRow() + startRow, region.getLastRow() + startRow, + region.getFirstColumn() + startCol, region.getLastColumn() + startCol); + sheet.addMergedRegion(newRegion); + } + } + + public void printSetup(Sheet fromSheet, Sheet toSheet, int zoom, boolean lanscape) { + PrintSetup printSetup = toSheet.getPrintSetup(); + printSetup.setLandscape(lanscape);//false 为横向 + printSetup.setPaperSize(fromSheet.getPrintSetup().getPaperSize()); //纸张 + toSheet.setMargin(Sheet.BottomMargin, fromSheet.getMargin(Sheet.BottomMargin));// 页边距(下) + toSheet.setMargin(Sheet.LeftMargin, fromSheet.getMargin(Sheet.LeftMargin));// 页边距(左) + toSheet.setMargin(Sheet.RightMargin, fromSheet.getMargin(Sheet.RightMargin));// 页边距(右) + toSheet.setMargin(Sheet.TopMargin, fromSheet.getMargin(Sheet.TopMargin));// 页边距(上) + toSheet.setMargin(Sheet.HeaderMargin, fromSheet.getMargin(Sheet.HeaderMargin));//页眉 + toSheet.setMargin(Sheet.FooterMargin, fromSheet.getMargin(Sheet.FooterMargin));//页脚 + toSheet.setZoom(zoom); + toSheet.setHorizontallyCenter(true);//水平居中 + toSheet.setVerticallyCenter(true);//垂直居中 + int[] rowBreaks = fromSheet.getRowBreaks(); + for (int rowBreaksIndex = 0; rowBreaksIndex < rowBreaks.length; rowBreaksIndex++) { + toSheet.setRowBreak(rowBreaks[rowBreaksIndex]); + } + printSetup.setScale(fromSheet.getPrintSetup().getScale()); + } + + public String getCellAddress(Cell cell) { + int colIndex = cell.getColumnIndex(); + // 行的序列号开始于1 + int rowIndex = cell.getRowIndex() + 1; + String colStr = null; + if (colIndex < 26) { + colStr = (char) (colIndex + 65) + ""; + } else { + char a = (char) (colIndex / 26 + 64); + char b = (char) (colIndex % 26 + 65); + colStr = a + "" + b; + } + return colStr + rowIndex; + } + + /** + * 插入缩放图片 + * @author liaochangjiang + * @since 2022-01-21 09:08:01 + */ + public void insertImageResize(InputStream in, int imageType, int col1, int row1, int col2, int row2) { + insertImageResize(0, in, imageType, 0, 0, 0, 0, col1, row1, col2, row2, -0.001); + } + + /** + * @param in 图片输入流 + * @param imageType 图片格式类型 + * @param resize 是否按图片原比例缩放 + * @param dx1 图片在起始单元格x轴坐标 + * @param dy1 图片在起始单元格y轴坐标 + * @param dx2 图片在结束单元格x轴坐标 + * @param dy2 图片在结束单元格y轴坐标 + * dx1,dy1,dx2,dy2,貌似不起效果 + * @param col1 图片左上角所在的cellNum,从0开始 + * @param row1 图片左上角所在的RowNum,从0开始 + * @param col2 图片右下角所在的cellNum,从0开始 + * @param row2 图片右下角所在的RowNum,从0开始 + */ + public void insertImage(InputStream in, int imageType, boolean resize, + int dx1, int dy1, int dx2, int dy2, + int col1, int row1, int col2, int row2){ + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] b = new byte[1024]; + int len; + while ((len = in.read(b)) != -1) { + baos.write(b, 0, len); + } + Drawing drawing = sheet.createDrawingPatriarch(); + XSSFClientAnchor anchor = new XSSFClientAnchor(dx1, dy1, dx2, dy2, + col1, row1, col2, row2); + Picture pic = drawing.createPicture(anchor, wb.addPicture(baos.toByteArray(), imageType)); + if (resize) { + double scale = getImageResizeScale( col1, row1, col2, row2,pic); + pic.resize(scale); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + public void insertImageResize(InputStream in, int imageType, + int dx1, int dy1, int dx2, int dy2, + int col1, int row1, int col2, int row2,double scale1){ + insertImageResize(0,in, imageType, dx1, dy1, dx2, dy2, col1, row1, col2, row2, scale1); + } + + /** + * + * @param dynamic 调整dx1的动态参数,如果发现图片失真,可以通过这个参数调整比例,4表示缩小4%的图片宽度 + * @param in + * @param imageType + * @param dx1 + * @param dy1 + * @param dx2 + * @param dy2 + * @param col1 + * @param row1 + * @param col2 + * @param row2 + * @param scale1 缩放比例,一旦设置,将在计算后的缩放比例上加上此值 + * @throws Exception + */ + public void insertImageResize(int dynamic,InputStream in, int imageType, + int dx1, int dy1, int dx2, int dy2, + int col1, int row1, int col2, int row2,double scale1){ + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] b = new byte[1024]; + int len; + while ((len = in.read(b)) != -1) { + baos.write(b, 0, len); + } + Drawing drawing = sheet.createDrawingPatriarch(); + XSSFClientAnchor anchor = new XSSFClientAnchor(dx1, dy1, dx2, dy2, + col1, row1, col2, row2); + XSSFPicture pic = (XSSFPicture)drawing.createPicture(anchor, wb.addPicture(baos.toByteArray(), imageType)); + XSSFPictureData data = (XSSFPictureData) pic.getPictureData(); + double scale = getImageResizeScale( col1, row1, col2, row2,pic); + scale = scale + scale1; + setAnchor(dynamic,pic,anchor,data,drawing,scale,col1, row1, col2, row2); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + public void setAnchor(int dynamic,XSSFPicture pic,XSSFClientAnchor anchor,XSSFPictureData data,Drawing drawing,double scale,int col1, int row1, int col2, int row2) throws Exception{ + XSSFClientAnchor pref = pic.getPreferredSize(scale); + int row21 = anchor.getRow1() + (pref.getRow2() - pref.getRow1()); + int col21 = anchor.getCol1() + (pref.getCol2() - pref.getCol1()); + Dimension size = ImageUtils.getImageDimension( + data.getPackagePart().getInputStream(), data.getPictureType()); + double scaledWidth = size.getWidth() * scale; + double scaledHeight = size.getHeight() * scale; + XSSFDrawing dra = (XSSFDrawing)drawing; + float w = 0;//行总的Pixels + for(int i=col1;icolumnWidthInPixels&&marginStartBoolean){ + marginStart = marginStart - columnWidthInPixels; + }else{ + marginStartBoolean = false; + } + if(w<0){ + o_col1++;//开始位置+1 + } + if(NumberUtil.round((scaledWidth),1).doubleValue()>NumberUtil.round(w,1).doubleValue()){ +// log.info(NumberUtil.round((scaledWidth+marginW),2).toString()); +// log.info(NumberUtil.round(w,2).toString()); + o_col2 ++;//结束位置+1 + }else{ + double end = scaledWidth + marginW; + double end1 = end - (w +marginW- columnWidthInPixels); + if(end1>0&&marginW!=0){ + marginEnd = end1; + }else{ + marginEnd = end; + } + break; + } + } + int dx1 = (int)(9525*(marginStart+dynamic)); + int dx2 = (int)(9525*(marginEnd)); + anchor.setDx2(dx2); + anchor.setDx1(dx1); + anchor.setCol1(o_col1); + anchor.setCol2(o_col2); + int o_row1=row1;//计算开始行位置 + int o_row2=row1;//计算结束行位置 + double h = 0; + for(int i=row1;icolumnHeightInPixels&&marginStartBoolean){ + marginStart1 = marginStart1 - columnHeightInPixels; + }else{ + marginStartBoolean = false; + } + if(h<0){ + o_row1++;//开始位置+1 + } + if(NumberUtil.round((scaledHeight),1).doubleValue()>NumberUtil.round(h,1).doubleValue()){ + o_row2 ++;//结束位置+1 + }else{ + double end = scaledHeight + marginH; + double end1 = end - (h +marginH- columnHeightInPixels); + if(end1>0&&marginH!=0){ + marginEnd1 = end1; + }else{ + marginEnd1 = end; + } + break; + } + } + int dy1 = (int)(9525*(marginStart1)); + int dy2 = (int)(9525*(marginEnd1)); + anchor.setDy2(dy2); + anchor.setDy1(dy1); + anchor.setRow1(o_row1); + anchor.setRow2(o_row2); + CTPositiveSize2D size2d = pic.getCTPicture().getSpPr().getXfrm().getExt(); + size2d.setCx((long)(scaledWidth*9525)); + size2d.setCy((long)(scaledHeight*9525)); +// log.info("c1={};c2={}",o_col1,o_col2); +// log.info("r1={};r2={}",o_row1,o_row2); +// log.info("dy1={};dy2={}",marginStart1,marginEnd1); +// log.info("dx1={};dx2={}",marginStart,marginEnd); +// log.info("-------------------------------------------------"); + } + + private float getRowHeightInPixels(int rowIndex,XSSFDrawing drawing){ + XSSFSheet sheet = (XSSFSheet)drawing.getParent(); + XSSFRow row = sheet.getRow(rowIndex); + float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); + return height*96F/72F; + } + + private float getColumnWidthInPixels(int columnIndex,XSSFDrawing drawing){ + XSSFSheet sheet = (XSSFSheet)drawing.getParent(); + CTCol col = sheet.getColumnHelper().getColumn(columnIndex, false); + double numChars = col == null || !col.isSetWidth() ? 9.140625f : col.getWidth(); + return (float)numChars*XSSFWorkbook.DEFAULT_CHARACTER_WIDTH; + } + + private float getColumnWidthInPixels(int columnIndex, XSSFSheet sheet){ + CTCol col = sheet.getColumnHelper().getColumn(columnIndex, false); + double numChars = col == null || !col.isSetWidth() ? 9.140625f : col.getWidth(); + return (float)numChars*XSSFWorkbook.DEFAULT_CHARACTER_WIDTH; + } + + /** + * 计算缩放比例 + * @param col1 + * @param row1 + * @param col2 + * @param row2 + * @return + */ + public double getImageResizeScale( int col1, int row1, int col2, int row2,Picture pic) throws Exception{ + //图片区域长宽 + float cellWidth = 0; + for (int i = col1; i < col2; i++) { +// cellWidth += sheet.getColumnWidth(i); + cellWidth += getColumnWidthInPixels(i,(XSSFSheet)sheet); + } +// cellWidth = cellWidth / 256.0F * 7.0017F;//转成像素 + float cellHeight = 0; + for (int i = row1; i < row2; i++) { + cellHeight += sheet.getRow(i).getHeightInPoints(); + } + cellHeight = cellHeight * 96.0F / 72.0F;//转成像素 + //图片原始长宽 + XSSFPictureData data = (XSSFPictureData) pic.getPictureData(); + Dimension size = ImageUtils.getImageDimension( + data.getPackagePart().getInputStream(), data.getPictureType()); + double imageWidth = size.getWidth(); + double imageHeight = size.getHeight(); + //缩放比例 + if (imageWidth > 0 && imageHeight > 0) { + double scaleWidth = cellWidth / imageWidth; + double scaleHeight = cellHeight / imageHeight; + double scale = Math.min(scaleWidth, scaleHeight); + return scale; + } + return 1.0; + } + + /** + * 生成多个sheet页 + * @param workbook + * @param sheetNum + * @param sheetTitle + * @param headerList + * @param dataList + * @param response + * @throws Exception + */ + public void createMultipleSheetExcel(SXSSFWorkbook workbook, int sheetNum, + String sheetTitle, List headerList, List> dataList, + HttpServletResponse response) throws Exception { + // 生成一个表格 + Sheet sheet = workbook.createSheet(); + workbook.setSheetName(sheetNum, sheetTitle); + Map styles = createStyles(workbook); + // Create header + if (headerList == null) { + throw new RuntimeException("headerList not null!"); + } + Row headerRow = sheet.createRow(0); + headerRow.setHeightInPoints(16); + for (int i = 0; i < headerList.size(); i++) { + Cell cell = headerRow.createCell(i); + cell.setCellStyle(styles.get("header")); + String[] ss = StringUtils.split(headerList.get(i), "**", 2); + if (ss.length == 2) { + cell.setCellValue(ss[0]); + Comment comment = this.sheet.createDrawingPatriarch().createCellComment( + new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6)); + comment.setString(new XSSFRichTextString(ss[1])); + cell.setCellComment(comment); + } else { + cell.setCellValue(headerList.get(i)); + } + sheet.autoSizeColumn(i); + } + for (int i = 0; i < headerList.size(); i++) { + int colWidth = sheet.getColumnWidth(i) * 2; + sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth); + } + + if (dataList != null && dataList.size() > 0) { + int index = 1; + for (List list : dataList) { + Row row = sheet.createRow(index); + for (int i = 0; i < list.size(); i++) { + addCell(row, i, list.get(i)); + } + index++; + } + } + } + + /** + * 读取excel里的字段转为字符串 + */ + public static String getStringCellValue(Row values, int column) { + Cell cell = values.getCell(column); + if (cell == null) { + return ""; + } + //if (cell.getCellType() != CellType.STRING) { + // cell.setCellType(CellType.STRING); + //} + return cell.getStringCellValue(); + } + + /** + * 读取excel文件 + */ + public static Workbook getExcel(InputStream inputStream, String fileName) throws IOException { + Workbook workbook; + try { + if (null == fileName) { + return null; + } + if (fileName.endsWith(".xls")) { + workbook = new HSSFWorkbook(inputStream); + } else if (fileName.endsWith(".xlsx")) { + workbook = new XSSFWorkbook(inputStream); + } else { + throw new IOException("The document type not support"); + } + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + log.error(e.getLocalizedMessage(), e); + } + } + } + return workbook; + } + + /** + * 复制模板-指向对应的开始行和结束行 + * @author liaochangjiang + * @since 2024-01-30 10:11 + */ + public void copyRows(int start, int end, ExportExcel excel, XSSFSheet fromSheet, XSSFSheet toSheet) { + for (int i = start-1; i < end; i++) { + Row fromRow = fromSheet.getRow(i); + Row toRow = toSheet.createRow(toSheet.getLastRowNum() + 1); + excel.copyRow(fromSheet, fromRow, toSheet, toRow, true); + } + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/FileUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/FileUtil.java new file mode 100644 index 0000000..9ee0d09 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/FileUtil.java @@ -0,0 +1,267 @@ +package com.centricsoftware.commons.utils; + +import jcifs.util.Base64; +import org.apache.commons.fileupload.disk.DiskFileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.io.IOUtils; +import org.apache.http.entity.ContentType; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.commons.CommonsMultipartFile; + +import java.io.*; +import java.net.FileNameMap; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; + +/** + * 从网络获取图片到本地 + * 同类方法{@link cn.hutool.http.HttpUtil} + * @author Harry + * @version 1.0 + * @since + */ +public class FileUtil { + /** + * 测试 + * @param args + */ + public static void main(String[] args) { + String url = "http://www.baidu.com/img/baidu_sylogo1.gif"; + String dest = "D:\\test\\image"; +// String fileName = downloadImage(url,"D:\\test\\image"); +// System.out.println(fileName); +// HttpUtil.downloadFile(url,dest); + } + + public static String downloadImage(String url,String filePath){ + if(!"".equals(url) && url != null){ + byte[] btImg = getImageFromNetByUrl(url); + if(null != btImg && btImg.length > 0){ + String fileName = url.substring(url.lastIndexOf("/")+1); + writeImageToDisk(btImg, filePath,fileName); + return filePath+fileName; + } + } + return null; + } + /** + * 将图片写入到磁盘 + * @param img 图片数据流 + * @param fileName 文件保存时的名称 + */ + public static void writeImageToDisk(byte[] img, String filePath, String fileName){ + try { + File folder = new File(filePath); + folder.mkdirs(); + File file = new File(filePath+fileName); + FileOutputStream fops = new FileOutputStream(file); + fops.write(img); + fops.flush(); + fops.close(); + System.out.println("图片已经写入"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 将图片写入到磁盘 + * @param img 图片数据流 + * @param filePath 文件保存时的路径 + */ + public static void writeImageToDisk(byte[] img, String filePath){ + try { + File file = new File(filePath); + FileOutputStream fops = new FileOutputStream(file); + fops.write(img); + fops.flush(); + fops.close(); + System.out.println("图片已经写入"); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** + * 根据地址获得数据的字节流 + * @param strUrl 网络连接地址 + * @return + */ + public static byte[] getImageFromNetByUrl(String strUrl){ + try { + URL url = new URL(strUrl); + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5 * 1000); + InputStream inStream = conn.getInputStream();//通过输入流获取图片数据 + byte[] btImg = readInputStream(inStream);//得到图片的二进制数据 + return btImg; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + /** + * 从输入流中获取数据 + * @param inStream 输入流 + * @return + * @throws Exception + */ + public static byte[] readInputStream(InputStream inStream) throws Exception{ + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while( (len=inStream.read(buffer)) != -1 ){ + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toByteArray(); + } + + public static void write2File(String filename,String str){ + try { + String strEncode = Base64.encode(str.getBytes()); + byte[] bytes = strEncode.getBytes(); + File file = new File(filename); + FileOutputStream fops = new FileOutputStream(file); + fops.write(bytes); + fops.flush(); + fops.close(); + } catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + } + } + + public static String byte2hex(byte[] b) // 二进制转字符串 + { + StringBuffer sb = new StringBuffer(); + String stmp = ""; + for (int n = 0; n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0XFF); + if (stmp.length() == 1){ + sb.append("0" + stmp); + }else{ + sb.append(stmp); + } + + } + return sb.toString(); + } + + public static byte[] hex2byte(String str) { // 字符串转二进制 + if (str == null) + return null; + str = str.trim(); + int len = str.length(); + if (len == 0 || len % 2 == 1) + return null; + byte[] b = new byte[len / 2]; + try { + for (int i = 0; i < str.length(); i += 2) { + b[i / 2] = (byte) Integer.decode("0X" + str.substring(i, i + 2)).intValue(); + } + return b; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static MultipartFile getMultipartFile(InputStream is, String fileName){ + return getMultipartFile(is,fileName,"file"); + } + + public static MultipartFile getMultipartFile(InputStream is, String fileName,String item){ + String dest = "c:/temp/" + fileName; + File file = cn.hutool.core.io.FileUtil.writeFromStream(is, new File(dest)); + return getMultipartFile(file,item); + } + + /** + * 输入流转MultipartFile + * + * @param fileName + * @param inputStream + * @return + */ + public static MultipartFile getMultipartFile(String fileName, InputStream inputStream) { + MultipartFile multipartFile = null; + try { + multipartFile = new MockMultipartFile(fileName, fileName, + ContentType.APPLICATION_OCTET_STREAM.toString(), inputStream); + } catch (Exception e) { + e.printStackTrace(); + } + return multipartFile; + } + + /** + * 通过file获取MultipartFile + * 更新记录:2025-02-25 xulin.xie 修复png透明底变更黑色底的问题 + * @param file + * @return + */ + public static MultipartFile getMultipartFile(File file,String item) { +// DiskFileItem fileItem = (DiskFileItem) new DiskFileItemFactory().createItem("file", +// MediaType.ALL_VALUE, true, file.getName()); + DiskFileItem fileItem = (DiskFileItem) new DiskFileItemFactory().createItem(item, + MediaType.ALL_VALUE, true, file.getName()); + + try (InputStream input = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()) { + IOUtils.copy(input, os); + }catch (Exception e){ + throw new IllegalArgumentException("Invalid file: " + e, e); + } + MultipartFile multi = new CommonsMultipartFile(fileItem); + return multi; + } + + + /** + * 通过inputStream获取MultipartFile + * + * @param inputstream 输入流 + * @return + */ + public static MultipartFile getMultipartFileByStream(InputStream inputstream, String item) { +// DiskFileItem fileItem = (DiskFileItem) new DiskFileItemFactory().createItem("file", +// MediaType.ALL_VALUE, true, file.getName()); + DiskFileItem fileItem = (DiskFileItem) new DiskFileItemFactory().createItem(item, + MediaType.ALL_VALUE, true, "file"); + + + try (OutputStream os = fileItem.getOutputStream()) { + IOUtils.copy(inputstream, os); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid file: " + e, e); + } + return new CommonsMultipartFile(fileItem); + } + + public static MultipartFile getNewMultipartFile(File file,String item) { + try { + FileInputStream input = new FileInputStream(file); + FileNameMap fileNameMap = URLConnection.getFileNameMap(); + String mimeType = fileNameMap.getContentTypeFor(file.getName()); + String contentType = MediaType.ALL_VALUE; + if ("image/png".equals(mimeType)) { + contentType = MediaType.IMAGE_PNG_VALUE; + } + MockMultipartFile multipartFile = new MockMultipartFile( + "file", + file.getName(), + contentType, + input + ); + return multipartFile; + } catch (Exception e){ + e.printStackTrace(); + } + return null; + } + + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/FtpUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/FtpUtil.java new file mode 100644 index 0000000..6c7117c --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/FtpUtil.java @@ -0,0 +1,198 @@ +package com.centricsoftware.commons.utils; + +import cn.hutool.extra.ftp.Ftp; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; + +import java.io.*; +import java.net.SocketException; +/** + * Ftp上传下载工具类 + * 同类方法 {@link cn.hutool.extra.ftp.Ftp} + * @author zheng.gong + * @date 2020/4/27 + */ +public class FtpUtil { + + public static void main(String[] args) throws IOException { + //匿名登录(无需帐号密码的FTP服务器) + Ftp ftp = new Ftp("172.0.0.1"); + //进入远程目录 + ftp.cd("/opt/upload"); + //上传本地文件 + ftp.upload("/opt/upload", cn.hutool.core.io.FileUtil.file("e:/test.jpg")); + //下载远程文件 + ftp.download("/opt/upload", "test.jpg", cn.hutool.core.io.FileUtil.file("e:/test2.jpg")); + + //关闭连接 + ftp.close(); + } + + /** + * 获取FTPClient对象 + * + * @param ftpHost + * FTP主机服务器 + * @param ftpPassword + * FTP 登录密码 + * @param ftpUserName + * FTP登录用户名 + * @param ftpPort + * FTP端口 默认为21 + * @return + */ + public static FTPClient getFTPClient(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort) { + FTPClient ftpClient = new FTPClient(); + try { + ftpClient = new FTPClient(); + ftpClient.connect(ftpHost, ftpPort);// 连接FTP服务器 + ftpClient.login(ftpUserName, ftpPassword);// 登陆FTP服务器 + if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { + System.out.println("未连接到FTP,用户名或密码错误。"); + ftpClient.disconnect(); + } else { + System.out.println("FTP连接成功。"); + } + } catch (SocketException e) { + e.printStackTrace(); + System.out.println("FTP的IP地址可能错误,请正确配置。"); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("FTP的端口错误,请正确配置。"); + } + return ftpClient; + } + + /* + * 从FTP服务器下载文件 + * + * @param ftpHost FTP IP地址 + * + * @param ftpUserName FTP 用户名 + * + * @param ftpPassword FTP用户名密码 + * + * @param ftpPort FTP端口 + * + * @param ftpPath FTP服务器中文件所在路径 格式: ftptest/aa + * + * @param localPath 下载到本地的位置 格式:H:/download + * + * @param fileName 文件名称 + */ + public static String downloadFtpFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, + String ftpPath, String localPath, String fileName) { + + FTPClient ftpClient = null; + + try { + ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort); + ftpClient.setControlEncoding("UTF-8"); // 中文支持 + ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); + ftpClient.enterLocalPassiveMode(); + ftpClient.changeWorkingDirectory(ftpPath); + + File localFile = new File(localPath + File.separatorChar + fileName); + OutputStream os = new FileOutputStream(localFile); + ftpClient.retrieveFile(fileName, os); + os.close(); + ftpClient.logout(); + + } catch (FileNotFoundException e) { + System.out.println("没有找到" + ftpPath + "文件"); + e.printStackTrace(); + } catch (SocketException e) { + System.out.println("连接FTP失败."); + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("文件读取错误。"); + e.printStackTrace(); + } + + return localPath + File.separatorChar + fileName; + } + + /** + * Description: 向FTP服务器上传文件 + * + * @param ftpHost + * FTP服务器hostname + * @param ftpUserName + * 账号 + * @param ftpPassword + * 密码 + * @param ftpPort + * 端口 + * @param ftpPath + * FTP服务器中文件所在路径 格式: ftptest/aa + * @param fileName + * ftp文件名称 + * @param input + * 文件流 + * @return 成功返回true,否则返回false + */ + public static boolean uploadFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, + String ftpPath, String fileName, InputStream input) { + boolean success = false; + FTPClient ftpClient = null; + try { + int reply; + ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort); + reply = ftpClient.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + ftpClient.disconnect(); + return success; + } + ftpClient.setControlEncoding("UTF-8"); // 中文支持 + ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); + ftpClient.enterLocalPassiveMode(); + ftpClient.changeWorkingDirectory(ftpPath); + + ftpClient.storeFile(fileName, input); + + input.close(); + ftpClient.logout(); + success = true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (ftpClient.isConnected()) { + try { + ftpClient.disconnect(); + } catch (IOException ioe) { + } + } + } + return success; + } + + /** + * 根据文件前缀找出ftp服务器上的完整文件名 + **/ + public static String getFileName(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, String ftpPath, String prefix) { + FTPClient ftpClient = null; + String filename = ""; + try { + ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort); + ftpClient.setControlEncoding("UTF-8"); // 中文支持 + System.out.println("Path is:" + ftpPath); + FTPFile[] files = ftpClient.listFiles(ftpPath); + + for (FTPFile file : files) { + if (file.isFile()) { + if (file.getName().startsWith(prefix)) { + filename = file.getName(); + break; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return filename; + } + + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/ImageUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/ImageUtil.java new file mode 100644 index 0000000..0b5008f --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/ImageUtil.java @@ -0,0 +1,121 @@ +package com.centricsoftware.commons.utils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +/** + * 图片工具类 + * 同类方法{@link cn.hutool.core.img.ImgUtil} + * @author zheng.gong + * @date 2020/4/27 + */ +public class ImageUtil { + + public static void resize(String filePath, int height, int width) { + try { + // Thumbnails.of(filePath).size(1024, 1024).toFile(filePath); + resizePng(new File(filePath), width, height, true); + File f = new File(filePath); + BufferedImage bi = ImageIO.read(f); + Image itemp = bi.getScaledInstance(bi.getWidth(), bi.getHeight(), BufferedImage.SCALE_SMOOTH); + BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // 获取Graphics2D + Graphics2D g2d = newImage.createGraphics(); + // ---------- 增加下面的代码使得背景透明 ----------------- + newImage = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT); + g2d.dispose(); + g2d = newImage.createGraphics(); + // ---------- 背景透明代码结束 ----------------- + if (width == itemp.getWidth(null)) { + g2d.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2, itemp.getWidth(null), + itemp.getHeight(null), null, null); + } else { + g2d.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0, itemp.getWidth(null), itemp.getHeight(null), + null, null); + } + g2d.dispose(); + ImageIO.write(newImage, "png", f); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void resizePng(File fromFile, int outputWidth, int outputHeight, boolean proportion) { + try { + BufferedImage bi2 = ImageIO.read(fromFile); + int newWidth; + int newHeight; + // 判断是否是等比缩放 + if (proportion) { + // 为等比缩放计算输出的图片宽度及高度 + double rate1 = ((double) bi2.getWidth(null)) / (double) outputWidth; + double rate2 = ((double) bi2.getHeight(null)) / (double) outputHeight; + // 根据缩放比率大的进行缩放控制 + double rate = rate1 > rate2 ? rate1 : rate2; + newWidth = (int) ((bi2.getWidth(null)) / rate); + newHeight = (int) ((bi2.getHeight(null)) / rate); + } else { + newWidth = outputWidth; // 输出的图片宽度 + newHeight = outputHeight; // 输出的图片高度 + } + BufferedImage to = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = to.createGraphics(); + to = g2d.getDeviceConfiguration().createCompatibleImage(newWidth, newHeight, Transparency.TRANSLUCENT); + g2d.dispose(); + g2d = to.createGraphics(); + @SuppressWarnings("static-access") + Image from = bi2.getScaledInstance(newWidth, newHeight, bi2.SCALE_AREA_AVERAGING); + g2d.drawImage(from, 0, 0, null); + g2d.dispose(); + ImageIO.write(to, "png", fromFile); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public static void fillInSquare(String filePath) { + + try { + File f = new File(filePath); + BufferedImage bi = ImageIO.read(f); + + int width = bi.getWidth(); + int height = bi.getHeight(); + // Image itemp = bi.getScaledInstance(width, height, + // BufferedImage.SCALE_SMOOTH); + Image itemp = bi; + BufferedImage image = null; + + if (width >= height) { + image = new BufferedImage(width, width, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, width, width); + g.drawImage(itemp, 0, (width - height) / 2, width, height, Color.white, null); + g.dispose(); + } else { + image = new BufferedImage(height, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + g.setColor(Color.white); + g.fillRect(0, 0, height, height); + g.drawImage(itemp, (height - width) / 2, 0, width, height, Color.white, null); + g.dispose(); + } + + itemp = image; + + ImageIO.write((BufferedImage) itemp, "png", f); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void main(String[] args) { + fillInSquare("C:\\C8\\Temp\\KA98308-11-03.jpg"); + } + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/ImportExcel.java b/commons/src/main/java/com/centricsoftware/commons/utils/ImportExcel.java new file mode 100644 index 0000000..c9c5ce7 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/ImportExcel.java @@ -0,0 +1,257 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.centricsoftware.commons.utils; + +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFDateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 导入Excel文件(支持“XLS”和“XLSX”格式) + * @author jeeplus + * @version 2016-03-10 + */ +public class ImportExcel { + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 标题行号 + */ + @Getter + private int headerNum; + + /** + * 构造函数 + * @param fileName 导入文件,读取第一个工作表 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(String fileName, int headerNum) + throws InvalidFormatException, IOException { + this(new File(fileName), headerNum); + } + + /** + * 构造函数 + * @param file 导入文件对象,读取第一个工作表 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(File file, int headerNum) + throws InvalidFormatException, IOException { + this(file, headerNum, 0); + } + + /** + * 构造函数 + * @param fileName 导入文件 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(String fileName, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + this(new File(fileName), headerNum, sheetIndex); + } + + /** + * 构造函数 + * @param file 导入文件对象 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(File file, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + this(file.getName(), new FileInputStream(file), headerNum, sheetIndex); + } + + /** + * 构造函数 + * @param multipartFile 导入文件对象 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex); + } + + /** + * 构造函数 + * @param fileName 导入文件对象 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + if (StringUtils.isBlank(fileName)){ + throw new RuntimeException("导入文档为空!"); + }else if(fileName.toLowerCase().endsWith("xls")){ + this.wb = new HSSFWorkbook(is); + }else if(fileName.toLowerCase().endsWith("xlsx")){ + this.wb = new XSSFWorkbook(is); + }else{ + throw new RuntimeException("文档格式不正确!"); + } + if (this.wb.getNumberOfSheets()> getCustomDataList(){ + List> dataList = new ArrayList<>(); + for (int i = this.getDataRowNum(); i <= this.getLastDataRowNum(); i++) { + Row row = this.getRow(i); + List dataRow = new ArrayList<>(); + for(int j = 0; j < this.getLastCellNum(); j++){ + dataRow.add(this.getCellValue(row, j).toString()); + } + dataList.add(dataRow); + } + return dataList; + } + + /** + * 删除行 + * @param startRow 开始行 + * @param endRow 结束行 + */ + public void deleteRow(int startRow,int endRow,int n){ + this.sheet.shiftRows(startRow,endRow,n); + } + +// /** +// * 导入测试 +// */ +// public static void main(String[] args) throws Throwable { +// +// ImportExcel ei = new ImportExcel("target/export.xlsx", 1); +// +// for (int i = ei.getDataRowNum(); i < ei.getLastDataRowNum(); i++) { +// Row row = ei.getRow(i); +// for (int j = 0; j < ei.getLastCellNum(); j++) { +// Object val = ei.getCellValue(row, j); +// System.out.print(val+", "); +// } +// System.out.print("\n"); +// } +// +// } + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/IpUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/IpUtil.java new file mode 100644 index 0000000..0e82d4f --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/IpUtil.java @@ -0,0 +1,36 @@ +package com.centricsoftware.commons.utils; + + +import javax.servlet.http.HttpServletRequest; + +/** + * 获取前台的IP地址 + */ +public class IpUtil { + static final String UNKNOWN = "unknown"; + + public static String getIpAddr(HttpServletRequest request) { + if (request == null) { + return UNKNOWN; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + //System.out.println(ip); + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; + } +} \ No newline at end of file diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/JsonHelper.java b/commons/src/main/java/com/centricsoftware/commons/utils/JsonHelper.java new file mode 100644 index 0000000..e46dad2 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/JsonHelper.java @@ -0,0 +1,349 @@ +package com.centricsoftware.commons.utils; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +import java.lang.reflect.Method; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; + +/** + * + * JSON工具类 + * 同类方法{@link cn.hutool.json.JSONUtil} + * @author GHUANG + * @version 2016年3月28日 下午8:15:11 + * + * + */ +public class JsonHelper { + /** + * + * @param javaBean + * @return + * @author GHUANG + * @version 2016年3月28日 下午8:15:27 + */ + public static Map toMap(Object javaBean) { + + Map result = new HashMap(); + Method[] methods = javaBean.getClass().getDeclaredMethods(); + + for (Method method : methods) { + + try { + + if (method.getName().startsWith("get")) { + + String field = method.getName(); + field = field.substring(field.indexOf("get") + 3); + field = field.toLowerCase().charAt(0) + field.substring(1); + + Object value = method.invoke(javaBean, (Object[]) null); + result.put(field, null == value ? "" : value.toString()); + + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + return result; + + } + + public static JSONArray getJAfromList(List list) throws Exception { + JSONArray ja = new JSONArray(); + for (HashMap map : list) { + JSONObject json = new JSONObject(); + for (Entry gmap : map.entrySet()) { + String key = gmap.getKey(); + String value = gmap.getValue(); + json.put(key, value); + } + ja.put(json); + } + return ja; + } + + public static JSONObject convertJS(JSONObject js) throws Exception { + JSONObject json = new JSONObject(); + Iterator it = js.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + String value = js.getString(key); + json.put(value, key); + } + return json; + } + + public static JSONObject getJsonfromMap(HashMap map) throws Exception { + JSONObject json = new JSONObject(); + for (Entry gmap : map.entrySet()) { + String key = gmap.getKey(); + String value = gmap.getValue(); + json.put(key, value); + + } + return json; + } + + /** + * + * @param json + * @return + * @author GHUANG + * @version 2019年5月15日 上午11:13:46 + */ + public static String getJsonToXml(JSONObject json) { + Iterator it = json.keys(); + StringBuffer sb = new StringBuffer(); + while (it.hasNext()) { + String key = it.next(); + String value = json.optString(key); + try { + if (value.startsWith("[")) { + JSONArray ja = new JSONArray(value); + + sb.append("<").append(key).append(">"); + if (ja.length() > 0) { + for (int j = 0; j < ja.length(); j++) { + JSONObject sjs = ja.getJSONObject(j); + if (key.equals("JSON")) { // 去掉非 + sb.append("<").append(key).append(">"); + String sbstr = getJsonToXml(sjs); + sb.append(sbstr); + sb.append(""); + } else { + String sbstr = getJsonToXml(sjs); + sb.append(sbstr); + } + } + } + sb.append(""); + + } else if (value.startsWith("{")) { + JSONObject jsonSon = new JSONObject(value); + sb.append("<").append(key).append(">"); + sb.append(getJsonToXml(jsonSon)); + sb.append(""); + } else { + sb.append("<").append(key).append(">").append(value).append(""); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return sb.toString(); + } + + /** + * + * @param jsonString + * @return + * @throws JSONException + * @author GHUANG + * @version 2016年3月28日 下午8:15:44 + */ + public static HashMap toMap(String jsonString) throws JSONException { + + JSONObject jsonObject = new JSONObject(jsonString); + + HashMap result = new HashMap(); + Iterator iterator = jsonObject.keys(); + String key = null; + String value = null; + + while (iterator.hasNext()) { + + key = (String) iterator.next(); + value = jsonObject.getString(key); + result.put(key, value); + + } + return result; + + } + + /** + * + * @param jsonString + * @return + * @throws JSONException + * @author GHUANG + * @version 2016年3月28日 下午8:15:44 + */ + public static ArrayList toMapfromArray(String jsonString) throws JSONException { + + JSONArray jsonObject = new JSONArray(jsonString); + ArrayList infoList = new ArrayList(); + for (int i = 0; i < jsonObject.length(); i++) { + String jobj = jsonObject.getJSONObject(i).toString(); + HashMap map = JsonHelper.toMap(jobj); + // System.out.println("map="+map.toString()); + infoList.add(map); + } + + return infoList; + + } + + /** + * + * @param bean + * @return + * @author GHUANG + * @version 2016年3月28日 下午8:15:51 + */ + public static JSONObject toJSON(Object bean) { + + return new JSONObject(toMap(bean)); + + } + + /** + * + * @param javabean + * @param data + * @return + * @author GHUANG + * @version 2016年3月28日 下午8:15:57 + */ + public static Object toJavaBean(Object javabean, Map data) { + + Method[] methods = javabean.getClass().getDeclaredMethods(); + for (Method method : methods) { + + try { + if (method.getName().startsWith("set")) { + + String field = method.getName(); + field = field.substring(field.indexOf("set") + 3); + field = field.toLowerCase().charAt(0) + field.substring(1); + method.invoke(javabean, data.get(field)); + + } + } catch (Exception e) { + } + + } + + return javabean; + + } + + /** + * 通过JSONObject获取对应的key值(String) + * + * @param jsonObj + * @param key + * @return + */ + private static String getJSONString(JSONObject jsonObj, String key) { + String retString = ""; + try { + Object obj = jsonObj.get(key); + if (obj != null) { + retString = String.valueOf(obj); + } + } catch (Exception e) { + // TODO: handle exception + retString = ""; + } + return retString; + } + + /** + * + * @param javabean + * @param jsonString + * @throws ParseException + * @throws JSONException + * @author GHUANG + * @version 2016年3月28日 下午8:16:04 + */ + public static void toJavaBean(Object javabean, String jsonString) + throws ParseException, JSONException { + + JSONObject jsonObject = new JSONObject(jsonString); + + Map map = toMap(jsonObject.toString()); + + toJavaBean(javabean, map); + + } + + public static HashMap getJsonToMap(JSONObject json) throws Exception { + HashMap map = new HashMap(); + for (Iterator it = json.keys(); it.hasNext();) { + String key = (String) it.next(); + String value = json.getString(key); + map.put(key, value); + + } + return map; + } + + public static HashMap getMapfromJson(JSONObject json) throws Exception { + HashMap map = new HashMap(); + for (Iterator it = json.keys(); it.hasNext();) { + String key = (String) it.next(); + String value = json.getString(key); + map.put(key, value); + + } + return map; + } + + public static ArrayList getListfromJa(JSONArray ja) throws Exception { + ArrayList list = new ArrayList(); + for (int i = 0; i < ja.length(); i++) { + JSONObject json = ja.getJSONObject(i); + HashMap map = getMapfromJson(json); + list.add(map); + } + return list; + } + + public static JSONObject parseJS(JSONObject a, JSONObject b) throws Exception { + Iterator it = a.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + Object value = a.get(key); + if (value instanceof String) { + b.put(key, value); + } else if (value instanceof JSONArray) { + JSONArray ja = (JSONArray) value; + b.put(key, ja); + } + + } + return b; + } + + public static Date[] getJsonToDateArray(String jsonString) throws ParseException, JSONException { + + JSONArray jsonArray = new JSONArray(jsonString); + Date[] dateArray = new Date[jsonArray.length()]; + String dateString; + Date date; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + for (int i = 0; i < jsonArray.length(); i++) { + dateString = jsonArray.getString(i); + try { + date = sdf.parse(dateString); + dateArray[i] = date; + } catch (Exception e) { + e.printStackTrace(); + } + } + return dateArray; + } + +} \ No newline at end of file diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/JsonUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/JsonUtil.java new file mode 100644 index 0000000..7fa3f1e --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/JsonUtil.java @@ -0,0 +1,225 @@ +package com.centricsoftware.commons.utils; + +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +@Slf4j +public class JsonUtil { + private static ObjectMapper mapper = new ObjectMapper(); + + private static ObjectMapper NULL_STRING_MAPPER; + + /** + * 动态排除的过滤器key + */ + private static final String DYNA_EXCLUDES = "dynaExcludes"; + + @JsonFilter(DYNA_EXCLUDES) + interface DynamicExclude { + + } + + static { + // 如果存在未知属性,则忽略不报错 + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + //允许空对象转成json + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + // 允许key没有双引号 + mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); + // 允许key有单引号 + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + // 防止double变成科学计数法 + mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addDeserializer(LocalDateTime.class, + new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(CommonUtil.YMD_HMS_DASH))); + javaTimeModule.addSerializer(LocalDateTime.class, + new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(CommonUtil.YMD_HMS_DASH))); + javaTimeModule.addDeserializer(LocalDate.class, + new LocalDateDeserializer(DateTimeFormatter.ofPattern(CommonUtil.YMD_DASH))); + javaTimeModule.addSerializer(LocalDate.class, + new LocalDateSerializer(DateTimeFormatter.ofPattern(CommonUtil.YMD_DASH))); + javaTimeModule.addDeserializer(LocalTime.class, + new LocalTimeDeserializer(DateTimeFormatter.ofPattern(CommonUtil.HMS_DASH))); + javaTimeModule.addSerializer(LocalTime.class, + new LocalTimeSerializer(DateTimeFormatter.ofPattern(CommonUtil.HMS_DASH))); + mapper.registerModule(javaTimeModule); + + NULL_STRING_MAPPER = mapper.copy(); + NULL_STRING_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS); + NULL_STRING_MAPPER.getSerializerProvider().setNullValueSerializer(new JsonSerializer() { + @Override + public void serialize(Object param, JsonGenerator jsonGenerator, SerializerProvider paramSerializerProvider) + throws IOException { + jsonGenerator.writeString(""); + } + }); + } + + /** + * 转换为json字符串 + */ + public static String toJSONString(Object obj) { + if (Objects.isNull(obj)) { + return null; + } + try { + return mapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException("转换对象到json失败, 参数:" + obj); + } + } + + /** + * 转换为json字符串. null会转成"" + */ + public static String toJSONStringNull2Empty(Object obj) { + if (Objects.isNull(obj)) { + return null; + } + try { + return NULL_STRING_MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException("转换对象到json失败, 参数:" + obj); + } + } + + /** + * 转换为json字符串, 排除忽略的字段 + */ + public static String toJSONString(Object obj, String[] ignoreFields) { + if (ArrayUtils.isEmpty(ignoreFields)) { + return toJSONString(obj); + } + try { + ObjectMapper objectMapper = mapper.copy().setFilterProvider( + new SimpleFilterProvider().addFilter(DYNA_EXCLUDES, + SimpleBeanPropertyFilter.serializeAllExcept(ignoreFields))); + objectMapper.addMixIn(obj.getClass(), DynamicExclude.class); + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException("转换对象到json失败, 参数:" + obj); + } + } + + /** + * 转换为json字符串, 排除忽略的字段 + */ + public static String toJSONStringNull2Empty(Object obj, String[] ignoreFields) { + if (ArrayUtils.isEmpty(ignoreFields)) { + return toJSONStringNull2Empty(obj); + } + try { + ObjectMapper objectMapper = NULL_STRING_MAPPER.copy().setFilterProvider( + new SimpleFilterProvider().addFilter(DYNA_EXCLUDES, + SimpleBeanPropertyFilter.serializeAllExcept(ignoreFields))); + objectMapper.addMixIn(obj.getClass(), DynamicExclude.class); + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException("转换对象到json失败, 参数:" + obj); + } + } + + /** + * 转换为json字节数组 + */ + public static byte[] toJSONBytes(Object obj) { + if (Objects.isNull(obj)) { + return new byte[0]; + } + try { + return mapper.writeValueAsBytes(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException("转换对象到json失败, 参数:" + obj, e); + } + } + + /** + * 转换为java对象 + */ + public static T parseObject(String json, Class clazz) { + if (org.apache.commons.lang3.StringUtils.isBlank(json)) { + return null; + } + try { + return mapper.readValue(json, clazz); + } catch (IOException e) { + throw new RuntimeException("转换字符串到对象失败,json: " + json); + } + } + + /** + * 转换为java对象 + */ + public static T parseObject(String json, TypeReference ref) { + if (StringUtils.isBlank(json)) { + return null; + } + try { + return mapper.readValue(json, ref); + } catch (IOException e) { + throw new RuntimeException("转换字符串到对象失败,json: " + json); + } + } + + /** + * 转换为对象集合 + */ + public static List parseArray(String json, Class clazz) { + try { + JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, clazz); + return mapper.readValue(json, javaType); + } catch (IOException e) { + throw new RuntimeException("转换字符串到对象数组失败,json: " + json); + } + } + + /** + * 转换为集合 + * + */ + @SuppressWarnings("unchecked") + public static List> parseArray(String json) { + try { + return mapper.readValue(json, List.class); + } catch (IOException e) { + throw new RuntimeException("转换字符串到对象数组失败,json: " + json); + } + } + + /** + * 获取Mapper + * + */ + public static ObjectMapper getMapper() { + return mapper; + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/MailUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/MailUtil.java new file mode 100644 index 0000000..70c7e3e --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/MailUtil.java @@ -0,0 +1,181 @@ +package com.centricsoftware.commons.utils; + +import com.centricsoftware.config.entity.CsProperties; +import lombok.extern.slf4j.Slf4j; + +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.util.Properties; + +/** + * 邮件工具类,同类型工具类{@link cn.hutool.extra.mail.Mail},按个人喜好使用即可 + * + * @ClassName: MailUtil + * @Description: 发送Email + * @author: Harry + * @date: 2016-5-30 下午9:42:56 + * + */ +@Slf4j +public class MailUtil { + + + + /** + * @param args + * @throws Exception + */ + public static void main(String[] args) throws Exception { + // sendMail("mail.bestseller.com.cn","noreply@bestseller.com.cn","PLM + // Admin","bs_stAff_123$","harry.liang@centricsoftware.com","PO:1631P0100087C质检已完成,请给出QAC决策","PO:1631P0100087C质检已完成,请给出QAC决策
PO:1631P0100087C质检已完成,请给出QAC决策"); +// testSendMail(); + } + + public static void testSendMail( String mailToUsers) throws Exception{ + CsProperties properties = SpringUtil.getBean(CsProperties.class); + System.out.println("mailToUsers=" + mailToUsers); + String poNumber = "PO12390393245"; + String qaDecision = "不合格/Fail"; + String wareHouse = "050A"; + String vDecision = "不合格/Fail"; + String mDecision = "不合格/Fail"; + String comment = "不合格/Fail"; + mailToUsers = "harry.liang@centricsoftware.com;jief@bestseller.com.cn"; + try { + if (!"".equals(mailToUsers) && !";".equals(mailToUsers)) { + String title = "PO:" + poNumber + " 已经完成质检(" + qaDecision + "),请尽快提供QAC决策. QA Inspection has been done(" + + qaDecision + "), please make QAC Decision ASAP. "; + String content = "Dear Buyer,
"; + content += "PO Number: " + poNumber + "
"; + content += "Ware House ID:" + wareHouse + "
"; + content += "QAV Result: " + vDecision + "
"; + content += "QAM Result: " + mDecision + "
"; + content += "QA Decision: " + qaDecision + "
"; + content += "QA Comments: " + comment + "
"; + MailUtil.sendMail(properties.getValue("mail.host"), properties.getValue("mail.sender"), + properties.getValue("mail.senderName"), properties.getValue("mail.senderPwd"), mailToUsers, + title, content); + System.out.println("Sent Mail to =" + mailToUsers); + } + } catch (Exception e) { + // TODO: handle exception + System.out.println("Fail to send Mail to =" + mailToUsers); + } + } + + public static void testMail() throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.host", "mail.bestseller.com.cn"); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + // 使用JavaMail发送邮件的5个步骤 + // 1、创建session + Session session = Session.getInstance(prop); + // 开启Session的debug模式,这样就可以查看到程序发送Email的运行状态 + session.setDebug(true); + // 2、通过session得到transport对象 + Transport ts = session.getTransport(); + // 3、使用邮箱的用户名和密码连上邮件服务器,发送邮件时,发件人需要提交邮箱的用户名和密码给smtp服务器,用户名和密码都通过验证之后才能够正常发送邮件给收件人。 + ts.connect("mail.bestseller.com.cn", "noreply@bestseller.com.cn", "bs_stAff_123$"); + // 4、创建邮件 + Message message = createSimpleMail(session); + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); + } + + /** + * 发送邮件 + * + * @param mailHost + * @param sender + * @param senderName + * @param senderPwd + * @param toUsers + * 多个用户使用分号分割 + * @param title + * @param content + */ + public static String sendMail(String mailHost, String sender, String senderName, String senderPwd, String toUsers, + String title, String content) { + Properties prop = new Properties(); + prop.setProperty("mail.host", mailHost); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + Transport ts; + String result = "success"; + try { + // 使用JavaMail发送邮件的5个步骤 + // 1、创建session + Session session = Session.getInstance(prop); + // 开启Session的debug模式,这样就可以查看到程序发送Email的运行状态 + session.setDebug(true); + // 2、通过session得到transport对象 + ts = session.getTransport(); + // 3、使用邮箱的用户名和密码连上邮件服务器,发送邮件时,发件人需要提交邮箱的用户名和密码给smtp服务器,用户名和密码都通过验证之后才能够正常发送邮件给收件人。 + ts.connect(mailHost, sender, senderPwd); + // 4、创建邮件 + Message message = new MimeMessage(session); + // 指明邮件的发件人 + InternetAddress address = new InternetAddress(sender); + address.setPersonal(senderName); + message.setFrom(address); + // 指明邮件的收件人,现在发件人和收件人是一样的,那就是自己给自己发 + if (toUsers.indexOf(";") >= 0) { + String[] toUsersList = toUsers.split(";"); + for (String toUser : toUsersList) { + toUser = toUser.trim(); + if (!"".equals(toUser)) { + message.addRecipient(Message.RecipientType.TO, new InternetAddress(toUser)); + } + } + } else { + message.addRecipient(Message.RecipientType.TO, new InternetAddress(toUsers)); + } + // 邮件的标题 + message.setSubject(title); + // 邮件的文本内容 + message.setContent(content, "text/html;charset=UTF-8"); + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + log.error("", e); + result = "fail"; + } + return result; + } + + /** + * @Method: createSimpleMail + * @Description: 创建一封只包含文本的邮件 + * @Anthor:Harry Liang + * + * @param session + * @return + * @throws Exception + */ + public static MimeMessage createSimpleMail(Session session) + throws Exception { + // 创建邮件对象 + MimeMessage message = new MimeMessage(session); + // 指明邮件的发件人 + InternetAddress address = new InternetAddress("noreply@bestseller.com.cn"); + address.setPersonal("PLM Admin"); + message.setFrom(address); + // 指明邮件的收件人,现在发件人和收件人是一样的,那就是自己给自己发 + message.addRecipient(Message.RecipientType.TO, new InternetAddress("harry.liang@centricsoftware.com")); + message.addRecipient(Message.RecipientType.TO, new InternetAddress("jief@bestseller.com.cn")); + // 邮件的标题 + message.setSubject("邮件发送个测试"); + // 邮件的文本内容 + message.setContent("你好啊!", "text/html;charset=UTF-8"); + // 返回创建好的邮件对象 + return message; + } +} \ No newline at end of file diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/MarkImageUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/MarkImageUtil.java new file mode 100644 index 0000000..dd5b0fc --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/MarkImageUtil.java @@ -0,0 +1,55 @@ +package com.centricsoftware.commons.utils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; + +public class MarkImageUtil { + + /** + * 添加图标在图片上 + * @author liaochangjiang + * @since 2024-02-21 11:27 + */ + public static InputStream addImgMark(Image con, int iconWidth, int iconHeight, Image img, int x, int y,int width, int height) throws IOException { + BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = bi.createGraphics(); + g.getDeviceConfiguration().createCompatibleImage(img.getWidth(null), img.getHeight(null), Transparency.TRANSLUCENT); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(img.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); + float clarity = 1f;//透明度 + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, clarity)); + g.drawImage(con.getScaledInstance(iconWidth, iconHeight, Image.SCALE_SMOOTH), x, y, null); + g.dispose(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(bi, "png", os); + // 最终返回的文件流 + return new ByteArrayInputStream(os.toByteArray()); + } + + /** + * 添加文字在图片上 + * @author liaochangjiang + * @since 2024-02-21 11:27 + */ + public static InputStream addTextMark(Image img, int x, int y,int width, int height,String martText, Color color, Font font) throws IOException { + BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = bi.createGraphics(); + g.getDeviceConfiguration().createCompatibleImage(img.getWidth(null), img.getHeight(null), Transparency.TRANSLUCENT); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(img.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); + if(null != color) { + g.setColor(color); + } + if(null != font){ + g.setFont(font); + } + g.drawString(martText, x, y); + g.dispose(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(bi, "png", os); + // 最终返回的文件流 + return new ByteArrayInputStream(os.toByteArray()); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/PageUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/PageUtil.java new file mode 100644 index 0000000..3658723 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/PageUtil.java @@ -0,0 +1,21 @@ +package com.centricsoftware.commons.utils; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.commons.dto.PlmPageReqVo; +import com.centricsoftware.config.cons.Constants; + +public class PageUtil { + /** + * 构建分页对象 + * + * @author liaochangjiang + * @since 2021-01-18 15:59:16 + */ + public static Page buildPageParam(PlmPageReqVo req, Class clazz) { + final Page page = new Page<>(req.getPageNum(), req.getPageSize()); + if (page.getSize() > Constants.PAGE_MAX_LIMIT) { + page.setMaxLimit(page.getSize()); + } + return page; + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/SMBFileUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/SMBFileUtil.java new file mode 100644 index 0000000..be2e3c8 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/SMBFileUtil.java @@ -0,0 +1,197 @@ +package com.centricsoftware.commons.utils; + +import com.centricsoftware.config.entity.CsProperties; +import jcifs.smb.NtlmPasswordAuthentication; +import jcifs.smb.SmbFile; +import jcifs.smb.SmbFileInputStream; +import jcifs.smb.SmbFileOutputStream; + +import java.io.*; + +/** + * 向共享盘上传递图片信息 + * 同类方法 {@link cn.hutool.extra.ftp.Ftp} + * @author Harry + * + */ +public class SMBFileUtil { + + public String fName = ""; + public String fPassword = ""; + public String fHost = ""; + public String fFolder = ""; + + public SMBFileUtil(String userName, String password, String host,String folder){ + this.fName = userName; + this.fPassword = password; + this.fHost = host; + this.fFolder = folder; + } + + public SMBFileUtil(){ + CsProperties properties = SpringUtil.getBean(CsProperties.class); + String userName = properties.getValue("cs.imageserver.username"); + String password = properties.getValue("cs.imageserver.password"); + String host = properties.getValue("cs.imageserver.host"); + String folder = properties.getValue("cs.imageserver.folder"); + + this.fName = userName; + this.fPassword = password; + this.fHost = host; + this.fFolder = folder; + } + + public String uploadFile(File localFile){ + InputStream in = null; + OutputStream out = null; + String filepath = ""; + try { + //获取图片 + + String remotePhotoUrl = "smb://"+this.fHost+"/"+this.fFolder; //存放图片的共享目录 + NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("",this.fName,this.fPassword); + //SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmssSSS_"); + SmbFile remoteFile = new SmbFile(remotePhotoUrl + "/" + localFile.getName(),auth); + remoteFile.connect(); //尝试连接 + + //如果远程可以连接,那就判断先父节点目录是否存在,不存在就创建 + SmbFile parentDir = new SmbFile(remoteFile.getParent(),auth); + if (!parentDir.exists()) + { + parentDir.mkdirs(); + } + + filepath = "http://"+this.fHost+"/"+this.fFolder.replace("eeka", "")+"/"+localFile.getName(); + in = new BufferedInputStream(new FileInputStream(localFile)); + out = new BufferedOutputStream(new SmbFileOutputStream(remoteFile)); + + byte[] buffer = new byte[4096]; + int len = 0; //读取长度 + while ((len = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); //刷新缓冲的输出流 + } + catch (Exception e) { + String msg = "发生错误:" + e.getLocalizedMessage(); + System.out.println(msg); + } + finally { + try { + if(out != null) { + out.close(); + } + if(in != null) { + in.close(); + } + } + catch (Exception e) {} + } + return filepath; + } + + public static void uploadFile(File localFile,String userName, String password, String host,String folder){ + InputStream in = null; + OutputStream out = null; + try { + //获取图片 + + String remotePhotoUrl = "smb://"+host+"/"+folder; //存放图片的共享目录 + //SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmssSSS_"); + NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("",userName,password); + SmbFile remoteFile = new SmbFile(remotePhotoUrl + "/" + localFile.getName(),auth); + remoteFile.connect(); //尝试连接 + + //如果远程可以连接,那就判断先父节点目录是否存在,不存在就创建 + SmbFile parentDir = new SmbFile(remoteFile.getParent(),auth); + if (!parentDir.exists()) + { + parentDir.mkdirs(); + } + + System.out.println(remotePhotoUrl+"=connected"); + in = new BufferedInputStream(new FileInputStream(localFile)); + out = new BufferedOutputStream(new SmbFileOutputStream(remoteFile)); + + byte[] buffer = new byte[4096]; + int len = 0; //读取长度 + while ((len = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); //刷新缓冲的输出流 + } + catch (Exception e) { + e.printStackTrace(); + String msg = "发生错误:" + e.getLocalizedMessage(); + System.out.println(msg); + } + finally { + try { + if(out != null) { + out.close(); + } + if(in != null) { + in.close(); + } + } + catch (Exception e) {} + } + } + + /** + * 下载文件 + * @return + */ + public byte[] downloadFile(String fileName){ + InputStream in = null ; + ByteArrayOutputStream out = null ; + try { + //创建远程文件对象 + String remotePhotoUrl = "smb://"+this.fHost+"/"+this.fFolder+"/"+fileName; + NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("",this.fName,this.fPassword); + SmbFile remoteFile = new SmbFile(remotePhotoUrl,auth); + remoteFile.connect(); //尝试连接 + + //如果远程可以连接,那就判断先父节点目录是否存在,不存在就创建 + SmbFile parentDir = new SmbFile(remoteFile.getParent(),auth); + if (!parentDir.exists()) + { + parentDir.mkdirs(); + } + + + //创建文件流 + in = new BufferedInputStream(new SmbFileInputStream(remoteFile)); + out = new ByteArrayOutputStream((int)remoteFile.length()); + //读取文件内容 + byte[] buffer = new byte[4096]; + int len = 0; //读取长度 + while ((len = in.read(buffer, 0, buffer.length)) != - 1) { + out.write(buffer, 0, len); + } + + out.flush(); //刷新缓冲的输出流 + return out.toByteArray(); + } + catch (Exception e) { + String msg = "下载远程文件出错:" + e.getLocalizedMessage(); + System.out.println(msg); + } + finally { + try { + if(out != null) { + out.close(); + } + if(in != null) { + in.close(); + } + } + catch (Exception e) {} + } + return null; + } + + public static void main(String[] args) { + uploadFile(new File("D:\\C8\\C8Test.model.xml"), "eeka", "Gst#2017", "10.7.121.10", "eeka/centric/"); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/ServicesUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/ServicesUtil.java new file mode 100644 index 0000000..4ef4b13 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/ServicesUtil.java @@ -0,0 +1,326 @@ +/** +* @author GHUANG +* @version 2016年3月28日 下午5:48:18 +* +*/ +package com.centricsoftware.commons.utils; + +import org.apache.commons.codec.binary.Base64; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.util.HashMap; + +/** + * 发送http请求工具类 + * 同类方法{@link cn.hutool.http.HttpUtil} + * @author zheng.gong + * @date 2020/4/27 + */ +public class ServicesUtil { + + public static String HtmLog = "ConnectHtmLog"; + + static HashMap initmap = null; + static { + initmap = new HashMap(); + initmap.put("0", ""); + initmap.put("1004", "Exception:服务器网络异常,请稍后重试"); + initmap.put("1001", "Exception:请求参数错误"); + initmap.put("1002", "Exception:该用户不存在"); + initmap.put("1003", "Exception:该sign签名不合法"); + initmap.put("1005", "Exception:SCM错误!"); + initmap.put("1006", "Exception:SCM错误!"); + initmap.put("1007", "Exception:网络连接错误!"); + initmap.put("1008", "Exception:SCM错误!"); + initmap.put("1009", "Exception:SCM错误!"); + } + + /** + * REST + * + * @param targetURL + * @param url + * @param typeId + * @return + * @author GHUANG + * @version 2019年4月20日 上午7:26:42 + */ + public static String getData(String targetURL, String url, String typeId) { + // + String output = ""; + try { + + URL restServiceURL = new URL(targetURL); + + HttpURLConnection httpConnection = (HttpURLConnection) restServiceURL.openConnection(); + httpConnection.setRequestMethod("GET"); + httpConnection.setRequestProperty("Connection", "Keep-Alive");// 维持长连接 + httpConnection.setRequestProperty("Charset", "UTF-8"); + httpConnection.setRequestProperty("Accept", "application/json"); + + if (httpConnection.getResponseCode() != 200) { + throw new RuntimeException("HTTP GET Request Failed with Error code : " + + httpConnection.getResponseCode()); + } + + BufferedReader responseBuffer = new BufferedReader(new InputStreamReader( + (httpConnection.getInputStream()))); + + String line; + while ((line = responseBuffer.readLine()) != null) { + output = output + line; + } + httpConnection.disconnect(); + System.out.println("get Server Success:\n" + output); + if (output.indexOf("{") > 0) { + output = output.substring(output.indexOf("{"), output.lastIndexOf("}") + 1); + } + } catch (Exception e) { + StackTraceElement[] stackArray = e.getStackTrace(); + for (int i = 0; i < stackArray.length; i++) { + StackTraceElement element = stackArray[i]; + } + + e.printStackTrace(); + output = "Exception when PLM try to connect K3 "; + + } + return output; + } + +// static void callWebService(String param, String url, String user, String psd) throws AxisFault { +// System.out.println("call begin....."); +// RPCServiceClient serviceClient = new RPCServiceClient(); +// Options options = serviceClient.getOptions(); +// EndpointReference targetEPR = new EndpointReference(url); +// options.setTo(targetEPR); +// options.setManageSession(true); +// options.setProperty(HTTPConstants.REUSE_HTTP_CLIENT, true); +// options.setTimeOutInMilliSeconds(600000L); +// options.setUserName("connuser"); +// options.setPassword("a87654321"); +// // String response = "application/json "; +// Object[] opAddEntryArgs = new Object[] { param }; +// Class[] classes = new Class[] { String.class }; +// QName opAddEntry = new QName("http://ws.apache.org/axis2", "receive"); +// System.out.println("result=" + serviceClient.invokeBlocking(opAddEntry, opAddEntryArgs, classes)[0]); +// System.out.println("call end..."); +// } + + /** + * REST支持 + * + * @param targetURL + * @param jsonarray + * @param typeId + * @return + * @author GHUANG + * @version 2019年4月20日 上午7:26:30 + */ + + public static String postData(String targetURL, String jsonarray, String user, String password) { + String remsg = ""; + HttpURLConnection httpConnection = null; + try { + System.out.println(targetURL); + URL targetUrl = new URL(targetURL); + System.out.println(user + "----" + password); + httpConnection = (HttpURLConnection) targetUrl.openConnection(); + httpConnection.setDoOutput(true); + httpConnection.setDoInput(true); + httpConnection.setRequestMethod("POST"); + httpConnection.setUseCaches(false); + // 1.4的问题,必须有SOAPAction + httpConnection.setRequestProperty("SOAPAction", "application/soap+xml; charset=utf-8"); + httpConnection.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); + httpConnection.setRequestProperty("Connection", "Keep-Alive");// 维持长连接 + httpConnection.setRequestProperty("Charset", "UTF-8"); + byte[] requestStringBytes = jsonarray.getBytes(StandardCharsets.UTF_8); + httpConnection.setRequestProperty("Content-length", "" + requestStringBytes.length); + String users = user + ":" + password; + httpConnection.addRequestProperty("Authorization", + "Basic " + new String(Base64.encodeBase64(users.getBytes()))); + OutputStream outputStream = httpConnection.getOutputStream(); + outputStream.write(requestStringBytes); + outputStream.flush(); + outputStream.close(); + if (httpConnection.getResponseCode() != 200) { + InputStream err = httpConnection.getErrorStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(err, StandardCharsets.UTF_8)); + StringBuffer sb = new StringBuffer(); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + throw new RuntimeException("Failed : HTTP error code : " + + httpConnection.getResponseCode() + ",exception info" + sb.toString()); + } + BufferedReader responseBuffer = new BufferedReader(new InputStreamReader( + (httpConnection.getInputStream()))); +// +// String line; +// System.out.println("Output from Server:\n"); +// while ((line = responseBuffer.readLine()) != null) { +// System.out.println(line); +// } + responseBuffer.close(); + httpConnection.disconnect(); + + } catch (Exception e) { + e.printStackTrace(); + remsg = e.getMessage(); + if (remsg.contains("refused")) { + remsg = "对方接口拒绝连接请求!"; + } + remsg = "Exception:" + remsg; + + } finally { + if (httpConnection != null) { + httpConnection.disconnect(); + } + } + return remsg; + } + + /** + * REST支持SOAP,xml格式param + * + * @param targetURL + * @param param + * @param user + * @param password + * @return + * @author GHUANG + * @version 2019年4月20日 上午7:26:30 + */ + public static String putData(String targetURL, String param, String user, String password) + throws Exception { + String result = ""; + HttpURLConnection con = null; + try { + long t1 = System.currentTimeMillis(); // 执行开始时间记录 + URL url = new URL(targetURL); + con = (HttpURLConnection) url.openConnection(); + con.setDoOutput(true); + con.setDoInput(true); + con.setConnectTimeout(30 * 60 * 1000); + con.setRequestProperty("Content-Type", "text/xml;charset=UTF-8"); + // con.setRequestProperty("Content-Type", "multipart/form-data; charset=UTF-8; "); + con.setRequestProperty("accept", "*/*"); + con.setRequestProperty("Connection", "Keep-Alive"); + con.setDoInput(true); + con.setDoOutput(true); + // con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + // con.setRequestProperty("Charset", "UTF-8"); + // con.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); + con.setRequestMethod("POST"); + con.setUseCaches(false); + String users = user + ":" + password; + con.addRequestProperty("Authorization", "Basic " + new String(Base64.encodeBase64(users.getBytes()))); + con.connect(); + // param = URLEncoder.encode(param, "utf-8"); + OutputStream os = con.getOutputStream(); + OutputStreamWriter out = new OutputStreamWriter(os, StandardCharsets.UTF_8); + // String utf8String = new String(param.getBytes(), "utf-8"); + // NodeUtil.outInfo("-----" + con.getContentEncoding() + "", HtmLog); + out.write(param); + out.flush(); + out.close(); + System.out.println(con.getResponseCode()); + if (con.getResponseCode() == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)); + + br.close(); + } else { + InputStream err = con.getErrorStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(err, StandardCharsets.UTF_8)); + StringBuffer sb = new StringBuffer(); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + throw new RuntimeException("Failed : HTTP error code : " + + con.getResponseCode() + ",exception info" + sb.toString()); + } + con.disconnect(); + + } catch (Exception e) { + result = e.getMessage(); + + } finally { + if (con != null) { + con.disconnect(); + } + } + return result; + } + + /** + * 生成sign + * + * @param appkey + * @param secret + * @param sign_method + * @return + */ + public static String getSign(String epid, String appkey, String secret, String timestamp, String jsonStr) { + if (jsonStr.length() == 0) { + return ""; + } + String paramString = epid + appkey + secret + timestamp + jsonStr; + String sign = ""; + sign = byte2hex(encryptMD5(paramString, "UTF-8")); + return sign; + } + + /** + * MD5加密 + * + * @param data + * 字符串 + * + * @param format + * 数据编码方式,如:UTF-8、GBK + * @return byte[]字节数组 + * @throws IOException + */ + public static byte[] encryptMD5(String data, String format) { + byte[] bytes = null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(data.getBytes(format)); + bytes = md.digest(); + } catch (GeneralSecurityException gse) { + gse.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return bytes; + } + + public static String byte2hex(byte[] bytes) { + StringBuilder sign = new StringBuilder(); + + for (int i = 0; i < bytes.length; ++i) { + String hex = Integer.toHexString(bytes[i] & 0xFF); + if (hex.length() == 1) { + sign.append("0"); + } + sign.append(hex.toUpperCase()); + } + return sign.toString().toLowerCase(); + } + + public static void main(String[] args) throws Exception { + // TODO Auto-generated method stub + // freezeMaterial(); + System.out.println("---" + getData("http://192.168.37.133/plmservice/services/ESHub/pushChangeData?url=111" + + "&attrname=222&attrvalue=22211&flag=y", "", "")); + } + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/SpringContextHolder.java b/commons/src/main/java/com/centricsoftware/commons/utils/SpringContextHolder.java new file mode 100644 index 0000000..c3c92c7 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/SpringContextHolder.java @@ -0,0 +1,88 @@ +package com.centricsoftware.commons.utils; + +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ + +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +/** + * 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候取出ApplicaitonContext. + * + */ +@Service +@Lazy(false) +public class SpringContextHolder implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + + private static final Logger logger = LoggerFactory.getLogger(SpringContextHolder.class); + + /** + * 取得存储在静态变量中的ApplicationContext. + */ + public static ApplicationContext getApplicationContext() { + assertContextInjected(); + return applicationContext; + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + public static void clearHolder() { + if (logger.isDebugEnabled()){ + logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); + } + applicationContext = null; + } + + /** + * 实现ApplicationContextAware接口, 注入Context到静态变量中. + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + SpringContextHolder.applicationContext = applicationContext; + } + + public static String getStatic(){ + return SpringContextHolder.getApplicationContext().getApplicationName()+ "/static"; + } + /** + * 实现DisposableBean接口, 在Context关闭时清理静态变量. + */ + @Override + public void destroy(){ + SpringContextHolder.clearHolder(); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + Validate.validState(applicationContext != null, "applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder."); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/SpringUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/SpringUtil.java new file mode 100644 index 0000000..229105e --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/SpringUtil.java @@ -0,0 +1,43 @@ +package com.centricsoftware.commons.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +/** + * Spring工具类,只有两个功能即获取容器中注册的bean,向容器中注册bean + * @author zheng.gong + * @date 2020/4/27 + */ +@Component +public class SpringUtil implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringUtil.applicationContext == null) { + SpringUtil.applicationContext = applicationContext; + } + } + + //获取applicationContext + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + //通过name获取 Bean. + public static Object getBean(String name) { + return getApplicationContext().getBean(name); + } + + //通过class获取Bean. + public static T getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + //通过name,以及Clazz返回指定的Bean + public static T getBean(String name, Class clazz) { + return getApplicationContext().getBean(name, clazz); + } +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/StringUtils.java b/commons/src/main/java/com/centricsoftware/commons/utils/StringUtils.java new file mode 100644 index 0000000..421d9d6 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/StringUtils.java @@ -0,0 +1,616 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package com.centricsoftware.commons.utils; + +import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类 + * + * @author jeeplus + * @version 2016-05-22 + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + private static final Logger logger = LoggerFactory.getLogger(StringUtils.class); + + private static final char SEPARATOR = '_'; + private static final String CHARSET_NAME = "UTF-8"; + + /** + * 转换为字节数组 + * + * @param str + * @return + */ + public static byte[] getBytes(String str) { + if (str != null) { + try { + return str.getBytes(CHARSET_NAME); + } catch (UnsupportedEncodingException e) { + return null; + } + } else { + return null; + } + } + + /** + * 转换为字节数组 + * + * @param bytes + * @return + */ + public static String toString(byte[] bytes) { + try { + return new String(bytes, CHARSET_NAME); + } catch (UnsupportedEncodingException e) { + return EMPTY; + } + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inString(String str, String... strs) { + if (str != null) { + for (String s : strs) { + if (str.equals(trim(s))) { + return true; + } + } + } + return false; + } + + /** + * 替换掉HTML标签方法 + */ + public static String replaceHtml(String html) { + if (isBlank(html)) { + return ""; + } + String regEx = "<.+?>"; + Pattern p = Pattern.compile(regEx); + Matcher m = p.matcher(html); + String s = m.replaceAll(""); + return s; + } + + /** + * 替换为手机识别的HTML,去掉样式及属性,保留回车。 + * + * @param html + * @return + */ + public static String replaceMobileHtml(String html) { + if (html == null) { + return ""; + } + return html.replaceAll("<([a-z]+?)\\s+?.*?>", "<$1>"); + } + + /** + * 替换为手机识别的HTML,去掉样式及属性,保留回车。 + * + * @param txt + * @return + */ + public static String toHtml(String txt) { + if (txt == null) { + return ""; + } + return replace(replace(EncodeUtils.escapeHtml(txt), "\n", "
"), "\t", "    "); + } + + /** + * 缩略字符串(不区分中英文字符) + * + * @param str 目标字符串 + * @param length 截取长度 + * @return + */ + public static String abbr(String str, int length) { + if (str == null) { + return ""; + } + try { + StringBuilder sb = new StringBuilder(); + int currentLength = 0; + for (char c : replaceHtml(StringEscapeUtils.unescapeHtml4(str)).toCharArray()) { + currentLength += String.valueOf(c).getBytes("GBK").length; + if (currentLength <= length - 3) { + sb.append(c); + } else { + sb.append("..."); + break; + } + } + return sb.toString(); + } catch (UnsupportedEncodingException e) { + logger.error(e.getMessage(), e); + } + return ""; + } + + public static String abbr2(String param, int length) { + if (param == null) { + return ""; + } + StringBuffer result = new StringBuffer(); + int n = 0; + char temp; + boolean isCode = false; // 是不是HTML代码 + boolean isHTML = false; // 是不是HTML特殊字符,如  + for (int i = 0; i < param.length(); i++) { + temp = param.charAt(i); + if (temp == '<') { + isCode = true; + } else if (temp == '&') { + isHTML = true; + } else if (temp == '>' && isCode) { + n = n - 1; + isCode = false; + } else if (temp == ';' && isHTML) { + isHTML = false; + } + try { + if (!isCode && !isHTML) { + n += String.valueOf(temp).getBytes("GBK").length; + } + } catch (UnsupportedEncodingException e) { + logger.error(e.getMessage(), e); + } + + if (n <= length - 3) { + result.append(temp); + } else { + result.append("..."); + break; + } + } + // 取出截取字符串中的HTML标记 + String temp_result = result.toString().replaceAll("(>)[^<>]*(]*/?>", + ""); + // 去掉成对的HTML标记 + temp_result = temp_result.replaceAll("<([a-zA-Z]+)[^<>]*>(.*?)", "$2"); + // 用正则表达式取出标记 + Pattern p = Pattern.compile("<([a-zA-Z]+)[^<>]*>"); + Matcher m = p.matcher(temp_result); + List endHTML = Lists.newArrayList(); + while (m.find()) { + endHTML.add(m.group(1)); + } + // 补全不成对的HTML标记 + for (int i = endHTML.size() - 1; i >= 0; i--) { + result.append(""); + } + return result.toString(); + } + + /** + * 转换为Double类型 + */ + public static Double toDouble(Object val) { + if (val == null) { + return 0D; + } + try { + return Double.valueOf(trim(val.toString())); + } catch (Exception e) { + return 0D; + } + } + + /** + * 转换为Float类型 + */ + public static Float toFloat(Object val) { + return toDouble(val).floatValue(); + } + + /** + * 转换为Long类型 + */ + public static Long toLong(Object val) { + return toDouble(val).longValue(); + } + + /** + * 转换为Integer类型 + */ + public static Integer toInteger(Object val) { + return toLong(val).intValue(); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + + s = s.toLowerCase(); + + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCapitalizeCamelCase(String s) { + if (s == null) { + return null; + } + s = toCamelCase(s); + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toUnderScoreCase(String s) { + if (s == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + boolean nextUpperCase = true; + + if (i < (s.length() - 1)) { + nextUpperCase = Character.isUpperCase(s.charAt(i + 1)); + } + + if ((i > 0) && Character.isUpperCase(c)) { + if (!upperCase || !nextUpperCase) { + sb.append(SEPARATOR); + } + upperCase = true; + } else { + upperCase = false; + } + + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 转换为JS获取对象值,生成三目运算返回结果 + * + * @param objectString 对象串 例如:row.user.id + * 返回:!row?'':!row.user?'':!row.user.id?'':row.user.id + */ + public static String jsGetVal(String objectString) { + StringBuilder result = new StringBuilder(); + StringBuilder val = new StringBuilder(); + String[] vals = split(objectString, "."); + for (int i = 0; i < vals.length; i++) { + val.append("." + vals[i]); + result.append("!" + (val.substring(1)) + "?'':"); + } + result.append(val.substring(1)); + return result.toString(); + } + + /** + * 将字符串首字母转大写 + * + * @param str + * @return + */ + public static String firstUpperCase(String str) { + if ((str == null) || (str.length() == 0)) + return str; + char[] ch = str.toCharArray(); + if (ch[0] >= 'a' && ch[0] <= 'z') { + ch[0] = (char) (ch[0] - 32); + } + return new String(ch); + } + + /** + * 将字符串首字母转小写 + * + * @param str + * @return + */ + public static String firstLowerCase(String str) { + if ((str == null) || (str.length() == 0)) + return str; + char[] ch = str.toCharArray(); + if (ch[0] >= 'A' && ch[0] <= 'Z') { + ch[0] = (char) (ch[0] + 32); + } + return new String(ch); + } + + /*** + * 截取字符串 里面的数字 + * + * @param str + * @param breakflag 遇到非字符串是否立即终止 + * @return + * @date:2018年7月10日 + * @author:wuxiaoting + */ + public static String getNumeric(String str, boolean breakflag) { + StringBuilder sBuilder = new StringBuilder(20); + for (int i = 0; i < str.length(); i++) { + if (!Character.isDigit(str.charAt(i))) { + if (breakflag) + break; + continue; + } + sBuilder.append(str.charAt(i)); + } + return sBuilder.toString(); + } + + /** + * 利用正则表达式判断字符串是否是数字 + * + * @param str + * @return + */ + public static boolean isNumeric(String str) { + if (StringUtils.isBlank(str)) + return false; + Pattern pattern = Pattern.compile("^[-+]?(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)$"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + public static boolean isInteger(String str) { + if (StringUtils.isBlank(str)) + return false; + Pattern pattern = Pattern.compile("[0-9]*"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + /** + * 将ErrorStack转化为String. + */ + public static String getStackTraceAsString(Throwable e) { + if (e == null){ + return ""; + } + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + /** + * 检查Email格式 + * @param email + * @return + */ + public static boolean checkEmail(String email){ + if (StringUtils.isBlank(email)) { + return false; + } + boolean flag = false; + try{ + String check = "^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; + Pattern regex = Pattern.compile(check); + Matcher matcher = regex.matcher(email); + flag = matcher.matches(); + }catch(Exception e){ + flag = false; + } + return flag; + } + + public static String getFormatedValue(String valueStr, String format) { + if (StringUtils.isBlank(valueStr) || "".equals(valueStr.trim())) { + valueStr = "0"; + } + DecimalFormat df = new DecimalFormat(format); + return df.format(Double.parseDouble(valueStr.trim())); + } + + /** + * 对字符串采用UTF-8编码后,用MD5进行摘要。 + */ + public static byte[] encryptMD5(String data) throws IOException { + return encryptMD5(data.getBytes(CHARSET_NAME)); + } + + + /** + * 对字节流进行MD5摘要。 + */ + public static byte[] encryptMD5(byte[] data) throws IOException { + byte[] bytes = null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + bytes = md.digest(data); + } catch (GeneralSecurityException gse) { + throw new IOException(gse.toString()); + } + return bytes; + } + + /** + * 把字节流转换为十六进制表示方式。 + */ + public static String byte2hex(byte[] bytes) { + StringBuilder sign = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + String hex = Integer.toHexString(bytes[i] & 0xFF); + if (hex.length() == 1) { + sign.append("0"); + } + sign.append(hex.toUpperCase()); + } + return sign.toString(); + } + + /** + * 将字符型的日期转成long + */ + public static long date2Long(String date,String format) { + DateFormat dateFormat = new SimpleDateFormat(format); + long tsvalue = 0; + try { + Timestamp ts = new Timestamp(dateFormat.parse(date).getTime()); + tsvalue = ts.getTime() / 1000; + } catch (Exception e) { + return 0; + } + return tsvalue; + } + + /** + * 字符型日期是否合法 + * @param date + * @param format + * @return + * @author 谢旭林 + * @version 创建时间:2020年4月8日 下午4:23:47 + * + */ + public static boolean validDateFormat(String date,String format) { + DateFormat dateFormat = new SimpleDateFormat(format); + try { + dateFormat.parse(date); + } catch (Exception e) { + return false; + } + return true; + } + + /** + * 将字符串转译,以免xml报错 + */ + public static String escape4Xml(String xmlStr) { + return xmlStr.replace("&", "&").replace("<", "<").replace(">", ">") + .replace("'", "'").replace("\"", """); + } + + public static List extractMessageByRegular(String msg){ + return extractMessageByRegular(msg,"3"); + } + /** + *正则表达式匹配以##,{},[]为标记的变量 + * @param msg + * @return + */ + public static List extractMessageByRegular(String msg,String type){ + String pattern = ""; + switch (type){ + case "1": + pattern = "(#[^#]*#)";//以##为标记 + break; + case "2": + pattern = "(\\[[^\\]]*\\])"; //以[]为标记 + break; + case "3": + pattern = "(\\{[^\\}]*\\})";//以{}为标记 + break; + default: + pattern = "(\\{[^\\}]*\\})";//以{}为标记 + } + List list=new ArrayList(); + Pattern p = Pattern.compile(pattern); + Matcher m = p.matcher(msg); + while(m.find()){ + list.add(m.group().substring(1, m.group().length()-1)); + } + return list; + } + + public static String fillValueInMsg(String msg,String variableName,String value){ + return fillValueInMsg( msg, variableName, value,"3"); + } + /** + * 填充msg中以##,{},[]为标记的变量 + * @param msg + * @param variableName + * @param value + * @param type + * @return + */ + public static String fillValueInMsg(String msg,String variableName,String value,String type){ + String newValue = ""; + switch (type){ + case "1": + newValue = "#"+variableName+"#";//以##为标记 + break; + case "2": + newValue = "\\["+variableName+"\\]"; //以[]为标记 + break; + case "3": + newValue = "\\{"+variableName+"\\}";//以{}为标记 + break; + default: + newValue = "\\{"+variableName+"\\}";//以{}为标记 + } + if(StringUtils.isBlank(value)){ + value = ""; + } + return msg.replaceAll(newValue,value); + } + +} diff --git a/commons/src/main/java/com/centricsoftware/commons/utils/ZIPUtil.java b/commons/src/main/java/com/centricsoftware/commons/utils/ZIPUtil.java new file mode 100644 index 0000000..da9aa80 --- /dev/null +++ b/commons/src/main/java/com/centricsoftware/commons/utils/ZIPUtil.java @@ -0,0 +1,393 @@ +package com.centricsoftware.commons.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * 解压Zip文件工具类 + * 同类方法{@link cn.hutool.core.util.ZipUtil} + * @author harry liang + * + */ +@Slf4j +public class ZIPUtil { + private static final int buffer = 2048; + /** + * 解压Zip文件 + * + * @param path + * 文件目录 + * @return 解压缩文件目录 + */ + public static String unZip(String path) { + int count = -1; + String savepath = ""; + + File file = null; + InputStream is = null; + FileOutputStream fos = null; + BufferedOutputStream bos = null; + savepath = path.substring(0, path.lastIndexOf(".")) + File.separator; // 保存解压文件目录 + new File(savepath).mkdir(); // 创建保存目录 + ZipFile zipFile = null; + try { + try { + // zipFile = new ZipFile(path, Charset.forName("gbk")); + zipFile = new ZipFile(path, Charset.forName("utf-8")); // 解决中文乱码问题 + } catch (Exception e) { + + } + Enumeration entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + byte[] buf = new byte[buffer]; + + ZipEntry entry = (ZipEntry) entries.nextElement(); + + String filename = entry.getName(); + boolean ismkdir = false; + if (filename.lastIndexOf("/") != -1) { // 检查此文件是否带有文件夹 + ismkdir = true; + } + filename = savepath + filename; + + if (entry.isDirectory()) { // 如果是文件夹先创建 + file = new File(filename); + file.mkdirs(); + continue; + } + file = new File(filename); + if (!file.exists()) { // 如果是目录先创建 + if (ismkdir) { + new File(filename.substring(0, filename.lastIndexOf("/"))).mkdirs(); // 目录先创建 + } + } + file.createNewFile(); // 创建文件 + + is = zipFile.getInputStream(entry); + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos, buffer); + + while ((count = is.read(buf)) > -1) { + bos.write(buf, 0, count); + } + bos.flush(); + bos.close(); + fos.close(); + + is.close(); + } + + zipFile.close(); + + } catch (IOException ioe) { + ioe.printStackTrace(); + } finally { + try { + if (bos != null) { + bos.close(); + } + if (fos != null) { + fos.close(); + } + if (is != null) { + is.close(); + } + if (zipFile != null) { + zipFile.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return savepath; + } + + private static final int BUFFER_SIZE = 2 * 1024; + + /** + * + * 压缩成ZIP 方法1 + * + * @param srcDir + * 压缩文件夹路径 + * + * @param out + * 压缩文件输出流 + * + * @param KeepDirStructure + * 是否保留原来的目录结构,true:保留目录结构; + * + * false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败) + * + * @throws RuntimeException + * 压缩失败会抛出运行时异常 + * + */ + + public static void toZip(String srcDir, OutputStream out, boolean KeepDirStructure) + + throws RuntimeException { + + long start = System.currentTimeMillis(); + + ZipOutputStream zos = null; + + try { + + zos = new ZipOutputStream(out); + + File sourceFile = new File(srcDir); + + compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure); + + long end = System.currentTimeMillis(); + + System.out.println("压缩完成,耗时:" + (end - start) + " ms"); + + } catch (Exception e) { + + throw new RuntimeException("zip error from ZipUtils", e); + + } finally { + + if (zos != null) { + + try { + + zos.close(); + + } catch (IOException e) { + + e.printStackTrace(); + + } + + } + + } + + } + + /** + * + * 压缩成ZIP 方法2 + * + * @param srcFiles + * 需要压缩的文件列表 + * + * @param out + * 压缩文件输出流 + * + * @throws RuntimeException + * 压缩失败会抛出运行时异常 + * + */ + + public static void toZip(List srcFiles, OutputStream out) throws RuntimeException { + + long start = System.currentTimeMillis(); + + ZipOutputStream zos = null; + + try { + + zos = new ZipOutputStream(out); + + for (File srcFile : srcFiles) { + + byte[] buf = new byte[BUFFER_SIZE]; + + zos.putNextEntry(new ZipEntry(srcFile.getName())); + + int len; + + FileInputStream in = new FileInputStream(srcFile); + + while ((len = in.read(buf)) != -1) { + + zos.write(buf, 0, len); + + } + + zos.closeEntry(); + + in.close(); + + } + + long end = System.currentTimeMillis(); + + System.out.println("压缩完成,耗时:" + (end - start) + " ms"); + + } catch (Exception e) { + log.error("ZIP", e); + throw new RuntimeException("zip error from ZipUtils", e); + + } finally { + + if (zos != null) { + + try { + + zos.close(); + + } catch (IOException e) { + + e.printStackTrace(); + + } + + } + + } + + } + + /** + * + * 递归压缩方法 + * + * @param sourceFile + * 源文件 + * + * @param zos + * zip输出流 + * + * @param name + * 压缩后的名称 + * + * @param KeepDirStructure + * 是否保留原来的目录结构,true:保留目录结构; + * + * false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败) + * + * @throws Exception + * + */ + + private static void compress(File sourceFile, ZipOutputStream zos, String name, + + boolean KeepDirStructure) throws Exception { + + byte[] buf = new byte[BUFFER_SIZE]; + + if (sourceFile.isFile()) { + + // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字 + + zos.putNextEntry(new ZipEntry(name)); + + // copy文件到zip输出流中 + + int len; + + FileInputStream in = new FileInputStream(sourceFile); + + while ((len = in.read(buf)) != -1) { + + zos.write(buf, 0, len); + + } + + // Complete the entry + + zos.closeEntry(); + + in.close(); + + } else { + + File[] listFiles = sourceFile.listFiles(); + + if (listFiles == null || listFiles.length == 0) { + + // 需要保留原来的文件结构时,需要对空文件夹进行处理 + + if (KeepDirStructure) { + + // 空文件夹的处理 + + zos.putNextEntry(new ZipEntry(name + "/")); + + // 没有文件,不需要文件的copy + + zos.closeEntry(); + + } + + } else { + + for (File file : listFiles) { + + // 判断是否需要保留原来的文件结构 + + if (KeepDirStructure) { + + // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠, + + // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了 + + compress(file, zos, name + "/" + file.getName(), KeepDirStructure); + + } else { + + compress(file, zos, file.getName(), KeepDirStructure); + + } + + } + + } + + } + + } + + public static void main(String[] args) throws Exception { + + /** 测试压缩方法1 */ + + FileOutputStream fos1 = new FileOutputStream(new File("c:/mytest01.zip")); + + ZIPUtil.toZip("D:/log", fos1, true); + + /** 测试压缩方法2 */ + + List fileList = new ArrayList<>(); + + fileList.add(new File("D:/Java/jdk1.7.0_45_64bit/bin/jar.exe")); + + fileList.add(new File("D:/Java/jdk1.7.0_45_64bit/bin/java.exe")); + + FileOutputStream fos2 = new FileOutputStream(new File("c:/mytest02.zip")); + + ZIPUtil.toZip(fileList, fos2); + + } + /* + * public static void main(String[] args) { unZip("F:\\110000002.zip"); String f = "F:\\110000002"; File file = new + * File(f); String[] test=file.list(); for(int i=0;i + + + Aspose.Total for Java + + Enterprise + 20991231 + 20991231 + 8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7 + + sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU= + \ No newline at end of file diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/config/.mvn/wrapper/MavenWrapperDownloader.java b/config/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/config/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/config/.mvn/wrapper/maven-wrapper.jar b/config/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/config/.mvn/wrapper/maven-wrapper.properties b/config/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/config/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/config/pom.xml b/config/pom.xml new file mode 100644 index 0000000..fa8fdc0 --- /dev/null +++ b/config/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + config + 2.0 + config + Demo project for Spring Boot + + + 1.8 + 3.0.2 + + + + + org.springframework.boot + spring-boot-starter + provided + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + + cn.hutool + hutool-all + + + + + + + + com.google.guava + guava + + + + + + + src/main/java + + **/*.xml + + + true + + + src/main/resources + + + + + **/*.yml + logback-spring.xml + + true + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/main/java/com/centricsoftware/config/config/ExecutorConfig.java b/config/src/main/java/com/centricsoftware/config/config/ExecutorConfig.java new file mode 100644 index 0000000..476e4c4 --- /dev/null +++ b/config/src/main/java/com/centricsoftware/config/config/ExecutorConfig.java @@ -0,0 +1,48 @@ +package com.centricsoftware.config.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +@EnableAsync +public class ExecutorConfig { + @Value("${pool.executor.corePoolSize:8}") + private int corePoolSize; + @Value("${pool.executor.maxPoolSize:8}") + private int maxPoolSize; + @Value("${pool.executor.queueCapacity:1000}") + private int queueCapacity; + @Value("${pool.executor.keepAliveTime:300}") + private int keepAliveTime; + //线程池命名 + public static final String THREAD_POOL_NAME = "taskExecutorI"; + + @Bean(THREAD_POOL_NAME) + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + //配置核心线程数 + executor.setCorePoolSize(corePoolSize); + //配置最大线程数 + executor.setMaxPoolSize(maxPoolSize); + //配置队列大小 + executor.setQueueCapacity(queueCapacity); + //配置线程池中的线程的名称前缀 + executor.setThreadNamePrefix("thread-"); + //配置线程空闲后的最大存活时间 + executor.setKeepAliveSeconds(keepAliveTime); + // rejection-policy:当pool已经达到max size的时候,如何处理新任务 + // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 等待所有任务结束后再关闭线程池 + executor.setWaitForTasksToCompleteOnShutdown(true); + //执行初始化 + executor.initialize(); + return executor; + } +} diff --git a/config/src/main/java/com/centricsoftware/config/cons/Constants.java b/config/src/main/java/com/centricsoftware/config/cons/Constants.java new file mode 100644 index 0000000..00929ea --- /dev/null +++ b/config/src/main/java/com/centricsoftware/config/cons/Constants.java @@ -0,0 +1,151 @@ +package com.centricsoftware.config.cons; + + +/** + * 系统内相关常量,公共常量放这里,使用接口 + * @author zheng.gong + * @date 2020/4/17 + */ +public interface Constants { + + /** + * 分页最大单次限制 + */ + public static final int PAGE_MAX_LIMIT = 500; + + /** + * 表名 + * @author zheng.gong + * @date 2020/4/17 + */ + interface PlmTable { + String PLM_SAMPLE="plm_sample"; + } + + interface Bool{ + String TRUE="true"; + String FALSE="false"; + } + + interface SysStr{ + String SESSION_EXPIRE_ERROR_MSG="The client session has expired or is invalid"; + String SECRET="pvKMVgcoYEtwnmqmWhJmaA=="; + String AUTH_CONTENT = "ICICLE_INTERFACE_AUTH"; + String AUTH_ENCODE = "beErF6b+D/CSC7k0TgVRNw=="; + } + + + + /** + * 标点符号 + * @author zheng.gong + * @date 2020/4/17 + */ + interface SysConts{ + /** + * 点号 + */ + String SYMBOL_DOT = "."; + + /** + * 星号 + */ + String SYMBOL_STAR = "*"; + + /** + * 邮箱符号 + */ + String SYMBOL_EMAIL = "@"; + + + + } + + /** + * http请求头 + * @author zheng.gong + * @date 2020/4/17 + */ + interface HttpHeadEncodingType{ + String DEFAULT_CHARSET = "UTF-8"; + + String CONTENT_TYPE_JSON="application/json;charset=UTF-8"; + + String CONTENT_TYPE_XML="text/xml;charset=UTF-8"; + + String CONTENT_TYPE_HTML="text/html;charset=utf-8"; + + String CONTENT_TYPE_FORM="application/x-www-form-urlencoded"; + } + + /** + * redis相关常量 + */ + interface Redis{ + /** + * 默认配置刷新时间 7天 + */ + Integer DEFUALT_PROPERTIES_RELOAD = 604800; + + Integer C8INTERFACE_EXPIRE_TIME=3000; + + } + + /** + * rabbit相关常量 + * 此类为service使用配置 + */ + interface RabbitConsts { + /** + * 直接模式1 + */ + String DIRECT_MODE_QUEUE_ONE = "queue.direct.1"; + + /** + * 队列2 + */ + String QUEUE_TWO = "queue.2"; + + /** + * 队列3 + */ + String QUEUE_THREE = "3.queue"; + + /** + * 分列模式 + */ + String FANOUT_MODE_QUEUE = "fanout.mode"; + + /** + * 主题模式 + */ + String TOPIC_MODE_QUEUE = "topic.mode"; + + /** + * 路由1 + */ + String TOPIC_ROUTING_KEY_ONE = "queue.#"; + + /** + * 路由2 + */ + String TOPIC_ROUTING_KEY_TWO = "*.queue"; + + /** + * 路由3 + */ + String TOPIC_ROUTING_KEY_THREE = "3.queue"; + + /** + * 延迟队列 + */ + String DELAY_QUEUE = "delay.queue"; + + /** + * 延迟队列交换器 + */ + String DELAY_MODE_QUEUE = "delay.mode"; + } + + +} diff --git a/config/src/main/java/com/centricsoftware/config/entity/CenterProperties.java b/config/src/main/java/com/centricsoftware/config/entity/CenterProperties.java new file mode 100644 index 0000000..85d7c85 --- /dev/null +++ b/config/src/main/java/com/centricsoftware/config/entity/CenterProperties.java @@ -0,0 +1,36 @@ +package com.centricsoftware.config.entity; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; + +import java.sql.Struct; +import java.util.Map; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Component +@ConfigurationProperties(prefix = "cs.center") +public class CenterProperties { + Map prop; + + public String getValue(String key, String defValue){ + String value = this.getProp().get(key); + if(StrUtil.isBlank(value)&&StrUtil.isNotBlank(defValue)){ + return defValue; + }else{ + return value; + } + } + + public String getValue(String key){ + return this.getProp().get(key); + } +} diff --git a/config/src/main/java/com/centricsoftware/config/entity/CsProperties.java b/config/src/main/java/com/centricsoftware/config/entity/CsProperties.java new file mode 100644 index 0000000..23c2ed3 --- /dev/null +++ b/config/src/main/java/com/centricsoftware/config/entity/CsProperties.java @@ -0,0 +1,60 @@ +package com.centricsoftware.config.entity; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.Map; +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Component +@ConfigurationProperties(prefix = "cs") +public class CsProperties { + Map plm; + + private Environment env; + + @Autowired + public CsProperties(Environment env) { + this.env = env; + } + + public String getValue(String key, String defValue){ + String value = this.getPlm().get(key); + if(StrUtil.isBlank(value)&&StrUtil.isNotBlank(defValue)){ + return defValue; + }else{ +// if(StrUtil.equals(env.getProperty("password.encrypt"),"true")){ +// // 密钥 +// byte[] encryptkey = Base64.decode(Constants.SysStr.SECRET); +// AES aes = SecureUtil.aes(encryptkey); +// // 解密 +// return aes.decryptStr(value, CharsetUtil.CHARSET_UTF_8); +// } + return value; + } + } + + public String getValue(String key){ + return this.getPlm().get(key); + } + public String getProperty(String key){ + return this.getEnv().getProperty(key); + } + public String getProperty(String key, String defValue){ + String value = this.getEnv().getProperty(key); + if(StrUtil.isBlank(value)) { + value = defValue; + } + return value; + } + +} diff --git a/config/src/main/java/com/centricsoftware/config/entity/EsProperties.java b/config/src/main/java/com/centricsoftware/config/entity/EsProperties.java new file mode 100644 index 0000000..e37d2ed --- /dev/null +++ b/config/src/main/java/com/centricsoftware/config/entity/EsProperties.java @@ -0,0 +1,27 @@ +package com.centricsoftware.config.entity; + +import lombok.*; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "es") +@Data +public class EsProperties { + + private boolean enable; + + private String brand; + + private String scheme = "http"; + /** + * ip:port,以逗号分隔 + */ + private String host; + + private String user; + + private String password; + /** + * 日志索引名 + */ + private String logIndex; +} \ No newline at end of file diff --git a/config/src/main/resources/application-common.yml b/config/src/main/resources/application-common.yml new file mode 100644 index 0000000..6e8e5e9 --- /dev/null +++ b/config/src/main/resources/application-common.yml @@ -0,0 +1,17 @@ +#######################公共配置################### +cs.plm.common: + log: + feign: + #feign日志中,响应报文中标记请求处理状态的字段,支持getByPath + success-field: code + #feign日志中,响应报文中标记请求处理成功的值;可以通过feign的c8_code + success-value: 0,200 + #feign日志中,请求报文中标记主键的字段,支持getByPath + key-field: url,id,URL,ID + +cs.plm: + #是否记录日志,此配置用于开启ES和DB的日志记录方式 + feign-log: true + #用于配置日志的存储方式 + log-save-type: db # db or es + diff --git a/config/src/main/resources/application-es.yml b/config/src/main/resources/application-es.yml new file mode 100644 index 0000000..d0e5eef --- /dev/null +++ b/config/src/main/resources/application-es.yml @@ -0,0 +1,38 @@ +###这个yml文件存放es相关配置(无业务) + +################################开发环境################################ +spring: + profiles: dev + +es: + enable: true + brand: PLM + host: 127.0.0.1:9200 + user: elastic + password: c8admin + logIndex: plm-log-stream + +--- +################################测试环境################################ +spring: + profiles: test + +es: + enable: true + brand: PLM + host: 127.0.0.1:9200 + user: elastic + password: c8admin + +--- +################################正式环境################################ +spring: + profiles: prod + +es: + enable: true + brand: PLM + host: 127.0.0.1:9200 + user: elastic + password: c8admin + diff --git a/config/src/main/resources/application-import.yml b/config/src/main/resources/application-import.yml new file mode 100644 index 0000000..7788333 --- /dev/null +++ b/config/src/main/resources/application-import.yml @@ -0,0 +1,31 @@ +#######################导入配置################### +cs.excel.upload: + ##69码导入 + SKUImport: + nodeType: SKU + create: false + searchXml: + seadEmail: false + tips: "系统中找不到对应的SKU名称:{C8_SKU_URL}
" + isSingleSave: true + columns: + - attributeId: "C8_SKU_URL" + colName: SKU名称 + update: false + required: true + tips: "不存在SKU信息
" + - attributeId: "C8_SKU_69Code" + colName: 69码 + required: true + valid: ^\d{13}$ + tips: "SKU名称为 {C8_SKU_URL} 的69码不是13位纯数字!
" + continueTrans: true + - attributeId: "C8_SKU_69Code" + colName: 69码 + required: true + valid: + validResultRequired: false + update: false + path: "Child:xx" + tips: "SKU名称为 {C8_SKU_URL} 的69码不唯一!
" + type: ref diff --git a/config/src/main/resources/application-integration.yml b/config/src/main/resources/application-integration.yml new file mode 100644 index 0000000..45c4f83 --- /dev/null +++ b/config/src/main/resources/application-integration.yml @@ -0,0 +1,63 @@ +#######################第三方企业APP配置################### +################################开发环境################################ +spring: + profiles: dev + +#企业微信 +cs: + wechat: + url: https://qyapi.weixin.qq.com/cgi-bin + agentid: 1000999 + appkey: ww0215d6203xxxxx + secret: FughJHJeTAgdF426IGRvEfeyzgGG1s6XXXXXXX + flybook: + baseUrl: https://open.feishu.cn/open-apis/ + appid: cli_a19177fb9178500d + secret: uppAX8hRAS73JafIRCFHu7vUssAnynMB + dingtalk: + appkey: + secret: + agentid: + + +--- +################################测试环境################################ +spring: + profiles: test + + +#企业微信 +cs: + wechat: + url: https://qyapi.weixin.qq.com/cgi-bin + agentid: 1000999 + appkey: ww0215d6203xxxxx + secret: FughJHJeTAgdF426IGRvEfeyzgGG1s6XXXXXXX + flybook: + baseUrl: https://open.feishu.cn/open-apis/ + appid: cli_a19177fb9178500d + secret: uppAX8hRAS73JafIRCFHu7vUssAnynMB + dingtalk: + appkey: + secret: + agentid: +--- +################################正式环境################################ +spring: + profiles: prod + +#企业微信 +cs: + wechat: + url: https://qyapi.weixin.qq.com/cgi-bin + agentid: 1000999 + appkey: ww0215d6203xxxxx + secret: FughJHJeTAgdF426IGRvEfeyzgGG1s6XXXXXXX + flybook: + baseUrl: https://open.feishu.cn/open-apis/ + appid: cli_a19177fb9178500d + secret: uppAX8hRAS73JafIRCFHu7vUssAnynMB + dingtalk: + appkey: + secret: + agentid: \ No newline at end of file diff --git a/config/src/main/resources/application-mybatis.yml b/config/src/main/resources/application-mybatis.yml new file mode 100644 index 0000000..87864b3 --- /dev/null +++ b/config/src/main/resources/application-mybatis.yml @@ -0,0 +1,60 @@ +##################数据库配置############## +##############开发环境#################### +spring: + profiles: dev + #数据库配置 + datasource: + dynamic: + lazy: true + primary: c8 + datasource: + c8: + url: jdbc:sqlserver://120.79.18.201:1433;DatabaseName=C8 + username: csidba + password: csidba + hse: + url: jdbc:postgresql://120.79.18.201:25432/exportdb + username: CSIDBA + password: CSIDBA + +--- +##############测试环境#################### +spring: + profiles: test + #数据库配置 + datasource: + dynamic: + lazy: true + primary: c8 + datasource: + c8: + url: jdbc:sqlserver://120.79.18.201:1433;DatabaseName=C8 + username: csidba + password: csidba + hse: + url: jdbc:postgresql://120.79.18.201:25432/exportdb + username: CSIDBA + password: CSIDBA + + +--- +##############正式环境#################### +spring: + profiles: prod + #数据库配置 + datasource: + dynamic: + lazy: true + primary: c8 + datasource: + c8: + url: jdbc:sqlserver://120.79.18.201:1433;DatabaseName=C8 + username: csidba + password: csidba + hse: + url: jdbc:postgresql://120.79.18.201:25432/exportdb + username: CSIDBA + password: CSIDBA + + + diff --git a/config/src/main/resources/application-plm.yml b/config/src/main/resources/application-plm.yml new file mode 100644 index 0000000..6090545 --- /dev/null +++ b/config/src/main/resources/application-plm.yml @@ -0,0 +1,118 @@ +#######################Centric8服务器地址################### +spring: + profiles: dev +cs: + plm: +# http: http://120.79.18.201:80 + http: http://119.23.55.145:80 + #是否自动登陆,默认自动登陆,只有当值不为true或者不为空时不自动登陆 + auto-login: true + enum-display-cache: true + # 是否记录Feign日志 + feign-log: false + #######Centric8服务器登陆用户名/密码 + ###密码加密代码在com.centricsoftware.core.JavaTest中 + user: Administrator + #c8admin + # pwd: centric(bvXcDZBCR1rgv6yr5T8Kn7KnKd4/C4d3) + pwd: c8admin + dbhost: http://119.23.55.145:1433 + dbuser: csidba + dbpwd: csidba + dbname: C8 + # dbtype: Oracle + mail: + host: testmailhost + sender: administrator + senderName: administrator + senderPwd: 123456 + + hse: + host: localhost + portNumber: 9200 + proto: http +#定时任务配置 +task: + #晚上12点,中午12点执行登陆 + cron: 0 0 0,12 * * ? + test: 0/5 * * * * ? +--- +spring: + profiles: test +cs: + plm: + http: http://119.23.55.145.32:80 + #是否自动登陆,默认自动登陆,只有当值不为true或者不为空时不自动登陆 + auto-login: true + enum-display-cache: true + # 是否记录Feign日志 + feign-log: false + #######Centric8服务器登陆用户名/密码 + ###密码加密代码在com.centricsoftware.core.JavaTest中 + user: Administrator + #c8admin + # pwd: centric(bvXcDZBCR1rgv6yr5T8Kn7KnKd4/C4d3) + pwd: c8admin + dbhost: http://119.23.55.145.32:1433 + dbuser: csidba + dbpwd: csidba + dbname: C8 + # dbtype: Oracle + mail: + host: testmailhost + sender: administrator + senderName: administrator + senderPwd: 123456 + hse: + host: localhost + portNumber: 9200 + proto: http +#定时任务配置 +task: + #晚上12点,中午12点执行登陆 + cron: 0 0 0,12 * * ? + test: 0/5 * * * * ? +--- +spring: + profiles: prod +cs: + plm: + http: http://119.23.55.145.32:80 + #是否自动登陆,默认自动登陆,只有当值不为true或者不为空时不自动登陆 + auto-login: true + enum-display-cache: false + # 是否记录Feign日志 + feign-log: false + #######Centric8服务器登陆用户名/密码 + ###密码加密代码在com.centricsoftware.core.JavaTest中 + user: Administrator + #c8admin +# pwd: centric(bvXcDZBCR1rgv6yr5T8Kn7KnKd4/C4d3) + pwd: c8admin + dbhost: http://119.23.55.145.32:1433 + dbuser: csidba + #csidba + dbpwd: csidba + dbname: C8 + # dbtype: Oracle + ##数据库链接 用于DBUtil + pq: + dbuser: csidba + dbpwd: csidba + dbtype: Oracle + dbhost: localhost + dbname: C8REPORT + mail: + host: testmailhost + sender: administrator + senderName: administrator + senderPwd: 123456 + + hse: + host: localhost + portNumber: 9200 + proto: http +#定时任务配置 +task: + #晚上12点,中午12点执行登陆 + cron: 0 0 0,12 * * ? diff --git a/config/src/main/resources/application-pro.yml b/config/src/main/resources/application-pro.yml new file mode 100644 index 0000000..7f1e3f1 --- /dev/null +++ b/config/src/main/resources/application-pro.yml @@ -0,0 +1,94 @@ +###这个yml文件存放项目环境相关配置(无业务) + +################################开发环境################################ +spring: + profiles: dev + mvc: + view: + prefix: / + suffix: .jsp + thymeleaf: + enabled: false + cache: false + check-template-location: false +web: + port: 8088 + logging: + path: D:/plmservice/logs + auth: + username: c8admin + password: c8admin +inter: + fileupload: + msg: + dingding: + http: + systemCode: + token: + bds: + urlPrefix: + signKey: + platformNo: + version: + http: + + +#定时任务配置 +task: + #每周日上午3点刷新缓存 + cron: 0 0 3 ? * SUN +##加密配置 + + +--- +################################测试环境################################ +spring: + profiles: test +web: + port: 8088 + logging: + path: D:/plmservice/logs +inter: + fileupload: + msg: + dingding: + http: + systemCode: + token: + bds: + urlPrefix: + signKey: + platformNo: + version: + http: + +#定时任务配置 +task: + #每周日上午3点刷新缓存 + cron: 0 0 3 ? * SUN +--- +################################正式环境################################ +spring: + profiles: prod +web: + port: 8088 + logging: + path: C:/plmservice/logs +inter: + fileupload: + msg: + dingding: + http: + systemCode: + token: + bds: + urlPrefix: + signKey: + platformNo: + version: + http: + +#定时任务配置 +task: + #每周日上午3点刷新缓存 + cron: 0 0 3 ? * SUN diff --git a/config/src/main/resources/application-rabbitmq.yml b/config/src/main/resources/application-rabbitmq.yml new file mode 100644 index 0000000..51877b8 --- /dev/null +++ b/config/src/main/resources/application-rabbitmq.yml @@ -0,0 +1,38 @@ +###这个yml文件存放rabbitmq相关配置(无业务) + +################################开发环境################################ +spring: + profiles: dev + + rabbitmq: + host: 127.0.0.1 + port: 5672 + username: guest + password: guest + virtual-host: / + +--- +################################测试环境################################ +spring: + profiles: test + + rabbitmq: + host: 127.0.0.1 + port: 5672 + username: guest + password: guest + virtual-host: / +--- +################################正试环境################################ +spring: + profiles: prod + + rabbitmq: + host: 127.0.0.1 + port: 5672 + username: guest + password: guest + virtual-host: / + + + diff --git a/config/src/main/resources/application-redis.yml b/config/src/main/resources/application-redis.yml new file mode 100644 index 0000000..fda45ac --- /dev/null +++ b/config/src/main/resources/application-redis.yml @@ -0,0 +1,73 @@ +###这个yml文件存放redis相关配置(无业务) + +################################开发环境################################ +spring: + profiles: dev + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis + +--- +################################测试环境################################ +spring: + profiles: test + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis + +--- +################################正式环境################################ +spring: + profiles: prod + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis + diff --git a/config/src/main/resources/logback-spring.xml b/config/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..ea27887 --- /dev/null +++ b/config/src/main/resources/logback-spring.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + ${LOG_PATTERN} + utf-8 + + + + + true + + ${LOG_PATTERN} + utf-8 + + ${FILE_NAME} + + ${FILE_NAME_PATTERN} + + 50MB + + + 7 + + + + + + true + + ${LOG_PATTERN} + utf-8 + + ${TASK_FILE_NAME} + + ${TASK_LOG_FILE_NAME_PATTERN} + + 50 MB + + + 30 + + + debug + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/core/.mvn/wrapper/MavenWrapperDownloader.java b/core/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/core/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/core/.mvn/wrapper/maven-wrapper.jar b/core/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/core/.mvn/wrapper/maven-wrapper.properties b/core/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/core/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..2e98bd9 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,202 @@ + + + 4.0.0 + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + core + 2.0 + + + core + Demo project for Spring Boot Dev 2 + + + 1.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + org.projectlombok + lombok + true + + + + + + + + + com.centricsoftware + config + + + com.centricsoftware + commons + + + com.centricsoftware + enhancement + + + com.centricsoftware + sso + + + + + + + + + + + + + + + + + + + + + + + + + + plmservice + + + org.springframework.boot + spring-boot-maven-plugin + 2.5.1 + + + org.springframework.boot.experimental + spring-boot-thin-layout + 1.0.27.RELEASE + + + + + org.springframework.boot.experimental + spring-boot-thin-maven-plugin + 1.0.27.RELEASE + + + + resolve + + resolve + + false + + + + + + + src/main/resources + + **/*.properties + **/*.xml + **/*.yml + **/*.tld + **/*.doc + + true + + + src/main/resources + + **/*.xlsx + **/*.xls + banner.txt + + + **/*.properties + **/*.xml + **/*.tld + **/*.doc + + false + + + src/main/webapp + META-INF/resources + + + + + diff --git a/core/src/main/java/com/centricsoftware/core/CoreApplication.java b/core/src/main/java/com/centricsoftware/core/CoreApplication.java new file mode 100644 index 0000000..ff79f38 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/CoreApplication.java @@ -0,0 +1,35 @@ +package com.centricsoftware.core; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * 代码入口 + * @author zheng.gong + * @date 2020/4/21 + */ +@ComponentScan("com.centricsoftware") +@EnableScheduling +@SpringBootApplication +@EnableFeignClients(basePackages = "com.centricsoftware.*") +@EnableWebMvc +@EnableAsync +@Import(cn.hutool.extra.spring.SpringUtil.class) +public class CoreApplication extends SpringBootServletInitializer { + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(CoreApplication.class); + } + public static void main(String[] args) { + SpringApplication.run(CoreApplication.class, args); + } + +} diff --git a/core/src/main/java/com/centricsoftware/core/aspect/AopAspect.java b/core/src/main/java/com/centricsoftware/core/aspect/AopAspect.java new file mode 100644 index 0000000..309e892 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/aspect/AopAspect.java @@ -0,0 +1,203 @@ +package com.centricsoftware.core.aspect; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.ant.ControllerLog; +import com.centricsoftware.commons.utils.IpUtil; +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.config.cons.Constants; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.dto.log.PlmLog; +import com.centricsoftware.enhancement.modules.c8.service.es.LogElasticsearchService; +import com.centricsoftware.enhancement.service.log.AsyncLogService; +import com.centricsoftware.enhancement.service.log.impl.LogServiceImpl; +import lombok.extern.slf4j.Slf4j; +import okhttp3.RequestBody; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * AopAspect对controller和service类或者带有自定义注解的类增强减少log代码冗余 + * @author ZhengGong + * @date 2020/4/16 + */ +@Aspect +@Component +@Slf4j +public class AopAspect extends BaseAspect { + + @Resource + private LogElasticsearchService logElasticsearchService; + + @Resource + private AsyncLogService asyncLogService; + + @Resource + private LogServiceImpl logService; + + private ThreadLocal logThreadLocal = new ThreadLocal<>(); + + @Pointcut(value = "(execution(* com.centricsoftware.core.controller..*(..))||@annotation(com.centricsoftware.commons.ant.ControllerLog))") + public void controllerAspect(){} + + @Pointcut("execution(* com.centricsoftware.enhancement.modules.c8.feign.*.*(..))||@annotation(com.centricsoftware.commons.ant.ServiceLog)") + public void serviceAspcet(){} + /** + * 对Controller环绕增强 + * @param joinPoint + * @return + * @throws Throwable + */ + @Around("controllerAspect()") + public Object controllerAround(ProceedingJoinPoint joinPoint) throws Throwable { + log.info("---------------------Controller方法调用开始-----------------------"); + // getControllerArgsDescription(joinPoint); + //是否记录日志 + CsProperties csProperties = SpringContextHolder.getBean(CsProperties.class); + Instant t1 = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)); + Object proceed; + if(StrUtil.equals(csProperties.getValue("feign-log"), Constants.Bool.TRUE, true)){ + proceed = around(joinPoint, log); + }else{ + proceed = joinPoint.proceed(); + } + Instant t2 = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)); + Duration between = Duration.between(t1, t2); + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + log.warn("{}执行开始时间:{},结束时间:{},耗时{}ms",methodSignature.getMethod(),t1,t2,between.toMillis()); + log.debug("方法{}执行成功,返回结果{}",methodSignature.getMethod().getName(),proceed); + log.info("---------------------Controller方法调用结束-----------------------"); + return proceed; + } + + /** + * 对Service环绕增强 + * @param joinPoint + * @return + * @throws Throwable + */ + @Around("serviceAspcet()") + public Object serviceAround(ProceedingJoinPoint joinPoint) throws Throwable { + Object proceed = joinPoint.proceed(); + log.debug("---------------------Service方法调用 开始-----------------------"); + getServiceArgsDescription(joinPoint); + Instant t1 = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)); + Instant t2 = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)); + Duration between = Duration.between(t1, t2); + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + log.debug("{}执行开始时间:{},结束时间:{},耗时{}ms",methodSignature.getMethod(),t1,t2,between.toMillis()); + log.debug("方法{}执行成功,返回结果{}",methodSignature.getMethod(),proceed); + log.debug("---------------------Service方法调用 结束-----------------------"); + return proceed; + } + + public static void getServiceArgsDescription(JoinPoint joinPoint){ + //1.获取到所有的参数值的数组 + Object[] args = joinPoint.getArgs(); + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + //2.获取到方法的所有参数名称的字符串数组 + String[] parameterNames = methodSignature.getParameterNames(); + log.debug("---------------参数列表---------------------"); + for (int i =0 ,len=parameterNames.length;i < len ;i++){ + log.debug("参数名:{},参数值:{}",parameterNames[i],args[i]); + + } + } + // ================日志采集======================= + @Override + protected void preHandle(ProceedingJoinPoint jp, Object reqArgs) { + MethodSignature ms = (MethodSignature) jp.getSignature(); + ControllerLog controllerLog = ms.getMethod().getAnnotation(ControllerLog.class); + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + PlmLog plmLog = buildPlmLog(controllerLog, request, reqArgs); + logThreadLocal.set(plmLog); + } + + @Override + protected void finallyHandle(MethodSignature ms, Object result,ControllerLog controllerLog) { + PlmLog plmLog = logThreadLocal.get(); + // 没有注解就不记录接口日志 + if (plmLog != null) { + logElasticsearchService.buildUpdatePlmLog(plmLog, result); + } + saveLog(plmLog,controllerLog);// 最后做日志存储 + logThreadLocal.remove(); + } + + private void saveLog(PlmLog plmLog, ControllerLog controllerLog) { + CsProperties csProperties = SpringContextHolder.getBean(CsProperties.class); + String value = csProperties.getValue("log-save-type"); + if(StrUtil.equals(value, "es", true)){ + logElasticsearchService.saveLog(plmLog); + } + if(StrUtil.equals(value, "db", true)){ + saveLogInDB(plmLog,controllerLog); + } + } + + private void saveLogInDB(PlmLog plmLog,ControllerLog controllerLog) { + FeignLogDto convert = Convert.convert(FeignLogDto.class, plmLog); + if(controllerLog.interfaceLog()){ + convert.setLogType("接口日志"); + }else{ + convert.setLogType("操作日志"); + } + convert.setUrl(plmLog.getPath()); + convert.setReturnMsg(plmLog.getReturnText()); + convert.setRequestId(controllerLog.requestId()); + convert.setResponseId(controllerLog.responseId()); + logService.saveLog(convert); + } + + private PlmLog buildPlmLog(ControllerLog controllerLog, HttpServletRequest request, Object param) { + String requestBody = getRequestBody(request); + String desc = Optional.ofNullable(controllerLog).map(ControllerLog::value).orElse(StringUtils.EMPTY); + PlmLog plmLog = logElasticsearchService.newLog(request.getRequestURI(), IpUtil.getIpAddr(request), param, desc); + plmLog.setDebug(requestBody); + if (request instanceof ContentCachingRequestWrapper) { + ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request; + String body = new String(wrappedRequest.getContentAsByteArray()); + plmLog.setDebug(body); + } + return plmLog; + } + + private String getRequestBody(HttpServletRequest request) { + StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + }catch (Exception e){ + log.error("获取请求体失败",e); + } + return stringBuilder.toString(); + } + +} diff --git a/core/src/main/java/com/centricsoftware/core/aspect/BaseAspect.java b/core/src/main/java/com/centricsoftware/core/aspect/BaseAspect.java new file mode 100644 index 0000000..8816e92 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/aspect/BaseAspect.java @@ -0,0 +1,203 @@ +package com.centricsoftware.core.aspect; + +import com.centricsoftware.commons.ant.ControllerLog; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.utils.JsonUtil; +import com.google.common.base.Stopwatch; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.omg.CORBA.SystemException; +import org.slf4j.Logger; +import org.springframework.ui.Model; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * 基础环绕切面处理方法 + * @author liaochangjiang + * @since 2024-05-07 11:59 + */ +public class BaseAspect { + + public Object around(ProceedingJoinPoint jp, Logger log) { + Stopwatch stopwatch = Stopwatch.createStarted(); + MethodSignature ms = (MethodSignature) jp.getSignature(); + ControllerLog controllerLog = ms.getMethod().getAnnotation(ControllerLog.class); + boolean logResp = !Objects.isNull(controllerLog) && controllerLog.response(); + final String signatureName = getSignatureName(controllerLog, ms); + Object result = null; + try { + Object reqArgs = getRequestArgs(jp, ms); + String argsJson = StringUtils.EMPTY; + try { + argsJson = JsonUtil.toJSONString(reqArgs, new String[]{}); + log.info("{} 请求参数: {}", signatureName, argsJson); + } catch (Exception e) { + log.warn("序列化请求参数失败", e); + } + + preHandle(jp, argsJson); + result = jp.proceed(); + if (!logResp) { + log.info("{} 耗时: {}", signatureName, stopwatch); + } + return result; + } catch (Throwable e) { + String logPrefix = "系统"; + String errorLogStr = String.format("%s异常 %s", logPrefix, signatureName); + errorLogStr += " 耗时:" + stopwatch; + if (e instanceof SystemException) { + //真实日志信息 + String logText = e.getLocalizedMessage(); + String message = e.getMessage(); + if (StringUtils.isNotEmpty(logText)) { + message = logText; + } + errorLogStr += " logText:" + message; + } + log.error(errorLogStr, e); + result = handleResponse(ms, e, log); + } finally { + stopwatch.stop(); + if (logResp) { + log.info("{} 耗时: {} 返回: {}", signatureName, stopwatch, JsonUtil.toJSONString(result)); + } + finallyHandle(ms, result,controllerLog); + } + return result; + } + + /** + * 处理前调用 + * + * @author liaochangjiang + * @since 2021-09-26 19:12:55 + */ + protected void preHandle(ProceedingJoinPoint jp, Object argsJson) { + + } + + /** + * finally处理时调用 + * + * @author liaochangjiang + * @since 2021-12-27 18:59:08 + */ + protected void finallyHandle(MethodSignature ms, Object result,ControllerLog controllerLog) { + + } + + /** + * 获取请求参数 + * + * @author liaochangjiang + * @since 2022-02-25 13:10:32 + */ + private Object getRequestArgs(ProceedingJoinPoint jp, MethodSignature ms) { + Object reqArgs = new Object(); + if (ArrayUtils.getLength(jp.getArgs()) >= 1 && (jp.getArgs()[0] instanceof ResEntity)) { + reqArgs = jp.getArgs()[0]; + } else { + String name = ms.getMethod().getDeclaringClass().getName(); + if (name.endsWith("Controller") || name.endsWith("C8Feign")) { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + return reqArgs; + } + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + String contentType = request.getContentType(); + if (org.apache.commons.lang3.StringUtils.isNotBlank(contentType) && contentType.contains("form-")) { + Map parameterMap = request.getParameterMap(); + if (!parameterMap.isEmpty()) { + return parameterMap; + } + } + return request.getParameterMap(); +// Object firstNormalArgs = getFirstNormalArgs(jp); +// if (firstNormalArgs == null) { +// return request.getParameterMap(); +// } +// reqArgs = firstNormalArgs; + } + } + return reqArgs; + } + + /** + * 获取第一个正常参数 + * + * @author liaochangjiang + * @since 2022-05-07 09:20:32 + */ + private Object getFirstNormalArgs(ProceedingJoinPoint jp) { + if (jp.getArgs().length == 0) { + return null; + } + Object[] args = jp.getArgs(); + for (Object arg : args) { + if (!(arg instanceof ServletRequest) && !(arg instanceof ServletResponse) && !(arg instanceof HttpSession) + && !(arg instanceof MultipartFile) && !(arg instanceof Model)) { + return arg; + } + } + return null; + } + + /** + * 处理异常响应 + * + * @author liaochangjiang + * @since 2021-09-26 19:03:29 + */ + @SuppressWarnings("rawtypes") + protected Object handleResponse(MethodSignature ms, Throwable e,Logger log) { + Class returnType = ms.getReturnType(); + if (returnType == Void.TYPE) { + Optional.ofNullable(RequestContextHolder.getRequestAttributes()).map(t -> (ServletRequestAttributes) t) + .map(ServletRequestAttributes::getResponse) + .ifPresent(resp -> writeResponse(resp,WebResponse.failure(e.getMessage()),log)); + return null; + } + try { + return returnType.newInstance(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static void writeResponse(HttpServletResponse response, ResEntity resp, Logger log) { + response.setContentType("application/json;charset=UTF-8"); + try { + response.getWriter().print(JsonUtil.toJSONString(resp)); + if (!resp.isSuccess()) { + response.sendError(resp.getCode(), resp.getMsg()); + } + } catch (IOException e) { + log.error("响应失败", e); + } + } + + protected String getSignatureName(ControllerLog controllerLog, MethodSignature ms) { + if (!Objects.isNull(controllerLog) && StringUtils.isNotBlank(controllerLog.value())) { + return controllerLog.value() + "|" + ms.toShortString(); + } + return ms.toShortString(); + } + +} diff --git a/core/src/main/java/com/centricsoftware/core/config/BootConfig.java b/core/src/main/java/com/centricsoftware/core/config/BootConfig.java new file mode 100644 index 0000000..af8f17e --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/config/BootConfig.java @@ -0,0 +1,77 @@ +package com.centricsoftware.core.config; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.utils.SpringUtil; +import com.centricsoftware.config.cons.Constants; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.modules.c8.component.EnumCache; +import com.centricsoftware.enhancement.modules.c8.service.C8LoginService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Scheduled; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; + +/** + * 包含一些启动后需要注册的信息 + * @author zheng.gong + * @date 2020/4/21 + */ +@Slf4j +@Configuration +public class BootConfig { + + + /** + * 按照标准时间来算,晚上12点,中午12点执行登陆 + */ + @Scheduled(cron = "${task.cron}") + public void job1() { + log.info("【重新登陆】"+LocalDateTime.now()); + } + + /** + * 启动后配置 + * @return LoadingConfig + */ + @Bean + public LoadingConfig cfg(){ + return new LoadingConfig(); + } + + /** + * 启动后的初始化操作 + * @author zheng.gong + * @date 2020/4/23 + */ + static class LoadingConfig{ + @Autowired + CsProperties csProperties; + /** + * 启动后自动登陆PLM + */ + @PostConstruct + public void init(){ + if(StrUtil.equals(csProperties.getValue("auto-login"), Constants.Bool.TRUE,true) || StrUtil.isBlank(csProperties.getValue( + "auto-login"))){ + log.debug("===========系统启动后登陆PLM============"); + C8LoginService loginService = SpringUtil.getBean(C8LoginService.class); + loginService.login(); + log.debug("=========登陆成功!========="); + log.debug("=========开始初始化缓存!========="); + EnumCache enumCache = SpringUtil.getBean(EnumCache.class); + boolean equals = StrUtil.equals(csProperties.getValue("enum-display-cache"), Constants.Bool.TRUE, true); + enumCache.init(equals); + log.debug("=========初始化缓存成功!========="); + + }else{ + log.debug("===========系统启动后不登陆PLM============"); + } + } + } + + +} diff --git a/core/src/main/java/com/centricsoftware/core/config/WebMvcConfig.java b/core/src/main/java/com/centricsoftware/core/config/WebMvcConfig.java new file mode 100644 index 0000000..f0f58ea --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/config/WebMvcConfig.java @@ -0,0 +1,54 @@ +package com.centricsoftware.core.config; + +import com.centricsoftware.enhancement.filter.AddCookieFilter; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; + +/** + * 跨域处理 + * @author zheng.gong + * @date 2020/4/16 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + private static final long MAX_AGE_SECS = 3600; + + @Resource + AddCookieFilter addCookieFilter; + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE") + .maxAge(MAX_AGE_SECS); + } + + + /** + * 注册拦截器 + * @param registry InterceptorRegistry + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(addCookieFilter) + .addPathPatterns("/**"); + } + + + /** + * 注册拦截器 + * @param registry InterceptorRegistry + */ +// @Override +// public void addInterceptors(InterceptorRegistry registry) { +// registry.addInterceptor(new SimpleAuthInterceptor()).addPathPatterns(interceptLIst()); +// } +// +// public List interceptLIst(){ +// return Lists.newArrayList("/test/testAuth"); +// } +} diff --git a/core/src/main/java/com/centricsoftware/core/controller/TestController.java b/core/src/main/java/com/centricsoftware/core/controller/TestController.java new file mode 100644 index 0000000..0db2f41 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/controller/TestController.java @@ -0,0 +1,193 @@ +package com.centricsoftware.core.controller; + +import cn.hutool.json.JSONUtil; +import cn.hutool.poi.excel.ExcelReader; +import cn.hutool.poi.excel.ExcelUtil; +import com.centricsoftware.commons.ant.ControllerLog; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.utils.ExportExcel; +import com.centricsoftware.commons.utils.IpUtil; +import com.centricsoftware.config.entity.CenterProperties; +import com.centricsoftware.core.dto.DataPackage; +import com.centricsoftware.core.service.CommonExportService; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.modules.c8.component.EnumCache; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.dml.dto.node.change.C8OperationNode; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8Search; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; + +/** + * 测试控制类 + * @author zheng.gong + * @date 2020/4/16 + */ +@Slf4j +@RequestMapping("/test") +@Controller +public class TestController { + + @Value("${cs.plm.http}") + private String host; + + @Autowired + CenterProperties centerProperties; + + @Autowired + C8NodeService c8NodeService; + + @Resource + private CommonExportService commonExportService; + + /** + * jsp测试 + * + * @return + */ + @RequestMapping("/hello") + @ResponseBody + @ControllerLog("日志采集") + public ResEntity hello() { + // String name = c8NodeService.queryExpressionResult("`Node Name`", "centric://REFLECTION/INSTANCE/User/Administrator"); + return WebResponse.success("欢迎你:"+"; host:"+host); + } + + /** + * jsp测试 + * @return + */ + @RequestMapping("/testJsp") + public String testJsp(){ + return "index"; + } + + + @ResponseBody + @PostMapping("/testParam") + public ResEntity testDemo(@RequestBody Map map){ + log.debug("get json param:{}", JSONUtil.toJsonStr(map)); + Assert.isTrue(map.containsKey("test"),"不包含test"); + return WebResponse.success(ResCode.SUCCESS,map); + } + + @ResponseBody + @PostMapping("/testProp") + public ResEntity testCsProp(){ + Map plm = centerProperties.getProp(); + return WebResponse.success(ResCode.SUCCESS,plm); + } + + @ResponseBody + @PostMapping("testCookie") + public ResEntity testCookie() throws Exception { + + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @PostMapping("logout") + public ResEntity logout() throws Exception { + return WebResponse.success(ResCode.SUCCESS); + } + + + @RequestMapping("/testXML") + @ResponseBody + @ControllerLog("执行XML日志") + public ResEntity cookie() { + String xml = C8OperationNode.changeNode("C1402").changeAttribute("Code", "string", "10086").getXml(); + String url = c8NodeService.createURL(); + // String materialSecurityGroup1 = C8OperationNode.createNode(url, "MaterialSecurityGroup").getXml(); + c8NodeService.processNode(xml); + return WebResponse.success(xml); + } + + @Resource + EnumCache enumCache; + + @ResponseBody + @RequestMapping("testEnum") + public ResEntity testEnum() throws Exception { + String fullnameByDisplay = enumCache.getFullnameByDisplay("C8_Apeed", "选中"); + return WebResponse.success(ResCode.SUCCESS,fullnameByDisplay); + } + + @ResponseBody + @RequestMapping("/getIP") + public ResEntity getIp(HttpServletRequest request) throws Exception { + String ip = IpUtil.getIpAddr(request); + return WebResponse.success(ResCode.SUCCESS,ip); + } + @ResponseBody + @RequestMapping("/excel") + public ResEntity excel(MultipartFile file) throws Exception { + ExcelReader reader = ExcelUtil.getReader(file.getInputStream()); + List> maps = reader.readAll(); + System.out.println(maps.toString()); + return WebResponse.success(ResCode.SUCCESS); + } + + /** + * Excel导出通用方式 + * @author liaochangjiang + * @since 2024-03-05 14:22 + */ + @PostMapping("exportExcel") + public void export1(HttpServletResponse response) throws Exception { + // final ExportExcel excel = commonExportService.export1(); + // excel.write(response, "区域块化导出.xlsx").dispose(); + // commonExportService.export2(response);// 固定模板填充占位 + // commonExportService.export5(response);// 固定模板填充占位 + // final ExportExcel excel = commonExportService.export3(response);// Launchmap实现 + // excel.write(response, "Launchmap实现.xlsx").dispose(); + // commonExportService.export4(response);// 动态table填充占位 + } + + @ResponseBody + @RequestMapping("/test") + @ControllerLog(value = "测试日志",interfaceLog = true) + public ResEntity testLog(String name,String url,HttpServletResponse response){ + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/test1") + @ControllerLog(value = "测试日志1",interfaceLog = true) + public ResEntity testLog1(@RequestBody FeignLogDto dto){ + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/test2") + public ResEntity test(){ + String xml = C8Search.newSearch("DataPackage") + .attr("C8_DP_Type", "EQ", "C8_DPType:1") + .attr("C8_DP_CloComState", "EQ", "true") + .attrRef("Category1", "EQ", "C1848462", "Child:__Parent__") + .getXml(); + DepPath depPath = DepPath.builderXml() + .xml(xml) + .build(); + List dataPackages = c8NodeService.depPathByEntity(depPath, DataPackage.class); + return WebResponse.success(ResCode.SUCCESS); + } + +} diff --git a/core/src/main/java/com/centricsoftware/core/dto/BaseDto.java b/core/src/main/java/com/centricsoftware/core/dto/BaseDto.java new file mode 100644 index 0000000..a24c7e8 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/dto/BaseDto.java @@ -0,0 +1,13 @@ +package com.centricsoftware.core.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseDto implements Serializable { + @JsonIgnore + private String url; + +} diff --git a/core/src/main/java/com/centricsoftware/core/dto/CommonExportDto.java b/core/src/main/java/com/centricsoftware/core/dto/CommonExportDto.java new file mode 100644 index 0000000..b0fb123 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/dto/CommonExportDto.java @@ -0,0 +1,35 @@ +package com.centricsoftware.core.dto; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.util.ExportUtil.ExportTemplateRegion; +import lombok.Data; + +import java.io.InputStream; + +@Data +@ExportTemplateRegion(row1 = 0, col1 = 2, row2 = 11, col2 = 3, groups = "akaBuyTags") +@ExportTemplateRegion(row1 = 3, col1 = 1, row2 = 9, col2 = 1, groups = "launchmap") +public class CommonExportDto { + + private InputStream styleImageIn; + private InputStream styleImageIn1; + + private Integer index; + + // 名称 + @DepPathField(exp = "$Name") + private String name; + + // 编码 + @DepPathField(exp = "Code") + private String code; + + // 描述 + @DepPathField(exp = "Description") + private String description; + + // 图片 + @DepPathField(exp = "Images[].Viewable") + private String realImgUrl; +} diff --git a/core/src/main/java/com/centricsoftware/core/dto/DataPackage.java b/core/src/main/java/com/centricsoftware/core/dto/DataPackage.java new file mode 100644 index 0000000..6334173 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/dto/DataPackage.java @@ -0,0 +1,32 @@ +package com.centricsoftware.core.dto; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +@Data +public class DataPackage { + + /** + * 唯一id + */ + @DepPathField(exp = "$URL") + private String url; + @DepPathField(exp = "C8_DP_AcceptTechPack") + private boolean acceptTechPack; + @DepPathField(exp = "C8_DP_BOM", type = DepPathEntityType.DOUBLE) + private Double bom; + @DepPathField(exp = "C8_DP_DesignType") + private String designType; + @DepPathField(exp = "C8_DP_DesignType", type = DepPathEntityType.ENUM_DESC) + private String designTypeDesc; + @DepPathField(exp = "C8_DP_DesignType", type = DepPathEntityType.ENUM_VALUE) + private String designTypeValue; + @DepPathField(exp = "C8_DP_DesignActualDate", type = DepPathEntityType.DATA) + private Date designActualDate; + +} diff --git a/core/src/main/java/com/centricsoftware/core/handler/GlobalExceptionHandler.java b/core/src/main/java/com/centricsoftware/core/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..3666fee --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/handler/GlobalExceptionHandler.java @@ -0,0 +1,122 @@ +package com.centricsoftware.core.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; +import com.centricsoftware.commons.exception.ParamException; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; + +import javax.validation.ConstraintViolationException; + +/** + * 全局异常处理 + * @author ZhengGong + * @date 2019/5/24 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(value = NoHandlerFoundException.class) + public ResEntity handleNoHandlerFoundException(NoHandlerFoundException e) { + log.info("-------------------------进入全局异常捕获NoHandlerFoundException---------------------------"); + log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", e.getRequestURL(), e.getHttpMethod()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.REQUEST_NOT_FOUND); + } + @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) + public ResEntity handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { + log.info("-------------------------进入全局异常捕获HttpRequestMethodNotSupportedException---------------------------"); + log.error("【全局异常拦截】HttpRequestMethodNotSupportedException: 当前请求方式 {}, 支持请求方式 {}", e.getMethod(), JSONUtil.toJsonStr(e.getSupportedHttpMethods())); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.HTTP_BAD_METHOD); + } + @ExceptionHandler(value = MethodArgumentNotValidException.class) + public ResEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.info("-------------------------进入全局异常捕获MethodArgumentNotValidException---------------------------"); + log.error("【全局异常拦截】MethodArgumentNotValidException", e); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.BAD_REQUEST.getCode(), e.getBindingResult() + .getAllErrors() + .get(0) + .getDefaultMessage(), false); + } + @ExceptionHandler(value = ConstraintViolationException.class) + public ResEntity handleConstraintViolationException(ConstraintViolationException e) { + log.info("-------------------------进入全局异常捕获ConstraintViolationException---------------------------"); + log.error("【全局异常拦截】ConstraintViolationException", e); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.BAD_REQUEST.getCode(), CollUtil.getFirst(e.getConstraintViolations()) + .getMessage(), null); + } + @ExceptionHandler(value = MethodArgumentTypeMismatchException.class) + public ResEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { + log.info("-------------------------进入全局异常捕获MethodArgumentTypeMismatchException---------------------------"); + log.error("【全局异常拦截】MethodArgumentTypeMismatchException: 参数名 {}, 异常信息 {}", e.getName(), e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.PARAM_NOT_MATCH); + } + @ExceptionHandler(value = HttpMessageNotReadableException.class) + public ResEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.info("-------------------------进入全局异常捕获HttpMessageNotReadableException---------------------------"); + log.error("【全局异常拦截】HttpMessageNotReadableException: 错误信息 {}", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.PARAM_NOT_NULL); + } + @ExceptionHandler(value = MissingServletRequestParameterException.class) + public ResEntity handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { + log.info("-------------------------进入全局异常捕获MissingServletRequestParameterException---------------------------"); + log.error("【全局异常拦截】MissingServletRequestParameterException: 异常信息 {}", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.PARAM_NOT_MATCH,e.getMessage()); + } + @ExceptionHandler(value = ParamException.class) + public ResEntity handleParamException(ParamException e) { + log.info("-------------------------进入全局异常捕获ParamException---------------------------"); + log.error("【全局异常拦截】ParamException: 异常信息 {}", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.PARAM_NOT_NULL,e.getMessage()); + } + @ExceptionHandler(value = JsonProcessingException.class) + public ResEntity handleJsonProcessingException(JsonProcessingException e) { + log.info("-------------------------进入全局异常捕获JsonProcessingException---------------------------"); + log.error("【全局异常拦截】JsonProcessingException: 异常信息 {}", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.JSON_PARSE_ERROR,e.getMessage()); + } + @ExceptionHandler(value = BaseException.class) + public ResEntity handleBaseException(BaseException e) { + log.error("【全局异常拦截】BaseException: 异常信息 {}", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.parse(e.getCode()),e.getMessage()); + } + + + @ExceptionHandler(value = Exception.class) + public ResEntity handleGlobalException(Exception e) { + log.info("-------------------------进入全局异常捕获Exception---------------------------"); + log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.SYSTEM_RUNTIME_ERROR,e.getMessage()); + } + + @ExceptionHandler(value = IllegalArgumentException.class) + public ResEntity handleAssertException(IllegalArgumentException e){ + log.info("-------------------------进入全局异常捕获Exception---------------------------"); + log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage()); + log.error("异常栈:",e); + return WebResponse.failure(ResCode.SYSTEM_RUNTIME_ERROR,e.getMessage()); + } +} diff --git a/core/src/main/java/com/centricsoftware/core/service/CommonExportService.java b/core/src/main/java/com/centricsoftware/core/service/CommonExportService.java new file mode 100644 index 0000000..0f6e519 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/service/CommonExportService.java @@ -0,0 +1,216 @@ +package com.centricsoftware.core.service; + + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillWrapper; +import com.centricsoftware.commons.utils.ExportExcel; +import com.centricsoftware.commons.utils.MarkImageUtil; +import com.centricsoftware.core.dto.CommonExportDto; +import com.centricsoftware.enhancement.modules.c8.component.design.chain.IDepPathSearchChain; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.util.ExportUtil.AnalysisCell; +import com.centricsoftware.enhancement.util.ExportUtil.ExportUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.io.IOUtils; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Service +public class CommonExportService { + + @Resource + private ExportUtil exportUtil; + + @Resource + private C8NodeService c8NodeService; + + @Resource + private IDepPathSearchChain iDepPathSearchChain; + + // 本地资源类路径 + private static final String classPath = "D:/plmservice/"; + + private List getBoList () { + List list = Lists.newArrayList(); + list.add("C482"); + list.add("C1402"); + list.add("C1403"); + list.add("C523"); + list.add("C525"); + DepPath deppath = DepPath.builder().addUrls(list).build(); + List resultList = iDepPathSearchChain.execute(deppath,CommonExportDto.class,null); + for (CommonExportDto bo : resultList) { + bo.setStyleImageIn(c8NodeService.getFileFromDirectAddr(bo.getRealImgUrl())); + } + return resultList; + } + + public ExportExcel export1() throws Exception { + // 获取配色信息 + List list = getBoList(); + ClassPathResource classPathResource = new ClassPathResource("template/export1.xlsx"); + try (InputStream in = classPathResource.getInputStream()) { + XSSFWorkbook wb = new XSSFWorkbook(Objects.requireNonNull(in)); + ExportExcel excel = new ExportExcel(wb, wb.getSheetAt(0)); + exportUtil.initTemplateRegion(CommonExportDto.class, "akaBuyTags"); + XSSFSheet sheet = excel.getSrcWb().cloneSheet(0, "sheet1"); + excel.setSheet(sheet); + exportUtil.fillDataComment(excel,7,list); + sheet.getPrintSetup().setScale((short) 75); + sheet.getPrintSetup().setTopMargin(0); + wb.removeSheetAt(0); + return excel; + } finally { + exportUtil.remoteTemplateRegion(); + } + } + + /** + * 获取数据 + * @author liaochangjiang + * @since 2024-03-05 15:04 + */ + private CommonExportDto getData () { + // 获取配色信息 + CommonExportDto bo = getBoList().get(0); + // 获取到的BO进行图片填充 + bo.setStyleImageIn(c8NodeService.getFileFromDirectAddr(bo.getRealImgUrl())); + bo.setStyleImageIn1(c8NodeService.getImageViewable("C1616")); + return bo; + } + + /** + * 固定模板导出 + * @author liaochangjiang + * @since 2024-03-05 15:01 + */ + public void export2(HttpServletResponse response) throws Exception { + ExportUtil.setExportResponseInfo(response, "固定模板导出.xlsx");// 设置文件名 + CommonExportDto bo = getData(); + String template = "template/export2.xlsx";// 获取模板文件 + exportUtil.exportByStatic(response.getOutputStream(),template,bo);// 固定模板导出 + } + + /** + * 固定模板导出-多数据源 + * @author liaochangjiang + * @since 2024-03-05 15:01 + */ + public void export5(HttpServletResponse response) throws Exception { + ExportUtil.setExportResponseInfo(response, "固定模板导出-多数据源.xlsx");// 设置文件名 + CommonExportDto bo = getData(); + // 获取配色信息 + List list = getBoList(); + List request = Lists.newArrayList(); + request.add(new FillWrapper("list", list)); + request.add(bo); + String template = "template/export5.xlsx";// 获取模板文件 + exportUtil.exportByStatic(response.getOutputStream(),template,request);// 固定模板导出 + } + + + /** + * 做图片转换-打上星号或者文字 + * @author liaochangjiang + * @since 2024-02-20 21:05 + */ + public InputStream exchangeImage(InputStream sourceIn) throws Exception { + Map imageMap = getImageIcon(); + if (sourceIn == null) { + return null; + } + // 获取初始的图片的宽度 + Image img = ImageIO.read(sourceIn); + // 宽度 + int initX = img.getWidth(null); + // 高度 + int initY = img.getHeight(null); + int iconWidth = 50; + int x = 0; + int y = 0; + // 添加对应的图标坐标 + return MarkImageUtil.addImgMark(imageMap.get("red"),iconWidth,iconWidth, img,x,y,initX,initY); + } + + /** + * 插入文字 + * @return + */ + public InputStream addTextMark(InputStream sourceIn) throws IOException { + if (sourceIn == null) { + return null; + } + // 获取初始的图片的宽度 + Image img = ImageIO.read(sourceIn); + // 宽度 + int initX = img.getWidth(null); + // 高度 + int initY = img.getHeight(null); + Color color = Color.RED; + Font font = new Font("微软雅黑", Font.BOLD, 18); + return MarkImageUtil.addTextMark(img, 10, 10, initX, initY, "测试文字", color, font); + } + + /** + * 获取图片的流集合;用于打星 + * @author liaochangjiang + * @since 2024-02-20 21:04 + */ + public Map getImageIcon() throws Exception { + Map imageMap = new HashMap<>(); + imageMap.put("red", ImageIO.read(Objects.requireNonNull(this.getClass().getClassLoader().getResourceAsStream("static/icon/red.jpg"))));// 核心款 + return imageMap; + } + + + public ExportExcel export3(HttpServletResponse response) throws Exception { + // 获取配色信息 + List list = getBoList(); + ClassPathResource classPathResource = new ClassPathResource("template/export3.xlsx"); + try (InputStream in = classPathResource.getInputStream()) { + XSSFWorkbook wb = new XSSFWorkbook(Objects.requireNonNull(in)); + ExportExcel excel = new ExportExcel(wb, wb.getSheetAt(0)); + exportUtil.initTemplateRegion(CommonExportDto.class, "launchmap"); + XSSFSheet sheet = excel.getSrcWb().cloneSheet(0, "sheet1"); + excel.setSheet(sheet); + exportUtil.fillDataComment(excel,32,list); + sheet.getPrintSetup().setScale((short) 75); + sheet.getPrintSetup().setTopMargin(0); + wb.removeSheetAt(0); + return excel; + } finally { + exportUtil.remoteTemplateRegion(); + } + } + + public void export4(HttpServletResponse response) throws Exception { + ExportUtil.setExportResponseInfo(response, "列表型导出.xlsx");// 设置文件名 + String template = "template/export4.xlsx";// 获取模板文件 + // 获取数据 + List list = getBoList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).setIndex(i + 1); + } + exportUtil.exportByStatic(response.getOutputStream(),template,list);// 固定模板导出 + } +} diff --git a/core/src/main/java/com/centricsoftware/core/task/config/TaskConfig.java b/core/src/main/java/com/centricsoftware/core/task/config/TaskConfig.java new file mode 100644 index 0000000..350325e --- /dev/null +++ b/core/src/main/java/com/centricsoftware/core/task/config/TaskConfig.java @@ -0,0 +1,39 @@ +package com.centricsoftware.core.task.config; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +/** + * 使用java配置文件配置task + * + * @author zheng.gong + * @date 2020/4/21 + */ +@Configuration +@EnableScheduling +@ComponentScan(basePackages = {"com.centricsoftware.core.task.job"}) +public class TaskConfig implements SchedulingConfigurer { + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskExecutor()); + } + + /** + * 这里等同于配置文件配置 + * {@code spring.task.scheduling.pool.size=20} - Maximum allowed number of threads. + * {@code spring.task.scheduling.thread-name-prefix=Job-Thread- } - Prefix to use for the names of newly created threads. + * {@link org.springframework.boot.autoconfigure.task.TaskSchedulingProperties} + */ + @Bean + public Executor taskExecutor() { + return new ScheduledThreadPoolExecutor(20, new BasicThreadFactory.Builder().namingPattern("Job-Thread-%d").build()); + } +} diff --git a/core/src/main/java/com/centricsoftware/task/job/inter/DeleteLogTask.java b/core/src/main/java/com/centricsoftware/task/job/inter/DeleteLogTask.java new file mode 100644 index 0000000..8a87929 --- /dev/null +++ b/core/src/main/java/com/centricsoftware/task/job/inter/DeleteLogTask.java @@ -0,0 +1,38 @@ +package com.centricsoftware.task.job.inter; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.config.cons.Constants; +import com.centricsoftware.enhancement.service.log.impl.LogServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 删除接口日志、操作日志等 + */ +@Component +@Slf4j +public class DeleteLogTask { + + @Resource + LogServiceImpl logService; + + //注入cs.plm.feign-log的yml配置 + @Value("${cs.plm.feign-log:false}") + private String feignLog; + + @Scheduled(cron = "0 0 5 * * ? ") //每天凌晨5点 + public void deleteLog() { + log.info("删除日志开始.."); + if(StrUtil.equals(feignLog, Constants.Bool.TRUE, true)){ + int count = logService.deleteLog(60); + log.info("删除日志{}条" ,count); + }else{ + log.info("未启用日志记录,不执行删除操作。启用配置项:{}" ,"cs.plm.feign-log"); + } + log.info("删除日志结束"); + } +} diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml new file mode 100644 index 0000000..bf2991f --- /dev/null +++ b/core/src/main/resources/application.yml @@ -0,0 +1,39 @@ +###这个yml文件存放系统级别的相关配置 +spring: + application: + name: plmservice + http: + encoding: + charset: UTF-8 + profiles: + active: @profileActive@ + include: redis,rabbitmq,mybatis,plm,import,pro,common,sso,integration,es +server: + port: ${web.port} + tomcat: + uri-encoding: UTF-8 + servlet: + context-path: /plmservice +logging: + config: classpath:logback-spring.xml + file: + path: ${web.logging.path} +feign: + httpclient: + enabled: false + okhttp: + enabled: true + +#最大连接数 +http: + maxTotal: 100 + #并发数 + defaultMaxPerRoute: 20 + #创建连接的最长时间 + connectTimeout: 10000 + #从连接池中获取到连接的最长时间 + connectionRequestTimeout: 5000 + #数据传输的最长时间 + socketTimeout: 100000 + #线程失活检查 + validateAfterInactivity: 30000 diff --git a/core/src/main/resources/banner.txt b/core/src/main/resources/banner.txt new file mode 100644 index 0000000..bf24f54 --- /dev/null +++ b/core/src/main/resources/banner.txt @@ -0,0 +1,7 @@ + _ _ + ___ ___ _ __ | |_ _ __ (_) ___ + / __|/ _ \| '_ \ | __|| '__|| | / __| +| (__| __/| | | || |_ | | | || (__ + \___|\___||_| |_| \__||_| |_| \___| + https://www.centricsoftwarechina.com/about-us/ + :: Spring Boot C8 base on okHttp:: (v2.2.6.RELEASE) \ No newline at end of file diff --git a/core/src/main/resources/bootstrap.yml b/core/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..7027c52 --- /dev/null +++ b/core/src/main/resources/bootstrap.yml @@ -0,0 +1,26 @@ +###这个yml文件存放系统级别的相关配置 +spring: + application: + name: plmservice +# boot: +# admin: +# client: +# url: http://localhost:9001 +logging: + file: + path: D:/plmservice/logs +#management: +# health: +# elasticsearch: +# enabled: false +# redis: +# enabled: false +# db: +# enabled: false +# endpoints: +# web: +# exposure: +# include: '*' +# endpoint: +# logfile: +# external-file: D:/plmservice/logs/plmservice.log \ No newline at end of file diff --git a/core/src/main/resources/jboss-deployment-structure.xml b/core/src/main/resources/jboss-deployment-structure.xml new file mode 100644 index 0000000..0cad80a --- /dev/null +++ b/core/src/main/resources/jboss-deployment-structure.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/core/src/main/resources/template/export1.xlsx b/core/src/main/resources/template/export1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..95a9dd4f3f9c67f15c844da15a32463c12b6d863 GIT binary patch literal 10719 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 literal 0 HcmV?d00001 diff --git a/core/src/main/resources/template/export2.xlsx b/core/src/main/resources/template/export2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4a44a4eb571f3e706d2bd19261737ced46c6414b GIT binary patch literal 15024 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 literal 0 HcmV?d00001 diff --git a/core/src/main/resources/template/export4.xlsx b/core/src/main/resources/template/export4.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..41f5f8d6c89e3910aec65c236984065230fb4234 GIT binary patch literal 53318 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 AkpKVy literal 0 HcmV?d00001 diff --git a/core/src/main/webapp/index.jsp b/core/src/main/webapp/index.jsp new file mode 100644 index 0000000..a3b8f63 --- /dev/null +++ b/core/src/main/webapp/index.jsp @@ -0,0 +1,16 @@ +<%-- + Created by IntelliJ IDEA. + User: zheng.gong + Date: 2020/6/17 + Time: 16:07 + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Title + + + test jsp + + diff --git a/core/src/test/java/com/centricsoftware/core/model/TimeSeries.java b/core/src/test/java/com/centricsoftware/core/model/TimeSeries.java new file mode 100644 index 0000000..d1822b4 --- /dev/null +++ b/core/src/test/java/com/centricsoftware/core/model/TimeSeries.java @@ -0,0 +1,16 @@ +package com.centricsoftware.core.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +@Data +@Accessors(chain = true) +public class TimeSeries { + + private String message; + @JsonProperty("@timestamp") + private LocalDateTime startTime; +} diff --git a/core/src/test/java/com/centricsoftware/core/service/ElasticsearchClientTest.java b/core/src/test/java/com/centricsoftware/core/service/ElasticsearchClientTest.java new file mode 100644 index 0000000..e020189 --- /dev/null +++ b/core/src/test/java/com/centricsoftware/core/service/ElasticsearchClientTest.java @@ -0,0 +1,204 @@ +package com.centricsoftware.core.service; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.aggregations.Aggregate; +import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; +import co.elastic.clients.elasticsearch._types.aggregations.AggregationBuilders; +import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch.core.IndexRequest; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.json.JsonData; +import com.centricsoftware.commons.utils.JsonUtil; +import com.centricsoftware.config.entity.EsProperties; +import com.centricsoftware.core.CoreApplication; +import com.centricsoftware.core.model.TimeSeries; +import com.centricsoftware.enhancement.dto.log.PlmLog; +import com.centricsoftware.enhancement.dto.log.QueryPlmLogReq; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +@SpringBootTest(classes = CoreApplication.class) +public class ElasticsearchClientTest { + + @Resource + private ElasticsearchClient client; + @Resource + private EsProperties esProperties; + + @Test + public void testSearch() throws IOException { + SearchResponse search = client.search(s -> s + .index(esProperties.getLogIndex()) +// .query(q->q.matchAll(QueryBuilders.matchAll().build())) + .query(q -> q + .term(t -> t + .field("message") + .value(v -> v.stringValue("hell")) + ) + ) + , + TimeSeries.class); + for (Hit hit : search.hits().hits()) { + System.out.println(hit.source()); + } + } + + @Test + public void testInsert() throws IOException { + IndexRequest req = new IndexRequest.Builder() + .index(esProperties.getLogIndex()) + .document(new TimeSeries().setMessage("hello").setStartTime(LocalDateTime.now())) + .build(); + IndexResponse index = client.index(req); + System.out.println(JsonUtil.toJSONString(index)); + } + + @Test + public void testSaveLog() { + PlmLog data = new PlmLog() + .setPath("PLM测试2") + .setName("测试接口") + .setHost("127.0.0.1") + .setBrand("PLM") + .setStartTime(LocalDateTime.now()) + .setEndTime(LocalDateTime.now().plusSeconds(20)) + .setSuccess(true) + .setCostMs(20000L) + .setParam("[{\"printingComment\":\"\",\"TransferKey\":\"C6227096\",\"stylecode\":\"152238715\",\"phase\":\"首样\",\"supplier\":\"\",\"mechcomment\":\"\",\"replicate\":\"右袖袋减短,按样衣画线;袖长做准;\",\"CommentsComfirmDate\":\"2021-07-13 10:45:55\",\"ReType\":\"复版+复花版\",\"replicate1\":\"右袖袋减短,按样衣画线;袖长做准;\",\"replicate2\":\"\",\"replicate3\":\"\",\"issueType\":\"不提供图稿\",\"ReTypePhase\":\"复版+复花版\"}]") + .setReturnCode("0") + .setReturnMsg("操作成功") + .setReturnText("{\"Sucess\":true,\"Msg\":\"接收成功\",\"Detail\":[{\"Result\":true,\"ErrorMsg\":\"\",\"TransferKey\":\"C6227096\"}]}"); + for (int i = 0;i < 10000;i++) { + data.setName("测试接口" + i); + IndexRequest req = new IndexRequest.Builder() + .index(esProperties.getLogIndex()) + .document(data) + .build(); + IndexResponse index = null; + try { + index = client.index(req); + if (log.isDebugEnabled()) { + log.debug("index: {}, id:{}, result:{}", index.index(), index.id(), index.result()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Test + public void testQueryLogs() throws IOException { + QueryPlmLogReq req = new QueryPlmLogReq(); + req.setPath("ESCM.SetView_FB"); + req.setName("测试接口"); + req.setParam("printingComment"); + SearchResponse search = client.search(s -> s + .index(esProperties.getLogIndex()) + .query(q -> { + BoolQuery.Builder builder = QueryBuilders.bool(); + List param = new ArrayList<>(); + if (StringUtils.isNotBlank(req.getPath())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("path").value(v -> v.stringValue(req.getPath())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getName())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("name").value(v -> v.stringValue(req.getName())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getBrand())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("brand").value(v -> v.stringValue(req.getBrand())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getStartTimeBegin())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("@timestamp").gte(JsonData.of(req.getStartTimeBegin())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getStartTimeEnd())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("@timestamp").lt(JsonData.of(req.getStartTimeEnd())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getEndTimeBegin())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("endTime").gte(JsonData.of(req.getEndTimeBegin())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getEndTimeEnd())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("endTime").lt(JsonData.of(req.getEndTimeEnd())).build()) + .build()); + } + if (Objects.nonNull(req.getSuccess())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("success").value(v -> v.booleanValue(req.getSuccess())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getHost())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("host").value(v -> v.stringValue(req.getHost())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getParam())) { + param.add(new Query.Builder() + .matchPhrase(QueryBuilders.matchPhrase().field("param").query(req.getParam()).build()) + .build()); + } + if (Objects.nonNull(req.getReturnCode())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("returnCode").value(v -> v.longValue(req.getReturnCode())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getReturnText())) { + param.add(new Query.Builder() + .matchPhrase(QueryBuilders.matchPhrase().field("returnText").query(req.getReturnText()).build()) + .build()); + } + builder = builder.filter(param); + return q.bool(builder.build()); + }) + .from((int) ((req.getPageNum() - 1) * req.getPageSize().intValue())) + .size(req.getPageSize().intValue()) + , + PlmLog.class); + System.out.println(JsonUtil.toJSONString(search.hits().hits().stream().map(Hit::source).collect(Collectors.toList()))); + } + + @Test + public void testAggs() throws IOException { + SearchResponse resp = client.search(s -> s + .index(esProperties.getLogIndex()) + .query(q -> q + .term(t -> t + .field("brand") + .value(v -> v.stringValue("od-clo")) + ) + ) + .aggregations("desc", Aggregation.of(t -> t.terms(AggregationBuilders.terms().field("name").size(100).build()))) + .size(0) + , PlmLog.class); + Aggregate aggr = resp.aggregations().get("desc"); + List array = aggr.sterms().buckets().array(); + List collect = array.stream().map(StringTermsBucket::key).collect(Collectors.toList()); + System.out.println(JsonUtil.toJSONString(collect)); + } +} diff --git a/core/src/test/postman/Test.http b/core/src/test/postman/Test.http new file mode 100644 index 0000000..dd6823b --- /dev/null +++ b/core/src/test/postman/Test.http @@ -0,0 +1,75 @@ +GET http://localhost:8988//plmservice/test/testRequest +Content-Type: application/json + +{} + +### +#Rest Api session login +POST http://localhost:80/api/item +Content-Type: application/json + +{} + +### +#测试配置文件读取 +POST {{baseUrl}}/test/testProp +Content-Type: application/json + + +### +###用户自定义controller +POST {{baseUrl}}/custom/test +Content-Type: application/json + +{"param": "456"} + +### +POST {{baseUrl}}/watchService/post/DemoStrategyService1Impl +Content-Type: application/json + +{"configName": "style"} + +### +#测试PLM登出 +POST {{baseUrl}}/test/logout +Content-Type: application/json + + +### +#测试cookie +POST {{baseUrl}}/test/testCookie +Content-Type: application/json + + +### + + +### +#测试延迟队列 +POST {{baseUrl}}/test/testRabbitMQ +Content-Type: application/json + +{"msg": "test delay message"} + +### +#测试策略转发实现类 DemoStrategyService1Impl DemoStrategyService2Impl demoStrategyServiceImpl +POST {{baseUrl}}/watchService/post/DemoStrategyService2Impl +Content-Type: application/json + +{"user": "admin","type":"style"} + +### +#testDemo request +POST {{baseUrl}}/test/testDemo +Content-Type: application/json + +{"test": "testjson"} + +### +#testRequest +POST {{baseUrl}}/test/testRequest +Content-Type: application/json + +{"test": "testjson"} + +### \ No newline at end of file diff --git a/core/src/test/postman/http-client.env.json b/core/src/test/postman/http-client.env.json new file mode 100644 index 0000000..e7da21a --- /dev/null +++ b/core/src/test/postman/http-client.env.json @@ -0,0 +1,11 @@ +{ + "dev": { + "baseUrl": "http://localhost:8088/plmservice" + }, + "test": { + "baseUrl": "http://114.55.209.111:8088/plmservice" + }, + "tomcat": { + "baseUrl": "http://localhost:8988/plmservice" + } +} \ No newline at end of file diff --git a/enhancement/.gitignore b/enhancement/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/enhancement/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/enhancement/pom.xml b/enhancement/pom.xml new file mode 100644 index 0000000..40a0ac4 --- /dev/null +++ b/enhancement/pom.xml @@ -0,0 +1,71 @@ + + + + + 2.0.1 + 7.16.2 + + + plmservice + com.centricsoftware + 2.0 + + 4.0.0 + + enhancement + 2.1 + + + + com.centricsoftware + commons + + + com.centricsoftware + redis + + + org.projectlombok + lombok + + + com.centricsoftware + config + + + org.springframework + spring-webmvc + + + org.apache.tomcat.embed + tomcat-embed-core + + + co.elastic.clients + elasticsearch-java + ${elasticsearch.version} + + + jakarta.json + jakarta.json-api + ${jakarta-json.version} + + + org.springframework.boot + spring-boot-test + test + + + junit + junit + test + + + com.aliyun.oss + aliyun-sdk-oss + 3.13.0 + + + diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Column.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Column.java new file mode 100644 index 0000000..a441e90 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Column.java @@ -0,0 +1,33 @@ +package com.centricsoftware.enhancement.ant; + + +import com.centricsoftware.commons.em.C8ImportTypeEnum; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface C8Column { + String attributeId();//C8字段ID + String colName() default "";//导入列名 + //String类型可在这定义正则表达式;ref类型的可以写Search的xml(这里写yml的变量,在变量中写xml,C8Entity中的createXml一样) + //xml中可以写变量,比如{name},程序会自动获取实体类中getName值用于替换变量; + String valid() default ""; + String stringCase() default "";//字符串是否转成大小写,upper:转大写;lower:转小写 + String path() default "";//路径 + C8ImportTypeEnum type() default C8ImportTypeEnum.STRING;//字段类型 + String timeFormat() default "yyyy-MM-dd";//时间格式 + boolean update() default true;//字段是否更新 + boolean required() default true;//字段是否必填 + boolean forceUpdate() default false;//是否强制更新,true表示,如果值为空,c8的数据将被设置为空 + boolean convertParentForceUpdate() default false;//强制更新是否以字段级别的配置为准,默认为false + boolean key() default false; //用来判断是新增还是更新 + String enumPrefix() default "";//enum值的前缀,不需要加冒号 + String refBO() default "";//当type为ref类型时,在这里注明哪个BO + String refAttr() default "Node Name";//当type为ref类型时,默认根据Node Name匹配 + //ref类型过滤xml Active + String split() default ";";//当字段为数组时,默认的分隔符号 + String tips() default "";//重新定制错误信息 + String otherXmlAttr() default "";//保存的时候其他属性(flag等);例子: otherXmlAttr="FG='0x1'" +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8ColumnConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8ColumnConfig.java new file mode 100644 index 0000000..e91fafc --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8ColumnConfig.java @@ -0,0 +1,71 @@ +package com.centricsoftware.enhancement.ant; + +import lombok.Data; +import org.springframework.util.StringUtils; + +@Data +public class C8ColumnConfig { + + private String attributeId;//C8字段ID + + private String colName;//导入列名 + + private String valid ;//String类型可在这定义正则表达式;ref,enum类型的可以写Search的xml,其中enum只要写限制条件就行;enumlist暂不支持校验 + + /** + * 执行valid后,返回的字段值, + * enum类型有效: + * 当addAttribute为true时:该字段不配置的时,返回DependsOn[0].Value,配置则返回validAttr的值。 + * 用途:比如填写材料小类,自动带出材料中类; + * ref类型有效: + * 当valid已配置:如配置了validAttr,则返回valid第一个查询结果的【validAttr】值,可以是表达式,但是必须是ref。 + * 例子:NodeUtil.queryExpressionResult(validAttr,result.get(0)); + */ + private String validAttr; + + private boolean validResultRequired=true;//valid为xml时,true:无返回值报错;false:有返回值报错 + + private String stringCase;//字符串是否转成大小写,upper:转大写;lower:转小写;其他值则不作处理 + + private String path;//路径 + + private String type="string";//C8的字段类型 + + private String timeFormat="yyyy-MM-dd";//时间格式转换 + + private boolean update=true;//字段是否校验更新 + + private boolean required=true;//字段是否校验必填 + + private boolean addAttribute=false;//额外的属性,无需用户导入;比如Subtype;为true时,不校验必填 + + private boolean forceUpdate=true;//是否强制更新,true表示,如果值为空,c8的数据将被设置为空 + + private String enumPrefix;//enum值的前缀,不需要加冒号 + + private String split=";";//当字段为数组时,默认的分隔符号 + + private String tips="";//报错时的提示 + + private boolean key=false;//用来判断是新增还是更新;yml配置的版本目前暂未实现该用法,注解的版本已实现 + + private String refBO;//ref类型的字段,当valid没配置时,并且配置了此字段,则按照refBO和refAttr搜索 + + private String refAttr;//ref类型的字段,当valid没配置时,并且配置了此字段,则按照refBO和refAttr搜索,如果不配置,则按照Node Name搜索;只能是SValue + + private String appendSuffix = "";//对导入的值拼接后缀 + + private boolean removeExp = false;//是否去除表达式 + + private String otherXmlAttr = "";//保存的时候其他属性(flag等);例子: otherXmlAttr : FG='0x1' + + private boolean continueTrans = false; // 如果Excel中列名重复, 是否继续读取重复列的数据,为true时读取,为false时不读取. 但如果为false可能会出现重复列的数值报空值错误. + + public String getId() { + if (StringUtils.isEmpty(this.getPath())) { + return this.getAttributeId(); + } else { + return this.getAttributeId() + " " + this.getPath(); + } + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Entity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Entity.java new file mode 100644 index 0000000..6439b52 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8Entity.java @@ -0,0 +1,45 @@ +package com.centricsoftware.enhancement.ant; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface C8Entity { + + //BO的Node Type + String nodeType(); + + //如果存在错误提示,是否要保存校验成功的行,默认是保存的;当isSingleSave为true时,改指标不生效 + boolean failSave() default true; + + //一旦出现错误信息,后续的数据行是否继续执行 + boolean failContinue() default true; + + //是否添加校验语句,即 + boolean validateNode() default false; + + //是否逐条保存,默认是统一保存 + boolean singleSave() default false; + + //创建的xml,获取yml或者properties中的属性;cs.center.prop前缀省略(upload.style.searchxml表示获取cs.center.prop.upload.style.searchxml) + String createXml() default ""; + + //查询的xml,获取yml或者properties中的属性;cs.center.prop前缀省略 + String searchXml() default ""; + + boolean create() default true;//如果找不到行记录数据是否新增 + + //去重的字段 + String[] removeDuplicate() default {}; + + boolean seadEmail() default false;//默认发送给当前用户 + + boolean redis() default false;//是否发送到redis + + //为所有字段设置是否强制更新,默认为false;如果设置为true,则会覆盖字段级别的设置;如果为false,则此字段不生效 + boolean forceUpdate() default false; + + String tips() default "";//定制错误提示信息 + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8EntityConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8EntityConfig.java new file mode 100644 index 0000000..afde832 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8EntityConfig.java @@ -0,0 +1,61 @@ +package com.centricsoftware.enhancement.ant; + +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.List; + +@Data +public class C8EntityConfig { + + private String nodeType;//C8的Node Type + + private boolean create = true;//如果找不到行记录数据是否新增 + + private String tips = "";//提示信息:当searchXml查询不到时,create又设置为false的提示信息; + +// private String[] cols;//必须显示声明所有的列 + + //如果存在错误提示,是否要保存校验成功的行,默认是保存的;当isSingleSave为true时,该指标不生效 + private boolean isFailSave = true; + + //一旦出现错误信息,后续的数据行是否继续执行 + private boolean isFailContinue = true; + + //是否添加校验语句,即 + private boolean validateNode = false; + + //是否逐条保存,默认是统一保存 + private boolean isSingleSave = true; + + //是否直接导入,true则将xml发送到redis + private boolean redis = false; + + //创建的xml,获取yml或者properties中的属性;cs.center前缀省略(upload.style.searchxml表示获取cs.center.prop.upload.style.searchxml) + private String createXml; + + //查询的xml,获取yml或者properties中的属性;cs.center前缀省略 + private String searchXml; + + //其他的更新字段,这些字段不在导入的字段中 + private String[] otherChangeXml; + + //去重的字段 + private String[] removeDuplicate; + + //是否发送邮件 + private boolean sendEmail = false; +// private String emailSearchXml = "";//当sendEmail为true时,将按照改字段搜索邮箱 +// private String emailAttr = "Email";//当emailSearchXml搜索到数据时,按照emailAttr取邮箱,默认Email + + //导入前后需要执行的bean名称,需要实现DoBeforeOrAfterImportInterface接口 + //返回值错误信息 + private String beforeOrAfterImportBeanName=""; + + //插件:当校验通过时,额外执行的操作 + private String itemImportAroundAOPBeanName=""; + + private List columns = Lists.newArrayList(); + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8NativeExport.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8NativeExport.java new file mode 100644 index 0000000..08a543f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/C8NativeExport.java @@ -0,0 +1,17 @@ +package com.centricsoftware.enhancement.ant; + +import com.centricsoftware.commons.em.C8NativeExportType; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +@Deprecated +public @interface C8NativeExport { + String attributeName(); + String path() default ""; + C8NativeExportType type() default C8NativeExportType.STRING; + String timeFormat() default "yyyy-MM-dd HH:mm:ss";//时间格式 + String colName() default "";//导出时的列名 +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/ExcelAlias.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/ExcelAlias.java new file mode 100644 index 0000000..4337e6d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/ExcelAlias.java @@ -0,0 +1,18 @@ +package com.centricsoftware.enhancement.ant; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface ExcelAlias { + String colName(); + String attributeId(); + + String path() default ""; + + String type() default "string";//不支持List类型 + + boolean update() default true;//是否更新 + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridColumn.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridColumn.java new file mode 100644 index 0000000..289e432 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridColumn.java @@ -0,0 +1,75 @@ +package com.centricsoftware.enhancement.ant.grid; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface GridColumn { + + /** + * 字段ID:如果不配做,则默认取字段名 + */ + String field() default ""; + + /** + * 字段标题 + */ + String title(); + + /** + * 排序 + */ + boolean sortable() default true; + + /** + * 自定义转换 + * demo: + * 1、this.format.formatBoolean + * 2、this.format.formatDate + */ + String formatter() default ""; + + /** + * Form搜索配置;必须配置为true,才可在Form上搜索 + */ + boolean formFilter() default false; + + /** + * 过滤项:默认文本过滤 + * demo1: 下拉 + * [ + * { label: "是", value: "Y" }, + * { label: "否", value: "N" } + * ] + * demo2: 数值 + * [{ data: { min: "0", max: "0" } }] + * demo3: 文本 + * [{ data: ”“ }] + * demo4: 时间 + * { data: { value: [], valueFormat: "yyyy-MM-dd HH:mm:ss" } } + * 支持:时间、文本、数值、下拉 + */ + String filters() default "[{ data:\"\"}]"; + + /** + * 过滤项:默认文本过滤 + * 列上过滤的组件、支持:时间、文本、数值、下拉 + * {name: "FilterInput"} + * FilterTime、FilterComplex、FilterContent、FilterSelect、FilterInput + */ + String filterRender() default "{name: \"FilterInput\"}"; + + /** + * 列款 + */ + int width() default 150; + + /** + * 固定列:left、right + */ + String fixed() default ""; + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridForm.java b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridForm.java new file mode 100644 index 0000000..6804347 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/ant/grid/GridForm.java @@ -0,0 +1,56 @@ +package com.centricsoftware.enhancement.ant.grid; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface GridForm { + + /** + * 字段ID + */ + String field() default ""; + + /** + * 字段标题 + */ + String title() default ""; + + /** + * 过滤提醒信息: + * icon : el-icon-warning + */ + String titlePrefix() default ""; + + /** + * 所有项的栅格占据的列数(共 24 分栏) + */ + int span() default 4; + + /** + * input, textarea, select, $input, $textarea, $select, $button, $buttons, $radio, $checkbox, $switch + */ + String filterType() default "$input"; + + boolean clearable() default true; + + String props() default ""; + + /** + * 提示信息 + */ + String placeholder() default ""; + + boolean filterable() default true; + + /** + * 如下方选择的,如果是下拉选择的,则需要配置options选项 + * [ + * { value: "C8_Phase:002", label: "首样后" }, + * { value: "C8_Phase:003", label: "预览后" } + * ] + */ + String options() default ""; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/component/CacheComponent.java b/enhancement/src/main/java/com/centricsoftware/enhancement/component/CacheComponent.java new file mode 100644 index 0000000..18cab31 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/component/CacheComponent.java @@ -0,0 +1,42 @@ +package com.centricsoftware.enhancement.component; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.date.DateUnit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * description: + * Date: 2023/3/8 11:23 + */ +@Component +@Slf4j +public class CacheComponent { + + TimedCache cache = CacheUtil.newTimedCache(1 * DateUnit.MINUTE.getMillis()); + + public String getStr(String key){ + Object o = cache.get(key); + return o==null?"":String.valueOf(o); + } + + public Object getObject(String key){ + return cache.get(key); + } + + public void setValue(String key,Object value ){ + cache.put(key,value); + } + + /** + * @param key key + * @param value value + * @param timeOut 毫秒数 + */ + public void setValue(String key,Object value,long timeOut){ + cache.put(key,value,timeOut); + } + +} + diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/component/ExcelAliasHelper.java b/enhancement/src/main/java/com/centricsoftware/enhancement/component/ExcelAliasHelper.java new file mode 100644 index 0000000..a8d0e60 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/component/ExcelAliasHelper.java @@ -0,0 +1,131 @@ +package com.centricsoftware.enhancement.component; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.poi.excel.ExcelReader; +import com.centricsoftware.commons.utils.CommonUtil; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.ant.ExcelAlias; +import com.centricsoftware.enhancement.modules.dml.dto.node.change.C8OperationNode; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/12/8 17:07 + */ +@Service +@Slf4j +public class ExcelAliasHelper { + + /** + * 添加别名 + */ + public void addHeaderAlias(ExcelReader reader, Class clazz) { + List fields = CommonUtil.getAllFields(clazz);//所有的字段 + for (Field field : fields) { + ExcelAlias annotation = field.getAnnotation(ExcelAlias.class); + if (annotation == null) { + continue; + } + reader.addHeaderAlias(annotation.colName(), field.getName()); + } + } + + public String extractOperation(List data, Class clazz) throws Exception { + return this.extractOperation(data, clazz, false, ""); + } + + /** + * 获取Operation + * + * @return 返回Operation + */ + public String extractOperation(List data, Class clazz, boolean isCreate, String boType) { + StringBuilder xml = new StringBuilder(); + StringBuilder error = new StringBuilder(); + Map annoMap = getAnnoList(clazz); + for (E dto : data) { + StringBuilder pathXml = new StringBuilder(); + String url = null; + try { + url = String.valueOf(clazz.getMethod("getUrl").invoke(dto)); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + C8OperationNode c8OperationNode; + if (isCreate) { + c8OperationNode = C8OperationNode.createNode(url, boType); + } else { + if(url==null||"".equals(url)){ + continue; + } + c8OperationNode = C8OperationNode.changeNode(url); + } + for (Map.Entry entry : annoMap.entrySet()) { + String fieldName = entry.getKey(); + Field field = entry.getValue(); + try { + if (StrUtil.isBlank(url)) { + url = String.valueOf(clazz.getMethod("getUrl").invoke(dto)); + } + fieldName = StringUtils.capitalize(fieldName); + String value = String.valueOf(clazz.getMethod("get" + fieldName).invoke(dto)); + ExcelAlias annotation = field.getAnnotation(ExcelAlias.class); + doExtractOperation(url,value, c8OperationNode,pathXml,annotation);//拼接Operation + } catch (IllegalAccessException e) { + error.append("无权限访问字段:").append(fieldName).append(e.getMessage()); + } catch (InvocationTargetException e) { + error.append(e.getMessage()); + } catch (NoSuchMethodException e) { + error.append("找不到方法:").append("get").append(fieldName).append(e.getMessage()); + } + } + xml.append(c8OperationNode.getXml()).append(pathXml); + } + return xml.toString(); + } + + private void doExtractOperation(String url, String value, C8OperationNode c8OperationNode, StringBuilder pathXml, ExcelAlias annotation){ + boolean update = false; + if (annotation != null) { + update = annotation.update(); + } + if (!update) { + return; + } + String attributeId = annotation.attributeId(); + String path = annotation.path(); + String type = annotation.type(); + if (StrUtil.isBlank(path)) { + c8OperationNode.changeAttribute(attributeId, type, value); + } else { + String zeous = C8OperationNode.changeNode(url, path).changeAttribute(attributeId, type, value).getXml(); + pathXml.append(zeous); + } + } + + /** + * 获取字段和注解的映射关系 + */ + private Map getAnnoList(Class clazz){ + Map map = Maps.newHashMap(); + List fields = CommonUtil.getAllFields(clazz);//所有的字段 + for (Field field : fields) { + ExcelAlias annotation = field.getAnnotation(ExcelAlias.class); + String fieldName = field.getName(); + if (annotation == null&&"url".equals(fieldName)) { + continue; + } + map.put(field.getName(),field); + } + return map; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/component/RedisUtils.java b/enhancement/src/main/java/com/centricsoftware/enhancement/component/RedisUtils.java new file mode 100644 index 0000000..a0ad244 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/component/RedisUtils.java @@ -0,0 +1,635 @@ +package com.centricsoftware.enhancement.component; + +import cn.hutool.core.date.DateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundZSetOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Component +public class RedisUtils { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @return + */ + public boolean setExpire(String key, long time) { + boolean flag = false; + if (time > 0) { + flag = redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return flag; + } + + /** + * 根据key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete(CollectionUtils.arrayToList(key)); + } + } + } + + /** + * 模糊删除key + * @param keys + */ + public void delByDim(String keys) { + if(keys!=null && !"".equals(keys)){ + Set keySet = redisTemplate.keys(keys); + redisTemplate.delete(keySet); + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + */ + public void set(String key, Object value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + */ + public void set(String key, Object value, long time) { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + } + + /** + * 递增 + * + * @param key 键 + * @param delta 要增加几(大于0) + * @return + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递增因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, delta); + } + + /** + * 递减 + * + * @param key 键 + * @param delta 要减少几(大于0) + * @return + */ + public long decr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递减因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, -delta); + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hGetItem(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hGetMap(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + */ + public void hPutItem(String key, String item, Object value) { + redisTemplate.opsForHash().put(key, item, value); + } + + /** + * 向一张hash表中放入数据,如果不存在将创建,并设置时间 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) + */ + public void hPutItem(String key, String item, Object value, long time) { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + setExpire(key, time); + } + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + */ + public void hPutAll(String key, Map map) { + redisTemplate.opsForHash().putAll(key, map); + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + */ + public void hPutAll(String key, Map map, long time) { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + setExpire(key, time); + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hDelItems(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasItem(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + */ + public void hIncr(String key, String item, int by) { + Object value = hGetItem(key, item); + if (value instanceof Integer) { + value = (int) value + by; + } else if (value instanceof Long) { + value = (long) value + by; + } else { + throw new RuntimeException("ERR hash value is not an integer"); + } + hPutItem(key, item, value); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(大于0) + */ + public void hDecr(String key, String item, int by) { + Object value = hGetItem(key, item); + if (value instanceof Integer) { + value = (int) value - by; + } else if (value instanceof Long) { + value = (long) value - by; + } else { + throw new RuntimeException("ERR hash value is not an integer"); + } + hPutItem(key, item, value); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + return redisTemplate.opsForSet().add(key, values); + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0){ + setExpire(key, time); + } + return count; + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasMember(String key, Object value) { + return redisTemplate.opsForSet().isMember(key, value); + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSize(String key) { + return redisTemplate.opsForSet().size(key); + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long sRemoveMembers(String key, Object... values) { + return redisTemplate.opsForSet().remove(key, values); + } + + /** + * 返回给定所有集合的差集 + * @param key1 + * @param key2 + * @return + */ + @SuppressWarnings("unchecked") + public Set sDiff(String key1, String... key2){ + Set set = sGet(key1); + if (key2 != null && key2.length > 0) { + if (key2.length == 1) { + set = redisTemplate.opsForSet().difference(key1, key2[0]); + } else { + set = redisTemplate.opsForSet().difference(key1, CollectionUtils.arrayToList(key2)); + } + } + return set; + } + + /** + * 返回给定所有集合的交集 + * @param key1 + * @param key2 + * @return + */ + @SuppressWarnings("unchecked") + public Set sInter(String key1, String... key2){ + Set set = sGet(key1); + if (key2 != null && key2.length > 0) { + if (key2.length == 1) { + set = redisTemplate.opsForSet().intersect(key1, key2[0]); + } else { + set = redisTemplate.opsForSet().intersect(key1, CollectionUtils.arrayToList(key2)); + } + } + return set; + } + + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + return redisTemplate.opsForList().range(key, start, end); + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetSize(String key) { + return redisTemplate.opsForList().size(key); + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetItem(String key, long index) { + return redisTemplate.opsForList().index(key, index); + } + + /** + * 将单个值追加到list缓存最后,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + */ + public void lRightPush(String key, Object value) { + redisTemplate.opsForList().rightPush(key, value); + } + + /** + * 将单个值追加到list缓存最后,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + */ + public void lRightPush(String key, Object value, long time) { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0){ + setExpire(key, time); + } + + } + + /** + * 将集合追加到list缓存最后,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + */ + public void lRightPushAll(String key, List value) { + redisTemplate.opsForList().rightPushAll(key, value); + } + + /** + * 将集合追加到list缓存最后,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + */ + public void lRightPushAll(String key, List value, long time) { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0){ + setExpire(key, time); + } + + } + + /** + * 将单个值插入到list缓存头部,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + */ + public void lLeftPush(String key, Object value) { + redisTemplate.opsForList().leftPush(key, value); + } + + /** + * 将单个值插入到list缓存头部,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + */ + public void lLeftPush(String key, Object value, long time) { + redisTemplate.opsForList().leftPush(key, value); + if (time > 0) { + setExpire(key, time); + } + } + + /** + * 将集合插入到list缓存头部,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + */ + public void lLeftPushAll(String key, List value) { + redisTemplate.opsForList().leftPushAll(key, value); + } + + /** + * 将集合插入到list缓存头部,如果list不存在,则创建 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + */ + public void lLeftPushAll(String key, List value, long time) { + redisTemplate.opsForList().leftPushAll(key, value); + if (time <= 0) { + setExpire(key, time); + } + + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + */ + public void lUpdateItem(String key, long index, Object value) { + redisTemplate.opsForList().set(key, index, value); + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + return redisTemplate.opsForList().remove(key, count, value); + } + + /** + * 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 + * @param key + * @param start 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推 + * @param end 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推 + */ + public void lTrim(String key, long start, long end) { + redisTemplate.opsForList().trim(key, start, end); + } + + + /** + * 将value元素及其对应的score 值加入到有序集 key 当中。 + * @param key key + * @param value value + * @param score 排序值 + * @return + */ + public boolean zadd(String key,String value,double score,long time){ + BoundZSetOperations operation=redisTemplate.boundZSetOps(key); + Boolean add = operation.add(value, score); + if (time > 0) { + setExpire(key, time); + operation.removeRangeByScore(0, DateUtil.currentSeconds()-time);//清除过期数据 + } + return add; + } + + public boolean zadd(String key,String value,long score){ + return zadd(key,value,score,0L); + } + + /** + * 将value元素及其对应的score 值加入到有序集 key 当中。 + * @param key key + * @param value value + * @param score 排序值 + * @return + */ + public boolean zadd(String key,String value,Integer score,long time){ + return zadd(key,value,score.doubleValue(),time); + } + + /** + * 将value元素及其对应的score 值加入到有序集 key 当中。 + * @param key key + * @param value value + * @param score 排序值 + * @return + */ + public boolean zadd(String key,String value,Integer score){ + return zadd(key,value,score.doubleValue(),0L); + } + + public Set zreverseRange(String key,int start,int end){ + BoundZSetOperations operation=redisTemplate.boundZSetOps(key); + return operation.reverseRange(start, end); + } + + /** + * @Author liaochangjiang + * @Description //TODO 根据key进行加锁 + * @Date 15:36 2024/3/21 + * @Param [key锁的key值, requestId请求唯一值, expireTime过期时间, timeout等待时间] + * @return boolean + **/ + public boolean tryGetDistributedLock(String key, String requestId, int expireTime, int timeout) { + long start = System.currentTimeMillis(); + long end; + do { + Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(key, requestId, (long)expireTime, TimeUnit.SECONDS); + if (flag != null && flag) { + return true; + } + end = System.currentTimeMillis() - start; + } while(end < (long)(timeout * 1000)); + return false; + } + + /** + * 释放锁 + * @author liaochangjiang + * @since 2024-03-21 16:22 + */ + public boolean releaseDistributedLock(String key, String requestId) { + RedisScript script = new DefaultRedisScript("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class); + Long result = this.redisTemplate.execute(script, Collections.singletonList(key), new Object[]{requestId}); + return Objects.equals(result, 1L); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/component/StandaloneLockComponent.java b/enhancement/src/main/java/com/centricsoftware/enhancement/component/StandaloneLockComponent.java new file mode 100644 index 0000000..789ff4a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/component/StandaloneLockComponent.java @@ -0,0 +1,87 @@ +package com.centricsoftware.enhancement.component; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.thread.ThreadUtil; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class StandaloneLockComponent { + + //获取锁相关信息 + @Getter + ThreadLocal lockInfo = new ThreadLocal<>(); + + int waitTime = 60 * 60 * 1000;//等待时间1小时;如果1小时内未获取锁,则返回加锁失败 + + /** + * 用于实现锁机制 + * 缓存时间默认60分钟; + */ + TimedCache cache = CacheUtil.newTimedCache(60 * DateUnit.MINUTE.getMillis()); + + public boolean tryLock(String key, String user,int waitTime) { + while (waitTime > 0) { + waitTime -= 500; + boolean lock = lock(key, user); + if (!lock) { + //睡眠500毫秒 + try { + ThreadUtil.safeSleep(500 * DateUnit.MS.getMillis()); + } catch (Exception e) { + log.error("线程休眠报错:" + e.getLocalizedMessage(), e); + } + } else { + return true; + } + } + return false; + } + + public boolean tryLock(String key) { + return tryLock( key, "Admin" , waitTime); + } + + /** + * 尝试加锁;如果10秒内未能获取锁,则返回加锁失败 + * @param key + * @param user + * @return + */ + public boolean tryLock(String key, String user) { + return tryLock( key, user, waitTime); + } + + /** + * 加锁 + * + * @param key 加锁的key + * @param user 加锁用户 + * @return 加锁成功返回true,否则返回false + */ + private synchronized boolean lock(String key, String user) { + boolean contain = cache.containsKey(key); + if (!contain) { + //如果缓存中没值,则加入缓存 + cache.put(key, user); + return true; + } + String u = cache.get(key); + lockInfo.set(u);//设置当前锁用户 + return false; + } + + /** + * 解锁 + * + * @param key + */ + public synchronized void unLock(String key) { + cache.remove(key); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/component/c8/NativeExportComponent.java b/enhancement/src/main/java/com/centricsoftware/enhancement/component/c8/NativeExportComponent.java new file mode 100644 index 0000000..4e44513 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/component/c8/NativeExportComponent.java @@ -0,0 +1,301 @@ +package com.centricsoftware.enhancement.component.c8; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.poi.excel.ExcelWriter; +import com.centricsoftware.commons.em.C8NativeExportType; +import com.centricsoftware.commons.utils.CommonUtil; +import com.centricsoftware.enhancement.ant.C8NativeExport; +import com.centricsoftware.enhancement.modules.c8.component.EnumCache; +import com.centricsoftware.enhancement.modules.c8.dto.nativeexport.ExtractParameterEntity; +import com.centricsoftware.enhancement.modules.c8.dto.nativeexport.ExtractResultEntity; +import com.centricsoftware.enhancement.mapper.ExtractMapper; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 注意:该方法已于2020年后废弃,因为C8版本的更迭可能导致存储过程变化,需要实时维护。 + * 可用DepPath查询方式替代。 + * 通过存储过程批量查询数据,并返回List;可支持分页查询。 + * 需要指定数据库(配置datasource.MSSQL): + * 1、oracle为Oracle数据库,忽略大小写 + * 2、其余的都是MSSQL + */ +@Component +@Slf4j +@Deprecated +public class NativeExportComponent { + @Autowired + EnumCache enumCache; + + @Autowired + ExtractMapper extractMapper; + + @Value("datasource.dbtype") + private String dbtype; + + /** + * + * @param queryXml + * @param clazz + * @return + * @throws Exception + */ + public List queryByExtractProc(String queryXml, Class clazz){ + String detailXmlStr = buildExtractXml(clazz); + List resultList ; + resultList = doExtractProc(queryXml, detailXmlStr); + List ts = new ArrayList<>(); + try { + ts = processResults(resultList, clazz); + } catch (Exception e) { + log.error(e.getLocalizedMessage(),e); + } + return ts; + } + + /** + * 通过存储过程ns_xmlQueryVerticalExtract查询数据 + * @param queryXmlStr 查询XML + * @param detailXmlStr 返回字段XML + * @return + */ + private List doExtractProc(String queryXmlStr, String detailXmlStr) { + if (StrUtil.isBlank(queryXmlStr) || StrUtil.isBlank(detailXmlStr)) { + return null; + } + ExtractParameterEntity parameter = new ExtractParameterEntity(); + parameter.setQueryXml(queryXmlStr); + parameter.setExtractXml(detailXmlStr); + List list = null; + if("oracle".equalsIgnoreCase(dbtype)){ + extractMapper.findByXmlQueryVerticalExtractOracle(parameter); + }else{ + list = extractMapper.findByXmlQueryVerticalExtract(parameter); + parameter.setResultList(list); + } + return parameter.getResultList(); + } + + /** + * 将查询结果行转列,并封装成具体类返回 + * @param resultList + * @param clazz + * @return + * @throws Exception + */ + private List processResults(List resultList, Class clazz) throws Exception { + Map dataMap = Maps.newLinkedHashMap(); + String fieldName = ""; + Object value; + Integer startId; + List fields = CommonUtil.getAllFields(clazz); + Map cacheUrlMap = Maps.newHashMap();//url_id和url的映射缓存 + for (ExtractResultEntity er : resultList) { + startId = er.getStartId(); + T t = dataMap.get(startId) == null ? clazz.newInstance() : dataMap.get(startId); + Field targetField = null; + C8NativeExport annotation = null; + String attrId = er.getAttrId(); + if (StrUtil.isBlank(attrId)) { + continue; + } + for (int i=0;i(dataMap.values()); + } + + /** + * 将tartId映射到clazz + * @param dataMap + * @param clazz + */ + private void fillStartId(Map dataMap,Class clazz,Map cacheUrlMap) throws Exception{ + Field field = null; + Method getMethod = null; + Method setMethod = null; + try { + field = clazz.getDeclaredField("url"); + getMethod = clazz.getMethod("getUrl"); + setMethod = clazz.getMethod("setUrl",field.getType()); + } catch (NoSuchMethodException e) { + log.warn(clazz.getName()+":没有url字段,取消url自动注入"); + } catch (SecurityException e) { + log.warn(clazz.getName()+":没有url字段,取消url自动注入"); + }catch (NoSuchFieldException e) { + log.warn(clazz.getName()+":没有url字段,取消url自动注入"); + } + if(getMethod!=null&&setMethod!=null&&field!=null){ + for (Map.Entry entry : dataMap.entrySet()) { + Integer k = entry.getKey(); + T v = entry.getValue(); + String value; + if(cacheUrlMap.containsKey(k)){ + value = cacheUrlMap.get(k); + }else{ + value = extractMapper.findUrlById(k.toString()); + } + value = "centric:".equals(value) ? "" : value; + clazz.getMethod("setUrl", field.getType()).invoke(v, value); + } + } + } + + private Object getExtractValueByType(ExtractResultEntity er, C8NativeExportType type, String timeFormat, Map cacheUrlMap) { + Object value = null; + switch (type) { + case INTEGER: + value = NumberUtil.parseInt(er.getValueString()); + break; + case DOUBLE: + value = er.getValueNumber(); + break; + case BOOLEAN: + if(StrUtil.isNotEmpty(er.getValueString())){ + value = Boolean.valueOf(er.getValueString()); + }else{ + value = er.getValueBoolean(); + } + break; + case URL_ID: + value = er.getValueUrl(); + break; + case URL: + String urlId = er.getValueUrl(); + if(cacheUrlMap.containsKey(urlId)){ + value = cacheUrlMap.get(urlId); + }else{ + value = extractMapper.findUrlById(er.getValueUrl()); + value = "centric:".equals(value) ? "" : value; + cacheUrlMap.put(urlId,value.toString()); + } + break; + case ENUM: + value = er.getValueString(); + break; + case ENUM_KEY: + String[] enumAll= er.getValueString().split(":"); + value = enumAll.length > 1 ? enumAll[1] : ""; + break; + case ENUM_DESC: + value = enumCache.getDescByFullname(er.getValueString()); + break; + case ENUM_DISPLAY: + value = enumCache.getDisplayByFullname(er.getValueString()); + break; + case TIME: + Double time = er.getValueNumber(); + SimpleDateFormat sm = new SimpleDateFormat(timeFormat); + if (time != null && time != 0) { + Timestamp ts = new Timestamp(time.longValue()); + value = sm.format(ts); + } else { + Timestamp ts = new Timestamp(0); + value = sm.format(ts); + } + break; + default: + value = er.getValueString(); + } + return value; + } + + + /** + * 拼接需要返回的字段 + * @param clazz + * @return + */ + private String buildExtractXml(Class clazz) { + if (clazz == null) { + return ""; + } + StringBuilder xml = new StringBuilder(); + xml.append(""); + List fields = CommonUtil.getAllFields(clazz); + for (int i=0;i"); + } + xml.append(""); + return xml.toString(); + } + + /** + * 为列名添加别名 + * @param writer + * @param clazz + */ + public void export2ExcelAlias(ExcelWriter writer, Class clazz){ + List fields = CommonUtil.getAllFields(clazz); + for (int i=0;i mails) throws Exception { + log.info("otherParam==============="+otherParam); + StringBuilder error = new StringBuilder(); +// ExcelReader reader = ExcelUtil.getReader(file.getInputStream()); +// List> read = reader.readAll(); +// reader.close(); + List> read = readAll(file); + C8EntityConfig config = uploadtProperties.getValue(importName); + String beforeOrAfterImportBeanName = config.getBeforeOrAfterImportBeanName(); + Map beforeMap = Maps.newHashMap(); + if(StrUtil.isNotBlank(beforeOrAfterImportBeanName)){ + DoBeforeOrAfterImportInterface bean = SpringContextHolder.getBean(beforeOrAfterImportBeanName); + beforeMap = bean.before(file, importName, otherParam, mails, error); + } + if(config==null){ + return WebResponse.failure("importName名称错误"); + } + List> readAll = transMapKey(read,config,otherParam,beforeMap);//转换key + List> unique = removeDuplicate(readAll, config);//去重 + if(readAll.size()!=unique.size()){ + error.append("数据存在重复,请校验数据:"+getRemoveDuplicate(config)); + return WebResponse.failure(error.toString()); + } + importPropertiesService.process(importName,config,readAll,error);//保存 + sendMail(config,error.toString(),mails); + if(StrUtil.isNotBlank(beforeOrAfterImportBeanName)){ + DoBeforeOrAfterImportInterface bean = SpringContextHolder.getBean(beforeOrAfterImportBeanName); + String afterXml = bean.after(file, importName, otherParam, mails,error); + if(StrUtil.isNotBlank(afterXml)){ + c8NodeService.processNode(afterXml); + } + } + if(StrUtil.isBlank(error.toString())){ + return WebResponse.success(error.toString()); + }else{ + return WebResponse.failure(error.toString()); + } + } + + /** + * 去重 + * @param readAll + * @param config + * @return + * @throws Exception + */ + private List> removeDuplicate(List> readAll,C8EntityConfig config) throws Exception{ + String[] removeDuplicate = config.getRemoveDuplicate(); + if(removeDuplicate==null){ + return readAll; + } + if(removeDuplicate.length>0){ + List> unique =readAll.stream().collect( + Collectors. collectingAndThen( + Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing( + o -> { + String str = ""; + for(String attr:removeDuplicate){ + String v = Convert.toStr(o.get(attr)); + str = str + v+";"; + } + return str; + }))), ArrayList::new) + ); + return unique; + } + return readAll; + } + + /** + * 将转换map的key + * @param readAll + * @param config + */ + private List> transMapKey(List> readAll,C8EntityConfig config,String otherParam,Map beforeMap){ + JSONObject jsonObject = JSONUtil.parseObj(otherParam); + Map paramMap = Convert.toMap(String.class, String.class, jsonObject); + List> list = Lists.newArrayList(); + List columns = config.getColumns(); + for(Map map : readAll){ + Map m = Maps.newHashMap(); + for (Map.Entry entry : map.entrySet()) { + for(C8ColumnConfig col:columns){ + if(col.getColName().equals(entry.getKey())){ + String val = StrUtil.trim(entry.getValue()+"");//去空格 + String appendSuffix = col.getAppendSuffix(); + if(StrUtil.isNotBlank(appendSuffix)){ + m.put(col.getId(),val+appendSuffix); + }else{ + m.put(col.getId(),val); + } + m.putAll(beforeMap);//将导入前校验的所有返回值放入每一行 + m.put(col.getId()+" old",val);//保存原始数据 + m.putAll(paramMap); + if (!col.isContinueTrans()) break; + } + } + } + list.add(m); + } + return list; + } + + /** + * 获取去重的字段名 + * @param config + * @return + * @throws Exception + */ + public String getRemoveDuplicate(C8EntityConfig config) throws Exception{ + List columns = config.getColumns(); + String[] removeDuplicate = config.getRemoveDuplicate(); + StringBuilder names = new StringBuilder(); + for(String attr : removeDuplicate){ + for(C8ColumnConfig col :columns){ + if(col.getId().equals(attr)){ + names.append(col.getColName()+";"); + } + } + } + return names.toString(); + } + + /** + * 发送邮件 + * 需要配置相关信息: https://hutool.cn/docs/#/extra/%E9%82%AE%E4%BB%B6%E5%B7%A5%E5%85%B7-MailUtil + * @param config + * @param error + * @param mails + */ + private void sendMail(C8EntityConfig config, String error, ArrayList mails){ + boolean sendEmail = config.isSendEmail(); + if(sendEmail){ + String context = StrUtil.isBlank(error)?"数据导入成功;":error; + MailUtil.send(mails, "数据导入通知", context, false); + } + } + + public List> readAll(MultipartFile file) throws Exception{ + List> result = Lists.newArrayList(); + ImportExcel importExcel = new ImportExcel(file, 0, 0); + Row headRow = importExcel.getRow(importExcel.getHeaderNum()); + for (int i = importExcel.getDataRowNum(); i <= importExcel.getLastDataRowNum(); i++) { + Map map = Maps.newHashMap(); + Row row = importExcel.getRow(i); + boolean isValidRow = false;//是否有效行 + for(int j = 0; j < importExcel.getLastCellNum(); j++){ + String key = headRow.getCell(j).toString(); + if(row==null){ + continue; + } + String value = importExcel.getCellValue(row, j).toString(); + if(StrUtil.isNotBlank(value)){ + isValidRow = true; + } + map.put(key,value); + } + if(isValidRow){ + result.add(map); + } + } + return result; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/C8TestController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/C8TestController.java new file mode 100644 index 0000000..7d9aac4 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/C8TestController.java @@ -0,0 +1,663 @@ +package com.centricsoftware.enhancement.controller.c8; +import com.centricsoftware.enhancement.modules.c8.dto.ExpResultEntity; +import com.fasterxml.jackson.core.type.TypeReference; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.utils.FileUtil; +import com.centricsoftware.commons.utils.JsonUtil; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.dto.OperationResultEntity; +import com.centricsoftware.enhancement.modules.c8.service.C8LoginService; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.c8.service.curd.C8OperationService; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.feign.C8Feign; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.multipart.MultipartFile; + +import org.springframework.core.io.Resource; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@RestController +@Slf4j +public class C8TestController { + + @Autowired + C8LoginService c8LoginService; + + @Autowired + C8NodeService c8NodeService; + + @Autowired + C8OperationService c8OperationService; + + @Autowired + public C8Feign c8Feign; + + @GetMapping("/login") + public ResEntity login() { + c8LoginService.login(); + return WebResponse.success(ResCode.SUCCESS); + } + + @GetMapping("getFileDirect") + public ResEntity getFileDirect() { + InputStream file = c8NodeService.getFileFromDirectAddr("cf://fv/38898"); + cn.hutool.core.io.FileUtil.writeFromStream(file, "D://11.doc"); + return WebResponse.success(ResCode.SUCCESS); + } + + + @GetMapping("/executeExp") + public ResEntity executeExp() { + log.info("测试List: {}", c8NodeService.queryExpressionList("Hierarchy", "C67079"));//测试List + log.info("测试字符: {}", c8NodeService.queryExpressionResult("C8_CStyle_PurQuantity*C8_CStyle_DJ", "C159609")); + log.info("测试日期: {}", c8NodeService.queryExpressionDate("nowTime()", "C159609"));//测试日期 + Date c159609 = c8NodeService.queryExpressionDate0("nowTime()", "C159609");//返回日期 + log.info("测试时间: {}", c8NodeService.queryExpressionTime("nowTime()", "C159609"));//测试时间 + log.info("测试Map: {}", c8NodeService.queryExpressionMap("Images", "C159609"));//测试Map + log.info("测试List: {}", c8NodeService.queryExpressionList("ProductColors.filter(Active).list()", "C0/REW0132|Style"));//测试List + log.info("测试Enum: {}", c8NodeService.queryExpressionEnumkey("C8_Style_PlanPeriod", "C0/REW0132|Style"));//测试List + String[] strings = c8NodeService.queryExpressionArray("", "");//返回数组 + Object o = c8NodeService.queryExpressionResultObject("", "");//返回Object + String url = "C159609"; + c8NodeService.getImageSmallImage(url); + c8NodeService.getImageThumbnail(url); + c8NodeService.getImageViewable(url); + c8NodeService.getFileFromDirectAddr(""); + c8NodeService.getFileFromNode("", ""); + return WebResponse.success(ResCode.SUCCESS); + } + + @GetMapping("/executeSearch") + public ResEntity executeSearch() { + DepPath build = DepPath.builderXml().xml("\n" + "").addPath("Child:Attributes").addPath("Child:ProductColors").build(); + DepPathResult result = c8NodeService.depPathByXml(build); + System.out.println(result.getValue("Attributes.C8_SA_ABC", "C0/REH0151|Style").getStr()); + System.out.println(result.getValue("Attributes", "C0/CW000001|Colorway").getStr()); + + List strings = c8NodeService.queryByXML("search语句");//返回查询结果所有的URL + String s = c8NodeService.queryFirstByXML("search语句");//返回查询结果第一个URL + c8NodeService.queryFirstByNodeName("", "");//通过BO类型和Node Name查询,并且返回第一个URL + return WebResponse.success(ResCode.SUCCESS); + } + + @GetMapping("/executeSearchUrl") + public ResEntity executeSearchUrl() { + DepPath build = DepPath.builder().addUrl("C0/REH0151|Style").build(); + DepPathResult result = c8NodeService.depPathByUrl(build); + System.out.println(result.getValue("Attributes", "C0/REH0151|Style").getStr()); + System.out.println(c8NodeService.querySingleUrl("C0/REH0151|Style")); + return WebResponse.success(ResCode.SUCCESS); + } + + @GetMapping("/executeOperation") + public ResEntity executeOperation() { + BufferedInputStream inputStream = cn.hutool.core.io.FileUtil.getInputStream("D:\\centric\\11.png"); + MultipartFile file = FileUtil.getMultipartFile(inputStream, "11.png"); + OperationResultEntity operationResultEntity = c8OperationService.uploadFile(file, "C3344", "File"); + return WebResponse.success(ResCode.SUCCESS); + } + + @GetMapping("/publishFile") + public ResEntity publishFile() { + BufferedInputStream inputStream = cn.hutool.core.io.FileUtil.getInputStream("D:\\centric\\11.png"); + MultipartFile file = FileUtil.getMultipartFile(inputStream, "11.png", "FL"); + OperationResultEntity operationResultEntity = c8OperationService.publishFile(file, ""); + System.out.println(operationResultEntity.toString()); + return WebResponse.success(ResCode.SUCCESS); + } + + @GetMapping("/testAAA") + public ResEntity testAAA(@RequestParam String url) { + + DepPath build = DepPath.builder().addUrl(url).build(); + DepPathResult result = c8NodeService.depPathByUrl(build); + Map allNodes = result.getAllNodes(); + System.out.println(allNodes); + + + return WebResponse.success(ResCode.SUCCESS, allNodes); + } + + /** + * 客户 Customer + */ + @GetMapping("/api/customer") + public ResEntity customer(@RequestParam Map map, WebRequest webRequest) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append("") + .append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 尺码 ProductSize + */ + @GetMapping("/api/productSize") + public ResEntity ProductSize(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append("") + .append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 颜色库 ColorSpecification + */ + @GetMapping("/api/colorSpecification") + public ResEntity colorSpecification(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append("") + .append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 配色表 Colorway + */ + @GetMapping("/api/colorway") + public ResEntity colorWay(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 款式 Style + */ + @GetMapping("/api/style") + public ResEntity style(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append("") + .append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 版单进度 DataPackage + */ + @GetMapping("/api/dataPackage") + public ResEntity dataPackage(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 材料 Material + */ + @GetMapping("/api/material") + public ResEntity material(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 款式BOM ApparelBOM + */ + @GetMapping("/api/apparelBOM") + public ResEntity apparelBOM(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 搭配 PartMaterial + */ + @GetMapping("/api/partMaterial") + public ResEntity partMaterial(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 图片 Image + */ + @GetMapping("/api/image") + public ResEntity image(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + + String viewable = (String) map.get("viewable"); + + if (!StrUtil.isBlank(viewable)) { + xmlBuilder.append(String.format( + "", + viewable + )); + } + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 层级分类 Category + */ + @GetMapping("/api/category") + public ResEntity category(@RequestParam Map map) { + StringBuilder xmlBuilder = new StringBuilder(); + + String category = (String) map.get("category"); // 客户: Category1, 大类: Category2 + + ArrayList categoryArray = new ArrayList<>(); + categoryArray.add("Category1"); + categoryArray.add("Category2"); + + if (!StrUtil.isBlank(category)) { + xmlBuilder.append(String.format( + "", + category + )); + } else if (!categoryArray.contains(category)) { + return ResEntity.builder().code(400).msg("category 不在可选范围!!").build(); + } else { + return ResEntity.builder().code(400).msg("category 必填!!").build(); + } + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 根据url码获取信息 + */ + @GetMapping("/searchByUrl") + public ResEntity searchByUrl(@RequestParam Map map) { + String url = (String) map.get("url"); + JSONObject jsonObject = nodeInfoByUrl(url); + return WebResponse.success(ResCode.SUCCESS, jsonObject); + } + + public JSONObject nodeInfoByUrl(String url) { + DepPath build = DepPath.builder().addUrl(url).build(); + DepPathResult result = c8NodeService.depPathByUrl(build); + return result.getAllNodes().get(url); + } + + /** + * 根据年份获取每年style的图片 + */ + @GetMapping("/api/getStyleImageBySeason") + public ResEntity getStyleImageBySeason(@RequestParam Map map) { + String parentSeason = (String) map.get("parentSeason"); + if (parentSeason == null || parentSeason.isEmpty()) { + parentSeason = ""; // 设置默认值,避免拼接时出现 NullPointerException + } + String xmlBuilder = "" + + "" + + ""; + + DepPath depPath = DepPath.builderXml() + .xml(xmlBuilder) + .build(); + + DepPathResult result = c8NodeService.depPathByXml(depPath); + JSONObject jsonResult = JSONUtil.parseObj(result); + + List nodes = Collections.emptyList(); + if (jsonResult.containsKey("nodes") && jsonResult.getJSONArray("nodes") != null) { + nodes = jsonResult.getJSONArray("nodes"); + } + + ArrayList imageUrls = new ArrayList<>(); + + for (Object node : nodes) { + extractImageUrlsFromNode(node, imageUrls); + } + + int size = imageUrls.size(); + HashMap resultMap = new HashMap<>(); + resultMap.put("size", size); + resultMap.put("imageUrls", imageUrls); + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + private void extractImageUrlsFromNode(Object node, List imageUrls) { + JSONObject nodeJson = JSONUtil.parseObj(node); + JSONObject imagesDict = nodeJson.getJSONObject("Images"); + + if (imagesDict != null && !imagesDict.isEmpty()) { + imageUrls.addAll(imagesDict.values().stream() + .filter(value -> value instanceof String) + .map(value -> (String) value) + .collect(Collectors.toList())); + } + } + + /** + * 处理图片文件流 + */ + + @GetMapping("/api/imageStreamViewable") + public ResponseEntity imageStreamViewable(@RequestParam("imageUrl") String imageUrl) { + return processImageStream(imageUrl, c8NodeService::getImageViewable, "_viewable"); + } + + @GetMapping("/api/imageStreamSmall") + public ResponseEntity imageStreamSmall(@RequestParam("imageUrl") String imageUrl) { + return processImageStream(imageUrl, c8NodeService::getImageSmallImage, "_small"); + } + + @GetMapping("/api/imageStreamThumbnail") + public ResponseEntity imageStreamThumbnail(@RequestParam("imageUrl") String imageUrl) { + return processImageStream(imageUrl, c8NodeService::getImageThumbnail, "_thumbnail"); + } + + private ResponseEntity processImageStream(String imageUrl, Function imageFetcher, String infix) { + if (imageUrl == null || imageUrl.isEmpty()) { + return ResponseEntity.badRequest().body(null); + } + + List imageExtensions = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "tiff", "ico", "svg", "webp"); + + try { + JSONObject imageInfo = nodeInfoByUrl(imageUrl); + if (imageInfo == null || !imageInfo.containsKey("Node Name")) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + String imageName = imageInfo.getStr("Node Name"); + String extension = ".jpg"; + String fileName = imageUrl + infix + extension; + // 如果图片名称包含.且包含扩展名在imageExtensions内,则使用原始扩展名 + if (imageName != null && imageName.contains(".") && imageExtensions.contains(imageName.substring(imageName.lastIndexOf(".") + 1))) { + extension = "." + imageName.substring(imageName.lastIndexOf(".") + 1); + fileName = imageUrl + infix + extension; + } + + InputStream imageStream = imageFetcher.apply(imageUrl); + if (imageStream == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + InputStreamResource imageResource = new InputStreamResource(imageStream); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(imageResource); + } catch (Exception e) { + log.error("Exception: {}", imageUrl, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + /** + * 通过文件柜地址获取文件的流 + */ + @GetMapping("/api/fileStreamByFileAddr") + public ResponseEntity fileStreamByFileAddr(@RequestParam Map map) { + String fileAddrUrl = (String) map.get("fileAddrUrl"); + if (fileAddrUrl == null || fileAddrUrl.isEmpty()) { + return ResponseEntity.badRequest().body(null); + } + String fileName = (String) map.get("fileName"); + if (fileName == null || fileName.isEmpty()) { + return ResponseEntity.badRequest().body(null); + } + try { + InputStream fileStream = c8NodeService.getFileFromDirectAddr(fileAddrUrl); + InputStreamResource fileResource = new InputStreamResource(fileStream); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(fileResource); + + } catch (Exception e) { + log.error("Exception: {}", fileAddrUrl, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + /** + * 枚举列表 EnumList + */ + @GetMapping("/api/enumList") + public ResEntity enumList(@RequestParam Map map) { + String enumListId = (String) map.get("enumListId"); + StringBuilder xmlBuilder = new StringBuilder(); + // 如果有enumListId,则根据enumListId查询枚举列表 + if (StrUtil.isNotBlank(enumListId)) { + xmlBuilder.append("") + .append(""); + } else { + // 如果没有enumListId,则根据enumListId查询枚举列表 + xmlBuilder.append(""); + } + HashMap resultMap = getResEntity(map, xmlBuilder); + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 枚举值 EnumValue + */ + @GetMapping("/api/enumValue") + public ResEntity enumValue(@RequestParam Map map) { + String enumValueId = (String) map.get("enumValueId"); + StringBuilder xmlBuilder = new StringBuilder(); + + if (StrUtil.isNotBlank(enumValueId)) { + xmlBuilder.append("") + .append(""); + } else { + xmlBuilder.append(""); + } + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 按类型通用查询接口 + */ + @GetMapping("/api/{typeName}") + public ResEntity plmQueryByType(@RequestParam Map map, @PathVariable String typeName) { + log.info("PathVariable: {}", typeName); + log.info("RequestParam: {}", map); + + StringBuilder xmlBuilder = new StringBuilder(); + + if (!StrUtil.isBlank(typeName)) { + xmlBuilder.append(String.format( + "", + typeName + )); + } else { + return ResEntity.builder().code(400).msg("typeName 必填!!").build(); + } + + HashMap resultMap = getResEntity(map, xmlBuilder); + + return WebResponse.success(ResCode.SUCCESS, resultMap); + } + + /** + * 统一处理,获取返回结果 + */ + public HashMap getResEntity(@RequestParam Map map, StringBuilder xmlBuilder) { + // 时间戳字符串 + String modifiedTimeStart = (String) map.get("modifiedTimeStart"); + String modifiedTimeEnd = (String) map.get("modifiedTimeEnd"); + + int page = Convert.toInt(map.get("page"), 1); + int pageSize = Convert.toInt(map.get("pageSize"), 10); + + String sequence = Convert.toStr(map.get("sequence"), "DESC"); + String nodeName = (String) map.get("nodeName"); + String parent = (String) map.get("parent"); + + String sValue = (String) map.get("sValue"); + String rValue = (String) map.get("rValue"); + + ObjectMapper mapper = new ObjectMapper(); + + try { + Map sValueMap = mapper.readValue(sValue, new TypeReference>() {}); + + for (Map.Entry entry : sValueMap.entrySet()) { + xmlBuilder.append(String.format( + "", + entry.getKey(), + entry.getValue() + )); + } + + Map rValueMap = mapper.readValue(rValue, new TypeReference>() {}); + for (Map.Entry entry : rValueMap.entrySet()) { + xmlBuilder.append(String.format( + "", + entry.getKey(), + entry.getValue() + )); + } + } catch (Exception e) { + log.error("Error parsing JSON: {}", e.getMessage()); + } + + int start = (page - 1) * pageSize + 1; + int end = pageSize * page; + + xmlBuilder.append(String.format("", sequence)); + + if (!StrUtil.isBlank(modifiedTimeStart)) { + xmlBuilder.append(String.format( + "", + modifiedTimeStart + )); + } + if (!StrUtil.isBlank(modifiedTimeEnd)) { + xmlBuilder.append(String.format( + "", + modifiedTimeEnd + )); + } + + if (!StrUtil.isBlank(nodeName)) { + xmlBuilder.append(String.format( + "", + nodeName + )); + } + + if (!StrUtil.isBlank(parent)) { + xmlBuilder.append(String.format( + "", + parent + )); + } + + System.out.println("xmlBuilder: " + xmlBuilder.toString()); + + DepPath depPath = DepPath.builderXml() + .xml(xmlBuilder.toString()) + .build(); + + DepPathResult preResult = c8NodeService.depPathByXml(depPath, 1, 1); + + DepPathResult result = c8NodeService.depPathByXml(depPath, start, end); + + + int total = 0; + int curNum = 0; + List nodes = Collections.emptyList(); + + JSONObject preJsonResult = JSONUtil.parseObj(preResult); + if (preJsonResult.getJSONArray("nodes") != null) { + total = preJsonResult.getJSONArray("completeResultRefs").size(); + } + + JSONObject jsonResult = JSONUtil.parseObj(result); + + if (jsonResult.getJSONArray("nodes") != null) { + nodes = jsonResult.getJSONArray("nodes"); + curNum = nodes.size(); + } + String nodeStr = JsonUtil.toJSONString(nodes); + + HashMap resultMap = new HashMap<>(); + resultMap.put("total", total); + resultMap.put("page", page); + resultMap.put("pageSize", pageSize); + resultMap.put("curNum", curNum); + resultMap.put("items", JsonUtil.parseObject(nodeStr, List.class)); + + return resultMap; + } + + @GetMapping("/api/expressionResult") + public ResEntity expressionResult(@RequestParam Map map) { + String expression = (String) map.get("expression"); + String url = (String) map.get("url"); + Object o = c8NodeService.queryExpressionResultObject(expression, url);//返回Object + if (o == null) { + return WebResponse.success(ResCode.SUCCESS, new ArrayList<>()); + } + String input = o.toString(); + // 1. 去除方括号 + String cleaned = input.substring(1, input.length() - 1); + + // 2. 按逗号分割并转为 List + List list = new ArrayList<>(Arrays.asList(cleaned.split(",\\s*"))); + + return WebResponse.success(ResCode.SUCCESS, list); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/FiledPermissionsController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/FiledPermissionsController.java new file mode 100644 index 0000000..151c65a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/c8/FiledPermissionsController.java @@ -0,0 +1,33 @@ +package com.centricsoftware.enhancement.controller.c8; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.enhancement.service.lui.FieldPermissionsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * description: + * Date: 2023/3/17 11:23 + */ + +@RestController +@Slf4j +public class FiledPermissionsController { + + @Resource + FieldPermissionsService fieldPermissionsService; + + @GetMapping("createAttributeForFieldPermissions") + public ResEntity createAttributeForFieldPermissions(){ + fieldPermissionsService.createLookupItemSubtype(); + fieldPermissionsService.createLookupItemFiled(); + fieldPermissionsService.createView(); + return WebResponse.success(ResCode.SUCCESS,"请大更新系统!"); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogController.java new file mode 100644 index 0000000..6a3aebd --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogController.java @@ -0,0 +1,134 @@ +package com.centricsoftware.enhancement.controller.log; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.utils.PageUtil; +import com.centricsoftware.config.entity.EsProperties; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.dto.log.LogDto; +import com.centricsoftware.enhancement.dto.log.PlmLog; +import com.centricsoftware.enhancement.dto.log.QueryPlmLogReq; +import com.centricsoftware.enhancement.modules.c8.service.es.LogElasticsearchService; +import com.centricsoftware.enhancement.service.log.impl.CallBackLogServiceImpl; +import com.centricsoftware.enhancement.service.log.impl.LogServiceImpl; +import com.google.common.collect.Lists; +import org.springframework.beans.factory.annotation.Value; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("log") +public class LogController { + + @Resource + private LogServiceImpl logService; + @Resource + private CallBackLogServiceImpl callBackLogService; + + @Resource + private LogElasticsearchService logElasticsearchService; + + @Resource + EsProperties esProperties; + + @Value("${web.logging.path}") + private String filePath; +// +// @RequestMapping("page") +// public ResEntity page(@RequestParam("current") int current, @RequestParam("size") int size, @RequestBody FeignLogDto dto) { +// Page page = logService.page(current, size, dto); +// return WebResponse.autoResponse(page, null); +// } + + /** + * 初始化查询枚举 + * + * @author liaochangjiang + * @since 2021-12-28 11:01:48 + */ + @GetMapping("init") + public ResEntity init() throws IOException { + List logNames = logElasticsearchService.listLogNames(esProperties.getBrand()); + return WebResponse.success(ResCode.SUCCESS,logNames); + } + + /** + * 查询表格数据 + * + * @author liaochangjiang + * @since 2021-12-28 11:03:19 + */ + @PostMapping("queryData") + public ResEntity queryData(@RequestBody QueryPlmLogReq req) throws IOException { + req.setBrand(esProperties.getBrand()); + Page page = PageUtil.buildPageParam(req, PlmLog.class); + logElasticsearchService.queryLogs(page, req); + return WebResponse.success(ResCode.SUCCESS,page); + } + + /** + * 获取日志目录下的所有文件名字 + * @author liaochangjiang + * @since 2024-10-21 23:11 + */ + @ResponseBody + @RequestMapping("/getFile") + public ResEntity getFile(){ + File total = new File(filePath); + File[] files = total.listFiles(); + List result = Lists.newArrayList(); + if (files != null) { + for (File fileArr : files) { + File[] fileList = fileArr.listFiles(); + LogDto logDto = new LogDto(); + logDto.setLabel(fileArr.getName()); + List fileLogDtoList = Lists.newArrayList(); + if (fileList != null) { + for (File file : fileList) { + LogDto fileLogDto = new LogDto(); + fileLogDto.setLabel(file.getName()); + fileLogDtoList.add(fileLogDto); + } + logDto.setChildren(fileLogDtoList); + result.add(logDto); + } + } + } + return WebResponse.success(ResCode.SUCCESS,result); + } + + /** + * 根据文件名字获取文件数据 + * @author liaochangjiang + * @since 2024-10-22 9:26 + */ + @ResponseBody + @RequestMapping("/getFileByName") + public String getFileByName(String name) { + File file = new File(filePath + "/" + name); + StringBuilder result = new StringBuilder(); + try{ + // 构造一个BufferedReader类来读取文件 + BufferedReader br = new BufferedReader(new FileReader(file)); + String s; + // 使用readLine方法,一次读一行 + while((s = br.readLine())!=null){ + result.append(System.lineSeparator()).append(s); + } + br.close(); + }catch(Exception e){ + e.printStackTrace(); + } + return result.toString(); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogForRelationshipDBController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogForRelationshipDBController.java new file mode 100644 index 0000000..c411b87 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/log/LogForRelationshipDBController.java @@ -0,0 +1,89 @@ +package com.centricsoftware.enhancement.controller.log; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; +import com.centricsoftware.commons.utils.PageUtil; +import com.centricsoftware.config.entity.EsProperties; +import com.centricsoftware.enhancement.dto.PlmReqDto; +import com.centricsoftware.enhancement.dto.log.*; +import com.centricsoftware.enhancement.modules.c8.service.es.LogElasticsearchService; +import com.centricsoftware.enhancement.service.log.impl.CallBackLogServiceImpl; +import com.centricsoftware.enhancement.service.log.impl.LogServiceImpl; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("log/db") +public class LogForRelationshipDBController { + + @Resource + private LogServiceImpl logService; + @Resource + private CallBackLogServiceImpl callBackLogService; + + @Resource + private LogElasticsearchService logElasticsearchService; + + @Resource + EsProperties esProperties; + + @Value("${web.logging.path}") + private String filePath; + +// @RequestMapping("page") +// public ResEntity page(@RequestParam("current") int current, @RequestParam("size") int size, @RequestBody FeignLogDto dto) { +// Page page = logService.page(current, size, dto); +// return WebResponse.autoResponse(page, null); +// } + + @PostMapping("queryData") + public ResEntity queryData(@RequestBody FeignLogDto req) throws IOException { + int pageNum = req.getPageNum().intValue(); + int pageSize = req.getPageSize().intValue(); + Page page = logService.page(pageNum, pageSize, req); + return WebResponse.autoResponse(page, null); + } + + /** + * 初始化查询枚举 + * + * @author liaochangjiang + * @since 2021-12-28 11:01:48 + */ + @GetMapping("init") + public ResEntity init() throws IOException { + List logNames = Lists.newArrayList("PLM"); + return WebResponse.success(ResCode.SUCCESS,logNames); + } + + /** + * 主动调用plm日志接口,用于异步接口日志调用 + * @param param feignLog日志DTO + */ + @RequestMapping("/receive/msg") + @ResponseBody + public ResEntity receiveMsg(@RequestBody PlmReqDto param) { + try { + callBackLogService.saveLog(param.getParam()); + return WebResponse.success(ResCode.SUCCESS); + } catch (BaseException e) { + return WebResponse.failure(ResCode.ERROR, e.getData()); + } catch (Exception e) { + return WebResponse.failure(e.getMessage()); + } + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/controller/specification/SpecificationDataSheetController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/specification/SpecificationDataSheetController.java new file mode 100644 index 0000000..a093935 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/controller/specification/SpecificationDataSheetController.java @@ -0,0 +1,57 @@ +package com.centricsoftware.enhancement.controller.specification; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.service.specification.SpecificationDataSheetService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * description:工艺单 + * Author: Xulin.Xie + * Date: 2022/7/6 15:22 + */ +@Slf4j +@Controller +@RequestMapping("SpecificationDataSheet") +public class SpecificationDataSheetController { + + final + private SpecificationDataSheetService specificationDataSheetService; + private final C8NodeService c8NodeService; + + + /** + * localhost:8088/plmservice/SpecificationDataSheet/ResetSDSection?currentVersion=C102687 + *从工艺单中新建:刷新所有行 + */ + @ResponseBody + @RequestMapping("ResetSDSection") + public ResEntity urls(String currentVersion) { + String[] items = c8NodeService.queryExpressionArray("Items",currentVersion); + String error = specificationDataSheetService.resetSection(currentVersion,items); + return WebResponse.success(error); + } + + /** + * 从工艺单中新建:刷新新建的行 + * @param urls 工艺单明细 + * @param currentVersion 工艺单版本 + * @return + */ + @ResponseBody + @RequestMapping("ResetSection") + public ResEntity urls(String[] urls,String currentVersion) { + String error = specificationDataSheetService.resetSection(currentVersion,urls); + return WebResponse.success(""); + } + + public SpecificationDataSheetController(SpecificationDataSheetService specificationDataSheetService, C8NodeService c8NodeService) { + this.specificationDataSheetService = specificationDataSheetService; + this.c8NodeService = c8NodeService; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/AnnoImportLineError.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/AnnoImportLineError.java new file mode 100644 index 0000000..b1410fc --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/AnnoImportLineError.java @@ -0,0 +1,22 @@ +package com.centricsoftware.enhancement.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Data +public class AnnoImportLineError { + + private Object lineData; + + private String error; + + private boolean success; + + private int seq; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/PlmReqDto.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/PlmReqDto.java new file mode 100644 index 0000000..e98a80d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/PlmReqDto.java @@ -0,0 +1,19 @@ +package com.centricsoftware.enhancement.dto; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 传输接口的接受基类,所有传入的对象都需要用此对象包含起来 + * 如 : @RequestBody PlmDto param + * + * @param + */ +@Data +public class PlmReqDto implements Serializable { + @JsonProperty("param") + private T param; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/UploadImagesEntity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/UploadImagesEntity.java new file mode 100644 index 0000000..2c9cd72 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/UploadImagesEntity.java @@ -0,0 +1,19 @@ +package com.centricsoftware.enhancement.dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Data +public class UploadImagesEntity { + private String smallImage; + private String viewable; + private String name; + private String imageUrl; + private String url; + private String seq; + private String alt; + private String pid; + private String thumb; + private String src; + private MultipartFile raw; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/OkHttpCache.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/OkHttpCache.java new file mode 100644 index 0000000..7819420 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/OkHttpCache.java @@ -0,0 +1,8 @@ +package com.centricsoftware.enhancement.dto.cache; + +import org.springframework.stereotype.Component; + +@Component +public class OkHttpCache { + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/ThreadLocalCache.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/ThreadLocalCache.java new file mode 100644 index 0000000..5a0943b --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/cache/ThreadLocalCache.java @@ -0,0 +1,17 @@ +package com.centricsoftware.enhancement.dto.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/8/4 14:02 + */ +@Slf4j +@Component +public class ThreadLocalCache { + + public ThreadLocal personalCookie = new ThreadLocal<>(); + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/excel/ExcelAroundAOP.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/excel/ExcelAroundAOP.java new file mode 100644 index 0000000..1991913 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/excel/ExcelAroundAOP.java @@ -0,0 +1,39 @@ +package com.centricsoftware.enhancement.dto.excel; + +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +/** + * description:基于yml导入时,如果有设置前置通知,返回的实体类 + * Author: Xulin.Xie + * Date: 2023/2/1 13:29 + */ +@Data +@Builder +public class ExcelAroundAOP { + + /** + * 是否正确 + * false时,将把error返回给用户 + * 如果是后置通知,false将不执行operation保存操作 + */ + @Builder.Default + private boolean success = true; + + @Builder.Default + private String error = ""; + + /** + * 后置通知,如果校验成功,拼接的operation + */ + @Builder.Default + private String operation=""; + + /** + * 如果itemMap有值的话,会将键值对加载到行数据内,可供yml使用 + */ + private Map itemMap; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogDto.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogDto.java new file mode 100644 index 0000000..f4e4906 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogDto.java @@ -0,0 +1,133 @@ +package com.centricsoftware.enhancement.dto.log; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.util.Date; + +import static org.apache.ibatis.type.JdbcType.BIT; + + +@Slf4j +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("C8_PS_Operation_Log") +public class FeignLogDto extends FeignLogReqDto{ + + /** + * 接口名称:长度50 + */ + @TableField(value = "name", condition = SqlCondition.LIKE) + private String name; + + /** + * 请求地址:长度150 + */ + private String url; + + /** + * key:长度50 + */ + @TableField(value = "data_key") + private String dataKey; + + /** + * 请求主机:长度50 + */ + private String host; + + /** + * 请求路径:长度100 + */ + private String path; + + /** + * 预留字段,ES版本的日志可能有用:长度50 + */ + private String brand; + + /** + * 发起时间 + */ + @TableField(value = "send_date", condition = "%s>#{%s}") + private Date startTime; + + /** + * 发起时间 + */ + @TableField(value = "end_date", condition = "%s>#{%s}") + private Date endTime; + + /** + * 耗时 + */ + @TableField("cost_ms") + private Long costMs; + + /** + * 响应http状态码:长度100 + */ + @TableField("return_code") + private String returnCode; + + /** + * 响应内容:text + */ + @TableField("response") + private String returnMsg; + + /** + * 请求内容:text + */ + private String debug; + + @TableId(value = "id", type = IdType.AUTO)//指定自增策略 MYSQL + private Integer id; + + /** + * 异步请求,对应的原ID是多少 + */ + private String refid; // 默认为空 + + /** + * 接口方式 post、get... + * 长度10 + */ + private String method; + + /** + * 请求头:长度1000 + */ + private String header; + + /** + * 查询条件:长度1000 + */ + private String param; + + @TableField(value = "success",jdbcType=BIT) + private Boolean success; + + /** + * 日志类型:长度20 + * 接口日志;操作日志 + */ + @TableField(value = "log_type") + private String logType; + + /** + * 响应code + */ + @TableField(exist=false) + private String code; + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogReqDto.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogReqDto.java new file mode 100644 index 0000000..f9408f0 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/FeignLogReqDto.java @@ -0,0 +1,46 @@ +package com.centricsoftware.enhancement.dto.log; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; + +import static org.apache.ibatis.type.JdbcType.BIT; + + +@Data +public class FeignLogReqDto{ + + @TableField(exist=false) + private Long pageNum = 1L; + + @TableField(exist=false) + private Long pageSize = 10L; + + @TableField(exist=false) + private String startTimeBegin; + + @TableField(exist=false) + private String startTimeEnd; + + @TableField(exist=false) + private Integer successStr; + + /** + * 请求报文中key字段 + */ + @TableField(exist=false) + private String requestId; + + /** + * 响应报文中状态字段 + */ + @TableField(exist=false) + private String responseId; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/LogDto.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/LogDto.java new file mode 100644 index 0000000..2a2b6c0 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/LogDto.java @@ -0,0 +1,11 @@ +package com.centricsoftware.enhancement.dto.log; + +import lombok.Data; + +import java.util.List; + +@Data +public class LogDto { + private String label; + private List children; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/PlmLog.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/PlmLog.java new file mode 100644 index 0000000..0cf65ee --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/PlmLog.java @@ -0,0 +1,43 @@ +package com.centricsoftware.enhancement.dto.log; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * plm日志 + * + */ +@Data +@Accessors(chain = true) +public class PlmLog implements Serializable { + + private String path; + + private String name; + + private String brand; + @JsonProperty("@timestamp") + private LocalDateTime startTime; + + private LocalDateTime endTime; + + private boolean success; + + private Long costMs; + + private String host; + + private String param; + + private String returnCode; + + private String returnMsg; + + private String returnText; + + private String debug; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/QueryPlmLogReq.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/QueryPlmLogReq.java new file mode 100644 index 0000000..0fc94ad --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/log/QueryPlmLogReq.java @@ -0,0 +1,48 @@ +package com.centricsoftware.enhancement.dto.log; + +import com.centricsoftware.commons.dto.PlmPageReqVo; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 查询日志请求 + * + */ +@Data +@EqualsAndHashCode() +@ToString(callSuper = true) +@Accessors(chain = true) +public class QueryPlmLogReq extends PlmPageReqVo { + + private String path; + + private String name; + + private String brand; + + private Boolean success; + + private String host; + + private String param; + + private Integer returnCode; + + private String returnText; + + private String startTimeBegin; + + private String startTimeEnd; + + private String endTimeBegin; + + private String endTimeEnd; + + private String debug; + /** + * tid + */ + private String tid; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridOptions.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridOptions.java new file mode 100644 index 0000000..7ca7f3e --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridOptions.java @@ -0,0 +1,120 @@ +package com.centricsoftware.enhancement.dto.table; + +import com.centricsoftware.enhancement.dto.table.gridconfig.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * description: 前端工程通用配置类 + * Author: Xulin.Xie + * Date: 2022/8/11 9:57 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class GridOptions { + + /** + * 边框线配置 + */ + @Builder.Default + private boolean border = true; + + /** + * 当表头内容过长时显示为省略号 + */ + @Builder.Default + private boolean showHeaderOverflow = true; + + /** + * 当内容过长时显示为省略号 + */ + @Builder.Default + private boolean showOverflow = true; + + /** + * 是否记录操作记录,左上角小红点显示 + */ + @Builder.Default + private boolean keepSource = true; + + /** + * 是否记录操作记录,左上角小红点显示 + */ + @Builder.Default + private String id = "full_edit_2"; + + /** + * 表格高度设定,目前已经采用mounted去做动态更新 + */ + @Builder.Default + private int height = 0; + + /** + * 行配置项 + */ + @Builder.Default + private RowConfig rowConfig = new RowConfig(); + + /** + * 列配置项 + */ + @Builder.Default + private ColumnBaseConfig columnConfig = new ColumnBaseConfig(); + + /** + * 打印配置项 + */ + @Builder.Default + private PrintConfig printConfig = new PrintConfig(); + + /** + * 排序配置项 + */ + @Builder.Default + private SortConfig sortConfig = new SortConfig(); + + /** + * 分页配置项 + */ + @Builder.Default + private PagerConfig pagerConfig = new PagerConfig(); + + // 工具栏配置 + @Builder.Default + private ToolbarConfig toolbarConfig = new ToolbarConfig(); + + /** + * 具体表格配置 + */ + @Builder.Default + private List columns = new ArrayList<>(); + + /** + * 筛选配置项 + */ + @Builder.Default + private FilterConfig filterConfig = new FilterConfig(); + + /** + * 表单配置项;最前面的筛选 + */ + private FormConfig formConfig; + + /** + * 右侧按钮 + */ + @Builder.Default + private List rightBtnConfigs = new ArrayList<>(); + + @Builder.Default + private int rightBtnWidth = 160; + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponse.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponse.java new file mode 100644 index 0000000..310f55f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponse.java @@ -0,0 +1,28 @@ +package com.centricsoftware.enhancement.dto.table; + + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 返回信息实体类 + * @author ZhengGong + * @date 2020/4/16 + */ +@Data +@Builder +public class GridResponse implements Serializable { + + private long total; + /** + * 返回对象 + */ + private List records; + /** + * 是否成功 + */ + private boolean success ; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponseData.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponseData.java new file mode 100644 index 0000000..3c28dbe --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/GridResponseData.java @@ -0,0 +1,20 @@ +package com.centricsoftware.enhancement.dto.table; + + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * 返回信息实体类 + * @author ZhengGong + * @date 2020/4/16 + */ +@Data +@Builder +public class GridResponseData implements Serializable { + + private GridResponse data; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnBaseConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnBaseConfig.java new file mode 100644 index 0000000..8a049cc --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnBaseConfig.java @@ -0,0 +1,21 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/8/12 14:59 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ColumnBaseConfig { + @Builder.Default + private boolean resizable = true; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnConfig.java new file mode 100644 index 0000000..6a5fb87 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ColumnConfig.java @@ -0,0 +1,102 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 列配置 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ColumnConfig { + + /** + * 字段 ID + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String field; + + /** + * 第一列可以展示为复选框:checkbox + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String type; + + /** + * 固定列:"left" + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String fixed; + + /** + * 标题 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String title; + + /** + * 排序 + */ + @Builder.Default + private boolean sortable = true; + + /** + * 宽度 + */ + @Builder.Default + private int width = 150; + + /** + * 自定义转换 + * demo: + * 1、this.format.formatBoolean + * 2、this.format.formatDate + */ + @Builder.Default + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String formatter = ""; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String slots; + + /** + * 过滤项:默认文本过滤 + * demo1: 下拉 + * [ + * { label: "是", value: "Y" }, + * { label: "否", value: "N" } + * ] + * demo2: 数值 + * [{ data: { min: "0", max: "0" } }] + * demo3: 文本 + * [{ data: ”“ }] + * demo4: 时间 + * { data: { value: [], valueFormat: "yyyy-MM-dd HH:mm:ss" } } + * 支持:时间、文本、数值、下拉 + */ + @Builder.Default + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Object filters = new cn.hutool.json.JSON[]{JSONUtil.parse("{ data:\"\"}")}; + + /** + * 过滤项:默认文本过滤 + * 列上过滤的组件、支持:时间、文本、数值、下拉 + * {name: "FilterInput"} + * FilterTime、FilterComplex、FilterContent、FilterSelect、FilterInput + */ + @Builder.Default + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private JSONObject filterRender = JSONUtil.parseObj("{name: \"FilterInput\"}"); + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ExportConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ExportConfig.java new file mode 100644 index 0000000..71f4da9 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ExportConfig.java @@ -0,0 +1,34 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:导出配置 + * Author: Xulin.Xie + * Date: 2022/8/11 13:47 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ExportConfig { + + /** + * 开启远程导出 + */ + private boolean remote = false; + + private boolean useStyle = true; + + private String exportMethod ; + + private String types; + + private boolean resize = true; + + private String modes; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FilterConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FilterConfig.java new file mode 100644 index 0000000..a19f8c1 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FilterConfig.java @@ -0,0 +1,32 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 筛选配置项 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FilterConfig { + + /** + * 去掉筛选的图标 + */ + @Builder.Default + private boolean showIcon = true; + + /** + * 是否启动服务端排序 + */ + @Builder.Default + private boolean remote = false; + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormConfig.java new file mode 100644 index 0000000..9034d85 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormConfig.java @@ -0,0 +1,46 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * description: 表单配置项 + * Author: Xulin.Xie + * Date: 2022/8/11 10:32 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FormConfig { + + /** + * 所有项的标题宽度 + */ + @Builder.Default + private int titleWidth = 80; + + /** + * 所有项的标题对齐方式 + */ + @Builder.Default + private String titleAlign = "right"; + + /** + * 尺寸 + */ + @Builder.Default + private String size = "small"; + + /** + * 列配置 + */ + private List items; + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormItemConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormItemConfig.java new file mode 100644 index 0000000..1a6f051 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FormItemConfig.java @@ -0,0 +1,65 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import cn.hutool.core.text.StrFormatter; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 列配置 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FormItemConfig { + + /** + * 字段名,点击提交的时候会根据这个id去做字段传递,或者可以直接通过this.$refs.xGrid.formdata + */ + private String field; + + /** + * 字段名,点击提交的时候会根据这个id去做字段传递, + */ + private String title; + + /** + * 过滤提醒信息: + * icon : el-icon-warning + */ + @Builder.Default + private String titlePrefix =""; + + public void setTitlePrefix(String message, String icon){ + this.titlePrefix= StrFormatter.format("{message:{},icon:\"{}\"}",message,icon); + } + + /** + * 是否显示标题冒号 + */ + @Builder.Default + private boolean titleColon = true; + + /** + * 是否显示必填字段的红色星号 + */ + @Builder.Default + private boolean titleAsterisk = false; + + /** + * 所有项的栅格占据的列数(共 24 分栏) + */ + @Builder.Default + private int span = 4; + + /** + * 项渲染器配置项 + */ + private FromItemRender itemRender; + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FromItemRender.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FromItemRender.java new file mode 100644 index 0000000..672a580 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/FromItemRender.java @@ -0,0 +1,74 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/8/11 13:10 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FromItemRender { + + /** + * input, textarea, select, $input, $textarea, $select, $button, $buttons, $radio, $checkbox, $switch + */ + @Builder.Default + private String name = "$input"; + + /** + * 如下方选择的,如果是下拉选择的,则需要配置options选项 + * [ + * { value: "C8_Phase:002", label: "首样后" }, + * { value: "C8_Phase:003", label: "预览后" } + * ] + */ + @Builder.Default + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List options = new ArrayList<>(); + + // 属性自定义的数据 + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private JSONObject props; + + @Builder.Default + @JsonIgnore + private boolean clearable = true; + + @Builder.Default + @JsonIgnore + private String placeholder = ""; + + @Builder.Default + @JsonIgnore + private boolean filterable = true; + + public JSONObject getProps(){ + if(props==null||props.isEmpty()){ + return JSONUtil.parseObj(StrFormatter.format("{clearable:{},placeholder:\"{}\"},filterable:{}}",clearable,placeholder,filterable)); + }else{ + return props; + } + } + + public void setOptions(String value, String label){ + String format = StrFormatter.format("{value:\"{}\",label:\"{}\"}}", value, label); + options.add(format); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PagerConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PagerConfig.java new file mode 100644 index 0000000..9df758a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PagerConfig.java @@ -0,0 +1,34 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 分页配置项 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PagerConfig { + + /** + * 是否启动服务端排序 + */ + private String[] layouts = {"PrevJump", "PrevPage", "Number", "NextPage", "NextJump", "Sizes", "FullJump", "Total"}; + + private int pageSize = 200; + + /** + * 每页大小选项列表 + */ + private int[] pageSizes = {10, 50, 100, 200, 500, 5000}; + + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PrintConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PrintConfig.java new file mode 100644 index 0000000..cfdf09f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/PrintConfig.java @@ -0,0 +1,26 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * description:GridOptions 打印配置项 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PrintConfig { + + /** + * 执行默认展示需要打印的列 + */ + private List columns; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RightBtnConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RightBtnConfig.java new file mode 100644 index 0000000..595c8b6 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RightBtnConfig.java @@ -0,0 +1,31 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 列配置 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RightBtnConfig { + + /** + * 字段 ID + */ + private String title; + + @Builder.Default + private String status = "primary"; + + private String content; + + private String clickUrl; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RowConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RowConfig.java new file mode 100644 index 0000000..3255e49 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/RowConfig.java @@ -0,0 +1,24 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 行配置项 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class RowConfig { + + /** + * 鼠标悬浮,是否高亮 + */ + private boolean isHover = true; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/SortConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/SortConfig.java new file mode 100644 index 0000000..d9a15ae --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/SortConfig.java @@ -0,0 +1,29 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:GridOptions 排序配置项 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SortConfig { + + /** + * default(点击按钮触发), cell(点击表头触发) + */ + private String trigger = "cell"; + + /** + * 是否启动服务端排序 + */ + private boolean remote = false; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolbarConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolbarConfig.java new file mode 100644 index 0000000..e6beb85 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolbarConfig.java @@ -0,0 +1,69 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * description:工具栏 + * Author: Xulin.Xie + * Date: 2022/8/11 13:26 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolbarConfig { + + /** + * 按钮大小 + */ + private String size = "small"; + + /** + * 搜索栏下的按钮,暂时不封装;这边支持单个按钮平铺和下拉列表按钮 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String buttons; + + /** + * 自定义的tool的按钮 + */ + private List tools = ToolsConfig.getClearBtn(); + + /** + * 刷新 + */ + private boolean refresh = true; + + /** + * 上传 + */ + @JsonProperty("import") + private boolean importFile = false; + /** + * 导出 + */ + private boolean export = true; + + /** + * 打印 + */ + private boolean print = true; + + /** + * 是否允许最大化显示 + */ + private boolean zoom = true; + + /** + * 自定义列配置 + */ + private boolean custom = true; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolsConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolsConfig.java new file mode 100644 index 0000000..07acf93 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/dto/table/gridconfig/ToolsConfig.java @@ -0,0 +1,54 @@ +package com.centricsoftware.enhancement.dto.table.gridconfig; + +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * description:GridOptions 列配置 + * Author: Xulin.Xie + * Date: 2022/8/11 10:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolsConfig { + + public static List getClearBtn(){ + ArrayList objects = Lists.newArrayList(); + ToolsConfig build = ToolsConfig.builder().code("clearFilter").text("清空筛选") + .type("button").icon("el-icon-delete").build(); + objects.add(build); + return objects; + } + + /** + * 字段 ID + */ + private String code = "clearFilter"; + + /** + * 第一列可以展示为复选框:checkbox + */ + private String text; + + private String type; + + private String icon; + + @Builder.Default + private boolean circle = true; + + @Builder.Default + private boolean transfer = true; + + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/es/config/EsAutoConfigure.java b/enhancement/src/main/java/com/centricsoftware/enhancement/es/config/EsAutoConfigure.java new file mode 100644 index 0000000..d596600 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/es/config/EsAutoConfigure.java @@ -0,0 +1,76 @@ +package com.centricsoftware.enhancement.es.config; + + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import com.centricsoftware.commons.utils.JsonUtil; +import com.centricsoftware.config.entity.EsProperties; +import org.apache.commons.lang3.StringUtils; +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.conn.ssl.NoopHostnameVerifier; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.net.ssl.SSLContext; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Configuration +@EnableConfigurationProperties(EsProperties.class) +public class EsAutoConfigure { + + @Bean + @ConditionalOnProperty(prefix = "es", name = "enable", havingValue = "true") + public ElasticsearchClient elasticsearchClient(EsProperties properties) { + HttpHost[] httpHosts = Arrays.stream(properties.getHost().split(",")) + .map(url -> { + String[] ipPorts = url.split(":"); + return new HttpHost(ipPorts[0], Integer.parseInt(ipPorts[1]), properties.getScheme()); + }).toArray(HttpHost[]::new); + boolean ssl = Objects.equals("https", properties.getScheme()); + RestClientBuilder builder = RestClient.builder(httpHosts); + if (StringUtils.isNotBlank(properties.getUser()) && StringUtils.isNotBlank(properties.getPassword())) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials(properties.getUser(), properties.getPassword())); + + builder.setHttpClientConfigCallback(httpClientBuilder -> { + httpClientBuilder.disableAuthCaching(); + HttpAsyncClientBuilder clientBuilder = httpClientBuilder + .setKeepAliveStrategy((resp, ctx) -> TimeUnit.MINUTES.toMillis(3)) + .setDefaultCredentialsProvider(credentialsProvider); + if (ssl) { + // 信任所有 + SSLContext sslContext = null; + try { + sslContext = new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build(); + } catch (Exception e) { + throw new RuntimeException("初始化es失败"); + } + SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(sslContext, NoopHostnameVerifier.INSTANCE); + clientBuilder.setSSLStrategy(sessionStrategy); + } + return clientBuilder; + }); + } + RestClient restClient = builder.build(); + ElasticsearchTransport transport = new RestClientTransport(restClient, + new JacksonJsonpMapper(JsonUtil.getMapper().copy())); + return new ElasticsearchClient(transport); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/filter/AddCookieFilter.java b/enhancement/src/main/java/com/centricsoftware/enhancement/filter/AddCookieFilter.java new file mode 100644 index 0000000..7f62c60 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/filter/AddCookieFilter.java @@ -0,0 +1,40 @@ +package com.centricsoftware.enhancement.filter; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.dto.cache.ThreadLocalCache; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/8/4 14:20 + */ +@Component +public class AddCookieFilter extends HandlerInterceptorAdapter { + + @Resource + ThreadLocalCache threadLocalCache; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String header = request.getHeader("C8_Cookie"); + if(StrUtil.isNotBlank(header)){ + threadLocalCache.personalCookie.set(header); + } + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + @Nullable ModelAndView modelAndView) throws Exception { + threadLocalCache.personalCookie.remove();; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/filter/CachingRequestBodyFilter.java b/enhancement/src/main/java/com/centricsoftware/enhancement/filter/CachingRequestBodyFilter.java new file mode 100644 index 0000000..827ae49 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/filter/CachingRequestBodyFilter.java @@ -0,0 +1,22 @@ +package com.centricsoftware.enhancement.filter; + +import org.springframework.stereotype.Component; +import org.springframework.web.filter.GenericFilterBean; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Component +public class CachingRequestBodyFilter extends GenericFilterBean { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { + HttpServletRequest currentRequest = (HttpServletRequest) servletRequest; + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(currentRequest); + chain.doFilter(wrappedRequest, servletResponse); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/mapper/ExtractMapper.java b/enhancement/src/main/java/com/centricsoftware/enhancement/mapper/ExtractMapper.java new file mode 100644 index 0000000..434e0a3 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/mapper/ExtractMapper.java @@ -0,0 +1,20 @@ +package com.centricsoftware.enhancement.mapper; + +import com.centricsoftware.enhancement.modules.c8.dto.nativeexport.ExtractParameterEntity; +import com.centricsoftware.enhancement.modules.c8.dto.nativeexport.ExtractResultEntity; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ExtractMapper { + + List findByXmlQueryVerticalExtract(ExtractParameterEntity param); + + void findByXmlQueryVerticalExtractOracle(ExtractParameterEntity param); + + @Select({" select url from all_url where url_id=#{id} "}) + String findUrlById(String id); + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/mapper/LogMapper.java b/enhancement/src/main/java/com/centricsoftware/enhancement/mapper/LogMapper.java new file mode 100644 index 0000000..5c5f243 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/mapper/LogMapper.java @@ -0,0 +1,10 @@ +package com.centricsoftware.enhancement.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import org.springframework.stereotype.Repository; + +@Repository +public interface LogMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/ant/DepPathField.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/ant/DepPathField.java new file mode 100644 index 0000000..7f58321 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/ant/DepPathField.java @@ -0,0 +1,29 @@ +package com.centricsoftware.enhancement.modules.c8.ant; + +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; + +import java.lang.annotation.*; + +/** + * DepPath实体类查询时,注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface DepPathField { + + DepPathEntityType type() default DepPathEntityType.STRING;//类型 + + String exp();//表达式 + + String timeFormat() default "yyyy-MM-dd HH:mm:ss";//时间类型默认格式 + + String conjunctionForListToString() default ",";//List转字符串时,连接符 + + boolean recursion() default false;//递归查询 + + boolean removeDuplicate() default true;//List相关类型是否去重 + + Class generic() default String.class;//类型为List时的泛型 + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/EnumCache.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/EnumCache.java new file mode 100644 index 0000000..97172f2 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/EnumCache.java @@ -0,0 +1,257 @@ +package com.centricsoftware.enhancement.modules.c8.component; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.dto.enum4cache.EnumList; +import com.centricsoftware.commons.dto.enum4cache.EnumValue; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@Slf4j +public class EnumCache { + + private + + @Autowired + C8NodeService c8NodeService; + + //默认 + TimedCache cache = CacheUtil.newTimedCache(60 * DateUnit.DAY.getMillis()); + + /** + * 初始化缓存 + */ + public void init(boolean display) { + DepPathResult result = getDescInfo();//获取描述枚举 + DepPathResult localeInfo = null; + if (display) localeInfo = getLocaleInfo();//翻译枚举 + for (String url : result.getCompleteResultRefs()) { + doInit(result, localeInfo, url); + } +// log.debug(cache.get("C8_Cat1").toString()); + } + + private void doInit(DepPathResult result, DepPathResult localeInfo, String url) { + EnumList enumList = new EnumList(); + enumList.setDependsOn(result.getValue("DependsOn", url).getStr()); + enumList.setDescription(result.getValue("Description", url).getStr()); + enumList.setEnums(result.getValue("Enums", url).getList()); + enumList.setValues(result.getValue("Values", url).getList()); + enumList.setNodeName(result.getValue("$Name", url).getStr()); + List enumValues = setEnumValue(result, url); + enumList.setDescCache(initDescCache(enumValues));//初始化描述缓存 + if (localeInfo != null) { + enumList.setDisplayZhCache(initLocaleCache(enumValues, localeInfo, "zh"));//初始化中文缓存 + enumList.setDisplayEnCache(initLocaleCache(enumValues, localeInfo, "en"));//初始化英文缓存 + } + enumList.setEnumValues(enumValues); + cache.put(enumList.getNodeName(), enumList); + } + + /** + * 初始化翻译枚举 + */ + private Map initLocaleCache(List enumValues, DepPathResult localeInfo, String locale) { + Map localeInfoMap = localeInfo.getValue("LocaleConfiguration", "centric://REFLECTION/INSTANCE/CompanyInfo/Global").getMap(); + String localeConfigurationUrl = localeInfoMap.getOrDefault(locale, ""); + HashMap map = Maps.newHashMap(); + if (StrUtil.isBlank(localeConfigurationUrl)) return map;//如果本地化为空,则返回空map + for (EnumValue value : enumValues) { + String fullName = value.getValue(); + Map resources = localeInfo.getValue("Resources", localeConfigurationUrl).getMap(); + String orDefault = resources.getOrDefault(fullName, ""); + map.put(fullName, orDefault); + if (StrUtil.isNotBlank(orDefault)) { + map.put(orDefault, fullName); + } + } + return map; + } + + /** + * 初始化描述缓存 + **/ + private Map initDescCache(List enumValues) { + HashMap map = Maps.newHashMap(); + for (EnumValue value : enumValues) { + map.put(value.getValue(), value.getDescription()); + if (StrUtil.isBlank(value.getDescription())) { + continue; + } + map.put(value.getDescription(), value.getValue()); + } + return map; + } + + /** + * 获取翻译值 + */ + private DepPathResult getLocaleInfo() { + DepPath build = DepPath.builder().addUrl("centric://REFLECTION/INSTANCE/CompanyInfo/Global") + .addPath("Child:LocaleConfiguration").build(); + return c8NodeService.depPathByUrl(build); + } + + /** + * 获取描述枚举 + */ + private DepPathResult getDescInfo() { + String xml = ""; + DepPath build = DepPath.builderXml().xml(xml).addPath("Child:Values").build(); + return c8NodeService.depPathByXml(build); + } + + + private List setEnumValue(DepPathResult result, String url) { + List list = Lists.newArrayList(); + List values = result.getValue("Values", url).getList(); + for (String v : values) { + EnumValue enumValue = new EnumValue(); + enumValue.setActive(result.getValue("Active", v).getBoolean()); + enumValue.setDependsOn(result.getValue("DependsOn", v).getStr()); + enumValue.setDescription(result.getValue("Description", v).getStr()); + enumValue.setNodeName(result.getValue("$Name", v).getStr()); + enumValue.setValue(result.getValue("Value", v).getStr()); + list.add(enumValue); + } + return list; + } + + + /** + * 通过enum的key和描述值获取fullname + **/ + public String getFullNameByDesc(String prefix, String desc) { + EnumList enumList = cache.get(prefix); + if (enumList == null) { + initByPrefix(prefix);//初始化单个prefix + enumList = cache.get(prefix); + if (enumList == null) return ""; + } + String orDefault = enumList.getDescCache().getOrDefault(desc, ""); + if (StrUtil.isBlank(orDefault)) { + initByPrefix(prefix);//初始化单个prefix + enumList = cache.get(prefix); + orDefault = enumList.getDescCache().getOrDefault(desc, ""); + } + return orDefault; + } + + /** + * 通过fullname获取描述 + **/ + public String getDescByFullname(String fullname) { + String[] split = fullname.split(":", 2); + if (split.length < 2) { + return ""; + } + String prefix = split[0]; + String s = doGetDescByFullname(prefix, fullname); + if (StrUtil.isBlank(s)) { + initByPrefix(prefix);//初始化单个prefix + } + return doGetDescByFullname(prefix, fullname); + } + + private String doGetDescByFullname(String prefix, String fullname) { + EnumList enumList = cache.get(prefix); + if (enumList == null) { + return ""; + } + return enumList.getDescCache().getOrDefault(fullname, ""); + } + + /** + * 初始化单个EnumList + * @param prefix enum前缀 + */ + private void initByPrefix(String prefix) { + DepPathResult descInfo = getDescInfo(prefix); + DepPathResult localeInfo = getLocaleInfo();//翻译枚举 + List resultRefs = descInfo.getCompleteResultRefs(); + for (String url : resultRefs) { + doInit(descInfo, localeInfo, url); + } + } + + /** + * 获取描述枚举 + */ + private DepPathResult getDescInfo(String prefix) { + String xml = ""; + DepPath build = DepPath.builderXml().xml(xml).addPath("Child:Values").build(); + return c8NodeService.depPathByXml(build); + } + + + /** + * 通过enum的key和显示名获取fullname + **/ + public String getFullnameByDisplay(String prefix, String display) { + String s = doGetFullnameByDisplay(prefix, display); + if (StrUtil.isBlank(s)) { + initByPrefix(prefix);//初始化单个prefix + } + return doGetFullnameByDisplay(prefix, display); + } + + private String doGetFullnameByDisplay(String prefix, String display) { + EnumList enumList = cache.get(prefix); + if (enumList == null) { + return ""; + } + String orDefault = enumList.getDisplayZhCache().getOrDefault(display, ""); + if (StrUtil.isBlank(orDefault)) orDefault = enumList.getDisplayEnCache().getOrDefault(display, ""); + return orDefault; + } + + + /** + * 通过fullname获取显示名 + **/ + public String getDisplayByFullname(String fullname) { + String[] split = fullname.split(":", 2); + if (split.length < 2) { + return ""; + } + String prefix = split[0]; + String s = doGetDisplayByFullname(prefix, fullname); + if (StrUtil.isBlank(s)) { + initByPrefix(prefix);//初始化单个prefix + } + return doGetDisplayByFullname(prefix, fullname); + } + + private String doGetDisplayByFullname(String prefix, String fullname) { + EnumList enumList = cache.get(prefix); + if (enumList == null) { + return ""; + } + String orDefault = enumList.getDisplayZhCache().getOrDefault(fullname, ""); + if (StrUtil.isBlank(orDefault)) orDefault = enumList.getDisplayEnCache().getOrDefault(fullname, ""); + return orDefault; + } + + /** + * 通过prefix获取enumList + * @param prefix enum前缀 + * @return + */ + public EnumList getEnumListByPrefix(String prefix) { + return cache.get(prefix); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResult.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResult.java new file mode 100644 index 0000000..718df31 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResult.java @@ -0,0 +1,41 @@ +package com.centricsoftware.enhancement.modules.c8.component.dep; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import lombok.Data; + +/** + * description:查询C8的参数和返回值 + * CentricResultForFeign 经过封装后的实体类 + * Date: 2024/1/31 14:19 + */ +@Data +public class CentricResult { + + /** + * Feign返回的实体类 + */ + private CentricResultForFeign centricResultForFeign; + + /** + * 原始DepPath + */ + private DepPath depPath; + + /** + * DepPath by URL 可以通过resultNodes获取原始的urls的Nodes + */ + private JSONArray resultNodes; + + /** + * DepPath中addPath加载的Nodes + */ + private JSONArray pathNodes; + + /** + * DepPath by XML 查询结果集的Nodes + */ + private JSONObject completeResultRefs; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultCache.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultCache.java new file mode 100644 index 0000000..9e55613 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultCache.java @@ -0,0 +1,9 @@ +package com.centricsoftware.enhancement.modules.c8.component.dep; + +/** + * description:NodeService查询缓存 + * Date: 2024/2/1 9:53 + */ +public class CentricResultCache { + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultForFeign.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultForFeign.java new file mode 100644 index 0000000..5964b0b --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/CentricResultForFeign.java @@ -0,0 +1,40 @@ +package com.centricsoftware.enhancement.modules.c8.component.dep; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * description:Feign直接返回的实体类 + * Date: 2024/1/31 16:09 + */ +public class CentricResultForFeign { + + @JsonProperty("Status") + private String status; + + /** + * {Node": [{}]} + */ + @JsonProperty("NODES") + private Object nodes; + + /** + * {Modified": "C159609"} + */ + @JsonProperty("URLS") + private Object urls; + + @JsonProperty("ErrorAdmin") + private String errorAdmin; + + @JsonProperty("Error") + private String error; + + /** + * 发布文件时,需要用publishFile,获取里面的URL + */ + @JsonProperty("FileInfo") + private Object fileInfo; + + @JsonProperty("DBQuery") + private Object dbQuery; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathCache.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathCache.java new file mode 100644 index 0000000..d29571e --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathCache.java @@ -0,0 +1,33 @@ +package com.centricsoftware.enhancement.modules.c8.component.dep; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.text.StrFormatter; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; + +/** + * description: + * Date: 2024/2/1 9:52 + */ +public class DepPathCache { + + TimedCache timedCache = CacheUtil.newTimedCache(DateUnit.MINUTE.getMillis() * 10); + + final String format = "{}::{}"; + + public DepPathResultValue getCache(String exp,String url){ + return timedCache.get(getKey(exp, url)); + } + + public void setCache(String exp,String url,DepPathResultValue value){ + timedCache.put(getKey(exp, url),value); + } + + /** + * 获取缓存key + */ + private String getKey(String exp,String url){ + return StrFormatter.format(this.format,url,exp); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathParser.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathParser.java new file mode 100644 index 0000000..8601701 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathParser.java @@ -0,0 +1,445 @@ +package com.centricsoftware.enhancement.modules.c8.component.dep; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.modules.c8.dto.dep.CentricAttrs; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathExp; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.ParserFieldType; +import com.centricsoftware.enhancement.modules.c8.util.CentricAttributesUtil; +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * description: + * Date: 2024/1/31 14:09 + */ +@Data +public class DepPathParser { + + /** + * 表达式分隔标识 + */ + private char SPLIT_CHAR = CharUtil.DOT; + + + /** + * 解析表达式,并且封装成DepPathResultValue返回值 + * @param exp 表达式 + * @param url URL + * @param allNodes DepPath查询返回的所有nodes集合 + * @return + */ + public DepPathResultValue getValueByAnalysisExpression(String exp, String url,HashMap allNodes){ + if(MapUtil.isEmpty(allNodes)){ + /** + * 查询结果为空,返回DepPathResultValue实例 + */ + return new DepPathResultValue(); + } + check(exp);//校验表达式是否合法 + /** + * 解析表达式: + * 1、将表达式拆分为多个子表达式实体类; + * 2、处理表达式中的角标问题 + * 3、维护子表达式的前后依赖关系 + */ + List expList = analysisExpression(exp,url); + /** + * 获取DepPathResultValue: + * 1、完善子表达式实体类的字段类型、子表达式的值 + */ + return getValueFromExp(expList,allNodes); + } + + + /** + * 获取DepPathResultValue: + * 1、完善子表达式实体类的字段类型、子表达式的值 + */ + private DepPathResultValue getValueFromExp(List expList, HashMap allNodes) { + DepPathResultValue value = new DepPathResultValue(); + if(expList.size()==0){ + return value; + } + for(int i=0;i allNodes) { + /** + * attrs封装 + */ + CentricAttrs attrs = new CentricAttrs(); + attrs.setType("ref"); + /** + * 前序信息封装 + */ + String url = currentExp.getUrl(); + DepPathExp preExp = DepPathExp.builder().url(url).value(url).fullExp(currentExp.getFullExp()).attrs(attrs).type(ParserFieldType.REF).build(); + currentExp.setBeforeExp(preExp); + currentExp.setBeforeValue(url); + preExp.setAfterExp(currentExp); + } + + + /** + * 解析表达式:通过分隔符将表达式拆分,并且封装成List + * 1、将表达式拆分为多个子表达式实体类; + * 2、处理表达式中的角标问题 + * 3、维护子表达式的前后依赖关系 + * @param exp 表达式 + * @param url URL + * @return 子表达式List + */ + private List analysisExpression(String exp,String url){ + List expList = Lists.newArrayList(); + List split = StrUtil.split(exp, SPLIT_CHAR); + for(int i=0;iindexInt){ + return jsonArray.get(indexInt); + } + return jsonArray; + } + return currentJSONObject.getStr(subExp); + } + + /** + * 设置当前表达式实体类信息:字段类型与flag;当前value + */ + private CentricAttrs setCurrentDepPathExpInfo(DepPathExp currentExp,JSONObject currentJSONObject){ + if(currentExp.getAttrs()!=null){ + return currentExp.getAttrs(); + } + if(currentJSONObject ==null){ + return null; + } + JSONObject $attrs = currentJSONObject.getJSONObject("$attrs"); + JSONObject jsonObject = $attrs.getJSONObject(currentExp.getExp()); + CentricAttrs centricAttrs = Convert.convert(CentricAttrs.class, jsonObject); + currentExp.setAttrs(centricAttrs); + return centricAttrs; + } + + /** + * 按照子表达式获取值 + */ + private void doGetValueFromExp(DepPathExp currentExp,HashMap allNodes) { + parseDepPathForMap(currentExp,allNodes);//处理preValue为map + parseDepPathForList(currentExp,allNodes);//处理preValue为list + parseDepPathForRef(currentExp,allNodes);//处理preValue为ref + } + + /** + * 当前序值为map: + * 1、遍历前序map的所有value,取出对应的node + * 2、根据子表达式在node中取出对应的值和字段类型 + * 3、根据字段类型,确认返回值类型:如:ref-》list;map-》map;list-》list + */ + private void parseDepPathForMap(DepPathExp currentExp, HashMap allNodes){ + DepPathExp beforeExp = currentExp.getBeforeExp(); + ParserFieldType beforeType = beforeExp.getType(); + if(beforeType!=ParserFieldType.MAP){ + return; + } + Object beforeValue = currentExp.getBeforeValue(); + /* + 修复exp路径中如果有【centric:】会导致结果报错或者为{} + */ + if(beforeValue==null|| ObjectUtil.isEmpty(beforeValue)){ + return; + } + JSONObject beforeJSONObject = Convert.convert(JSONObject.class, beforeValue); + Object values = null; + for (Map.Entry entry : beforeJSONObject.entrySet()) { + Object v = entry.getValue(); + JSONObject jsonObject = allNodes.get(v); + if (jsonObject != null) { + Object o = getCurrentValue(jsonObject, currentExp); + if(o==null){ + continue; + } + values = doParseDepPathForListAndMap(values,o,currentExp); + } + } + /** + * 1、封装当前节点的值; + * 2、重置当前节点的类型 + * 3、赋值:【前序节点】的【后续节点】值 + */ + doParseDepPath(values,currentExp); + } + + + /** + * 前序类型为List, + * 1、前序为List,当前为List,则结果为List:当前的所有list聚合 + * 2、前序为List,当前为Map,则结果为List:当前的所有map的value聚合 + * 3、前序为List,当前为其他,则结果为List:当前值聚合 + */ + private void parseDepPathForList(DepPathExp currentExp, HashMap allNodes) { + DepPathExp beforeExp = currentExp.getBeforeExp(); + ParserFieldType beforeType = beforeExp.getType(); + if (beforeType != ParserFieldType.LIST) { + return; + } + Object beforeValue = currentExp.getBeforeValue(); + /* + 修复exp路径中如果有【centric:】会导致结果报错或者为{} + */ + if(beforeValue==null|| ObjectUtil.isEmpty(beforeValue)){ + return; + } + List list = JSONUtil.parseArray(beforeValue).toList(String.class); + Object values = null; + for (String url : list) { + JSONObject jsonObject = allNodes.get(url); + if (jsonObject != null) { + Object o = getCurrentValue(jsonObject, currentExp); + values = doParseDepPathForListAndMap(values,o,currentExp); + } + } + /** + * 1、封装当前节点的值; + * 2、重置当前节点的类型 + * 3、赋值:【前序节点】的【后续节点】值 + */ + doParseDepPath(values,currentExp); + } + + private Object doParseDepPathForListAndMap(Object values1,Object o, DepPathExp currentExp){ + Object values = values1; + boolean currentTypeIsMap = CentricAttributesUtil.isMap(currentExp);//getCurrentValue后才能获取类型 + boolean currentTypeIsList = CentricAttributesUtil.isList(currentExp);//getCurrentValue后才能获取类型 + if(o ==null){ + return values; + } + if(currentTypeIsList){ + JSONArray m = Convert.convert(JSONArray.class, o); + if(values==null){ + values = new JSONArray(); + } + ((JSONArray)values).addAll(m); + } else if (currentTypeIsMap) { + JSONObject m = Convert.convert(JSONObject.class, o); + if(values==null){ + values = new JSONObject(); + } + ((JSONObject)values).putAll(m); + }else{ + if(values==null){ + values = new JSONArray(); + } + ((JSONArray)values).add(o); + } + return values; + } + + + private void parseDepPathForRef(DepPathExp currentExp, HashMap allNodes){ + DepPathExp beforeExp = currentExp.getBeforeExp(); + ParserFieldType beforeType = beforeExp.getType(); + if(beforeType!=ParserFieldType.REF){ + return; + } + DepPathExp afterExp = currentExp.getAfterExp(); + /* + 修复exp路径中如果有【centric:】会导致结果报错或者为{} + */ + Object beforeValueObj = currentExp.getBeforeValue(); + /* + 修复exp路径中如果有【centric:】会导致结果报错或者为{} + */ + if(beforeValueObj==null|| ObjectUtil.isEmpty(beforeValueObj)){ + return; + } + String beforeValue = (String) beforeValueObj; + JSONObject jsonObject = allNodes.get(beforeValue); + Object values = getCurrentValue(jsonObject, currentExp); + String currentType = "ref"; + CentricAttrs attrs = currentExp.getAttrs(); + if(attrs!=null){ + attrs = currentExp.getAttrs(); + currentType = attrs.getType(); + } + beforeExp.setAfterValue(values); + if(afterExp!=null){ + afterExp.setBeforeValue(values); + } + currentExp.setValue(values); + currentExp.setBeforeValue(beforeExp.getValue()); + String exp = currentExp.getExp(); + if (currentExp.getType() == ParserFieldType.INDEX||"$CR".equals(exp)) { + currentExp.setType(ParserFieldType.REF); + } else { + currentExp.setType(CentricAttributesUtil.getParserFieldType(currentType)); + } + } + + + /** + * 1、封装当前节点的值; + * 2、重置当前节点的类型 + * 3、赋值:【前序节点】的【后续节点】值 + */ + private void doParseDepPath(Object values,DepPathExp currentExp){ + ParserFieldType type = currentExp.getType(); + DepPathExp afterExp = currentExp.getAfterExp(); + DepPathExp beforeExp = currentExp.getBeforeExp(); + currentExp.setValue(values); + currentExp.setBeforeValue(beforeExp.getValue()); + beforeExp.setAfterValue(values); + CentricAttrs attrs = currentExp.getAttrs(); + if(attrs==null){ + return; + } + String currentType = attrs.getType(); + if(afterExp!=null){ + afterExp.setBeforeValue(values); + } + if(values==null){ + return; + } + if(values instanceof List){ + currentExp.setType(type ==ParserFieldType.INDEX?ParserFieldType.INDEX_LIST:ParserFieldType.LIST); + return; + } + if(values instanceof Map){ + currentExp.setType(type ==ParserFieldType.INDEX?ParserFieldType.INDEX_MAP:ParserFieldType.MAP); + return; + } + String exp = currentExp.getExp(); + if("$CR".equals(exp)||CentricAttributesUtil.isRef(currentType)){ + currentExp.setType(ParserFieldType.REF); + } + currentExp.setType(ParserFieldType.OTHER); + } + + + /** + * 校验表达式、allNodes是否合法 + */ + private void check(String exp) { + if(StrUtil.isBlankOrUndefined(exp)){ + new BaseException(ResCode.ERROR,"DepPath查询的表达式不能为空!"); + } + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathResult.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathResult.java new file mode 100644 index 0000000..69accb8 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/dep/DepPathResult.java @@ -0,0 +1,187 @@ +package com.centricsoftware.enhancement.modules.c8.component.dep; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathType; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * description:DepPath查询返回类 + * Date: 2024/1/31 14:16 + */ +@Slf4j +public class DepPathResult { + + @Getter + private HashMap urlNodes; //结果集对应的map + @Getter + private HashMap pathNodes;//path集对应的map + @Getter + private HashMap allNodes = new HashMap<>();//结果集+path集对应的map + @Getter + private DepPath depPath; + @Getter + private CentricResult centricResult; + + /** + * DepPath.ByXML可以通过completeResultRefs获取查询结果集的URL + * DepPath.ByURL可以通过completeResultRefs获取DepPath的addUrls的所有url + */ + @Getter + private List completeResultRefs; + + + /** + * DepPath.ByXML可以通过completeResultRefs获取查询结果集的Node + * DepPath.ByURL可以通过completeResultRefs获取DepPath的addUrls的所有Node + */ + @Getter + private JSONArray resultNodeJSONArray; + + /** + * 解析器 + */ + @Getter + DepPathContext depPathContext; + + + /** + * DepPath的XML和URL的结果集 + */ + @Getter + private JSONArray nodes;// 结果集 + + + public DepPathResult(){ + + } + + /** + * @param depPathContext 上下文参数,包含解析器组件、缓存组件 + */ + public DepPathResult(CentricResult centricResult,DepPathContext depPathContext){ + init(centricResult,depPathContext); + } + + public DepPathResultValue getValue(String exp,String url){ + DepPathResultValue value = getFromCache(exp, url); + if(value!=null){ + return value; + } + value = getValueByAnalysisExpression(exp,url); + setCache(exp,url,value); + return value; + } + + /** + * 解析表达式 + */ + private DepPathResultValue getValueByAnalysisExpression(String exp,String url){ + DepPathParser depPathParser = depPathContext.getDepPathParser(); + if(depPathParser==null){ + new BaseException(ResCode.CONFIG_NOT_INIT,"配置未初始化:找不到DepPathParser组件!"); + } + DepPathResultValue depPathResultValue = depPathParser.getValueByAnalysisExpression(exp, url, allNodes); + return depPathResultValue; + } + + /** + * 从缓存组件中获取数据 + */ + private DepPathResultValue getFromCache(String exp,String url){ + DepPathCache depPathCache = depPathContext.getDepPathCache(); + if(depPathCache!=null){ + DepPathResultValue cache = depPathCache.getCache(exp, url); + if(cache!=null){ + log.debug("DepPath查询,从缓存组件中返回值:exp={},url={},value={}",exp,url,cache.getValue()); + return cache; + } + } + return null; + } + + /** + * 从缓存组件中获取数据 + */ + private void setCache(String exp,String url,DepPathResultValue value){ + DepPathCache depPathCache = depPathContext.getDepPathCache(); + if(depPathCache!=null){ + depPathCache.setCache(exp,url,value); + } + } + + /** + * 初始化 + * @param depPathContext 解析器 + */ + public DepPathResult init(CentricResult centricResult,DepPathContext depPathContext){ + this.depPath = centricResult.getDepPath(); + this.centricResult = centricResult; + this.nodes = centricResult.getResultNodes(); + this.resultNodeJSONArray = centricResult.getResultNodes(); + this.pathNodes = initNodeMap(centricResult.getPathNodes()); + this.urlNodes = initNodeMap(centricResult.getResultNodes()); + this.depPathContext = depPathContext; + initCompleteResultRefs();//初始化所有的URL + return this; + } + + /** + * 初始化DepPath.ByXML的completeResultRefs + */ + private void initCompleteResultRefs() { + DepPathType type = depPath.getType(); + if(type==DepPathType.URL){ + this.completeResultRefs = new ArrayList(depPath.getUrls()); + }else{ + JSONObject completeResult = centricResult.getCompleteResultRefs(); + if (completeResult == null) { + return; + } + this.completeResultRefs = completeResult.get("ref", List.class); + } + } + + /** + * 初始化pathNodes、urlNodes、nodes + */ + private HashMap initNodeMap(JSONArray array){ + if (array == null) { + return new HashMap(); + } + HashMap map = new HashMap(getMapInitialCapacity(array.size())); + array.forEach(i -> { + JSONObject obj = JSONUtil.parseObj(i); + map.put(obj.getStr("$URL"), obj); + }); + this.allNodes.putAll(map); + return map; + } + + + /** + * 获取Map的初始化容量,如果小于16,则按照默认值16;否则初始容量= 长度/0.75+1;向上取整 + * @param length + * @return + */ + private int getMapInitialCapacity(int length){ + int initialCapacity = NumberUtil.round(length/0.75,0).intValue() + 1;//初始化容量因子,提高性能 + if(initialCapacity<16){ + initialCapacity = 16; + } + return initialCapacity; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/IDepPathSearchChain.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/IDepPathSearchChain.java new file mode 100644 index 0000000..2b2f017 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/IDepPathSearchChain.java @@ -0,0 +1,150 @@ +package com.centricsoftware.enhancement.modules.c8.component.design.chain; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.google.common.collect.Lists; +import org.apache.ibatis.reflection.DefaultReflectorFactory; +import org.apache.ibatis.reflection.Reflector; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public interface IDepPathSearchChain { + + + List execute(DepPath depPath, Class clazz, Consumer function); + + List execute(DepPath depPath, List urls, Class clazz, Consumer function); + + List execute(DepPathResult result, Class clazz); + + List execute(DepPathResult result, List urls, Class clazz, Consumer function); + + List execute(DepPathResult result,Class clazz, Consumer function); + + default Reflector initDepPathByEntityContext(DepPathByEntityContext context,DepPathResult result,Class clazz){ + DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory(); + Reflector reflector = defaultReflectorFactory.findForClass(clazz); + context.setReflector(reflector);//反射器 + context.setDepPathResult(result); + context.setClazz(clazz); + context.setResultUrls(result.getCompleteResultRefs()); + return reflector; + } + + default List getResultUrls(DepPathByEntityContext context,List urls){ + if(urls!=null){ + return urls; + } + DepPathResult depPathResult = context.getDepPathResult(); + List resultRefs = depPathResult.getCompleteResultRefs(); + return resultRefs; + } + + default List getPaths(Class clazz){ + List paths = getPaths(null, clazz); + List collect = new ArrayList<>(); + for (String i : paths) { + if (!i.contains(StrUtil.DOT)) { + continue; + } + if (StrUtil.isBlankOrUndefined(i)) { + continue; + } + String[] split = StrUtil.split(i,StrUtil.DOT); + if(split.length==1){//表达式的长度等于1,则不需要path + continue; + } + //表达式的长度大于1,则只需要length-1的path + String[] sub = ArrayUtil.sub(split, 0, split.length - 1); + String finalPath = StrUtil.join(StrUtil.DOT,sub); + String format = StrFormatter.format("Child:{}", StrUtil.replace(finalPath, StrUtil.DOT, "/Child:")); + collect.add(format); + } + return collect; + } + /** + * 通过class获取DepPath的paths + */ + default List getPaths(String prePath,Class clazz){ + Field[] fieldArray = ReflectUtil.getFields(clazz); + List fields = Arrays.stream(fieldArray).filter(i->i.getAnnotation(DepPathField.class)!=null).collect(Collectors.toList()); + ArrayList allPath = Lists.newArrayList(); + /** + * 获取当前对象的path + */ + allPath.addAll(getCurrentPaths(prePath,fields)); + /** + * 获取处理递对象的path + */ + allPath.addAll(getRecursionPaths(prePath,fields)); + return allPath; + } + + /** + * 处理递归字段 + */ + default List getRecursionPaths(String prePath,List fields){ + List expList = new ArrayList<>(); + for (Field field : fields) { + DepPathField depPathField = field.getAnnotation(DepPathField.class); + if (depPathField.recursion()) { + Class genericClazz = getGenericClazz(field); + String allPre = StrUtil.isBlankOrUndefined(prePath)?"":prePath+StrUtil.DOT; + String format = StrFormatter.format("{}{}", allPre, depPathField.exp()); + List paths = getPaths(format, genericClazz); + expList.addAll(paths); + } + } + return expList; + } + + /** + * 处理当前字段 + */ + default List getCurrentPaths(String prePath,List fields){ + String pre = StrUtil.isBlankOrUndefined(prePath)?"":prePath; + if(StrUtil.isNotBlank(pre)&&!pre.endsWith(StrUtil.DOT)){ + pre += StrUtil.DOT; + } + String finalPre = pre; + List paths= fields.stream() + .filter(i ->!i.getAnnotation(DepPathField.class).recursion()) + .map(i-> StrFormatter.format("{}{}",finalPre,i.getAnnotation(DepPathField.class).exp())) + .distinct() + .collect(Collectors.toList()); + return paths; + } + + + /** + * 获取Field的类,基础类型则返回null + */ + default Class getGenericClazz(Field field){ + Class fieldClazz = field.getType();//获取f的类 + if(fieldClazz.isPrimitive()) return null; //判断是否为基本类型 + if(fieldClazz.isAssignableFrom(List.class)){//判断fc是否和List相同或者其父类 + Type fc = field.getGenericType(); //如果是List类型,得到其Generic的类型 + if(fc instanceof ParameterizedType){ + ParameterizedType pt = (ParameterizedType) fc; + Class genericClazz = (Class)pt.getActualTypeArguments()[0]; + return genericClazz; + } + } + return fieldClazz; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/impl/DefaultDepPathSearchChainImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/impl/DefaultDepPathSearchChainImpl.java new file mode 100644 index 0000000..8a71b27 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/design/chain/impl/DefaultDepPathSearchChainImpl.java @@ -0,0 +1,173 @@ +package com.centricsoftware.enhancement.modules.c8.component.design.chain.impl; + +import cn.hutool.core.util.ReflectUtil; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.component.design.chain.IDepPathSearchChain; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.em.DepPathType; +import com.centricsoftware.enhancement.modules.c8.service.curd.C8SearchService; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.Reflector; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * description: + * Date: 2024/3/14 17:29 + */ +//@ConditionalOnMissingBean(IDepPathSearchChain.class) +@Component +@Slf4j +public class DefaultDepPathSearchChainImpl implements IDepPathSearchChain { + + @Resource + private List handleList; + @Resource + C8SearchService c8SearchService; + + @Override + public List execute(DepPath depPath,Class clazz, Consumer function) { + return execute(depPath,null,clazz,function); + } + + @Override + public List execute(DepPath depPath, List urls,Class clazz, Consumer function) { + depPath.setPaths(new TreeSet<>(getPaths(clazz))); + DepPathType type = depPath.getType(); + DepPathResult depPathResult; + if(type==DepPathType.XML){ + depPathResult = c8SearchService.depPathByXml(depPath); + }else{ + depPathResult = c8SearchService.depPathByUrl(depPath); + } + return execute(depPathResult,urls,clazz,function); + } + + @Override + + public List execute(DepPathResult result,Class clazz) { + return execute(result,clazz,null); + } + + @Override + public List execute(DepPathResult result, Class clazz, Consumer function) { + return execute(result,null,clazz,null); + } + + @Override + public List execute(DepPathResult result, List urls ,Class clazz,Consumer function){ + DepPathByEntityContext context = new DepPathByEntityContext(); + Reflector reflector = initDepPathByEntityContext(context, result, clazz); + List resultUrls = getResultUrls(context, urls); + List list = new ArrayList<>(); + Field[] fields = ReflectUtil.getFields(clazz); + List annotationFields = Arrays.stream(fields).filter(i -> i.getAnnotation(DepPathField.class) != null).collect(Collectors.toList()); + for(String url : resultUrls){ + T o = null; + try { + o = (T) reflector.getDefaultConstructor().newInstance(); + context.setUrl(url); + context.setInstance(o); + for(Field field : annotationFields){ + executeField(context,field,function); + } + if(function!=null){ + function.accept(context); + } + list.add(o); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e ) { + printError(context,e,clazz,url); + throw new RuntimeException(e); + }catch (Exception e){ + printError(context,e,clazz,url); + log.error("错误信息:getMessage={},getLocalizedMessage={}",e.getMessage(),e.getLocalizedMessage()); + throw new RuntimeException(e); + } + } + return list; + } + + /** + * 按字段映射赋值 + */ + private void executeField(DepPathByEntityContext context,Field field,Consumer function) throws InvocationTargetException, IllegalAccessException{ + DepPathField annotation = field.getAnnotation(DepPathField.class); + Reflector reflector = context.getReflector(); + String fieldName = field.getName(); + context.setDepPathField(annotation); + context.setFieldName(fieldName); + context.setField(field); + context.setInvoker(reflector.getSetInvoker(fieldName)); + /** + * 递归查询 + */ + if(annotation.recursion()){ + //递归查询 + executeRecursion(context,function); + return; + } + /** + * 非递归 + */ + for (IDepPathEntitySearchService handleIntercept : handleList) { + if(!handleIntercept.isHandle(context)){ + continue; + } + handleIntercept.process(context); + if(!handleIntercept.isContinue(context)){ + //是否继续执行责任链 + break; + } + } + } + + /** + * 递归查询 + */ + private void executeRecursion(DepPathByEntityContext context,Consumer function) throws InvocationTargetException, IllegalAccessException { + Field field = context.getField(); + DepPathResult depPathResult = context.getDepPathResult(); + Class type = field.getType(); + Class clazz = getGenericClazz(field); + if(clazz==null){ + return; + } + List urls = getFieldListValues(context); + List result = execute(depPathResult, urls,clazz, function); + if(type == List.class){ + Object[] valueArray = new Object[]{result}; + context.getInvoker().invoke(context.getInstance(),valueArray); + }else if(result.size()>0){ + Object[] valueArray = new Object[]{result.get(0)}; + context.getInvoker().invoke(context.getInstance(),valueArray); + } + } + + /** + * 获取Filed的List值,递归状态使用 + */ + private List getFieldListValues(DepPathByEntityContext context){ + DepPathResult depPathResult = context.getDepPathResult(); + DepPathField depPathField = context.getDepPathField(); + String exp = depPathField.exp(); + String url = context.getUrl(); + return depPathResult.getValue(exp, url).getList(depPathField.removeDuplicate(), String.class); + } + + private void printError(DepPathByEntityContext context,Exception e,Class clazz,String url){ + log.error("映射生成实体类报错:实例类名称={},url={},字段名={};cause by:{}",clazz.getName(), url, context.getFieldName(), e.getMessage()); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/ICentricAbstractFactory.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/ICentricAbstractFactory.java new file mode 100644 index 0000000..3dde44c --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/ICentricAbstractFactory.java @@ -0,0 +1,15 @@ +package com.centricsoftware.enhancement.modules.c8.component. + factory; + +/** + * description: + * Date: 2024/1/29 10:50 + */ +public interface ICentricAbstractFactory { + + IDepPathFactory getDepPathFactory(); + + INodeServiceFactory getNodeServiceFactory(); + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/IDepPathFactory.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/IDepPathFactory.java new file mode 100644 index 0000000..d0a8738 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/IDepPathFactory.java @@ -0,0 +1,14 @@ +package com.centricsoftware.enhancement.modules.c8.component.factory; + +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathCache; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathParser; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathContext; + +public interface IDepPathFactory { + + DepPathParser getDepPathParser(DepPathContext context); + + DepPathCache getDepPathCache(DepPathContext context); + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/INodeServiceFactory.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/INodeServiceFactory.java new file mode 100644 index 0000000..246b838 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/INodeServiceFactory.java @@ -0,0 +1,12 @@ +package com.centricsoftware.enhancement.modules.c8.component.factory; + +import com.centricsoftware.enhancement.modules.c8.component.parser.ICentricResult; +import com.centricsoftware.enhancement.modules.c8.component.parser.ICentricResultCache; + +public interface INodeServiceFactory { + + ICentricResult getCentricResultFactory(); + + ICentricResultCache getCentricResultCache(); + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultDepPathFactory.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultDepPathFactory.java new file mode 100644 index 0000000..8c1377b --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultDepPathFactory.java @@ -0,0 +1,34 @@ +package com.centricsoftware.enhancement.modules.c8.component.factory.impl; + +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathCache; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathParser; +import com.centricsoftware.enhancement.modules.c8.component.factory.IDepPathFactory; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathContext; +import org.springframework.stereotype.Component; + +/** + * description: + * Date: 2024/1/29 11:05 + */ +@Component +public class DefaultDepPathFactory implements IDepPathFactory { + + @Override + public DepPathParser getDepPathParser(DepPathContext context) { + DepPathParser depPathParser = new DepPathParser(); + if(context!=null){ + context.setDepPathParser(depPathParser); + } + return depPathParser; + } + + @Override + public DepPathCache getDepPathCache(DepPathContext context) { + DepPathCache depPathCache = new DepPathCache(); + if(context!=null){ + context.setDepPathCache(depPathCache); + } + return depPathCache; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultICentricAbstractFactory.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultICentricAbstractFactory.java new file mode 100644 index 0000000..cff6ba1 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultICentricAbstractFactory.java @@ -0,0 +1,38 @@ +package com.centricsoftware.enhancement.modules.c8.component.factory.impl; + +import com.centricsoftware.enhancement.modules.c8.component.factory.ICentricAbstractFactory; +import com.centricsoftware.enhancement.modules.c8.component.factory.IDepPathFactory; +import com.centricsoftware.enhancement.modules.c8.component.factory.INodeServiceFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * description:生成DefaultDepPathFactory、DefaultNodeServiceFactory等工厂的超级工厂类 + * 1、可支持其他实现方式,只要实现CentricAbstractFactory接口,并且交给Spring容器管理即可 + * 2、容器中不允许存在多个CentricAbstractFactory的实现类,当配置了其他实现时,DefaultCentricAbstractFactory就会失效 + * 3、当自定义了多个CentricAbstractFactory的实现,将会报错 + * + * Date: 2024/1/31 13:15 + */ +@Component +public class DefaultICentricAbstractFactory implements ICentricAbstractFactory { + + @Resource + IDepPathFactory depPathFactory; + + @Resource + INodeServiceFactory nodeServiceFactory; + + + @Override + public IDepPathFactory getDepPathFactory() { + return depPathFactory; + } + + @Override + public INodeServiceFactory getNodeServiceFactory() { + return nodeServiceFactory; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultINodeServiceFactory.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultINodeServiceFactory.java new file mode 100644 index 0000000..7bfdc52 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/factory/impl/DefaultINodeServiceFactory.java @@ -0,0 +1,26 @@ +package com.centricsoftware.enhancement.modules.c8.component.factory.impl; + +import com.centricsoftware.enhancement.modules.c8.component.factory.INodeServiceFactory; +import com.centricsoftware.enhancement.modules.c8.component.parser.ICentricResult; +import com.centricsoftware.enhancement.modules.c8.component.parser.ICentricResultCache; +import org.springframework.stereotype.Component; + +/** + * description: + * Date: 2024/1/29 11:06 + */ +@Component +public class DefaultINodeServiceFactory implements INodeServiceFactory { + + @Override + public ICentricResult getCentricResultFactory() { + return null; + } + + @Override + public ICentricResultCache getCentricResultCache() { + return null; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResult.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResult.java new file mode 100644 index 0000000..6c4e46b --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResult.java @@ -0,0 +1,19 @@ +package com.centricsoftware.enhancement.modules.c8.component.parser; + +import com.centricsoftware.enhancement.modules.c8.component.dep.CentricResultForFeign; + +/** + * 返回的结果集: + * 1、DepPath返回的Result + * 2、C8返回的通用结果集 + * @author Xulin.Xie 2024-1-31 + */ +public interface ICentricResult { + + /** + * 获取Feign返回的实体类,如果需要重写CentricResultForFeign,可重新实现这个接口,并且继承CentricResultForFeign + */ + CentricResultForFeign getCentricResultForFeign(); + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResultCache.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResultCache.java new file mode 100644 index 0000000..e590883 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/ICentricResultCache.java @@ -0,0 +1,16 @@ +package com.centricsoftware.enhancement.modules.c8.component.parser; + + +import com.centricsoftware.enhancement.modules.c8.component.dep.CentricResultCache; + +/** + * 缓存器: + * 1、通过NodeService到C8查询时,执行缓存,避免每次都取C8查询:考虑到场景的通用性,默认的缓存器不支持全局缓存(跨线程) + * @author Xulin.Xie 2024-1-31 + */ +public interface ICentricResultCache { + + CentricResultCache getCentricResultCache(); + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/CentricResultCacheImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/CentricResultCacheImpl.java new file mode 100644 index 0000000..82192f4 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/CentricResultCacheImpl.java @@ -0,0 +1,20 @@ +package com.centricsoftware.enhancement.modules.c8.component.parser.impl; + +import com.centricsoftware.enhancement.modules.c8.component.dep.CentricResultCache; +import com.centricsoftware.enhancement.modules.c8.component.parser.ICentricResultCache; +import org.springframework.stereotype.Component; + +/** + * description: + * Date: 2024/2/1 9:51 + */ +@Component +public class CentricResultCacheImpl implements ICentricResultCache { + + @Override + public CentricResultCache getCentricResultCache() { + CentricResultCache centricResultCache = new CentricResultCache(); + return centricResultCache; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/DefaultICentricResultImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/DefaultICentricResultImpl.java new file mode 100644 index 0000000..cc04e73 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/component/parser/impl/DefaultICentricResultImpl.java @@ -0,0 +1,22 @@ +package com.centricsoftware.enhancement.modules.c8.component.parser.impl; + +import com.centricsoftware.enhancement.modules.c8.component.dep.CentricResultForFeign; +import com.centricsoftware.enhancement.modules.c8.component.parser.ICentricResult; +import org.springframework.stereotype.Component; + +/** + * 返回的结果集: + * 1、C8返回的通用结果集 + * 可重新实现该类 + * @author Xulin.Xie 2024-1-31 + */ +@Component +public class DefaultICentricResultImpl implements ICentricResult { + + @Override + public CentricResultForFeign getCentricResultForFeign(){ + CentricResultForFeign centricResultForFeign = new CentricResultForFeign(); + return centricResultForFeign; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/FeignConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/FeignConfig.java new file mode 100644 index 0000000..3a06052 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/FeignConfig.java @@ -0,0 +1,129 @@ +package com.centricsoftware.enhancement.modules.c8.config; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.commons.utils.SpringUtil; +import com.centricsoftware.config.cons.Constants; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.modules.c8.config.okhttp.CookieJarHandler; +import com.centricsoftware.enhancement.dto.cache.ThreadLocalCache; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.modules.c8.service.C8LoginService; +import com.centricsoftware.enhancement.service.log.LogService; +import com.centricsoftware.enhancement.service.log.impl.LogServiceImpl; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; + +@Configuration +@Slf4j +public class FeignConfig { + + @Bean + public OkHttpClient okHttpClient() { + return new OkHttpClient.Builder().cookieJar(getCookieJar()).addInterceptor(getInterceptor()).build(); + } + + /** + * 添加Cookie + * 后续需要根据条件设置是否添加Cookie + * + * @return + */ + @Bean + public CookieJar getCookieJar() { + return new CookieJarHandler(); + } + + @Bean + public Interceptor getInterceptor() { + return new FeignOkHttpClientResponseInterceptor(); + } + + /** + * okHttp响应拦截器 + * 此拦截器用于C8交互时,判断Session是否过期,如果过期,则自动登录 + */ + public class FeignOkHttpClientResponseInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + TimeInterval timer = DateUtil.timer(); + Request originalRequest = chain.request(); + //封装处理请求日志 + FeignLogDto feignLogDto = startProcessLog(originalRequest); + //设置cookie + originalRequest = setCookie(originalRequest); + //执行 + Response response = chain.proceed(originalRequest);//执行请求,获取返回值 + //校验C8请求是否需要重新登录,如果登录超时,则会重新登录并且重试 + response = checkSession(chain,originalRequest,response); + // + endProcessLog(feignLogDto,response,timer); + return response; + } + } + + /** + * 校验C8请求是否需要重新登录,如果登录超时,则会重新登录并且重试 + */ + private Response checkSession(Interceptor.Chain chain, Request originalRequest, Response response) throws IOException { + C8LoginService c8LoginService = SpringUtil.getBean(C8LoginService.class); + boolean needLogin = c8LoginService.checkSession(originalRequest, response);//校验session + if (needLogin) { + response = chain.proceed(response.request());// //重试请求,获取返回值; + } + return response; + } + + /** + * 设置cookie + */ + private Request setCookie(Request originalRequest) { + ThreadLocalCache cache = SpringContextHolder.getBean(ThreadLocalCache.class); + if (StrUtil.isNotBlank(cache.personalCookie.get())) { + return originalRequest.newBuilder().addHeader("Cookie", cache.personalCookie.get()).build(); + } + return originalRequest; + } + + @Bean + public ObjectMapper jacksonMapper() { + ObjectMapper mapper = new ObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + return mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + /** + * 处理Feign的接口日志 + */ + private FeignLogDto startProcessLog(Request originalRequest) { + LogService logService = SpringUtil.getBean(LogServiceImpl.class); + FeignLogDto feignLogDto = null; + CsProperties csProperties = SpringContextHolder.getBean(CsProperties.class); + //是否记录日志 + if(StrUtil.equals(csProperties.getValue("feign-log"), Constants.Bool.TRUE, true)){ + feignLogDto = logService.setRequestData(originalRequest); + } + return feignLogDto; + } + /** + * 处理Feign的接口日志 + */ + private void endProcessLog(FeignLogDto feignLogDto,Response response,TimeInterval timer) { + if(feignLogDto==null){ + return; + } + LogService logService = SpringUtil.getBean(LogServiceImpl.class); + logService.setResponseData(response, timer, feignLogDto);//封装返回请求报文 + logService.saveLog(feignLogDto);//保存日志 + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/okhttp/CookieJarHandler.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/okhttp/CookieJarHandler.java new file mode 100644 index 0000000..417d71a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/config/okhttp/CookieJarHandler.java @@ -0,0 +1,42 @@ +package com.centricsoftware.enhancement.modules.c8.config.okhttp; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.enhancement.dto.cache.ThreadLocalCache; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class CookieJarHandler implements CookieJar { + + private ConcurrentHashMap> cookieStore = new ConcurrentHashMap<>(); + + @Override + public void saveFromResponse(HttpUrl httpUrl, List list) { + //保存Cookies + ThreadLocalCache cache = SpringContextHolder.getBean(ThreadLocalCache.class); + if(cache==null){ + cookieStore.put(httpUrl.host(), list); + } + if(cache!=null && StrUtil.isBlank(cache.personalCookie.get())){ + cookieStore.put(httpUrl.host(), list); + } + } + + @Override + public List loadForRequest(HttpUrl httpUrl) { + //加载Cookies + List cookies = cookieStore.get(httpUrl.host()); + ThreadLocalCache cache = SpringContextHolder.getBean(ThreadLocalCache.class); + if(cache!=null && StrUtil.isNotBlank(cache.personalCookie.get())){ + return new ArrayList(); + }else{ + return cookies != null ? cookies : new ArrayList(); + } + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/C8LogReturnEntity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/C8LogReturnEntity.java new file mode 100644 index 0000000..1a4b2a7 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/C8LogReturnEntity.java @@ -0,0 +1,26 @@ +package com.centricsoftware.enhancement.modules.c8.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class C8LogReturnEntity implements Serializable { + + @JsonProperty("Status") + private String status; + + @JsonProperty("Error") + private String error; + + @JsonProperty("ErrorAdmin") + private String errorAdmin; + + @JsonProperty("NODES") + private Object nodes; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/ExpResultEntity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/ExpResultEntity.java new file mode 100644 index 0000000..3cebde6 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/ExpResultEntity.java @@ -0,0 +1,42 @@ +package com.centricsoftware.enhancement.modules.c8.dto; + +import cn.hutool.json.JSONObject; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class ExpResultEntity implements Serializable { + @JsonProperty("Status") + private String status; + + @JsonProperty("Time") + private String time; + + @JsonProperty("Dependency") + private JSONObject dependency; + + @JsonProperty("Debug") + private JSONObject debug; + + @JsonProperty("Error") + private String error; + + @JsonProperty("ErrorAdmin") + private String errorAdmin; + + @JsonProperty("Result") + private ExpResultEntity.Result result; + + @Data + public class Result { + + @JsonProperty("Type") + private String type; + + @JsonProperty("Value") + private Object value; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/OperationResultEntity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/OperationResultEntity.java new file mode 100644 index 0000000..38a5cce --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/OperationResultEntity.java @@ -0,0 +1,36 @@ +package com.centricsoftware.enhancement.modules.c8.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class OperationResultEntity extends C8LogReturnEntity { + + @JsonProperty("Status") + private String status; + + /** + * {Node": [{}]} + */ + @JsonProperty("NODES") + private Object nodes; + + /** + * {Modified": "C159609"} + */ + @JsonProperty("URLS") + private Object urls; + + @JsonProperty("ErrorAdmin") + private String errorAdmin; + + @JsonProperty("Error") + private String error; + + @JsonProperty("FileInfo") + private Object fileInfo; + + @JsonProperty("DBQuery") + private Object dbQuery; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/CustomViewConfig.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/CustomViewConfig.java new file mode 100644 index 0000000..b036fd3 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/CustomViewConfig.java @@ -0,0 +1,194 @@ +package com.centricsoftware.enhancement.modules.c8.dto.customview; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.EnumTypeForCV; +import com.google.common.collect.Lists; +import lombok.Data; +import org.apache.poi.ss.formula.functions.T; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * description:定制化视图配置类 + * Date: 2024/5/14 14:58 + */ +@Data +public class CustomViewConfig { + + public CustomViewConfig(String viewUrl){ + this.viewUrl = viewUrl; + } + + /** + * CustomView URL,比如 C3155(_CS_PreferenceView) + */ + private String viewUrl; + + /** + * 行BO名称 + */ + private String targetClass; + + /** + * 视图角色 + */ + private List umEntry; + + /** + * 列配置项 + */ + private List cols = Lists.newArrayList(); + + /** + * 别名,如果需要重新定制返回的列名,则需要配置此属性 + * key:列名,value:别名 + * key默认为attributeId,如果attributeId重复,则默认为attributeId+流水,比如attributeId1、attributeId2+attributeId3 + * 如果是Node Name字段: + * 1、【Node Name:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例:ProductColors + * 2、Node Name:(Style):0为例:Node Name + * 如果执行Node Name规则后还是重复,则再按照attributeId(修改后)+流水执行,比如ProductColors1、ProductColors2、ProductColors3 + */ + private Map aliasMap = new HashMap<>(); + + /** + * 组件会对attributeId进行默认处理,如果配置了aliasMap,则处理后会根据aliasMap进行映射 + * key:fullPath + * value: 字段 + */ + private Map attributeIdMapping = new HashMap<>();; + + /** + * 优化列名,默认为false;优化项: + * 1、开启后,Node Name会被优化为Name; + * 2、如果的是Ref类型的数据,取值则取Node Name + * 3、如果attributeId重复,则向上取一个path,如果再重复,则按照attributeId(原始)+流水执行;注意: + * 3.1 targetClass上的字段(本BO上的字段)不会加流水 + * 3.2 如果向上取再重复了,直接全路径 + * 3.3 matrix字段同样规则 + * 5、enum类型转换 + */ + private boolean optimizationAttributeId = false; + + /** + * CustomView的DepPathResult结果集 + */ + private DepPathResult customViewResult; + + /** + * DepPath的Path + */ + private List paths; + + /** + * List转成String + */ + private boolean listToString = true; + + /** + * List转成String + */ + private String conjunction = String.valueOf(StrUtil.C_COMMA); + + /** + * Map转成String + */ + private boolean mapToString = true; + + /** + * 枚举值转换,默认转成key;无法对单列定义。如果不同列转换不一致,请用Lambda查询定制 + */ + private EnumTypeForCV enumTypeForCV = EnumTypeForCV.KEY; + + + + @Data + public class ColumnConfig { + /** + * 列对应的BO名称: + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例:Colorway + */ + private String businessObjectName; + + /** + * 列最终取值字段 + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例:C8_CW_CostDev1 + */ + private String attributeId; + + /** + * 是否是matrix + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例:true + */ + private boolean matrix; + + /** + * matrix字段名称 + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例:ProductColors,获取的是{ProductColors}中的ProductColors + * 如果重复,则取全路径,比如 A:(Style)Child:B(XX)/Child:ProductColors(Colorway):0{ProductColors}: B.ProductColors + */ + private String matrixAttributeId; + + /** + * matrix路径 + * 以【C8_CA_RatingO3:(Style)Child:C(XXX)/Child:ProductColors(Colorway)/Child:Attributes(ColorwayAttributes):0{ProductColors}】为例:C.ProductColors + */ + private String matrixPath; + + /** + * matrix后的ID + * 以【C8_CA_RatingO3:(Style)Child:ProductColors(Colorway)/Child:Attributes(ColorwayAttributes):0{ProductColors}】为例:Attributes.C8_CA_RatingO3 + */ + private String attributeIdAfterMatrix; + + /** + * 路径:不包括最终的取值字段 + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例: + * ProductColors + */ + private String path; + + /** + * 最后一个path+“.”+attributeId + */ + private String path1; + + /** + * BO路径 + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例: + * Style.Colorway + */ + private String boPath; + + /** + * 全路径,包括最终的取值字段 + * 以【C8_CW_CostDev1:(Style)Child:ProductColors(Colorway):0{ProductColors}】为例: + * ProductColors.C8_CW_CostDev1 + */ + private String fullPath; + + /** + * 原始表达式,_CS_PreferenceView中Keys的元素 + */ + private String originExpressions; + + /** + * 最终取值 + */ + private DepPathResultValue value; + + public String getGroupId() { + if(StrUtil.isBlankOrUndefined(matrixAttributeId)){ + return attributeId; + } + return StrFormatter.format("{}.{}",matrixAttributeId,attributeId); + } + } + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/LambdaParam.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/LambdaParam.java new file mode 100644 index 0000000..064a3d5 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/customview/LambdaParam.java @@ -0,0 +1,16 @@ +package com.centricsoftware.enhancement.modules.c8.dto.customview; + +import cn.hutool.json.JSONObject; +import lombok.Data; + +/** + * description: + * Date: 2024/5/15 16:31 + */ +@Data +public class LambdaParam { + + private CustomViewConfig config; + + private JSONObject result; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/CentricAttrs.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/CentricAttrs.java new file mode 100644 index 0000000..7d74486 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/CentricAttrs.java @@ -0,0 +1,28 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import lombok.Data; + +/** + * description:存储DepPath查询时返回字段的flag和type信息 + * Convert.convert不支持 @JsonProperty,所以此处的字段命名没按照规范命名 + * Date: 2024/2/5 0:12 + */ +@Data +public class CentricAttrs { + + /** + * C8字段类型 + */ + private String Type; + + /** + * C8字段FG + */ + private String FG; + + + private String EXP; + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPath.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPath.java new file mode 100644 index 0000000..6e51350 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPath.java @@ -0,0 +1,145 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import cn.hutool.core.collection.ListUtil; +import com.centricsoftware.enhancement.modules.c8.em.DepPathType; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; + +@Data +public class DepPath { + + private TreeSet paths = Sets.newTreeSet() ; + private TreeSet urls = Sets.newTreeSet() ; + private String xml; + private DepPathType type; + + public static DepPathBuilder builder(){ + DepPath depPath = new DepPath(); + depPath.setType(DepPathType.URL); + DepPathBuilder builder = depPath.new DepPathBuilder(depPath); + return builder; + } + + public static DepPathXmlBuilder builderXml(){ + DepPath depPath = new DepPath(); + depPath.setType(DepPathType.XML); + DepPathXmlBuilder builder = depPath.new DepPathXmlBuilder(depPath); + return builder; + } + + /** + * 解析path,比如Child:__Parent__/Child:__Parent__,解析为Child:__Parent__和Child:__Parent__/Child:__Parent__ + * @param paths + */ + public static List getPathPart(String paths){ + List list = Lists.newArrayList(); + String[] split = paths.split("/"); + for(int i = 0;i pathPart = DepPath.getPathPart(path); + depPath.getPaths().addAll(pathPart); + return this; + } + public DepPathBuilder addPaths(String[] paths){ + if(paths.length==0){ + return this; + } + ArrayList strings = ListUtil.toList(paths); + strings.forEach(item->{ + List pathPart = DepPath.getPathPart(item); + depPath.getPaths().addAll(pathPart); + }); + return this; + } + + public DepPathBuilder addUrl(String url){ + depPath.getUrls().add(url); + return this; + } + + public DepPathBuilder xml(String xml){ + depPath.setXml(""+xml+""); + return this; + } + + public DepPathBuilder addUrls(String[] urls){ + for(String url:urls){ + depPath.getUrls().add(url); + } + return this; + } + public DepPathBuilder addUrls(List urls){ + for(String url:urls){ + depPath.getUrls().add(url); + } + return this; + } + public DepPath build(){ + return depPath; + } + } + + public class DepPathXmlBuilder{ + DepPath depPath; + DepPathXmlBuilder( DepPath depPath){ + this.depPath = depPath; + } + + public DepPathXmlBuilder xml(String xml){ + depPath.setXml(""+xml+""); + return this; + } + public DepPathXmlBuilder addPath(String path){ + addPaths(path.split(",")); + List pathPart = DepPath.getPathPart(path); + depPath.getPaths().addAll(pathPart); + return this; + } + + public DepPathXmlBuilder addPaths(String[] paths){ + if(paths.length==0){ + return this; + } + ArrayList list = ListUtil.toList(paths); + list.forEach(item->{ + List pathPart = DepPath.getPathPart(item); + depPath.getPaths().addAll(pathPart); + }); + return this; + } + + public DepPathXmlBuilder addUrls(String[] urls){ + for(String url:urls){ + depPath.getUrls().add(url); + } + return this; + } + public DepPath build(){ + return depPath; + } + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathByEntityContext.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathByEntityContext.java new file mode 100644 index 0000000..592721d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathByEntityContext.java @@ -0,0 +1,68 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import lombok.Data; +import org.apache.ibatis.reflection.Reflector; +import org.apache.ibatis.reflection.invoker.Invoker; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * description:DepPath实体类查询上下文参数 + * Date: 2024/3/8 10:34 + */ +@Data +public class DepPathByEntityContext { + + /** + * 当前行URL + */ + private String url; + + /** + * 所有结果集 + */ + private List resultUrls; + + /** + * 查询结果集 + */ + private DepPathResult depPathResult; + + /** + * 缓存了类的定义信息,比如方法签名等 + */ + private Reflector reflector; + + /** + * 实体类的实例 + */ + private Object instance; + + /** + * 方法调用程序,可通过 invoker.invoke(o, new String[]{"值"})对实例进行赋值 + */ + private Invoker invoker; + + + private Class clazz; + + /** + * 实体类字段 + */ + private String fieldName; + + /** + * 实体类字段 + */ + private Field field; + + + /** + * 字段上的注解 + */ + private DepPathField depPathField; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathContext.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathContext.java new file mode 100644 index 0000000..8b8e029 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathContext.java @@ -0,0 +1,24 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathCache; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathParser; +import lombok.Data; + +/** + * description:DepPath查询时,上下文参数 + * Date: 2024/2/1 9:48 + */ +@Data +public class DepPathContext { + + /** + * 解析器组件 + */ + private DepPathParser depPathParser; + + /** + * 缓存组件 + */ + private DepPathCache depPathCache; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathExp.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathExp.java new file mode 100644 index 0000000..95b3676 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathExp.java @@ -0,0 +1,92 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.em.ParserFieldType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * description:解析器组件返回的实体类 + * Date: 2024/2/2 13:11 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DepPathExp { + + /** + * 完整的表达式,比如Style.Images.XXX.XXX + */ + private String fullExp; + + /** + * exp用分隔符分隔后,第N个的原始表达式 + */ + private String originExp; + + /** + * 对originExp做处理后的表达式:比如去除角标 + */ + private String exp; + + /** + * originExp如果还有角标,则提取角标到index + */ + private String index; + + /** + * 子表达式的字段类型:有角标的以INDEX_为前缀 + * 当前表达式执行后,值的类型;和attrs中的类型未必一致。 + * 如:在Style的URL下,表达式为Images.ModifiedBy,attrs的类型为ref, + * 但是type为可能为List(当Images存在多个时),也可能为ref。 + */ + private ParserFieldType type; + + + /** + * 只有JSONArray、JSONObject、String三种类型 + */ + private Object value; + + /** + * 存储当前值的flag和type + */ + private CentricAttrs attrs; + + /** + * 前子表达式值 + */ + private Object beforeValue; + + /** + * 后子表达式值 + */ + private Object afterValue; + + /** + * 前子表达式对象 + */ + private DepPathExp beforeExp; + + /** + * 后子表达式对象 + */ + private DepPathExp afterExp; + + /** + * 原始URL + */ + private String url; + + /** + * 重写toString方法:因为DepPathExp存在循环引用,默认的toString会溢出; + * @return + */ + @Override + public String toString(){ + return value.toString(); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResult0.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResult0.java new file mode 100644 index 0000000..5d1cfc0 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResult0.java @@ -0,0 +1,38 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.dto.C8LogReturnEntity; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +public class DepPathResult0 extends C8LogReturnEntity { + + @JsonProperty("Status") + private String status; + + /** + * 该值可能为空, + * 也可能是{ref": "C0/REH0151|Style"} + * 也可能是{ref": ["C0/REH0151|Style"]} + */ + @JsonProperty("CompleteResultRefs") + private Object completeResultRefs; + + /** + * 该字段可能为空 + * 也可能为{ResultNode": []} + */ + @JsonProperty("NODES") + private Object nodes; + + @JsonProperty("TotalCount") + private int totalCount; + + @JsonProperty("HasMoreResults") + private boolean hasMoreResults; + + private DepPath depPath; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResultValue.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResultValue.java new file mode 100644 index 0000000..1a060ab --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/dep/DepPathResultValue.java @@ -0,0 +1,425 @@ +package com.centricsoftware.enhancement.modules.c8.dto.dep; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.modules.c8.component.EnumCache; +import com.centricsoftware.enhancement.modules.c8.util.CentricAttributesUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * description:DepPathResult.getValue()返回类 + * Date: 2024/2/2 13:24 + */ +@NoArgsConstructor +@Slf4j +public class DepPathResultValue { + + public DepPathResultValue(Object value){ + this.value = value; + } + + /** + * 原始值 + */ + @Setter + private Object value; + + /** + * 子表达式列表 + */ + @Getter + @Setter + private List expList; + + /** + * 获取List,不去重 + */ + public List getList() { + return getList(false,String.class); + } + + /** + * 获取List:去重 + * @return + */ + public List getListRemoveDuplicate() { + return getList(true,String.class); + } + + /** + * 获取List:可配置List的泛型和是否去重 + * @param removeDuplicate 是否去重 + * @param t List泛型 + */ + public List getList(boolean removeDuplicate,Class t) { + if(value==null){ + return Lists.newArrayList(); + } + Object value1 = getValue(); + List ts = Lists.newArrayList(); + if(CentricAttributesUtil.valueIsList(value1)){ + ts = JSONUtil.parseArray(value1).toList(t); + }else if (CentricAttributesUtil.valueIsMap(value1)) { + Map convert = Convert.convert(Map.class, value1); + ts.addAll(convert.values()); + }else if(value1 instanceof String ){ + ts.add((T)value1); + }else{ + ts = JSONUtil.parseArray(value1).toList(t); + } + if(removeDuplicate) { + return ts.stream().distinct().collect(Collectors.toList()); + } + return ts; + } + + /** + * 获取Map值: + */ + public Map getMap() { + Map map = getMap(String.class, String.class); + return map; + } + + /** + * 获取Map值:指定泛型 + */ + public Map getMap(Class k, Class v) { + Map map = null; + Object value1 = getValue(); + try { + if(value1 instanceof List){ + JSONArray v1 = (JSONArray) value1; + for (Object i : v1) { + map = new HashMap<>(); + map.put((K) i, (V) i); + } + return map; + } + map = Convert.toMap(k,v, value1); + } catch (Exception e) { + HashMap kvHashMap = MapUtil.newHashMap(); + return kvHashMap; + } + if(map == null ){ + return Maps.newHashMap(); + } + return map; + } + + /** + * 获取Long类型的值 + * @return 长整型 + */ + public long getLong() { + Object v = getValue(); + try { + return NumberUtil.parseNumber(v.toString()).longValue(); + } catch (Exception e) { + log.error("表达式={},原始值={}:转化为Long失败,赋默认值0",getLastDepPathExp().getFullExp(),value); + return 0L; + } + } + + /** + * 获取枚举的Value值:比如C8_Phase:01,则返回01 + * @return 枚举的Value + */ + public String getEnumValue(){ + String str = getValue().toString(); + String[] split = str.split(":"); + if (split.length > 1) { + return split[1]; + } + return ""; + } + + /** + * 获取枚举的Key值:比如C8_Phase:01,则返回C8_Phase + * @return 枚举的Key,不带冒号 + */ + public String getEnumKey(){ + String str = getValue().toString(); + String[] split = str.split(":"); + if (split.length > 1) { + return split[0]; + } + return ""; + } + + /** + * 获取日期 + */ + public Date getDate() { + long value = getLong()*1000; + if (0L == value) { + return null; + } + return new Date(value); + } + + /** + * 获取日期字符格式值 + * @return yyyy-MM-dd + */ + public String getDateStr() { + return getDateFormat("yyyy-MM-dd"); + } + + /** + * 获取日期时间字符格式值 + * @return yyyy-MM-dd HH:mm:ss + */ + public String getDateAndTimeStr() { + return getDateFormat("yyyy-MM-dd HH:mm:ss "); + } + + /** + * 格式化获取日期字符值 + * @param format 格式化,比如yyyy-MM-dd、yyyy-MM-dd HH:mm:ss + * @return + */ + public String getDateFormat(String format) { + long value = getLong()*1000; + if (0L == value) { + return ""; + } + Timestamp ts = new Timestamp(value); + SimpleDateFormat sm = new SimpleDateFormat(format); + return sm.format(ts); + } + + /** + * 获取字符串类型的值 + */ + public String getStr(){ + if(value==null){ + return ""; + } + String str = Convert.toStr(getValue()); + if("{}".equals(str)){ + return ""; + } + return str; + } + + /** + * 获取整形的值 + */ + public int getInt(){ + String value = getStr(); + try { + return NumberUtil.parseNumber(value).intValue(); + } catch (Exception e) { + log.error("表达式={},原始值={}:转化为Int失败,赋默认值0",getLastDepPathExp().getFullExp(),getValue()); + return 0; + } + } + + /** + * 获取BigDecimal模式,并且设置保留位数和四舍五入模式 + * @param newScale 保留小数位 + * @param roundingMode 四舍五入模式 + * - CEILING: 舍入模式向正无穷大舍入。 +* - DOWN: 舍入模式向零舍入。 + * - FLOOR: 舍入模式向负无穷大舍入。 + * - HALF_DOWN: 舍入模式向“最近邻居”舍入,除非两个邻居等距,在这种情况下向下舍入。 + * - HALF_EVEN: 舍入模式向“最近邻居”舍入,除非两个邻居等距,在这种情况下,向着偶邻居舍入。 + * - HALF_UP: 舍入模式向“最近邻居”舍入,除非两个邻居等距,在这种情况下向上舍入。 + * - UNNECESSARY: 舍入模式断言所请求的操作具有精确结果,因此不需要舍入。 + * - UP: 舍入模式从零开始舍入。 + */ + public BigDecimal getBigDecimal(int newScale,RoundingMode roundingMode) { + String str = getStr(); + BigDecimal bigDecimal = NumberUtil.toBigDecimal(str); + bigDecimal = bigDecimal.setScale(newScale, roundingMode); + return bigDecimal; + } + + /** + * 四舍五入获取 BigDecimal + * @param newScale 保留小数位 + * @return + */ + public BigDecimal getBigDecimalByHalfUp(int newScale) { + return getBigDecimal(newScale,RoundingMode.HALF_UP); + } + + /** + * 获取Double值,保留所有精度 + */ + public double getDouble(){ + String str = getStr(); + double v = 0.0; + try { + v = NumberUtil.parseNumber(str).doubleValue(); + } catch (Exception e) { + log.error("表达式={},原始值={}:转化为Double失败,赋默认值0.0",getLastDepPathExp().getFullExp(),value); + } + return v; + } + + /** + * 获取Double值,四舍五入模式 + * @param newScale 保留小数位 + */ + public double getDouble(int newScale){ + BigDecimal bigDecimal = getBigDecimalByHalfUp(newScale); + return bigDecimal.doubleValue(); + } + + /** + * 获取布尔类型:值为true、是、Y,返回true,否则为false + * @return + */ + public boolean getBoolean() { + String str = getStr(); + return "true".equals(str)||"是".equals(str)||"Y".equals(str); + } + + /** + * 以conjunction 为分隔符将多个对象转换为字符串 + * @param conjunction 为分隔符将多个对象转换为字符串 + * @param removeDuplicate 是否去重 + * @return 连接后的字符串 + */ + public String getStrForList(String conjunction,boolean removeDuplicate){ + List list = getList(removeDuplicate, String.class); + return StrUtil.join(conjunction, list); + } + + /** + * 以conjunction 为分隔符将多个对象转换为字符串 + * @param conjunction 为分隔符将多个对象转换为字符串 + * @return 连接后的字符串 + */ + public String getStrForList(String conjunction){ + return getStrForList(conjunction, true); + } + + /** + * 以半角逗号【,】为分隔符将多个对象转换为字符串 + * @return 连接后的字符串 + */ + public String getStrForList(){ + return getStrForList(String.valueOf(StrUtil.C_COMMA)); + } + + /** + * 根据C8返回的类型,返回枚举描述 + * @return + */ + public Object getEnumDescByType() { + DepPathExp lastDepPathExp = getLastDepPathExp(); + String type = lastDepPathExp.getAttrs().getType(); + if("enum".equals(type)){ + return getEnumDesc(); + } + if(CentricAttributesUtil.isList(lastDepPathExp)){ + return getEnumDescList(false); + } + return getEnumDesc(); + } + + /** + * 根据C8返回的类型,返回枚举值 + * @return + */ + public Object getEnumValueByType() { + DepPathExp lastDepPathExp = getLastDepPathExp(); + String type = lastDepPathExp.getAttrs().getType(); + if("enum".equals(type)){ + return getEnumValue(); + } + if(CentricAttributesUtil.isList(lastDepPathExp)){ + return getEnumValueList(false); + } + return getEnumValue(); + } + + /** + * 获取枚举描述值 + */ + public String getEnumDesc() { + EnumCache enumCache = getEnumCache(); + String str = getStr(); + return enumCache.getDescByFullname(str); + } + + /** + * 获取枚举描述值:会先对List去重 + */ + public List getEnumDescListRemoveDuplicate() { + return getEnumDescList(true); + } + + /** + * 获取枚举描述值 + * @param removeDuplicate true表示对list值去重 + */ + public List getEnumDescList(boolean removeDuplicate) { + EnumCache enumCache = getEnumCache(); + List reList = Lists.newArrayList(); + List list = getList(removeDuplicate,String.class); + for (String str : list) { + String descByFullname = enumCache.getDescByFullname(str); + if (StrUtil.isNotBlank(descByFullname)) { + reList.add(descByFullname); + } + } + return reList; + } + + /** + * 获取枚举值List + */ + public List getEnumValueList(boolean removeDuplicate) { + List list = getList(removeDuplicate,String.class); + List collect = list.stream().filter(i -> i.contains(StrUtil.COLON)).map(i -> i.split(StrUtil.COLON)[1]).collect(Collectors.toList()); + return collect; + } + + public Object getValue(){ + return value==null?"":value; + } + + private EnumCache enumCache; + private EnumCache getEnumCache() { + if (enumCache != null) return this.enumCache; + EnumCache bean = SpringUtil.getBean(EnumCache.class); + this.enumCache = bean; + return this.enumCache; + } + + /** + * 获取最后一个子表达式 + * @return + */ + public DepPathExp getLastDepPathExp(){ + List expList1 = getExpList(); + if(expList1==null||expList1.size()==0){ + return new DepPathExp(); + } + return expList1.get(expList1.size()-1); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractParameterEntity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractParameterEntity.java new file mode 100644 index 0000000..cf9d2a5 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractParameterEntity.java @@ -0,0 +1,23 @@ +package com.centricsoftware.enhancement.modules.c8.dto.nativeexport; + +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.List; + +/** + * NativeExport参数实体类 + */ +@Data +@Deprecated +public class ExtractParameterEntity { + private String queryXml; + private String extractXml; + private String limitUrls=""; + private String delimiter=""; + private int startRow=1; + private int endRow=2147483647; + private String userUrl=""; + private int traceLevel=0; + private List resultList = Lists.newArrayList(); +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractResultEntity.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractResultEntity.java new file mode 100644 index 0000000..fd9f221 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/dto/nativeexport/ExtractResultEntity.java @@ -0,0 +1,24 @@ +package com.centricsoftware.enhancement.modules.c8.dto.nativeexport; + +import lombok.Data; + + +/** + * NativeExport 返回值实体类 + */ +@Data +@Deprecated +public class ExtractResultEntity { + private Integer startId; + private String name; + private String seq; + private String valueString; + private Double valueNumber; + private Boolean valueBoolean; + private String valueUrl; + private String attrId; + + public String getAttrId(){ + return getName()+getSeq(); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/CentricAttributeType.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/CentricAttributeType.java new file mode 100644 index 0000000..e88e2e9 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/CentricAttributeType.java @@ -0,0 +1,48 @@ +package com.centricsoftware.enhancement.modules.c8.em; + +import cn.hutool.core.util.StrUtil; + +/** + * C8的字段类型,除了Enum会有衍生类型,其他的类型与C8一一对应 + * 1、为了将类型做统一管理,此处并没有根据组件单独定义枚举类 + * 2、部分组件未必支持所有的类型,对于不支持的类型,将走默认的处理逻辑 + */ +public enum CentricAttributeType { + + REF("ref","ref"), + REF_LIST("reflist","reflist"), + STRING("string","string"); + + /** + * Java中识别的字段类型,Java中会根据type做不通的逻辑处理。type与c8Type可能是多对1的关系 + * 比如:type为enum、enum_desc、enum_value等对应的c8Type为enum + */ + private String type; + + /** + * C8的字段类型 + */ + private String c8Type; + + + CentricAttributeType(String type, String c8Type ){ + this.type = type; + this.c8Type = c8Type; + } + + public boolean isList() { + if(StrUtil.endWithAny(this.c8Type,"list","vector","set")){ + return true; + } + return false; + } + + public boolean isMap() { + if(StrUtil.endWithAny(this.c8Type,"map")){ + return true; + } + return false; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathEntityType.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathEntityType.java new file mode 100644 index 0000000..da97be5 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathEntityType.java @@ -0,0 +1,93 @@ +package com.centricsoftware.enhancement.modules.c8.em; + +import lombok.Getter; + +import java.util.Date; +import java.util.List; + +/** + * DepPath实体类查询中注解的类型 + */ +@Getter +public enum DepPathEntityType { + + /** + * 对应C8为ref类型,实体类字段为String + */ + STRING("string",String.class), + + DATA_STRING("data_string", String.class), + + DATA("data_string", Date.class), + + REF("ref",String.class), + + /** + * 对应C8为double类型,实体类字段为Double + */ + DOUBLE("double",Double.class), + + /** + * 对应C8为integer类型,实体类字段为Integer + */ + INTEGER("integer",Integer.class), + + /** + * 对应C8为file类型,实体类字段为String + */ + FILE("file",String.class), + + /** + * list相关类型 + */ + LIST("list", List.class), + + /** + * 枚举描述List + */ + ENUM_DESC_LIST("enum_desc_list", List.class), + + /** + * 枚举值List + */ + ENUM_VALUE_LIST("enum_value_list", List.class), + + /** + * 枚举value + */ + ENUM_VALUE("enum_value", String.class), + + /** + * 枚举描述 + */ + ENUM_DESC("enum_desc", String.class), + + /** + * 枚举描述List转字符 + */ + ENUM_DESC_LIST_TO_String("enum_desc_list_to_string", String.class), + + /** + * 枚举值List转字符 + */ + ENUM_VALUE_LIST_TO_String("enum_value_list_to_string", String.class), + + /** + * 对应C8为file类型,实体类字段为String + */ + LIST_TO_String("list_to_string", String.class); + + private String name; + + /** + * 实体类对应的字段类型 + */ + private Class clazz; + + + DepPathEntityType(String name, Class clazz){ + this.name = name; + this.clazz = clazz; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathType.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathType.java new file mode 100644 index 0000000..41fc3b2 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/DepPathType.java @@ -0,0 +1,19 @@ +package com.centricsoftware.enhancement.modules.c8.em; + +/** + * 语言包枚举,用户EnumUtil缓存工具 + */ +public enum DepPathType { + XML("xml"), + URL("url"); + + private String value; + + DepPathType(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/EnumTypeForCV.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/EnumTypeForCV.java new file mode 100644 index 0000000..83dd4ee --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/EnumTypeForCV.java @@ -0,0 +1,28 @@ +package com.centricsoftware.enhancement.modules.c8.em; + +/** + * Custom View Enum枚举类型 + */ +public enum EnumTypeForCV { + + /** + * 原始值 + */ + ORIGIN("origin"), + + /** + * 枚举的Value + */ + KEY("key"), + + /** + * 枚举的描述值 + */ + DESCRIPTION("description"); + + private String value; + + EnumTypeForCV(String value ){ + this.value = value; + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/ParserFieldType.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/ParserFieldType.java new file mode 100644 index 0000000..c6a2c17 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/em/ParserFieldType.java @@ -0,0 +1,49 @@ +package com.centricsoftware.enhancement.modules.c8.em; + +/** + * C8的字段类型,除了Enum会有衍生类型,其他的类型与C8一一对应 + * 1、为了将类型做统一管理,此处并没有根据组件单独定义枚举类 + * 2、部分组件未必支持所有的类型,对于不支持的类型,将走默认的处理逻辑 + */ +public enum ParserFieldType { + + /** + * MAP或者List有角标:表达式解析过程的中间状态, + * 当获取到值后,就会根据值的类型,自动转换为Index_的类型; + * 比如INDEX_LIST、INDEX_MAP + */ + INDEX("index","index"), + + /** + * 表达式有角标:list + */ + INDEX_LIST("index_list","index_list"), + + /** + * 表达式有角标:map + */ + INDEX_MAP("index_map","index_map"), + + LIST("list","list"), + MAP("map","map"), + REF("ref","ref"), + OTHER("other","other"); + + /** + * Java中识别的字段类型,Java中会根据type做不通的逻辑处理。type与c8Type可能是多对1的关系 + * 比如:type为enum、enum_desc、enum_value等对应的c8Type为enum + */ + private String type; + + /** + * C8的字段类型 + */ + private String c8Type; + + + ParserFieldType(String type, String c8Type ){ + this.type = type; + this.c8Type = c8Type; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8Feign.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8Feign.java new file mode 100644 index 0000000..4ad0875 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8Feign.java @@ -0,0 +1,103 @@ +package com.centricsoftware.enhancement.modules.c8.feign; + +import com.centricsoftware.enhancement.modules.c8.dto.ExpResultEntity; +import com.centricsoftware.enhancement.modules.c8.dto.OperationResultEntity; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResult0; +import feign.Response; +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; + +@Lazy +@FeignClient(url = "${cs.plm.http}",name = "c8-java-op",configuration = C8Feign.MultipartSupportConfig.class) +public interface C8Feign { + +// /** +// * 执行表达式 +// */ +// @PostMapping(value = "/csi-requesthandler/RequestHandler", +// headers = {"Content-Type=multipart/form-data;charset=UTF-8;boundary=C6EE5092158B4206939DC028F7CEC00F"}) +// ExpResultEntity executeExp(@RequestParam Map map); + + /** + * 执行表达式 + */ + @PostMapping(value = "/csi-requesthandler/RequestHandler", + consumes = {"application/x-www-form-urlencoded;charset=UTF-8"}) + ExpResultEntity executeExp(Map map); + + /** + * 执行DepPath + */ + @PostMapping(value = "/csi-requesthandler/RequestHandler", + consumes = {"application/x-www-form-urlencoded;charset=UTF-8"}) + DepPathResult0 depPath(Map map); +// headers = {"Content-Type=multipart/form-data;charset=UTF-8;boundary=C6EE5092158B4206939DC028F7CEC00F"}) +// DepPathResult0 depPath(@RequestParam Map map); + + /** + * 执行ProcessNode + */ + @PostMapping(value = "/csi-requesthandler/RequestHandler", + consumes = {"application/x-www-form-urlencoded;charset=UTF-8"}) + OperationResultEntity excuteOperation(Map map); + + /** + * 获取文件 + */ + @PostMapping(value = "/csi-requesthandler/RequestHandler?c8_file=true", + consumes = {"application/x-www-form-urlencoded;charset=UTF-8"}) + Response getFileFromNode(Map map); + + /** + * 上传文件 + */ + @PostMapping(value = "/csi-requesthandler/RequestHandler", + produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}, + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + OperationResultEntity excuteOperationFile(@RequestPart("file") MultipartFile file,@RequestParam Map map); + + /** + * 上传文件 + */ + @PostMapping(value = "/csi-requesthandler/RequestHandler", + produces = {MediaType.MULTIPART_FORM_DATA_VALUE}, + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + OperationResultEntity publishFile(@RequestPart("FL") MultipartFile file, @RequestParam Map map); + + + class MultipartSupportConfig { + @Autowired + private ObjectFactory messageConverters; + +// @Bean +// public Decoder feignDecoder(){ +// return new ResponseEntityDecoder(new SpringDecoder(messageConverters)); +// } + + @Bean + public Encoder feignFormEncoder() { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } +// +// @Bean +// Encoder feignFormEncoder(ObjectFactory converters) { +// return new FormEncoder(new SpringEncoder(converters)); +// } + + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8LoginFeign.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8LoginFeign.java new file mode 100644 index 0000000..c739565 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/feign/C8LoginFeign.java @@ -0,0 +1,42 @@ +package com.centricsoftware.enhancement.modules.c8.feign; + +import com.centricsoftware.enhancement.modules.c8.dto.C8LogReturnEntity; +import feign.codec.Encoder; +import feign.form.FormEncoder; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.Map; + +/** + * C8登录操作 + */ +@FeignClient(url = "${cs.plm.http}",name = "c8-java-login",configuration = C8LoginFeign.MultipartSupportConfig.class) +public interface C8LoginFeign { + + @PostMapping(value = "/csi-requesthandler/RequestHandler", + consumes = {"application/x-www-form-urlencoded;charset=UTF-8"}) + C8LogReturnEntity login( Map formParams); + + class MultipartSupportConfig { + @Autowired + private ObjectFactory messageConverters; + +// @Bean +// public Encoder feignFormEncoder() { +// return new SpringFormEncoder(new SpringEncoder(messageConverters)); +// } + + @Bean + Encoder feignFormEncoder(ObjectFactory converters) { + return new FormEncoder(new SpringEncoder(converters)); + } + + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8LoginService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8LoginService.java new file mode 100644 index 0000000..f1c122e --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8LoginService.java @@ -0,0 +1,132 @@ +package com.centricsoftware.enhancement.modules.c8.service; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.modules.c8.dto.C8LogReturnEntity; +import com.centricsoftware.enhancement.modules.c8.feign.C8LoginFeign; +import com.centricsoftware.enhancement.util.http.C8HttpUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.Map; + +@Service +@Slf4j +public class C8LoginService { + + public static String Successful = "Successful"; + + public static String Failed = "Failed"; + + public static String SRV_ERR_SiteAdmin_INVALID_SESSION = "SRV_ERR_SiteAdmin_INVALID_SESSION"; + + @Value("${cs.plm.user}") + private String user; + + @Value("${cs.plm.pwd}") + private String pwd; + + @Value("${cs.plm.http}") + private String http; + + @Autowired + C8LoginFeign c8LoginFeign; + + @Autowired + @Lazy + ObjectMapper mapper; + + /** + * 登录C8 + * @return + */ + public synchronized boolean login(){ + Map map = Maps.newHashMap(); + map.put("Module","DataSource"); + map.put("Operation","SimpleLogin"); + map.put("LoginID",user); + map.put("Password",pwd); + map.put("OutputJSON","2"); + map.put("Fmt.Version","2"); + C8LogReturnEntity login = c8LoginFeign.login(map); + String status = login.getStatus(); + if(Successful.equals(status)){ + return true; + } + //可在此做一些通知操作,比如发邮件通知管理员等 + throw new RuntimeException(StrFormatter.format("登录异常,请联系管理员。异常信息:{};{}",login.getError(),login.getErrorAdmin())); + } + + /** + * 判断是否需要重新登录C8 + * @return 返回true表示需要重试 + */ + public boolean checkSession(Request originalRequest, Response response) throws IOException { + HttpUrl url = originalRequest.url();//获取访问的URL + String path = url.uri().getPath();//获取相对访问路径 + String operation = url.queryParameter("Operation"); + if(StrUtil.isBlank(path)||!path.contains("csi-requesthandler/RequestHandler")||"SimpleLogin".equals(operation)){ + return false; + } + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + //C8交互时,校验是否需要检查session是否过期 + String content = C8HttpUtil.copyResponseBody(response); + C8LogReturnEntity convert = null; + try { + convert = mapper.convertValue(JSONUtil.parse(content), C8LogReturnEntity.class);//将body转成实体类 + } catch (Exception e) { + String isFile = originalRequest.url().queryParameter("c8_file"); + if(!"true".equals(isFile)){ + log.error("返回报文为:{}",content); + log.error("验证session过期拦截器报错:",e); + } + } + if(convert!=null&&Failed.equals(convert.getStatus())){ + if(SRV_ERR_SiteAdmin_INVALID_SESSION.equals(convert.getError())){ +// checkLoginNum(originalRequest);//校验登录次数 + log.info("Session过期,重新登录。",convert.getErrorAdmin()); + log.info("=====================登录开始==================="); + boolean login = login();//重新登录 + if(login){ + log.info("=====================登录成功==================="); + }else{ + log.info("=====================登录失败==================="); + } + return true; + } + } + return false; + } + + /** + * 判断登录次数,超过三次不再登录 + * 该功能暂时不启用:处于半成品状态 + * @param originalRequest + * @return + */ + public void checkLoginNum(Request originalRequest){ + String head = "C8_LoginNum"; + String count = originalRequest.header(head); + if(StrUtil.isNotBlank(count)){ + int i = Integer.parseInt(count); + if(i>=3){ + log.error("自动登录次数超过三次,不再自动登录,抛出运行时异常"); + throw new RuntimeException("Session过期,自动登录失败,请联系管理员"); + } + }else{ + originalRequest.newBuilder().addHeader("C8_LoginNum","1"); + } + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8NodeService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8NodeService.java new file mode 100644 index 0000000..298dc98 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8NodeService.java @@ -0,0 +1,450 @@ +package com.centricsoftware.enhancement.modules.c8.service; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.utils.FileUtil; +import com.centricsoftware.commons.utils.SpringUtil; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.component.design.chain.IDepPathSearchChain; +import com.centricsoftware.enhancement.modules.c8.dto.OperationResultEntity; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.service.curd.C8DealExpResultService; +import com.centricsoftware.enhancement.modules.c8.service.curd.C8OperationService; +import com.centricsoftware.enhancement.modules.c8.service.curd.C8SearchService; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8Search; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +@Slf4j +@Service +public class C8NodeService { + + @Resource + C8DealExpResultService c8DealExpResultService; + + @Resource + C8SearchService c8SearchService; + + @Resource + C8OperationService c8OperationService; + + /** + * ======================================================Operation 相关============================================== + */ + + /** + * 通过传入的urls,拼成or查询语句 + */ + public String getNativeExportXml(String type, List urls, String filterXml) { + if (urls.size() == 0) { + throw new RuntimeException("传入的url不能为空"); + } + StringBuilder xml = new StringBuilder(); + xml.append(""); + xml.append(""); + for (String url : urls) { + xml.append(""); + } + xml.append("").append(filterXml); + return getNativeExportXml(xml.toString()); + } + + public String getNativeExportXmlByUrl(String url) { + return getNativeExportXml(""); + } + + /** + * 将普通search转换为数据库查询语句 + * + * @return 新的url + */ + public String getNativeExportXml(String xml) { + OperationResultEntity operationResultEntity = c8OperationService.getNativeExportXml(xml); + if (!C8LoginService.Successful.equals(operationResultEntity.getStatus())) { + String format = StrFormatter.format("转换查询语句失败。错误信息={}", operationResultEntity.getErrorAdmin()); + throw new RuntimeException(format); + } + String url = JSONUtil.parseObj(operationResultEntity.getDbQuery()).getStr("XML", ""); + if (StrUtil.isBlank(url)) { + throw new RuntimeException("转换查询语句失败。"); + } + return url; + } + + /** + * 获取文件 + * + * @param url C开头的URL + * @param attribute URL对应的属性 + */ + public InputStream getFileFromNode(String url, String attribute) { + return c8OperationService.getFileFromNode(url, attribute); + } + + /** + * 获取图片小图 + */ + public InputStream getImageSmallImage(String url) { + return c8OperationService.getFileFromNode(url, "SmallImage"); + } + + /** + * 获取图片缩略图 + */ + public InputStream getImageThumbnail(String url) { + return c8OperationService.getFileFromNode(url, "Thumbnail"); + } + + /** + * 获取图片原图 + */ + public InputStream getImageViewable(String url) { + return c8OperationService.getFileFromNode(url, "Viewable"); + } + + /** + * 通过文件柜地址获取图片 + */ + public InputStream getFileFromDirectAddr(String url) { + return c8OperationService.getFileFromDirectAddr(url); + } + + /** + * 执行 Operation: 返回nodes + */ + public OperationResultEntity processNode(String xml, boolean singleThreaded, boolean returnNodes, boolean omitResults) { + OperationResultEntity operationResultEntity = c8OperationService.excuteOperation(xml, singleThreaded, returnNodes, omitResults); + if (!C8LoginService.Successful.equals(operationResultEntity.getStatus())) { + String format = StrFormatter.format("执行Operation报错.错误信息={},XML={}", operationResultEntity.getErrorAdmin(), xml); + throw new RuntimeException(format); + } + return operationResultEntity; + } + + public OperationResultEntity processNode(String xml) { + return processNodeMin(xml); + } + + /** + * 创建URL + * + * @return 新的url + */ + public String createURL() { + OperationResultEntity operationResultEntity = c8OperationService.createUrl(); + if (!C8LoginService.Successful.equals(operationResultEntity.getStatus())) { + String format = StrFormatter.format("创建URL报错。错误信息={}", operationResultEntity.getErrorAdmin()); + throw new RuntimeException(format); + } + String url = JSONUtil.parseObj(operationResultEntity.getUrls()).getStr("URL", ""); + if (StrUtil.isBlank(url)) { + throw new RuntimeException("创建URL报错。"); + } + return url; + } + + public String publishImage(MultipartFile file, String fileName) throws Exception { + String fileAddr = publishFile(file, fileName); + StringBuffer xml = new StringBuffer(); + String imageUrl = createURL(); + xml.append("\n") + .append("\n") + .append(""); + processNode(xml.toString()); + return imageUrl; + } + + public String publishImage(MultipartFile file) throws Exception { + return publishImage(file,file.getOriginalFilename()); + } + + /** + * 发布文件,可发布视频 + * @param file 文件 + * @param fileName 文件名,优先获取file.getOriginalFilename(),如果file.getOriginalFilename()有值,fileName可传入空 + * @return 文件柜地址 + */ + public String publishFile(MultipartFile file, String fileName) throws Exception{ + MultipartFile multipartFile = FileUtil.getMultipartFile(file.getInputStream(), file.getOriginalFilename() == null || file.getOriginalFilename() == "" ? fileName : file.getOriginalFilename(), "FL"); + OperationResultEntity operationResultEntity = c8OperationService.publishFile(multipartFile, null); + if (!C8LoginService.Successful.equals(operationResultEntity.getStatus())) { + String format = StrFormatter.format("发布文件报错。错误信息={}", operationResultEntity.getErrorAdmin()); + throw new RuntimeException(format); + } + JSONObject fileInfo = JSONUtil.parseObj(operationResultEntity.getFileInfo()); + String url = fileInfo.getStr("URL", ""); + if (StrUtil.isBlank(url)) { + throw new RuntimeException("发布文件报错。"); + } + return url; + } + + /** + * 不返回修改的node + * + * @param xml + * @return + */ + public OperationResultEntity processNodeMin(String xml) { + return processNode(xml, true, true, true); + } + + /** + * 不返回修改的node的所有字段 + * + * @param xml + * @return + */ + public OperationResultEntity processNodeMax(String xml) { + return processNode(xml, true, true, false); + } + + + /** + * ======================================================查询 相关============================================== + */ + + @Resource + IDepPathSearchChain iDepPathSearchChain; + + + /** + * 通过实体类查询:DepPath.XML DepPath.URL共用 + */ + public List depPathByEntity(DepPath depPath, Class clazz) { + return iDepPathSearchChain.execute(depPath, clazz, null); + } + + /** + * 通过实体类查询:DepPath.XML DepPath.URL共用 + */ + public List depPathByEntity(DepPath depPath, Class clazz, Consumer function) { + List list; + try { + return iDepPathSearchChain.execute(depPath, clazz, function); + } catch (Exception e) { + log.error("获取数据失败", e); + throw new RuntimeException(e); + } + } + + /** + * DepPath xml查询 + */ + public DepPathResult depPathByXml(DepPath depPath, int start, int end) { + return c8SearchService.depPathByXml(depPath, start, end); + } + + /** + * DepPath xml查询 + */ + public DepPathResult depPathByXml(DepPath depPath) { + return c8SearchService.depPathByXml(depPath); + } + + + public String queryFirstByNodeName(String type, String typeName) { + String xml = C8Search.newSearch(type).attr("Node Name", "EQ", typeName).getXml(); + return queryFirstByXML(xml); + } + + public String queryFirstByXML(String xml) { + List strings = queryByXML(xml); + if (strings.size() == 0) { + return ""; + } + return strings.get(0); + } + + /** + * 通过xml查询 + */ + public List queryByXML(String xml) { + DepPath build = DepPath.builderXml().xml(xml).build(); + return c8SearchService.depPathByXml(build).getCompleteResultRefs(); + } + + + /** + * DepPath url查询: DepPath + */ + public DepPathResult depPathByUrl(DepPath depPath) { + return c8SearchService.depPathByUrl(depPath); + } + + /** + * DepPath url查询 : url + */ + public Map querySingleUrl(String url) { + DepPath build = DepPath.builder().addUrl(url).build(); + DepPathResult result = c8SearchService.depPathByUrl(build); + JSONObject jsonObject = result.getAllNodes().get(url); + return Convert.toMap(String.class, String.class, jsonObject); + } + + /** + * DepPath url: url + */ + public DepPathResult queryUrl(String url) { + ArrayList strings = Lists.newArrayList(url); + return queryUrl(strings); + } + + /** + * DepPath url : urls + */ + public DepPathResult queryUrl(List url) { + DepPath build = DepPath.builder().addUrls(url).build(); + return c8SearchService.depPathByUrl(build); + } + + /** + * DepPath url: urls + paths + */ + public DepPathResult queryUrl(List url, List paths) { + return queryUrl(url, (String[]) paths.toArray()); + } + + /** + * DepPath url: urls + paths + */ + public DepPathResult queryUrl(List url, String[] paths) { + DepPath build = DepPath.builder().addUrls(url).addPaths(paths).build(); + return c8SearchService.depPathByUrl(build); + } + + /** + * ======================================================表达式相关============================================== + */ + + /** + * 执行表达式,返回 String + */ + public String queryExpressionResult(String exp, String url) { + return c8DealExpResultService.queryExpressionResult(exp, url); + } + + /** + * 执行表达式,返回boolean + */ + public boolean queryExpressionBoolean(String exp, String url) { + return Boolean.parseBoolean(c8DealExpResultService.queryExpressionResult(exp, url)); + } + + /** + * 执行表达式,返回 Map + * + * @return + */ + public Map queryExpressionMap(String exp, String url) { + return c8DealExpResultService.queryExpressionMap(exp, url); + } + + /** + * 执行表达式,返回 Date + */ + public Date queryExpressionDate0(String exp, String url) { + return c8DealExpResultService.queryExpressionDate0(exp, url); + } + + /** + * 执行表达式,返回字符串日期:yyyy-MM-dd + * + * @return + */ + public String queryExpressionDate(String exp, String url) { + return c8DealExpResultService.queryExpressionDate(exp, url); + } + + /** + * 执行表达式,返回字符串日期+时间:yyyy-MM-dd hh:mm:ss + * + * @return + */ + public String queryExpressionTime(String exp, String url) { + return c8DealExpResultService.queryExpressionTime(exp, url); + } + + /** + * 执行表达式,返回 List + * + * @return + */ + public List queryExpressionList(String exp, String url) { + return c8DealExpResultService.queryExpressionList(exp, url); + } + + public String[] queryExpressionArray(String exp, String url) { + List list = queryExpressionList(exp, url); + String[] re = list.toArray(new String[list.size()]); + return re; + } + + + /** + * 执行表达式,返回 enum的key + * + * @return + */ + public String queryExpressionEnumkey(String exp, String url) { + return c8DealExpResultService.queryExpressionEnumkey(exp, url); + } + + /** + * 执行表达式,返回Object + */ + public Object queryExpressionResultObject(String exp, String url) { + return c8DealExpResultService.queryExpressionResultObject(exp, url); + } + + /** + * 获取配置信息 + * @return CsProperties + */ + public CsProperties getProperties(){ + return SpringUtil.getBean(CsProperties.class); + } + + /** + * 判断当前日期是否是假期 + * @return + */ + public boolean isTodayHoliday(){ + return isSomedayHoliday(new Date()); + } + + /** + * 判断某个给定日期是否是假期 + * @return + */ + public boolean isSomedayHoliday(Date date){ + if(date!=null){ + String queryXML = "\n" + + "\n" + + "\n" + + ""; + List list = queryByXML(queryXML); + return list.size()>0; + }else + return false; + + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8Service.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8Service.java new file mode 100644 index 0000000..2bfd3b2 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/C8Service.java @@ -0,0 +1,15 @@ +package com.centricsoftware.enhancement.modules.c8.service; + +import com.centricsoftware.enhancement.modules.c8.feign.C8Feign; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class C8Service { + + @Autowired + public C8Feign c8Feign; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8DealExpResultService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8DealExpResultService.java new file mode 100644 index 0000000..80d0d7c --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8DealExpResultService.java @@ -0,0 +1,128 @@ +package com.centricsoftware.enhancement.modules.c8.service.curd; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.dto.ExpResultEntity; +import com.centricsoftware.enhancement.modules.c8.feign.C8Feign; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class C8DealExpResultService{ + + @Autowired + public C8Feign c8Feign; + + /** + * 执行表达式,返回String + */ + public String queryExpressionResult(String exp,String url){ + Object o = queryExpressionResultObject(exp, url); + return Convert.toStr(o); + } + + /** + * 执行表达式,返回 Map + * @return + */ + public Map queryExpressionMap(String exp, String url){ + Object o = queryExpressionResultObject(exp, url); + return doQueryExpressionMap(o); + } + + private Map doQueryExpressionMap(Object o){ + if(o==null){ + return Maps.newHashMap(); + } + String s = o.toString(); + if("{}".equals(s)|| StrUtil.isBlank(s)){ + return Maps.newHashMap(); + } + s = s.substring(1,s.length()-1);//去前后的花括号 + HashMap map = Maps.newHashMap(); + String[] split = s.split(","); + for(String i : split){ + String[] split1 = i.split("="); + if(split1.length<2) continue; + map.put(split1[0],split1[1]); + } + return map; + } + + /** + * 执行表达式,返回 Date + */ + public Date queryExpressionDate0(String exp, String url){ + long o = Long.parseLong(queryExpressionResult(exp, url)); + return DateUtil.date(o);//返回的时间为秒级,DateUtil.date只支持毫秒级,所以要乘以1000 + } + + + /** + * 执行表达式,返回字符串日期:yyyy-MM-dd + * @return + */ + public String queryExpressionDate(String exp, String url){ + Date date = queryExpressionDate0(exp, url); + return DateUtil.formatDate(date); + } + + /** + * 执行表达式,返回字符串日期+时间:yyyy-MM-dd hh:mm:ss + * @return + */ + public String queryExpressionTime(String exp, String url){ + Date date = queryExpressionDate0(exp, url); + return DateUtil.formatDateTime(date); + } + + /** + * 执行表达式,返回 List + * @return + */ + public List queryExpressionList(String exp, String url){ + Object o = queryExpressionResultObject(exp, url); + return Convert.toList(String.class,o); + } + + /** + * 执行表达式,返回 enum的key + * @return + */ + public String queryExpressionEnumkey(String exp, String url){ + String fullName = queryExpressionResult(exp, url); + if (fullName.indexOf(":") > 0) { + if (!fullName.endsWith(":")) { + fullName = fullName.substring(fullName.indexOf(":") + 1); + } else { + fullName = ""; + } + } + return fullName; + } + + /** + * 执行表达式,返回Object + */ + public Object queryExpressionResultObject(String exp,String url){ + log.debug("执行表达式:url={};exp={}",url,exp); + Map map = Maps.newHashMap(); + map.put("Module","Expression"); + map.put("Operation","TryJustExpression"); + map.put("URL",url); + map.put("Expression",exp); + map.put("OutputJSON","2"); + ExpResultEntity login = c8Feign.executeExp(map); + return Convert.toStr(login.getResult().getValue()); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8OperationService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8OperationService.java new file mode 100644 index 0000000..9981584 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8OperationService.java @@ -0,0 +1,174 @@ +package com.centricsoftware.enhancement.modules.c8.service.curd; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.dto.OperationResultEntity; +import com.centricsoftware.enhancement.modules.c8.feign.C8Feign; +import com.google.common.collect.Maps; +import feign.Response; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +@Slf4j +@Service +public class C8OperationService { + + @Autowired + public C8Feign c8Feign; + + /** + * 执行 Operation + * + * @param xml xml + * @param singleThreaded 是否单线程执行 + * @param returnNodes 是否返回node详细 + * @param omitResults 是否简单返回 + */ + public OperationResultEntity excuteOperation(String xml, boolean singleThreaded, boolean returnNodes, boolean omitResults) { + String format = StrFormatter.format("{}", xml); + Map map = Maps.newHashMap(); + map.put("Module", "NodeProcessor"); + map.put("Operation", "Execute"); + map.put("UpdatedNodes", format); + map.put("SingleThreaded", singleThreaded);//多线程执行 + map.put("OutputJSON", "2");//返回json + map.put("ReturnNodes", returnNodes);//是否返回node详细 + map.put("OmitResults", omitResults);//简单返回 + map.put("ReturnCNLs", "All");// None, Modified, Deleted, All + return c8Feign.excuteOperation(map); + } + + /** + * 创建URL + * + * @return + */ + public OperationResultEntity createUrl() { + Map map = Maps.newHashMap(); + map.put("Module", "NodeProcessor"); + map.put("Operation", "CreateNodeURL"); + map.put("OutputJSON", "2");//返回json + map.put("Count", "1"); + return c8Feign.excuteOperation(map); + } + + + /** + * 上传文件,可用于上传视频文件, + * @param file 文件 + * @param nodeUrl 上传文件所在的url + * @param attrId 文件存入的字段ID + * @return + */ + public OperationResultEntity uploadFile(MultipartFile file, String nodeUrl, String attrId) { + Map map = Maps.newHashMap(); + map.put("Module", "SiteAdmin"); + map.put("Operation", "MultiFilePublish"); + map.put("OutputJSON", "2"); + map.put("Fmt.AC.Rights", "Current"); + map.put("Fmt.Attr.Info", "Mid"); + map.put("Name", file.getOriginalFilename()); + map.put("NewNodeAttrId", attrId); + map.put("NewFileAttrId", attrId); + map.put("URL", nodeUrl); + map.put("Prepend", "true"); + map.put("NewNodeTypes", "Image"); + map.put("FormFileFieldName", "file"); + return c8Feign.excuteOperationFile(file, map); + } + + /** + * 发布文件 + * @param file 文件 + * @param fileUrl 可以传入URL,则可覆盖地址上的问价,目前没有场景用这个字段,都可以传入空 + * @return 文件柜地址 + */ + public OperationResultEntity publishFile(MultipartFile file, String fileUrl) { + Map map = Maps.newHashMap(); + map.put("Module", "Publisher"); + map.put("Operation", "Publish"); + map.put("OutputJSON", "2"); + map.put("FN", file.getOriginalFilename()); +// map.put("filename", file.getOriginalFilename()); + if (StrUtil.isNotBlank(fileUrl)) { + map.put("URL", fileUrl); + } + return c8Feign.publishFile(file, map); + } + + + public OperationResultEntity createUrls(int num) { + Map map = Maps.newHashMap(); + map.put("Module", "NodeProcessor"); + map.put("Operation", "CreateNodeURL"); + map.put("Count", num); + return c8Feign.excuteOperation(map); + } + + /** + * 将search转换成nativate export查询语句 + * + * @param searchXml + * @return + */ + public OperationResultEntity getNativeExportXml(String searchXml) { + Map map = Maps.newHashMap(); + map.put("Module", "NodeBrowserSupport"); + map.put("Operation", "TranslateQueryToDBXML"); + map.put("Qry.Limit.Path", ""); + map.put("OutputJSON", "2"); + map.put("Qry.XML", "" + searchXml + ""); + map.put("Qry.Limit.End", "10"); + return c8Feign.excuteOperation(map); + } + + /** + * 可获取文件、图片等 + * + * @param url 文件url地址:C开头的URL + * @param attribute url对应的属性 + * @return + */ + public InputStream getFileFromNode(String url, String attribute) { + Map map = Maps.newHashMap(); + map.put("Module", "Publisher"); + map.put("Operation", "GetFromNode"); + map.put("Attribute", attribute); + map.put("URL", url); + map.put("OutputJSON", "2"); + return getFileFromNode(map); + } + + /** + * 可获取文件、图片等 + * + * @param url 文件柜地址,不能放Image上的文件柜地址 + */ + public InputStream getFileFromDirectAddr(String url) { + Map map = Maps.newHashMap(); + map.put("Module", "Publisher"); + map.put("Operation", "GetDirect"); + map.put("URL", url); + return getFileFromNode(map); + } + + private InputStream getFileFromNode(Map map) { + Response response = c8Feign.getFileFromNode(map); + InputStream inputStream = null; + try { + inputStream = response.body().asInputStream(); + } catch (IOException e) { + log.error("获取文件失败:", e); + return null; + } + return inputStream; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8SearchService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8SearchService.java new file mode 100644 index 0000000..91b50b5 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/curd/C8SearchService.java @@ -0,0 +1,108 @@ +package com.centricsoftware.enhancement.modules.c8.service.curd; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.modules.c8.component.dep.CentricResult; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.component.factory.ICentricAbstractFactory; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResult0; +import com.centricsoftware.enhancement.modules.c8.feign.C8Feign; +import com.centricsoftware.enhancement.modules.c8.service.C8LoginService; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class C8SearchService{ + + @Resource + private C8Feign c8Feign; + + @Resource + private ICentricAbstractFactory centricAbstractFactory; + + /** + * DepPath查询; + * 因为以上的这两个方法是直接调用的NodeUtil中的代码;后续NodeUtil将废弃 + */ + public DepPathResult depPathByUrl(DepPath depPath){ + log.debug("执行DepPath查询:url={};depPath={}",depPath.toString()); + Map map = Maps.newHashMap(); + map.put("Module","Search"); + map.put("Operation","QueryByURL"); + map.put("OutputJSON","2"); + List urls = new ArrayList(depPath.getUrls()); + map.put("Qry.URL",urls); + List paths = new ArrayList(depPath.getPaths()); + map.put("Dep.Path",paths); + DepPathResult0 result = c8Feign.depPath(map); + result.setDepPath(depPath); + return dealResult(result); + } + + public DepPathResult depPathByXml(DepPath depPath){ + return depPathByXml(depPath,1,4999); + } + public DepPathResult depPathByXml(DepPath depPath,int start, int end){ + log.debug("执行DepPath查询:url={};depPath={}",depPath.toString()); + Map map = Maps.newHashMap(); + map.put("Module","Search"); + map.put("Operation","QueryByXML"); + map.put("Qry.Limit.Filter","10000"); + map.put("Fmt.Complete","Ref"); + map.put("OutputJSON","2"); + map.put("Qry.Limit.Begin",start); + map.put("Qry.Limit.End",end); + map.put("Qry.XML",depPath.getXml()); + List paths = new ArrayList(depPath.getPaths()); + map.put("Dep.Path",paths); + DepPathResult0 result = c8Feign.depPath(map); + result.setDepPath(depPath); + return dealResult(result); + } + + private DepPathResult dealResult(DepPathResult0 result){ + String status = result.getStatus(); + if(!C8LoginService.Successful.equals(status)){ + log.error("查询失败:{}",result.getDepPath().getXml()); + throw new RuntimeException("查询失败,"+result.getErrorAdmin()); + } + Object returnValue = result.getNodes(); + if(StrUtil.isBlankOrUndefined(returnValue.toString())){ + returnValue = "{}"; + } + JSONObject nodes = JSONUtil.parseObj(returnValue); + + Object completeResultRefs1 = result.getCompleteResultRefs(); + if(null != completeResultRefs1 && StrUtil.isBlankOrUndefined(completeResultRefs1.toString())){ + completeResultRefs1 = "{}"; + } + + JSONObject completeResultRefs = JSONUtil.parseObj(completeResultRefs1); + JSONArray node = nodes.getJSONArray("Node"); + + JSONArray resultNode = nodes.getJSONArray("ResultNode"); + CentricResult centricResult = new CentricResult(); + centricResult.setResultNodes(resultNode); + centricResult.setDepPath(result.getDepPath()); + centricResult.setPathNodes(node); + centricResult.setCompleteResultRefs(completeResultRefs); + DepPathContext depPathContext = new DepPathContext(); + centricAbstractFactory.getDepPathFactory().getDepPathCache(depPathContext); + centricAbstractFactory.getDepPathFactory().getDepPathParser(depPathContext); + DepPathResult depPathResult = new DepPathResult(centricResult,depPathContext); + return depPathResult; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewHandler.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewHandler.java new file mode 100644 index 0000000..1d6d612 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewHandler.java @@ -0,0 +1,39 @@ +package com.centricsoftware.enhancement.modules.c8.service.customview; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.centricsoftware.enhancement.modules.c8.dto.customview.CustomViewConfig; +import com.centricsoftware.enhancement.modules.c8.dto.customview.LambdaParam; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; + +import java.util.Map; +import java.util.function.Consumer; + +/** + * description: + * Date: 2024/5/14 17:19 + */ +public interface ICustomViewHandler { + + /** + * @param depPath 不需要指定paths,组件自动计算 + */ + JSONArray get(String customViewUrl, DepPath depPath); + + /** + * @param depPath 不需要指定paths,组件自动计算 + */ + JSONArray get(String customViewUrl, Map alisaMap, DepPath depPath); + + /** + * @param depPath 不需要指定paths,组件自动计算 + */ + JSONArray get(String customViewUrl, CustomViewConfig config, DepPath depPath); + + /** + * 支持按行Lambda定义,每一行的所有列执行完后再执行Lambda;Matrix算一行 + * @param depPath 不需要指定paths,组件自动计算 + */ + JSONArray getAndLambdaByLine(String customViewUrl, CustomViewConfig config, DepPath depPath, Consumer function); + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewParseService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewParseService.java new file mode 100644 index 0000000..9f7c703 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/ICustomViewParseService.java @@ -0,0 +1,31 @@ +package com.centricsoftware.enhancement.modules.c8.service.customview; + +import com.centricsoftware.enhancement.modules.c8.dto.customview.CustomViewConfig; + +/** + * description:根据Custom View URL解析视图,并封装成配置 + * Date: 2024/5/14 17:17 + */ +public interface ICustomViewParseService { + + /** + * 可传入自定义的配置,解析后的配置会直接封装在config + */ + void parseCustomView(String url, CustomViewConfig config); + + /** + * 如果通过getDefaultCustomViewConfig()获取的配置项,config内就有url,无需在传入 + */ + void parseCustomView(CustomViewConfig config); + + /** + * 按照默认配置解析 + */ + CustomViewConfig parseCustomView(String url); + + /** + * 获取默认配置,返回的config还需要调用parseXXX进行封装数据 + */ + CustomViewConfig getDefaultCustomViewConfig(String url); + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewHandlerImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewHandlerImpl.java new file mode 100644 index 0000000..f3b16f6 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewHandlerImpl.java @@ -0,0 +1,236 @@ +package com.centricsoftware.enhancement.modules.c8.service.customview.impl; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.dto.customview.CustomViewConfig; +import com.centricsoftware.enhancement.modules.c8.dto.customview.LambdaParam; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathExp; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.EnumTypeForCV; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.c8.service.customview.ICustomViewHandler; +import com.centricsoftware.enhancement.modules.c8.service.customview.ICustomViewParseService; +import com.centricsoftware.enhancement.modules.c8.util.CentricAttributesUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * description:Custom View Search Handler + * Date: 2024/5/15 16:26 + */ +@Service +@Slf4j +public class DefaultCustomViewHandlerImpl implements ICustomViewHandler { + + @Resource + ICustomViewParseService defaultCustomViewParseServiceImpl; + + @Resource + private C8NodeService c8NodeService; + @Override + public JSONArray get(String customViewUrl, DepPath depPath) { + CustomViewConfig config = defaultCustomViewParseServiceImpl.parseCustomView(customViewUrl); + return getAndLambdaByLine(customViewUrl,config,depPath,null); + } + + @Override + public JSONArray get(String customViewUrl, Map alisaMap,DepPath depPath) { + CustomViewConfig config = defaultCustomViewParseServiceImpl.parseCustomView(customViewUrl); + config.setAliasMap(alisaMap); + return getAndLambdaByLine(customViewUrl,config,depPath,null); + } + + @Override + public JSONArray get(String customViewUrl, CustomViewConfig config,DepPath depPath) { + defaultCustomViewParseServiceImpl.parseCustomView(customViewUrl,config); + return getAndLambdaByLine(customViewUrl,config,depPath,null); + } + + @Override + public JSONArray getAndLambdaByLine(String customViewUrl, CustomViewConfig config, DepPath depPath, Consumer function) { + LambdaParam parameter = new LambdaParam(); + parameter.setConfig(config); + //获取数据 + DepPathResult result = getDepPathData(config,depPath); + List resultRefs = result.getCompleteResultRefs(); + JSONArray array = JSONUtil.createArray(); + for(String url : resultRefs){ + //按视图配置封装数据 + JSONObject json = extractData(url,config,result); + parameter.setResult(json); + if(function!=null){ + function.accept(parameter); + } + array.add(json); + } + return array; + } + + /** + * 按视图配置封装数据 + */ + private JSONObject extractData(String url, CustomViewConfig config, DepPathResult result) { + List cols = config.getCols(); + JSONObject row = JSONUtil.createObj(); + //处理非matrix数据 + List normalCols = cols.stream().filter(i -> !i.isMatrix()).collect(Collectors.toList()); + extractNormalData(url,normalCols,row,config,result); + //处理matrix数据 + List matrixCols = cols.stream().filter(i -> i.isMatrix()).collect(Collectors.toList()); + extractMatrixData(url,matrixCols,row,config,result); + return row; + } + + /** + * 解析Matrix列数据 + * @param url matrix行URL + * @param row 行数据 + * @param result DepPathResult + */ + private void extractMatrixData(String url,List matrixCols, JSONObject row,CustomViewConfig config, DepPathResult result) { + //对matrixCols按照matrixAttributeId进行分组;每个分组表示一行对应的matrix数据(比如款式matrix到配色,这里一个分组表示一行款式对应的所有配色:ProductColors) + Map> matrixColsMap = matrixCols.stream().collect(Collectors.groupingBy(CustomViewConfig.ColumnConfig::getMatrixAttributeId)); + for(Map.Entry> entry : matrixColsMap.entrySet()){ + String matrixAttributeId = entry.getKey(); + //matrix的列 + List matrixColsList = entry.getValue(); + CustomViewConfig.ColumnConfig first = matrixColsList.get(0); + String matrixPath = first.getMatrixPath(); + List matrixUrls = result.getValue(matrixPath, url).getList(); + JSONArray array = JSONUtil.createArray(); + for(String matrixUrl : matrixUrls){ + //matrixUrl表示每个matrix行,如果是款式matrix到配色,则matrix表示是配色的URL + JSONObject matrixRow = JSONUtil.createObj(); + for(CustomViewConfig.ColumnConfig col : matrixColsList){ + String exp = col.getAttributeIdAfterMatrix(); + DepPathResultValue value = getOptimizationValue(exp, matrixUrl,result,config); + Map mapping = config.getAttributeIdMapping(); + String attributeId = col.getAttributeId(); + attributeId = mapping.getOrDefault(attributeId, attributeId); + Object object = transValue(value, config); + String fullPath = col.getFullPath(); + attributeId = mapping.getOrDefault(fullPath, attributeId); + matrixRow.put(attributeId,object); + } + array.add(matrixRow); + } + row.put(matrixAttributeId, array); + } + } + + /** + * 解析非Matrix列数据 + * @param url 行URL + * @param row 行数据 + * @param result DepPathResult + */ + private void extractNormalData(String url,List normalCols, JSONObject row,CustomViewConfig config, DepPathResult result) { + Map aliasMap = config.getAliasMap(); + row.put(aliasMap.getOrDefault("id","id"), url); + for(CustomViewConfig.ColumnConfig col : normalCols){ + String fullPath = col.getFullPath(); + Map mapping = config.getAttributeIdMapping(); + DepPathResultValue value = getOptimizationValue(fullPath, url,result,config); + String attributeId = mapping.getOrDefault(fullPath, fullPath); + Object object = transValue(value, config); + row.put(attributeId, object); + } + } + + /** + * 对List和Map类型进行转换 + */ + private Object transValue(DepPathResultValue value,CustomViewConfig config){ + DepPathExp lastDepPathExp = value.getLastDepPathExp(); + if(CentricAttributesUtil.isEnum(lastDepPathExp)){ + //此处不直接返回,是需要后续的list转换 + transEnumValue(value,config); + } + if(CentricAttributesUtil.valueIsList(value.getValue())){ + boolean listToString = config.isListToString(); + if(listToString){ + //List转String + return value.getStrForList(config.getConjunction()); + }else{ + return value.getList(); + } + } + if(CentricAttributesUtil.valueIsMap(value.getValue())){ + boolean mapToString = config.isMapToString(); + if(mapToString){ + return StrUtil.toString(value.getMap()); + }else{ + return value.getMap(); + } + } + return value.getValue(); + } + + + /** + * 获取值,如果配置了优化项,则需要执行优化 + */ + private DepPathResultValue getOptimizationValue(String exp,String url,DepPathResult result,CustomViewConfig config){ + DepPathResultValue value = result.getValue(exp, url); + boolean optimizationAttributeId = config.isOptimizationAttributeId(); + if(!optimizationAttributeId){ + //不优化Ref + return value; + } + DepPathExp lastDepPathExp = value.getLastDepPathExp(); + boolean ref = CentricAttributesUtil.isRef(lastDepPathExp); + if(!ref&&!exp.endsWith("$CR")){ + //非Ref相关字段,不执行优化 + return value; + } + String finalExp = StrFormatter.format("{}.Node Name",exp); + return result.getValue(finalExp, url); + } + + /** + * 转换枚举值 + */ + private DepPathResultValue transEnumValue(DepPathResultValue value, CustomViewConfig config) { + EnumTypeForCV enumTypeForCV = config.getEnumTypeForCV(); + if(enumTypeForCV==EnumTypeForCV.ORIGIN){ + return value; + } + if(enumTypeForCV==EnumTypeForCV.KEY){ + value.setValue(value.getEnumValueByType()); + } + if(enumTypeForCV==EnumTypeForCV.DESCRIPTION){ + value.setValue(value.getEnumDescByType()); + } + return value; + } + + + /** + * 查询C8,返回DepPathResult + */ + private DepPathResult getDepPathData(CustomViewConfig config,DepPath depPath) { + depPath.getPaths().addAll(config.getPaths()); + DepPathResult result; + if(!StrUtil.isBlankOrUndefined(depPath.getXml())){ + //DepPath.XML + result = c8NodeService.depPathByXml(depPath); + }else{ + //DepPath.URL + result = c8NodeService.depPathByUrl(depPath); + } + return result; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewParseServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewParseServiceImpl.java new file mode 100644 index 0000000..05fa53f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/customview/impl/DefaultCustomViewParseServiceImpl.java @@ -0,0 +1,363 @@ +package com.centricsoftware.enhancement.modules.c8.service.customview.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.dto.customview.CustomViewConfig; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.c8.service.customview.ICustomViewParseService; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * description: + * Date: 2024/5/14 17:26 + */ +@Service +@Slf4j +public class DefaultCustomViewParseServiceImpl implements ICustomViewParseService { + + @Resource + private C8NodeService c8NodeService; + + @Override + public void parseCustomView(String url, CustomViewConfig config) { + config.setViewUrl(url); + parseCustomView(config); + } + + @Override + public CustomViewConfig parseCustomView(String url) { + CustomViewConfig config = getDefaultCustomViewConfig(url); + parseCustomView(config); + return config; + } + + @Override + public CustomViewConfig getDefaultCustomViewConfig(String url) { + return new CustomViewConfig(url); + } + + @Override + public void parseCustomView(CustomViewConfig config) { + /** + * 初始化配置,封装视图的列配置:将Keys按顺序装载 + */ + initCustomViewConfig(config); + /** + * 解析列配置 + */ + List cols = config.getCols(); + for(CustomViewConfig.ColumnConfig column : cols){ + String originExp = column.getOriginExpressions(); + //设置BO路径和列对应的BO + extractBusinessObjectName(originExp,column); + //提取路径,获取路径和属性ID + extractPath(originExp,column); + //提取matrix信息 + extractMatrixInfo(originExp,column); + } + //封装自定义视图配置 + extractCustomViewConfig(config); + //设置字段映射,如果配置了aliasMap,则用aliasMap覆盖 + initAttributeIdMapping(config); + } + + /** + * 设置字段映射,如果配置了alias则用aliasMap覆盖 + * 1、如果attributeId不重复,则直接使用attributeId作为最终字段名 + * 2、如果attributeId重复,则通过字段+流水作为字段名;根据optimizationAttributeId是否配置,会有不同的流水规则 + * 3、Node Name做了特殊处理。如果非当前对象(targetClass)的Node Name,则会拼接前一个path + * @param config + */ + private void initAttributeIdMapping(CustomViewConfig config) { + Map aliasMap = config.getAliasMap(); + List cols = config.getCols(); + //对cols按照attributeId进行分组;path为空的数据不需要重新映射 + Map> groupByAttributeId = cols.stream().collect(Collectors.groupingBy(CustomViewConfig.ColumnConfig::getGroupId)); + //遍历groupByAttributeId + groupByAttributeId.forEach((attributeId, columnConfigs) -> { + //提取mapping信息 + extractAttributeIdMapping(config,columnConfigs); + }); + //对cols的matrix为true的,按照matrixAttributeId进行分组,如果分组个数大于1,则重置matrixAttributeId为matrixPath + resetMatrixAttributeId(cols); + //按照AlisaMapping重新设置Mapping + resetMappingWithAlisaMapping(config); + } + + /** + * 对cols的matrix为true的,按照matrixAttributeId进行分组,如果分组个数大于1,则重置matrixAttributeId为matrixPath + */ + private void resetMatrixAttributeId( List cols){ + List collect = cols.stream().filter(CustomViewConfig.ColumnConfig::isMatrix).collect(Collectors.toList()); + Map> groupByAttributeId = collect.stream().collect(Collectors.groupingBy(CustomViewConfig.ColumnConfig::getMatrixAttributeId)); + groupByAttributeId.forEach((k,v)->{ + if(v.size()>1){ + v.forEach(columnConfig -> { + columnConfig.setMatrixAttributeId(columnConfig.getMatrixPath()); + }); + } + }); + } + + /** + * 按照AlisaMapping重新设置Mapping + */ + private void resetMappingWithAlisaMapping(CustomViewConfig config) { + Map attributeIdMapping = config.getAttributeIdMapping(); + Map aliasMap = config.getAliasMap(); + if(CollUtil.isEmpty(aliasMap)){ + return; + } + //遍历attributeIdMapping,如果value在alisaMap中的key,则替换value + attributeIdMapping.forEach((key, value) -> { + if(aliasMap.containsKey(value)){ + attributeIdMapping.put(key,aliasMap.get(value)); + } + }); + } + + /** + * 提取Mapping信息 + */ + private void extractAttributeIdMapping(CustomViewConfig config, List columnConfigs) { + //重复 + boolean optimizationAttributeId = config.isOptimizationAttributeId(); + if(optimizationAttributeId){ + //优化attributeId + extractMappingWhenOptimizationAttributeId(config,columnConfigs); + }else{ + extractMappingNotOptimizationAttributeId(config,columnConfigs); + } + } + + /** + * 不优化attributeId,重复则直接拼接流水,不重复则直接使用attributeId作为字段名 + */ + private void extractMappingNotOptimizationAttributeId(CustomViewConfig config,List cols) { + Map mapping = config.getAttributeIdMapping(); + boolean repeat = cols.size() > 1; + if(!repeat){ + //不重复则直接使用attributeId作为字段名 + cols.forEach(column -> mapping.put(column.getFullPath(),column.getAttributeId())); + }else{ + //cols是否存在path为空的 + boolean match = cols.stream().anyMatch(column -> StrUtil.isEmpty(column.getPath())); + //cols中的attributeId重复了,则获取按照attributeId拼接流水 + int seq = 1; + for(int i=0; i cols) { + Map mapping = config.getAttributeIdMapping(); + boolean repeat = cols.size() > 1; + if(!repeat){ + //不重复则直接使用attributeId作为字段名 + cols.forEach(column -> mapping.put(column.getFullPath(),"Node Name".equals(column.getAttributeId())?"Name":column.getAttributeId())); + }else{ + //对cols按照path1进行分组 + Map> groupByPath1 = cols.stream().collect(Collectors.groupingBy(CustomViewConfig.ColumnConfig::getPath1)); + groupByPath1.forEach((path1, columnConfigs) -> { + if(columnConfigs.size()>1){ + //优化后再重复,则直接按照fullPath作为value值 + columnConfigs.forEach(i-> mapping.put(i.getFullPath(), StrUtil.replace(i.getFullPath(),"Node Name","Name"))); + }else{ + columnConfigs.forEach(column -> { + String name = path1.endsWith("Node Name")? StrUtil.replace(path1,"Node Name","Name"):path1; + mapping.put(column.getFullPath(),name); + }); + } + }); + } + } + + /** + * 将明细数据汇总到自定义视图配置中,此处可以做优化 + */ + private void extractCustomViewConfig(CustomViewConfig config) { + List cols = config.getCols(); + boolean optimizationAttributeId = config.isOptimizationAttributeId(); + //将ColumnConfig中的path汇总到config的path字段 + List paths = new ArrayList<>(); + for(CustomViewConfig.ColumnConfig column : cols){ + String path = column.getPath(); + if(optimizationAttributeId){ + path = column.getFullPath(); + } + if(StrUtil.isNotBlank(path)){ + paths.add(path); + } + } + config.setPaths(paths); + //将paths封装成Child:的格式,如果结尾是Node Name或者Name,则去除 + resetPathForDepPath(config); + } + + /** + *将paths封装成Child:的格式,如果结尾是Node Name或者Name,则去除 + */ + private void resetPathForDepPath(CustomViewConfig config) { + ArrayList list = Lists.newArrayList(); + List paths = config.getPaths(); + paths.forEach(i->{ + if(i.endsWith("Node Name")){ + i = StrUtil.subBefore(i,"Node Name",false); + }; + if(i.endsWith("Name")){ + i = StrUtil.subBefore(i,"Name",false); + }; + String replaceStr = i.replaceAll("\\.", "/Child:");//A.B.C=>A/Child:B/Child:C + //A/Child:B/Child:C => >Child:A/Child:B/Child:C + if(!StrUtil.isBlankOrUndefined(replaceStr)){ + String path = StrFormatter.format("Child:{}",replaceStr); + list.add(path); + } + }); + config.setPaths(list); + } + + + /** + * 提取Matrix信息 + */ + public void extractMatrixInfo(String expression,CustomViewConfig.ColumnConfig column) { + Pattern pattern = Pattern.compile("\\{(.*?)\\}$"); // 正则表达式匹配以"{attr}"结尾的部分 + Matcher matcher = pattern.matcher(expression); + if (matcher.find()) { + String matrixAttributeId = matcher.group(1); // 获取匹配到的attr部分 + column.setMatrixAttributeId(matrixAttributeId); + column.setMatrix(true); + String fullPath = column.getFullPath(); + int index = fullPath.indexOf(matrixAttributeId); + Assert.isTrue(index>=0,"路径解析错误:"+fullPath); + //计算matrixPath + column.setMatrixPath(fullPath.substring(0,0+matrixAttributeId.length())); + column.setAttributeIdAfterMatrix(StrUtil.subAfter(fullPath,StrFormatter.format("{}.",matrixAttributeId, index),false)); + } + } + + /** + * 提取路径,获取路径和属性ID + */ + public void extractPath(String expression,CustomViewConfig.ColumnConfig column) { + //清除()、{}、Child:、:0 + String cleanedExp = expression.replaceAll("\\{.*?\\}", "").replaceAll("\\(.*?\\)", "").replace("Child:", ""); + cleanedExp = cleanedExp.replaceAll(":0",""); + //将/换成. + cleanedExp = cleanedExp.replaceAll("/","\\."); + //用:对cleanedExp进行分组,第一个元素即为最终字段,其余元素则是路径 + String[] parts = cleanedExp.split(":"); + if (parts.length > 0) { + String attributeId = parts[0]; + //替换attributeId,按照{"___CR":"$CR","___CT","$CT"}规则,比如attributeId为___CR,则替换为$CR + attributeId = replaceAttributeId(attributeId); + //当parts.length==1时,表示无路径 + String path = ""; + //全路径包括最终字段 + String fullPath = attributeId; + if(parts.length>1){ + path = parts[1]; + fullPath = path+"."+attributeId; + } + column.setAttributeId(attributeId); + column.setPath(path); + column.setFullPath(fullPath); + //对path按照.进行split,获取最后一个,并拼接上attributeId + String path1 = StrUtil.subAfter(path, ".", true); + if(StrUtil.isBlankOrUndefined(path1)){ + column.setPath1(attributeId); + }else{ + column.setPath1(StrFormatter.format("{}.{}",path1,attributeId)); + } + } + } + + /** + * 因为视图有些字段在DepPath查询中没有,需要做转换 + * @param attributeId + * @return + */ + private String replaceAttributeId(String attributeId){ + HashMap map = Maps.newHashMap(); + map.put("___CR","$CR"); + map.put("___CT","$CT"); + if(map.containsKey(attributeId)){ + return map.get(attributeId); + } + return attributeId; + } + + /** + * 设置BO路径和列对应的BO + */ + public void extractBusinessObjectName(String expression,CustomViewConfig.ColumnConfig column) { + List paths = new ArrayList<>(); + Pattern pattern = Pattern.compile("\\((.*?)\\)"); + Matcher matcher = pattern.matcher(expression); + while (matcher.find()) { + paths.add(matcher.group(1)); // group(1) 获取第一个括号内匹配的内容 + } + String boPath = StrUtil.join(".", paths); + //设置BO路径 + column.setBoPath(boPath); + int size = paths.size(); + //设置最终值所属的BO + column.setBusinessObjectName(size >0?paths.get(size-1):""); + } + + /** + * 初始化配置,封装视图的列配置:将Keys按顺序装载 + */ + private void initCustomViewConfig(CustomViewConfig config) { + String viewUrl = config.getViewUrl(); + DepPathResult result = c8NodeService.queryUrl(viewUrl); + Map keys = result.getValue("Keys", viewUrl).getMap(); + List attributes = result.getValue("Attributes",viewUrl).getList(); + String targetClass = result.getValue("TargetClass",viewUrl).getStr(); + config.setTargetClass(targetClass); + List cols = config.getCols(); + for (int i = 0; i < attributes.size(); i++) { + CustomViewConfig.ColumnConfig columnConfig = config.new ColumnConfig(); + String attribute = attributes.get(i); + String key = keys.get(attribute); + columnConfig.setOriginExpressions(key); + cols.add(columnConfig); + } + config.setCustomViewResult(result); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/IDepPathEntitySearchService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/IDepPathEntitySearchService.java new file mode 100644 index 0000000..002542d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/IDepPathEntitySearchService.java @@ -0,0 +1,101 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep; + + +import cn.hutool.core.convert.Convert; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import org.apache.ibatis.reflection.invoker.Invoker; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * DepPath实体类查询字段类型处理接口,每个实现类处理一种字段类型 + */ +public interface IDepPathEntitySearchService { + List getHandleTypes(); + + /** + * 是否执行后续的责任链 + * @param context 上下文参数 + * @return true表示继续执行后续责任链节点;false表示终止 + */ + boolean isContinue(DepPathByEntityContext context); + + /** + * 是否执行当前责任链 + * @param context 上下文参数 + * @return true表示当前节点会处理 + */ + boolean isHandle(DepPathByEntityContext context); + + /** + * 执行当前节点逻辑:赋值逻辑需要通过反射执行 + * @param context 上下文参数 + */ + void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException; + + + /** + * + * @param context 上下文参数 + * @param value 需要设置的值。调用字段对应的setter方法,因为方法可能存在多个入参,所以value为数组 + */ + default void setFieldValue(DepPathByEntityContext context,Object[] value) throws InvocationTargetException, IllegalAccessException { + if(value==null){ + return; + } + /** + * 强制类型转换,避免报错 + */ + for(int i=0;i type = context.getField().getType(); + value[i] = Convert.convert(type,o); + } + Object instance = context.getInstance(); + Invoker invoker = context.getInvoker(); + if(invoker!=null){ + invoker.invoke(instance, value); + } + + } + + /** + * 是否是当前节点需要处理的类型;可以通过重新该方法修改规则 + * @return true表示当前节点需要处理;false表示不需要 + */ + default boolean isCurrentHandleType(DepPathByEntityContext context){ + List handleTypes = getHandleTypes(); + if(handleTypes==null){ + return true; + } + if(handleTypes.size()==0){ + return false; + } + DepPathEntityType type = context.getDepPathField().type(); + if(handleTypes.contains(type)){ + return true; + } + return false; + } + + + /** + * 获取当前值 + * @param context + * @return + */ + default DepPathResultValue getValue(DepPathByEntityContext context){ + DepPathField depPathField = context.getDepPathField(); + String exp = depPathField.exp(); + String url = context.getUrl(); + DepPathResult depPathResult = context.getDepPathResult(); + DepPathResultValue value = depPathResult.getValue(exp, url); + return value; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DateDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DateDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..66fb8cf --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DateDepPathEntitySearchServiceImpl.java @@ -0,0 +1,60 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import cn.hutool.core.convert.Convert; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(999) +@Component +public class DateDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.DATA_STRING); + types.add(DepPathEntityType.DATA); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException{ + DepPathField depPathField = context.getDepPathField(); + DepPathEntityType type = depPathField.type(); + DepPathResultValue value = getValue(context); + Class clazz = type.getClazz(); + Object v = value.getValue(); + if(DepPathEntityType.DATA==type){ + v = value.getDate(); + } + if(DepPathEntityType.DATA_STRING==type){ + v = value.getDateFormat(depPathField.timeFormat()); + } + Object convertValue = Convert.convert(clazz, v); + Object[] valueArray = new Object[]{convertValue}; + setFieldValue(context,valueArray); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DefaultDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DefaultDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..33b1b54 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/DefaultDepPathEntitySearchServiceImpl.java @@ -0,0 +1,57 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import cn.hutool.core.convert.Convert; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(999) +@Component +public class DefaultDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.STRING); + types.add(DepPathEntityType.REF); + types.add(DepPathEntityType.INTEGER); + types.add(DepPathEntityType.DOUBLE); + types.add(DepPathEntityType.FILE); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException{ + DepPathField depPathField = context.getDepPathField(); + DepPathEntityType type = depPathField.type(); + DepPathResultValue value = getValue(context); + Class clazz = type.getClazz(); + Object convertValue = Convert.convert(clazz, value.getValue()); + Object[] valueArray = new Object[]{convertValue}; + setFieldValue(context,valueArray); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..131cf31 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumDepPathEntitySearchServiceImpl.java @@ -0,0 +1,56 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(700) +@Component +public class EnumDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.ENUM_VALUE); + types.add(DepPathEntityType.ENUM_DESC); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException{ + DepPathField depPathField = context.getDepPathField(); + DepPathResultValue value = getValue(context); + String valueStr = value.getStr(); + if(depPathField.type()==DepPathEntityType.ENUM_VALUE){ + valueStr = value.getEnumValue(); + } + if(depPathField.type()==DepPathEntityType.ENUM_DESC){ + valueStr = value.getEnumDesc(); + } + Object[] valueArray = new Object[]{valueStr}; + setFieldValue(context,valueArray); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..1762375 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListDepPathEntitySearchServiceImpl.java @@ -0,0 +1,58 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(710) +@Component +public class EnumListDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.ENUM_DESC_LIST); + types.add(DepPathEntityType.ENUM_VALUE_LIST); + types.add(DepPathEntityType.ENUM_DESC_LIST_TO_String); + types.add(DepPathEntityType.ENUM_VALUE_LIST_TO_String); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException{ + DepPathField depPathField = context.getDepPathField(); + DepPathResultValue value = getValue(context); + List valueList = value.getList(); + if(depPathField.type()==DepPathEntityType.ENUM_DESC_LIST){ + valueList = value.getEnumDescList(depPathField.removeDuplicate()); + } + if(depPathField.type()==DepPathEntityType.ENUM_VALUE_LIST){ + valueList = value.getEnumValueList(depPathField.removeDuplicate()); + } + Object[] valueArray = new Object[]{valueList}; + setFieldValue(context,valueArray); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListToStringDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListToStringDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..18bd33f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/EnumListToStringDepPathEntitySearchServiceImpl.java @@ -0,0 +1,58 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(710) +@Component +public class EnumListToStringDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.ENUM_DESC_LIST_TO_String); + types.add(DepPathEntityType.ENUM_VALUE_LIST_TO_String); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException{ + DepPathField depPathField = context.getDepPathField(); + DepPathResultValue value = getValue(context); + List valueList = value.getList(); + if(depPathField.type()==DepPathEntityType.ENUM_DESC_LIST_TO_String){ + value.getEnumDescList(depPathField.removeDuplicate()); + } + if(depPathField.type()==DepPathEntityType.ENUM_VALUE_LIST_TO_String){ + valueList = value.getEnumValueList(depPathField.removeDuplicate()); + } + String valueStr = StrUtil.join(depPathField.conjunctionForListToString(), valueList); + Object[] valueArray = new Object[]{valueStr}; + setFieldValue(context,valueArray); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..dd16455 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListDepPathEntitySearchServiceImpl.java @@ -0,0 +1,50 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(600) +@Component +public class ListDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.LIST); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException { + DepPathField depPathField = context.getDepPathField(); + boolean removeDuplicate = depPathField.removeDuplicate(); + Class generic = depPathField.generic(); + DepPathResultValue value = getValue(context); + Object[] valueArray = new Object[]{value.getList(removeDuplicate,generic)}; + setFieldValue(context,valueArray); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListToStringDepPathEntitySearchServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListToStringDepPathEntitySearchServiceImpl.java new file mode 100644 index 0000000..7c55716 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/dep/impl/ListToStringDepPathEntitySearchServiceImpl.java @@ -0,0 +1,51 @@ +package com.centricsoftware.enhancement.modules.c8.service.dep.impl; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathByEntityContext; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.centricsoftware.enhancement.modules.c8.service.dep.IDepPathEntitySearchService; +import com.google.common.collect.Lists; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * description:默认处理器。处理C8字段类型为ref、double、integer、file + * Date: 2024/3/8 12:33 + */ +@Order(800) +@Component +public class ListToStringDepPathEntitySearchServiceImpl implements IDepPathEntitySearchService { + + @Override + public List getHandleTypes() { + List types = Lists.newArrayList(); + types.add(DepPathEntityType.LIST_TO_String); + return types; + } + + @Override + public boolean isContinue(DepPathByEntityContext context) { + return true; + } + + @Override + public boolean isHandle(DepPathByEntityContext context) { + return isCurrentHandleType(context); + } + + @Override + public void process(DepPathByEntityContext context) throws InvocationTargetException, IllegalAccessException { + DepPathField depPathField = context.getDepPathField(); + DepPathResultValue resultValue = getValue(context); + List value = resultValue.getList(depPathField.removeDuplicate(), String.class); + String valueStr = StrUtil.join(depPathField.conjunctionForListToString(),value); + Object[] valueArray = new Object[]{valueStr}; + setFieldValue(context,valueArray); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/es/LogElasticsearchService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/es/LogElasticsearchService.java new file mode 100644 index 0000000..7ae184e --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/service/es/LogElasticsearchService.java @@ -0,0 +1,259 @@ +package com.centricsoftware.enhancement.modules.c8.service.es; + +import cn.hutool.json.JSONUtil; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.FieldSort; +import co.elastic.clients.elasticsearch._types.SortOrder; +import co.elastic.clients.elasticsearch._types.aggregations.Aggregate; +import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; +import co.elastic.clients.elasticsearch._types.aggregations.AggregationBuilders; +import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch.core.IndexRequest; +import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.elasticsearch.core.search.HitsMetadata; +import co.elastic.clients.json.JsonData; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.utils.JsonUtil; +import com.centricsoftware.config.entity.EsProperties; +import com.centricsoftware.enhancement.dto.log.PlmLog; +import com.centricsoftware.enhancement.dto.log.QueryPlmLogReq; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.annotation.Resource; +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * es + * @author liaochangjiang + * @since 2024-04-26 15:29 + */ +@Service +@Slf4j +public class LogElasticsearchService { + + @Resource + protected ElasticsearchClient client; + + @Resource + protected EsProperties esProperties; + + /** + * @param api 调用的接口 + * @param ip 调用方IP + * @param param 接口参数 + * @author liaochangjiang + * @since 2021-12-28 17:32:57 + */ + public PlmLog newLog(String api, String ip, Object param, String desc) { + if (param == null) { + param = StringUtils.EMPTY; + } + return new PlmLog() + .setPath(api) + .setName(desc) + .setHost(ip) + .setBrand("PLM") + .setParam((param instanceof String) ? (String) param : JSONUtil.toJsonStr(param)) + .setStartTime(LocalDateTime.now()); + } + + /** + * 更新日志 + * + * @param apiLog log实体类 + * @param success 是否成功 + * @param errorCode 错误编码 + * @param msg 错误信息 + * @param returnText 接口返回值 + */ + public void updateLog(PlmLog apiLog, boolean success, String errorCode, String msg, String returnText) { + int code; + try { + code = Integer.parseInt(errorCode); + } catch (NumberFormatException e) { + code = 500; + } + ResEntity respVo = ResEntity.builder().code(success ? 0 : code).msg(msg).success(true).build(); + if (StringUtils.isNotBlank(returnText)) { + respVo.setData(returnText); + } + buildUpdatePlmLog(apiLog, respVo); + } + + /** + * 构建更新参数 + * + * @author liaochangjiang + * @since 2021-12-27 17:10:23 + */ + @SuppressWarnings("rawtypes") + public void buildUpdatePlmLog(PlmLog data, Object response) { + data.setEndTime(LocalDateTime.now()) + .setCostMs(Duration.between(data.getStartTime(), data.getEndTime()).toMillis()); + if (response == null) { + Optional.ofNullable(RequestContextHolder.getRequestAttributes()).map(t -> (ServletRequestAttributes) t) + .map(ServletRequestAttributes::getResponse).ifPresent( + resp -> data.setSuccess(resp.getStatus() == 200).setReturnCode(String.valueOf(resp.getStatus()))); + return; + } + if (response instanceof ResEntity) { + ResEntity resp = (ResEntity) response; + data.setSuccess(resp.isSuccess()).setReturnCode(String.valueOf(resp.getCode())).setReturnMsg(resp.getMsg()) + .setReturnText(JsonUtil.toJSONString(resp)); + } else { + data.setReturnText(JsonUtil.toJSONString(response)); + } + } + + /** + * 保存日志 + * + * @author liaochangjiang + * @since 2021-12-17 15:39:55 + */ + public void saveLog(PlmLog data) { + IndexRequest req = new IndexRequest.Builder() + .index(esProperties.getLogIndex()) + .document(data) + .build(); + IndexResponse index = null; + try { + index = client.index(req); + if (log.isDebugEnabled()) { + log.debug("index: {}, id:{}, result:{}", index.index(), index.id(), index.result()); + } + } catch (IOException e) { + log.error("保存操作记录失败", e); + } + } + + /** + * 查询分页日志 + * + * @author liaochangjiang + * @since 2021-12-17 16:38:24 + */ + public Page queryLogs(Page page, QueryPlmLogReq req) throws IOException { + SearchResponse search = client.search(s -> s + .index(esProperties.getLogIndex()) + .query(q -> { + BoolQuery.Builder builder = QueryBuilders.bool(); + List param = new ArrayList<>(); + if (StringUtils.isNotBlank(req.getPath())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("path").value(v -> v.stringValue(req.getPath())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getName())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("name").value(v -> v.stringValue(req.getName())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getBrand())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("brand").value(v -> v.stringValue(req.getBrand())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getStartTimeBegin())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("@timestamp").gte(JsonData.of(req.getStartTimeBegin())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getStartTimeEnd())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("@timestamp").lt(JsonData.of(req.getStartTimeEnd())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getEndTimeBegin())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("endTime").gte(JsonData.of(req.getEndTimeBegin())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getEndTimeEnd())) { + param.add(new Query.Builder() + .range(QueryBuilders.range().field("endTime").lt(JsonData.of(req.getEndTimeEnd())).build()) + .build()); + } + if (Objects.nonNull(req.getSuccess())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("success").value(v -> v.booleanValue(req.getSuccess())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getHost())) { + param.add(new Query.Builder() + .term(QueryBuilders.term().field("host").value(v -> v.stringValue(req.getHost())).build()) + .build()); + } + if (StringUtils.isNotBlank(req.getParam())) { + param.add(new Query.Builder() + .matchPhrase(QueryBuilders.matchPhrase().field("param").query(req.getParam()).build()) + .build()); + } + if (Objects.nonNull(req.getReturnCode())) { + param.add(new Query.Builder().term(QueryBuilders.term().field("returnCode") + .value(v -> v.longValue(req.getReturnCode())).build()).build()); + } + if (StringUtils.isNotBlank(req.getReturnText())) { + param.add(new Query.Builder().matchPhrase( + QueryBuilders.matchPhrase().field("returnText").query(req.getReturnText()) + .build()).build()); + } + if (StringUtils.isNotBlank(req.getDebug())) { + param.add(new Query.Builder().matchPhrase( + QueryBuilders.matchPhrase().field("debug").query(req.getDebug()).build()) + .build()); + } + builder = builder.filter(param); + return q.bool(builder.build()); + }) + .from((int) ((req.getPageNum() - 1) * req.getPageSize().intValue())) + .size(req.getPageSize().intValue()) + .sort(t -> t.field(new FieldSort.Builder().field("@timestamp").order(SortOrder.Desc).build())) + , + PlmLog.class); + HitsMetadata hits = search.hits(); + long total = hits.total().value(); + List list = hits.hits().stream().map(Hit::source).collect(Collectors.toList()); + return page.setTotal(total).setRecords(list); + } + + /** + * 获取日志接口名列表 + * + * @author liaochangjiang + * @since 2021-12-20 16:07:26 + */ + public List listLogNames(String brand) throws IOException { + SearchResponse resp = client.search(s -> s + .index(esProperties.getLogIndex()) + .query(q -> q + .term(t -> t + .field("brand") + .value(v -> v.stringValue(brand)) + ) + ) + .aggregations("desc", Aggregation.of(t -> t.terms(AggregationBuilders.terms().field("name").size(300).build()))) + .size(0) + , PlmLog.class); + Aggregate aggr = resp.aggregations().get("desc"); + List array = aggr.sterms().buckets().array(); + return array.stream().map(StringTermsBucket::key).collect(Collectors.toList()); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/util/CentricAttributesUtil.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/util/CentricAttributesUtil.java new file mode 100644 index 0000000..9ab8f90 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/c8/util/CentricAttributesUtil.java @@ -0,0 +1,133 @@ +package com.centricsoftware.enhancement.modules.c8.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.modules.c8.dto.dep.CentricAttrs; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathExp; +import com.centricsoftware.enhancement.modules.c8.em.ParserFieldType; + +import java.util.List; +import java.util.Map; + +/** + * description: + * Date: 2024/2/5 0:15 + */ +public class CentricAttributesUtil { + + + /** + * 如果C8字段类型为list、vector、set结尾,返回true + */ + public static boolean isList(String type) { + if(StrUtil.endWithAny(type,"list","vector","set")){ + return true; + } + return false; + } + + /** + * 如果C8字段类型为list、vector、set结尾,返回true + */ + public static boolean isList(DepPathExp exp) { + if(exp==null){ + return false; + } + CentricAttrs attrs = exp.getAttrs(); + if(attrs==null){ + return false; + } + String type = attrs.getType(); + return CentricAttributesUtil.isList(type); + } + + /** + * 如果C8字段类型为map结尾,返回true + */ + public static boolean isMap(String type) { + if(StrUtil.endWithAny(type,"map")){ + return true; + } + return false; + } + + /** + * 如果C8字段类型为map结尾,返回true + */ + public static boolean isMap(DepPathExp exp) { + if(exp==null){ + return false; + } + CentricAttrs attrs = exp.getAttrs(); + if(attrs==null){ + return false; + } + String type = attrs.getType(); + return CentricAttributesUtil.isMap(type); + } + + + public static boolean isRef(String type) { + if(StrUtil.startWith(type,"ref")){ + return true; + } + return false; + } + + public static boolean isRef(DepPathExp exp) { + if(exp==null){ + return false; + } + CentricAttrs attrs = exp.getAttrs(); + if(attrs==null){ + return false; + } + String type = attrs.getType(); + return CentricAttributesUtil.isRef(type); + } + + public static boolean isEnum(DepPathExp exp) { + if(exp==null){ + return false; + } + CentricAttrs attrs = exp.getAttrs(); + if(attrs==null){ + return false; + } + String type = attrs.getType(); + if(StrUtil.startWith(type,"enum")){ + return true; + } + return false; + } + + + public static ParserFieldType getParserFieldType(String type) { + if(StrUtil.endWithAny(type,"list","vector","set")){ + return ParserFieldType.LIST; + } + if(StrUtil.endWithAny(type,"map")){ + return ParserFieldType.MAP; + } + if("ref".equals(type)){ + return ParserFieldType.REF; + } + return ParserFieldType.OTHER; + } + + public static boolean valueIsList(Object value){ + if(value instanceof List ||(value.toString().startsWith("[")&&value.toString().endsWith("]"))){ + return true; + } + return false; + } + + public static boolean valueIsMap(Object value){ + if(value instanceof Map ||(value.toString().startsWith("{")&&value.toString().endsWith("}"))) { + return true; + } + return false; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/CustomViewDemoController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/CustomViewDemoController.java new file mode 100644 index 0000000..4ccb1dc --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/CustomViewDemoController.java @@ -0,0 +1,62 @@ +package com.centricsoftware.enhancement.modules.demo.controller; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.enhancement.modules.c8.dto.customview.CustomViewConfig; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.service.customview.ICustomViewHandler; +import com.centricsoftware.enhancement.modules.c8.service.customview.ICustomViewParseService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.annotation.Resource; + +/** + * description:CustomView 查询测试 + * Date: 2024/5/16 10:17 + */ +@Slf4j +@RequestMapping("/cv") +@Controller +public class CustomViewDemoController { + + @Resource + private ICustomViewHandler defaultCustomViewHandlerImpl; + + /** + * 未优化查询 + */ + @ResponseBody + @RequestMapping("/search") + public ResEntity search() throws Exception { + //以太平鸟测试机,款式-配色视图 + DepPath build = DepPath.builder().addUrl("C36131").build(); + JSONArray jsonArray = defaultCustomViewHandlerImpl.get("C3155", build); + log.info(JSONUtil.toJsonPrettyStr(jsonArray)); + return WebResponse.success(ResCode.SUCCESS); + } + + @Resource + ICustomViewParseService defaultCustomViewParseServiceImpl; + + /** + * 优化查询 + */ + @ResponseBody + @RequestMapping("/search_opt") + public ResEntity searchOptimizationAttributeId() throws Exception { + String cv = "C36131"; + CustomViewConfig config = defaultCustomViewParseServiceImpl.getDefaultCustomViewConfig(cv); + config.setOptimizationAttributeId(true); + //以太平鸟测试机,款式-配色视图 + DepPath build = DepPath.builder().addUrl(cv).build(); + JSONArray jsonArray = defaultCustomViewHandlerImpl.get("C3155",config,build); + log.info(JSONUtil.toJsonPrettyStr(jsonArray)); + return WebResponse.success(ResCode.SUCCESS); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DDLDemoController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DDLDemoController.java new file mode 100644 index 0000000..9875efb --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DDLDemoController.java @@ -0,0 +1,66 @@ +package com.centricsoftware.enhancement.modules.demo.controller; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8Search; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8SearchAttribute; +import com.centricsoftware.enhancement.modules.dml.util.C8Write; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.ArrayList; + +/** + * description: + * Date: 2024/7/2 16:16 + */ +@Slf4j +@RequestMapping("/ddl") +@Controller +public class DDLDemoController { + + @ResponseBody + @RequestMapping("/search") + public ResEntity search() throws Exception { + C8Search style = C8Search.newSearch("Style"); + C8SearchAttribute or = style.or(); + or.attr("Code","EQ","10086"); + String xml = style.getXml(); + log.info(xml); + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/operation") + public ResEntity operation() throws Exception { + String operation = C8Write.changeNode("C10086") + .changeAttribute("Code", "string", "1") + .getXml(); + log.info(operation); + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/delete") + public ResEntity delete() throws Exception { + String delete = C8Write.deleteNode("C10086").getXml(); + log.info(delete); + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/changeList") + public ResEntity changeList() throws Exception { + ArrayList values = new ArrayList<>(); + values.add("C0001"); + String operation = C8Write.changeNode("C10086") + .changeAttributeList("Colorways", "reflist", values,"ref") + .getXml(); + log.info(operation); + return WebResponse.success(ResCode.SUCCESS); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DepPathDemoController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DepPathDemoController.java new file mode 100644 index 0000000..3a28566 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/DepPathDemoController.java @@ -0,0 +1,151 @@ +package com.centricsoftware.enhancement.modules.demo.controller; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.component.design.chain.IDepPathSearchChain; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPathResultValue; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.c8.service.curd.C8SearchService; +import com.centricsoftware.enhancement.modules.demo.dto.dep.SampleDemoDto; +import com.centricsoftware.enhancement.modules.demo.dto.dep.StyleDemoDTO; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.annotation.Resource; +import java.math.RoundingMode; +import java.util.List; + +/** + * description: + * Date: 2024/3/15 10:10 + */ +@Slf4j +@RequestMapping("/dep") +@Controller +public class DepPathDemoController { + + @Resource + C8SearchService c8SearchService; + + /** + * DEMO的URL为款式 + * @throws Exception + */ + @ResponseBody + @RequestMapping("/url") + public ResEntity depPathByUrl() throws Exception { + DepPath build = DepPath.builder().addUrl("C36133") + .addPath("Child:ActiveColorways/Child:C8_CW_PhotoSizes") + .addPath("Child:ActiveColorways/Child:Images") + .addPath("Child:Images/Child:C8_Image_MappingOSS") + .build(); + DepPathResult depPathResult = c8SearchService.depPathByUrl(build); + log.info("================================================="); + + DepPathResultValue value0 = depPathResult.getValue("$Name", "C36133"); + log.info("$Name={}",value0.getValue()); + + DepPathResultValue value2 = depPathResult.getValue("ActiveColorways.Attributes.C8_CW_PhotoSizes", "C36133"); + log.info("ActiveColorways.Attributes.C8_CW_PhotoSizes={}",value2.getList()); + + DepPathResultValue value21 = depPathResult.getValue("ActiveColorways.C8_CW_PhotoSizes", "C36133"); + DepPathResultValue value22 = depPathResult.getValue("ActiveColorways.C8_CW_PhotoSizes[1]", "C36133"); + log.info("ActiveColorways.C8_CW_PhotoSizes={}",value21.getList()); + log.info("ActiveColorways.C8_CW_PhotoSizes[1]={}",value22.getStr()); + + DepPathResultValue value1 = depPathResult.getValue("ActiveColorways", "C36133"); + log.info("ActiveColorways={}",value1.getList()); + + DepPathResultValue value3 = depPathResult.getValue("Images.Thumbnail", "C36133"); + log.info("List => Images.Thumbnail={}",value3.getList()); + log.info("Map =>Images.Thumbnail={}",value3.getMap()); + + DepPathResultValue value4 = depPathResult.getValue("Images", "C36133"); + log.info("Map =>Images={}",value4.getMap()); + + DepPathResultValue value5 = depPathResult.getValue("ActiveColorways.Images", "C36133"); + log.info("Map =>ActiveColorways.Images={}",value5.getMap()); + + DepPathResultValue value6 = depPathResult.getValue("Images.C8_Image_MappingOSS", "C36133"); + log.info("Map =>Images.C8_Image_MappingOSS={}",value6.getMap()); + + DepPathResultValue value7 = depPathResult.getValue("ModifiedAt", "C36133"); + log.info("Date =>ModifiedAt={}",value7.getDateStr()); + log.info("Date =>ModifiedAt={}",value7.getDateAndTimeStr()); + log.info("Date =>ModifiedAt={}",value7.getDateFormat("yyyy/MM/dd")); + log.info("Date =>ModifiedAt={}",value7.getDate()); + + DepPathResultValue value8 = depPathResult.getValue("C8_Style_GarmentLength", "C36133"); + log.info("Double =>C8_Style_GarmentLength={}",value8.getDouble()); + log.info("Double =>C8_Style_GarmentLength={}",value8.getDouble(2)); + log.info("Double =>C8_Style_GarmentLength={}",value8.getBigDecimal(1, RoundingMode.HALF_UP)); + + log.info("Double 1.24 => {}",new DepPathResultValue("1.24").getDouble(1)); + log.info("Double 1.25 => {}",new DepPathResultValue("1.25").getDouble(1)); + log.info("Double 1.26 => {}",new DepPathResultValue("1.26").getDouble(1)); + log.info("Double 1.99999999 => {}",new DepPathResultValue("1.99999999").getDouble(1)); + log.info("================================================="); + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/fix") + public ResEntity fix() throws Exception { + DepPath build = DepPath.builder().addUrl("C193244") + .addPath("Child:CurrentRevision/Child:BOMProductColors") + .build(); + DepPathResult depPathResult = c8SearchService.depPathByUrl(build); + log.info("================================================="); + DepPathResultValue value0 = depPathResult.getValue("CurrentRevision.BOMProductColors", "C193244"); + log.info("value ={}",value0.getList()); + DepPathResultValue value1 = depPathResult.getValue("CurrentRevision.BOMProductColors.Code", "C193244"); + log.info("value ={}",value1.getList()); + log.info("================================================="); + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/fix1") + public ResEntity fix1() throws Exception { + DepPath build = DepPath.builder().addUrl("C193244") + .addPath("Child:CurrentRevision/Child:BOMProductColors") + .build(); + DepPathResult depPathResult = c8SearchService.depPathByUrl(build); + log.info("================================================="); + DepPathResultValue value0 = depPathResult.getValue("CurrentRevision.BOMProductColors[0]", "C193244"); + log.info("value ={}",value0.getList()); + log.info("================================================="); + return WebResponse.success(ResCode.SUCCESS); + } + + @Resource + IDepPathSearchChain iDepPathSearchChain; + + @Resource + C8NodeService c8NodeService; + + @ResponseBody + @RequestMapping("/dto") + public ResEntity depPathByDTO(){ + DepPath deppath = DepPath.builder().addUrl("C2567990").build(); + List execute = iDepPathSearchChain.execute(deppath, StyleDemoDTO.class, null); +// List styleDemoDTOS = c8NodeService.depPathByEntity(deppath, StyleDemoDTO.class); + log.info(execute.toString()); + return WebResponse.success(ResCode.SUCCESS); + } + + @ResponseBody + @RequestMapping("/dto1") + public ResEntity depPathByDTO1(){ + DepPath deppath = DepPath.builder().addUrl("C765541").build(); + List execute = iDepPathSearchChain.execute(deppath, SampleDemoDto.class, null); + log.info(execute.toString()); + return WebResponse.success(ResCode.SUCCESS); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/FeignLogDemoController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/FeignLogDemoController.java new file mode 100644 index 0000000..8410ebc --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/FeignLogDemoController.java @@ -0,0 +1,62 @@ +package com.centricsoftware.enhancement.modules.demo.controller; + +import com.centricsoftware.commons.ant.ControllerLog; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.enhancement.modules.c8.component.design.chain.IDepPathSearchChain; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.c8.service.customview.ICustomViewHandler; +import com.centricsoftware.enhancement.modules.demo.dto.dep.StyleDemoDTO; +import com.centricsoftware.enhancement.modules.demo.feign.LocalhostFeign; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.annotation.Resource; +import java.util.List; + +/** + * description:CustomView 查询测试 + * Date: 2024/5/16 10:17 + */ +@Slf4j +@RequestMapping("/feign-log") +@Controller +public class FeignLogDemoController { + + @Resource + private ICustomViewHandler defaultCustomViewHandlerImpl; + + @ResponseBody + @RequestMapping("/receive") + public ResEntity search(@RequestBody StyleDemoDTO styleDemoDTO) throws Exception { + log.info(styleDemoDTO.toString()); + return WebResponse.success(ResCode.SUCCESS); + } + @ResponseBody + @RequestMapping("/receive-list") + public ResEntity search(@RequestBody List styleDemoDTO) throws Exception { + log.info(styleDemoDTO.toString()); + return WebResponse.success(ResCode.SUCCESS); + } + + + @Resource + IDepPathSearchChain iDepPathSearchChain; + + @Resource + LocalhostFeign localhostFeign; + @ResponseBody + @RequestMapping("/send") + public ResEntity depPathByDTO1(){ + DepPath deppath = DepPath.builder().addUrl("C120527").build(); + List execute = iDepPathSearchChain.execute(deppath, StyleDemoDTO.class, null); + localhostFeign.sendStyles(execute); + localhostFeign.sendStyle(execute.get(0)); + return WebResponse.success(ResCode.SUCCESS); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/MultipleDataSourceDemoController.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/MultipleDataSourceDemoController.java new file mode 100644 index 0000000..e8532b9 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/controller/MultipleDataSourceDemoController.java @@ -0,0 +1,33 @@ +package com.centricsoftware.enhancement.modules.demo.controller; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.enhancement.modules.demo.service.IMultipleDataSourceDemoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; + +/** + * description:多数据源测试类 + * Date: 2024/9/9 15:42 + */ +@Slf4j +@RequestMapping("/multiple-data-source") +@RestController +public class MultipleDataSourceDemoController { + + @Resource + private IMultipleDataSourceDemoService multipleDataSourceDemoService; + @RequestMapping("hse") + public ResEntity searchPartMaterial(){ + List hashMaps = multipleDataSourceDemoService.querySeason(); + log.info("查询结果:{}",hashMaps); + return WebResponse.success(ResCode.SUCCESS,hashMaps); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayAttributesDemoDTO.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayAttributesDemoDTO.java new file mode 100644 index 0000000..f444788 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayAttributesDemoDTO.java @@ -0,0 +1,18 @@ +package com.centricsoftware.enhancement.modules.demo.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import lombok.Data; + +import java.io.Serializable; + +/** + * description: + * Date: 2024/3/15 10:21 + */ +@Data +public class ColorwayAttributesDemoDTO implements Serializable { + + @DepPathField(exp = "__DomainKey__") + private String domainKey; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayDemoDTO.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayDemoDTO.java new file mode 100644 index 0000000..214be6d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/ColorwayDemoDTO.java @@ -0,0 +1,31 @@ +package com.centricsoftware.enhancement.modules.demo.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import lombok.Data; + +import java.io.Serializable; + +/** + * description: + * Date: 2024/3/15 10:21 + */ +@Data +public class ColorwayDemoDTO implements Serializable { + + @DepPathField(exp = "$Name") + private String name; + + @DepPathField(exp = "Code") + private String code; + + @DepPathField(exp = "Active") + private Boolean active; + + @DepPathField(exp = "ClassConfig.$Name") + private String classConfigName; + + + @DepPathField(exp = "Attributes",recursion = true) + private ColorwayAttributesDemoDTO colorwayAttributesDemoDTO; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/SampleDemoDto.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/SampleDemoDto.java new file mode 100644 index 0000000..9cebf4c --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/SampleDemoDto.java @@ -0,0 +1,21 @@ +package com.centricsoftware.enhancement.modules.demo.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class SampleDemoDto { + @DepPathField(exp = "__Parent__.__Parent__") + private String image_url; + + @JsonProperty("customer_no") + @DepPathField(exp = "__Parent__.__Parent__.__Parent__.Collection") + private String customerNo; + + @JsonProperty("plm_url") + @DepPathField(exp = "$URL") + private String plmUrl; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleAttributesDemoDTO.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleAttributesDemoDTO.java new file mode 100644 index 0000000..4dd9f8e --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleAttributesDemoDTO.java @@ -0,0 +1,21 @@ +package com.centricsoftware.enhancement.modules.demo.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import lombok.Data; + +import java.io.Serializable; + +/** + * description: + * Date: 2024/3/15 10:20 + */ +@Data +public class StyleAttributesDemoDTO implements Serializable { + + @DepPathField(exp = "ActualSizeRange") + private String actualSizeRange; + + @DepPathField(exp = "BOMMainMaterialCount") + private String count; + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleDemoDTO.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleDemoDTO.java new file mode 100644 index 0000000..446e84f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/dto/dep/StyleDemoDTO.java @@ -0,0 +1,48 @@ +package com.centricsoftware.enhancement.modules.demo.dto.dep; + +import com.centricsoftware.enhancement.modules.c8.ant.DepPathField; +import com.centricsoftware.enhancement.modules.c8.em.DepPathEntityType; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * description: + * Date: 2024/3/15 10:20 + */ +@Data +public class StyleDemoDTO implements Serializable { + + @DepPathField(exp = "$Name") + private String styleName; + + @DepPathField(exp = "$URL") + private String url; + + @DepPathField(exp = "Active") + private Boolean active; + + @DepPathField(exp = "ModifiedAt",timeFormat="yyyy-MM-dd",type = DepPathEntityType.DATA_STRING) + private String dataStr; + + @DepPathField(exp = "AssortmentBOM.CurrentRevision") + private String currentRevision; + + @DepPathField(exp = "CntColorway") + private Integer cntColorway; + + @DepPathField(exp = "Collection",type = DepPathEntityType.REF) + private String collection; + + @DepPathField(exp = "ProductColors",recursion = true) + private List productColors; + + @DepPathField(exp = "ActiveColorways",type = DepPathEntityType.LIST) + private List activeColorways; + + @DepPathField(exp = "Attributes",recursion = true) + private StyleAttributesDemoDTO styleAttributesDemoDTO; + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/feign/LocalhostFeign.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/feign/LocalhostFeign.java new file mode 100644 index 0000000..fbe5ca8 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/feign/LocalhostFeign.java @@ -0,0 +1,26 @@ +package com.centricsoftware.enhancement.modules.demo.feign; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.enhancement.modules.c8.feign.C8Feign; +import com.centricsoftware.enhancement.modules.demo.dto.dep.StyleDemoDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + + +/** + * scm 接口调用客户端 + */ +@FeignClient(url = "http://localhost:8088/plmservice/",name = "localhost-api",configuration = C8Feign.MultipartSupportConfig.class) +public interface LocalhostFeign { + + @PostMapping("/feign-log/receive?c8_name=测试款式接口") + ResEntity sendStyle(@RequestBody StyleDemoDTO requestBody); + + @PostMapping("/feign-log/receive-list?c8_name=测试款式接口List") + ResEntity sendStyles(@RequestBody List requestBody); + +} + diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/mapper/MultipleDataSourceDemoMapper.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/mapper/MultipleDataSourceDemoMapper.java new file mode 100644 index 0000000..6ebbd2a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/mapper/MultipleDataSourceDemoMapper.java @@ -0,0 +1,21 @@ +package com.centricsoftware.enhancement.modules.demo.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import org.springframework.stereotype.Repository; + +import java.util.HashMap; +import java.util.List; + +/** + * description: + * Date: 2024/9/9 15:48 + */ +@Mapper +@Repository +public interface MultipleDataSourceDemoMapper extends BaseMapper { + + @Select("select * from ed_season limit 1") + List querySeason(); +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/IMultipleDataSourceDemoService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/IMultipleDataSourceDemoService.java new file mode 100644 index 0000000..d320f56 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/IMultipleDataSourceDemoService.java @@ -0,0 +1,16 @@ +package com.centricsoftware.enhancement.modules.demo.service; + +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; + +/** + * description: + * Date: 2024/9/9 15:45 + */ +@Service +public interface IMultipleDataSourceDemoService { + + List querySeason(); +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/impl/MultipleDataSourceDemoServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/impl/MultipleDataSourceDemoServiceImpl.java new file mode 100644 index 0000000..ce64af9 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/demo/service/impl/MultipleDataSourceDemoServiceImpl.java @@ -0,0 +1,27 @@ +package com.centricsoftware.enhancement.modules.demo.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.centricsoftware.enhancement.modules.demo.mapper.MultipleDataSourceDemoMapper; +import com.centricsoftware.enhancement.modules.demo.service.IMultipleDataSourceDemoService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; + +/** + * description: + * Date: 2024/9/9 15:45 + */ +@Service +public class MultipleDataSourceDemoServiceImpl implements IMultipleDataSourceDemoService { + + @Resource + private MultipleDataSourceDemoMapper multipleDataSourceDemoMapper; + @DS("hse") + public List querySeason() { + List hashMaps = multipleDataSourceDemoMapper.querySeason(); + return hashMaps; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/NodeHelper.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/NodeHelper.java new file mode 100644 index 0000000..bc4d389 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/NodeHelper.java @@ -0,0 +1,58 @@ +package com.centricsoftware.enhancement.modules.dml.dto.node; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.List; +import java.util.function.Consumer; + +/** + * description: + * Date: 2023/11/19 16:26 + */ +public class NodeHelper { + + public static Element setRefList(Document doc,Element element, List list){ + return NodeHelper.setList(doc,element,list,"ref"); + } + + public static Element setListRemoveTag(Document doc,Element element, List list,String lineLabel){ + for(String url : list){ + Element ref = doc.createElement(lineLabel); + ref.setTextContent(url); + element.appendChild(ref); + } + return element; + } + + public static Element setList(Document doc,Element element, List list,String lineLabel){ + Element listElement = doc.createElement("List"); + element.appendChild(listElement); + for(String url : list){ + Element ref = doc.createElement(lineLabel); + ref.setTextContent(url); + listElement.appendChild(ref); + } + return listElement; + } + + public static Element addRefLine(Document doc, String ref, Consumer function){ + Element refElement = doc.createElement("ref"); + refElement.setTextContent(ref); + if(function!=null){ + function.accept(refElement); + } + return refElement; + + } + + public static Element searchNode(String parameter, String op,String value,Document doc){ + Element search = doc.createElement("Node"); + search.setAttribute("Parameter",parameter); + search.setAttribute("Op",op); + search.setAttribute("Value",value); + return search; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/call/C8CallMethod.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/call/C8CallMethod.java new file mode 100644 index 0000000..2d19fe9 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/call/C8CallMethod.java @@ -0,0 +1,111 @@ +package com.centricsoftware.enhancement.modules.dml.dto.node.call; + +import cn.hutool.core.util.XmlUtil; +import com.centricsoftware.enhancement.modules.dml.dto.node.NodeHelper; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8Search; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.List; + +public class C8CallMethod { + + public C8CallMethod(Document doc, Element firstElement){ + this.doc = doc; + this.firstElement = firstElement; + } + + public Document doc; + public Element firstElement; + + /** + * 标准的callList方法 + */ + public static C8CallMethod callList(String module, String operation, List list, String parameter, Integer limit) { + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("CallList"); + element.setAttribute("Module",module); + element.setAttribute("Operation",operation); + element.setAttribute("Parameter",parameter); + element.setAttribute("Limit",limit!=null?limit.toString():""); + NodeHelper.setRefList(doc,element,list); + C8CallMethod callMethod = new C8CallMethod(doc,element); + return callMethod; + } + + /** + * 标准的CallMethod + */ + public static C8CallMethod newCall(String module, String operation){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("Call"); + doc.appendChild(element); + element.setAttribute("Module",module); + element.setAttribute("Operation",operation); + C8CallMethod callMethod = new C8CallMethod(doc,element); + return callMethod; + } + + /** + * 标准的CallQuery + */ + public static C8CallMethod callQuery(String module, String operation, String param, C8Search search){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("CallQuery"); + element.setAttribute("Module",module); + element.setAttribute("Operation",operation); + element.setAttribute("Parameter",param); + //添加标签 + Element query = doc.createElement("Query"); + element.appendChild(query); + //添加Search语句 + query.appendChild(search.getFirstElement()); + C8CallMethod callQuery = new C8CallMethod(doc,element); + return callQuery; + } + + /** + * Call相关方法下,添加Parameter标签 + */ + public C8CallMethod addParameter(String id, String type,String value){ + Element parameterElement = doc.createElement("Parameter"); + parameterElement.setAttribute("Id",id); + parameterElement.setAttribute("Type",type); + parameterElement.setAttribute("Value",value); + firstElement.appendChild(parameterElement); + return this; + } + + /** + * Call相关方法下,添加DependencyPath标签 + * 例如:Child:Attributes + */ + public C8CallMethod addCallDependencyPath(String[] paths){ + for(String path : paths){ + addCallDependencyPath(path); + } + return this; + } + + + /** + * Call相关方法下,添加DependencyPath标签 + * 例如:Child:Attributes + */ + public C8CallMethod addCallDependencyPath(String path){ + Element dependencyPath = doc.createElement("DependencyPath"); + dependencyPath.setTextContent(path); + firstElement.appendChild(dependencyPath); + return this; + } + + /** + * 获取Operation + * @return + */ + public String getXml(){ + return XmlUtil.toStr(doc,"UTF-8",true,true); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/change/C8OperationNode.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/change/C8OperationNode.java new file mode 100644 index 0000000..751cca4 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/change/C8OperationNode.java @@ -0,0 +1,516 @@ +package com.centricsoftware.enhancement.modules.dml.dto.node.change; + + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.XmlUtil; +import cn.hutool.http.HtmlUtil; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.modules.dml.dto.node.NodeHelper; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8Search; +import com.google.common.collect.Lists; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +public class C8OperationNode { + + public C8OperationNode(Document doc, Element firstElement){ + this.doc = doc; + this.firstElement = firstElement; + doc.appendChild(firstElement); + } + + public Document doc; + public Element firstElement; + + public static C8OperationNode changeNode(String url) { + return C8OperationNode.changeNode(url, ""); + } + + public static C8OperationNode changeNode(String url, String path) { + String pathUrl = url; + if (StrUtil.isNotBlank(path)) { + pathUrl = url+"?Path=" + path.replaceAll(":", "%3A"); + } + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("ChangeNode"); + element.setAttribute("URL",pathUrl); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + public static C8OperationNode createNode(String url, String type){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("CreateNode"); + element.setAttribute("URL",url); + element.setAttribute("Type",type); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + /** + * 删除Node + */ + public static C8OperationNode deleteNode(String url){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("DeleteNode"); + element.setAttribute("URL",url); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + /** + * 删除用户 + */ + public static C8OperationNode deleteUser(String userId){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("DeleteUser"); + element.setAttribute("UserID",userId); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + /** + * 复制Node + */ + public static C8OperationNode copyNode(String url,String fromUrl){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("CopyNode"); + element.setAttribute("URL",url); + element.setAttribute("FromURL",fromUrl); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + /** + * 冻结/解冻 + * @param freeze true或者null:冻结; false:解冻; + */ + public static C8OperationNode freezeNode(String url,Boolean freeze){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("FreezeNode"); + element.setAttribute("URL",url); + element.setAttribute("Freeze",freeze==null?"true":freeze.toString()); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + /** + * FlushChanges + */ + public static C8OperationNode flushChanges(){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("FlushChanges"); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + + /** + * 校验Node + */ + public static C8OperationNode validateNode(String url){ + Document doc = XmlUtil.createXml(); + Element element = doc.createElement("ValidateNode"); + element.setAttribute("URL",url); + C8OperationNode changeNode = new C8OperationNode(doc,element); + return changeNode; + } + + /** + * @param otherAttributes 如果需要在PublishAttribute中添加其他属性,可以在map中添加,key未属性名,value为对应的值 + */ + public C8OperationNode publishAttribute(String id, String type, String value, HashMap otherAttributes) { + Element publishAttribute = doc.createElement("PublishAttribute"); + publishAttributeBase(publishAttribute,id,type,value,otherAttributes); + return this; + } + + /** + * @param otherAttributes 如果需要在PublishAttribute中添加其他属性,可以在map中添加,key未属性名,value为对应的值 + */ + private C8OperationNode publishAttributeBase(Element element, String id, String type, String value, HashMap otherAttributes) { + firstElement.appendChild(element); + element.setAttribute("Id",id); + element.setAttribute("Type",type); + element.setAttribute("Value",escape(value)); + if(otherAttributes!=null){ + otherAttributes.forEach((k,v)->element.setAttribute(k,v)); + } + return this; + } + + /** + * @param function 可通过入参Element添加ChangeAttribute的属性 + */ + public C8OperationNode publishAttribute(String id, String type, String value, Consumer function) { + Element publishAttribute = doc.createElement("PublishAttribute"); + publishAttributeBase(publishAttribute,id,type,value,null); + if(function!=null){ + function.accept(publishAttribute); + } + return this; + } + + public C8OperationNode publishAttribute(String id, String type, String value) { + publishAttribute(id,type,value,new HashMap<>()); + return this; + } + + /** + * 执行一次性表达式 + */ + public C8OperationNode changeAttributeOneExp(String id, String exp){ + Element element = doc.createElement("ChangeAttribute"); + element.setAttribute("ExpOp","OneTimeIfNone"); + element.setAttribute("Id",id); + element.setAttribute("Exp",escape(exp)); + firstElement.appendChild(element); + return this; + } + + /** + * 执行一次性表达式 + * @param attributeUrl 字段的URL,比如款式Node Name的URL为【centric://REFLECTION/BuildInAttribute/Style/Node Name】 + */ + public C8OperationNode changeAttributeOneExpByAttributeUrl(String id, String attributeUrl){ + String exp = StrFormatter.format("ref(\"{}\").Expression.eval()", attributeUrl); + Element element = doc.createElement("ChangeAttribute"); + element.setAttribute("ExpOp","OneTimeIfNone"); + element.setAttribute("Id",id); + element.setAttribute("Exp",escape(exp)); + firstElement.appendChild(element); + return this; + } + + /** + * 禁用实时表达式,并对其赋值 + */ + public C8OperationNode changeAttributeDisable(String id , String type, String attributeValue){ + disableAttribute(id); + Element element = doc.createElement("ChangeAttribute"); + element.setAttribute("Id",id); + element.setAttribute("Type",type); + element.setAttribute("Value",attributeValue); + firstElement.appendChild(element); + return this; + } + + /** + * 禁用实时表达式 + */ + public C8OperationNode disableAttribute(String id){ + Element element = doc.createElement("ChangeAttribute"); + element.setAttribute("AddFG","4194304"); + element.setAttribute("DelFG","16384"); + element.setAttribute("Id",id); + firstElement.appendChild(element); + return this; + } + + /** + * 设置字段的flag为:MODIFIED BY_CLIENT + */ + public C8OperationNode modifyAttribute(String id){ + Element element = doc.createElement("ChangeAttribute"); + element.setAttribute("AddFG","262144"); + element.setAttribute("Id",id); + firstElement.appendChild(element); + return this; + } + + /** + * 在ChangeNode中拼接自定义的ChangeAttribute + */ + public C8OperationNode addCustomChangeXml(String changeXml){ + firstElement.setTextContent(changeXml); + return this; + } + + public C8OperationNode changeAttribute(String id, String type, String value){ + return changeAttribute(id,type,value,null); + } + + public C8OperationNode changeAttributeRef(String id, String value){ + modifyAttribute(id); + Element element = changeAttributeBase(id, "ref", escape(value), null); + firstElement.appendChild(element); + return this; + } + + private Element changeAttributeBase(String id, String type,String value,Element element){ + if(element==null){ + element = doc.createElement("ChangeAttribute"); + } + element.setAttribute("Id",id); + element.setAttribute("Type",type); + element.setAttribute("Value",value); + return element; + } + + /** + * @param function 可通过入参Element添加ChangeAttribute的属性 + */ + public C8OperationNode changeAttribute(String id, String type, String value, Function function){ + modifyAttribute(id); + /* + * 如果value不是json,需要转译 + */ + if(!JSONUtil.isJson(value)){ + value = escape(value); + } + Element changeElement = changeAttributeBase(id, type, value,null); + firstElement.appendChild(changeElement); + if(function!=null){ + function.apply(changeElement); + } + return this; + } + + /** + * 将changeNode或者createNode的URL,添加到 @param url的 @param id中 + */ + public C8OperationNode attach(String url, String id){ + Element attach = doc.createElement("Attach"); + attach.setAttribute("URL",url); + attach.setAttribute("Id",id); + firstElement.appendChild(attach); + return this; + } + + /** + * 将changeNode或者createNode的URL,添加到 @param url的 @param id中 + */ + public C8OperationNode attach(List urls, String id){ + for(String url : urls){ + attach(url,id); + } + return this; + } + + /** + * @param lineLabel 行标签,比如等 + * @param addPropertyFunction 在ChangeAttribute中添加属性,入参是Element + * @param addLinePropertyFunction 在ChangeAttribute的明细行中添加属性,入参是Element + */ + public C8OperationNode changeMap(String id, String lineLabel, Consumer addPropertyFunction, Consumer addLinePropertyFunction){ + mapBase("ChangeAttribute",id,lineLabel,addPropertyFunction,addLinePropertyFunction); + return this; + } + + /** + * @param lineLabel 行标签,比如等 + * @param addPropertyFunction 在ChangeAttribute中添加属性,入参是Element + * @param addLinePropertyFunction 在ChangeAttribute的明细行中添加属性,入参是Element + */ + public C8OperationNode publishMap(String id, String lineLabel, Consumer addPropertyFunction, Consumer addLinePropertyFunction){ + mapBase("PublishAttribute",id,lineLabel,addPropertyFunction,addLinePropertyFunction); + return this; + } + + /** + * @param lineLabel 行标签,比如等 + * @param addPropertyFunction 在ChangeAttribute中添加属性,入参是Element + * @param addLinePropertyFunction 在ChangeAttribute的明细行中添加属性,入参是Element + */ + private C8OperationNode mapBase(String changeOrPublish, String id, String lineLabel, Consumer addPropertyFunction, Consumer addLinePropertyFunction){ + //拼接ChangeAttribute + Element element = doc.createElement(changeOrPublish); + element.setAttribute("Id",id); + firstElement.appendChild(element); + if(addPropertyFunction!=null){ + addPropertyFunction.accept(element); + } + //拼接ChangeAttribute下的行 + if(addLinePropertyFunction!=null){ + Element lineElement = doc.createElement(lineLabel); + addLinePropertyFunction.accept(lineElement); + element.appendChild(lineElement); + } + return this; + } + + /** + * 修改通过key修改refmap的值 + */ + public C8OperationNode changeRefMapBySetKey(String id, String key, String value){ + if(StrUtil.isBlank(value)){ + return this; + } + Element element = doc.createElement("ChangeAttribute"); + element.setAttribute("Type","refmap"); + element.setAttribute("Id",id); + element.setAttribute("ValueOp","SetKey"); + firstElement.appendChild(element); + Element refLine = NodeHelper.addRefLine(doc, value, i->i.setAttribute("Key", key)); + element.appendChild(refLine); + return this; + } + + /** + * 修改map的值,覆盖原有值 + */ + public C8OperationNode changeAttributeMap(String id, String type, String lineLabel, Map map){ + return changeAttributeMap(id,type,map,lineLabel,""); + } + + /** + * map中添加值 + */ + public C8OperationNode changeAttributeMapAppend(String id, String type, String lineLabel, Map map){ + return changeAttributeMap(id,type,map,lineLabel,"Append"); + } + + /** + * 修改map字段的信息 + * @param id 字段ID + * @param type map类型:refmap、stringmap等 + * @param map map下的具体信息 + * @param lineLabel map行标签,比如ref、string等 + * @param valueOp ChangeAttribute中的valueOp选项 + */ + public C8OperationNode changeAttributeMap(String id, String type, Map map, String lineLabel, String valueOp){ + changeMap(id,lineLabel,ca->{ + ca.setAttribute("Type",type); + ca.setAttribute("ValueOp",valueOp); + for(String v : map.keySet()){ + Element line = doc.createElement(lineLabel); + line.setAttribute("Key",v); + line.setTextContent(map.get(v)); + ca.appendChild(line); + } + },null); + return this; + } + + /** + * 修改publishAttribute类型未stringmap的字段 + * @param valueOp 比如 ValueOp="Remove" + */ + public C8OperationNode publishAttributeStringMap(String id, Map map, String valueOp){ + publishMap(id,"string",ca->{ + ca.setAttribute("Type","stringmap"); + ca.setAttribute("ValueOp",valueOp); + map.forEach((k,v)->{ + Element line = doc.createElement("string"); + line.setTextContent(map.get(v)); + ca.appendChild(line); + }); + },null); + return this; + } + + /** + * 对list进行赋值,该操作会覆盖原有的值 + */ + public C8OperationNode changeAttributeList(String id, String type, List value, String lineLabel){ + return changeAttributeList(id,type,value,"",lineLabel); + } + + /** + * 对list字段进行操作,通过valueOp字段指定操作类型 + * @param valueOp 标准的valueOp字段 + */ + public C8OperationNode changeAttributeList(String id, String type, String value, String valueOp, String lineLabel){ + List list = Lists.newArrayList(); + list.add(value); + return changeAttributeList(id,type,list,valueOp,lineLabel); + } + + /** + * 对list字段进行操作,通过valueOp字段指定操作类型 + * @param valueOp 标准的valueOp字段 + */ + public C8OperationNode changeAttributeList(String id, String type, List valueList, String valueOp, String lineLabel){ + Element element = doc.createElement("ChangeAttribute"); + firstElement.appendChild(element); + element.setAttribute("Id",id); + element.setAttribute("Type",type); + if(!StrUtil.isBlankOrUndefined(valueOp)){ + element.setAttribute("ValueOp",valueOp); + } + NodeHelper.setListRemoveTag(doc,element,valueList,lineLabel); + return this; + } + + /** + * 在list中追加值 + * @return + */ + public C8OperationNode changeAttributeListAppend(String id, String type, String url, String lineLabel){ + List strings = Lists.newArrayList(url); + return changeAttributeList(id,type,strings,"Append",lineLabel); + } + public C8OperationNode changeAttributeListRemove(String id, String type, String url, String lineLabel){ + List strings = Lists.newArrayList(url); + return changeAttributeList(id,type,strings,"Remove",lineLabel); + } + public C8OperationNode changeAttributeListRemove(String id, String type, List strings, String lineLabel){ + return changeAttributeList(id,type,strings,"Remove",lineLabel); + } + public C8OperationNode changeAttributeListAppend(String id, String type, List strings, String lineLabel){ + return changeAttributeList(id,type,strings,"Append",lineLabel); + } + public C8OperationNode changeAttributeListAppendOrRemove(String id, String type, List strings, String valueOp, String lineLabel){ + return changeAttributeList(id,type,strings,valueOp,lineLabel); + } + + public C8OperationNode publishAttributeStringMap(String id, Map value){ + return publishAttributeStringMap(id,value,""); + } + + + /** + * ChangeQuery、validateQuery、deleteQuery + */ + public static C8OperationNode operationByQuery(String operationType, C8Search search){ + Document doc = search.getDoc(); + Element query = search.getFirstElement(); + Element firstElement = doc.createElement(operationType); + doc.removeChild(query); + firstElement.appendChild(query); + return new C8OperationNode(doc,firstElement); + } + + /** + * 该方法暂时有问题,setTextContent会将内容进行转译 + * ChangeQuery、validateQuery、deleteQuery + */ + public static C8OperationNode operationByQuery(String operationType, String search){ + Document doc = XmlUtil.createXml(); + Element firstElement = doc.createElement(operationType); + Element queryElement = doc.createElement("Query"); + queryElement.setTextContent(search); + firstElement.appendChild(queryElement); + return new C8OperationNode(doc,firstElement); + } + + public static C8OperationNode operationByList(String operationType, List urls){ + Document doc = XmlUtil.createXml(); + Element firstElement = doc.createElement(operationType); + NodeHelper.setRefList(doc,firstElement,urls); + return new C8OperationNode(doc,firstElement); + } + + public String escape(String text){ + String text1 = text; + try{ + text1 = HtmlUtil.escape(text); + }finally { + return text1; + } + } + + public String getXml(){ + return XmlUtil.toStr(doc,"UTF-8",true,true); + } + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/delete/C8Delete.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/delete/C8Delete.java new file mode 100644 index 0000000..0854615 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/delete/C8Delete.java @@ -0,0 +1,19 @@ +package com.centricsoftware.enhancement.modules.dml.dto.node.delete; + +import java.util.List; + +public class C8Delete { + + public static String deleteList(List list){ + StringBuilder xml = new StringBuilder(); + xml.append(""); + xml.append(""); + for(String url : list){ + xml.append("").append(url).append(""); + } + xml.append(""); + xml.append(""); + return xml.toString(); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8Search.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8Search.java new file mode 100644 index 0000000..5d15395 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8Search.java @@ -0,0 +1,157 @@ +package com.centricsoftware.enhancement.modules.dml.dto.node.search; + +import cn.hutool.core.util.XmlUtil; +import com.centricsoftware.enhancement.modules.dml.dto.node.NodeHelper; +import lombok.Getter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * Search构造器 + */ +public class C8Search { + + @Getter + public Document doc; + @Getter + public Element firstElement; + + public C8Search(Document doc,Element firstElement){ + this.doc = doc; + this.firstElement = firstElement; + } + + public static C8Search newSearch(String type){ + Document doc = XmlUtil.createXml(); + Element search = doc.createElement("Query"); + doc.appendChild(search); + Element typeNode = NodeHelper.searchNode("Type", "EQ", type, doc); + search.appendChild(typeNode); + C8Search node = new C8Search(doc,search); + return node; + } + + public C8SearchAttribute or(){ + Element or = doc.createElement("OR"); + firstElement.appendChild(or); + return new C8SearchAttribute(doc,or); + } + + public C8SearchAttribute and(){ + Element and = doc.createElement("AND"); + firstElement.appendChild(and); + return new C8SearchAttribute(doc,and); + } + + /** + * @param sequence ASC、DESC + */ + public C8Search sortByNode(String parameter,String sequence,String path){ + Element order = doc.createElement("OrderByNode"); + order.setAttribute("Path",path); + order.setAttribute("Parameter",parameter); + order.setAttribute("Sequence",sequence); + firstElement.appendChild(order); + return this; + } + + /** + * @param sequence ASC、DESC + */ + public C8Search sortByAttribute(String parameter,String sequence,String path){ + Element order = doc.createElement("OrderByAttribute"); + order.setAttribute("Path",path); + order.setAttribute("Id",parameter); + order.setAttribute("Sequence",sequence); + firstElement.appendChild(order); + return this; + } + + /** + * @param sequence ASC、DESC + */ + public C8Search sortByIndex(String parameter,String sequence,String path){ + Element order = doc.createElement("OrderByIndex"); + order.setAttribute("Path",path); + order.setAttribute("Id",parameter); + order.setAttribute("Sequence",sequence); + firstElement.appendChild(order); + return this; + } + + public C8Search access(String right,String path){ + Element order = doc.createElement("Access"); + order.setAttribute("Path",path); + order.setAttribute("Right",right); + firstElement.appendChild(order); + return this; + } + + + public C8Search dimension(String id, String op,String value){ + return dimension(id,op,value,""); + } + + public C8Search dimension(String id, String op,String value,String path){ + Element attribute = doc.createElement("Attribute"); + firstElement.appendChild(attribute); + attribute.setAttribute("Path",path); + attribute.setAttribute("Id",id); + attribute.setAttribute("Path",path); + attribute.setAttribute("Op",op); + attribute.setAttribute("Dimension",value); + return this; + } + + public C8Search node(String parameter, String op,String value){ + NodeHelper.searchNode(parameter, op,value, doc); + return this; + } + + private C8Search attr(String path, String id, String op, String valueType,String value){ + Element attribute = doc.createElement("Attribute"); + firstElement.appendChild(attribute); + attribute.setAttribute("Path",path); + attribute.setAttribute("Id",id); + attribute.setAttribute("Op",op); + attribute.setAttribute(valueType,value); + return this; + } + + public C8Search attrRef(String id, String op,String value){ + return attr("",id,op,"RValue",value); + } + + public C8Search attrRef(String id, String op,String value,String path){ + return attr(path,id,op,"RValue",value); + } + + public C8Search attr(String id, String op,String value){ + return attr("",id,op,"SValue",value); + } + + public C8Search attr(String id, String op,String value,String path){ + return attr(path,id,op,"SValue",value); + } + + public C8Search attrNum(String id, String op, String valueType,String value,String path){ + return attr(path,id,op,"DValue",value); + } + + public C8Search attrNum(String id, String op, String valueType,String value){ + return attr("",id,op,"DValue",value); + } + + public String getXml(){ + String xml = XmlUtil.toStr(doc, "UTF-8", true, true); + xml = xml.replaceAll("",""); + xml = xml.replaceAll("",""); + return xml; + } + + public String getXmlWithSearchTag(){ + return XmlUtil.toStr(doc, "UTF-8", true, true); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8SearchAttribute.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8SearchAttribute.java new file mode 100644 index 0000000..a5fa0cf --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/dto/node/search/C8SearchAttribute.java @@ -0,0 +1,112 @@ +package com.centricsoftware.enhancement.modules.dml.dto.node.search; + +import cn.hutool.core.util.XmlUtil; +import com.centricsoftware.enhancement.modules.dml.dto.node.NodeHelper; +import lombok.Getter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.List; + +/** + * description:OR和And的Search 构造器 + * Date: 2023/11/19 22:30 + */ +public class C8SearchAttribute { + + @Getter + public Element firstElement; + + private Document doc; + + public C8SearchAttribute(Document doc,Element firstElement){ + this.doc = doc; + this.firstElement = firstElement; + } + + public C8SearchAttribute or(){ + Element or = getDoc().createElement("OR"); + firstElement.appendChild(or); + return new C8SearchAttribute(doc,or); + } + + public C8SearchAttribute and(){ + Element and = getDoc().createElement("AND"); + firstElement.appendChild(and); + return new C8SearchAttribute(doc,and); + } + + + public C8SearchAttribute dimension(String id, String op,String value){ + return dimension(id,op,value,""); + } + + public C8SearchAttribute dimension(String id, String op,String value,String path){ + Element attribute = getDoc().createElement("Attribute"); + firstElement.appendChild(attribute); + attribute.setAttribute("Path",path); + attribute.setAttribute("Id",id); + attribute.setAttribute("Path",path); + attribute.setAttribute("Op",op); + attribute.setAttribute("Dimension",value); + return this; + } + + public C8SearchAttribute node(String parameter, String op,String value){ + NodeHelper.searchNode(parameter, op,value, getDoc()); + return this; + } + + /** + * 查询 + */ + private C8SearchAttribute orList(String path, String id, String op, String valueType, List value){ + for(String url : value){ + attr(path,id,op,valueType,url); + } + return this; + } + + private C8SearchAttribute attr(String path, String id, String op, String valueType,String value){ + Element attribute = getDoc().createElement("Attribute"); + firstElement.appendChild(attribute); + attribute.setAttribute("Path",path); + attribute.setAttribute("Id",id); + attribute.setAttribute("Op",op); + attribute.setAttribute("Path",path); + attribute.setAttribute(valueType,value); + return this; + } + + public C8SearchAttribute attrRef(String id, String op,String value){ + return attr("",id,op,"RValue",value); + } + + public C8SearchAttribute attrRef(String id, String op,String value,String path){ + return attr(path,id,op,"RValue",value); + } + + public C8SearchAttribute attr(String id, String op,String value){ + return attr("",id,op,"SValue",value); + } + + public C8SearchAttribute attr(String id, String op,String value,String path){ + return attr(path,id,op,"SValue",value); + } + + public C8SearchAttribute attrNum(String id, String op, String valueType,String value,String path){ + return attr(path,id,op,"DValue",value); + } + + public C8SearchAttribute attrNum(String id, String op, String valueType,String value){ + return attr("",id,op,"DValue",value); + } + + public Document getDoc(){ + if(doc==null){ + doc = XmlUtil.createXml(); + } + return doc; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/util/C8Write.java b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/util/C8Write.java new file mode 100644 index 0000000..2e66b63 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/modules/dml/util/C8Write.java @@ -0,0 +1,141 @@ +package com.centricsoftware.enhancement.modules.dml.util; + +import com.centricsoftware.enhancement.modules.dml.dto.node.call.C8CallMethod; +import com.centricsoftware.enhancement.modules.dml.dto.node.change.C8OperationNode; +import com.centricsoftware.enhancement.modules.dml.dto.node.search.C8Search; + +import java.util.List; + +/** + * description:@module csi/xml/Writer + * Date: 2023/11/19 16:03 + */ +public class C8Write { + + /** + * 复制Node + */ + public static C8OperationNode copyNode(String url,String fromUrl){ + return C8OperationNode.copyNode(url,fromUrl); + } + + /** + * 删除Node + */ + public static C8OperationNode deleteNode(String url){ + return C8OperationNode.deleteNode(url); + } + + /** + * 删除用户 + */ + public static C8OperationNode deleteUser(String userId){ + return C8OperationNode.deleteUser(userId); + } + + /** + * 冻结/解冻 + * @param freeze true或者null:冻结; false:解冻; + */ + public static C8OperationNode freezeNode(String url,Boolean freeze){ + return C8OperationNode.freezeNode(url,freeze); + } + + public static C8OperationNode flushChanges(){ + return C8OperationNode.flushChanges(); + } + + /** + * 校验Node + */ + public static C8OperationNode validateNode(String url){ + return C8OperationNode.validateNode(url); + } + + /** + * 修改Node + */ + public static C8OperationNode changeNode(String url){ + return C8OperationNode.changeNode(url); + } + + /** + * 修改Node,带路径 + */ + public static C8OperationNode changeNode(String url, String path){ + return C8OperationNode.changeNode(url,path); + } + + /** + * 标准callList + */ + public static C8CallMethod callList(String module, String operation, List list, String parameter, Integer limit){ + return C8CallMethod.callList(module, operation, list, parameter, limit); + } + + /** + * 标准callList + */ + public static C8CallMethod callList(String module, String operation, List list, String parameter){ + return C8CallMethod.callList(module, operation, list, parameter, 10000); + } + + /** + * 标准callList + */ + public static C8CallMethod callMethod(String module, String operation){ + return C8CallMethod.newCall(module, operation); + } + + public static C8CallMethod callQuery(String module, String operation, String param, C8Search search){ + return C8CallMethod.callQuery(module, operation,param,search); + } + + public static C8OperationNode changeQuery( C8Search search){ + return C8OperationNode.operationByQuery("ChangeQuery",search); + } + + /** + * 该方法暂时有问题,setTextContent会将内容进行转译 + */ + public static C8OperationNode changeQuery(String search){ + return C8OperationNode.operationByQuery("ChangeQuery",search); + } + + /** + * 该方法暂时有问题,setTextContent会将内容进行转译 + */ + public static C8OperationNode validateQuery(String search){ + return C8OperationNode.operationByQuery("ValidateQuery",search); + } + + /** + * 该方法暂时有问题,setTextContent会将内容进行转译 + */ + public static C8OperationNode deleteQuery( String search){ + return C8OperationNode.operationByQuery("DeleteQuery", search); + } + + + public static C8OperationNode validateQuery(C8Search search){ + return C8OperationNode.operationByQuery("ValidateQuery",search); + } + + public static C8OperationNode deleteQuery( C8Search search){ + return C8OperationNode.operationByQuery("DeleteQuery", search); + } + + public static C8OperationNode changeList(List urls){ + return C8OperationNode.operationByList("ChangeList", urls); + } + + public static C8OperationNode deleteList(List urls){ + return C8OperationNode.operationByList("DeleteList", urls); + } + + public static C8OperationNode validateList(List urls){ + return C8OperationNode.operationByList("ValidateList", urls); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/ImagesUploadService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/ImagesUploadService.java new file mode 100644 index 0000000..468e76e --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/ImagesUploadService.java @@ -0,0 +1,119 @@ +package com.centricsoftware.enhancement.service; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.dto.UploadImagesEntity; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +/** + * 多图片编辑方案 + */ +@Service +public class ImagesUploadService { + + @Autowired + C8NodeService c8NodeService; + //图片缓存地址 + @Value("${cs.plm.cache-file-dir:D:\\C8\\JavaService}") + private String cachePath; +// private String classPath = ImagesUploadService.class.getClassLoader().getResource("").getPath(); + + /** + * 删除图片 + * @param url + * @param fieldId + * @param delList + * @throws Exception + */ + public void removeImage(String url,String fieldId,String delList) throws Exception { + JSONArray array = JSONUtil.parseArray(delList); + StringBuilder xml = new StringBuilder(); + xml.append(" "); + xml.append(""); + for(int i =0;i"+pid+""); + } + } + xml.append(""); + xml.append(""); + c8NodeService.processNode(xml.toString()); + } + + /** + * 插入图片 + * @param url + * @param fieldId + * @param saveFiles + */ + public void insertImages(String url, String fieldId, MultipartFile[] saveFiles) throws Exception { + StringBuilder xml = new StringBuilder(); + xml.append(" "); + xml.append(""); + for(MultipartFile file : saveFiles){ +// String filePath = cachePath+file.getName(); +// FileUtil.writeBytes(file.getBytes(),filePath); + String imageUrl = c8NodeService.publishFile(file, file.getName()); + xml.append(""+imageUrl+""); +// FileUtil.del(filePath); + } + xml.append(""); + xml.append(""); + c8NodeService.processNode(xml.toString()); + } + + /** + * 图片编辑初始化 + * @param url + * @param fieldId + * @param error + * @return + */ + public List initImages(String url, String fieldId,StringBuilder error) throws Exception{ + List> list = Lists.newArrayList(); + String fields = c8NodeService.queryExpressionResult(fieldId,url); + if(StrUtil.isBlank(fields)){ + return list; + } + String[] split = fields.split(","); + for(int i=0;i map = Maps.newHashMap(); + map.put("smallImage",pre+smallImage); + map.put("viewable",pre+viewable); + map.put("name",node_name); + map.put("imageUrl",file); + map.put("url",pre+smallImage); + map.put("seq",i+""); + map.put("alt",node_name); + map.put("pid",file); + map.put("thumb",pre+smallImage); + map.put("src",pre+smallImage); + list.add(map); + } + return list; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/TableConfigService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/TableConfigService.java new file mode 100644 index 0000000..0783823 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/TableConfigService.java @@ -0,0 +1,124 @@ +package com.centricsoftware.enhancement.service; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.enhancement.ant.grid.GridColumn; +import com.centricsoftware.enhancement.ant.grid.GridForm; +import com.centricsoftware.enhancement.dto.table.GridOptions; +import com.centricsoftware.enhancement.dto.table.gridconfig.*; +import com.centricsoftware.enhancement.service.importAnnotation.BaseImportService; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.lang.reflect.Field; +import java.util.List; + +@Service +@Slf4j +public class TableConfigService { + @Resource + BaseImportService baseImportService; + + + public GridOptions getCommonGridOptions(Class configClass){ + GridOptions gridOptions = GridOptions.builder().build(); + List fields = baseImportService.getAllFields(configClass); + addColumnConfig(fields,gridOptions);//添加表格列配置 + addFormConfig(fields,gridOptions); + return gridOptions; + } + + public void addRightBtn(GridOptions gridOptions,String title,String content,String clickUrl){ + RightBtnConfig build = RightBtnConfig.builder().title(title).content(content).clickUrl(clickUrl).build(); + gridOptions.getRightBtnConfigs().add(build); + } + + /** + * 添加Grid列配置 + * @param configClass 实体类 + * @param gridOptions 传入需要返回到前端的实体化配置 + */ + public void addColumnConfig(Class configClass,GridOptions gridOptions) { + List fields = baseImportService.getAllFields(configClass); + addColumnConfig(fields,gridOptions);//添加Grid列配置 + addFormConfig(fields,gridOptions);//添加Form搜索配置 + } + + /** + * 添加Form搜索配置 + * @param fields 所有字段 + * @param gridOptions 传入需要返回到前端的实体化配置 + */ + private void addFormConfig(List fields, GridOptions gridOptions) { + + FormConfig formConfig = FormConfig.builder().build(); + List items = Lists.newArrayList(); + for(Field field : fields){ + GridColumn gridAnnotation = AnnotationUtil.getAnnotation(field, GridColumn.class);//获取注解 + if(gridAnnotation!=null) { + if(!gridAnnotation.formFilter()) continue; + } + GridForm formAnnotation = AnnotationUtil.getAnnotation(field, GridForm.class);//获取注解 + FormItemConfig formItemConfig = FormItemConfig.builder().build(); + FromItemRender itemRender = FromItemRender.builder().build(); + if(formAnnotation==null){ + if(gridAnnotation==null) continue; + //未配置GridForm,则取GridColumn上的title、field,其余的都按照默认值 + formItemConfig.setField(StrUtil.isBlank(gridAnnotation.field())?field.getName():gridAnnotation.field()); + formItemConfig.setTitle(gridAnnotation.title()); + }else { + //配置GridForm,以配置的GridForm为准 + formItemConfig.setField(StrUtil.isBlank(formAnnotation.field())?field.getName():formAnnotation.field()); + formItemConfig.setTitle(StrUtil.isBlank(formAnnotation.title())?field.getName():formAnnotation.title()); + formItemConfig.setSpan(formAnnotation.span()); + formItemConfig.setTitlePrefix(formAnnotation.titlePrefix()); + if(StrUtil.isNotBlank(formAnnotation.options())){ + JSONArray objects = JSONUtil.parseArray(formAnnotation.options()); + List convert = Convert.convert(List.class, objects); + itemRender.setOptions(convert); + } + itemRender.setName(formAnnotation.filterType()); + itemRender.setProps(StrUtil.isBlank(formAnnotation.props())?null:JSONUtil.parseObj(formAnnotation.props())); + itemRender.setClearable(formAnnotation.clearable()); + itemRender.setPlaceholder(formAnnotation.placeholder()); + itemRender.setFilterable(formAnnotation.filterable()); + } + formItemConfig.setItemRender(itemRender); + items.add(formItemConfig); + } + formConfig.setItems(items); + gridOptions.setFormConfig(formConfig); + } + + /** + * 添加Grid列配置 + * @param fields 所有字段 + * @param gridOptions 传入需要返回到前端的实体化配置 + */ + public void addColumnConfig(List fields,GridOptions gridOptions) { + List list = Lists.newArrayList(); + for(Field field : fields){ + GridColumn gridAnnotation = AnnotationUtil.getAnnotation(field, GridColumn.class);//获取注解 + if(gridAnnotation==null) continue; + ColumnConfig build = ColumnConfig.builder() + .field(StrUtil.isBlank(gridAnnotation.field())?field.getName():gridAnnotation.field()) + .title(gridAnnotation.title()) + .width(gridAnnotation.width()) + .fixed(gridAnnotation.fixed()) + .sortable(gridAnnotation.sortable()) + .formatter(gridAnnotation.formatter()) + .filters(StrUtil.isBlank(gridAnnotation.filters())?null: JSONUtil.parse(gridAnnotation.filters())) + .filterRender(StrUtil.isBlank(gridAnnotation.filterRender())?null:JSONUtil.parseObj(gridAnnotation.filterRender())) + .build(); + list.add(build); + } + gridOptions.setColumns(list); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesDCLService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesDCLService.java new file mode 100644 index 0000000..894319c --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesDCLService.java @@ -0,0 +1,193 @@ +package com.centricsoftware.enhancement.service.bo; + + +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.component.CacheComponent; +import com.centricsoftware.enhancement.modules.c8.dto.OperationResultEntity; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * description:创建BO字段;生成operation + * Date: 2023/3/16 15:51 + */ +@Slf4j +@Service +public class BOAttributesDCLService { + + @Resource + C8NodeService c8NodeService; + + @Resource + CacheComponent cacheComponent; + + private String cantCopyOperation(){ + return "\n" + + "\t\t\tCopyCategory:NoCopy\n" + + "\t\t"; + } + + private String getInitialValue(String type){ + String initValue = """"; + if(type.endsWith("list")||type.endsWith("set")||type.endsWith("vector")){ + initValue = "[]"; + }else if("ref".equals(type)){ + initValue = ""centric:""; + }else if("boolean".equals(type)){ + initValue = "false"; + }else if(type.endsWith("map")){ + initValue = "{}"; + } + return initValue; + } + + public boolean createFieldBooleanNoCopy(String boUrl,String attributeId,String attributeName,String type,String tips,String notes){ + String other = cantCopyOperation(); + return createField(boUrl,attributeId,attributeName,type,tips,notes,false,other); + } + + public boolean createFieldStrNoCopy(String boUrl,String attributeId,String attributeName,String type,String tips,String notes){ + String other = cantCopyOperation(); + return createField(boUrl,attributeId,attributeName,type,tips,notes,false,other); + } + + public boolean createFieldRefNoCopy(String boUrl,String attributeId,String attributeName,String type,String targetClassName,String tips,String notes){ + String other = cantCopyOperation(); + other += "\n" + + " "; + return createField(boUrl,attributeId,attributeName,type,tips,notes,false,other); + } + + public boolean createFieldBoolean(String boUrl,String attributeId,String attributeName,String type,String tips,String notes){ + return createField(boUrl,attributeId,attributeName,type,tips,notes,false,""); + } + + public boolean createFieldStr(String boUrl,String attributeId,String attributeName,String type,String tips,String notes){ + return createField(boUrl,attributeId,attributeName,type,tips,notes,false,""); + } + + public boolean createFieldRef(String boUrl,String attributeId,String attributeName,String type,String targetClassName,String tips,String notes){ + String other = "\n" + + " "; + return createField(boUrl,attributeId,attributeName,type,tips,notes,false,other); + } + + public boolean createField(String boUrl,String attributeId,String attributeName,String type ,String tips,String notes,boolean readOnly,String other){ + String attributeFlag = readOnly?"1":"0"; + String initValue = getInitialValue(type); + String customAttribute = c8NodeService.queryFirstByNodeName("CustomAttribute", attributeId); + if(StrUtil.isNotBlank(customAttribute)){ + return false; + } + String rootClassName = getRootClassName(boUrl); + String fieldUrl = boUrl+"/"+attributeId; + fieldUrl = fieldUrl.replace("BusinessObject","CustomAttribute"); + String operation = " \n" + + "\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\n" + + "\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\n" + + updateLocaleConfiguration(boUrl,attributeId,attributeName)+ + updateLocaleConfiguration(boUrl,attributeId+"_TT",tips)+ + "\t\n" + + "\t\t\n" + + "\t\n" + + "\t\n"; + try { + OperationResultEntity resultEntity = c8NodeService.processNode(operation); + return true; + }catch (Exception e){ + log.error("新建字段失败:"+e.getCause(),e); + return false; + } + + } + + private String updateLocaleConfiguration(String boUrl,String attributeId,String value){ + String defaultLocale = getDefaultLocale(); + String rootClassName = getRootClassName(boUrl); + String format = StrFormatter.format("{}.Attr.{}", rootClassName, attributeId); + return "\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\t"+value+"\n" + + "\t\t\n" + + "\t\n" ; + } + + private String getDefaultLocale(){ + String url = "centric://REFLECTION/INSTANCE/CompanyInfo/Global"; + String key = "DefaultLocale"; + String defaultLocale = cacheComponent.getStr(key); + if(StrUtil.isBlank(defaultLocale)){ + defaultLocale =c8NodeService.queryExpressionResult("DefaultLocale", url); + cacheComponent.setValue(key,defaultLocale,2*60 * DateUnit.MINUTE.getMillis());//2小时失效 + } + return defaultLocale; + } + + private String getRootClassName(String boUrl){ + String key = StrFormatter.format("BO-RootClassName-{}", boUrl); + String rootClassName = cacheComponent.getStr(key); + if(StrUtil.isBlank(rootClassName)){ + rootClassName = c8NodeService.queryExpressionResult("RootClassName", boUrl); + cacheComponent.setValue(key,rootClassName,2*60 * DateUnit.MINUTE.getMillis());//2小时失效 + } + return rootClassName; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesService.java new file mode 100644 index 0000000..6903319 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/bo/BOAttributesService.java @@ -0,0 +1,20 @@ +package com.centricsoftware.enhancement.service.bo; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * description:创建BO字段 + * Date: 2023/3/16 15:51 + */ +@Slf4j +@Service +public class BOAttributesService { + + @Resource + BOAttributesDCLService boAttributesDCLService; + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/BaseImportService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/BaseImportService.java new file mode 100644 index 0000000..5e16bba --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/BaseImportService.java @@ -0,0 +1,136 @@ +package com.centricsoftware.enhancement.service.importAnnotation; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HtmlUtil; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.config.entity.CenterProperties; +import com.centricsoftware.enhancement.ant.C8Column; +import com.centricsoftware.enhancement.ant.C8Entity; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +/** + * 该类主要是一些关于导入的通用工具类 + */ +@Service +@Slf4j +public abstract class BaseImportService implements ImportInterface { + @Autowired + CenterProperties centerProperties; + + @Autowired + C8NodeService c8NodeService; + + /** + * 查找类及其父类的所有字段 + * @param clazz + * @return + */ + public final List getAllFields(Class clazz) { + List fields = Lists.newArrayList(); + while (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + clazz = clazz.getSuperclass(); + } + return fields; + } + + /** + * 获取C8Column注解有key属性的Field + * @param fields + * @return + */ + public final List getKeyFields(List fields){ + List keyFieldList = Lists.newArrayList(); + for (Field f : fields) { + C8Column annotation = f.getAnnotation(C8Column.class); + if (annotation == null) { + continue; + } + if(annotation.key()){ + keyFieldList.add(f); + } + } + log.debug("导入程序中,设置为key的列有:"+keyFieldList.toString()); + return keyFieldList; + } + + /** + * ref字段返回对应的值 + * @param colAnno + * @param value + * @return + */ + public final String getRefValue(C8Column colAnno, String value){ + if(StrUtil.isBlank(value)||"null".equals(value)){ + return ""; + } + String refBO = colAnno.refBO();//获取ref的NodeType + String refAttr = colAnno.refAttr(); + StringBuilder xml = new StringBuilder(); + xml.append(""); + xml.append(""); + try{ + + List list = c8NodeService.queryByXML(xml.toString()); + if(list.size()>0){ + return list.get(0); + } + }catch (Exception e){ + log.error(e.getLocalizedMessage()); + } + return ""; + } + + /** + * 获取实体类上的注解 + * @param clazz + * @return + * @throws Exception + */ + public final C8Entity getC8EntityAnnotation(Class clazz){ + Annotation[] declaredAnnotations = clazz.getDeclaredAnnotationsByType(C8Entity.class); + if(declaredAnnotations.length==0){ + log.error("实例类上未添加C8Entity注解:"+clazz.getClass()); + throw new RuntimeException("实例类上未添加C8Entity注解:"+clazz.getClass()); + } + return (C8Entity) declaredAnnotations[0]; + } + + /** + * xml中将变量赋值 + * @param xml 要转换的xml + * @param clazz class + * @param error 错误信息 + * @param obj 数据 + * @return xml + */ + public void getValueXmlObj(String xml,Class clazz,Object obj,StringBuilder error){ + String result = xml; + List variableNames = StringUtils.extractMessageByRegular(xml); + for(String vari : variableNames){ + String vari1 = null; + try { + Method method = clazz.getMethod("get" + StringUtils.firstUpperCase(vari)); + vari1 = Convert.toStr(method.invoke(obj)); + } catch (Exception e) { + error.append("配置的变量不存在:"+vari+"
"); + log.error("配置的变量不存在:"+vari,e); + } + vari1 = HtmlUtil.escape(vari1);//转义 + result = StringUtils.fillValueInMsg(result, vari, vari1); + } + error.append(result); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DealWithImportDataService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DealWithImportDataService.java new file mode 100644 index 0000000..f7e70c3 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DealWithImportDataService.java @@ -0,0 +1,361 @@ +package com.centricsoftware.enhancement.service.importAnnotation; + + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.em.C8ImportTypeEnum; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.ant.C8Column; +import com.centricsoftware.enhancement.modules.c8.component.EnumCache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.regex.Pattern; + +/** + * 该类主要是处理数据, + * 比如数据的校验、拼接xml、将ref、enum等数据转换成可导入的格式 + */ +@Service +@Slf4j +public abstract class DealWithImportDataService extends ImportCreateNodeService { + + @Autowired + EnumCache enumCache; + + /**2 + * 将一些ref、enum等数据进行转换 + * @param entity + * @param entityClazz + * @param error + * @return true:无错误 + * @throws Exception + */ + public final boolean transData(Object entity,Class entityClazz,StringBuilder error) throws Exception{ + boolean noError = true; + List allFields = getAllFields(entityClazz);//获取所有的字段 + for(Field f : allFields){ + C8Column annotation = f.getAnnotation(C8Column.class); + if (annotation == null) { + continue; + } + if(!changeEntity(entity, entityClazz,annotation,f,error)){ + noError= false; + } + } + return noError; + } + + /** + * 处理所有字段类型的转换 + * @param entity + * @param annotation + * @param field + * @param error 表示错误信息 + * @return true 表示该条数据无错误;false表示至少有一个错误 + * @throws Exception + */ + public final boolean changeEntity(Object entity, Class entityClazz, C8Column annotation, Field field, StringBuilder error) throws Exception{ + C8ImportTypeEnum type = annotation.type(); + field.setAccessible(true);//不设置的话不能访问private变量 + String oldValue = field.get(entity)+""; + if("null".equals(oldValue)){ + oldValue = ""; + } + if(checkeBlank(oldValue,annotation,error)){ + return false; + } + Object newValue = oldValue; + StringBuilder errorLine = new StringBuilder(); + switch (type){ + case REF: + newValue = changeRef(entity,entityClazz,oldValue+"", annotation,errorLine); + break; + case TIME: + newValue = changeTime(oldValue+"", annotation,errorLine); + break; + case ENUM: + newValue = changeEnum(oldValue, annotation,errorLine); + break; + case ENUM_DESC: + newValue = changeEnumDesc(oldValue, annotation,errorLine); + break; + case ENUM_DISPLAY: + newValue = changeEnumDisplay(oldValue, annotation,errorLine); + break; + case REFLIST: + newValue = changeReflist(oldValue, annotation,errorLine); + break; + case INTEGER: + newValue = changeNumber(oldValue, annotation,errorLine); + break; + case DOUBLE: + newValue = changeDouble(oldValue, annotation,errorLine); + break; + case BOOLEAN: + newValue = changeBoolean(oldValue, annotation,errorLine); + break; + case STRING: + newValue = changeString(oldValue, annotation,errorLine); + break; + } + String tips = annotation.tips(); + if(StrUtil.isNotBlank(errorLine.toString())&&StrUtil.isNotBlank(tips)){ + errorLine = new StringBuilder(); + getValueXmlObj(tips,entityClazz,entity,errorLine); + } + error.append(errorLine); + field.set(entity,newValue); + return StringUtils.isBlank(error.toString()); + } + + /** + * 转换并校验Stirng值 + * @param oldValue + * @param annotation + * @param error + * @return + */ + public String changeString(String oldValue, C8Column annotation, StringBuilder error){ + String newValue = oldValue; + String vaild = annotation.valid();//获取正则表达式 + if(!StringUtils.isBlank(vaild)){ + boolean isMatch = Pattern.matches(vaild, newValue); + if(!isMatch){ + error.append(" [Error]"+oldValue+":正则表达式("+vaild+")检验不通过;(字段:"+annotation.colName()+")
"); + log.error("[Error]"+oldValue+":正则表达式("+vaild+")检验不通过;(字段:"+annotation.colName()+")"); + } + } + if("upper".equals(annotation.stringCase())){ + newValue = newValue.toUpperCase(); + } + if("lower".equals(annotation.stringCase())){ + newValue = newValue.toLowerCase(); + } + return newValue; + } + + /** + * 转换并校验Double值 + * @param oldValue + * @param annotation + * @param error + * @return + */ + public double changeDouble(String oldValue, C8Column annotation, StringBuilder error){ + double newValue = 0.0; + if(!StringUtils.isNumeric(oldValue)){ + error.append(" [Error]"+oldValue+":该字段只能输入数字;(字段:"+annotation.colName()+")
"); + log.error(" [Error]"+oldValue+":该字段只能输入数字;(字段:"+annotation.colName()+")
"); + }else{ + newValue = StringUtils.toDouble(oldValue); + } + return newValue; + } + + /** + * 转换并校验Number值 + * @param oldValue + * @param annotation + * @param error + * @return + */ + public int changeNumber(String oldValue, C8Column annotation, StringBuilder error){ + int newValue = 0; + if(!StringUtils.isNumeric(oldValue)){ + error.append(" [Error]"+oldValue+":该字段只能输入整数;(字段:"+annotation.colName()+")
"); + log.error(" [Error]"+oldValue+":该字段只能输入整数;(字段:"+annotation.colName()+")
"); + }else{ + if(StringUtils.toDouble(oldValue).intValue()!= StringUtils.toInteger(oldValue)){ + error.append(" [Error]"+oldValue+":该字段只能输入整数,不能有小数点;(字段:"+annotation.colName()+")
"); + log.error(" [Error]"+oldValue+":该字段只能输入整数,不能有小数点;(字段:"+annotation.colName()+")
"); + }else{ + newValue = StringUtils.toInteger(oldValue); + } + } + return newValue; + } + + /** + * 转换并校验Boolean值 + * @param oldValue + * @param annotation + * @param error + * @return + */ + public String changeBoolean(String oldValue, C8Column annotation, StringBuilder error){ + String newValue = oldValue; + if("是".equals(oldValue)){ + newValue = "true"; + } + if("否".equals(oldValue)){ + newValue = "false"; + } + if(!"true".equals(newValue.toLowerCase())&&!"false".equals(newValue.toLowerCase())){ + error.append(" "+oldValue+":请规范填写Boolean值;(字段:"+annotation.colName()+")
"); + log.error(" "+oldValue+":请规范填写Boolean值;(字段:"+annotation.colName()+")
"); + } + return newValue.toLowerCase(); + } + + /** + * 转换并校验enumDesc + * @param oldValue + * @param annotation + * @param error + * @return + */ + public String changeEnumDesc(String oldValue, C8Column annotation, StringBuilder error){ + String prefix = annotation.enumPrefix(); + String newValue = enumCache.getFullNameByDesc(prefix,oldValue); + if(!StringUtils.isBlank(oldValue)){ + if((prefix+":").equals(newValue)||"".equals(newValue)){ + log.error(" "+oldValue+":该值(Enum)在系统中不存在;(字段:"+annotation.colName()+")
"); + error.append(""+oldValue+":该值(Enum)在系统中不存在;(字段:"+annotation.colName()+")
"); + } + } + return newValue; + } + + /** + * 转换并校验enumDisplay + * @param oldValue + * @param annotation + * @param error + * @return + */ + public String changeEnumDisplay(String oldValue, C8Column annotation, StringBuilder error){ + String prefix = annotation.enumPrefix(); + String newValue = enumCache.getFullnameByDisplay(prefix,oldValue); + if((prefix+":").equals(newValue)||"".equals(newValue)){ + log.error(" "+oldValue+":该值(Enum)在系统中不存在;(字段:"+annotation.colName()+")
"); + error.append(" "+oldValue+":该值(Enum)在系统中不存在;(字段:"+annotation.colName()+")
"); + } + return newValue; + } + + + /** + * 校验并转换enum值 + * @param oldValue + * @param annotation + * @param error + * @return + */ + public String changeEnum(String oldValue, C8Column annotation, StringBuilder error){ + if("null".equals(oldValue)){ + oldValue = ""; + } + String prefix = annotation.enumPrefix(); + String newValue = prefix+":"+oldValue; + return newValue; + } + + /** + * 校验并且转化ref + * @param ref + * @param annotation + * @param error + * @return + */ + public String changeRef(Object entity, Class entityClazz, String ref, C8Column annotation, StringBuilder error) throws Exception{ + String valid = annotation.valid(); + String validxml = centerProperties.getValue(valid); + if(StrUtil.isNotBlank(validxml)){ + String url = seachXml(entityClazz,entity,validxml, error); + log.debug(annotation.colName()+"的validxml:"+url); + if(!StringUtils.isBlank(url)){ + return url; + } + error.append(" "+ref+":该值(Ref)在系统中不存在;"+annotation.colName()+"(对象:"+annotation.refBO()+",字段:"+annotation.refAttr()+")
") ; + log.error(" "+ref+":该值(Ref)在系统中不存在;"+annotation.colName()+"(对象:"+annotation.refBO()+",字段:"+annotation.refAttr()+")
") ; + return ref; + } + String newValue = getRefValue(annotation,ref); + if(!StringUtils.isBlank(ref)){ + if(StringUtils.isBlank(newValue)){ + error.append(" "+ref+":该值(Ref)在系统中不存在;"+annotation.colName()+"(对象:"+annotation.refBO()+",字段:"+annotation.refAttr()+")
") ; + log.error(" "+ref+":该值(Ref)在系统中不存在;"+annotation.colName()+"(对象:"+annotation.refBO()+",字段:"+annotation.refAttr()+")
") ; + } + } + return newValue; + } + + + /** + * 校验并且转化reflist + * @param reflist + * @param annotation + * @return + */ + public String changeReflist(String reflist, C8Column annotation, StringBuilder error){ + StringBuilder xml = new StringBuilder(); + String splitStr = annotation.split();//获取分隔符 + String[] arr = reflist.split(splitStr); + for(String ref : arr){ + String newValue = getRefValue(annotation,ref); + if(!StringUtils.isBlank(ref)){ + if(StringUtils.isBlank(newValue)){ + error.append(" "+ref+":该值(Reflist)在系统中不存在;(对象:"+annotation.refBO()+",字段:"+annotation.refAttr()+")
"); + log.error(" "+ref+":该值(Reflist)在系统中不存在;(对象:"+annotation.refBO()+",字段:"+annotation.refAttr()+")
"); + }else{ + xml.append(""+newValue+""); + } + } + } + if(StringUtils.isBlank(error.toString())){ + return xml.toString(); + } + return error.toString(); + } + + /** + * 转换并校验Time + * @param value + * @param annotation + * @param error + * @return + */ + public String changeTime(String value, C8Column annotation, StringBuilder error){ + if(StrUtil.isBlank(value)||"null".equals(value)){ + return "0"; + } + String newValue = value; + String format = annotation.timeFormat(); + if("yyyy-MM-dd".equals(format)||"yyyy/MM/dd".equals(format)){ + if(newValue.length()>9){ + newValue = newValue.substring(0,10); + } + } + if(StringUtils.validDateFormat(newValue, format)){ + newValue = StringUtils.date2Long(newValue, format)+""; + }else{ + error.append(" "+value+":日期格式错误;请按照"+format+"格式填写(字段:"+annotation.colName()+")
"); + log.error(" "+value+":日期格式错误;请按照"+format+"格式填写(字段:"+annotation.colName()+")
"); + } + return newValue; + } + + /** + * 校验字段是否为空 + * @param value + * @param annotation + * @param error + * @return + */ + public boolean checkeBlank(String value, C8Column annotation, StringBuilder error){ + if(annotation.required()){ + if(StringUtils.isBlank(value)&&StrUtil.isBlank(annotation.valid())){ + error.append(" "+value+":字段不能为空;(字段:"+annotation.colName()+")
"); + log.error(" "+value+":字段不能为空;(字段:"+annotation.colName()+")
"); + return true; + } + } + return false; + } + + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DefaultImportService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DefaultImportService.java new file mode 100644 index 0000000..4496324 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/DefaultImportService.java @@ -0,0 +1,331 @@ +package com.centricsoftware.enhancement.service.importAnnotation; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.em.C8ImportTypeEnum; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.ant.C8Column; +import com.centricsoftware.enhancement.ant.C8Entity; +import com.centricsoftware.enhancement.component.RedisUtils; +import com.centricsoftware.enhancement.dto.AnnoImportLineError; +import com.centricsoftware.enhancement.util.ImportUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 该类是处理导入的入口类,主要处理xml的拼接、校验等 + */ +@Service +@Slf4j +public class DefaultImportService extends DealWithImportDataService { + + @Value("${cs.excel.redis.prefix:uplaod::prefix::}") + String redisUploadPrefix; + + @Autowired + RedisUtils redisUtils; + + /** + * 单条保存 + * @param obj + * @param entityClazz + * @param error + * @return + * @throws Exception + */ + @Override + public boolean process(Object obj, Class entityClazz, StringBuilder error) throws Exception { + ArrayList list = Lists.newArrayList(); + list.add(obj); + return process(list,entityClazz,error); + } + + /** + * 多条保存:错误信息入参为StringBuilder + * 执行导入的入口,负责调度各个方法 + * @param list + * @param entityClazz + * @param error + * @return + * @throws Exception + */ + @Override + public boolean process(List list, Class entityClazz, StringBuilder error) throws Exception{ + StringBuilder allXml = new StringBuilder(); + C8Entity entityAnno = getC8EntityAnnotation(entityClazz); + for(int i=0;i entityClazz, List errorList) { + StringBuilder error = new StringBuilder(); + StringBuilder allXml = new StringBuilder(); + C8Entity entityAnno = getC8EntityAnnotation(entityClazz); + for(int i=0;i entityClazz,C8Entity entityAnno){ + StringBuilder errorLine = new StringBuilder(); + StringBuilder xml = new StringBuilder(); + int line = i+1; + try { + //获取url,如果系统已经存在则返回匹配的值,如果不存在,则返回一个新建的URL,并且将创建的xml拼在xml变量里面 + boolean transResult = transData(entity,entityClazz,errorLine);//将一些ref、enum等数据进行转换 + String url = getNodeUrl(entity,entityClazz,xml,errorLine); + if(StrUtil.isBlank(url)){ + errorLine.append("获取不到要更新的记录:"+entity.toString()); + appendError(line,error,errorLine);//汇总错误信息 + return true; + } + xml.append(""); + boolean isContinue = continueWhenError(entityAnno,transResult);//当存在错误时,是否继续执行 + if(!isContinue){ + appendError(line,error,errorLine);//汇总错误信息 + return false; + } + Map pathMap = Maps.newHashMap();//保存有path的xml,无结尾 + xml.append(appendSaveXml(url,entity,entityClazz,pathMap,errorLine));//拼接一行数据的xml + xml.append(appendOtherChangeAttributesXml(entity,entityClazz,errorLine));//添加定制化的,可重写该方法 + xml.append(""); + xml.append(ImportUtil.appendPathXml(pathMap)); + appendValidateNode(entityAnno,url,xml);//拼接校验规则语句 + if(entityAnno.singleSave()){//逐条保存 + try { + c8NodeService.processNode(xml.toString()); + }catch (Exception e){ + log.error(e.getLocalizedMessage()); + appendTips(entity,entityClazz,entityAnno,error,line,e); + } + }else{//统一保存 + if(StringUtils.isBlank(errorLine.toString())){ + allXml.append(xml); + } + } + appendError(line,error,errorLine);//汇总错误信息 + } catch (Exception e) { + appendTips(entity,entityClazz,entityAnno,error,line,e); + log.error("第"+line+"行抛出异常:\"+e.getSuppressed()+\"
",e); + } + return true; + } + + /** + * 定制错误提示信息 + * @param entityAnno 注解 + * @param error 错误信息 + * @return 返回值 + */ + private void appendTips( Object data,Class clazz,C8Entity entityAnno,StringBuilder error,int lineNum,Exception e){ + String tips = entityAnno.tips(); + if(StrUtil.isNotBlank(tips)){ + getValueXmlObj(tips,clazz,data,error); + }else{ + error.append("第"+(lineNum+1)+"行抛出异常:"+e.getSuppressed()+"
"); + } + + } + + /** + * 拼接校验规则语句 + * @param entityAnno + * @param url + * @param xml + */ + public final void appendValidateNode(C8Entity entityAnno, String url, StringBuilder xml) { + boolean isValidate = entityAnno.validateNode(); + if(isValidate){ + xml.append(""); + } + } + + /** + * 如果需要在标签内添加语句,重写该方法 + * @param entity 一行数据的实体类 + * @param entityClazz 可通过这个对象获取所有的注解配置 + * @param error 存放错误的信息 + * @return + */ + public String appendOtherChangeAttributesXml(Object entity, Class entityClazz, StringBuilder error){ + return ""; + } + + /** + * 拼接字段保存的xml + * @param entity + * @param entityClazz + * @param error + * @return + * @throws IllegalAccessException + */ + public String appendSaveXml(String url,Object entity,Class entityClazz,Map pathMap,StringBuilder error) throws Exception { + StringBuilder xml = new StringBuilder(); + List allFields = getAllFields(entityClazz); + C8Entity entityAnno = getC8EntityAnnotation(entityClazz); + for(Field field : allFields){ + C8Column annotation = field.getAnnotation(C8Column.class); + field.setAccessible(true); + String value = field.get(entity)+"";//值 + if("null".equals(value)|| StrUtil.isBlank(value)){ + value = ""; + } + if (annotation == null) { + continue; + } + if(!checkContinue(entityAnno,annotation,value)){ + continue; + } + String otherXmlAttr = annotation.otherXmlAttr(); + String fieldXml = ""; + String id = annotation.attributeId();//字段ID + C8ImportTypeEnum type = annotation.type();//字段类型 + String path = annotation.path();//路径 + String typeStr = type.toString().toLowerCase(); + switch (type){ + case REFLIST: + fieldXml = ImportUtil.getChangeAttrListXml(id,typeStr,value,otherXmlAttr); + break; + case ENUM: + case ENUM_DESC: + case ENUM_DISPLAY: + fieldXml = ImportUtil.getChangeAttrXml(id,"enum",value,otherXmlAttr); + break; + default: + fieldXml = ImportUtil.getChangeAttrXml(id,typeStr,value,otherXmlAttr); + } + if(StringUtils.isBlank(path)){ + xml.append(fieldXml); + }else{ + ImportUtil.setPathXml(url,path,pathMap,fieldXml); + } + } + return xml.toString(); + } + + /** + * 当数据存在错误是否继续解析 + * @param entityAnno + * @param transResult + * @return + */ + public final boolean continueWhenError(C8Entity entityAnno, boolean transResult) throws Exception { + if(!transResult){ + return entityAnno.failContinue(); + } + return true; + } + + /** + * 汇总错误信息 + * @param lineNum + * @param error + * @param errorLine + */ + public final void appendError(int lineNum,StringBuilder error,StringBuilder errorLine){ + if(!StringUtils.isBlank(errorLine.toString())){ + error.append("
第" + lineNum + "行出错:
") + .append(errorLine); + } + } + + /** + * 判断是否执行更新 + * @param annotation + * @param value + * @return + */ + public boolean checkContinue( C8Entity entityAnno,C8Column annotation, String value){ + boolean isContinue = true; + boolean update = annotation.update();//是否需要更新 + boolean forceUpdate = annotation.forceUpdate();//是否强制更新 + if(!update) { + isContinue = false; + } + if(entityAnno.forceUpdate()){ + //如果类上强制更新为true,无视字段级别的配置 + if(!annotation.convertParentForceUpdate()){ + //字段上是否配置强制更新以字段级别的更新为准,如果没配置(false),则直接返回强制更新 + return isContinue; + } + } + if(!forceUpdate&& StrUtil.isBlank(value)){ + //值为空时,如果不启用强制更新,将不更新 + isContinue = false; + } + return isContinue; + } + + /** + *存入redis + * @param entityClassName + * @param xml + */ + private void sendRedis(String entityClassName,String xml){ + redisUtils.lRightPush(redisUploadPrefix+entityClassName,xml); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportCreateNodeService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportCreateNodeService.java new file mode 100644 index 0000000..ef5d559 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportCreateNodeService.java @@ -0,0 +1,193 @@ +package com.centricsoftware.enhancement.service.importAnnotation; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.em.C8ImportTypeEnum; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.ant.C8Column; +import com.centricsoftware.enhancement.ant.C8Entity; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +/** + * 该类主要是处理导入的数据是新增还是更新, + * 如果是新增的话,如何新增;更新的话,根据什么规则更新 + * {@link ImportCreateNodeService#searchUrl(List, C8Entity, Object, Class, StringBuilder)} 重写该方法可以修改查找规则} + * {@link ImportCreateNodeService#createUrl(Class entityClazz,Object, StringBuilder, C8Entity, StringBuilder)} 重写该方法可以修改创建规则} + */ +@Service +@Slf4j +public abstract class ImportCreateNodeService extends BaseImportService { + + @Autowired + C8NodeService c8NodeService; + + + /** + * 通过key查询并返回Nodeurl,如果C8不存在,则返回一个新的URL + * @param entity + * @param entityClazz + * @param xml + * @return + */ + public String getNodeUrl(Object entity, Class entityClazz, StringBuilder xml,StringBuilder error) throws Exception{ + List fields = getAllFields(entityClazz);//获取实体类所有的字段 + List keyFields = getKeyFields(fields);//获取key被设置为true的字段 + C8Entity entityAnno = getC8EntityAnnotation(entityClazz); + String url = searchUrl(keyFields,entityAnno,entity,entityClazz,error); + if(StringUtils.isBlank(url)){ + if(!entityAnno.create()){ + //如果配置为查不到就不创建,则返回 + return ""; + } + url = createUrl(entityClazz,entity,xml,entityAnno,error); + } + try { + final Field field = entityClazz.getDeclaredField("url"); + entityClazz.getMethod("set" + StringUtils.firstUpperCase("url"),field.getType()).invoke(entity,url); + } catch (Exception e) { + log.warn("未配置url变量,不进行url注入"); + } + return url; + } + + + /** + * 根据配置的key查询系统中是否存在,不存在则直接创建nodeurl + * 如果匹配的规则不同,可以重新改方法 + * @param keyFields 实体的字段 + * @param entityAnno 实体类上的注解 + * @param entity 实体类 + * @param entityClazz 实体类class + * @return + */ + public String searchUrl(List keyFields, C8Entity entityAnno, Object entity, Class entityClazz, StringBuilder error) throws Exception{ + String searchxml = entityAnno.searchXml(); + searchxml = centerProperties.getValue(searchxml); + if(StrUtil.isNotBlank(searchxml)){ + return seachXml( entityClazz,entity,searchxml, error); + } + String nodeType = entityAnno.nodeType(); + if(StrUtil.isBlank(nodeType)){ + return ""; + } + StringBuilder xml = new StringBuilder(); + xml.append(""); + for(Field field: keyFields){ + C8Column colAnno = field.getAnnotation(C8Column.class); + String attributeId = colAnno.attributeId();//字段ID + C8ImportTypeEnum type = colAnno.type();//字段类型 + String path = colAnno.path();//路径 + String fieldName = field.getName(); + fieldName = StringUtils.firstUpperCase(fieldName); + String value = String.valueOf(entityClazz.getMethod("get" + fieldName).invoke(entity)); + String valueXml = getSearchType(colAnno,value); + xml.append(""); + } + List list = c8NodeService.queryByXML(xml.toString()); + if(list.size()>0){ + return list.get(0); + }else{ + return ""; + } + } + + /** + * 如果配置了searchXml,则解析、赋值并返回xml + * @param entityClazz + * @param entity + * @param xml + * @param error + * @return + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + public String seachXml(Class entityClazz,Object entity,String xml,StringBuilder error) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + String newXml = xml; + List params = StringUtils.extractMessageByRegular(xml); + for(String param:params){ + Method method = entityClazz.getMethod("get" + StringUtils.firstUpperCase(param)); + if(method==null){ + continue; + } + String value = String.valueOf(method.invoke(entity)); + newXml = StringUtils.fillValueInMsg(newXml,param,value); + } + List strings = c8NodeService.queryByXML(newXml); + if(strings.size()==0){ + return ""; + }else{ + return strings.get(0); + } + } + + public String createXml(Class entityClazz,Object entity,String createXml,StringBuilder xml) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + String newXml = createXml; + String url = null; + url = c8NodeService.createURL(); + List params = StringUtils.extractMessageByRegular(newXml); + for(String param:params){ + if("url".equals(param)){ + //如果包含url变量,则创建一个url进行替换 + newXml = StringUtils.fillValueInMsg(newXml,param,url); + continue; + } + Method method = method = entityClazz.getMethod("get" + StringUtils.firstUpperCase(param)); + String value = String.valueOf(method.invoke(entity)); + newXml = StringUtils.fillValueInMsg(newXml,param,value); + } + xml.append(newXml); + return url; + } + + + /** + * 创建Url,如果创建Url的方式不同,可以重写该方法,将拼接好的xml放入xml中即可 + * 并且返回创建的Url + * @param entity + * @param xml + * @return + */ + public String createUrl(Class entityClazz, Object entity, StringBuilder xml, C8Entity entityAnno , StringBuilder error) throws Exception{ + String createxml = entityAnno.createXml(); + createxml = centerProperties.getValue(createxml); + String url = c8NodeService.createURL(); +// entityClazz.getMethod("set" + StringUtils.firstUpperCase("url")).invoke(entity); + if(StrUtil.isNotBlank(createxml)){ + return createXml( entityClazz,entity,createxml, xml); + } + String nodeType = entityAnno.nodeType(); + xml.append(""); + return url; + } + + + /** + * 获取查询SValue、RValue,SValue部分 + * @param colAnno + * @param value + * @return + */ + public final String getSearchType(C8Column colAnno, String value){ + C8ImportTypeEnum type = colAnno.type();//字段类型 + String result = ""; + switch (type) { + case INTEGER: + result = "DValue='"+ value+"'"; + break; + case REF: + result = "RValue='"+value+"'"; + break; + default: + result = "SValue='"+ value+"'"; + } + return result; + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportInterface.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportInterface.java new file mode 100644 index 0000000..41256c7 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importAnnotation/ImportInterface.java @@ -0,0 +1,15 @@ +package com.centricsoftware.enhancement.service.importAnnotation; + + +import com.centricsoftware.enhancement.dto.AnnoImportLineError; + +import java.util.List; + +public interface ImportInterface { + //处理Excel导入总入口 + boolean process(List list, Class entityClazz, StringBuilder error) throws Exception; + + boolean process(Object obj, Class entityClazz, StringBuilder error) throws Exception; + + boolean process(List obj, Class entityClazz, List error) throws Exception; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/BaseImportPropertiesService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/BaseImportPropertiesService.java new file mode 100644 index 0000000..9372dc3 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/BaseImportPropertiesService.java @@ -0,0 +1,121 @@ +package com.centricsoftware.enhancement.service.importproperties; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HtmlUtil; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.config.entity.CenterProperties; +import com.centricsoftware.enhancement.ant.C8EntityConfig; +import com.centricsoftware.enhancement.modules.c8.component.EnumCache; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * 该类主要是一些关于导入的通用工具类 + */ +@Service +@Slf4j +public abstract class BaseImportPropertiesService{ + @Autowired + CenterProperties centerProperties; + + @Autowired + C8NodeService c8NodeService; + + @Autowired + EnumCache enumCache; + + public String getNodeUrl(Map map, C8EntityConfig config, StringBuilder xml, StringBuilder error) throws Exception{ + String url = searchUrl(map,config,error); + if(!config.isCreate()&& StrUtil.isBlank(url)){ + if(StrUtil.isBlank(config.getTips())){ + error.append("导入的数据有误,匹配不到相应的记录"); + }else{ + error.append(getValueXml(config.getTips(),map,error)); + } + return ""; + } + if(StrUtil.isBlank(error)&& StrUtil.isBlank(url)){ + url = c8NodeService.createURL(); + map.put("url",url); + createUrl(map,config,xml,error); + } + return url; + } + + /** + * 拼接创建的xml + * @param map + * @param config + * @param error + * @return + */ + private String createUrl(Map map, C8EntityConfig config, StringBuilder xml, StringBuilder error){ + String createXml = config.getCreateXml(); + if(StrUtil.isEmpty(createXml)){ + StringBuilder line = new StringBuilder(); + line.append(""); + line.append(""); + return line.toString(); + }else{ + createXml = getValueXml(createXml,map,error); + xml.insert(0,createXml); + return createXml; + } + } + + /** + * 查询数据 + * @param map + * @param config + * @param error + * @return + */ + private String searchUrl(Map map, C8EntityConfig config,StringBuilder error){ + String searchXml = config.getSearchXml(); + if(StrUtil.isBlank(searchXml)){ + return ""; + } + searchXml = getValueXml(searchXml,map,error); + List strings; + try { + strings = c8NodeService.queryByXML(searchXml); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + if(strings.size()>0){ + return strings.get(0); + } + return ""; + } + + + /** + * xml中将变量赋值 + * @param xml + * @param map + * @param error + * @return + */ + public String getValueXml(String xml,Map map,StringBuilder error){ + String result = xml; + List variableNames = StringUtils.extractMessageByRegular(xml); + for(String vari : variableNames){ + if(!map.containsKey(vari)){ + error.append("[Error]:配置的变量不存在:"+vari+"
"); + return "error"; + } + String vari1 = Convert.toStr(map.get(vari)); + vari1 = HtmlUtil.escape(vari1);//转义 + result = StringUtils.fillValueInMsg(result, vari, vari1); + } + return result; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/ImportPropertiesService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/ImportPropertiesService.java new file mode 100644 index 0000000..92466e3 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/ImportPropertiesService.java @@ -0,0 +1,235 @@ +package com.centricsoftware.enhancement.service.importproperties; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.enhancement.ant.C8EntityConfig; +import com.centricsoftware.enhancement.component.RedisUtils; +import com.centricsoftware.enhancement.dto.excel.ExcelAroundAOP; +import com.centricsoftware.enhancement.service.importproperties.plugin.ItemImportInterface; +import com.centricsoftware.enhancement.util.ImportUtil; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +public class ImportPropertiesService extends TransDataAndAppendXmlService { + + @Autowired + RedisUtils redisUtils; + + @Value("${cs.excel.redis.prefix:uplaod::prefix::}") + String redisUploadPrefix; + + /** + * readAll中的Map,key为C8的属性ID + * @param config + * @param readAll + * @param error + */ + public boolean process(String importName, C8EntityConfig config, List> readAll, StringBuilder error) throws Exception{ + StringBuilder allXml = new StringBuilder(); + for(int i =0;i map = readAll.get(i); + try { + //前置通知,如果前置通过校验不通过,则根据isFailContinue,判断是break还是continue + if(!doItemBeforeAOP(map,config,errorLine)){ + //校验不通过 + if(config.isFailContinue()) continue; + break; + } + if(StrUtil.isNotBlank(errorLine)) { + if(config.isFailContinue()) continue; + break; + } + boolean transResult = transData(map,config,errorLine);//将一些ref、enum等数据进行转换 + boolean isContinue = continueWhenError(config,transResult);//当存在错误时,是否继续执行 + if(!isContinue){ + appendError(i+1,error,errorLine);//汇总错误信息 + break; + }else if(!transResult){ + //校验失败,本行不执行保存操作 + appendError(i+1,error,errorLine);//汇总错误信息 + continue; + } + String url = getNodeUrl(map,config,xml,errorLine); + if(StrUtil.isBlank(url)){ + appendError(i+1,error,errorLine);//汇总错误信息 + continue; + } + map.put("url",url); + Map pathMap = Maps.newHashMap();//保存有path的xml,无结尾 + xml.append(""); + xml.append(appendSaveXml(url,map,config,pathMap,errorLine));// 拼接一行数据的xml + xml.append(appendOtherChangeAttributesXml(url,map,pathMap,config,errorLine));//添加定制化的,可重写该方法 + xml.append(""); + xml.append(ImportUtil.appendPathXml(pathMap)); + appendValidateNode(config,url,xml);//拼接校验规则语句 + if(doItemAfterAOP(url,map,config,xml,errorLine)){ + if(config.isSingleSave()){//逐条保存 + try { + c8NodeService.processNode(xml.toString()); + }catch (Exception e){ + errorLine.append("第"+(i+1)+"行保存失败:"+e.getLocalizedMessage()); + log.error(e.getLocalizedMessage()); + } + }else{//统一保存 + if(StrUtil.isBlank(errorLine.toString())){ + allXml.append(xml); + } + } + } + appendError(i+1,error,errorLine);//汇总错误信息 + } catch (Exception e) { + error.append("第"+(i+1)+"行抛出异常:"+e.getSuppressed()+"
"); + log.error("第"+(i+1)+"行抛出异常:\"+e.getSuppressed()+\"
",e); + } + } + if(StrUtil.isNotBlank(error.toString())){ + if(!config.isFailSave()){ //当失败的时候不保存 + allXml.delete( 0, allXml.length()); + return false; + } + } + if(StrUtil.isNotBlank(allXml.toString())){ + log.debug("执行的xml:"+allXml.toString()); + if(config.isRedis()){ + redisUtils.lRightPush(redisUploadPrefix+importName,allXml.toString()); + }else{ + c8NodeService.processNodeMin(allXml.toString()); + } + } + return true; + } + + /** + * 存在错误是否继续 + * @param config 配置类 + * @param transResult 转换是否成功 + * @return + * @throws Exception + */ + public boolean continueWhenError(C8EntityConfig config, boolean transResult) throws Exception { + if(!transResult){ + return config.isFailContinue(); + } + return true; + } + + /** + * 汇总错误信息 + * @param lineNum + * @param error + * @param errorLine + */ + public final void appendError(int lineNum,StringBuilder error,StringBuilder errorLine){ + if(StrUtil.isNotBlank(errorLine.toString())){ + error.append("
第" + lineNum + "行出错:
") + .append(errorLine); + } + } + + + /** + * 如果需要在标签内添加语句,重写该方法 + * @param map 一行数据的实体类 + * @param config 可通过这个对象获取所有的注解配置 + * @param error 存放错误的信息 + * @return + */ + public String appendOtherChangeAttributesXml(String url,Map map, Map pathMap,C8EntityConfig config, StringBuilder error){ + StringBuilder result = new StringBuilder(); + String[] otherChangeXml = config.getOtherChangeXml(); + if(otherChangeXml==null){ + return ""; + } + for(String xml : otherChangeXml){ + if(xml.contains("=>")){ + //带有path的 + String[] pathxml = xml.split("=>"); + String path = StrUtil.trim(pathxml[0]);//获取path + String changeXml = getValueXml(pathxml[1], map, error);//xml中变量赋值 + ImportUtil.setPathXml(url,path,pathMap,changeXml); + }else{ + result.append(getValueXml(xml, map, error)); + } + } + return result.toString(); + } + + /** + * 拼接校验规则语句 + * @param config + * @param url + * @param xml + */ + public final void appendValidateNode(C8EntityConfig config, String url, StringBuilder xml) { + boolean isValidate = config.isValidateNode(); + if(isValidate){ + xml.append(""); + } + } + + /** + * 行保存前置通知;在获取行URL之前执行 + * @param map 行数据 + * @param config 配置项目 + * @param lineError 行错误信息 + * @return 是否校验通过 + */ + private boolean doItemBeforeAOP(Map map,C8EntityConfig config,StringBuilder lineError){ + String aroundBean = config.getItemImportAroundAOPBeanName(); + if(StrUtil.isNotBlank(aroundBean)){ + ItemImportInterface bean = SpringContextHolder.getBean(aroundBean); + ExcelAroundAOP before = bean.before(map, config); + if(before==null){ + return true; + } + if(!before.isSuccess()){ + //失败的话 + lineError.append(before.getError()); + return false; + } + //如果执行成功 + Map itemMap = before.getItemMap(); + if(itemMap!=null&&!itemMap.isEmpty()){ + map.putAll(itemMap); + } + } + return true; + } + + /** + * 行保存前置通知;在获取行URL之前执行 + * @param map 行数据 + * @param config 配置项目 + * @param lineError 行错误信息 + * @return 是否校验通过 + */ + private boolean doItemAfterAOP(String url, Map map,C8EntityConfig config,StringBuilder operation,StringBuilder lineError){ + String aroundBean = config.getItemImportAroundAOPBeanName(); + if(StrUtil.isNotBlank(aroundBean)){ + ItemImportInterface bean = SpringContextHolder.getBean(aroundBean); + ExcelAroundAOP after = bean.after(url, map,config); + if(after==null){ + return true; + } + if(!after.isSuccess()){ + //失败的话 + lineError.append(after.getError()); + return false; + } + operation.append(after.getOperation()); + } + return true; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/TransDataAndAppendXmlService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/TransDataAndAppendXmlService.java new file mode 100644 index 0000000..09db10b --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/TransDataAndAppendXmlService.java @@ -0,0 +1,532 @@ +package com.centricsoftware.enhancement.service.importproperties; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.ant.C8ColumnConfig; +import com.centricsoftware.enhancement.ant.C8EntityConfig; +import com.centricsoftware.enhancement.util.ImportUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +@Slf4j +@Service +public class TransDataAndAppendXmlService extends BaseImportPropertiesService { + + /** + * 将导入的enum、ref等数据转换成可导入的数据 + * + * @param map + * @param config + * @param errorLine + * @return + * @throws Exception + */ + public boolean transData(Map map, C8EntityConfig config, StringBuilder errorLine) throws Exception { + boolean noError = true; + List columns = config.getColumns(); + for (C8ColumnConfig col : columns) { + if (!changeEntity(map, config, col, errorLine)) { + noError = false; + } + } + return noError; + } + + public final boolean changeEntity(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) throws Exception { + String type = col.getType(); + String key = col.getId(); + String oldValue = Convert.toStr(map.get(key)); + if (checkeBlank(oldValue, col, error)) { + initValue(map,col); + if(StrUtil.isNotBlank(error)){ + return false; + }else{ + return true; + } + } + String newValue = oldValue; + String typeStr = type.toLowerCase(); + switch (typeStr) { + case "ref": + newValue = changeRef(map, config, col, error); + break; + case "time": + newValue = changeTime(map, config, col, error); + break; + case "enum": + newValue = changeEnum(map, config, col, error); + newValue = validEnum(newValue, col, map, error); + break; + case "enum_display": + case "enum_desc": + newValue = changeEnumDesc(map, config, col, error); + newValue = validEnum(newValue, col, map, error); + break; +// case "enum_display": +// newValue = changeEnumDisplay(map,config,col,error); +// validEnum(newValue,col,map,error); +// break; + case "enumlist": + newValue = changeEnumList(map, config, col, error); + break; + case "reflist": + newValue = changeReflist(map, config, col, error); + break; + case "integer": + newValue = changeNumber(map, config, col, error); + break; + case "double": + newValue = changeDouble(map, config, col, error); + break; + case "boolean": + newValue = changeBoolean(map, config, col, error); + break; + case "string": + newValue = changeString(map, config, col, error); + break; + } + if ("null".equals(newValue)) { + newValue = ""; + } + map.put(col.getId(), newValue); + // map.put(key+" old",oldValue); + return StrUtil.isBlank(error.toString()); + } + + private void initValue(Map map, C8ColumnConfig col) { + //如果导入空值,针对不同字段类型设置初始值;否则在forceUpdate模式下会报错 + if ("enum".equals(col.getType()) || "enum_display".equals(col.getType()) || "enum_desc".equals(col.getType())) { + map.put(col.getId(), col.getEnumPrefix() + ":"); + } else if ("ref".equals(col.getType())) { + map.put(col.getId(), "centric:"); + }else if ("integer".equals(col.getType())||"double".equals(col.getType())||"time".equals(col.getType())) { + map.put(col.getId(), "0"); + }else if ("boolean".equals(col.getType())) { + map.put(col.getId(), "false"); + } + } + + + public String changeRef(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String value = Convert.toStr(map.get(col.getId())); + return singleRef(value, map, config, col, error); + } + + public String singleRef(String value, Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + map.put("singleValue", value); + String validxml = col.getValid(); + if (StrUtil.isNotBlank(validxml)) { + String xml = getValueXml(validxml, map, error); + List result = c8NodeService.queryByXML(xml); + boolean validResultRequired = col.isValidResultRequired(); + if (validResultRequired && result.size() > 0) { + //必须有返回值 + if (StrUtil.isNotBlank(col.getValidAttr())) { + String s = null; + try { + s = c8NodeService.queryExpressionResult(col.getValidAttr(), result.get(0)); + } catch (Exception e) { + e.printStackTrace(); + return "centric:"; + } + return s; + } + return result.get(0); + } else if (!validResultRequired && result.size() == 0) { + //必须没有返回值 + return "centric:"; + } + } else if (StrUtil.isNotBlank(col.getRefBO())) { + String xml = ""; + if (StrUtil.isNotBlank(col.getRefAttr())) { + xml += ""; + } else { + xml += ""; + } + List result = c8NodeService.queryByXML(xml); + boolean validResultRequired = col.isValidResultRequired(); + if (validResultRequired && result.size() > 0) { + //必须有返回值 + if (StrUtil.isNotBlank(col.getValidAttr())) { + String s = null; + try { + s = c8NodeService.queryExpressionResult(col.getValidAttr(), result.get(0)); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + return s; + } + return result.get(0); + } else if (!validResultRequired && result.size() == 0) { + //必须没有返回值 + return ""; + } + } + //报错 + String tips = col.getTips(); + if (StrUtil.isNotEmpty(tips)) { + tips = getValueXml(tips, map, error); + log.error("[Error]:" + col.getColName() + ":" + tips + "。"); + error.append("[Error]:" + col.getColName() + ":" + tips + "
"); + } else { + log.error("[Error]:" + col.getColName() + ":该Ref值在系统中不存在:"); + error.append(" [Error]:" + col.getColName() + ":该Ref值在系统中不存在
"); + } + return value; + } + + public String changeTime(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String value = Convert.toStr(map.get(col.getId())); + if (StrUtil.isBlank(value) || "null".equals(value)) { + return "0"; + } + String format = col.getTimeFormat(); + String newValue = value; + if ("yyyy-MM-dd".equals(format) || "yyyy/MM/dd".equals(format)) { + if (newValue.length() > 9) { + newValue = newValue.substring(0, 10); + } + } + if (StringUtils.validDateFormat(newValue, format)) { + newValue = StringUtils.date2Long(newValue, format) + ""; + } else { + newValue = newValue.replaceAll("/", "-"); + if (StringUtils.validDateFormat(newValue, format)) { + newValue = StringUtils.date2Long(newValue, format) + ""; + } else { + error.append(" [Error]:" + value + ":日期格式错误;请按照" + format + "格式填写(字段:" + col.getColName() + ")
"); + } + } + return newValue; + } + + /** + * enum值校验 + * + * @param newValue + * @param col + * @param map + * @param error + * @return + */ + public String validEnum(String newValue, C8ColumnConfig col, Map map, StringBuilder error) { + String validXml = col.getValid(); + if (StringUtils.isBlank(validXml)) { + return newValue; + } + //非必填并且值为空 + if (!col.isRequired() && (col.getEnumPrefix() + ":").equals(newValue)) { + return newValue; + } + StringBuilder xml = new StringBuilder(); + xml.append(""); + if (!col.isAddAttribute()) { + xml.append(""); + } + xml.append(""); + xml.append(validXml); + String valueXml = getValueXml(xml.toString(), map, error); + List strings = c8NodeService.queryByXML(valueXml); + if (strings.size() == 0) { + if (col.isAddAttribute()) { + apendErrorLog("enum自动带值失败", valueXml, col, map, error);//错误拼接 + return ""; + } else { + apendErrorLog("enum校验规则不通过", newValue, col, map, error);//错误拼接 + return ""; + } + } else { + String url = strings.get(0); + if (col.isAddAttribute()) { + String exp = "DependsOn[0].Value"; + if (StrUtil.isNotBlank(col.getValidAttr())) { + exp = col.getValidAttr(); + } + String dependsOn = null; + try { + dependsOn = c8NodeService.queryExpressionResult(exp, url); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + if (StrUtil.isNotBlank(dependsOn)) { + return dependsOn; + } else { + apendErrorLog("enum自动带值失败", valueXml, col, map, error);//错误拼接 + return ""; + } + } else { + return newValue; + } + } + + } + + public String changeEnum(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + if ("null".equals(oldValue)) { + oldValue = ""; + } + String prefix = col.getEnumPrefix(); + String newValue = prefix + ":" + oldValue; + return newValue; + } + + public String changeEnumDesc(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + if ("null".equals(oldValue)) { + oldValue = ""; + } + if (col.isAddAttribute()) { + //额外添加的字段不校验空值 + return oldValue; + } + String prefix = col.getEnumPrefix(); +// String newValue = EnumUtil.getFullNameByDesc(prefix,oldValue); + String newValue = enumCache.getFullNameByDesc(prefix, oldValue); + if ((prefix + ":").equals(newValue) || "".equals(newValue)) { + apendErrorLog(oldValue, col, map, error);//错误拼接 + } + return newValue; + } + + public String changeEnumList(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + StringBuilder newValue = new StringBuilder(); + if (StrUtil.isBlank(oldValue)) { + apendErrorLog(oldValue, col, map, error);//错误拼接 + return ""; + } + oldValue = oldValue.replaceAll(",", ","); + for (String em : oldValue.split(",")) { + String prefix = col.getEnumPrefix(); + String v = enumCache.getFullNameByDesc(prefix, em); + if ((prefix + ":").equals(v) || "".equals(v)) { + apendErrorLog(em, col, map, error);//错误拼接 + } else { + newValue.append("").append(v).append(""); + } + } + return newValue.toString(); + } + + + private void apendErrorLog(String oldValue, C8ColumnConfig col, Map map, StringBuilder error) { + apendErrorLog("Enum值在系统中不存在", oldValue, col, map, error); + } + + private void apendErrorLog(String msg, String oldValue, C8ColumnConfig col, Map map, StringBuilder error) { + String tips = col.getTips(); + String prefix = col.getEnumPrefix(); + if (StringUtils.isNotEmpty(tips)) { + tips = getValueXml(tips, map, error); + log.error("[Error]:" + col.getColName() + ":" + tips + "。" + prefix + ":" + oldValue); + error.append(" [Error]:" + oldValue + ":" + tips + "。(字段:" + col.getColName() + ")
"); + } else { + log.error("[Error]:" + col.getColName() + ":" + msg + ":" + prefix + ":" + oldValue); + error.append(" [Error]:" + oldValue + ":" + msg + ";(字段:" + col.getColName() + ")
"); + } + } + + public String changeReflist(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + StringBuilder xml = new StringBuilder(); + String splitStr = col.getSplit();//获取分隔符 + String reflist = Convert.toStr(map.get(col.getId())); + String[] arr = reflist.split(splitStr); + for (String ref : arr) { + String refValue = singleRef(ref, map, config, col, error); + if (!StringUtils.isBlank(refValue)) { + xml.append("" + refValue + ""); + } + } + return xml.toString(); + } + + public String changeNumber(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + String newValue = oldValue; + if (!StringUtils.isNumeric(oldValue)) { + error.append(" [Error]:" + oldValue + ":该字段只能输入整数;(字段:" + col.getColName() + ")
"); + } else { + if (StringUtils.toDouble(oldValue).intValue() != StringUtils.toInteger(oldValue)) { + error.append(" [Error]:" + oldValue + ":该字段只能输入整数,不能有小数点;(字段:" + col.getColName() + ")
"); + } else { + newValue = StringUtils.toInteger(oldValue) + ""; + } + } + return newValue; + } + + /** + * 如果最后带有%号,则去掉%,并且除以100 + * + * @param map + * @param config + * @param col + * @param error + * @return + */ + public String changeDouble(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + String newValue = oldValue; + if (oldValue.endsWith("%")) { + newValue = StrUtil.sub(newValue, 0, newValue.length()); + } + if (!StringUtils.isNumeric(newValue)) { + error.append(" [Error]:" + oldValue + ":该字段只能输入整数;(字段:" + col.getColName() + ")
"); + } else { + double p = StringUtils.toDouble(newValue); + if (oldValue.endsWith("%")) { + p = p / 100; + } + newValue = p + ""; + } + return newValue; + } + + public String changeBoolean(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + String newValue = oldValue; + if ("否".equals(oldValue)) { + newValue = "false"; + } + if ("是".equals(oldValue)) { + newValue = "true"; + } + if (!"true".equals(newValue.toLowerCase()) && !"false".equals(newValue.toLowerCase())) { + error.append(" [Error]:" + oldValue + ":请规范填写Boolean值;(字段:" + col.getColName() + ")
"); + } + return newValue.toLowerCase(); + } + + public String changeString(Map map, C8EntityConfig config, C8ColumnConfig col, StringBuilder error) { + String oldValue = Convert.toStr(map.get(col.getId())); + String newValue = oldValue; + String vaild = col.getValid();//获取正则表达式 + if (!StringUtils.isBlank(vaild)) { + boolean isMatch = Pattern.matches(vaild, newValue); + if (!isMatch) { + String tips = col.getTips(); + tips = getValueXml(tips, map, error); + if (StrUtil.isNotEmpty(tips)) { + error.append(" [Error]:" + tips); + } else { + error.append(" [Error]:" + oldValue + ":正则表达式(" + vaild + ")检验不通过;(字段:" + col.getColName() + ")
"); + } + } + } + if ("upper".equals(col.getStringCase())) { + newValue = newValue.toUpperCase(); + } + if ("lower".equals(col.getStringCase())) { + newValue = newValue.toLowerCase(); + } + return newValue; + } + + + /** + * 验证是否为空 + * + * @param value + * @param col + * @param error + * @return + */ + public boolean checkeBlank(String value, C8ColumnConfig col, StringBuilder error) { + if (col.isAddAttribute()) { + //如果是配置中加的字段,则无需判断是否为空,他的值从valid中获取 + return false; + } + if (col.isRequired()) { + if (StringUtils.isBlank(value) || "null".equals(value)) { + error.append(" [Error]:" + value + ":字段不能为空;(字段:" + col.getColName() + ")
"); + return true; + } + } else { + return StringUtils.isBlank(value) || "null".equals(value); + } + return false; + } + + /** + * 拼接字段保存的xml + * + * @param url + * @param map + * @param config + * @param pathMap + * @param error + * @return + * @throws IllegalAccessException + */ + public String appendSaveXml(String url, Map map, C8EntityConfig config, Map pathMap, StringBuilder error) throws IllegalAccessException { + StringBuilder xml = new StringBuilder(); + List columns = config.getColumns(); + for (C8ColumnConfig col : columns) { + String value = Convert.toStr(map.get(col.getId())); + if ("null".equals(value) || StrUtil.isBlank(value)) { + value = ""; + } + if (!checkContinue(col, value)) { + continue; + } + String fieldXml = ""; + String id = col.getAttributeId();//字段ID + String type = col.getType();//字段类型 + String path = col.getPath();//路径 + String otherXmlAttr = col.getOtherXmlAttr(); + String typeStr = type.toLowerCase(); + switch (typeStr) { + case "reflist": + fieldXml = ImportUtil.getChangeAttrListXml(col, id, typeStr, value, otherXmlAttr); + break; + case "enum": + case "enum_desc": + case "enum_display": + fieldXml = ImportUtil.getChangeAttrXml(col, id, "enum", value, otherXmlAttr); + break; + case "enumlist": + fieldXml = ImportUtil.getChangeAttrEnumListXml(col, id, "enumlist", value, otherXmlAttr); + break; + default: + fieldXml = ImportUtil.getChangeAttrXml(col, id, typeStr, value, otherXmlAttr); + } + if (StringUtils.isBlank(path)) { + xml.append(fieldXml); + } else { + ImportUtil.setPathXml(url, path, pathMap, fieldXml); + } + } + return xml.toString(); + } + + /** + * 判断是否执行更新 + * + * @param col + * @param value + * @return + */ + public boolean checkContinue(C8ColumnConfig col, String value) { + boolean isContinue = true; + boolean update = col.isUpdate();//是否需要更新 + boolean forceUpdate = col.isForceUpdate();//是否强制更新 + if (!update) { + isContinue = false; + } + if (!forceUpdate && (StrUtil.isEmpty(value) || "null".equals(value))) { + //值为空时,如果不启用强制更新,将不更新 + isContinue = false; + } + return isContinue; + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/DoBeforeOrAfterImportInterface.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/DoBeforeOrAfterImportInterface.java new file mode 100644 index 0000000..6831afd --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/DoBeforeOrAfterImportInterface.java @@ -0,0 +1,13 @@ +package com.centricsoftware.enhancement.service.importproperties.plugin; + +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.Map; + +public interface DoBeforeOrAfterImportInterface { + Map before(MultipartFile file, String importName, String otherParam, ArrayList mails, StringBuilder error); + + String after(MultipartFile file, String importName, String otherParam, ArrayList mails, StringBuilder error); + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/ItemImportInterface.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/ItemImportInterface.java new file mode 100644 index 0000000..c104753 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/importproperties/plugin/ItemImportInterface.java @@ -0,0 +1,23 @@ +package com.centricsoftware.enhancement.service.importproperties.plugin; + +import com.centricsoftware.enhancement.ant.C8EntityConfig; +import com.centricsoftware.enhancement.dto.excel.ExcelAroundAOP; + +import java.util.Map; + +/** + * @author Xulin.Xie + */ +public interface ItemImportInterface { + /** + * 返回的Map,会把键值对存入Excel行,可供yml使用; + */ + ExcelAroundAOP before(Map map, C8EntityConfig config); + + /** + * 当行校验通过后,在每一行数据存入后面,追加operation + * @param url 行URL + * @param map 行数据:转换后的 + */ + ExcelAroundAOP after(String url, Map map, C8EntityConfig config); +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/AsyncLogService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/AsyncLogService.java new file mode 100644 index 0000000..dfb9117 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/AsyncLogService.java @@ -0,0 +1,73 @@ +package com.centricsoftware.enhancement.service.log; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.ant.ControllerLog; +import com.centricsoftware.commons.em.ResCode; +import com.centricsoftware.commons.exception.BaseException; +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.commons.utils.SpringUtil; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.config.config.ExecutorConfig; +import com.centricsoftware.config.cons.Constants; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.dto.log.PlmLog; +import com.centricsoftware.enhancement.modules.c8.service.es.LogElasticsearchService; +import com.centricsoftware.enhancement.modules.dml.dto.node.change.C8OperationNode; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.mapper.LogMapper; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.service.log.impl.LogServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Async(ExecutorConfig.THREAD_POOL_NAME) +@Service +@Slf4j +public class AsyncLogService { + + @Resource + LogMapper logMapper; + + @Resource + C8NodeService c8NodeService; + + @Resource + private LogElasticsearchService logElasticsearchService; + + + public void saveLog(FeignLogDto feignLogDto) { + if (feignLogDto != null) { + String path = feignLogDto.getPath(); + if (StrUtil.isNotBlank(path) && path.contains("csi-requesthandler/RequestHandler")) { + return; + } + logMapper.insert(feignLogDto); + } + } + + + public void saveLog(PlmLog plmLog) { + saveLog(plmLog,null); + } + + /** + * 提供更直接的日志接口,-ES版本 + * @author liaochangjiang + * @since 2024-05-20 17:41 + */ + public void saveLog(PlmLog plmLog, ControllerLog controllerLog) { + if (plmLog == null) { + return; + } + logElasticsearchService.saveLog(plmLog); + } + + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/LogService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/LogService.java new file mode 100644 index 0000000..fbcb727 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/LogService.java @@ -0,0 +1,17 @@ +package com.centricsoftware.enhancement.service.log; + +import cn.hutool.core.date.TimeInterval; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import okhttp3.Request; +import okhttp3.Response; + +public interface LogService { + FeignLogDto setRequestData(Request originalRequest); + + void setResponseData(Response response, TimeInterval timer, FeignLogDto feignLogDto); + + void saveLog(FeignLogDto feignLogDto); + + Page page(int current, int size, FeignLogDto feignLogDto); +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/CallBackLogServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/CallBackLogServiceImpl.java new file mode 100644 index 0000000..b9ade6d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/CallBackLogServiceImpl.java @@ -0,0 +1,62 @@ +package com.centricsoftware.enhancement.service.log.impl; + +import cn.hutool.core.date.TimeInterval; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.dto.log.PlmLog; +import com.centricsoftware.enhancement.mapper.LogMapper; +import com.centricsoftware.enhancement.modules.c8.service.es.LogElasticsearchService; +import com.centricsoftware.enhancement.service.log.AsyncLogService; +import com.centricsoftware.enhancement.service.log.LogService; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 基于es的日志存储 + */ +@Service +@Slf4j +public class CallBackLogServiceImpl implements LogService { + + @Resource + LogMapper logMapper; + @Resource + AsyncLogService asyncLogService; + + @Resource + private LogElasticsearchService logElasticsearchService; + + @Override + public FeignLogDto setRequestData(Request originalRequest) { + return null; + } + + @Override + public void setResponseData(Response response, TimeInterval timer, FeignLogDto feignLogDto) { + } + + @Override + public Page page(int current, int size, FeignLogDto feignLogDto) { + return null; + } + + public void saveLog(FeignLogDto feignLogDto) { + asyncLogService.saveLog(feignLogDto); + } + + /** + * 日志记录-ES版本,用户 + * @author liaochangjiang + * @since 2024-05-20 17:47 + */ + public void saveLog(PlmLog plmLog) { + if (plmLog != null) { + asyncLogService.saveLog(plmLog); + } + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/LogServiceImpl.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/LogServiceImpl.java new file mode 100644 index 0000000..28512da --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/log/impl/LogServiceImpl.java @@ -0,0 +1,283 @@ +package com.centricsoftware.enhancement.service.log.impl; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.json.*; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.dto.log.FeignLogDto; +import com.centricsoftware.enhancement.mapper.LogMapper; +import com.centricsoftware.enhancement.service.log.AsyncLogService; +import com.centricsoftware.enhancement.service.log.LogService; +import com.centricsoftware.enhancement.util.http.C8HttpUtil; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okio.Buffer; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; + + +/** + * 基于DB的日志存储 + */ +@Service +@Slf4j +public class LogServiceImpl implements LogService { + + @Resource + LogMapper logMapper; + @Resource + AsyncLogService asyncLogService; + + /** + * 请求信息封装 + */ + @Override + public FeignLogDto setRequestData(Request originalRequest) { + try { + String headers = originalRequest.headers().toString();//请求头 + String method = originalRequest.method();//请求方式 + Buffer buffer = new Buffer(); + RequestBody body = originalRequest.body(); + body.writeTo(buffer); + String requestMsg = buffer.readString(StandardCharsets.UTF_8); + /* + * 接口名称 + */ + String name = originalRequest.url().queryParameter("c8_name"); + /* + * 接口主键字段 + */ + String key = originalRequest.url().queryParameter("c8_key"); + /* + * 接口成功状态字段 + */ + String code = originalRequest.url().queryParameter("c8_code"); + HttpUrl newUrl = originalRequest.url().newBuilder() + .removeAllQueryParameters("c8_name") + .removeAllQueryParameters("c8_key") + .removeAllQueryParameters("c8_code") + .build(); + String host = originalRequest.url().host(); + String param = originalRequest.url().query(); + String path = originalRequest.url().uri().getPath(); + originalRequest = originalRequest.newBuilder().url(newUrl).build(); + String url = originalRequest.url().url().toString();//请求地址 + return FeignLogDto.builder() + .name(name) + .header(headers) + .method(method) + .host(host) + .path(path) + .dataKey(key) + .url(url) + .code(code) + .param(param) + .debug(requestMsg) + .startTime(new Date()) + .logType("接口日志") + .build(); + } catch (IOException e) { + log.error("设置日志请求信息失败。",e); + } + return null; + } + + /** + * 返回信息封装 + */ + @Override + public void setResponseData(Response response, TimeInterval timer, FeignLogDto feignLogDto) { + if (feignLogDto==null) return; + String responseMsg = C8HttpUtil.copyResponseBody(response); + long spendTime = timer.interval();//用时 + feignLogDto.setReturnMsg(responseMsg); + feignLogDto.setReturnCode(String.valueOf(response.code())); + feignLogDto.setCostMs(spendTime); + feignLogDto.setEndTime(new Date()); + } + + /** + * 保存日志 + */ + @Override + public void saveLog(FeignLogDto feignLogDto) { + String path = feignLogDto.getUrl(); + if (StrUtil.isNotBlank(path) && path.contains("csi-requesthandler/RequestHandler")) { + return; + } + try{ + setLogSuccess(feignLogDto); + setKey(feignLogDto); + asyncLogService.saveLog(feignLogDto); + }catch (Exception e){ + log.error("保存日志失败。",e); + printLog(feignLogDto); + } + } + + private void setKey(FeignLogDto feignLogDto) { + String requestStr = feignLogDto.getDebug(); + doSetKey(requestStr,feignLogDto); + if(StrUtil.isBlankOrUndefined(feignLogDto.getDataKey())){ + String param = feignLogDto.getParam(); + doSetKey(param,feignLogDto); + } + } + + private void doSetKey(String str,FeignLogDto feignLogDto) { + CsProperties csProperties = SpringContextHolder.getBean(CsProperties.class); + String field = feignLogDto.getRequestId(); + if(StrUtil.isBlankOrUndefined(field)){ + field = csProperties.getValue("common.log.feign.key-field"); + } + String[] split = field.split(","); + ArrayList fields = Lists.newArrayList(split); + for(String key : fields){ + String valueInJson = getValueInJsonArray(str, key); + if(!StrUtil.isBlankOrUndefined(valueInJson)){ + feignLogDto.setDataKey(valueInJson); + break; + } + } + } + + /** + * 设置接口响应状态;如果feign方法上配置了c8_code, + * 则按照c8_code配置的字段从响应报文中获取值,如果未配置,则取cs.plm.common.log.feign.success-code + * 再匹配cs.plm.common.log.feign.success-value配置的值,如果返回的code在success-value中,则接口成功 + */ + private void setLogSuccess(FeignLogDto feignLogDto) { + CsProperties csProperties = SpringContextHolder.getBean(CsProperties.class); + String field = feignLogDto.getResponseId(); + if(StrUtil.isBlankOrUndefined(field)){ + field = csProperties.getValue("common.log.feign.success-field"); + } + String value = csProperties.getValue("common.log.feign.success-value"); + String code = feignLogDto.getCode(); + if(!StrUtil.isBlankOrUndefined(code)){ + /* + * 如果配置了 + */ + field = code; + } + String[] split = value.split(","); + ArrayList values = Lists.newArrayList(split); + String responseStr = feignLogDto.getReturnMsg(); + String returnCode = feignLogDto.getReturnCode(); + String valueInJson = getValueInJson(responseStr, field); + if(StrUtil.isBlankOrUndefined(valueInJson)){ + if("200".equals(returnCode)){ + feignLogDto.setSuccess(true); + } + } + if(values.contains(valueInJson)){ + feignLogDto.setSuccess(true); + } + } + + private String getValueInJsonArray(String responseStr,String field){ + if(JSONUtil.isJsonObj(responseStr)){ + return getValueInJson(responseStr,field); + } + if(!JSONUtil.isJsonArray(responseStr)){ + return ""; + } + JSONArray arr = JSONUtil.parseArray(responseStr); + ArrayList urls = Lists.newArrayList(); + for(Object obj : arr){ + String valueInJson = getValueInJson(obj.toString(), field); + if(!StrUtil.isBlankOrUndefined(valueInJson)){ + urls.add(valueInJson); + } + } + return urls.toString(); + } + + private String getValueInJson(String responseStr,String field){ + if(!JSONUtil.isJsonObj(responseStr)){ + return ""; + } + JSONObject json = JSONUtil.parseObj(responseStr); + if(json.containsKey(field)) { + return json.getStr(field); + } + String byPath = (String) json.getByPath(field); + if(StrUtil.isBlankOrUndefined(byPath)){ + return ""; + } + return byPath; + } + + public void printLog(FeignLogDto feignLogDto){ + log.debug("接口日志打印:后续开发前端查询=====================开始=========================="); + try { + JSONConfig jsonConfig = JSONConfig.create().setIgnoreNullValue(false); + JSON parse = JSONUtil.parse(feignLogDto.getDebug(), jsonConfig); + log.debug("请求报文:"); + log.debug(JSONUtil.toJsonPrettyStr(parse)); + log.debug("响应报文:"); + JSON parseResponse = JSONUtil.parse(feignLogDto.getReturnMsg(), jsonConfig); + log.debug(JSONUtil.toJsonPrettyStr(parseResponse)); + log.debug("接口日志打印:后续开发前端查询=====================结束=========================="); + }catch (Exception e){ + log.error("打印日志失败,直接打印原始数据:{}",feignLogDto); + log.error("打印日志失败,异常如下:",e); + } + + } + + @Override + public Page page(int current, int size, FeignLogDto feignLogDto) { + Page page = new Page<>(); + page.setCurrent(current); + page.setSize(size); + QueryWrapper wrapper = getWrapper(feignLogDto); + wrapper.orderByDesc("id"); + return logMapper.selectPage(page, wrapper); + } + + private QueryWrapper getWrapper(FeignLogDto dto){ + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.like(StrUtil.isAllNotBlank(dto.getName()),"name", dto.getName()); + wrapper.like(StrUtil.isAllNotBlank(dto.getDataKey()),"data_key", dto.getDataKey()); + if(dto.getSuccessStr() !=null){ + wrapper.eq("success", dto.getSuccessStr()); + } + wrapper.like(!StrUtil.isBlankOrUndefined(dto.getLogType()), "log_type", dto.getLogType()); + wrapper.like(!StrUtil.isBlankOrUndefined(dto.getParam()), "param", dto.getParam()); + wrapper.like(!StrUtil.isBlankOrUndefined(dto.getReturnMsg()), "response", dto.getReturnMsg()); + wrapper.ge(!StrUtil.isBlankOrUndefined(dto.getStartTimeBegin()) ,"send_date", DateUtil.parse(dto.getStartTimeBegin())); + wrapper.le(!StrUtil.isBlankOrUndefined(dto.getStartTimeEnd()), "send_date", DateUtil.parse(dto.getStartTimeEnd())); + return wrapper; + } + + /** + * 删除日志 + * @param day 删除day天前的数据 + */ + public int deleteLog(int day){ + //获取当前时间60天前,类型为DateTime + DateTime dateTime = DateUtil.offsetDay(new Date(), -day); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.le("send_date", dateTime); + return logMapper.delete(wrapper); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/lui/FieldPermissionsService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/lui/FieldPermissionsService.java new file mode 100644 index 0000000..bfa187f --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/lui/FieldPermissionsService.java @@ -0,0 +1,116 @@ +package com.centricsoftware.enhancement.service.lui; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.dml.dto.node.change.C8OperationNode; +import com.centricsoftware.enhancement.service.bo.BOAttributesDCLService; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.util.C8Constant; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * description:自动创建【字段权限】的LookupItemSubtype和字段 + * Date: 2023/3/16 15:38 + */ +@Slf4j +@Service +public class FieldPermissionsService { + + @Resource + C8NodeService c8NodeService; + + @Resource + BOAttributesDCLService boAttributesDCLService; + + + /** + * 创建【字段权限】类型 + * @return LookupItemSubtype URL + */ + public String createLookupItemSubtype(){ + String subtype = c8NodeService.queryFirstByNodeName("LookupItemSubtype", "字段权限"); + if(StrUtil.isNotBlank(subtype)){ + return subtype; + } + subtype = c8NodeService.createURL(); + String operation = C8OperationNode.createNode(subtype, "LookupItemSubtype") + .changeAttribute("Node Name", "ref", "字段权限") + .changeAttribute("Active", "boolean", "true") + .attach("centric://REFLECTION/INSTANCE/LookupItemConfig","Subtypes") + .getXml(); + c8NodeService.processNode(operation); + return subtype; + } + + /** + * 创建【字段权限】字段 + */ + public void createLookupItemFiled(){ + boAttributesDCLService.createFieldStr(C8Constant.BOUrl.LOOKUP_ITEM,"C8_LI_AuditAttr","受控字段ID","string","设置需要添加权限的字段,值为BO下字段的ID","字段权限"); + boAttributesDCLService.createFieldStr(C8Constant.BOUrl.LOOKUP_ITEM,"C8_LI_AuditBO","字段所属BO","string","填写BO名称,比如Material","字段权限"); + boAttributesDCLService.createFieldStr(C8Constant.BOUrl.LOOKUP_ITEM,"C8_LI_RuleEXP1","条件设置","string","与Rule中Exp属性的写法一致,比如【attr('C8_Mat_Type') == 'C8_MatType:801'】","字段权限"); + boAttributesDCLService.createFieldStr(C8Constant.BOUrl.LOOKUP_ITEM,"C8_LI_AuditEditRoles","可编辑角色","reflist","具备编辑权限的角色,和【条件设置】是【与】的关系","字段权限"); + boAttributesDCLService.createFieldStr(C8Constant.BOUrl.LOOKUP_ITEM,"C8_LI_RuleEXP","角色权限设置","string","该字段是根据【可编辑角色】自动生成,【可编辑角色】只是用于快速生成【权限设置】","字段权限"); + boAttributesDCLService.createFieldBoolean(C8Constant.BOUrl.LOOKUP_ITEM,"C8_LI_Comment","记录修改日志","boolean","配置是否记录修改日志到Comment","字段权限"); + } + + public void createView(){ + String viewName = "字段权限控制"; + String viewUrl = c8NodeService.queryFirstByNodeName("_CS_PreferenceView", viewName); + if(StrUtil.isNotBlank(viewUrl)){ + return; + } + String luiName = "字段权限"; + String url = c8NodeService.createURL(); + String operation = "\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\tNode Name:(LookupItem):0\n" + + "\t\t\tC8_LI_AuditAttr:(LookupItem):0\n" + + "\t\t\tC8_LI_AuditBO:(LookupItem):0\n" + + "\t\t\tC8_LI_RuleEXP1:(LookupItem):0\n" + + "\t\t\tC8_LI_AuditEditRoles:(LookupItem):0\n" + + "\t\t\tC8_LI_RuleEXP:(LookupItem):0\n" + + "\t\t\tC8_LI_Comment:(LookupItem):0\n" + + "\t\t\tActive:(LookupItem):0\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\t1\n" + + "\t\t\t2\n" + + "\t\t\t3\n" + + "\t\t\t4\n" + + "\t\t\t5\n" + + "\t\t\t6\n" + + "\t\t\t7\n" + + "\t\t\t8\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\n" + + "\t\n" + + "\t\t\n" + + "\t\t\n" + + "\t\t\t"+url+"\n" + + "\t\t\n" + + "\t\n" + + "\t\n" + + "\t\t\n" + + "\t\t\t"+url+"\n" + + "\t\t\n" + + "\t"; + operation+="\n" + + "\t\t\n" + + "\t\t\t"+url+"\n" + + "\t\t\n" + + "\t"; + c8NodeService.processNode(operation); + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/service/specification/SpecificationDataSheetService.java b/enhancement/src/main/java/com/centricsoftware/enhancement/service/specification/SpecificationDataSheetService.java new file mode 100644 index 0000000..446b82d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/service/specification/SpecificationDataSheetService.java @@ -0,0 +1,104 @@ +package com.centricsoftware.enhancement.service.specification; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.enhancement.modules.c8.component.dep.DepPathResult; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.enhancement.modules.c8.dto.dep.DepPath; +import com.centricsoftware.enhancement.modules.dml.dto.node.change.C8OperationNode; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * description:从工艺单种新建 + * Author: Xulin.Xie + * Date: 2022/7/6 15:25 + */ +@Slf4j +@Service +public class SpecificationDataSheetService { + + private final C8NodeService c8NodeService; + + + /** + * 自定义的【从工艺单中新建】功能,Section需要重置; + * 通过原Section名称,判断是否存在Section,不存在则新建 + */ + public String resetSection(String currentVersion,String[] urls){ + DepPathResult result = getData(urls); + //获取Section映射 + Map sectionMap = getSectionMap(currentVersion,result); + StringBuffer xml = new StringBuffer(); + for(String url : urls){ + String sectionName = result.getValue("Section.$Name", url).getStr(); + if(StrUtil.isBlank(sectionName)) sectionName = result.getValue("Section.Definition.$Name", url).getStr(); + if(sectionMap.containsKey(sectionName)){ + //存在Section,则直接重置 + xml.append(resetSection(url, sectionMap.get(sectionName))); + }else{ + //不存在Section,则新建Section + String sectionUrl = createSection(currentVersion,sectionName,xml); + sectionMap.put(sectionName,sectionUrl);//将section放入缓存 + xml.append(resetSection(url, sectionUrl));//重置Section + } + } + c8NodeService.processNode(xml.toString()); + return ""; + } + + /** + * 创建Section + */ + private String createSection(String currentVersion, String sectionName,StringBuffer xml) { + String section = c8NodeService.createURL(); + String createSection = "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + String changeName = C8OperationNode.changeNode(section).changeAttribute("Node Name", "string", sectionName).getXml(); + xml.append(createSection).append(changeName); + return section; + } + + /** + * 重置Section + */ + private String resetSection(String url, String section) { + return C8OperationNode.changeNode(url).changeAttribute("Section","ref",section).getXml(); + } + + /** + * 获取section name:url映射 + */ + private Map getSectionMap(String currentVersion, DepPathResult result) { + Map sectionMap = Maps.newHashMap(); + List urls = result.getValue("AllSections",currentVersion).getList(); + for(String url : urls){ + String sectionName = result.getValue("$Name", url).getStr(); + sectionMap.put(sectionName, url); + } + return sectionMap; + } + + /** + * 获取数据 + */ + private DepPathResult getData(String[] urls){ + DepPath depPath = DepPath.builder().addUrls(urls).addPath("Child:__Parent__/Child:AllSections") + .addPath("Child:Section/Child:Definition").build(); + return c8NodeService.depPathByUrl(depPath); + } + + public SpecificationDataSheetService(C8NodeService c8NodeService) { + this.c8NodeService = c8NodeService; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/C8Constant.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/C8Constant.java new file mode 100644 index 0000000..e865f1a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/C8Constant.java @@ -0,0 +1,12 @@ +package com.centricsoftware.enhancement.util; + +/** + * description: + * Date: 2023/3/17 10:34 + */ +public class C8Constant { + + public interface BOUrl { + String LOOKUP_ITEM = "centric://REFLECTION/BusinessObject/LookupItem"; + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/AnalysisCell.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/AnalysisCell.java new file mode 100644 index 0000000..9f13ea7 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/AnalysisCell.java @@ -0,0 +1,51 @@ +package com.centricsoftware.enhancement.util.ExportUtil; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 分析单元 + *

+ * 字符串:{{str:字段名|默认字段名}},其中str可省略;str|-4代表保留字符串后4位;|默认字段名可不填 + * 图片:{{img|行跨度,列跨度:字段名}}。 eg. {{img|1,5:imageUrl}} + *

+ * + * @author liaochangjiang + * @since 2022-02-09 08:44:16 + */ +@Data +@Accessors(chain = true) +public class AnalysisCell { + /** + * 行位置 + */ + private int row; + /** + * 列位置 + */ + private int col; + /** + * 填充类型 + */ + private ExportFieldTypeEnum type = ExportFieldTypeEnum.STRING; + /** + * 取值 + */ + private String field; + /** + * 默认取值(field对应的值为空时) + */ + private String defaultField; + /** + * 取值长度,结合StringUtils.substring使用 + */ + private int subLength = 0; + /** + * 行跨度 + */ + private int rowSpan; + /** + * 列跨度 + */ + private int colSpan; +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportFieldTypeEnum.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportFieldTypeEnum.java new file mode 100644 index 0000000..758f6c0 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportFieldTypeEnum.java @@ -0,0 +1,29 @@ +package com.centricsoftware.enhancement.util.ExportUtil; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 导出字段类型 + * @author liaochangjiang + * @since 2024-03-05 10:50 + */ +@Getter +@AllArgsConstructor +public enum ExportFieldTypeEnum { + + STRING("str"), + FIX("fix"), + IMAGE("img"); + + private String shortName; + + public static ExportFieldTypeEnum getByShortName(String shortName) { + for (ExportFieldTypeEnum data : values()) { + if (data.shortName.equals(shortName)) { + return data; + } + } + return null; + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportTemplateRegion.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportTemplateRegion.java new file mode 100644 index 0000000..18bf6c6 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportTemplateRegion.java @@ -0,0 +1,49 @@ +package com.centricsoftware.enhancement.util.ExportUtil; + +import java.lang.annotation.*; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 导出模板区域 + * @author liaochangjiang + * @since 2024-03-05 10:44 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Repeatable(ExportTemplateRegion.List.class) +public @interface ExportTemplateRegion { + /** + * 开始行 + */ + int row1() default 0; + + /** + * 开始列 + */ + int col1() default 0; + + /** + * 结束行 + */ + int row2() default 0; + + /** + * 结束列 + */ + int col2() default 0; + + /** + * 分组 + */ + String[] groups() default {}; + + @Target({TYPE}) + @Retention(RUNTIME) + @Documented + public @interface List { + ExportTemplateRegion[] value(); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportUtil.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportUtil.java new file mode 100644 index 0000000..fb436c5 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ExportUtil.java @@ -0,0 +1,498 @@ +package com.centricsoftware.enhancement.util.ExportUtil; + +import cn.hutool.json.JSONUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.centricsoftware.commons.utils.EncodeUtils; +import com.centricsoftware.commons.utils.ExportExcel; +import com.centricsoftware.commons.utils.StringUtils; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellCopyPolicy; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; + +@Slf4j +@Component +public class ExportUtil { + + @Resource + C8NodeService c8NodeService; + + private final ThreadLocal templateRegion = new ThreadLocal<>(); + + private static final String FIELD_PREFIX = "{{"; + + private static int seqCol,seqRow; + + /** + * 设置导出响应相关信息 + * + * @author liaochangjiang + * @since 2022-05-27 14:42:30 + */ + public static void setExportResponseInfo(HttpServletResponse response, String fileName) { + response.reset(); + response.setContentType("application/octet-stream; charset=utf-8"); + response.setHeader("Content-Disposition", "attachment; filename=" + EncodeUtils.urlEncode(fileName)); + } + + /** + * 获取分析后的导出模板单元 + * + * @author liaochangjiang + * @since 2022-02-07 16:43:35 + */ + public List getAnalysisCells(ExportExcel excel) { + Sheet templateSheet = excel.getTemplateSheet(); + List list = new ArrayList<>(); + ExportTemplateRegion templateRegion = this.templateRegion.get(); + if (templateRegion == null) { + throw new RuntimeException("找不到导出模板区域配置"); + } + int row1 = templateRegion.row1(), row2 = templateRegion.row2(), col1 = templateRegion.col1(), col2 = templateRegion.col2(); + for (int i = row1; i <= row2; i++) { + for (int j = col1; j <= col2; j++) { + Cell cell = null; + try { + cell = templateSheet.getRow(i).getCell(j); + } catch (Exception e) { + log.error("获取模板单元出错, 行:{}, 列:{}", i, j); + } + if (cell != null && StringUtils.isNotBlank(cell.getStringCellValue())) { + final String cellVal = cell.getStringCellValue(); + AnalysisCell analysisCell = new AnalysisCell().setRow(i - row1).setCol(j - col1); + if (!cellVal.contains(FIELD_PREFIX)) { + // 固定值 + analysisCell.setField(cellVal).setType(ExportFieldTypeEnum.FIX); + list.add(analysisCell); + continue; + } + // 模板字段值 + String fieldName = getTemplateField(cellVal); + if (StringUtils.isNotBlank(fieldName)) { + String[] split = fieldName.split(":"); + if (split.length > 1) { + fieldName = split[1]; + String[] shortNames = split[0].split("\\|"); + analysisCell.setType(ExportFieldTypeEnum.getByShortName(shortNames[0])); + if (analysisCell.getType() == ExportFieldTypeEnum.STRING) { + if (shortNames.length > 1) { + analysisCell.setSubLength(Integer.parseInt(shortNames[1])); + } + } else if (analysisCell.getType() == ExportFieldTypeEnum.IMAGE) { + String[] rolCols = shortNames[1].split(","); + analysisCell.setRowSpan(Integer.parseInt(rolCols[0])) + .setColSpan(Integer.parseInt(rolCols[1])); + } + } + if (fieldName.contains("|")) { + String[] fieldNames = fieldName.split("\\|"); + analysisCell.setField(fieldNames[0]); + analysisCell.setDefaultField(fieldNames[1]); + } else { + analysisCell.setField(fieldName); + } + list.add(analysisCell); + } + } + } + } + // 清空模板配置 + for (int i = row1; i <= row2; i++) { + for (int j = col1; j <= col2; j++) { + Optional.ofNullable(excel.getSheet().getRow(i).getCell(j)).ifPresent(t -> t.setCellValue("")); + } + } + return list; + } + + /** + * 获取模板字段名 + * + * @author liaochangjiang + * @since 2022-02-07 16:45:01 + */ + public String getTemplateField(String cellValue) { + if (StringUtils.isNotBlank(cellValue) && cellValue.contains(FIELD_PREFIX)) { + return cellValue.substring(cellValue.indexOf(FIELD_PREFIX) + 2, cellValue.indexOf("}}")); + } + return null; + } + + /** + * 填充数据 + * + * @param excel 导出对象 + * @param analysisCells 分析后的单元字段 + * @param colIdx 当前系列起始列索引 + * @param curColIdx 当前系列内列索引 + * @param curRow 当前行索引 + * @param data 待填充数据 + * @author liaochangjiang + * @since 2022-02-07 16:45:12 + */ + public int fillData(ExportExcel excel, List analysisCells, int colIdx, int curColIdx, int curRow, + Object data) { + return fillData(excel, (XSSFSheet) excel.getSheet(), analysisCells, colIdx, curColIdx, curRow, data, null); + } + + /** + * 填充数据 + * + * @param excel 导出对象 + * @param analysisCells 分析后的单元字段 + * @param colIdx 当前系列起始列索引 + * @param curColIdx 当前系列内列索引 + * @param curRow 当前行索引 + * @param data 待填充数据 + * @param cellCopyPolicy cell复制策略 + * @author liaochangjiang + * @since 2022-09-16 16:19:52 + */ + public int fillData(ExportExcel excel, List analysisCells, int colIdx, int curColIdx, int curRow, + Object data, CellCopyPolicy cellCopyPolicy) { + return fillData(excel, (XSSFSheet) excel.getSheet(), analysisCells, colIdx, curColIdx, curRow, data, null, + cellCopyPolicy); + } + + /** + * 填充数据 + * + * @author liaochangjiang + * @since 2022-09-06 11:26:53 + */ + public int fillData(ExportExcel excel, List analysisCells, int colIdx, int curColIdx, int curRow, + Object data, BiFunction fieldValueTransfer) { + return fillData(excel, (XSSFSheet) excel.getSheet(), analysisCells, colIdx, curColIdx, curRow, data, + fieldValueTransfer); + } + + /** + * 填充数据 + * + * @author liaochangjiang + * @since 2022-09-16 16:20:11 + */ + public int fillData(ExportExcel excel, XSSFSheet sheet, List analysisCells, int colIdx, int curColIdx, + int curRow, Object data, BiFunction fieldValueTransfer) { + return fillData(excel, sheet, analysisCells, colIdx, curColIdx, curRow, data, fieldValueTransfer, + new CellCopyPolicy.Builder().cellValue(false).build()); + } + + /** + * 填充数据 + * + * @param excel 导出对象 + * @param analysisCells 分析后的单元字段 + * @param colIdx 当前系列起始列索引 + * @param curColIdx 当前系列内列索引 + * @param curRow 当前行索引 + * @param data 待填充数据 + * @param fieldValueTransfer 字段转换器 + * @param cellCopyPolicy cell复制策略 + * @author liaochangjiang + * @since 2022-04-27 16:45:12 + */ + public int fillData(ExportExcel excel, XSSFSheet sheet, List analysisCells, int colIdx, int curColIdx, + int curRow, Object data, BiFunction fieldValueTransfer, + CellCopyPolicy cellCopyPolicy) { + ExportTemplateRegion templateRegion = this.templateRegion.get(); + int templateColSpan = templateRegion.col2() - templateRegion.col1() + 1; + if (curColIdx >= templateColSpan) { + // 复制新列 + createNewCols(excel, sheet, colIdx + curColIdx); + } + if (curRow > sheet.getLastRowNum()) { + // 复制新行 + sheet.copyRows(templateRegion.row1(), templateRegion.row2(), curRow, cellCopyPolicy); + } + DocumentContext docCtx = JsonPath.using(Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS)).parse(JSONUtil.toJsonStr(data)); + for (AnalysisCell analysisCell : analysisCells) { + String fieldValue; + switch (analysisCell.getType()) { + case IMAGE: + fieldValue = getFieldValue(docCtx, analysisCell.getField()); + if (StringUtils.isNotBlank(fieldValue)) { + try (InputStream imageIs = c8NodeService.getFileFromDirectAddr(fieldValue)) { + excel.insertImageResize(0, imageIs, XSSFWorkbook.PICTURE_TYPE_PNG, 0, 0, 0, 0, + colIdx + curColIdx + analysisCell.getCol(), curRow + analysisCell.getRow(), + colIdx + curColIdx + analysisCell.getCol() + analysisCell.getColSpan(), + curRow + analysisCell.getRow() + analysisCell.getRowSpan(), -0.002); + } catch (IOException e) { + log.error("获取图片失败,路径:{}", fieldValue); + } + } + break; + case STRING: + fieldValue = getFieldValue(docCtx, analysisCell.getField()); + if (StringUtils.isBlank(fieldValue) && StringUtils.isNotBlank(analysisCell.getDefaultField())) { + fieldValue = getFieldValue(docCtx, analysisCell.getDefaultField()); + } + if (analysisCell.getSubLength() != 0) { + fieldValue = StringUtils.substring(fieldValue, analysisCell.getSubLength()); + } + if (fieldValueTransfer != null) { + fieldValue = fieldValueTransfer.apply(analysisCell, fieldValue); + } + fillCell(sheet, colIdx, curColIdx, curRow, analysisCell, fieldValue); + break; + default: + fieldValue = analysisCell.getField(); + fillCell(sheet, colIdx, curColIdx, curRow, analysisCell, fieldValue); + } + } + curColIdx += templateColSpan; + return curColIdx; + } + + /** + * 填充数据 + * + * @param excel 导出对象 + * @param maxCol 指向最大的列,超过之后换行 + * @param list 待填充数据 + * @author liaochangjiang + * @since 2022-04-27 16:45:12 + */ + public void fillDataComment(ExportExcel excel,int maxCol, List list) { + ExportTemplateRegion templateRegion = this.templateRegion.get(); + int seqColIdx = 0;// 记录处理的行索引 + int colIdx = templateRegion.col1();// 初始开始行 + int curRow = templateRegion.row1();// 初始开始列 + List analysisCells = this.getAnalysisCells(excel); + for (Object data : list) { + // 填充内容 + seqColIdx = this.fillData(excel, analysisCells, colIdx, seqColIdx, curRow, data); + // 记录当满足什么时候进行换行。 + if (seqColIdx + templateRegion.col1() > maxCol) { + seqColIdx = 0;// 并且重置处理的行索引 + curRow += this.getTemplateRegionRowSpan();// 行切换以初始行+目前处理行索引 + } + } + } + + /** + * 填充单元格 + * + * @author liaochangjiang + * @since 2022-10-19 15:23:21 + */ + private void fillCell(XSSFSheet sheet, int colIdx, int curColIdx, int curRow, AnalysisCell analysisCell, + String fieldValue) { + try { + sheet.getRow(curRow + analysisCell.getRow()).getCell(colIdx + curColIdx + analysisCell.getCol()) + .setCellValue(fieldValue); + } catch (Exception e) { + log.error("填充excel失败。curRow:{}, templateRow:{}, curCol:{}, templateCol:{}, fieldValue:{}", curRow, + analysisCell.getRow(), colIdx + curColIdx, analysisCell.getCol(), fieldValue); + throw new RuntimeException("填充excel失败", e); + } + } + + /** + * 获取模板字段对应的值 + * + * @author liaochangjiang + * @since 2022-02-07 16:48:17 + */ + public String getFieldValue(DocumentContext jsonCtx, String field) { + return Optional.ofNullable(jsonCtx.read(String.format("$.%s", field))).map(String::valueOf) + .orElse(StringUtils.EMPTY); + } + + /** + * 创建新列 + * + * @author liaochangjiang + * @since 2022-01-24 16:11:44 + */ + public void createNewCols(ExportExcel excel, XSSFSheet sheet, int colIdx) { + Sheet templateSheet = excel.getTemplateSheet(); + int lastRow = sheet.getLastRowNum(); + ExportTemplateRegion templateRegion = this.templateRegion.get(); + for (int i = 0; i <= lastRow; i++) { + XSSFRow tmpRow = sheet.getRow(i); + for (int j = templateRegion.col1(); j <= templateRegion.col2(); j++) { + XSSFCell cell = tmpRow.getCell(j); + if (cell != null) { + int curColIdx = colIdx + j - templateRegion.col1(); + if (tmpRow.getCell(curColIdx) != null) { + return; + } + XSSFCell distCell = tmpRow.createCell(curColIdx); + excel.copyCell(cell, distCell, false); + // 设置列宽 + if (i == 1) { + sheet.setColumnWidth(curColIdx, sheet.getColumnWidth(j)); + } + int regions = templateSheet.getNumMergedRegions(); + for (int k = 0; k < regions; k++) { + CellRangeAddress region = templateSheet.getMergedRegion(k); + if (region.getLastRow() == i && region.getLastColumn() == j) { + CellRangeAddress newRegion = new CellRangeAddress(i, i, + curColIdx - (region.getLastColumn() - region.getFirstColumn()), curColIdx); + sheet.addMergedRegion(newRegion); + } + } + } + } + } + } + + + /** + * 初始化模板区域 + * + * @author liaochangjiang + * @since 2022-02-18 10:21:41 + */ + public void initTemplateRegion(Class clazz, String group) { + ExportTemplateRegion[] annotations = clazz.getAnnotationsByType(ExportTemplateRegion.class); + ExportTemplateRegion result = null; + for (ExportTemplateRegion region : annotations) { + if (StringUtils.isBlank(group)) { + result = region; + break; + } else if (ArrayUtils.indexOf(region.groups(), group) != -1) { + result = region; + break; + } + } + templateRegion.set(result); + } + + public ThreadLocal getTemplateRegion() { + return templateRegion; + } + + /** + * 获取模板区域行跨度 + * + * @author liaochangjiang + * @since 2022-02-18 10:32:35 + */ + public int getTemplateRegionRowSpan() { + ExportTemplateRegion region = templateRegion.get(); + if (region == null) { + throw new RuntimeException("找不到导出模板区域配置"); + } + return region.row2() - region.row1() + 1; + } + + /** + * 获取模板区域列跨度 + * + * @author liaochangjiang + * @since 2023-01-16 10:05:10 + */ + public int getTemplateRegionColSpan() { + ExportTemplateRegion region = templateRegion.get(); + if (region == null) { + throw new RuntimeException("找不到导出模板区域配置"); + } + return region.col2() - region.col1() + 1; + } + + /** + * 移除模板区域本地变量 + * + * @author liaochangjiang + * @since 2022-02-18 10:22:19 + */ + public void remoteTemplateRegion() { + templateRegion.remove(); + } + + /** + * 固定模板填充占位-EasyExcel + * 不导出前台,会根据传入的输出流输出到某个路径下, 并且做后续的操作,如:可重新将输出流的文件进行PDF文件转化 + * + * @param outputStream 文件输出流 + * @param template 模板文件位置 + * @param data 填充的对象数据 + * @author liaochangjiang + * @since 2022-09-16 16:19:52 + */ + public void exportByStatic(OutputStream outputStream,String template,Object data) { + ClassPathResource classPathResource = new ClassPathResource(template); + try (InputStream in = classPathResource.getInputStream()) { + ExcelWriter excelWriter = EasyExcel + .write(outputStream) + .withTemplate(in) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet(0) + .registerWriteHandler(new ImageModifyHandler()) + .build(); + excelWriter.fill(data, writeSheet); + excelWriter.finish(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void exportByStatic(OutputStream outputStream,String template,List list) { + ClassPathResource classPathResource = new ClassPathResource(template); + try (InputStream in = classPathResource.getInputStream()) { + ExcelWriter excelWriter = EasyExcel + .write(outputStream) + .withTemplate(in) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet(0) + .registerWriteHandler(new ImageModifyHandler()) + .build(); + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + for (Object vo : list) { + excelWriter.fill(vo, fillConfig, writeSheet); + } + excelWriter.finish(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 固定模板填充占位-EasyExcel + * 不导出前台,会根据传入的输出流输出到某个路径下, 并且做后续的操作,如:可重新将输出流的文件进行PDF文件转化 + * + * @param data 填充的对象数据 + * @author liaochangjiang + * @since 2022-09-16 16:19:52 + */ + public InputStream exportByStatic(InputStream in,Object data) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ExcelWriter excelWriter = EasyExcel + .write() + .withTemplate(in) + .file(os) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet(0) + .build(); + excelWriter.fill(data, writeSheet); + excelWriter.finish(); + byte[] buffer = os.toByteArray(); + return new ByteArrayInputStream(buffer); + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ImageModifyHandler.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ImageModifyHandler.java new file mode 100644 index 0000000..0d5278d --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ExportUtil/ImageModifyHandler.java @@ -0,0 +1,182 @@ +package com.centricsoftware.enhancement.util.ExportUtil; + +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.metadata.data.ImageData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.write.handler.CellWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import lombok.Setter; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFPictureData; +import org.springframework.util.CollectionUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + + +public class ImageModifyHandler implements CellWriteHandler { + @Setter + private List cellRangeAddressList = null; + + /** + * 得到合并行num + * + * @param cell 细胞 + * @param sheet 表 + * @return int + */ + public int getMergeRowNum(Cell cell, Sheet sheet) { + int mergeSize = 1; + List mergedRegions = sheet.getMergedRegions(); + if (CollectionUtils.isEmpty(cellRangeAddressList)) { + for (CellRangeAddress cellRangeAddress : mergedRegions) { + if (cellRangeAddress.isInRange(cell)) { + //获取合并的行数 + mergeSize = cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow() + 1; + break; + } + } + } else { + for (CellRangeAddress cellRangeAddress : cellRangeAddressList) { + //获取合并的行数 + mergeSize = cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow() + 1; + break; + } + } + + return mergeSize; + } + + /** + * 得到合并列num + * + * @param cell 细胞 + * @param sheet 表 + * @return int + */ + public int getMergeColumNum(Cell cell, Sheet sheet) { + int mergeSize = 1; + List mergedRegions = sheet.getMergedRegions(); + if (CollectionUtils.isEmpty(cellRangeAddressList)) { + for (CellRangeAddress cellRangeAddress : mergedRegions) { + if (cellRangeAddress.isInRange(cell)) { + //获取合并的列数 + mergeSize = cellRangeAddress.getLastColumn() - cellRangeAddress.getFirstColumn() + 1; + break; + } + } + } else { + for (CellRangeAddress cellRangeAddress : cellRangeAddressList) { + //获取合并的行数 + mergeSize = cellRangeAddress.getLastColumn() - cellRangeAddress.getFirstColumn() + 1; + break; + } + } + return mergeSize; + } + + /** + * 获取列宽像素-包含合并 + * @author liaochangjiang + * @since 2024-04-24 16:03 + */ + private Double getColumNumPx (Cell cell,Sheet sheet,int mergeColumNum) { + int start = cell.getColumnIndex(); + Row row = cell.getRow(); + double columLength = 0; + for (int i = start;i < start + mergeColumNum;i++) { + columLength += sheet.getColumnWidthInPixels(i); + } + return columLength; + } + + /** + * 获取行高像素-包含合并 + * @author liaochangjiang + * @since 2024-04-24 16:03 + */ + private Double getRowPx (Cell cell,Sheet sheet,int mergeRowNum) { + Row row = cell.getRow(); + int start = row.getRowNum(); + double rowLength = 0; + for (int i = start;i < start + mergeRowNum;i++) { + rowLength += (sheet.getRow(i).getHeightInPoints() / 72) * 96;// 像素单位 + } + return rowLength; + } + + /** + * 后单元格数据转换 + * + * @param writeSheetHolder 写单夹 + * @param writeTableHolder 写表夹 + * @param cellData 单元格数据 + * @param cell 细胞 + * @param head 头 + * @param relativeRowIndex 相对行索引 + * @param isHead 是头 + */ + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + WriteCellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + boolean noImageValue = Objects.isNull(cellData) || CollectionUtils.isEmpty(cellData.getImageDataList()); + if (Objects.equals(Boolean.TRUE, isHead) || noImageValue) { + return; + } + try { + Sheet sheet = cell.getSheet(); + int mergeColumNum = getMergeColumNum(cell, sheet);// 获取实际合并的列 + int mergeRowNum = getMergeRowNum(cell, sheet);// 获取实际合并的行 + double cellWidth = getColumNumPx(cell,sheet,mergeColumNum);// 计算真实的列宽,像素单位 + double cellHeight = getRowPx(cell,sheet,mergeRowNum);// 计算真是的行高,像素单位 + ImageData imageData = cellData.getImageDataList().get(0); + BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(cellData.getImageDataList().get(0).getImage()));// 获取图片的数据 + double imageWidth = bufferedImage.getWidth();// 图片的宽度,像素 + double imageHeight = bufferedImage.getHeight();// 图片的高度,像素 + //等比例缩小图片,直到图片能放在单元格下,每次缩小20% + Integer top = 0,left = 0; + // 比例计算 - 优化 + //厘米转换成像素,按实际需求转换 + // while (true) { + // if(imageHight <= rowLength && imageWidth <= columLength){ + // //计算边框值 + // top = Math.toIntExact(Math.round((rowLength - imageHight)/2)); + // left = Math.toIntExact(Math.round((columLength - imageWidth)/2)); + // break; + // }else { + // imageHight = imageHight * 0.8; + // imageWidth = imageWidth * 0.8; + // } + // } + //缩放比例 + if (imageWidth > 0 && imageHeight > 0) { + double scaleWidth = cellWidth / imageWidth; + double scaleHeight = cellHeight / imageHeight; + double scale = Math.min(scaleWidth, scaleHeight) - 0.001; + top = Math.toIntExact(Math.round((cellHeight - imageHeight * scale)/2)); + left = Math.toIntExact(Math.round((cellWidth - imageWidth * scale)/2)); + } + imageData.setRelativeLastRowIndex(mergeRowNum - 1); + imageData.setRelativeLastColumnIndex(mergeColumNum - 1); + // 上右下左需要留空,通过这种方式调整图片大小,单位为像素 + imageData.setTop(top); + imageData.setRight(left); + imageData.setBottom(top); + imageData.setLeft(left); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/ImportUtil.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ImportUtil.java new file mode 100644 index 0000000..3cfd84a --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/ImportUtil.java @@ -0,0 +1,107 @@ +package com.centricsoftware.enhancement.util; + + +import com.centricsoftware.enhancement.ant.C8ColumnConfig; + +import java.util.Map; + +public class ImportUtil { + /** + * 拼接保存的xml + * @param id + * @param type + * @param value + * @param otherXmlAttr + * @return + */ + public static String getChangeAttrListXml(C8ColumnConfig col, String id, String type, String value, String otherXmlAttr){ + StringBuilder xml = new StringBuilder(); + xml.append( ""); + xml.append(""); + xml.append(""); + return xml.toString(); + } + + public static String getChangeAttrListXml(String id, String type, String value, String otherXmlAttr){ + StringBuilder xml = new StringBuilder(); + xml.append( ""); + xml.append(""); + xml.append(""); + return xml.toString(); + } + + public static String getChangeAttrEnumListXml(C8ColumnConfig col,String id,String type,String value,String otherXmlAttr){ + StringBuilder xml = new StringBuilder(); + xml.append( ""); + xml.append(value); + xml.append(""); + return xml.toString(); + } + + /** + * 拼接保存的xml + * @param id + * @param type + * @param value + * @param otherXmlAttr + * @return + */ + public static String getChangeAttrXml(C8ColumnConfig col,String id,String type,String value,String otherXmlAttr){ + StringBuilder xml = new StringBuilder(); + xml.append( ""); + return xml.toString(); + } + + public static String getChangeAttrXml(String id,String type,String value,String otherXmlAttr){ + StringBuilder xml = new StringBuilder(); + xml.append( ""); + return xml.toString(); + } + + /** + * 封装带有path的xml + * @param url + * @param path + * @param pathMap + * @param fieldxml + */ + public static void setPathXml(String url, String path, Map pathMap, String fieldxml){ + if(pathMap.containsKey(path)){ + StringBuilder xml = pathMap.get(path); + xml.append(fieldxml); + }else{ + StringBuilder xml = new StringBuilder(); + String pathTrans = path.replaceAll(":", "%3A"); + xml.append(""); + xml.append(fieldxml); + pathMap.put(path,xml); + } + } + + /** + * 拼接并返回pathxml + * @param pathMap + * @return + */ + public static String appendPathXml(Map pathMap){ + StringBuilder xml = new StringBuilder(); + for(Map.Entry entry : pathMap.entrySet()){ + xml.append(entry.getValue()); + xml.append(""); + } + return xml.toString(); + } + + /** + * 计算表达式相关 + * @param col + * @return + */ + private static String appendExp(C8ColumnConfig col){ + if(col.isRemoveExp()){ + return "Exp=''"; + } + return ""; + } + +} diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/UploadtProperties.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/UploadtProperties.java new file mode 100644 index 0000000..0e30436 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/UploadtProperties.java @@ -0,0 +1,25 @@ +package com.centricsoftware.enhancement.util; + +import com.centricsoftware.enhancement.ant.C8EntityConfig; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Map; + + +/** + * 导入Excel时,获取cs.excel的配置文件 + */ +@Data +@Component +@ConfigurationProperties(prefix = "cs.excel") +public class UploadtProperties { + Map upload; + + public C8EntityConfig getValue(String key){ + return this.getUpload().containsKey(key)?this.getUpload().get(key):null; + } + +} + diff --git a/enhancement/src/main/java/com/centricsoftware/enhancement/util/http/C8HttpUtil.java b/enhancement/src/main/java/com/centricsoftware/enhancement/util/http/C8HttpUtil.java new file mode 100644 index 0000000..e4f7d39 --- /dev/null +++ b/enhancement/src/main/java/com/centricsoftware/enhancement/util/http/C8HttpUtil.java @@ -0,0 +1,53 @@ +package com.centricsoftware.enhancement.util.http; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.Headers; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.internal.http.HttpHeaders; +import okio.Buffer; +import okio.BufferedSource; + +import java.io.IOException; +import java.nio.charset.Charset; + +@Slf4j +public class C8HttpUtil { + + /** + * 复制请求体;只支持UTF-8 + * 传入的Response为okhttp + * 此方法用于C8的交互 + */ + public static String copyResponseBody(Response response){ + try { + ResponseBody responseBody = response.body(); + long contentLength = responseBody.contentLength(); + if(!HttpHeaders.hasBody(response)){ + return "";//无ResponseBody,返回空 + } else if (C8HttpUtil.bodyEncoded(response.headers())) { + //HTTP (encoded body omitted) + return ""; + } else { + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.getBuffer(); + Charset charset = Charset.forName("UTF-8"); + if (contentLength != 0) { + return buffer.clone().readString(charset); + } + } + } catch (IOException e) { + log.error("获取返回body出错",e); + return ""; + } + return ""; + } + + public static boolean bodyEncoded(Headers headers) { + String contentEncoding = headers.get("Content-Encoding"); + return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); + } + + +} diff --git a/enhancement/src/main/resources/mapper/ExtractMapper.xml b/enhancement/src/main/resources/mapper/ExtractMapper.xml new file mode 100644 index 0000000..b368468 --- /dev/null +++ b/enhancement/src/main/resources/mapper/ExtractMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/pom.xml b/integration/pom.xml new file mode 100644 index 0000000..08be529 --- /dev/null +++ b/integration/pom.xml @@ -0,0 +1,24 @@ + + + + plmservice + com.centricsoftware + 2.0 + + 4.0.0 + integration + 0.10 + + + + + + + + com.centricsoftware + enhancement + + + diff --git a/integration/src/main/java/com/centricsoftware/integration/controller/dingtalk/DingTalkController.java b/integration/src/main/java/com/centricsoftware/integration/controller/dingtalk/DingTalkController.java new file mode 100644 index 0000000..3a3077a --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/controller/dingtalk/DingTalkController.java @@ -0,0 +1,34 @@ +package com.centricsoftware.integration.controller.dingtalk; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.integration.service.dingtalk.DingTalkService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@Slf4j +@RestController +public class DingTalkController { + @Autowired + DingTalkService dingTalkService; + + /** + * 发送钉钉消息到个人用户 + * @param map + * @return + */ + @PostMapping("/sendDingMsg") + public ResEntity sendMsgSingle(@RequestBody Map map){ + String msgContent = (String)map.get("msgContent"); + String userList = (String)map.get("userList"); + log.info("发送钉钉消息:["+msgContent+"]给"+userList); + String result = dingTalkService.sendMsgToUser(msgContent,userList); + log.info("发送结果:"+result); + return WebResponse.success(""); + } +} diff --git a/integration/src/main/java/com/centricsoftware/integration/controller/flybook/FlyBookController.java b/integration/src/main/java/com/centricsoftware/integration/controller/flybook/FlyBookController.java new file mode 100644 index 0000000..b095b0f --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/controller/flybook/FlyBookController.java @@ -0,0 +1,73 @@ +package com.centricsoftware.integration.controller.flybook; + + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.integration.service.flybook.FlyBookService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 飞书集成发送消息控制器 + */ +@Slf4j +@RestController +public class FlyBookController { + @Autowired + FlyBookService flyBookService; + +// @PostMapping("/sendMsg") +// public ResEntity sendMsg(String server,String url,String name,String status,String users, String msgType,String msgContent){ +// //fbService.senMsgGroup(url,name,status); +// fbService.senMsgSingleByUserId(server,url,name,status,users,msgType,msgContent); +// return WebResponse.success(""); +// } + @PostMapping("/sendMsgSingle") + public ResEntity sendMsgSingle(String server, String url, String name, String status, String users, String msgType, String msgContent){ + flyBookService.senMsgSingleByEmail(server,url,name,status,users,msgType,msgContent); + return WebResponse.success(""); + } + @PostMapping("/sendMsgByEmail") + public ResEntity sendMsgByEmail(@RequestBody Map map){ + String server = (String)map.get("server"); + String url = (String)map.get("url"); + String status = (String)map.get("status"); + String users = (String)map.get("users"); + String msgType = (String)map.get("msgType"); + String name = (String)map.get("name"); + String msgContent = (String)map.get("msgContent"); + flyBookService.senMsgSingleByEmail(server,url,name,status,users,msgType,msgContent); + return WebResponse.success(""); + } +// @PostMapping("/sendMsgByUserId") +// public ResEntity sendMsgToPerson(@RequestBody Map map){ +// String server = (String)map.get("server"); +// String url = (String)map.get("url"); +// String status = (String)map.get("status"); +// String users = (String)map.get("users"); +// String msgType = (String)map.get("msgType"); +// String name = (String)map.get("name"); +// String msgContent = (String)map.get("msgContent"); +// fbService.senMsgSingleByUserId(server,url,name,status,users,msgType,msgContent); +// return WebResponse.success(""); +// } + @PostMapping("/sendMsgByDepartmentId") + public ResEntity sendMsgGroup(@RequestBody Map map){ + String server = (String)map.get("server"); + String name = (String)map.get("name"); + String status = (String)map.get("status"); + String users = (String)map.get("users"); + String msgType = (String)map.get("msgType"); + String url = (String)map.get("url"); + String msgContent = (String)map.get("msgContent"); + flyBookService.sendBatchMsgByDepatmentId(server,url,name,status,users,msgType,msgContent); + return WebResponse.success(""); + } + + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/controller/wechat/WeChatController.java b/integration/src/main/java/com/centricsoftware/integration/controller/wechat/WeChatController.java new file mode 100644 index 0000000..d6dce97 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/controller/wechat/WeChatController.java @@ -0,0 +1,32 @@ +package com.centricsoftware.integration.controller.wechat; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.integration.dto.param.BaseParamDto; +import com.centricsoftware.integration.dto.wechat.WeChatDataDto; +import com.centricsoftware.integration.service.wechat.WeChatService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + + +@RestController +@RequestMapping("/wechat") +public class WeChatController { + @Resource + WeChatService weChatService; + + @PostMapping("/getToken") + public ResEntity getToken() { + //return WebResponse.success(weChatService.getToken()); //此接口不对外开放 + return WebResponse.failure("此接口不对外开放"); + } + + @PostMapping("/sendMsg") + public ResEntity sendMsg(@RequestBody BaseParamDto param) { + return weChatService.sendMsg(param); + } +} diff --git a/integration/src/main/java/com/centricsoftware/integration/dto/param/BaseParamDto.java b/integration/src/main/java/com/centricsoftware/integration/dto/param/BaseParamDto.java new file mode 100644 index 0000000..2b776e1 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/dto/param/BaseParamDto.java @@ -0,0 +1,8 @@ +package com.centricsoftware.integration.dto.param; + +import lombok.Data; + +@Data +public class BaseParamDto { + public T param; +} diff --git a/integration/src/main/java/com/centricsoftware/integration/dto/wechat/WeChatDataDto.java b/integration/src/main/java/com/centricsoftware/integration/dto/wechat/WeChatDataDto.java new file mode 100644 index 0000000..bc20be0 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/dto/wechat/WeChatDataDto.java @@ -0,0 +1,115 @@ +package com.centricsoftware.integration.dto.wechat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 微信消息发送实体类 + * 发送微信消息的 https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token= + * + * @author Harry + * @history 2022年07月27日09:08:05 Jerry 优化调整 + */ +@Data +public class WeChatDataDto { + //touser、toparty、totag不能同时为空 + + /** + * 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。 + * 特殊情况:指定为"@all",则向该企业应用的全部成员发送 + */ + @JsonProperty("touser") + private String touser; + + /** + * 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。 + * 当touser为"@all"时忽略本参数 + */ + @JsonProperty("toparty") + private String toparty; + + /** + * 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。 + * 当touser为"@all"时忽略本参数 + */ + @JsonProperty("totag") + private String totag; + + /** + * 消息类型,此时固定为:text + */ + @JsonProperty("msgtype") + private String msgtype = "textcard"; + + /** + * 企业用用的agentid + * 企业应用的id,企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值 + */ + @JsonProperty("agentid") + private String agentid; + + /** + * 消息内容,最长不超过2048个字节,超过将截断(支持id转译) + */ +// @JsonProperty("text") +// private Content text; +// +// @JsonProperty("content") +// private String content; + + /** + * 内容体 + */ + @JsonProperty("textcard") + private Textcard text1; + + + /** + * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + */ +// @JsonProperty("safe") +// private int safe = 1; + + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + @JsonProperty("enable_id_trans") + private int enable_id_trans = 0; + + /** + * 表示是否开启重复消息检查,0表示否,1表示是,默认0 + */ + @JsonProperty("enable_duplicate_check") + private int enable_duplicate_check = 0; + + /** + * 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时 + */ + @JsonProperty("duplicate_check_interval") + private int duplicate_check_interval = 1800; +} + +//@Data +//class Content { +// @JsonProperty("content") +// private String content; +//} +@Data +class Textcard { + /** + * 标题,不超过128个字节,超过会自动截断(支持id转译) + */ + @JsonProperty("title") + private String title; + /** + * 描述,不超过512个字节,超过会自动截断(支持id转译) + */ + @JsonProperty("description") + private String description; + /** + * 点击后跳转的链接。最长2048字节,请确保包含了协议头(http/https) + */ + @JsonProperty("url") + private String url; +} + diff --git a/integration/src/main/java/com/centricsoftware/integration/feign/MultipartSupportConfig.java b/integration/src/main/java/com/centricsoftware/integration/feign/MultipartSupportConfig.java new file mode 100644 index 0000000..009a6e7 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/feign/MultipartSupportConfig.java @@ -0,0 +1,21 @@ +package com.centricsoftware.integration.feign; + +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; + +public class MultipartSupportConfig { + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Encoder feignFormEncoder() { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookFeignRequestInterceptor.java b/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookFeignRequestInterceptor.java new file mode 100644 index 0000000..625c326 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookFeignRequestInterceptor.java @@ -0,0 +1,33 @@ +package com.centricsoftware.integration.feign.flybook; + +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.integration.flybook.FlyBookAppAccessToken; +import com.centricsoftware.integration.service.flybook.FlyBookTokenService; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpHeaders; + +public class FlyBookFeignRequestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate template) { + FlyBookTokenService bean = SpringContextHolder.getBean(FlyBookTokenService.class); + FlyBookAppAccessToken token = bean.getToken(); + template.header(HttpHeaders.AUTHORIZATION,String.format("%s %s", "Bearer", token.getApp_access_token())); + } + + @Autowired + private ObjectFactory messageConverters; + + @Bean + public Encoder feignFormEncoder() { + return new SpringFormEncoder(new SpringEncoder(messageConverters)); + } +} \ No newline at end of file diff --git a/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookMessageFeign.java b/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookMessageFeign.java new file mode 100644 index 0000000..a5373ad --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookMessageFeign.java @@ -0,0 +1,31 @@ +package com.centricsoftware.integration.feign.flybook; + +import com.centricsoftware.integration.flybook.FlyBookBatchMessage; +import com.centricsoftware.integration.flybook.FlyBookMessage; +import com.centricsoftware.integration.flybook.FlyBookResultData; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + + +@FeignClient(url = "${cs.flybook.baseUrl}",name = "fb-msg",configuration = FlyBookFeignRequestInterceptor.class) +public interface FlyBookMessageFeign { + + @PostMapping(value = "/im/v1/messages?receive_id_type=chat_id") + FlyBookResultData sendMsgGroup(@RequestBody FlyBookMessage msg); + + @PostMapping(value = "/im/v1/messages?receive_id_type=email") + FlyBookResultData sendMsgSingleByEmail(@RequestBody FlyBookMessage msg); + +// @PostMapping(value = "/im/v1/messages?receive_id_type=user_id") +// ResultData sendMsgByUserId(@RequestBody FBMessage msg); + + /** + * 批量发送消息给用户 + *FBMessage消息体中department_ids, open_ids, user_ids 三个字段至少填一个 + * @param msg + * @return + */ + @PostMapping(value = "/im/v1/messages/v4/batch_send/") + FlyBookResultData sendBatchMsg(@RequestBody FlyBookBatchMessage msg); +} diff --git a/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookTokenFeign.java b/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookTokenFeign.java new file mode 100644 index 0000000..339fe6c --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/feign/flybook/FlyBookTokenFeign.java @@ -0,0 +1,16 @@ +package com.centricsoftware.integration.feign.flybook; + +import com.centricsoftware.integration.feign.MultipartSupportConfig; +import com.centricsoftware.integration.flybook.FlyBookAppAccessToken; +import com.centricsoftware.integration.flybook.FlyBookAppData; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(url = "${cs.flybook.baseUrl}",name = "fb-token",configuration = {MultipartSupportConfig.class}) +public interface FlyBookTokenFeign { + + @PostMapping(value = "/auth/v3/app_access_token/internal") + FlyBookAppAccessToken getAppAccessToken(@RequestBody FlyBookAppData flyBookAppData); + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/feign/wechat/WeChatFeign.java b/integration/src/main/java/com/centricsoftware/integration/feign/wechat/WeChatFeign.java new file mode 100644 index 0000000..2001e83 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/feign/wechat/WeChatFeign.java @@ -0,0 +1,26 @@ +package com.centricsoftware.integration.feign.wechat; + + +import com.centricsoftware.integration.dto.wechat.WeChatDataDto; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(url = "${cs.wechat.url}", name = "c8-wechat") +public interface WeChatFeign { + + /** + * 获取企业微信鉴权Token + * + * @param corpid appid + * @param corpsecret secret + * @return 微信token + */ + @PatchMapping(value = "/gettoken?corpid={corpid}&corpsecret={corpsecret}", headers = {"Content-Type=application/x-www-form-urlencoded;charset=UTF-8;"}) + String getToken(@PathVariable String corpid, @PathVariable String corpsecret); + + @PostMapping(value = "/message/send?access_token={access_token}", headers = {"Content-Type=application/json;charset=UTF-8;"}) + String sendMessage(@PathVariable String access_token, @RequestBody WeChatDataDto param); +} diff --git a/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAccessToken.java b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAccessToken.java new file mode 100644 index 0000000..9a908d2 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAccessToken.java @@ -0,0 +1,24 @@ +package com.centricsoftware.integration.flybook; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FlyBookAppAccessToken { + + @JsonProperty("app_access_token") + private String app_access_token; + + @JsonProperty("code") + private String code; + + @JsonProperty("expire") + private String expire; + + @JsonProperty("msg") + private String msg; + + @JsonProperty("tenant_access_token") + private String tenant_access_token; + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAuthenAccessToken.java b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAuthenAccessToken.java new file mode 100644 index 0000000..8a6ac98 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppAuthenAccessToken.java @@ -0,0 +1,16 @@ +package com.centricsoftware.integration.flybook; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +public class FlyBookAppAuthenAccessToken { + @JsonProperty("grant_type") + String grantType = "authorization_code"; + @JsonProperty("code") + String code; +} diff --git a/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppData.java b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppData.java new file mode 100644 index 0000000..b2ef1c7 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookAppData.java @@ -0,0 +1,15 @@ +package com.centricsoftware.integration.flybook; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FlyBookAppData { + + @JsonProperty("app_id") + private String app_id = ""; + + @JsonProperty("app_secret") + private String app_secret = ""; + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookBatchMessage.java b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookBatchMessage.java new file mode 100644 index 0000000..7d17ab4 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookBatchMessage.java @@ -0,0 +1,30 @@ +package com.centricsoftware.integration.flybook; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class FlyBookBatchMessage implements Serializable { + + @JsonIgnore + private String template = "{\"config\": {\"wide_screen_mode\": true},\"elements\": [ {\"tag\": \"div\",\"text\": {\"content\": \"{}\",\"tag\": \"lark_md\"}},{\"extra\": {\"tag\": \"button\",\"text\": {\"content\": \"点击查看\",\"tag\": \"lark_md\"},\"type\": \"danger\",\"url\": \"{}\"},\"tag\": \"div\",\"text\": {\"content\": \"发送时间:{}\",\"tag\": \"lark_md\"}}],\"header\": {\"template\": \"green\",\"title\": {\"content\": \"{}\",\"tag\": \"plain_text\"}}}"; + + @JsonProperty("content") + private String content; + + @JsonProperty("msg_type") + private String msg_type = "interactive"; + + @JsonProperty("department_ids") + private String[] department_ids = {}; + + @JsonProperty("open_ids") + private String[] open_ids = {}; + + @JsonProperty("user_ids") + private String[] user_ids = {}; + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookMessage.java b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookMessage.java new file mode 100644 index 0000000..71a1100 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookMessage.java @@ -0,0 +1,25 @@ +package com.centricsoftware.integration.flybook; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class FlyBookMessage implements Serializable { + + @JsonIgnore + private String template = "{\"config\": {\"wide_screen_mode\": true},\"elements\": [ {\"tag\": \"div\",\"text\": {\"content\": \"{}\",\"tag\": \"lark_md\"}},{\"extra\": {\"tag\": \"button\",\"text\": {\"content\": \"进入PLM系统\",\"tag\": \"lark_md\"},\"type\": \"danger\",\"url\": \"{}\"},\"tag\": \"div\",\"text\": {\"content\": \"发送时间:{}\",\"tag\": \"lark_md\"}}],\"header\": {\"template\": \"{}\",\"title\": {\"content\": \"{}\",\"tag\": \"plain_text\"}}}"; + + @JsonProperty("content") + private String content; + + @JsonProperty("msg_type") + private String msg_type = "interactive"; + + @JsonProperty("receive_id") + private String receive_id = ""; + + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookResultData.java b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookResultData.java new file mode 100644 index 0000000..0339857 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/flybook/FlyBookResultData.java @@ -0,0 +1,15 @@ +package com.centricsoftware.integration.flybook; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FlyBookResultData { + + @JsonProperty + private String code; + @JsonProperty + private Object data; + @JsonProperty + private String msg; +} diff --git a/integration/src/main/java/com/centricsoftware/integration/service/dingtalk/DingTalkService.java b/integration/src/main/java/com/centricsoftware/integration/service/dingtalk/DingTalkService.java new file mode 100644 index 0000000..45377bc --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/service/dingtalk/DingTalkService.java @@ -0,0 +1,118 @@ +package com.centricsoftware.integration.service.dingtalk; + +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.OapiGettokenRequest; +import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request; +import com.dingtalk.api.request.OapiMessageCorpconversationSendbytemplateRequest; +import com.dingtalk.api.response.OapiGettokenResponse; +import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response; +import com.dingtalk.api.response.OapiMessageCorpconversationSendbytemplateResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class DingTalkService { + +// public static void main (String args[]){ +// DingService dt = new DingService(); +// dt.sendMsgToUser("来自于程序的测试消息\\n发送时间:"+new Date(),"2749363561-2001123913"); +// } + + @Autowired + C8NodeService c8NodeService; + + /** + * 发送个人普通消息给用户,异步发送,不解析发送结果,调用成功即认为成功 + * @param msgContent + * @param userList 用户的钉钉id,用逗号分隔,代表发送给多个用户 + */ + public String sendMsgToUser(String msgContent, String userList) { + try { + DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"); + OapiMessageCorpconversationAsyncsendV2Request req = new OapiMessageCorpconversationAsyncsendV2Request(); + CsProperties properties = c8NodeService.getProperties(); + //用于发送消息的应用的appid,secret + String agentId = properties.getPlm().get("cs.dingtalk.agentid"); + if(StrUtil.isBlank(agentId)) + agentId = "0"; + long agentIntId = 0; + try{ + agentIntId = Long.parseLong(agentId); + }catch(Exception e){} + req.setAgentId(agentIntId); + req.setUseridList(userList); + + //设置消息内容,普通文本消息 + OapiMessageCorpconversationAsyncsendV2Request.Msg msgObj = new OapiMessageCorpconversationAsyncsendV2Request.Msg(); + msgObj.setMsgtype("text"); + OapiMessageCorpconversationAsyncsendV2Request.Text textObj = new OapiMessageCorpconversationAsyncsendV2Request.Text(); + textObj.setContent(msgContent); + msgObj.setText(textObj); + OapiMessageCorpconversationAsyncsendV2Request.OA obj3 = new OapiMessageCorpconversationAsyncsendV2Request.OA(); + OapiMessageCorpconversationAsyncsendV2Request.Body obj4 = new OapiMessageCorpconversationAsyncsendV2Request.Body(); + obj4.setContent(msgContent); + obj3.setBody(obj4); + msgObj.setOa(obj3); + req.setMsg(msgObj); + String token = getAccessToken(); + log.info("token="+token); + OapiMessageCorpconversationAsyncsendV2Response rsp = client.execute(req,token ); + log.info("rsp.getMessage()="+rsp.getMessage()); + log.info("rsp.getBody()="+rsp.getBody()); + return rsp.getBody(); + } catch (Exception e) { + e.printStackTrace(); + } + return "发送消息失败"; + } + + /** + * 获取钉钉端的token + * @return + * @throws Exception + */ + private String getAccessToken() throws Exception { + DefaultDingTalkClient client = + new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken"); + OapiGettokenRequest request = new OapiGettokenRequest(); + CsProperties properties = c8NodeService.getProperties(); + //用于发送消息的应用的appid,secret + String appid = properties.getPlm().get("cs.dingtalk.appkey");//"dinghrfbr11eavtoufli"; + String secret = properties.getPlm().get("cs.dingtalk.secret");//"WFCkJDizmEoD0BpXQ3QKVjkL4vkszEyhldLOksKG2jmrDoO6XmWsBttl3PPrPnkA" + log.debug("appid="+appid); + log.debug("secret="+secret); + //Appkey + request.setAppkey(appid); + //Appsecret + request.setAppsecret(secret); + /*请求方式*/ + request.setHttpMethod("GET"); + OapiGettokenResponse response = client.execute(request); + return response.getAccessToken(); + } + + /** + * 通过消息模板发送消息,目前只支持第三方企业应用,不支持企业内部应用,该方法暂不使用 + * @param msg + * @param userList + * @return + * @throws Exception + */ + private String sendWorkNofificationByTemplate(String msg, String userList) throws Exception{ + DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/sendbytemplate"); + OapiMessageCorpconversationSendbytemplateRequest req = new OapiMessageCorpconversationSendbytemplateRequest(); + req.setAgentId(1480464891L); + req.setUseridList(userList); + req.setTemplateId("e27a9eed42b34a14a2xxxx"); + req.setData("{\"name\":\"淘宝6\",\"name2\":\"http://www.taobao.com\"}"); + OapiMessageCorpconversationSendbytemplateResponse rsp = client.execute(req, getAccessToken()); + return rsp.getBody(); + } + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookService.java b/integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookService.java new file mode 100644 index 0000000..d97de3c --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookService.java @@ -0,0 +1,163 @@ +package com.centricsoftware.integration.service.flybook; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.centricsoftware.integration.feign.flybook.FlyBookMessageFeign; +import com.centricsoftware.integration.flybook.FlyBookBatchMessage; +import com.centricsoftware.integration.flybook.FlyBookMessage; +import com.centricsoftware.integration.flybook.FlyBookResultData; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +@Slf4j +@Service +public class FlyBookService { + @Autowired + FlyBookMessageFeign flyBookMessageFeign; + + private String location = "/WebAccess/home.html#URL={}"; + + + /** + * 发送飞书消息到个人, By用户的邮箱地址 + * @param server 飞书消息中的地址的server地址 + * @param url 飞书消息中的地址的对象URL + * @param name 暂时定义为:header的名字 + * @param status + * @param emails 邮件地址,逗号分隔 + * @param msgType 预留,根据不同的消息类型发送不同的消息模板 + * @param msgContent 消息内容 + */ + public void senMsgSingleByEmail(String server,String url,String name,String status,String emails, String msgType,String msgContent){ + String[] emailsList = emails.split(","); + for (String email:emailsList) { + if(!StrUtil.isBlank(email)){ + String format1 = StrFormatter.format(server+location, url); + FlyBookMessage msg = new FlyBookMessage(); + //msg.singleReceive(); + msg.setReceive_id(email); + String template = msg.getTemplate(); + String format = StrFormatter.format(template, msgContent,format1, DateUtil.now(),"green",name); + msg.setContent(format); + log.info("msgContent="+msg.getContent()); + try{ + FlyBookResultData result = flyBookMessageFeign.sendMsgSingleByEmail(msg); + log.info("发送飞书消息结束="+result); + log.info("发送飞书消息成功 email="+email); + }catch (Exception e){ + log.error("发送飞书消息失败:email="+email); + e.printStackTrace(); + } + + } + } + } + + /** + * 发送飞书消息到个人, By用户的邮箱地址 + * @param server 飞书消息中的地址的server地址 + * @param url 飞书消息中的地址的对象URL + * @param name 暂时定义为:header的名字 + * @param status + * @param emails 邮件地址,逗号分隔 + * @param msgType 预留,根据不同的消息类型发送不同的消息模板 + * @param msgContent 消息内容 + */ + public void senMsgSingleByEmailAndFormat(String server,String url,String name,String status,String emails, String msgType,String msgContent){ + String[] emailsList = emails.split(","); + for (String email:emailsList) { + if(!StrUtil.isBlank(email)){ + String format1 = StrFormatter.format(server+location, url); + FlyBookMessage msg = new FlyBookMessage(); + //msg.singleReceive(); + msg.setReceive_id(email); + String template = msg.getTemplate(); + String format = StrFormatter.format(template, msgContent,format1, DateUtil.now(),status,name); + msg.setContent(format); + log.info("msgContent="+msg.getContent()); + try{ + FlyBookResultData result = flyBookMessageFeign.sendMsgSingleByEmail(msg); + log.info("发送飞书消息结束="+result); + log.info("发送飞书消息成功 email="+email); + }catch (Exception e){ + log.error("发送飞书消息失败:email="+email); + e.printStackTrace(); + } + + } + } + } + + /** + * 发送飞书消息到个人 + * @param server 飞书消息中的地址的server地址 + * @param url 飞书消息中的地址的对象URL + * @param name 暂时定义为:header的名字 + * @param status + * @param usersId 用户的飞书Id号,逗号分隔 + * @param msgType 预留,根据不同的消息类型发送不同的消息模板 + * @param msgContent 消息内容 + */ +// public void senMsgSingleByUserId(String server,String url,String name,String status,String usersId, String msgType,String msgContent){ +// //Link 模板 +// String[] userList = usersId.split(","); +// for (String userFSId:userList) { +// if(!StrUtil.isBlank(userFSId)){ +// String format1 = StrFormatter.format(server+location, url); +// FBMessage msg = new FBMessage(); +// //msg.singleReceive(); +// msg.setReceive_id(userFSId); +// String template = msg.getTemplate(); +// String format = StrFormatter.format(template, msgContent,format1, DateUtil.now(),name); +// msg.setContent(format); +// log.info("msgContent="+msg.getContent()); +// ResultData result = otherFeign.sendMsgByUserId(msg); +// log.info("result="+result); +// } +// } +// } + + public void sendBatchMsgByUserId(String server,String url,String name,String status,String usersId, String msgType,String msgContent){ + //Link 模板 + if(usersId!=null) { + String format1 = StrFormatter.format(server + location, url); + FlyBookBatchMessage msg = new FlyBookBatchMessage(); + //msg.singleReceive(); + msg.setUser_ids(usersId.split(",")); + String template = msg.getTemplate(); + String format = StrFormatter.format(template, msgContent, format1, DateUtil.now(), name, "", ""); + msg.setContent(format); + log.info("msgContent="+msg.getContent()); + FlyBookResultData rd = flyBookMessageFeign.sendBatchMsg(msg); + log.info("result="+rd ); + } + } + + /** + * 给某个部门发送消息 + * @param server + * @param url + * @param name + * @param status + * @param departmentId + * @param msgType + * @param msgContent + */ + public void sendBatchMsgByDepatmentId(String server,String url,String name,String status,String departmentId, String msgType,String msgContent){ + //Link 模板 + if(departmentId!=null) { + String format1 = StrFormatter.format(server + location, url); + FlyBookBatchMessage msg = new FlyBookBatchMessage(); + //msg.singleReceive(); + msg.setDepartment_ids(departmentId.split(",")); + String template = msg.getTemplate(); + String format = StrFormatter.format(template, msgContent, format1, DateUtil.now(), name, "", ""); + msg.setContent(format); + log.info("msgContent="+msg.getContent()); + flyBookMessageFeign.sendBatchMsg(msg); + } + } +} diff --git a/integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookTokenService.java b/integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookTokenService.java new file mode 100644 index 0000000..459a224 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/service/flybook/FlyBookTokenService.java @@ -0,0 +1,28 @@ +package com.centricsoftware.integration.service.flybook; + +import com.centricsoftware.config.entity.CsProperties; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.integration.flybook.FlyBookAppAccessToken; +import com.centricsoftware.integration.flybook.FlyBookAppData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.centricsoftware.integration.feign.flybook.FlyBookTokenFeign; + +@Service +public class FlyBookTokenService { + @Autowired + FlyBookTokenFeign flyBookTokenFeign; + @Autowired + C8NodeService c8NodeService; + + public FlyBookAppAccessToken getToken(){ + CsProperties properties = c8NodeService.getProperties(); + String appid = properties.getPlm().get("cs.flybook.appid"); + String secret = properties.getPlm().get("cs.flybook.secret"); + FlyBookAppData flyBookAppData = new FlyBookAppData(); + flyBookAppData.setApp_id(appid); + flyBookAppData.setApp_secret(secret); + return flyBookTokenFeign.getAppAccessToken(flyBookAppData); + } + +} diff --git a/integration/src/main/java/com/centricsoftware/integration/service/inter/Initializable.java b/integration/src/main/java/com/centricsoftware/integration/service/inter/Initializable.java new file mode 100644 index 0000000..fb21af4 --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/service/inter/Initializable.java @@ -0,0 +1,8 @@ +package com.centricsoftware.integration.service.inter; + +/** + * 初始化接口 + */ +public interface Initializable { + public String getToken(); +} diff --git a/integration/src/main/java/com/centricsoftware/integration/service/inter/MsgAble.java b/integration/src/main/java/com/centricsoftware/integration/service/inter/MsgAble.java new file mode 100644 index 0000000..d40fd9f --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/service/inter/MsgAble.java @@ -0,0 +1,13 @@ +package com.centricsoftware.integration.service.inter; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.integration.dto.param.BaseParamDto; + +/** + * 赋予可发送消息的能力 + * + * @author jerry + */ +public interface MsgAble { + public ResEntity sendMsg(BaseParamDto baseDto); +} diff --git a/integration/src/main/java/com/centricsoftware/integration/service/wechat/WeChatService.java b/integration/src/main/java/com/centricsoftware/integration/service/wechat/WeChatService.java new file mode 100644 index 0000000..0198f0a --- /dev/null +++ b/integration/src/main/java/com/centricsoftware/integration/service/wechat/WeChatService.java @@ -0,0 +1,62 @@ +package com.centricsoftware.integration.service.wechat; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.enhancement.modules.c8.service.C8NodeService; +import com.centricsoftware.integration.dto.param.BaseParamDto; +import com.centricsoftware.integration.dto.wechat.WeChatDataDto; +import com.centricsoftware.integration.feign.wechat.WeChatFeign; +import com.centricsoftware.integration.service.inter.Initializable; +import com.centricsoftware.integration.service.inter.MsgAble; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Slf4j +@Service +public class WeChatService implements Initializable, MsgAble { + + @Resource + C8NodeService c8NodeService; + + @Resource + WeChatFeign weChatFeign; + + @Value("${cs.wechat.agentid}") + private String agentId; + + @Value("${cs.wechat.appkey}") + private String appid; + + @Value("${cs.wechat.secret}") + private String secret; + + + /** + * 获取Token 测试用,后续不开放此接口对外 + * 注意:企业微信的应用下面,需要对调用方的外网IP添加白名单,否则获取不到token + * + * @return 返回Token + */ + @Override + public String getToken() { + return weChatFeign.getToken(appid, secret); + } + + + @Override + public ResEntity sendMsg(BaseParamDto baseDto) { + try { + String token = getToken(); // TODO 每次发送都会请求TOKEN, 待优化 + JSONObject obj = JSONUtil.parseObj(token); + String realToken = obj.getStr("access_token"); + return WebResponse.success(weChatFeign.sendMessage(realToken, baseDto.getParam())); + } catch (Exception e) { + return WebResponse.failure(e.toString()); + } + } +} diff --git a/log.sql b/log.sql new file mode 100644 index 0000000..5efa262 --- /dev/null +++ b/log.sql @@ -0,0 +1,40 @@ + + +/****** + sqlserver Object: Table [dbo].[C8_PS_Operation_Log] + 操作日志+接口日志 +******/ +USE [C8] +GO +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +drop table [dbo].[C8_PS_Operation_Log] + +CREATE TABLE [dbo].[C8_PS_Operation_Log]( + [id] [int] IDENTITY(1,1) NOT NULL, + [refid] [nvarchar](50) NULL, + [name] [nvarchar](50) NULL, + [url] [nvarchar](150) NULL, + [data_key] [nvarchar](50) NULL, + [host] [nvarchar](50) NULL, + [path] [nvarchar](100) NULL, + [brand] [nvarchar](50) NULL, + [send_date] [datetime] NULL, + [end_date] [datetime] NULL, + [cost_ms] [int] NULL, + [return_code] [nvarchar](5) NULL, + [response] [ntext] NULL, + [debug] [ntext] NULL, + [method] [nvarchar](10) NULL, + [header] [nvarchar](1000) NULL, + [param] [nvarchar](1000) NULL, + [log_type] [nvarchar](20) NULL, + [success] [bit] NULL, +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO + + diff --git a/maven-setting.xml b/maven-setting.xml new file mode 100644 index 0000000..a1b0e27 --- /dev/null +++ b/maven-setting.xml @@ -0,0 +1,42 @@ + + + + D:\mvn_repos + + + + + alimaven + central + aliyun maven + http://maven.aliyun.com/nexus/content/repositories/central/ + + + + nexus-aliyun + * + Nexus aliyun + http://maven.aliyun.com/nexus/content/groups/public + + + + repo1 + central + Human Readable Name for this Mirror. + http://repo1.maven.org/maven2/ + + + + + repo2 + central + Human Readable Name for this Mirror. + http://repo2.maven.org/maven2/ + + + + + + diff --git a/mybatis/.gitignore b/mybatis/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/mybatis/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/mybatis/.mvn/wrapper/MavenWrapperDownloader.java b/mybatis/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/mybatis/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/mybatis/.mvn/wrapper/maven-wrapper.jar b/mybatis/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/mybatis/.mvn/wrapper/maven-wrapper.properties b/mybatis/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/mybatis/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/mybatis/pom.xml b/mybatis/pom.xml new file mode 100644 index 0000000..75429e6 --- /dev/null +++ b/mybatis/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + mybatis + 2.0 + + mybatis + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + 3.5.7 + + + + + org.springframework.boot + spring-boot-starter + true + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis.plus.version} + + + + + com.microsoft.sqlserver + mssql-jdbc + ${microsoft.sqlserver.version} + + + + com.oracle + ojdbc6 + + + + + + + + + + + + + org.projectlombok + lombok + true + + + com.centricsoftware + config + + + com.fasterxml.jackson.core + jackson-annotations + + + org.postgresql + postgresql + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.5.0 + + + + + + + + + + + + + + + + diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/config/MybatisPlusConfig.java b/mybatis/src/main/java/com/centricsoftware/mybatis/config/MybatisPlusConfig.java new file mode 100644 index 0000000..95ba1d9 --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/config/MybatisPlusConfig.java @@ -0,0 +1,77 @@ +package com.centricsoftware.mybatis.config; + + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * mybatis-plus 配置 + * + * @author zheng.gong + * @date 2020/4/20 + */ +@Slf4j +@Configuration +@MapperScan({"com.centricsoftware.core.mapper", "com.centricsoftware.mybatis.mapper", "com.centricsoftware.commons.mapper", "com.centricsoftware.enhancement.mapper","com.centricsoftware.enhancement.modules.demo.mapper"}) +@EnableTransactionManagement +public class MybatisPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置最大分页数 + paginationInnerInterceptor.setMaxLimit(10000L); + // 是否对超过最大分页时做溢出处理 + paginationInnerInterceptor.setOverflow(true); + // 添加分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor); + return interceptor; + } + + /** + * 全局配置 + * + * @return GlobalConfig + */ + @Bean + public GlobalConfig globalConfiguration() { + + GlobalConfig.DbConfig conf = new GlobalConfig.DbConfig(); + + /* + AUTO|0:"数据库ID自增", + NONE|1:"无", + INPUT|2:"用户输入ID", + ID_WORKER|3:"TwitterSnowflake方案(数字)", + UUID|4:"全局唯一ID UUID"; + ID_WORKER_STR|5:"TwitterSnowflake方案(字符串)"; + */ + //#主键类型 + conf.setIdType(IdType.AUTO); + conf.setLogicDeleteValue("1"); + conf.setLogicNotDeleteValue("0"); + +// conf.setKeyGenerator(oracleKeyGenerator()); + GlobalConfig globalConfig = new GlobalConfig(); +// globalConfig.setMetaObjectHandler(new MyMetaObjectHandler()); + + /* globalConfig.setDatacenterId(sysProperties.getDataCenterId()); + globalConfig.setWorkerId(sysProperties.getWorkerId());*/ + globalConfig.setDbConfig(conf); + globalConfig.setBanner(false); + return globalConfig; + } + + + +} + diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/entity/PlmStyleEntity.java b/mybatis/src/main/java/com/centricsoftware/mybatis/entity/PlmStyleEntity.java new file mode 100644 index 0000000..18a7f9f --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/entity/PlmStyleEntity.java @@ -0,0 +1,156 @@ +package com.centricsoftware.mybatis.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +/** + * 款式实体demo + * @author zheng.gong + * @date 2020/4/20 + */ +@TableName("plm_style") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class PlmStyleEntity { + /** + * id + */ + @JsonProperty("id") + private Integer id; + + /** + * 图片 + */ + @JsonProperty("image_url") + private String imageUrl; + + /** + * 款号 + */ + @JsonProperty("style_no") + private String styleNo; + + /** + * 款式 + */ + @JsonProperty("style_name") + private String styleName; + + /** + * 品牌季节 + */ + @JsonProperty("season") + private String season; + + /** + * 上市波段 + */ + @JsonProperty("category1") + private String category1; + + /** + * 大类 + */ + @JsonProperty("large_category") + private String largeCategory; + + /** + * 小类 + */ + @JsonProperty("small_category") + private String smallCategory; + + /** + * 系列 + */ + @JsonProperty("proseries") + private String proseries; + + /** + * 主题类型 + */ + @JsonProperty("theme_type") + private String themeType; + + /** + * 系列主题 + */ + @JsonProperty("proseries_theme") + private String proseriesTheme; + + /** + * 特殊工艺 + */ + @JsonProperty("special_tec") + private String specialTec; + + /** + * 设计组 + */ + @JsonProperty("category2") + private String category2; + + /** + * 设计师 + */ + @JsonProperty("designe") + private String designe; + + /** + * 设计要求 + */ + @JsonProperty("design_req") + private String designReq; + + /** + * 吊牌价 + */ + @JsonProperty("price") + private String price; + + /** + * 销售量 + */ + @JsonProperty("total") + private String total; + + /** + * 销售额 + */ + @JsonProperty("accounts") + private String accounts; + + /** + * 创建 + */ + @JsonProperty("create_time") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private String createTime; + + /** + * 配色 + */ + @JsonProperty("color_image_url") + private String colorImageUrl; + + /** + * 状态 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonProperty("status") + private String status; + + /** + * 销售排名图片url + */ + @JsonProperty("rank_image_url") + private String rankImageUrl; + +} diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/handler/MyMetaObjectHandler.java b/mybatis/src/main/java/com/centricsoftware/mybatis/handler/MyMetaObjectHandler.java new file mode 100644 index 0000000..d77ace4 --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/handler/MyMetaObjectHandler.java @@ -0,0 +1,36 @@ +package com.centricsoftware.mybatis.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * 自动属性替换 + * @author ZhengGong + * @date 2019/9/16 + */ +@Component +@Slf4j +public class MyMetaObjectHandler implements MetaObjectHandler { + + + @Override + public void insertFill(MetaObject metaObject) { + log.info("start insert fill ...."); + // 起始版本 3.3.0(推荐使用) +// this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); + } + + @Override + public void updateFill(MetaObject metaObject) { + log.info("start update fill ...."); + // 起始版本 3.3.0(推荐使用) +// this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + + } +} \ No newline at end of file diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/mapper/PlmStyleMapper.java b/mybatis/src/main/java/com/centricsoftware/mybatis/mapper/PlmStyleMapper.java new file mode 100644 index 0000000..4f535de --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/mapper/PlmStyleMapper.java @@ -0,0 +1,40 @@ +package com.centricsoftware.mybatis.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.centricsoftware.mybatis.entity.PlmStyleEntity; +import com.centricsoftware.mybatis.provider.PlmStyleSqlProvider; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.Update; +import org.springframework.stereotype.Component; + +import java.util.List; + + +/** + * 测试款式mapper` + * @author ChangJiang + * @date 2020/5/6 + */ +@Component +public interface PlmStyleMapper extends BaseMapper { + /** + * 将删除的数据还原,用于开发测试 + */ + @Update("update plm_style set status=0") + void restore(); + + /** + * 动态查询 + * @param wrapper 动态查询条件 + * @return List + */ + @Select("select * from plm_style ${ew.customSqlSegment}") + List queryByDynamicCondition(@Param("ew") QueryWrapper wrapper); + + @SelectProvider(type= PlmStyleSqlProvider.class,method = "queryStyleById") + PlmStyleEntity queryByProvider(String styleName,String tableName); +} diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/provider/PlmStyleSqlProvider.java b/mybatis/src/main/java/com/centricsoftware/mybatis/provider/PlmStyleSqlProvider.java new file mode 100644 index 0000000..2e709f6 --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/provider/PlmStyleSqlProvider.java @@ -0,0 +1,41 @@ +package com.centricsoftware.mybatis.provider; + +import org.apache.ibatis.jdbc.SQL; + +/** + * 自定义sql生成器 + * @author zheng.gong + * @date 2020/5/6 + */ +public class PlmStyleSqlProvider { + + + public String queryStyleById(){ + SQL sql = new SQL(); + sql.SELECT("id,image_url,style_no,style_name") + .FROM("plm_style a") + .LEFT_OUTER_JOIN("#{styleName} b") + .WHERE("style_no='X1940002'") + .WHERE("style_name=#{styleName}"); + return sql.toString(); + } + + public String queryStyleById1(){ + SQL sql = new SQL(); + sql.SELECT("id,image_url,style_no,style_name") + .FROM("plm_style a") + .LEFT_OUTER_JOIN("#{styleName} b") + .WHERE("style_no='X1940002'") + .WHERE("style_name=#{styleName}"); + return sql.toString(); + } + public String queryStyleById2(){ + SQL sql = new SQL(); + sql.SELECT("id,image_url,style_no,style_name") + .FROM("plm_style a") + .LEFT_OUTER_JOIN("#{styleName} b") + .WHERE("style_no='X1940002'") + .WHERE("style_name=#{styleName}"); + return sql.toString(); + } +} diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/service/StyleService.java b/mybatis/src/main/java/com/centricsoftware/mybatis/service/StyleService.java new file mode 100644 index 0000000..5ebb6e8 --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/service/StyleService.java @@ -0,0 +1,7 @@ +package com.centricsoftware.mybatis.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.centricsoftware.mybatis.entity.PlmStyleEntity; + +public interface StyleService extends IService { +} diff --git a/mybatis/src/main/java/com/centricsoftware/mybatis/service/impl/StyleServiceImpl.java b/mybatis/src/main/java/com/centricsoftware/mybatis/service/impl/StyleServiceImpl.java new file mode 100644 index 0000000..b40406e --- /dev/null +++ b/mybatis/src/main/java/com/centricsoftware/mybatis/service/impl/StyleServiceImpl.java @@ -0,0 +1,11 @@ +package com.centricsoftware.mybatis.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.centricsoftware.mybatis.entity.PlmStyleEntity; +import com.centricsoftware.mybatis.mapper.PlmStyleMapper; +import com.centricsoftware.mybatis.service.StyleService; +import org.springframework.stereotype.Service; + +@Service +public class StyleServiceImpl extends ServiceImpl implements StyleService { +} diff --git a/mybatis/src/main/resources/mapper/PlmStyleEntityMapper.xml b/mybatis/src/main/resources/mapper/PlmStyleEntityMapper.xml new file mode 100644 index 0000000..81a68fb --- /dev/null +++ b/mybatis/src/main/resources/mapper/PlmStyleEntityMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..13f5a0d --- /dev/null +++ b/pom.xml @@ -0,0 +1,199 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + com.centricsoftware + plmservice + 2.0 + plm-service-http + Demo project for Spring Boot depend on http + + pom + + + core + config + commons + enhancement + redis + mybatis + sso + + + + + + UTF-8 + UTF-8 + 1.8 + 2.2.6.RELEASE + 5.4.1 + 28.1-jre + 2.0 + 2.1 + 2.1 + 0.11 + 0.10 + 2.0 + 2.0 + 2.0 + 2.0 + 3.8.1 + 11.2.0.3 + + 8.2.2.jre8 + 2.0.1 + 7.16.2 + 7.16.2 + 2.2.4 + 2.2.5.RELEASE + + + + + + + + + + + + + + + + com.microsoft.sqlserver + mssql-jdbc + ${microsoft.sqlserver.version} + + + com.oracle + ojdbc6 + ${ojdbc6.version} + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + cn.hutool + hutool-all + ${hutool.version} + + + + com.google.guava + guava + ${guava.version} + + + com.centricsoftware + config + ${config.version} + + + com.centricsoftware + commons + ${commons.version} + + + com.centricsoftware + redis + ${redis.version} + + + com.centricsoftware + enhancement + ${enhancement.version} + + + com.centricsoftware + rabbitmq + ${rabbitmq.version} + + + com.centricsoftware + mybatis + ${mybatis.version} + + + com.centricsoftware + task + ${task.version} + + + com.centricsoftware + sso + ${sso.version} + + + com.aliyun.oss + aliyun-sdk-oss + 3.13.0 + + + + + + + + + + + + dev + + dev + + + true + + + + test + + test + + + false + + + + prod + + prod + + + false + + + + + + + central + https://maven.aliyun.com/repository/public + + + + + central + https://maven.aliyun.com/repository/public + + + + diff --git a/rabbitmq/.gitignore b/rabbitmq/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/rabbitmq/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/rabbitmq/.mvn/wrapper/MavenWrapperDownloader.java b/rabbitmq/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/rabbitmq/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/rabbitmq/.mvn/wrapper/maven-wrapper.jar b/rabbitmq/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/rabbitmq/.mvn/wrapper/maven-wrapper.properties b/rabbitmq/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/rabbitmq/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/rabbitmq/plugins/rabbitmq_delayed_message_exchange-20171215-3.6.x.ez b/rabbitmq/plugins/rabbitmq_delayed_message_exchange-20171215-3.6.x.ez new file mode 100644 index 0000000000000000000000000000000000000000..84e6e93f7eb777947f0e44afa653969f94791e15 GIT binary patch literal 31352 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 Mf9U-l=f8dZ50?th+yDRo literal 0 HcmV?d00001 diff --git a/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.8.0.ez b/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.8.0.ez new file mode 100644 index 0000000000000000000000000000000000000000..6332f5ff21621eac17b7095d50571334ab9e3d3a GIT binary patch literal 43377 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

?uBO{-t|xlm~pjHB4Q?aD$*U+ymt! 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 literal 0 HcmV?d00001 diff --git a/rabbitmq/pom.xml b/rabbitmq/pom.xml new file mode 100644 index 0000000..dd13b5e --- /dev/null +++ b/rabbitmq/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + rabbitmq + 2.0 + + rabbitmq + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.core + jackson-databind + + + + + + + + + + + + com.centricsoftware + config + + + org.elasticsearch + elasticsearch + + + + + + + diff --git a/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/config/RabbitMqConfig.java b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/config/RabbitMqConfig.java new file mode 100644 index 0000000..0833326 --- /dev/null +++ b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/config/RabbitMqConfig.java @@ -0,0 +1,95 @@ +package com.centricsoftware.rabbitmq.config; + +import com.centricsoftware.config.cons.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.*; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +/** + * RabbitMQ配置,主要是配置队列,如果提前存在该队列,可以省略本配置类 + * @author zheng.gong + * @date 2020/4/17 + */ +@Slf4j +@Configuration +public class RabbitMqConfig { + + + @Bean + public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) { + connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE); + connectionFactory.setPublisherReturns(true); + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause)); + return rabbitTemplate; + } +// +// @Bean +// public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){ +// SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); +// factory.setConnectionFactory(connectionFactory); +//// factory.setMessageConverter(new Jackson2JsonMessageConverter()); +// +//// factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); +// return factory; +// } + + + + /** + * 直接模式队列1 + */ + @Bean + public Queue directOneQueue() { + return new Queue(Constants.RabbitConsts.DIRECT_MODE_QUEUE_ONE); + } +// +// /** +// * 队列2 +// */ +// @Bean +// public Queue queueTwo() { +// return new Queue(Constants.RabbitConsts.QUEUE_TWO); +// } +// +// /** +// * 队列3 +// */ +// @Bean +// public Queue queueThree() { +// return new Queue(Constants.RabbitConsts.QUEUE_THREE); +// } +// /** +// * 延迟队列 +// */ +// @Bean +// public Queue delayQueue() { +// return new Queue(Constants.RabbitConsts.DELAY_QUEUE, true); +// } +// +// /** +// * 延迟队列交换器, x-delayed-type 和 x-delayed-message 固定 +// */ +// @Bean +// public CustomExchange delayExchange() { +// Map args = Maps.newHashMap(); +// args.put("x-delayed-type", "direct"); +// return new CustomExchange(Constants.RabbitConsts.DELAY_MODE_QUEUE, "x-delayed-message", true, false, args); +// } + +// /** +// * 延迟队列绑定自定义交换器 +// * +// * @param delayQueue 队列 +// * @param delayExchange 延迟交换器 +// */ +// @Bean +// public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) { +// return BindingBuilder.bind(delayQueue).to(delayExchange).with(Constants.RabbitConsts.DELAY_QUEUE).noargs(); +// } + +} diff --git a/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DelayQueueHandler.java b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DelayQueueHandler.java new file mode 100644 index 0000000..02268c2 --- /dev/null +++ b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DelayQueueHandler.java @@ -0,0 +1,44 @@ +package com.centricsoftware.rabbitmq.handler; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.json.JSONUtil; +import com.centricsoftware.config.cons.Constants; +import com.centricsoftware.rabbitmq.message.MessageStruct; +import com.rabbitmq.client.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * 延迟队列处理 + * @author zheng.gong + * @date 2020/4/21 + */ +@Slf4j +@Component +@RabbitListener(queues = Constants.RabbitConsts.DELAY_QUEUE) +public class DelayQueueHandler { + + @RabbitHandler + public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) { + // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 + final long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + log.info("延迟队列,手动ACK,接收消息:{},时间:{}", JSONUtil.toJsonStr(messageStruct), DateUtil.now()); + // 通知 MQ 消息已被成功消费,可以ACK了 + channel.basicAck(deliveryTag, false); + //do something + } catch (IOException e) { + try { + // 处理失败,重新压入MQ + channel.basicRecover(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } +} diff --git a/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DirectQueueOneHandler.java b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DirectQueueOneHandler.java new file mode 100644 index 0000000..cc7844b --- /dev/null +++ b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/handler/DirectQueueOneHandler.java @@ -0,0 +1,52 @@ +package com.centricsoftware.rabbitmq.handler; + +import com.centricsoftware.config.cons.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +/** + * 接队列1 处理器 + * @author zheng.gong + * @date 2020/4/17 + */ +@Slf4j +@Component +@RabbitListener(queues = Constants.RabbitConsts.DIRECT_MODE_QUEUE_ONE) +public class DirectQueueOneHandler { + + + + /** + * 如果 spring.rabbitmq.listener.direct.acknowledge-mode: auto,则可以用这个方式,会自动ack + */ + @RabbitHandler + public void directHandlerAutoAck(String messageStruct) { + log.info("直接队列处理器,接收消息:{}", messageStruct); + } + + /** + * 如果acknowledge-mode: manual ,则需要手动ack + * @param messageStruct 测试消息体 + * @param message 消息Object + * @param channel 信道 + */ +// @RabbitHandler +// public void directHandlerManualAck(String messageStruct, Message message, Channel channel) { +// // 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉 +// final long deliveryTag = message.getMessageProperties().getDeliveryTag(); +// try { +// log.info("直接队列1,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct)); +// // 通知 MQ 消息已被成功消费,可以ACK了 +// channel.basicAck(deliveryTag, false); +// } catch (Exception e) { +// try { +// // 处理失败,重新压入MQ +// channel.basicRecover(); +// } catch (IOException e1) { +// e1.printStackTrace(); +// } +// } +// } +} diff --git a/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/message/MessageStruct.java b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/message/MessageStruct.java new file mode 100644 index 0000000..3aaf311 --- /dev/null +++ b/rabbitmq/src/main/java/com/centricsoftware/rabbitmq/message/MessageStruct.java @@ -0,0 +1,23 @@ +package com.centricsoftware.rabbitmq.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 测试消息体 + * @author zheng.gong + * @date 2020/4/17 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MessageStruct implements Serializable { + private static final long serialVersionUID = 392365881428311040L; + + private String message; +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8ebd7d8 --- /dev/null +++ b/readme.md @@ -0,0 +1,29 @@ +# 帮助文档 + 启动成功访问以下链接: + http://localhost:8088/plmservice/test/hello +### 1、maven 配置 + 请自行配置好idea的Maven,配置文件可以参考maven-setting.xml + 如果maven镜像是https,可以禁用idea的https证书校验: + -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true + +### 2、JDK版本 + 工程值在JDK8中测试过,高版本或者open-jdk请自行测试 + +### 3、此工程和SpringbootDev1区别 +#### 3.1 C8交互 + 与C8的交互采用okhttp客户端,登录通过拦截器实现自动登录 +#### 3.2 模块版本-预发布 + 后续各个模块将启用版本。后续对各模块的修改,将发布个新版本。 +#### 3.3 新增enhancement模块 + 该模块为C8功能强化模块,封装了日志、导入、导出、C8交互 +#### 3.4 删除NodeUtil.java + 后续的访问,请引入C8NodeService +#### 3.5 其他 + 引入C8Change、C8Delete、C8Search用于构建operation、Search; +### 更新日志 + 已将以下分支合并入2.2-dev分支 + - 2.1-cv-search、 + - 2.1-fix-修复DepPath多路径取值失败问题 + - 2.1-add-multiple-datasource + - 2.1-dev-Excel标准化导出-Joseph + - 2.1-dev-日志模块实现-Joseph \ No newline at end of file diff --git a/redis/.gitignore b/redis/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/redis/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/redis/.mvn/wrapper/MavenWrapperDownloader.java b/redis/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/redis/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/redis/.mvn/wrapper/maven-wrapper.jar b/redis/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/redis/.mvn/wrapper/maven-wrapper.properties b/redis/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/redis/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/redis/pom.xml b/redis/pom.xml new file mode 100644 index 0000000..1dd9676 --- /dev/null +++ b/redis/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + redis + 2.0 + redis + Demo project for Spring Boot + + + + + org.springframework.boot + spring-boot-starter-web + true + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-starter-json + + + + org.projectlombok + lombok + true + + + + com.centricsoftware + config + + + + + diff --git a/redis/src/main/java/com/centricsoftware/redis/component/RedisExecutor.java b/redis/src/main/java/com/centricsoftware/redis/component/RedisExecutor.java new file mode 100644 index 0000000..4167ddd --- /dev/null +++ b/redis/src/main/java/com/centricsoftware/redis/component/RedisExecutor.java @@ -0,0 +1,197 @@ +package com.centricsoftware.redis.component; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +/** + * redis api封装 + * @author zheng.gong + * @date 2020/4/21 + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class RedisExecutor { + + private final StringRedisTemplate redisTemplate; + + private int validTime; + /** + * 查询key,支持模糊查询 + * + * @param key 传过来时key的前后端已经加入了*,或者根据具体处理 + * */ + public Set keys(String key){ + return redisTemplate.keys(key); + } + + /** + * 字符串获取值 + * @param key redis key + * */ + public Object get(String key){ + return redisTemplate.opsForValue().get(key); + } + + /** + * 字符串存入值 + * 默认过期时间为2小时 + * @param key redis key + * */ + public void set(String key, String value){ + redisTemplate.opsForValue().set(key,value, 7200,TimeUnit.SECONDS); + } + + /** + * 字符串存入值 + * @param expire 过期时间(毫秒计) + * @param key redis key + * */ + public void set(String key, String value,Integer expire){ + redisTemplate.opsForValue().set(key,value, expire,TimeUnit.SECONDS); + } + + /** + * 删出key + * 这里跟下边deleteKey()最底层实现都是一样的,应该可以通用 + * @param key redis key + * */ + public void delete(String key){ + redisTemplate.opsForValue().getOperations().delete(key); + } + + /** + * 添加单个 + * 默认过期时间为两小时 + * @param key key + * @param filed filed + * @param domain 对象 + */ + public void hset(String key,String filed,Object domain){ + redisTemplate.opsForHash().put(key, filed, domain); + } + + /** + * 添加单个 + * @param key key + * @param filed filed + * @param domain 对象 + * @param expire 过期时间(毫秒计) + */ + public void hset(String key,String filed,Object domain,Integer expire){ + redisTemplate.opsForHash().put(key, filed, domain); + redisTemplate.expire(key, expire,TimeUnit.SECONDS); + } + + /** + * 添加HashMap + * + * @param key key + * @param hm 要存入的hash表 + */ + public void hset(String key, HashMap hm){ + redisTemplate.opsForHash().putAll(key,hm); + } + + /** + * 如果key存在就不覆盖 + * 如果变量值存在,在变量中可以添加不存在的的键值对,如果变量不存在,则新增一个变量,同时将键值对添加到该变量 + * @param key redis key + * @param filed new key + * @param domain value + */ + public void hsetAbsent(String key,String filed,Object domain){ + redisTemplate.opsForHash().putIfAbsent(key, filed, domain); + } + + /** + * 查询key和field所确定的值 + * + * @param key 查询的key + * @param field 查询的field + * @return HV + */ + public Object hget(String key,String field) { + return redisTemplate.opsForHash().get(key, field); + } + + /** + * 查询该key下所有值 + * + * @param key 查询的key + * @return Map + */ + public Object hget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 删除key下所有值 + * + * @param key 查询的key + */ + public void deleteKey(String key) { + redisTemplate.opsForHash().getOperations().delete(key); + } + + /** + * 判断key和field下是否有值 + * + * @param key 判断的key + * @param field 判断的field + */ + public Boolean hasKey(String key,String field) { + return redisTemplate.opsForHash().hasKey(key,field); + } + + /** + * 判断key下是否有值 + * + * @param key 判断的key + */ + public Boolean hasKey(String key) { + return redisTemplate.opsForHash().getOperations().hasKey(key); + } + + /** + * String类型设值并设置过期 + * @param k 键 + * @param v 值 + * @param l 时间 + * @param t 类型 + */ + public void set(String k,String v,long l,TimeUnit t){ + redisTemplate.opsForValue().set(k,v,l,t); + } + + /** + * 获取键的过期时间 + * @param k 键 + * @param t 时间类型 + * @return Long + */ + public Long getExpire(String k,TimeUnit t){ + return redisTemplate.opsForValue().getOperations().getExpire(k,t); + } + + /** + * 设置k,v当且仅当k不存在 + * @param k key + * @param v value + * @return Boolean + */ + public Boolean setNx(String k,String v){ + return redisTemplate.execute((RedisCallback) redisConnection -> { + RedisSerializer serializer = redisTemplate.getStringSerializer(); + return redisConnection.setNX(Objects.requireNonNull(serializer.serialize(k)), Objects.requireNonNull(serializer.serialize(v))); + }); + } + +} diff --git a/redis/src/main/java/com/centricsoftware/redis/config/RedisConfig.java b/redis/src/main/java/com/centricsoftware/redis/config/RedisConfig.java new file mode 100644 index 0000000..50b5b75 --- /dev/null +++ b/redis/src/main/java/com/centricsoftware/redis/config/RedisConfig.java @@ -0,0 +1,70 @@ +package com.centricsoftware.redis.config; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.*; + +import java.io.Serializable; + +/** + * RedisConfig + * @author zheng.gong + * @date 2020/4/17 + */ +@Configuration +@AutoConfigureAfter(RedisAutoConfiguration.class) +@EnableCaching +public class RedisConfig { + + + /** + * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 + */ + @Bean + public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式 + */ + @Bean + public CacheManager cacheManager(RedisConnectionFactory factory) { + // 配置序列化 + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); + RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); + + return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build(); + } + + @Bean + @SuppressWarnings("unchecked") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + RedisSerializer fastJsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + redisTemplate.setValueSerializer(fastJsonRedisSerializer); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.afterPropertiesSet(); + // 开启事务 + redisTemplate.setEnableTransactionSupport(false); + return redisTemplate; + } + +} + + + diff --git a/sso/.gitignore b/sso/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/sso/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/sso/pom.xml b/sso/pom.xml new file mode 100644 index 0000000..af116bb --- /dev/null +++ b/sso/pom.xml @@ -0,0 +1,37 @@ + + + + plmservice + com.centricsoftware + 2.0 + + 4.0.0 + sso + 0.11 + + + + com.centricsoftware + enhancement + 2.1 + + + + org.opensaml + opensaml + 2.6.4 + + + + dom4j + dom4j + 1.6.1 + + + + + + + diff --git a/sso/src/main/java/com/centricsoftware/sso/controller/SamlSSOController.java b/sso/src/main/java/com/centricsoftware/sso/controller/SamlSSOController.java new file mode 100644 index 0000000..9ac4dd2 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/controller/SamlSSOController.java @@ -0,0 +1,64 @@ +package com.centricsoftware.sso.controller; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import com.centricsoftware.sso.service.saml2.common.SAMLService; +import com.centricsoftware.sso.service.saml2.xml.RequestSAMLService; +import com.centricsoftware.sso.service.security.AuthenticationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.codec.binary.Base64; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.annotation.Resource; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import java.net.URLEncoder; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/11/26 15:55 + */ +@Controller +@Slf4j +public class SamlSSOController { + + @Resource + RequestSAMLService requestSAMLService; + + @Resource + AuthenticationService authenticationService; + + @Resource + SPMetaDataDto spMetaDataDto; + + @Resource + SAMLService samlService; + + @RequestMapping("/sso/idp") + public ResEntity login(HttpServletRequest request, HttpServletResponse response) throws Exception { + //鉴权,可重写实现 + String userId = authenticationService.authentication(request); + String responseSAML = requestSAMLService.getResponseSAML(spMetaDataDto, userId); + response.reset(); + //String redirect = "http://47.107.226.252/WebAccess/home.html#URL=C59445&Tab=CompetitiveStyles&NR=1"; + String redirect = "http://47.107.226.252/WebAccess/home.html#URL%3DC59445%26Tab%3DCompetitiveStyles%26NR%3D1"; // =号及后面的内容需要转义 + log.info(URLEncoder.encode(redirect).toString()); + Cookie cookie = new Cookie(SPMetaDataDto.IDP_COOKIE_KEY, URLEncoder.encode(redirect).toString()); + cookie.setPath("/"); + response.addCookie(cookie); + PrintWriter printWriter = response.getWriter(); + //响应转化成string型 + String form = samlService.getResponseForm(spMetaDataDto.getSpAssertionConsumerURL(), redirect, new Base64().encodeAsString(responseSAML.getBytes("utf-8"))); + log.debug("跳转的报文为:"); + log.debug(form); + printWriter.write(form); + printWriter.flush(); + printWriter.close(); + return null; + } + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/controller/TestSAMLController.java b/sso/src/main/java/com/centricsoftware/sso/controller/TestSAMLController.java new file mode 100644 index 0000000..9457d3b --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/controller/TestSAMLController.java @@ -0,0 +1,33 @@ +package com.centricsoftware.sso.controller; + +import com.centricsoftware.commons.dto.ResEntity; +import com.centricsoftware.commons.dto.WebResponse; +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import com.centricsoftware.sso.service.saml2.xml.RequestSAMLService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.annotation.Resource; + +@Slf4j +@RequestMapping("/sso") +@Controller +public class TestSAMLController { + + + @Resource + RequestSAMLService requestSAMLService; + /** + * 获取请求报文 + */ + @RequestMapping("/getRequestSAML") + @ResponseBody + public ResEntity getRequestSAML() throws Exception{ + String requestSAML = requestSAMLService.getRequestSAML(new SPMetaDataDto()); + return WebResponse.success("requestSAML:"+requestSAML); + } + + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/AuthorizationRequestDto.java b/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/AuthorizationRequestDto.java new file mode 100644 index 0000000..96e4d3e --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/AuthorizationRequestDto.java @@ -0,0 +1,46 @@ +package com.centricsoftware.sso.dto.saml2.sp; + +import lombok.Data; + +/** + * description: 存储请求ID等信息 + * Author: Xulin.Xie + * Date: 2022/11/25 16:59 + */ +@Data +public class AuthorizationRequestDto { + + /** + * 请求ID + */ + private String requestId; + + + /** + * acs地址,即SAMLResponse返回的目标地址 + */ + private String assertionConsumerServiceUrl; + + /** + * 绑定方式 + */ + private String protocolBinding; + + /** + * sp entityId + */ + private String spIssuer; + + /** + * 未使用该参数 + */ + private String destination; + + /** + * 未使用该参数 + */ + private String version; + + + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/C8MetaDataDto.java b/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/C8MetaDataDto.java new file mode 100644 index 0000000..7649da3 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/C8MetaDataDto.java @@ -0,0 +1,94 @@ +package com.centricsoftware.sso.dto.saml2.sp; + +import com.centricsoftware.commons.utils.SpringContextHolder; +import com.centricsoftware.config.entity.CsProperties; +import lombok.Data; +import org.opensaml.xml.security.x509.BasicX509Credential; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; +import java.io.InputStream; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Collections; + +/** + * description:PLM MetaData 信息 + * Author: Xulin.Xie + * Date: 2022/11/25 13:10 + */ +@Data +@Configuration +public class C8MetaDataDto extends SPMetaDataDto{ + + + /** + * PLM IP + */ + @Value("${cs.plm.http}") + private String host; + + + + /** + * 需要初始化证书等相关信息 + */ + public C8MetaDataDto(){ + CsProperties csProperties = SpringContextHolder.getBean(CsProperties.class); + super.setKeyStorePath(csProperties.getValue("sso.keystore")); + super.setKeyStorePwd(csProperties.getValue("sso.keystore-pwd")); + super.setKeyStoreAlias(csProperties.getValue("sso.alias")); + try { + XMLSignatureFactory factory = XMLSignatureFactory.getInstance(); + KeyStore result = KeyStore.getInstance (KeyStore.getDefaultType ()); + setKeyStore(result); + InputStream keyStoreStream = C8MetaDataDto.class.getResourceAsStream(getKeyStorePath()); + result.load (keyStoreStream, getKeyStorePwd().toCharArray ()); + KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) getKeyStore().getEntry (getKeyStoreAlias(), new KeyStore.PasswordProtection(getKeyStorePwd().toCharArray ())); + setKeyPair(new KeyPair(entry.getCertificate ().getPublicKey (), entry.getPrivateKey ())); + KeyInfoFactory kFactory = factory.getKeyInfoFactory (); + setKeyInfo(kFactory.newKeyInfo(Collections.singletonList (kFactory.newX509Data(Collections.singletonList (entry.getCertificate ()))))); + X509Certificate certificate = (X509Certificate) entry.getCertificate(); + BasicX509Credential credential = new BasicX509Credential(); + credential.setEntityCertificate(certificate); + credential.setPrivateKey(entry.getPrivateKey()); + setCredential(credential); + } + catch (Exception ex) { + throw new RuntimeException("生成签名失败:",ex); + } + } + + /** + * C8配置的 SSOIdentityProviderServiceURL + */ + private String idpServiceURL = "http://localhost:8080/idp/sso"; + + /** + * C8配置的 SSOIdentityProviderEntityId + */ + private String idpEntityId = "http://localhost:8080/idp"; + + /** + * C8配置的 SSOServiceProviderAssertionConsumerURL + */ + private String spAssertionConsumerURL; + + /** + * C8配置的 SSOServiceProviderEntityId + */ + private String spEntityId; + + public String getSpAssertionConsumerURL(){ + return "http://"+host+"/csi-requesthandler/sso/sp"; + } + + public String getSpEntityId(){ + return "http://"+ host+"/SSO"; + } + + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/SPMetaDataDto.java b/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/SPMetaDataDto.java new file mode 100644 index 0000000..66b91b6 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/dto/saml2/sp/SPMetaDataDto.java @@ -0,0 +1,103 @@ +package com.centricsoftware.sso.dto.saml2.sp; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.opensaml.xml.security.x509.BasicX509Credential; + +import javax.xml.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.namespace.QName; +import java.security.KeyPair; +import java.security.KeyStore; + +/** + * description:SP MetaData 信息 + * Author: Xulin.Xie + * Date: 2022/11/25 13:07 + */ +@Data +@NoArgsConstructor +public class SPMetaDataDto{ + + + /** + * C8配置的 SSOIdentityProviderServiceURL + */ + private String idpServiceURL; + + /** + * C8配置的 SSOIdentityProviderEntityId + */ + private String idpEntityId; + + /** + * C8配置的 SSOServiceProviderAssertionConsumerURL + */ + private String spAssertionConsumerURL; + + /** + * C8配置的 SSOServiceProviderEntityId + */ + private String spEntityId; + + /** + * ====================证书相关======================= + */ + + /** + * 证书位置 + */ + private String keyStorePath; + + /** + * 证书密码 + */ + private String keyStorePwd; + + /** + * 证书别名 + */ + private String keyStoreAlias; + + private KeyStore keyStore; + + /** + * 密钥对 + */ + private KeyPair keyPair; + + private KeyInfo keyInfo; + /** + * 基本X 509凭证 + */ + private BasicX509Credential credential; + + + /** + * 初始化参数,正常情况下不需要修改这些默认参数;由于默认参数太多,并不是所有的都提取到实体类做成可配置项;如发现有需要修改的默认参数不能在这里配置的; + * 需要重写对应的接口实现类; + */ + + public static final QName DEFAULT_ELEMENT_NAME = new QName("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest", "saml2p"); + + public static final String SAML2_POST_BINDING_URI = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"; + + public static final QName DEFAULT_ELEMENT_NAME_Issuer = new QName("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "saml2"); + + public static final QName DEFAULT_ELEMENT_NAME_NameID = new QName("urn:oasis:names:tc:SAML:2.0:protocol", "NameIDPolicy", "saml2p"); + + public static final String UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + + public static final String STANDALONE = "standalone"; + + public static final String NAME_ID_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"; + + public static final String CONFIRMATION_METHOD = "bearer"; + + public static final String IDP_COOKIE_KEY = "redirect"; + /** + * idp 端 cookie的value + */ + public static final String IDP_COOKIE_VALUE = "idp_cookie_value111"; + + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/AssertionService.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/AssertionService.java new file mode 100644 index 0000000..eacc630 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/AssertionService.java @@ -0,0 +1,9 @@ +package com.centricsoftware.sso.service.saml2.common; + +import org.opensaml.saml2.core.Assertion; + +public interface AssertionService { + + public Assertion createStockAuthnAssertion (String idpEntityId, String assertionId, String spEntityId); + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLService.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLService.java new file mode 100644 index 0000000..d17c13b --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLService.java @@ -0,0 +1,44 @@ +package com.centricsoftware.sso.service.saml2.common; + +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.core.Subject; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.io.MarshallingException; +import org.w3c.dom.Document; + +import javax.xml.namespace.QName; + +/** + * description:Utility that uses OpenSAML to carry out common SAML tasks. + * Author: Xulin.Xie + * Date: 2022/11/25 13:46 + */ +public interface SAMLService { + + /** + * easier way to create objects using OpenSAML's builder system. + * cast to SAMLObjectBuilder is caller's choice + */ + @SuppressWarnings ("unchecked") + public T create (Class cls, QName qname); + + /** + Helper method to get an XMLObject as a DOM Document. + 获取XMLObject作为DOM文档的Helper方法。 + */ + public Document asDOMDocument (XMLObject object) throws MarshallingException; + + /** + * 创建Subject + * @param userId 登录名 + * @return + */ + public Subject createSubject(String userId, String format, String confirmationMethod, String recipient); + + Response createResponse (SPMetaDataDto mataData,Assertion assertion, String inResponseTo); + + + String getResponseForm(String idpServiceURL, String redirect, String response) throws Exception; +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLSignature.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLSignature.java new file mode 100644 index 0000000..3b40cc7 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/SAMLSignature.java @@ -0,0 +1,14 @@ +package com.centricsoftware.sso.service.saml2.common; + +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.dsig.XMLSignatureException; +import java.security.GeneralSecurityException; + +public interface SAMLSignature { + void signSAMLObject (SPMetaDataDto spMetaDataDto, Document doc, String referenceId, Node signNode) + throws GeneralSecurityException, XMLSignatureException, MarshalException; +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/AssertionServiceImpl.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/AssertionServiceImpl.java new file mode 100644 index 0000000..856a4a8 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/AssertionServiceImpl.java @@ -0,0 +1,63 @@ +package com.centricsoftware.sso.service.saml2.common.impl; + +import com.centricsoftware.sso.service.saml2.common.AssertionService; +import com.centricsoftware.sso.service.saml2.common.SAMLService; +import org.joda.time.DateTime; +import org.opensaml.saml2.core.*; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** +Simple examples of coding to the OpenSAML API. +Methods here can write and parse each of the three +main assertion types: authentication, authorization decision, and attributes. + 编码到OpenSAML API的简单示例。这里的方法可以编写和解析以下三种主要的断言类型: + 身份验证,授权决策和属性。 +*/ +@Service +public class AssertionServiceImpl implements AssertionService { + + @Resource + SAMLService samlService; + + /** + Creates a file whose contents are a SAML authentication assertion. + 创建一个内容为SAML身份验证断言的文件。 + */ + @Override + public Assertion createStockAuthnAssertion (String idpEntityId,String assertionId,String spEntityId) { + DateTime now = new DateTime (); + Issuer issuer = samlService.create (Issuer.class, Issuer.DEFAULT_ELEMENT_NAME); + issuer.setValue (idpEntityId); + issuer.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity"); + Conditions conditions = samlService.create + (Conditions.class, Conditions.DEFAULT_ELEMENT_NAME); + conditions.setNotBefore (now.minusSeconds (15)); + conditions.setNotOnOrAfter (now.plusSeconds (30)); + AudienceRestriction audienceRestriction = samlService.create(AudienceRestriction.class,AudienceRestriction.DEFAULT_ELEMENT_NAME); + Audience audience = samlService.create(Audience.class,Audience.DEFAULT_ELEMENT_NAME); + audience.setAudienceURI(spEntityId); + audienceRestriction.getAudiences().add(audience); + conditions.getAudienceRestrictions().add(audienceRestriction); + AuthnContextClassRef ref = samlService.create (AuthnContextClassRef.class, + AuthnContextClassRef.DEFAULT_ELEMENT_NAME); + ref.setAuthnContextClassRef (AuthnContext.PPT_AUTHN_CTX); + AuthnContext authnContext = samlService.create + (AuthnContext.class, AuthnContext.DEFAULT_ELEMENT_NAME); + authnContext.setAuthnContextClassRef (ref); + AuthnStatement authnStatement = samlService.create + (AuthnStatement.class, AuthnStatement.DEFAULT_ELEMENT_NAME); + authnStatement.setAuthnContext (authnContext); + authnStatement.setAuthnInstant(now); + Assertion assertion = + samlService.create (Assertion.class, Assertion.DEFAULT_ELEMENT_NAME); + assertion.setID (assertionId); + assertion.setIssueInstant (now); + assertion.setIssuer (issuer); + assertion.getStatements ().add (authnStatement); + assertion.setConditions(conditions); + return assertion; + } + +} \ No newline at end of file diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLServiceImpl.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLServiceImpl.java new file mode 100644 index 0000000..06ccf2c --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLServiceImpl.java @@ -0,0 +1,181 @@ +package com.centricsoftware.sso.service.saml2.common.impl; + +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import com.centricsoftware.sso.service.saml2.common.SAMLService; +import org.joda.time.DateTime; +import org.opensaml.DefaultBootstrap; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.saml2.core.*; +import org.opensaml.xml.Configuration; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.XMLObjectBuilder; +import org.opensaml.xml.io.Marshaller; +import org.opensaml.xml.io.MarshallingException; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.net.URLEncoder; + +/** + * description:Utility that uses OpenSAML to carry out common SAML tasks. + * Author: Xulin.Xie + * Date: 2022/11/25 13:46 + */ +@Service +public class SAMLServiceImpl implements SAMLService { + + private static final String CM_PREFIX = "urn:oasis:names:tc:SAML:2.0:cm:"; + + private DocumentBuilder documentBuilder; + + private SecureRandomIdentifierGenerator generator; + + public SAMLServiceImpl() throws Exception { + DefaultBootstrap.bootstrap (); + generator = new SecureRandomIdentifierGenerator(); + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance (); + factory.setNamespaceAware (true); + documentBuilder = factory.newDocumentBuilder (); + } + + /** + * easier way to create objects using OpenSAML's builder system. + * cast to SAMLObjectBuilder is caller's choice + */ + @SuppressWarnings ("unchecked") + @Override + public T create (Class cls, QName qname) + { + return (T) ((XMLObjectBuilder) + Configuration.getBuilderFactory ().getBuilder (qname)) + .buildObject (qname); + } + + @Override + public Document asDOMDocument(XMLObject object) throws MarshallingException { + Document document = documentBuilder.newDocument (); + Marshaller out = + Configuration.getMarshallerFactory ().getMarshaller (object); + out.marshall (object, document); + return document; + } + + /** + Helper method to get an XMLObject as a DOM Document. + 获取XMLObject作为DOM文档的Helper方法。 + */ + + + /** + Returns a SAML subject. + @param userId The subject name + @param format If non-null, we'll set as the subject name format + @param confirmationMethod If non-null, we'll create a SubjectConfirmation + element and use this as the Method attribute; must be "sender-vouches" + or "bearer", as HOK would require additional parameters and so is NYI + */ + @Override + public Subject createSubject(String userId, String format, String confirmationMethod,String recipient){ + NameID nameID = create (NameID.class, NameID.DEFAULT_ELEMENT_NAME); + nameID.setValue (userId); + if (format != null) nameID.setFormat (format); + Subject subject = create (Subject.class, Subject.DEFAULT_ELEMENT_NAME); + subject.setNameID (nameID); + if (confirmationMethod != null) { + SubjectConfirmation confirmation = create(SubjectConfirmation.class, SubjectConfirmation.DEFAULT_ELEMENT_NAME); + confirmation.setMethod (CM_PREFIX + confirmationMethod); + SubjectConfirmationData confirmationData = create(SubjectConfirmationData.class,SubjectConfirmationData.DEFAULT_ELEMENT_NAME); + confirmationData.setRecipient(recipient); + confirmation.setSubjectConfirmationData(confirmationData); + subject.getSubjectConfirmations ().add (confirmation); + } + return subject; + } + + /** + Helper method to generate a response, based on a pre-built assertion + and query ID. + 根据预先建立的断言和查询ID生成响应的Helper方法。 + */ + @Override + public Response createResponse(SPMetaDataDto mataData,Assertion assertion, String inResponseTo) { + Response response = createResponse (mataData,StatusCode.SUCCESS_URI, inResponseTo); + response.getAssertions ().add (assertion); + return response; + } + @Override + public String getResponseForm(String idpServiceURL, String redirect, String response) throws Exception{ + String encode = URLEncoder.encode(redirect); + encode = URLEncoder.encode(encode); + return "\n" + + "\n" + + "\n" + + "POST data\n" + + "\n" + + "\n" + + "

Note: Since your browser does not support JavaScript, you must press the button below once to proceed.

\n" + + "\n" + + "\t
\n" + + "\t\t
\n" + + "\t\t
\n" + + "\t\t\n" + + "\t
\n" + + "\n" + + ""; + } + + + public Response createResponse (SPMetaDataDto mataData,String statusCode, String inResponseTo) { + return createResponse (mataData,statusCode, null, inResponseTo); + } + + /** + Helper method to generate a shell response with a given status code, + status message, and query ID. + 使用给定状态代码,状态消息和查询ID生成Shell响应的Helper方法。 + */ + public Response createResponse(SPMetaDataDto mataData,String statusCode, String message, String inResponseTo) { + Response response = create + (Response.class, Response.DEFAULT_ELEMENT_NAME); + response.setID (generator.generateIdentifier ()); + if (inResponseTo != null) response.setInResponseTo (inResponseTo); + DateTime now = new DateTime (); + response.setIssueInstant (now); + if (mataData.getIdpEntityId() != null) + response.setIssuer (spawnIssuer(mataData)); + StatusCode statusCodeElement = create + (StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME); + statusCodeElement.setValue (statusCode); + Status status = create (Status.class, Status.DEFAULT_ELEMENT_NAME); + status.setStatusCode (statusCodeElement); + response.setStatus (status); + if (message != null) { + StatusMessage statusMessage = create + (StatusMessage.class, StatusMessage.DEFAULT_ELEMENT_NAME); + statusMessage.setMessage (message); + status.setStatusMessage (statusMessage); + } + return response; + } + + /** + Helper method to spawn a new Issuer element based on our issuer URL. + 根据我们的发行者URL生成新的Issuer元素的Helper方法。 + */ + public Issuer spawnIssuer(SPMetaDataDto mataData) { + String idpEntityId = mataData.getIdpEntityId(); + Issuer result = null; + if (idpEntityId != null) { + result = create (Issuer.class, Issuer.DEFAULT_ELEMENT_NAME); + result.setValue (idpEntityId); + } + return result; + } + + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLSignatureImpl.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLSignatureImpl.java new file mode 100644 index 0000000..e8eea12 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/common/impl/SAMLSignatureImpl.java @@ -0,0 +1,90 @@ +package com.centricsoftware.sso.service.saml2.common.impl; + + +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import com.centricsoftware.sso.service.saml2.common.SAMLSignature; +import org.opensaml.xml.signature.SignatureConstants; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.dsig.*; +import javax.xml.crypto.dsig.dom.DOMSignContext; +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; +import javax.xml.crypto.dsig.spec.TransformParameterSpec; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** +Utility for signing SAML DOM objects (assertions, requests, and responses) +and for validating and checking signatures on SAML DOM objects. +Unlike the rest of this package, this utility does not rely on OpenSAML; +it operates directly on DOM trees. (There is an import of OpenSAML's +XMLObject type, but that's just for our main method, which in turn is just +for testing purposes.) + 用于签名SAML DOM对象(断言,请求和响应)以及验证和检查SAML DOM对象上的签名的实用程序。与该软件包的其余部分不同,该实用程序不依赖于OpenSAML。 + 它直接在DOM树上运行。 (导入了OpenSAML的XMLObject类型,但这仅用于我们的主要方法,而这又仅用于测试目的 +*/ +@Service +public class SAMLSignatureImpl implements SAMLSignature { + + + + /** + * 签名SAML对象 + * @param doc + * @param referenceId + * @param signNode + * @throws GeneralSecurityException + * @throws XMLSignatureException + * @throws MarshalException + */ + @Override + public void signSAMLObject(SPMetaDataDto spMetaDataDto, Document doc, String referenceId, Node signNode) + throws GeneralSecurityException, XMLSignatureException, MarshalException { + List transforms = new ArrayList(2); + XMLSignatureFactory factory = XMLSignatureFactory.getInstance(); + transforms.add(factory.newTransform(SignatureConstants.TRANSFORM_ENVELOPED_SIGNATURE, (TransformParameterSpec) null)); + transforms.add(factory.newTransform(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, (TransformParameterSpec) null)); + Reference ref = factory.newReference("#" + referenceId, + factory.newDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA1, null), + transforms, null, null); + SignedInfo signedInfo = factory.newSignedInfo + (factory.newCanonicalizationMethod + (SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS, + (C14NMethodParameterSpec) null), + factory.newSignatureMethod(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1, null), + Collections.singletonList(ref)); + XMLSignature signature = factory.newXMLSignature(signedInfo, spMetaDataDto.getKeyInfo()); + DOMSignContext signContext = new DOMSignContext (spMetaDataDto.getKeyPair().getPrivate(), signNode); + Element element = (Element) doc.getElementsByTagName("saml2:Assertion").item(0); + element.setIdAttribute("ID", true); + signContext.setIdAttributeNS(element, null, "ID"); + signature.sign(signContext); + + Node signatureElement = signNode.getLastChild (); + + boolean foundIssuer = false; + Node elementAfterIssuer = null; + NodeList children = signNode.getChildNodes (); + for (int c = 0; c < children.getLength (); ++c) { + Node child = children.item (c); + if (foundIssuer) { + elementAfterIssuer = child; + break; + } + if (child.getNodeType () == Node.ELEMENT_NODE && child.getLocalName ().equals ("Issuer")) foundIssuer = true; + } + if (!foundIssuer || elementAfterIssuer != null) { + signNode.removeChild (signatureElement); + signNode.insertBefore (signatureElement, foundIssuer ? elementAfterIssuer : signNode.getFirstChild ()); + } + } + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/RequestSAMLService.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/RequestSAMLService.java new file mode 100644 index 0000000..2ef2e09 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/RequestSAMLService.java @@ -0,0 +1,14 @@ +package com.centricsoftware.sso.service.saml2.xml; + +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; + +public interface RequestSAMLService { + + /** + * 获取请求报文 + **/ + String getRequestSAML(SPMetaDataDto sp) throws Exception; + + + String getResponseSAML(SPMetaDataDto mataData, String userId) throws Exception; +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/impl/RequestSAMLServiceImpl.java b/sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/impl/RequestSAMLServiceImpl.java new file mode 100644 index 0000000..9db82fa --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/saml2/xml/impl/RequestSAMLServiceImpl.java @@ -0,0 +1,203 @@ +package com.centricsoftware.sso.service.saml2.xml.impl; + +import com.centricsoftware.sso.dto.saml2.sp.AuthorizationRequestDto; +import com.centricsoftware.sso.dto.saml2.sp.SPMetaDataDto; +import com.centricsoftware.sso.service.saml2.common.AssertionService; +import com.centricsoftware.sso.service.saml2.common.SAMLService; +import com.centricsoftware.sso.service.saml2.common.SAMLSignature; +import com.centricsoftware.sso.service.saml2.xml.RequestSAMLService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.saml2.core.*; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; + +import javax.annotation.Resource; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import java.util.Iterator; +import java.util.UUID; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/11/25 13:09 + */ +@Slf4j +@Service +public class RequestSAMLServiceImpl implements RequestSAMLService { + + /** + * 快速生产SAML报文服务 + */ + @Resource + SAMLService samlService; + + @Resource + AssertionService assertionService; + + @Resource + SAMLSignature samlSignature; + + + @Override + public String getRequestSAML(SPMetaDataDto sp) throws Exception{ + AuthnRequest authnRequest = createAuthnRequest(sp); + Document document = samlService.asDOMDocument(authnRequest); + DOMSource source=new DOMSource(document); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer former=tf.newTransformer(); + former.setOutputProperty(SPMetaDataDto.STANDALONE, "yes"); + StringWriter sw = new StringWriter(); + StreamResult sr = new StreamResult(sw); + former.transform(source, sr); + return sw.toString(); + } + + /** + * 获取响应报文 + */ + @Override + public String getResponseSAML(SPMetaDataDto mataData, String userId) throws Exception{ + String requestSAML = getRequestSAML(mataData); + String authnRequestXml = decodeRequestSAML(requestSAML,false); + AuthorizationRequestDto authnRequestField = readeAuthnRequest(authnRequestXml); + return generateSamlResponse(mataData,userId,authnRequestField); + } + + public String generateSamlResponse(SPMetaDataDto mataData, String userId, AuthorizationRequestDto requestField) throws Exception { + //创建Subject + String spEntityId = mataData.getSpEntityId(); + Subject subject = samlService.createSubject(userId, SPMetaDataDto.NAME_ID_PERSISTENT,SPMetaDataDto.CONFIRMATION_METHOD,mataData.getSpAssertionConsumerURL()); + NameID nameID = subject.getNameID(); + nameID.setNameQualifier(spEntityId); + nameID.setSPNameQualifier(spEntityId); + //创建断言Assertion + String assertionId = UUID.randomUUID().toString(); + Assertion assertion = assertionService.createStockAuthnAssertion(mataData.getIdpEntityId(),assertionId,spEntityId); + Issuer issuer = (Issuer) Configuration.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME) + .buildObject(Issuer.DEFAULT_ELEMENT_NAME); + issuer.setValue (mataData.getIdpEntityId()); + assertion.setIssuer(issuer); + assertion.setSubject(subject); + //创建response + Response response = samlService.createResponse(mataData,assertion,requestField.getRequestId()); + //签名 + Document document = samlService.asDOMDocument(response); + log.debug("生成的签名:Document为"); + log.debug(document.toString()); +// samlSignature.signSAMLObject(mataData,document,assertionId, document.getElementsByTagName("saml:Assertion").item(0)); + samlSignature.signSAMLObject(mataData,document,assertionId, document.getElementsByTagName("saml2:Assertion").item(0)); + DOMSource source=new DOMSource(document); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer former=tf.newTransformer(); + former.setOutputProperty(OutputKeys.STANDALONE, "yes"); + StringWriter sw = new StringWriter(); + StreamResult sr = new StreamResult(sw); + former.transform(source, sr); + String result=sw.toString(); + log.debug("Response SAML:"); + log.debug(result); + return result; + } + + /** + * 根据请求报文提取相关信息 + * @param authnRequestXml + */ + private AuthorizationRequestDto readeAuthnRequest(String authnRequestXml) throws Exception{ + if(authnRequestXml == null){ + return null; + } + AuthorizationRequestDto authnRequestField = new AuthorizationRequestDto(); + org.dom4j.Document doc = null; + doc = DocumentHelper.parseText(authnRequestXml); // 将字符串转为XML + Element rootElt = doc.getRootElement(); // 获取根节点 + String version = rootElt.attributeValue("Version"); + String ID = rootElt.attributeValue("ID"); + String destination = rootElt.attributeValue("Destination"); + String assertionConsumerServiceUrl = rootElt.attributeValue("AssertionConsumerServiceURL"); + String protocolBinding = rootElt.attributeValue("ProtocolBinding"); + Iterator elementIterator = rootElt.elementIterator(); + while (elementIterator.hasNext()){ + Element element = elementIterator.next(); + if("Issuer".equals(element.getName())){ + authnRequestField.setSpIssuer(element.getTextTrim()); + break; + } + } + authnRequestField.setVersion(version); + authnRequestField.setRequestId(ID); + authnRequestField.setDestination(destination); + authnRequestField.setAssertionConsumerServiceUrl(assertionConsumerServiceUrl); + authnRequestField.setProtocolBinding(protocolBinding); + return authnRequestField; + } + + /** + * 解析请求报文 + * @param encSAMLRequest 报文 + * @param isDecode true表示需要对encSAMLRequest进行解码;和C8的sso,因为请求报文是本工程自己生成的,没经过网络,所以请求报文并没有编码,无需解码; + * @return + */ + private String decodeRequestSAML(String encSAMLRequest,boolean isDecode) throws Exception{ + if(!isDecode) return encSAMLRequest; + String ret = null; + byte[] decodedBytes = null; + decodedBytes = new Base64().decode(encSAMLRequest.getBytes("UTF-8")); + return new String(inflate(decodedBytes, true)); + } + + private byte[] inflate(byte[] bytes, boolean nowrap) throws Exception { + Inflater decompressor = null; + try(ByteArrayOutputStream out = new ByteArrayOutputStream(); + InflaterInputStream decompressorStream = new InflaterInputStream(new ByteArrayInputStream(bytes), decompressor); + ) + { + decompressor = new Inflater(nowrap); + byte[] buf = new byte[1024]; + int count; + while ((count = decompressorStream.read(buf)) != -1) { + out.write(buf, 0, count); + } + return out.toByteArray(); + } finally { + if (decompressor != null) { + decompressor.end(); + } + } + } + + /** + * 创建AutheRequest对象 + */ + private AuthnRequest createAuthnRequest(SPMetaDataDto mataData){ + AuthnRequest authnRequest = samlService.create(AuthnRequest.class, SPMetaDataDto.DEFAULT_ELEMENT_NAME); + authnRequest.setIssueInstant(new DateTime()); + authnRequest.setDestination(mataData.getIdpServiceURL()); + authnRequest.setProtocolBinding(SPMetaDataDto.SAML2_POST_BINDING_URI); + authnRequest.setID(UUID.randomUUID().toString()); + authnRequest.setAssertionConsumerServiceURL(mataData.getSpAssertionConsumerURL()); + Issuer issuer = samlService.create(Issuer.class, SPMetaDataDto.DEFAULT_ELEMENT_NAME_Issuer); + issuer.setValue(mataData.getSpEntityId()); + authnRequest.setIssuer(issuer); + NameIDPolicy nameIDPolicy = samlService.create(NameIDPolicy.class,SPMetaDataDto.DEFAULT_ELEMENT_NAME_NameID); + nameIDPolicy.setAllowCreate(true); + nameIDPolicy.setFormat(SPMetaDataDto.UNSPECIFIED); + authnRequest.setNameIDPolicy(nameIDPolicy); + return authnRequest; + } + +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/security/AuthenticationService.java b/sso/src/main/java/com/centricsoftware/sso/service/security/AuthenticationService.java new file mode 100644 index 0000000..f7b9227 --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/security/AuthenticationService.java @@ -0,0 +1,20 @@ +package com.centricsoftware.sso.service.security; + +import javax.servlet.http.HttpServletRequest; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/11/26 15:58 + * + * @return + */ +public interface AuthenticationService { + + /** + * 鉴权成功,返回UserId;失败则抛出异常 + * @param request + * @return + */ + String authentication(HttpServletRequest request); +} diff --git a/sso/src/main/java/com/centricsoftware/sso/service/security/impl/AuthenticationServiceImpl.java b/sso/src/main/java/com/centricsoftware/sso/service/security/impl/AuthenticationServiceImpl.java new file mode 100644 index 0000000..bb554bd --- /dev/null +++ b/sso/src/main/java/com/centricsoftware/sso/service/security/impl/AuthenticationServiceImpl.java @@ -0,0 +1,27 @@ +package com.centricsoftware.sso.service.security.impl; + +import com.centricsoftware.sso.service.security.AuthenticationService; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; + +/** + * description: + * Author: Xulin.Xie + * Date: 2022/11/26 15:58 + * + * @return + */ +@Service +public class AuthenticationServiceImpl implements AuthenticationService { + /** + * 鉴权 + * @param request + */ + @Override + public String authentication(HttpServletRequest request) { + return "96262"; + } + + +} diff --git a/sso/src/main/resources/SPKeystore.jks b/sso/src/main/resources/SPKeystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..36432ba5a3dbe1e1b13c8d687b7fce80c7b69250 GIT binary patch literal 2255 zcmc(g`9IW+7RP7C%rw@q4k_D^l<+mg;}Ka}jL1HiM0UbNW3EAp@n|B1tYyZUxY<1q zW2s4s5iuB9%9f>U$xg!c+}nNK{)79&dA-hgzdk>l*XNwqIeUwHiy#mPauDEu2aFi# z=NWoX-R?tHB!WON2ta}E0RnIyWjGiLJ_!*5gJB>j1v+OSr|ajwGFm^==`uNcW120c zXg$Iq{3biZ#^M#1-P8Gs9@yJoA?~x)L6SWA%lQ`5UNLih1o_Z7zdQ#wsfi$$bp5wJ z3mQ8#9#e2prD&GBm~+q&%6hd-^y*lhLvaO2Q7|X?HjiZ-^%I>FyD63rd(&TV=F7Kd z#RW|pYnD@OW=^5I^SkzR&2qo#?lY~$Ay1Psje$fPYwgfcpXfoZ-U>sDcXO|pUzGVs z-i>@+P*nf*x}LSM^Ba2@*SJH1HCy@ImJRz}lfe-dXU|!@zzwJny-w}ut&y-mb4hZ% zv}Cwa5MSeu=10jBRRVpt1iT7N1?IaL7R0w()E=0S-@1UPL;B~jH^N)y#^m;NcZN%4 z=jq^S5y`cHJ_2H{a$6;p@RWZ($N7gaPK_kdrwLKYD5H(Okx}Y2k1+G<@QmK_E0<{< zWj!@C^03y5%+NQG^*9gbT``&5X0Wz;D~2?3qlFmFPY`jzl-pv+t!&ywwMo%Hub>;R z14337#+391IlkrPsk!{Zm=u`(+*p*SCFpxwW!*JVb47h*_4`#38;f51;O#C!>{nAc zQDT{Js3X0vm;9+pXHz4xvn|bGG;O3)>ewn&?O86b*2D*ztL}XrZb2lr>}G-2}Ny z*}~%NLR>+XA@qb%8muledf}h)IPYi{@lvsx*?Dl5CXXPWk%HGSJ;|K8Sm4A7Q{6Sc z1%;oWAPWGL7DDnZP|xf9j5_X9E;78-%HB9VJR90HzZqrNDwOJI8dX{5>={_7!VTjH zD(-DC&>!ko;@mZ?KJcbpdGApi2chP-(y_th#~t6kWIG0y=iYjh=Ez>r5DG`znXzskt+AbmV?y&J<`YtGc*m*AccThbv8I|V$M3WIODM!Km5La4#K_PH z)6290BPOL->t?ZtQZmF=Zk`?YvXOS(y5)^AO%8F&y1MYB$5>~9&*+@b%buxg-FelO zc*UF66f?aYbV;aA^%v-?#^BX6Vs`#6X-ncQWKP?k_a->WlMMltz3sP$dAp__gwdib z|DR;*J2lgx*y>m!;zu;Se~myhI^#}Xf>Oy!b1)2Y$D1jc-# zASnpVw87i1$YVl`)Nf{ge7AqPQ1+}&LZ>LKTC=l>cN&X&zOrBbpTp>&dK%CHfw-su zg)17MK>TvKAYcdt8oq8e1n|STPu?}TyU7m*^TI$tkUB5G4d=3h!bKqN1a%<*`FDX3 zK@fdh3En=gzW+ssenElylMsB<*IoT5;r$nHesG(`cUf3@b|b8u0>NDx2)^FSzIFsKT( z%i8$#hH7%7C-gda`9pkjURY18Fzp8XNb_t*sLORWO74$^yvyP__fzLGvxFN9{}FH} z(JLZA^}E#i2LgqPsQ=-9Zzj>t<6E9B==<10tsv4;T}L-n?6q8nWQ*s9k36a^K{4Q^1dT+DnIQgQv!l-)AKmyaJWHLE& z9K_y6SS6|eo4bGurYb&Y^g{2YvsW}*BxL$KatE)XIV|0$s}~veFzhy5o!P&=E<%=K*Q|;-oC@=f_c%^G zvN7JE^-#e8<7*^+1_tX|@TS!XzW~Sgy9KQjMf5IWJ3ubDS&sz`bhdewcl;jrk*~El J#lbY+e*wGP>YxAs literal 0 HcmV?d00001 diff --git a/sso/src/main/resources/application-sso.yml b/sso/src/main/resources/application-sso.yml new file mode 100644 index 0000000..8c66cc0 --- /dev/null +++ b/sso/src/main/resources/application-sso.yml @@ -0,0 +1,37 @@ +####################### SSO 证书等相关信息################### +spring: + profiles: dev +cs: + plm: + sso: + #证书位置 + keystore: /SPKeystore.jks + #证书秘钥 + keystore-pwd: centric + #别名 + alias: spkey +--- +spring: + profiles: test +cs: + plm: + sso: + #证书位置 + keystore: /SPKeystore.jks + #证书秘钥 + keystore-pwd: centric + #别名 + alias: spkey + +--- +spring: + profiles: prod +cs: + plm: + sso: + #证书位置 + keystore: /SPKeystore.jks + #证书秘钥 + keystore-pwd: centric + #别名 + alias: spkey \ No newline at end of file diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..c8a1cc3 --- /dev/null +++ b/start.bat @@ -0,0 +1,5 @@ +@echo off +set path=C:\Program Files\Java\jdk1.8.0_45\jre\bin +START "plmservice" "%path%\javaw" -jar plmservice.war +echo 启动 plmservice...... +pause \ No newline at end of file diff --git a/stop.bat b/stop.bat new file mode 100644 index 0000000..ed7d778 --- /dev/null +++ b/stop.bat @@ -0,0 +1,4 @@ +@echo off +wmic process where (commandline LIKE "%%plmservice%%" and caption="javaw.exe") delete +echo 关闭进程结束 +pause \ No newline at end of file diff --git a/task/.gitignore b/task/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/task/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/task/.mvn/wrapper/MavenWrapperDownloader.java b/task/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/task/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/task/.mvn/wrapper/maven-wrapper.jar b/task/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/task/.mvn/wrapper/maven-wrapper.properties b/task/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/task/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/task/pom.xml b/task/pom.xml new file mode 100644 index 0000000..409a0cb --- /dev/null +++ b/task/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + com.centricsoftware + plmservice + 2.0 + ../pom.xml + + + task + 2.0 + + task + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + true + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.commons + commons-lang3 + + + org.projectlombok + lombok + true + + + + + + + com.centricsoftware + config + provided + + + com.centricsoftware + commons + + + commons-beanutils + commons-beanutils + + + poi + org.apache.poi + + + poi-ooxml + org.apache.poi + + + true + + + + + + + + diff --git a/task/src/main/java/com/centricsoftware/task/config/TaskConfig.java b/task/src/main/java/com/centricsoftware/task/config/TaskConfig.java new file mode 100644 index 0000000..b9ab136 --- /dev/null +++ b/task/src/main/java/com/centricsoftware/task/config/TaskConfig.java @@ -0,0 +1,38 @@ +package com.centricsoftware.task.config; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +/** + * 使用java配置文件配置task + * @author zheng.gong + * @date 2020/4/21 + */ +@Configuration +@EnableScheduling +@ComponentScan(basePackages = {"com.centricsoftware.task.job"}) +public class TaskConfig implements SchedulingConfigurer { + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskExecutor()); + } + + /** + * 这里等同于配置文件配置 + * {@code spring.task.scheduling.pool.size=20} - Maximum allowed number of threads. + * {@code spring.task.scheduling.thread-name-prefix=Job-Thread- } - Prefix to use for the names of newly created threads. + * {@link org.springframework.boot.autoconfigure.task.TaskSchedulingProperties} + */ + @Bean + public Executor taskExecutor() { + return new ScheduledThreadPoolExecutor(20, new BasicThreadFactory.Builder().namingPattern("Job-Thread-%d").build()); + } +} diff --git a/task/src/main/java/com/centricsoftware/task/job/TaskJob.java b/task/src/main/java/com/centricsoftware/task/job/TaskJob.java new file mode 100644 index 0000000..70c4c4a --- /dev/null +++ b/task/src/main/java/com/centricsoftware/task/job/TaskJob.java @@ -0,0 +1,46 @@ +package com.centricsoftware.task.job; + +public class TaskJob { + + /** + * 按照标准时间来算,每隔 10s 执行一次 + */ + // @Scheduled(cron = "${task.test}") + //public void job1() throws Exception { +// log.info("【开始测试查询链接1】"); +// String queryXML = "\n" + +// ""; +// Document document = NodeUtil.queryByXML(queryXML); +// List list = C8ResponseXML.resultNodeCNLs(document); +// list.forEach(log::info); + // log.info("----------------------------从启动后每隔一段时间执行一次任务--------------------------"); + // } + + /** + * 从启动时间开始,间隔 60s 执行 + * 固定间隔时间 + */ +// @Scheduled(fixedRate = 60000) +// public void job2() throws Exception{ +// log.info("【开始测试查询链接2】"); +// String queryXML = "\n" + +// ""; +// Document document = NodeUtil.queryByXML(queryXML); +// List list = C8ResponseXML.resultNodeCNLs(document); +// list.forEach(log::info); +// } + +// /** +// * 从启动时间开始,延迟 5s 后间隔 4s 执行 +// * 固定等待时间 +// */ +// @Scheduled(fixedDelay = 4000, initialDelay = 5000) +// public void job3() throws Exception{ +// log.info("【开始测试查询链接3】"); +// String queryXML = "\n" + +// ""; +// Document document = NodeUtil.queryByXML(queryXML); +// List list = C8ResponseXML.resultNodeCNLs(document); +// list.forEach(log::info); +// } +} \ No newline at end of file diff --git a/task/src/main/java/com/centricsoftware/task/job/inter/CategoryJob.java b/task/src/main/java/com/centricsoftware/task/job/inter/CategoryJob.java new file mode 100644 index 0000000..2b86ac8 --- /dev/null +++ b/task/src/main/java/com/centricsoftware/task/job/inter/CategoryJob.java @@ -0,0 +1,18 @@ +package com.centricsoftware.task.job.inter; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 产品分类 \ 材料分类 接口 批量定时同步类 + * + * @author jerry + */ +@Component +public class CategoryJob { + + @Scheduled(cron = "0 0 1 * * ? ") //每天凌晨1点 + public void job1() throws Exception { + + } +}