湖北武汉水利工程java后台
zhongrj
2023-03-28 13d9b2eae29165432befb54ba8448a25661370f5
包,模块修改后提交
5 files modified
403 files added
54405 ■■■■■ changed files
skjcmanager-gateway/src/main/java/cn/gistack/gateway/GateWayApplication.java 3 ●●●● patch | view | raw | blame | history
skjcmanager-ops/pom.xml 36 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/README.md 21 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/pom.xml 119 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/AdminApplication.java 39 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/config/AdminConfiguration.java 32 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/config/DingTalkConfiguration.java 52 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/config/SecurityConfiguration.java 73 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/dingtalk/DingTalkNotifier.java 105 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/dingtalk/DingTalkService.java 110 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/dingtalk/MonitorProperties.java 67 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/security/InternalAuthorizationManager.java 75 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-admin/src/main/resources/bootstrap.yml 48 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/pom.xml 54 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/DevelopApplication.java 36 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/CodeController.java 190 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/DatasourceController.java 128 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/ModelController.java 228 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/ModelPrototypeController.java 136 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/dto/ModelDTO.java 42 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/Code.java 180 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/Datasource.java 71 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/Model.java 74 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/ModelPrototype.java 119 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/CodeMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/CodeMapper.xml 22 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/DatasourceMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/DatasourceMapper.xml 22 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelMapper.xml 27 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelPrototypeMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelPrototypeMapper.xml 35 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/ICodeService.java 38 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/IDatasourceService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/IModelPrototypeService.java 47 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/IModelService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/CodeServiceImpl.java 39 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/DatasourceServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/ModelPrototypeServiceImpl.java 55 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/ModelServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/resources/application-dev.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/resources/application-prod.yml 11 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/resources/application-test.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/main/resources/templates/code.properties 5 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/test/java/cn/gistack/test/CodeGenerator.java 95 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-develop/src/test/resources/templates/code.properties 5 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/doc/nacos/blade-flow-dev.yaml 39 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/pom.xml 97 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/FlowApplication.java 37 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/controller/WorkController.java 147 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/feign/FlowClient.java 107 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/service/FlowBusinessService.java 72 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/service/impl/FlowBusinessServiceImpl.java 333 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/config/FlowableConfiguration.java 44 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/constant/FlowEngineConstant.java 52 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowFollowController.java 70 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowManagerController.java 123 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowModelController.java 118 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowProcessController.java 96 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/entity/FlowExecution.java 50 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/entity/FlowModel.java 58 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/entity/FlowProcess.java 65 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/mapper/FlowMapper.java 46 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/mapper/FlowMapper.xml 53 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/service/FlowEngineService.java 166 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/service/impl/FlowEngineServiceImpl.java 559 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/utils/FlowCache.java 78 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/resources/application-dev.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/resources/application-prod.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/resources/application-test.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/resources/application.yml 13 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/main/resources/processes/LeaveProcess.bpmn20.xml 123 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/test/java/cn/gistack/flow/test/BladeTest.java 46 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/test/java/cn/gistack/flow/test/launch/LauncherTestServiceImpl.java 43 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/test/resources/application-dev.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/test/resources/application-prod.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/test/resources/application-test.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-flow/src/test/resources/application.yml 13 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/pom.xml 58 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/LogApplication.java 35 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/controller/LogApiController.java 66 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/controller/LogErrorController.java 66 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/controller/LogUsualController.java 66 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/feign/LogClient.java 69 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogApiMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogApiMapper.xml 26 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogErrorMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogErrorMapper.xml 27 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogUsualMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogUsualMapper.xml 22 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/ILogApiService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/ILogErrorService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/ILogUsualService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/impl/LogApiServiceImpl.java 34 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/impl/LogErrorServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/impl/LogUsualServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/resources/application-dev.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/resources/application-prod.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-log/src/main/resources/application-test.yml 9 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/pom.xml 62 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/src/main/java/cn/gistack/report/ReportApplication.java 35 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/src/main/java/cn/gistack/report/config/BladeReportConfiguration.java 43 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/src/main/resources/application-dev.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/src/main/resources/application-prod.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/src/main/resources/application-test.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-report/src/main/resources/application.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/pom.xml 112 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/ResourceApplication.java 36 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/AliOssBuilder.java 63 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/MinioOssBuilder.java 48 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/OssBuilder.java 160 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/QiniuOssBuilder.java 52 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/TencentOssBuilder.java 66 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/AliSmsBuilder.java 50 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/QiniuSmsBuilder.java 47 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/SmsBuilder.java 157 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/TencentSmsBuilder.java 46 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/YunpianSmsBuilder.java 44 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/config/BladeOssConfiguration.java 44 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/config/BladeSmsConfiguration.java 47 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/controller/AttachController.java 127 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/controller/OssController.java 151 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/controller/SmsController.java 152 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/endpoint/OssEndpoint.java 246 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/endpoint/SmsEndpoint.java 176 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/feign/SmsClient.java 70 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/AttachMapper.java 42 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/AttachMapper.xml 28 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/OssMapper.java 42 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/OssMapper.xml 30 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/SmsMapper.java 41 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/SmsMapper.xml 30 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/IAttachService.java 40 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/IOssService.java 56 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/ISmsService.java 56 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/impl/AttachServiceImpl.java 40 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/impl/OssServiceImpl.java 67 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/impl/SmsServiceImpl.java 67 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/wrapper/OssWrapper.java 49 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/wrapper/SmsWrapper.java 49 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/resources/application-dev.yml 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/resources/application-prod.yml 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/resources/application-test.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-resource/src/main/resources/application.yml 13 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/pom.xml 57 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/src/main/java/cn/gistack/swagger/SwaggerApplication.java 36 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application-dev.yml 12 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application-prod.yml 12 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application-test.yml 12 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-swagger/src/main/resources/banner.txt 8 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/Dockerfile 13 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/doc/XXL-JOB官方文档.md 1740 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/doc/XXL-JOB架构图.pptx patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/doc/db/tables_xxl_job.sql 119 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/doc/nacos/blade-xxljob-admin-dev.yaml 16 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/pom.xml 117 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/JobAdminApplication.java 17 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/IndexController.java 93 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobApiController.java 129 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobCodeController.java 96 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobGroupController.java 165 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobInfoController.java 166 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobLogController.java 230 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/UserController.java 172 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/annotation/PermissionLimit.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/interceptor/CookieInterceptor.java 43 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/interceptor/PermissionInterceptor.java 59 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/interceptor/WebMvcConfig.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/resolver/WebExceptionResolver.java 64 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/conf/XxlJobAdminConfig.java 148 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/cron/CronExpression.java 1666 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/exception/XxlJobException.java 14 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobGroup.java 76 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobInfo.java 218 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobLog.java 157 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobLogGlue.java 75 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobLogReport.java 54 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobRegistry.java 55 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobUser.java 73 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/old/RemoteHttpJobBean.java 32 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/old/XxlJobDynamicScheduler.java 413 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/old/XxlJobThreadPool.java 58 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/ExecutorRouteStrategyEnum.java 49 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/ExecutorRouter.java 24 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteBusyover.java 47 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java 85 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteFailover.java 48 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteFirst.java 19 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteLFU.java 79 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteLRU.java 76 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteLast.java 19 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteRandom.java 23 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteRound.java 39 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/scheduler/XxlJobScheduler.java 113 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobFailMonitorHelper.java 209 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobLogReportHelper.java 152 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobRegistryMonitorHelper.java 111 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobScheduleHelper.java 354 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobTriggerPoolHelper.java 145 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/trigger/TriggerTypeEnum.java 26 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/trigger/XxlJobTrigger.java 211 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/CookieUtil.java 98 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/FtlUtil.java 31 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/I18nUtil.java 80 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/JacksonUtil.java 92 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/LocalCacheUtil.java 133 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobGroupDao.java 26 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobInfoDao.java 49 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobLogDao.java 60 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobLogGlueDao.java 24 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobLogReportDao.java 26 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobRegistryDao.java 38 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobUserDao.java 31 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/LoginService.java 107 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/XxlJobService.java 86 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/impl/AdminBizImpl.java 171 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/impl/XxlJobServiceImpl.java 373 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application-dev.yml 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application-prod.yml 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application-test.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application.yml 61 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/i18n/message.properties 262 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/i18n/message_en.properties 262 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/logback.xml 40 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml 63 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml 229 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml 71 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml 249 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml 62 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml 62 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml 87 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css 11 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.eot patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.svg 2230 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.ttf patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.woff patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js 2 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css 77 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.css 269 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.js 1653 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.css.map 1 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css 6 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg 288 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2 patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css 1 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js 8 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js 166 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/fastclick/fastclick.js 841 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.css.map 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.min.css 4 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/FontAwesome.otf patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.eot patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.svg 2671 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.ttf patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff2 patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/jquery-slimscroll/jquery.slimscroll.min.js 16 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/jquery/jquery.min.js 2 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/moment/moment.min.js 1 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/dist/css/AdminLTE.min.css 7 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/dist/css/skins/_all-skins.min.css 1 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/dist/js/adminlte.min.js 14 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/icheck.min.js 10 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.css 62 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.png patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue@2x.png patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/favicon.ico patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/common.1.js 156 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/index.js 207 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/jobcode.index.1.js 97 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/jobgroup.index.1.js 239 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/jobinfo.index.1.js 648 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/joblog.detail.1.js 91 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/joblog.index.1.js 375 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/login.1.js 66 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/user.index.1.js 328 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/addon/hint/anyword-hint.js 41 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.css 36 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.js 434 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.css 346 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.js 9698 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/clike/clike.js 879 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/javascript/javascript.js 899 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/php/php.js 234 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/powershell/powershell.js 398 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/python/python.js 409 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/shell/shell.js 152 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/cronGen/cronGen.js 1079 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js 1079 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/echarts/echarts.common.min.js 22 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js 117 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/jquery/jquery.validate.min.js 4 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/layer.js 2 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/icon-ext.png patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/icon.png patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/layer.css 1 ●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/loading-0.gif patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/loading-1.gif patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/loading-2.gif patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/common/common.exception.ftl 31 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/common/common.macro.ftl 239 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/help.ftl 47 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/index.ftl 147 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/jobcode/jobcode.index.ftl 164 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl 199 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl 434 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/joblog/joblog.detail.ftl 73 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/joblog/joblog.index.ftl 180 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/login.ftl 45 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/user/user.index.ftl 188 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/Dockerfile 13 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/pom.xml 63 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/JobApplication.java 36 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/config/XxlJobConfig.java 74 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/controller/TestController.java 23 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/jobhandler/SampleXxlJob.java 195 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/src/main/resources/application.yml 21 ●●●●● patch | view | raw | blame | history
skjcmanager-ops/skjcmanager-xxljob/src/main/resources/logback.xml 40 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/controller/LeaveController.java 66 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/controller/NoticeController.java 150 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/mapper/LeaveMapper.xml 6 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/mapper/NoticeMapper.java 50 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/service/INoticeService.java 39 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/service/impl/LeaveServiceImpl.java 81 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/service/impl/NoticeServiceImpl.java 43 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/wrapper/NoticeWrapper.java 64 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/resources/application-dev.yml 11 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/resources/application-prod.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-desk/src/main/resources/application-test.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/pom.xml 66 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/SystemApplication.java 5 ●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/AuthClientController.java 120 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DataScopeController.java 123 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DeptController.java 179 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DictBizController.java 179 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DictController.java 196 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/PostController.java 149 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/SearchController.java 98 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/TenantController.java 235 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/TenantPackageController.java 133 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/TopMenuController.java 138 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/excel/RegionExcel.java 90 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/ApiScopeClient.java 63 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/DataScopeClient.java 109 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/DictBizClient.java 63 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/DictClient.java 63 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/SysClient.java 194 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/ApiScopeMapper.xml 5 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DataScopeMapper.xml 5 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DictBizMapper.java 64 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DictBizMapper.xml 51 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DictMapper.xml 54 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/MenuMapper.xml 478 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/ParamMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/ParamMapper.xml 20 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/PostMapper.xml 40 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/RegionMapper.xml 105 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/RoleMenuMapper.java 41 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/TenantMapper.java 41 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/TopMenuMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IApiScopeService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IDataScopeService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IDeptService.java 119 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IParamService.java 37 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IRoleScopeService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IRoleService.java 112 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/ITenantPackageService.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/DeptServiceImpl.java 173 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/DictBizServiceImpl.java 119 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/MenuServiceImpl.java 297 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/RegionServiceImpl.java 119 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/TenantServiceImpl.java 5 ●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/TopMenuServiceImpl.java 63 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/TopMenuSettingServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/DeptWrapper.java 78 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/DictWrapper.java 60 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/PostWrapper.java 47 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/RoleWrapper.java 61 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-system/src/main/resources/application-dev.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/Dockerfile 15 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/UserApplication.java 2 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/excel/UserExcel.java 97 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/excel/UserImporter.java 40 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/feign/UserClient.java 88 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserDeptMapper.xml 5 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserOauthMapper.java 29 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserOtherMapper.java 30 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserWebMapper.java 30 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserWebMapper.xml 5 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/IUserDeptService.java 30 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/IUserOauthService.java 30 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/IUserSearchService.java 64 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/impl/UserServiceImpl.java 2 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/resources/application-dev.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/resources/application-prod.yml 11 ●●●●● patch | view | raw | blame | history
skjcmanager-service/skjcmanager-user/src/main/resources/application-test.yml 10 ●●●●● patch | view | raw | blame | history
skjcmanager-gateway/src/main/java/cn/gistack/gateway/GateWayApplication.java
@@ -16,6 +16,7 @@
 */
package cn.gistack.gateway;
import cn.gistack.common.constant.AppsConstant;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -33,7 +34,7 @@
public class GateWayApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_GATEWAY_NAME, GateWayApplication.class, args);
        BladeApplication.run(AppsConstant.APPLICATION_GATEWAY_NAME, GateWayApplication.class, args);
    }
}
skjcmanager-ops/pom.xml
New file
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-ops</artifactId>
    <name>${project.artifactId}</name>
    <version>3.0.1.RELEASE</version>
    <packaging>pom</packaging>
    <modules>
        <module>skjcmanager-admin</module>
        <module>skjcmanager-develop</module>
        <module>skjcmanager-flow</module>
        <module>skjcmanager-log</module>
        <module>skjcmanager-report</module>
        <module>skjcmanager-resource</module>
        <module>skjcmanager-swagger</module>
        <module>skjcmanager-xxljob</module>
        <module>skjcmanager-xxljob-admin</module>
    </modules>
    <dependencies>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-metrics</artifactId>
        </dependency>
    </dependencies>
</project>
skjcmanager-ops/skjcmanager-admin/Dockerfile
New file
@@ -0,0 +1,15 @@
FROM bladex/alpine-java:openjdk8-openj9_cn_slim
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/admin
WORKDIR /blade/admin
EXPOSE 7002
ADD ./target/blade-admin.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]
skjcmanager-ops/skjcmanager-admin/README.md
New file
@@ -0,0 +1,21 @@
## SDK下载
#### Java SDK 下载
    下载SDK: https://open-doc.dingtalk.com/microapp/faquestions/vzbp02
## 配置项
#### bootstrap.yml
```
# 监控的相关配置
monitor:
  ding-talk:
    enabled: false
    # 用于自定义域名,默认会自动填充为 http://ip:port
    link: http://localhost:${server.port}
    # 钉钉配置的令牌
    access-token: xxx
    # 如果采用密钥形式,需要添加,否则需要去掉该参数
    secret: xxx
```
skjcmanager-ops/skjcmanager-admin/pom.xml
New file
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-admin</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <!--Blade-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springblade</groupId>
                    <artifactId>blade-core-launch</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-launch</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-undertow</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-prometheus</artifactId>
        </dependency>
        <!-- Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.nacos</groupId>
                    <artifactId>nacos-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.nacos</groupId>
                    <artifactId>nacos-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </dependency>
        <!--Admin-Server-->
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
        <!--Security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--<dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        </dependency>-->
        <!--Taobao-Sdk-->
        <dependency>
            <groupId>com.taobao</groupId>
            <artifactId>taobao-sdk</artifactId>
            <version>20201116</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/AdminApplication.java
New file
@@ -0,0 +1,39 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.admin;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
 * admin启动器
 *
 * @author Chill
 */
@EnableAdminServer
@EnableDiscoveryClient
@SpringBootApplication
public class AdminApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_ADMIN_NAME, AdminApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/config/AdminConfiguration.java
New file
@@ -0,0 +1,32 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.admin.config;
import cn.gistack.admin.dingtalk.MonitorProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * 启动器
 *
 * @author Chill
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(MonitorProperties.class)
public class AdminConfiguration {
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/config/DingTalkConfiguration.java
New file
@@ -0,0 +1,52 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu 卢春梦 (596392912@qq.com)
 */
package cn.gistack.admin.config;
import cn.gistack.admin.dingtalk.DingTalkNotifier;
import cn.gistack.admin.dingtalk.DingTalkService;
import cn.gistack.admin.dingtalk.MonitorProperties;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.reactive.function.client.WebClient;
/**
 * 钉钉自动配置
 *
 * @author L.cm
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "monitor.ding-talk.enabled", havingValue = "true")
public class DingTalkConfiguration {
    @Bean
    public DingTalkService dingTalkService(MonitorProperties properties,
                                           WebClient.Builder builder) {
        return new DingTalkService(properties, builder.build());
    }
    @Bean
    public DingTalkNotifier dingTalkNotifier(MonitorProperties properties,
                                             DingTalkService dingTalkService,
                                             InstanceRepository repository,
                                             Environment environment) {
        return new DingTalkNotifier(dingTalkService, properties, environment, repository);
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/config/SecurityConfiguration.java
New file
@@ -0,0 +1,73 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu 卢春梦 (596392912@qq.com)
 */
package cn.gistack.admin.config;
import cn.gistack.admin.security.InternalAuthorizationManager;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import java.net.URI;
/**
 * 监控安全配置
 *
 * @author L.cm
 */
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(AdminServerProperties.class)
public class SecurityConfiguration {
    private final String contextPath;
    public SecurityConfiguration(AdminServerProperties adminServerProperties) {
        this.contextPath = adminServerProperties.getContextPath();
    }
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        RedirectServerAuthenticationSuccessHandler successHandler = new RedirectServerAuthenticationSuccessHandler();
        successHandler.setLocation(URI.create(contextPath + "/"));
        return http.headers().frameOptions().disable().and()
            .authorizeExchange()
            // 放开静态文件和登陆
            .pathMatchers(
                contextPath + "/assets/**"
                , contextPath + "/login"
                , contextPath + "/v1/agent/**"
                , contextPath + "/v1/catalog/**"
                , contextPath + "/v1/health/**"
            ).permitAll()
            // 内网可访问 actuator
            .pathMatchers(contextPath + "/actuator", contextPath + "/actuator/**").access(new InternalAuthorizationManager())
            .anyExchange().authenticated().and()
            .formLogin().loginPage(contextPath + "/login")
            .authenticationSuccessHandler(successHandler).and()
            .logout().logoutUrl(contextPath + "/logout").and()
            .httpBasic().disable()
            .csrf().disable()
            .build();
        // @formatter:on
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/dingtalk/DingTalkNotifier.java
New file
@@ -0,0 +1,105 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu 卢春梦 (596392912@qq.com)
 */
package cn.gistack.admin.dingtalk;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.domain.values.Registration;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.lang.NonNull;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
 * 服务上下线告警
 *
 * <p>
 * 注意:AbstractStatusChangeNotifier 这个事件有毛病
 * </p>
 *
 * @author L.cm
 */
@Slf4j
public class DingTalkNotifier extends AbstractEventNotifier {
    private final DingTalkService dingTalkService;
    private final MonitorProperties properties;
    private final Environment environment;
    public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public DingTalkNotifier(DingTalkService dingTalkService, MonitorProperties properties,
                            Environment environment, InstanceRepository repository) {
        super(repository);
        this.dingTalkService = dingTalkService;
        this.properties = properties;
        this.environment = environment;
    }
    @NonNull
    @Override
    protected Mono<Void> doNotify(@NonNull InstanceEvent event, @NonNull Instance instance) {
        if (event instanceof InstanceStatusChangedEvent) {
            // 构造请求结构
            return createAndPushMsg(event, instance);
        }
        return Mono.empty();
    }
    private Mono<Void> createAndPushMsg(InstanceEvent event, Instance instance) {
        Registration registration = instance.getRegistration();
        // 服务名
        String appName = registration.getName();
        // 服务地址
        String serviceUrl = registration.getServiceUrl();
        StatusInfo status = instance.getStatusInfo();
        // 时间
        LocalDateTime localDateTime = LocalDateTime.ofInstant(event.getTimestamp(), ZoneId.systemDefault());
        MonitorProperties.DingTalk dingTalk = properties.getDingTalk();
        String title = dingTalk.getService().getTitle();
        String message = "## **" + title + "**\n" +
            "#### **【服务】** " + appName + "\n" +
            "#### **【环境】** " + environment.getActiveProfiles()[0] + "\n" +
            "#### **【地址】** " + serviceUrl + "\n" +
            "#### **【状态】** " + statusCn(status) + "\n" +
            "#### **【时间】** " + DATETIME_FORMATTER.format(localDateTime) + "\n" +
            "#### **【详情】** " + dingTalk.getLink() + "\n";
        return dingTalkService.pushMsg(title, message);
    }
    private String statusCn(StatusInfo status) {
        if (status.isUp()) {
            return "应用上线(IS UP)";
        } else if (status.isDown()) {
            return "应用宕机(IS DOWN)";
        } else if (status.isOffline()) {
            return "应用掉线(IS OFFLINE)";
        } else if (status.isUnknown()) {
            return "未知状态(UNKNOWN)";
        } else {
            return "异常状态";
        }
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/dingtalk/DingTalkService.java
New file
@@ -0,0 +1,110 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu 卢春梦 (596392912@qq.com)
 */
package cn.gistack.admin.dingtalk;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriUtils;
import reactor.core.publisher.Mono;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
/**
 * 钉钉 服务
 *
 * @author L.cm
 */
@Slf4j
@RequiredArgsConstructor
public class DingTalkService {
    private static final String DING_TALK_ROBOT_URL = "https://oapi.dingtalk.com/robot/send?access_token=";
    private final MonitorProperties properties;
    private final WebClient webClient;
    /**
     * 发送消息
     *
     * @param title title
     * @param text  消息
     */
    public Mono<Void> pushMsg(String title, String text) {
        log.info("钉钉消息:[创建消息体]title:{}, text:{}", title, text);
        HashMap<String, String> params = new HashMap<>(2);
        params.put("title", title);
        params.put("text", text);
        Map<String, Object> body = new HashMap<>(2);
        body.put("msgtype", "markdown");
        body.put("markdown", params);
        log.info("创建消息体 json:{}", body);
        MonitorProperties.DingTalk dingTalk = properties.getDingTalk();
        String accessToken = dingTalk.getAccessToken();
        if (!StringUtils.hasText(accessToken)) {
            log.error("DingTalk alert config accessToken ${monitor.ding-talk.access-token} is blank.");
            return Mono.empty();
        }
        String urlString = DING_TALK_ROBOT_URL + dingTalk.getAccessToken();
        // 有私钥要签名
        String secret = dingTalk.getSecret();
        if (StringUtils.hasText(secret)) {
            long timestamp = System.currentTimeMillis();
            urlString += String.format("&timestamp=%s&sign=%s", timestamp, getSign(secret, timestamp));
        }
        return webClient.post()
            .uri(URI.create(urlString))
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(body))
            .retrieve()
            .bodyToMono(String.class)
            .doOnSuccess((result) -> log.info("钉钉消息:[消息返回]result:{}", result))
            .then();
    }
    private static String getSign(String secret, long timestamp) {
        String stringToSign = timestamp + "\n" + secret;
        byte[] hmacSha256Bytes = digestHmac(stringToSign, secret);
        return UriUtils.encode(Base64Utils.encodeToString(hmacSha256Bytes), StandardCharsets.UTF_8);
    }
    public static byte[] digestHmac(String data, String key) {
        SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        try {
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/dingtalk/MonitorProperties.java
New file
@@ -0,0 +1,67 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu 卢春梦 (596392912@qq.com)
 */
package cn.gistack.admin.dingtalk;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
/**
 * 监控配置
 *
 * @author L.cm
 */
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("monitor")
public class MonitorProperties {
    private DingTalk dingTalk = new DingTalk();
    @Getter
    @Setter
    public static class DingTalk {
        /**
         * 启用钉钉告警,默认为 true
         */
        private boolean enabled = false;
        /**
         * 钉钉机器人 token
         */
        private String accessToken;
        /**
         * 签名:如果有 secret 则进行签名,兼容老接口
         */
        private String secret;
        /**
         * 地址配置
         */
        private String link;
        private Service service = new Service();
    }
    @Getter
    @Setter
    public static class Service {
        /**
         * 服务 状态 title
         */
        private String title = "服务状态通知";
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/java/cn/gistack/admin/security/InternalAuthorizationManager.java
New file
@@ -0,0 +1,75 @@
/*
 *      Copyright (c) 2018-2028, DreamLu All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: DreamLu 卢春梦 (596392912@qq.com)
 */
package cn.gistack.admin.security;
import org.springblade.core.launch.utils.INetUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Optional;
/**
 * 内网认证管理,内网放行,外网认证
 *
 * @author L.cm
 */
public class InternalAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    private static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext context) {
        return Mono.just(getAuthorizationDecision(context));
    }
    private static AuthorizationDecision getAuthorizationDecision(AuthorizationContext context) {
        return new AuthorizationDecision(isInternalNet(context));
    }
    /**
     * 判断是否内网 ip 请求
     *
     * @param context AuthorizationContext
     * @return 是否内网 ip
     */
    private static boolean isInternalNet(AuthorizationContext context) {
        ServerHttpRequest request = Optional.ofNullable(context)
            .map(AuthorizationContext::getExchange)
            .map(ServerWebExchange::getRequest)
            .orElse(null);
        if (request == null) {
            return false;
        }
        HttpHeaders headers = request.getHeaders();
        // 如果没有 X-Forwarded-For 代表为 admin 拉取
        if (!headers.containsKey(HEADER_X_FORWARDED_FOR)) {
            return true;
        }
        return Optional.of(request)
            .map(ServerHttpRequest::getRemoteAddress)
            .map(InetSocketAddress::getAddress)
            .map(INetUtil::isInternalIp)
            .orElse(false);
    }
}
skjcmanager-ops/skjcmanager-admin/src/main/resources/bootstrap.yml
New file
@@ -0,0 +1,48 @@
server:
  port: 7002
  undertow:
    threads:
      # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
      io: 16
      # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
      worker: 400
    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
    buffer-size: 1024
    # 是否分配的直接内存
    direct-buffers: true
spring:
  boot:
    admin:
      # 忽略服务名
      discovery:
        ignored-services:
          - consul
          - serverAddr
      # 自定义UI界面
      ui:
        title: BladeX Monitor
        external-views:
          - label: 架构官网
            url: https://bladex.vip/
            order: 1
            iframe: true
      # 用于内网安全,判断 admin proxy
      instance-proxy:
        ignored-headers: "X-Forwarded-For"
  # 自定义登录用户名密码
  security:
    user:
      name: blade
      password: blade
# 监控的相关配置
monitor:
  ding-talk:
    enabled: false
    # 用于自定义域名,默认会自动填充为 http://ip:port
    link: http://localhost:${server.port}
    # 钉钉配置的令牌
    access-token: xxx
    # 如果采用密钥形式,需要添加,否则需要去掉该参数
    secret:
skjcmanager-ops/skjcmanager-develop/pom.xml
New file
@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springblade</groupId>
        <artifactId>skjcmanager-ops</artifactId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-develop</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <!--Blade-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-boot</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-develop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-swagger</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-dict-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/DevelopApplication.java
New file
@@ -0,0 +1,36 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
/**
 * Develop启动器
 *
 * @author Chill
 */
@BladeCloudApplication
public class DevelopApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_DEVELOP_NAME, DevelopApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/CodeController.java
New file
@@ -0,0 +1,190 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.controller;
import cn.gistack.develop.entity.Code;
import cn.gistack.develop.entity.Datasource;
import cn.gistack.develop.entity.Model;
import cn.gistack.develop.entity.ModelPrototype;
import cn.gistack.develop.service.ICodeService;
import cn.gistack.develop.service.IDatasourceService;
import cn.gistack.develop.service.IModelPrototypeService;
import cn.gistack.develop.service.IModelService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.*;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.develop.support.BladeCodeGenerator;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * 控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/code")
@Api(value = "代码生成", tags = "代码生成")
@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
public class CodeController extends BladeController {
    private final ICodeService codeService;
    private final IDatasourceService datasourceService;
    private final IModelService modelService;
    private final IModelPrototypeService modelPrototypeService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入code")
    public R<Code> detail(Code code) {
        Code detail = codeService.getOne(Condition.getQueryWrapper(code));
        return R.data(detail);
    }
    /**
     * 分页
     */
    @GetMapping("/list")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "codeName", value = "模块名", paramType = "query", dataType = "string"),
        @ApiImplicitParam(name = "tableName", value = "表名", paramType = "query", dataType = "string"),
        @ApiImplicitParam(name = "modelName", value = "实体名", paramType = "query", dataType = "string")
    })
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入code")
    public R<IPage<Code>> list(@ApiIgnore @RequestParam Map<String, Object> code, Query query) {
        IPage<Code> pages = codeService.page(Condition.getPage(query), Condition.getQueryWrapper(code, Code.class));
        return R.data(pages);
    }
    /**
     * 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "新增或修改", notes = "传入code")
    public R submit(@Valid @RequestBody Code code) {
        return R.status(codeService.submit(code));
    }
    /**
     * 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(codeService.removeByIds(Func.toLongList(ids)));
    }
    /**
     * 复制
     */
    @PostMapping("/copy")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "复制", notes = "传入id")
    public R copy(@ApiParam(value = "主键", required = true) @RequestParam Long id) {
        Code code = codeService.getById(id);
        code.setId(null);
        code.setCodeName(code.getCodeName() + "-copy");
        return R.status(codeService.save(code));
    }
    /**
     * 代码生成
     */
    @PostMapping("/gen-code")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "代码生成", notes = "传入ids")
    public R genCode(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        Collection<Code> codes = codeService.listByIds(Func.toLongList(ids));
        codes.forEach(code -> {
            BladeCodeGenerator generator = new BladeCodeGenerator();
            // 设置基础模型
            Model model = modelService.getById(code.getModelId());
            generator.setModelCode(model.getModelCode());
            generator.setModelClass(model.getModelClass());
            // 设置模型集合
            List<ModelPrototype> prototypes = modelPrototypeService.prototypeList(model.getId());
            generator.setModel(JsonUtil.readMap(JsonUtil.toJson(model)));
            generator.setPrototypes(JsonUtil.readListMap(JsonUtil.toJson(prototypes)));
            if (StringUtil.isNotBlank(code.getSubModelId())) {
                Model subModel = modelService.getById(Func.toLong(code.getSubModelId()));
                List<ModelPrototype> subPrototypes = modelPrototypeService.prototypeList(subModel.getId());
                generator.setSubModel(JsonUtil.readMap(JsonUtil.toJson(subModel)));
                generator.setSubPrototypes(JsonUtil.readListMap(JsonUtil.toJson(subPrototypes)));
            }
            // 设置数据源
            Datasource datasource = datasourceService.getById(model.getDatasourceId());
            generator.setDriverName(datasource.getDriverClass());
            generator.setUrl(datasource.getUrl());
            generator.setUsername(datasource.getUsername());
            generator.setPassword(datasource.getPassword());
            // 设置基础配置
            generator.setCodeStyle(code.getCodeStyle());
            generator.setCodeName(code.getCodeName());
            generator.setServiceName(code.getServiceName());
            generator.setPackageName(code.getPackageName());
            generator.setPackageDir(code.getApiPath());
            generator.setPackageWebDir(code.getWebPath());
            generator.setTablePrefix(Func.toStrArray(code.getTablePrefix()));
            generator.setIncludeTables(Func.toStrArray(code.getTableName()));
            // 设置模版信息
            generator.setTemplateType(code.getTemplateType());
            generator.setAuthor(code.getAuthor());
            generator.setSubModelId(code.getSubModelId());
            generator.setSubFkId(code.getSubFkId());
            generator.setTreeId(code.getTreeId());
            generator.setTreePid(code.getTreePid());
            generator.setTreeName(code.getTreeName());
            // 设置是否继承基础业务字段
            generator.setHasSuperEntity(code.getBaseMode() == 2);
            // 设置是否开启包装器模式
            generator.setHasWrapper(code.getWrapMode() == 2);
            // 设置是否开启远程调用模式
            generator.setHasFeign(code.getFeignMode() == 2);
            // 设置控制器服务名前缀
            generator.setHasServiceName(Boolean.TRUE);
            // 启动代码生成
            generator.run();
        });
        return R.success("代码生成成功");
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/DatasourceController.java
New file
@@ -0,0 +1,128 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.controller;
import cn.gistack.develop.entity.Datasource;
import cn.gistack.develop.service.IDatasourceService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
 * 数据源配置表 控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/datasource")
@Api(value = "数据源配置表", tags = "数据源配置表接口")
public class DatasourceController extends BladeController {
    private final IDatasourceService datasourceService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入datasource")
    public R<Datasource> detail(Datasource datasource) {
        Datasource detail = datasourceService.getOne(Condition.getQueryWrapper(datasource));
        return R.data(detail);
    }
    /**
     * 分页 数据源配置表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入datasource")
    public R<IPage<Datasource>> list(Datasource datasource, Query query) {
        IPage<Datasource> pages = datasourceService.page(Condition.getPage(query), Condition.getQueryWrapper(datasource));
        return R.data(pages);
    }
    /**
     * 新增 数据源配置表
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入datasource")
    public R save(@Valid @RequestBody Datasource datasource) {
        return R.status(datasourceService.save(datasource));
    }
    /**
     * 修改 数据源配置表
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入datasource")
    public R update(@Valid @RequestBody Datasource datasource) {
        return R.status(datasourceService.updateById(datasource));
    }
    /**
     * 新增或修改 数据源配置表
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入datasource")
    public R submit(@Valid @RequestBody Datasource datasource) {
        datasource.setUrl(datasource.getUrl().replace("&amp;", "&"));
        return R.status(datasourceService.saveOrUpdate(datasource));
    }
    /**
     * 删除 数据源配置表
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(datasourceService.deleteLogic(Func.toLongList(ids)));
    }
    /**
     * 数据源列表
     */
    @GetMapping("/select")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "下拉数据源", notes = "查询列表")
    public R<List<Datasource>> select() {
        List<Datasource> list = datasourceService.list();
        return R.data(list);
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/ModelController.java
New file
@@ -0,0 +1,228 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.controller;
import cn.gistack.develop.entity.Datasource;
import cn.gistack.develop.entity.Model;
import cn.gistack.develop.entity.ModelPrototype;
import cn.gistack.develop.service.IDatasourceService;
import cn.gistack.develop.service.IModelPrototypeService;
import cn.gistack.develop.service.IModelService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 数据模型表 控制器
 *
 * @author Chill
 */
@RestController
@AllArgsConstructor
@RequestMapping("/model")
@Api(value = "数据模型表", tags = "数据模型表接口")
public class ModelController extends BladeController {
    private final IModelService modelService;
    private final IModelPrototypeService modelPrototypeService;
    private final IDatasourceService datasourceService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入model")
    public R<Model> detail(Model model) {
        Model detail = modelService.getOne(Condition.getQueryWrapper(model));
        return R.data(detail);
    }
    /**
     * 分页 数据模型表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入model")
    public R<IPage<Model>> list(Model model, Query query) {
        IPage<Model> pages = modelService.page(Condition.getPage(query), Condition.getQueryWrapper(model));
        return R.data(pages);
    }
    /**
     * 新增 数据模型表
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "新增", notes = "传入model")
    public R save(@Valid @RequestBody Model model) {
        return R.status(modelService.save(model));
    }
    /**
     * 修改 数据模型表
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "修改", notes = "传入model")
    public R update(@Valid @RequestBody Model model) {
        return R.status(modelService.updateById(model));
    }
    /**
     * 新增或修改 数据模型表
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "新增或修改", notes = "传入model")
    public R submit(@Valid @RequestBody Model model) {
        return R.status(modelService.saveOrUpdate(model));
    }
    /**
     * 删除 数据模型表
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(modelService.deleteLogic(Func.toLongList(ids)));
    }
    /**
     * 模型列表
     */
    @GetMapping("/select")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "模型列表", notes = "模型列表")
    public R<List<Model>> select() {
        List<Model> list = modelService.list();
        list.forEach(model -> model.setModelName(model.getModelTable() + StringPool.COLON + StringPool.SPACE + model.getModelName()));
        return R.data(list);
    }
    /**
     * 获取物理表列表
     */
    @GetMapping("/table-list")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "物理表列表", notes = "传入datasourceId")
    public R<List<TableInfo>> tableList(Long datasourceId) {
        Datasource datasource = datasourceService.getById(datasourceId);
        ConfigBuilder config = getConfigBuilder(datasource);
        List<TableInfo> tableInfoList = config.getTableInfoList().stream()
            .filter(tableInfo -> !StringUtil.startsWithIgnoreCase(tableInfo.getName(), "ACT_"))
            .map(tableInfo -> tableInfo.setComment(tableInfo.getName() + StringPool.COLON + tableInfo.getComment()))
            .collect(Collectors.toList());
        return R.data(tableInfoList);
    }
    /**
     * 获取物理表信息
     */
    @GetMapping("/table-info")
    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "物理表信息", notes = "传入model信息")
    public R<TableInfo> tableInfo(Long modelId, String tableName, Long datasourceId) {
        if (StringUtil.isBlank(tableName)) {
            Model model = modelService.getById(modelId);
            tableName = model.getModelTable();
        }
        TableInfo tableInfo = getTableInfo(tableName, datasourceId);
        return R.data(tableInfo);
    }
    /**
     * 获取字段信息
     */
    @GetMapping("/model-prototype")
    @ApiOperationSupport(order = 10)
    @ApiOperation(value = "物理表字段信息", notes = "传入modelId与datasourceId")
    public R modelPrototype(Long modelId, Long datasourceId) {
        List<ModelPrototype> modelPrototypeList = modelPrototypeService.list(Wrappers.<ModelPrototype>query().lambda().eq(ModelPrototype::getModelId, modelId));
        if (modelPrototypeList.size() > 0) {
            return R.data(modelPrototypeList);
        }
        Model model = modelService.getById(modelId);
        String tableName = model.getModelTable();
        TableInfo tableInfo = getTableInfo(tableName, datasourceId);
        if (tableInfo != null) {
            return R.data(tableInfo.getFields());
        } else {
            return R.fail("未获得相关表信息");
        }
    }
    /**
     * 获取表信息
     *
     * @param tableName    表名
     * @param datasourceId 数据源主键
     */
    private TableInfo getTableInfo(String tableName, Long datasourceId) {
        Datasource datasource = datasourceService.getById(datasourceId);
        ConfigBuilder config = getConfigBuilder(datasource);
        List<TableInfo> tableInfoList = config.getTableInfoList();
        TableInfo tableInfo = null;
        Iterator<TableInfo> iterator = tableInfoList.stream().filter(table -> table.getName().equals(tableName)).collect(Collectors.toList()).iterator();
        if (iterator.hasNext()) {
            tableInfo = iterator.next();
            tableInfo.setEntityName(tableInfo.getEntityName().replace(StringUtil.firstCharToUpper(tableName.split(StringPool.UNDERSCORE)[0]), StringPool.EMPTY));
        }
        return tableInfo;
    }
    /**
     * 获取表配置信息
     *
     * @param datasource 数据源信息
     */
    private ConfigBuilder getConfigBuilder(Datasource datasource) {
        StrategyConfig strategyConfig = new StrategyConfig.Builder()
            .entityBuilder()
            .naming(NamingStrategy.underline_to_camel)
            .columnNaming(NamingStrategy.underline_to_camel).build();
        DataSourceConfig datasourceConfig = new DataSourceConfig.Builder(
            datasource.getUrl(), datasource.getUsername(), datasource.getPassword()
        ).build();
        return new ConfigBuilder(null, datasourceConfig, strategyConfig, null, null, null);
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/controller/ModelPrototypeController.java
New file
@@ -0,0 +1,136 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.controller;
import cn.gistack.develop.entity.ModelPrototype;
import cn.gistack.develop.service.IModelPrototypeService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
 * 数据原型表 控制器
 *
 * @author Chill
 */
@RestController
@AllArgsConstructor
@RequestMapping("/model-prototype")
@Api(value = "数据原型表", tags = "数据原型表接口")
public class ModelPrototypeController extends BladeController {
    private final IModelPrototypeService modelPrototypeService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入modelPrototype")
    public R<ModelPrototype> detail(ModelPrototype modelPrototype) {
        ModelPrototype detail = modelPrototypeService.getOne(Condition.getQueryWrapper(modelPrototype));
        return R.data(detail);
    }
    /**
     * 分页 数据原型表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入modelPrototype")
    public R<IPage<ModelPrototype>> list(ModelPrototype modelPrototype, Query query) {
        IPage<ModelPrototype> pages = modelPrototypeService.page(Condition.getPage(query), Condition.getQueryWrapper(modelPrototype));
        return R.data(pages);
    }
    /**
     * 新增 数据原型表
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入modelPrototype")
    public R save(@Valid @RequestBody ModelPrototype modelPrototype) {
        return R.status(modelPrototypeService.save(modelPrototype));
    }
    /**
     * 修改 数据原型表
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入modelPrototype")
    public R update(@Valid @RequestBody ModelPrototype modelPrototype) {
        return R.status(modelPrototypeService.updateById(modelPrototype));
    }
    /**
     * 新增或修改 数据原型表
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入modelPrototype")
    public R submit(@Valid @RequestBody ModelPrototype modelPrototype) {
        return R.status(modelPrototypeService.saveOrUpdate(modelPrototype));
    }
    /**
     * 批量新增或修改 数据原型表
     */
    @PostMapping("/submit-list")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "批量新增或修改", notes = "传入modelPrototype集合")
    public R submitList(@Valid @RequestBody List<ModelPrototype> modelPrototypes) {
        return R.status(modelPrototypeService.submitList(modelPrototypes));
    }
    /**
     * 删除 数据原型表
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(modelPrototypeService.deleteLogic(Func.toLongList(ids)));
    }
    /**
     * 数据原型列表
     */
    @GetMapping("/select")
    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "数据原型列表", notes = "数据原型列表")
    public R<List<ModelPrototype>> select(@ApiParam(value = "数据模型Id", required = true) @RequestParam Long modelId) {
        List<ModelPrototype> list = modelPrototypeService.list(Wrappers.<ModelPrototype>query().lambda().eq(ModelPrototype::getModelId, modelId));
        list.forEach(prototype -> prototype.setComment(prototype.getJdbcName() + StringPool.COLON + StringPool.SPACE + prototype.getComment()));
        return R.data(list);
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/dto/ModelDTO.java
New file
@@ -0,0 +1,42 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import cn.gistack.develop.entity.Model;
import cn.gistack.develop.entity.ModelPrototype;
import java.util.List;
/**
 * 代码模型DTO
 *
 * @author Chill
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ModelDTO extends Model {
    private static final long serialVersionUID = 1L;
    /**
     * 代码建模原型
     */
    private List<ModelPrototype> prototypes;
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/Code.java
New file
@@ -0,0 +1,180 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
 * 实体类
 *
 * @author Chill
 */
@Data
@TableName("blade_code")
@ApiModel(value = "Code对象", description = "Code对象")
public class Code implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 主键
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /**
     * 数据模型主键
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty(value = "数据模型主键")
    private Long modelId;
    /**
     * 模块名称
     */
    @ApiModelProperty(value = "服务名称")
    private String serviceName;
    /**
     * 模块名称
     */
    @ApiModelProperty(value = "模块名称")
    private String codeName;
    /**
     * 表名
     */
    @ApiModelProperty(value = "表名")
    private String tableName;
    /**
     * 实体名
     */
    @ApiModelProperty(value = "表前缀")
    private String tablePrefix;
    /**
     * 主键名
     */
    @ApiModelProperty(value = "主键名")
    private String pkName;
    /**
     * 后端包名
     */
    @ApiModelProperty(value = "后端包名")
    private String packageName;
    /**
     * 模版类型
     */
    @ApiModelProperty(value = "模版类型")
    private String templateType;
    /**
     * 作者信息
     */
    @ApiModelProperty(value = "作者信息")
    private String author;
    /**
     * 子表模型主键
     */
    @ApiModelProperty(value = "子表模型主键")
    private String subModelId;
    /**
     * 子表绑定外键
     */
    @ApiModelProperty(value = "子表绑定外键")
    private String subFkId;
    /**
     * 树主键字段
     */
    @ApiModelProperty(value = "树主键字段")
    private String treeId;
    /**
     * 树父主键字段
     */
    @ApiModelProperty(value = "树父主键字段")
    private String treePid;
    /**
     * 树名称字段
     */
    @ApiModelProperty(value = "树名称字段")
    private String treeName;
    /**
     * 基础业务模式
     */
    @ApiModelProperty(value = "基础业务模式")
    private Integer baseMode;
    /**
     * 包装器模式
     */
    @ApiModelProperty(value = "包装器模式")
    private Integer wrapMode;
    /**
     * 远程调用模式
     */
    @ApiModelProperty(value = "远程调用模式")
    private Integer feignMode;
    /**
     * 代码风格
     */
    @ApiModelProperty(value = "代码风格")
    private String codeStyle;
    /**
     * 后端路径
     */
    @ApiModelProperty(value = "后端路径")
    private String apiPath;
    /**
     * 前端路径
     */
    @ApiModelProperty(value = "前端路径")
    private String webPath;
    /**
     * 是否已删除
     */
    @TableLogic
    @ApiModelProperty(value = "是否已删除")
    private Integer isDeleted;
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/Datasource.java
New file
@@ -0,0 +1,71 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import org.springblade.core.mp.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
 * 数据源配置表实体类
 *
 * @author Chill
 */
@Data
@TableName("blade_datasource")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "Datasource对象", description = "数据源配置表")
public class Datasource extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * 名称
     */
    @ApiModelProperty(value = "名称")
    private String name;
    /**
     * 驱动类
     */
    @ApiModelProperty(value = "驱动类")
    private String driverClass;
    /**
     * 连接地址
     */
    @ApiModelProperty(value = "连接地址")
    private String url;
    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String username;
    /**
     * 密码
     */
    @ApiModelProperty(value = "密码")
    private String password;
    /**
     * 备注
     */
    @ApiModelProperty(value = "备注")
    private String remark;
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/Model.java
New file
@@ -0,0 +1,74 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.BaseEntity;
/**
 * 数据模型表实体类
 *
 * @author Chill
 */
@Data
@TableName("blade_model")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "Model对象", description = "数据模型表")
public class Model extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * 数据源主键
     */
    @ApiModelProperty(value = "数据源主键")
    @JsonSerialize(using = ToStringSerializer.class)
    private Long datasourceId;
    /**
     * 模型名称
     */
    @ApiModelProperty(value = "模型名称")
    private String modelName;
    /**
     * 模型编号
     */
    @ApiModelProperty(value = "模型编号")
    private String modelCode;
    /**
     * 物理表名
     */
    @ApiModelProperty(value = "物理表名")
    private String modelTable;
    /**
     * 模型类名
     */
    @ApiModelProperty(value = "模型类名")
    private String modelClass;
    /**
     * 模型备注
     */
    @ApiModelProperty(value = "模型备注")
    private String modelRemark;
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/entity/ModelPrototype.java
New file
@@ -0,0 +1,119 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.BaseEntity;
/**
 * 数据原型表实体类
 *
 * @author Chill
 */
@Data
@TableName("blade_model_prototype")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "ModelPrototype对象", description = "数据原型表")
public class ModelPrototype extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * 模型主键
     */
    @ApiModelProperty(value = "模型主键")
    @JsonSerialize(using = ToStringSerializer.class)
    private Long modelId;
    /**
     * 物理列名
     */
    @ApiModelProperty(value = "物理列名")
    private String jdbcName;
    /**
     * 物理类型
     */
    @ApiModelProperty(value = "物理类型")
    private String jdbcType;
    /**
     * 实体列名
     */
    @ApiModelProperty(value = "实体列名")
    private String propertyName;
    /**
     * 实体类型
     */
    @ApiModelProperty(value = "实体类型")
    private String propertyType;
    /**
     * 实体类型引用
     */
    @ApiModelProperty(value = "实体类型引用")
    private String propertyEntity;
    /**
     * 注释说明
     */
    @ApiModelProperty(value = "注释说明")
    private String comment;
    /**
     * 列表显示
     */
    @ApiModelProperty(value = "列表显示")
    private Integer isList;
    /**
     * 表单显示
     */
    @ApiModelProperty(value = "表单显示")
    private Integer isForm;
    /**
     * 独占一行
     */
    @ApiModelProperty(value = "独占一行")
    private Integer isRow;
    /**
     * 组件类型
     */
    @ApiModelProperty(value = "组件类型")
    private String componentType;
    /**
     * 字典编码
     */
    @ApiModelProperty(value = "字典编码")
    private String dictCode;
    /**
     * 是否必填
     */
    @ApiModelProperty(value = "是否必填")
    private Integer isRequired;
    /**
     * 查询配置
     */
    @ApiModelProperty(value = "查询配置")
    private Integer isQuery;
    /**
     * 查询类型
     */
    @ApiModelProperty(value = "查询类型")
    private String queryType;
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/CodeMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.mapper;
import cn.gistack.develop.entity.Code;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * Mapper 接口
 *
 * @author Chill
 */
public interface CodeMapper extends BaseMapper<Code> {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/CodeMapper.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.develop.mapper.CodeMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="codeResultMap" type="cn.gistack.develop.entity.Code">
        <id column="id" property="id"/>
        <result column="datasource_id" property="datasourceId"/>
        <result column="service_name" property="serviceName"/>
        <result column="code_name" property="codeName"/>
        <result column="table_name" property="tableName"/>
        <result column="pk_name" property="pkName"/>
        <result column="base_mode" property="baseMode"/>
        <result column="wrap_mode" property="wrapMode"/>
        <result column="table_prefix" property="tablePrefix"/>
        <result column="package_name" property="packageName"/>
        <result column="api_path" property="apiPath"/>
        <result column="web_path" property="webPath"/>
        <result column="is_deleted" property="isDeleted"/>
    </resultMap>
</mapper>
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/DatasourceMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.mapper;
import cn.gistack.develop.entity.Datasource;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * 数据源配置表 Mapper 接口
 *
 * @author Chill
 */
public interface DatasourceMapper extends BaseMapper<Datasource> {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/DatasourceMapper.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.develop.mapper.DatasourceMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="datasourceResultMap" type="cn.gistack.develop.entity.Datasource">
        <result column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_dept" property="createDept"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="driver_class" property="driverClass"/>
        <result column="url" property="url"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="remark" property="remark"/>
    </resultMap>
</mapper>
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.mapper;
import cn.gistack.develop.entity.Model;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * 数据模型表 Mapper 接口
 *
 * @author Chill
 */
public interface ModelMapper extends BaseMapper<Model> {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelMapper.xml
New file
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.develop.mapper.ModelMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="modelResultMap" type="cn.gistack.develop.entity.Model">
        <id column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="datasource_id" property="datasourceId"/>
        <result column="model_name" property="modelName"/>
        <result column="model_code" property="modelCode"/>
        <result column="model_table" property="modelTable"/>
        <result column="model_class" property="modelClass"/>
        <result column="model_remark" property="modelRemark"/>
    </resultMap>
    <select id="selectModelPage" resultMap="modelResultMap">
        select * from blade_model where is_deleted = 0
    </select>
</mapper>
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelPrototypeMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.mapper;
import cn.gistack.develop.entity.ModelPrototype;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * 数据原型表 Mapper 接口
 *
 * @author Chill
 */
public interface ModelPrototypeMapper extends BaseMapper<ModelPrototype> {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/mapper/ModelPrototypeMapper.xml
New file
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.develop.mapper.ModelPrototypeMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="modelPrototypeResultMap" type="cn.gistack.develop.entity.ModelPrototype">
        <id column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="jdbc_name" property="jdbcName"/>
        <result column="jdbc_type" property="jdbcType"/>
        <result column="comment" property="comment"/>
        <result column="property_type" property="propertyType"/>
        <result column="property_entity" property="propertyEntity"/>
        <result column="property_name" property="propertyName"/>
        <result column="is_form" property="isForm"/>
        <result column="is_row" property="isRow"/>
        <result column="component_type" property="componentType"/>
        <result column="dict_code" property="dictCode"/>
        <result column="is_required" property="isRequired"/>
        <result column="is_list" property="isList"/>
        <result column="is_query" property="isQuery"/>
        <result column="query_type" property="queryType"/>
    </resultMap>
    <select id="selectModelPrototypePage" resultMap="modelPrototypeResultMap">
        select * from blade_model_prototype where is_deleted = 0
    </select>
</mapper>
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/ICodeService.java
New file
@@ -0,0 +1,38 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service;
import cn.gistack.develop.entity.Code;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * 服务类
 *
 * @author Chill
 */
public interface ICodeService extends IService<Code> {
    /**
     * 提交
     *
     * @param code
     * @return
     */
    boolean submit(Code code);
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/IDatasourceService.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service;
import cn.gistack.develop.entity.Datasource;
import org.springblade.core.mp.base.BaseService;
/**
 * 数据源配置表 服务类
 *
 * @author Chill
 */
public interface IDatasourceService extends BaseService<Datasource> {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/IModelPrototypeService.java
New file
@@ -0,0 +1,47 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service;
import cn.gistack.develop.entity.ModelPrototype;
import org.springblade.core.mp.base.BaseService;
import java.util.List;
/**
 * 数据原型表 服务类
 *
 * @author Chill
 */
public interface IModelPrototypeService extends BaseService<ModelPrototype> {
    /**
     * 批量提交
     *
     * @param modelPrototypes 原型集合
     * @return boolean
     */
    boolean submitList(List<ModelPrototype> modelPrototypes);
    /**
     * 原型列表
     *
     * @param modelId 模型ID
     * @return List<ModelPrototype>
     */
    List<ModelPrototype> prototypeList(Long modelId);
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/IModelService.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service;
import cn.gistack.develop.entity.Model;
import org.springblade.core.mp.base.BaseService;
/**
 * 数据模型表 服务类
 *
 * @author Chill
 */
public interface IModelService extends BaseService<Model> {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/CodeServiceImpl.java
New file
@@ -0,0 +1,39 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service.impl;
import cn.gistack.develop.entity.Code;
import cn.gistack.develop.mapper.CodeMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.tool.constant.BladeConstant;
import cn.gistack.develop.service.ICodeService;
import org.springframework.stereotype.Service;
/**
 * 服务实现类
 *
 * @author Chill
 */
@Service
public class CodeServiceImpl extends ServiceImpl<CodeMapper, Code> implements ICodeService {
    @Override
    public boolean submit(Code code) {
        code.setIsDeleted(BladeConstant.DB_NOT_DELETED);
        return saveOrUpdate(code);
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/DatasourceServiceImpl.java
New file
@@ -0,0 +1,33 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service.impl;
import cn.gistack.develop.entity.Datasource;
import cn.gistack.develop.mapper.DatasourceMapper;
import org.springblade.core.mp.base.BaseServiceImpl;
import cn.gistack.develop.service.IDatasourceService;
import org.springframework.stereotype.Service;
/**
 * 数据源配置表 服务实现类
 *
 * @author Chill
 */
@Service
public class DatasourceServiceImpl extends BaseServiceImpl<DatasourceMapper, Datasource> implements IDatasourceService {
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/ModelPrototypeServiceImpl.java
New file
@@ -0,0 +1,55 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service.impl;
import cn.gistack.develop.entity.ModelPrototype;
import cn.gistack.develop.mapper.ModelPrototypeMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.mp.base.BaseServiceImpl;
import cn.gistack.develop.service.IModelPrototypeService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 * 数据原型表 服务实现类
 *
 * @author Chill
 */
@Service
public class ModelPrototypeServiceImpl extends BaseServiceImpl<ModelPrototypeMapper, ModelPrototype> implements IModelPrototypeService {
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean submitList(List<ModelPrototype> modelPrototypes) {
        modelPrototypes.forEach(modelPrototype -> {
            if (modelPrototype.getId() == null) {
                this.save(modelPrototype);
            } else {
                this.updateById(modelPrototype);
            }
        });
        return true;
    }
    @Override
    public List<ModelPrototype> prototypeList(Long modelId) {
        return this.list(Wrappers.<ModelPrototype>lambdaQuery().eq(ModelPrototype::getModelId, modelId));
    }
}
skjcmanager-ops/skjcmanager-develop/src/main/java/cn/gistack/develop/service/impl/ModelServiceImpl.java
New file
@@ -0,0 +1,33 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.develop.service.impl;
import cn.gistack.develop.entity.Model;
import cn.gistack.develop.mapper.ModelMapper;
import org.springblade.core.mp.base.BaseServiceImpl;
import cn.gistack.develop.service.IModelService;
import org.springframework.stereotype.Service;
/**
 * 数据模型表 服务实现类
 *
 * @author Chill
 */
@Service
public class ModelServiceImpl extends BaseServiceImpl<ModelMapper, Model> implements IModelService {
}
skjcmanager-ops/skjcmanager-develop/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,10 @@
#服务器端口
server:
  port: 7007
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.dev.url}
    username: ${blade.datasource.dev.username}
    password: ${blade.datasource.dev.password}
skjcmanager-ops/skjcmanager-develop/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,11 @@
#服务器端口
server:
  port: 7007
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.prod.url}
    username: ${blade.datasource.prod.username}
    password: ${blade.datasource.prod.password}
skjcmanager-ops/skjcmanager-develop/src/main/resources/application-test.yml
New file
@@ -0,0 +1,10 @@
#服务器端口
server:
  port: 7007
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.test.url}
    username: ${blade.datasource.test.username}
    password: ${blade.datasource.test.password}
skjcmanager-ops/skjcmanager-develop/src/main/resources/templates/code.properties
New file
@@ -0,0 +1,5 @@
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
author=BladeX
skjcmanager-ops/skjcmanager-develop/src/test/java/cn/gistack/test/CodeGenerator.java
New file
@@ -0,0 +1,95 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.test;
import org.springblade.develop.constant.DevelopConstant;
import org.springblade.develop.support.BladeCodeGenerator;
/**
 * 代码生成器
 *
 * @author Chill
 */
public class CodeGenerator {
    /**
     * 代码生成的模块名
     */
    public static String CODE_NAME = "资源管理";
    /**
     * 代码所在服务名
     */
    public static String SERVICE_NAME = "blade-develop";
    /**
     * 代码生成的包名
     */
    public static String PACKAGE_NAME = "org.springblade.develop";
    /**
     * 前端代码生成风格
     */
    public static String CODE_STYLE = DevelopConstant.SABER_NAME;
    /**
     * 前端代码生成地址
     */
    public static String PACKAGE_WEB_DIR = "/Users/chill/Workspaces/product/Saber";
    /**
     * 需要去掉的表前缀
     */
    public static String[] TABLE_PREFIX = {"blade_"};
    /**
     * 需要生成的表名(两者只能取其一)
     */
    public static String[] INCLUDE_TABLES = {"blade_datasource"};
    /**
     * 需要排除的表名(两者只能取其一)
     */
    public static String[] EXCLUDE_TABLES = {};
    /**
     * 是否包含基础业务字段
     */
    public static Boolean HAS_SUPER_ENTITY = Boolean.TRUE;
    /**
     * 是否包含远程调用
     */
    private static Boolean HAS_FEIGN = Boolean.TRUE;
    /**
     * 基础业务字段
     */
    public static String[] SUPER_ENTITY_COLUMNS = {"id", "create_time", "create_user", "create_dept", "update_time", "update_user", "status", "is_deleted"};
    /**
     * RUN THIS
     */
    public static void main(String[] args) {
        BladeCodeGenerator generator = new BladeCodeGenerator();
        generator.setCodeName(CODE_NAME);
        generator.setServiceName(SERVICE_NAME);
        generator.setCodeStyle(CODE_STYLE);
        generator.setPackageName(PACKAGE_NAME);
        generator.setPackageWebDir(PACKAGE_WEB_DIR);
        generator.setTablePrefix(TABLE_PREFIX);
        generator.setIncludeTables(INCLUDE_TABLES);
        generator.setExcludeTables(EXCLUDE_TABLES);
        generator.setHasSuperEntity(HAS_SUPER_ENTITY);
        generator.setHasFeign(HAS_FEIGN);
        generator.setSuperEntityColumns(SUPER_ENTITY_COLUMNS);
        generator.run();
    }
}
skjcmanager-ops/skjcmanager-develop/src/test/resources/templates/code.properties
New file
@@ -0,0 +1,5 @@
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
author=BladeX
skjcmanager-ops/skjcmanager-flow/Dockerfile
New file
@@ -0,0 +1,15 @@
FROM bladex/alpine-java:8_server-jre_cn_unlimited
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/flow
WORKDIR /blade/flow
EXPOSE 8008
ADD ./target/blade-flow.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]
skjcmanager-ops/skjcmanager-flow/doc/nacos/blade-flow-dev.yaml
New file
@@ -0,0 +1,39 @@
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #driver-class-name: org.postgresql.Driver
    #driver-class-name: oracle.jdbc.OracleDriver
    #driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    #driver-class-name: dm.jdbc.driver.DmDriver
    druid:
      # MySql、PostgreSQL、SqlServer、DaMeng校验
      validation-query: select 1
      # Oracle校验
      #validation-query: select 1 from dual
#项目模块集中配置
blade:
  #工作流模块开发生产环境数据库地址
  datasource:
    flow:
      dev:
        # MySql
        url: jdbc:mysql://localhost:3306/bladex_flow?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
        username: root
        password: root
        # PostgreSQL
        #url: jdbc:postgresql://127.0.0.1:5432/bladex_flow
        #username: postgres
        #password: 123456
        # Oracle
        #url: jdbc:oracle:thin:@127.0.0.1:1521:orcl
        #username: BLADEX_FLOW
        #password: BLADEX_FLOW
        # SqlServer
        #url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=bladex_flow
        #username: bladex_flow
        #password: bladex_flow
        # DaMeng
        #url: jdbc:dm://127.0.0.1:5236/BLADEX_FLOW?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
        #username: BLADEX_FLOW
        #password: BLADEX_FLOW
skjcmanager-ops/skjcmanager-flow/pom.xml
New file
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-flow</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <!-- Blade -->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-boot</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-swagger</artifactId>
        </dependency>
        <!--<dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-transaction</artifactId>
        </dependency>-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-dict-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-scope-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-auto</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-user-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-flow-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <!-- 工作流 -->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-flowable</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/FlowApplication.java
New file
@@ -0,0 +1,37 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
/**
 * Flowable启动器
 *
 * @author Chill
 */
//@SeataCloudApplication
@BladeCloudApplication
public class FlowApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_FLOW_NAME, FlowApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/controller/WorkController.java
New file
@@ -0,0 +1,147 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.business.controller;
import cn.gistack.flow.business.service.FlowBusinessService;
import cn.gistack.flow.core.entity.BladeFlow;
import cn.gistack.flow.core.utils.TaskUtil;
import cn.gistack.flow.engine.entity.FlowProcess;
import cn.gistack.flow.engine.service.FlowEngineService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.flowable.engine.TaskService;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.*;
/**
 * 流程事务通用接口
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("work")
@Api(value = "流程事务通用接口", tags = "流程事务通用接口")
public class WorkController {
    private final TaskService taskService;
    private final FlowEngineService flowEngineService;
    private final FlowBusinessService flowBusinessService;
    /**
     * 发起事务列表页
     */
    @GetMapping("start-list")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "发起事务列表页", notes = "传入流程类型")
    public R<IPage<FlowProcess>> startList(@ApiParam("流程类型") String category, Query query, @RequestParam(required = false, defaultValue = "1") Integer mode) {
        IPage<FlowProcess> pages = flowEngineService.selectProcessPage(Condition.getPage(query), category, mode);
        return R.data(pages);
    }
    /**
     * 待签事务列表页
     */
    @GetMapping("claim-list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "待签事务列表页", notes = "传入流程信息")
    public R<IPage<BladeFlow>> claimList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
        IPage<BladeFlow> pages = flowBusinessService.selectClaimPage(Condition.getPage(query), bladeFlow);
        return R.data(pages);
    }
    /**
     * 待办事务列表页
     */
    @GetMapping("todo-list")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "待办事务列表页", notes = "传入流程信息")
    public R<IPage<BladeFlow>> todoList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
        IPage<BladeFlow> pages = flowBusinessService.selectTodoPage(Condition.getPage(query), bladeFlow);
        return R.data(pages);
    }
    /**
     * 已发事务列表页
     */
    @GetMapping("send-list")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "已发事务列表页", notes = "传入流程信息")
    public R<IPage<BladeFlow>> sendList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
        IPage<BladeFlow> pages = flowBusinessService.selectSendPage(Condition.getPage(query), bladeFlow);
        return R.data(pages);
    }
    /**
     * 办结事务列表页
     */
    @GetMapping("done-list")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "办结事务列表页", notes = "传入流程信息")
    public R<IPage<BladeFlow>> doneList(@ApiParam("流程信息") BladeFlow bladeFlow, Query query) {
        IPage<BladeFlow> pages = flowBusinessService.selectDonePage(Condition.getPage(query), bladeFlow);
        return R.data(pages);
    }
    /**
     * 签收事务
     *
     * @param taskId 任务id
     */
    @PostMapping("claim-task")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "签收事务", notes = "传入流程信息")
    public R claimTask(@ApiParam("任务id") String taskId) {
        taskService.claim(taskId, TaskUtil.getTaskUser());
        return R.success("签收事务成功");
    }
    /**
     * 完成任务
     *
     * @param flow 请假信息
     */
    @PostMapping("complete-task")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "完成任务", notes = "传入流程信息")
    public R completeTask(@ApiParam("任务信息") @RequestBody BladeFlow flow) {
        return R.status(flowBusinessService.completeTask(flow));
    }
    /**
     * 删除任务
     *
     * @param taskId 任务id
     * @param reason 删除原因
     */
    @PostMapping("delete-task")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "删除任务", notes = "传入流程信息")
    public R deleteTask(@ApiParam("任务id") String taskId, @ApiParam("删除原因") String reason) {
        taskService.deleteTask(taskId, reason);
        return R.success("删除任务成功");
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/feign/FlowClient.java
New file
@@ -0,0 +1,107 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.business.feign;
import cn.gistack.flow.core.entity.BladeFlow;
import cn.gistack.flow.core.feign.IFlowClient;
import cn.gistack.flow.core.utils.TaskUtil;
import lombok.AllArgsConstructor;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.web.bind.annotation.GetMapping;
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;
/**
 * 流程远程调用实现类
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
public class FlowClient implements IFlowClient {
    private final RuntimeService runtimeService;
    private final IdentityService identityService;
    private final TaskService taskService;
    @Override
    @PostMapping(START_PROCESS_INSTANCE_BY_ID)
    public R<BladeFlow> startProcessInstanceById(String processDefinitionId, String businessKey, @RequestBody Map<String, Object> variables) {
        // 设置流程启动用户
        identityService.setAuthenticatedUserId(TaskUtil.getTaskUser());
        // 开启流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables);
        // 组装流程通用类
        BladeFlow flow = new BladeFlow();
        flow.setProcessInstanceId(processInstance.getId());
        return R.data(flow);
    }
    @Override
    @PostMapping(START_PROCESS_INSTANCE_BY_KEY)
    public R<BladeFlow> startProcessInstanceByKey(String processDefinitionKey, String businessKey, @RequestBody Map<String, Object> variables) {
        // 设置流程启动用户
        identityService.setAuthenticatedUserId(TaskUtil.getTaskUser());
        // 开启流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
        // 组装流程通用类
        BladeFlow flow = new BladeFlow();
        flow.setProcessInstanceId(processInstance.getId());
        return R.data(flow);
    }
    @Override
    @PostMapping(COMPLETE_TASK)
    public R completeTask(String taskId, String processInstanceId, String comment, @RequestBody Map<String, Object> variables) {
        // 增加评论
        if (StringUtil.isNoneBlank(processInstanceId, comment)) {
            taskService.addComment(taskId, processInstanceId, comment);
        }
        // 非空判断
        if (Func.isEmpty(variables)) {
            variables = Kv.create();
        }
        // 完成任务
        taskService.complete(taskId, variables);
        return R.success("流程提交成功");
    }
    @Override
    @GetMapping(TASK_VARIABLE)
    public R<Object> taskVariable(String taskId, String variableName) {
        return R.data(taskService.getVariable(taskId, variableName));
    }
    @Override
    @GetMapping(TASK_VARIABLES)
    public R<Map<String, Object>> taskVariables(String taskId) {
        return R.data(taskService.getVariables(taskId));
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/service/FlowBusinessService.java
New file
@@ -0,0 +1,72 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.business.service;
import cn.gistack.flow.core.entity.BladeFlow;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
 * 流程业务类
 *
 * @author Chill
 */
public interface FlowBusinessService {
    /**
     * 流程待签列表
     *
     * @param page      分页工具
     * @param bladeFlow 流程类
     * @return
     */
    IPage<BladeFlow> selectClaimPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
    /**
     * 流程待办列表
     *
     * @param page      分页工具
     * @param bladeFlow 流程类
     * @return
     */
    IPage<BladeFlow> selectTodoPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
    /**
     * 流程已发列表
     *
     * @param page      分页工具
     * @param bladeFlow 流程类
     * @return
     */
    IPage<BladeFlow> selectSendPage(IPage<BladeFlow> page, BladeFlow bladeFlow);
    /**
     * 流程办结列表
     *
     * @param page      分页工具
     * @param bladeFlow 流程类
     * @return
     */
    IPage<BladeFlow> selectDonePage(IPage<BladeFlow> page, BladeFlow bladeFlow);
    /**
     * 完成任务
     *
     * @param leave 请假信息
     * @return boolean
     */
    boolean completeTask(BladeFlow leave);
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/business/service/impl/FlowBusinessServiceImpl.java
New file
@@ -0,0 +1,333 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.business.service.impl;
import cn.gistack.flow.business.service.FlowBusinessService;
import cn.gistack.flow.core.constant.ProcessConstant;
import cn.gistack.flow.core.entity.BladeFlow;
import cn.gistack.flow.core.utils.TaskUtil;
import cn.gistack.flow.engine.constant.FlowEngineConstant;
import cn.gistack.flow.engine.entity.FlowProcess;
import cn.gistack.flow.engine.utils.FlowCache;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Service;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
 * 流程业务实现类
 *
 * @author Chill
 */
@Service
@AllArgsConstructor
public class FlowBusinessServiceImpl implements FlowBusinessService {
    private final TaskService taskService;
    private final HistoryService historyService;
    @Override
    public IPage<BladeFlow> selectClaimPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
        String taskUser = TaskUtil.getTaskUser();
        String taskGroup = TaskUtil.getCandidateGroup();
        List<BladeFlow> flowList = new LinkedList<>();
        // 个人等待签收的任务
        TaskQuery claimUserQuery = taskService.createTaskQuery().taskCandidateUser(taskUser)
            .includeProcessVariables().active().orderByTaskCreateTime().desc();
        // 定制流程等待签收的任务
        TaskQuery claimRoleWithTenantIdQuery = taskService.createTaskQuery().taskTenantId(AuthUtil.getTenantId()).taskCandidateGroupIn(Func.toStrList(taskGroup))
            .includeProcessVariables().active().orderByTaskCreateTime().desc();
        // 通用流程等待签收的任务
        TaskQuery claimRoleWithoutTenantIdQuery = taskService.createTaskQuery().taskWithoutTenantId().taskCandidateGroupIn(Func.toStrList(taskGroup))
            .includeProcessVariables().active().orderByTaskCreateTime().desc();
        // 构建列表数据
        buildFlowTaskList(bladeFlow, flowList, claimUserQuery, FlowEngineConstant.STATUS_CLAIM);
        buildFlowTaskList(bladeFlow, flowList, claimRoleWithTenantIdQuery, FlowEngineConstant.STATUS_CLAIM);
        buildFlowTaskList(bladeFlow, flowList, claimRoleWithoutTenantIdQuery, FlowEngineConstant.STATUS_CLAIM);
        // 计算总数
        long count = claimUserQuery.count() + claimRoleWithTenantIdQuery.count() + claimRoleWithoutTenantIdQuery.count();
        // 设置页数
        page.setSize(count);
        // 设置总数
        page.setTotal(count);
        // 设置数据
        page.setRecords(flowList);
        return page;
    }
    @Override
    public IPage<BladeFlow> selectTodoPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
        String taskUser = TaskUtil.getTaskUser();
        List<BladeFlow> flowList = new LinkedList<>();
        // 已签收的任务
        TaskQuery todoQuery = taskService.createTaskQuery().taskAssignee(taskUser).active()
            .includeProcessVariables().orderByTaskCreateTime().desc();
        // 构建列表数据
        buildFlowTaskList(bladeFlow, flowList, todoQuery, FlowEngineConstant.STATUS_TODO);
        // 计算总数
        long count = todoQuery.count();
        // 设置页数
        page.setSize(count);
        // 设置总数
        page.setTotal(count);
        // 设置数据
        page.setRecords(flowList);
        return page;
    }
    @Override
    public IPage<BladeFlow> selectSendPage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
        String taskUser = TaskUtil.getTaskUser();
        List<BladeFlow> flowList = new LinkedList<>();
        HistoricProcessInstanceQuery historyQuery = historyService.createHistoricProcessInstanceQuery().startedBy(taskUser).orderByProcessInstanceStartTime().desc();
        if (bladeFlow.getCategory() != null) {
            historyQuery.processDefinitionCategory(bladeFlow.getCategory());
        }
        if (bladeFlow.getProcessDefinitionName() != null) {
            historyQuery.processDefinitionName(bladeFlow.getProcessDefinitionName());
        }
        if (bladeFlow.getBeginDate() != null) {
            historyQuery.startedAfter(bladeFlow.getBeginDate());
        }
        if (bladeFlow.getEndDate() != null) {
            historyQuery.startedBefore(bladeFlow.getEndDate());
        }
        // 查询列表
        List<HistoricProcessInstance> historyList = historyQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
        historyList.forEach(historicProcessInstance -> {
            BladeFlow flow = new BladeFlow();
            // historicProcessInstance
            flow.setCreateTime(historicProcessInstance.getStartTime());
            flow.setEndTime(historicProcessInstance.getEndTime());
            flow.setVariables(historicProcessInstance.getProcessVariables());
            String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
            if (businessKey.length > 1) {
                flow.setBusinessTable(businessKey[0]);
                flow.setBusinessId(businessKey[1]);
            }
            flow.setHistoryActivityName(historicProcessInstance.getName());
            flow.setProcessInstanceId(historicProcessInstance.getId());
            flow.setHistoryProcessInstanceId(historicProcessInstance.getId());
            // ProcessDefinition
            FlowProcess processDefinition = FlowCache.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
            flow.setProcessDefinitionId(processDefinition.getId());
            flow.setProcessDefinitionName(processDefinition.getName());
            flow.setProcessDefinitionVersion(processDefinition.getVersion());
            flow.setProcessDefinitionKey(processDefinition.getKey());
            flow.setCategory(processDefinition.getCategory());
            flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
            flow.setProcessInstanceId(historicProcessInstance.getId());
            // HistoricTaskInstance
            List<HistoricTaskInstance> historyTasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(historicProcessInstance.getId()).orderByHistoricTaskInstanceEndTime().desc().list();
            if (Func.isNotEmpty(historyTasks)) {
                HistoricTaskInstance historyTask = historyTasks.iterator().next();
                flow.setTaskId(historyTask.getId());
                flow.setTaskName(historyTask.getName());
                flow.setTaskDefinitionKey(historyTask.getTaskDefinitionKey());
            }
            // Status
            if (historicProcessInstance.getEndActivityId() != null) {
                flow.setProcessIsFinished(FlowEngineConstant.STATUS_FINISHED);
            } else {
                flow.setProcessIsFinished(FlowEngineConstant.STATUS_UNFINISHED);
            }
            flow.setStatus(FlowEngineConstant.STATUS_FINISH);
            flowList.add(flow);
        });
        // 计算总数
        long count = historyQuery.count();
        // 设置总数
        page.setTotal(count);
        page.setRecords(flowList);
        return page;
    }
    @Override
    public IPage<BladeFlow> selectDonePage(IPage<BladeFlow> page, BladeFlow bladeFlow) {
        String taskUser = TaskUtil.getTaskUser();
        List<BladeFlow> flowList = new LinkedList<>();
        HistoricTaskInstanceQuery doneQuery = historyService.createHistoricTaskInstanceQuery().taskAssignee(taskUser).finished()
            .includeProcessVariables().orderByHistoricTaskInstanceEndTime().desc();
        if (bladeFlow.getCategory() != null) {
            doneQuery.processCategoryIn(Func.toStrList(bladeFlow.getCategory()));
        }
        if (bladeFlow.getProcessDefinitionName() != null) {
            doneQuery.processDefinitionName(bladeFlow.getProcessDefinitionName());
        }
        if (bladeFlow.getBeginDate() != null) {
            doneQuery.taskCompletedAfter(bladeFlow.getBeginDate());
        }
        if (bladeFlow.getEndDate() != null) {
            doneQuery.taskCompletedBefore(bladeFlow.getEndDate());
        }
        // 查询列表
        List<HistoricTaskInstance> doneList = doneQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
        doneList.forEach(historicTaskInstance -> {
            BladeFlow flow = new BladeFlow();
            flow.setTaskId(historicTaskInstance.getId());
            flow.setTaskDefinitionKey(historicTaskInstance.getTaskDefinitionKey());
            flow.setTaskName(historicTaskInstance.getName());
            flow.setAssignee(historicTaskInstance.getAssignee());
            flow.setCreateTime(historicTaskInstance.getCreateTime());
            flow.setExecutionId(historicTaskInstance.getExecutionId());
            flow.setHistoryTaskEndTime(historicTaskInstance.getEndTime());
            flow.setVariables(historicTaskInstance.getProcessVariables());
            FlowProcess processDefinition = FlowCache.getProcessDefinition(historicTaskInstance.getProcessDefinitionId());
            flow.setProcessDefinitionId(processDefinition.getId());
            flow.setProcessDefinitionName(processDefinition.getName());
            flow.setProcessDefinitionKey(processDefinition.getKey());
            flow.setProcessDefinitionVersion(processDefinition.getVersion());
            flow.setCategory(processDefinition.getCategory());
            flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
            flow.setProcessInstanceId(historicTaskInstance.getProcessInstanceId());
            flow.setHistoryProcessInstanceId(historicTaskInstance.getProcessInstanceId());
            HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance((historicTaskInstance.getProcessInstanceId()));
            if (Func.isNotEmpty(historicProcessInstance)) {
                String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
                flow.setBusinessTable(businessKey[0]);
                flow.setBusinessId(businessKey[1]);
                if (historicProcessInstance.getEndActivityId() != null) {
                    flow.setProcessIsFinished(FlowEngineConstant.STATUS_FINISHED);
                } else {
                    flow.setProcessIsFinished(FlowEngineConstant.STATUS_UNFINISHED);
                }
            }
            flow.setStatus(FlowEngineConstant.STATUS_FINISH);
            flowList.add(flow);
        });
        // 计算总数
        long count = doneQuery.count();
        // 设置总数
        page.setTotal(count);
        page.setRecords(flowList);
        return page;
    }
    @Override
    public boolean completeTask(BladeFlow flow) {
        String taskId = flow.getTaskId();
        String processInstanceId = flow.getProcessInstanceId();
        String comment = Func.toStr(flow.getComment(), ProcessConstant.PASS_COMMENT);
        // 增加评论
        if (StringUtil.isNoneBlank(processInstanceId, comment)) {
            taskService.addComment(taskId, processInstanceId, comment);
        }
        // 创建变量
        Map<String, Object> variables = flow.getVariables();
        if (variables == null) {
            variables = Kv.create();
        }
        variables.put(ProcessConstant.PASS_KEY, flow.isPass());
        // 完成任务
        taskService.complete(taskId, variables);
        return true;
    }
    /**
     * 构建流程
     *
     * @param bladeFlow 流程通用类
     * @param flowList  流程列表
     * @param taskQuery 任务查询类
     * @param status    状态
     */
    private void buildFlowTaskList(BladeFlow bladeFlow, List<BladeFlow> flowList, TaskQuery taskQuery, String status) {
        if (bladeFlow.getCategory() != null) {
            taskQuery.processCategoryIn(Func.toStrList(bladeFlow.getCategory()));
        }
        if (bladeFlow.getProcessDefinitionName() != null) {
            taskQuery.processDefinitionName(bladeFlow.getProcessDefinitionName());
        }
        if (bladeFlow.getBeginDate() != null) {
            taskQuery.taskCreatedAfter(bladeFlow.getBeginDate());
        }
        if (bladeFlow.getEndDate() != null) {
            taskQuery.taskCreatedBefore(bladeFlow.getEndDate());
        }
        taskQuery.list().forEach(task -> {
            BladeFlow flow = new BladeFlow();
            flow.setTaskId(task.getId());
            flow.setTaskDefinitionKey(task.getTaskDefinitionKey());
            flow.setTaskName(task.getName());
            flow.setAssignee(task.getAssignee());
            flow.setCreateTime(task.getCreateTime());
            flow.setClaimTime(task.getClaimTime());
            flow.setExecutionId(task.getExecutionId());
            flow.setVariables(task.getProcessVariables());
            HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId());
            if (Func.isNotEmpty(historicProcessInstance)) {
                String[] businessKey = Func.toStrArray(StringPool.COLON, historicProcessInstance.getBusinessKey());
                flow.setBusinessTable(businessKey[0]);
                flow.setBusinessId(businessKey[1]);
            }
            FlowProcess processDefinition = FlowCache.getProcessDefinition(task.getProcessDefinitionId());
            flow.setCategory(processDefinition.getCategory());
            flow.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
            flow.setProcessDefinitionId(processDefinition.getId());
            flow.setProcessDefinitionName(processDefinition.getName());
            flow.setProcessDefinitionKey(processDefinition.getKey());
            flow.setProcessDefinitionVersion(processDefinition.getVersion());
            flow.setProcessInstanceId(task.getProcessInstanceId());
            flow.setStatus(status);
            flowList.add(flow);
        });
    }
    /**
     * 获取历史流程
     *
     * @param processInstanceId 流程实例id
     * @return HistoricProcessInstance
     */
    private HistoricProcessInstance getHistoricProcessInstance(String processInstanceId) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/config/FlowableConfiguration.java
New file
@@ -0,0 +1,44 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.config;
import lombok.AllArgsConstructor;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.flowable.spring.boot.FlowableProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * Flowable配置类
 *
 * @author Chill
 */
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
@EnableConfigurationProperties(FlowableProperties.class)
public class FlowableConfiguration implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
    private final FlowableProperties flowableProperties;
    @Override
    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
        engineConfiguration.setActivityFontName(flowableProperties.getActivityFontName());
        engineConfiguration.setLabelFontName(flowableProperties.getLabelFontName());
        engineConfiguration.setAnnotationFontName(flowableProperties.getAnnotationFontName());
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/constant/FlowEngineConstant.java
New file
@@ -0,0 +1,52 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.constant;
/**
 * 流程常量.
 *
 * @author zhuangqian
 */
public interface FlowEngineConstant {
    String FLOWABLE_BASE_PACKAGES = "org.flowable.ui";
    String SUFFIX = ".bpmn20.xml";
    String ACTIVE = "active";
    String SUSPEND = "suspend";
    String STATUS_TODO = "todo";
    String STATUS_CLAIM = "claim";
    String STATUS_SEND = "send";
    String STATUS_DONE = "done";
    String STATUS_FINISHED = "finished";
    String STATUS_UNFINISHED = "unfinished";
    String STATUS_FINISH = "finish";
    String START_EVENT = "startEvent";
    String END_EVENT = "endEvent";
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowFollowController.java
New file
@@ -0,0 +1,70 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.controller;
import cn.gistack.flow.engine.entity.FlowExecution;
import cn.gistack.flow.engine.service.FlowEngineService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springframework.web.bind.annotation.*;
/**
 * 流程状态控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@RequestMapping("follow")
@AllArgsConstructor
@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
public class FlowFollowController {
    private final FlowEngineService flowEngineService;
    /**
     * 流程状态列表
     */
    @GetMapping("list")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "分页", notes = "传入notice")
    public R<IPage<FlowExecution>> list(Query query, @ApiParam(value = "流程实例id") String processInstanceId, @ApiParam(value = "流程key") String processDefinitionKey) {
        IPage<FlowExecution> pages = flowEngineService.selectFollowPage(Condition.getPage(query), processInstanceId, processDefinitionKey);
        return R.data(pages);
    }
    /**
     * 删除流程实例
     */
    @PostMapping("delete-process-instance")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "删除", notes = "传入主键集合")
    public R deleteProcessInstance(@ApiParam(value = "流程实例id") @RequestParam String processInstanceId, @ApiParam(value = "删除原因") @RequestParam String deleteReason) {
        boolean temp = flowEngineService.deleteProcessInstance(processInstanceId, deleteReason);
        return R.status(temp);
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowManagerController.java
New file
@@ -0,0 +1,123 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.controller;
import cn.gistack.flow.engine.constant.FlowEngineConstant;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import cn.gistack.flow.engine.entity.FlowProcess;
import cn.gistack.flow.engine.service.FlowEngineService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Objects;
/**
 * 流程管理接口
 *
 * @author Chill
 */
@NonDS
@RestController
@RequestMapping("manager")
@AllArgsConstructor
@Api(value = "流程管理接口", tags = "流程管理接口")
@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
public class FlowManagerController {
    private final FlowEngineService flowEngineService;
    /**
     * 分页
     */
    @GetMapping("list")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "分页", notes = "传入流程类型")
    public R<IPage<FlowProcess>> list(@ApiParam("流程类型") String category, Query query, @RequestParam(required = false, defaultValue = "1") Integer mode) {
        IPage<FlowProcess> pages = flowEngineService.selectProcessPage(Condition.getPage(query), category, mode);
        return R.data(pages);
    }
    /**
     * 变更流程状态
     *
     * @param state     状态
     * @param processId 流程id
     */
    @PostMapping("change-state")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "变更流程状态", notes = "传入state,processId")
    public R changeState(@RequestParam String state, @RequestParam String processId) {
        String msg = flowEngineService.changeState(state, processId);
        return R.success(msg);
    }
    /**
     * 删除部署流程
     *
     * @param deploymentIds 部署流程id集合
     */
    @PostMapping("delete-deployment")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "删除部署流程", notes = "部署流程id集合")
    public R deleteDeployment(String deploymentIds) {
        return R.status(flowEngineService.deleteDeployment(deploymentIds));
    }
    /**
     * 检查流程文件格式
     *
     * @param file 流程文件
     */
    @PostMapping("check-upload")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "上传部署流程文件", notes = "传入文件")
    public R checkUpload(@RequestParam MultipartFile file) {
        boolean temp = Objects.requireNonNull(file.getOriginalFilename()).endsWith(FlowEngineConstant.SUFFIX);
        return R.data(Kv.create().set("name", file.getOriginalFilename()).set("success", temp));
    }
    /**
     * 上传部署流程文件
     *
     * @param files    流程文件
     * @param category 类型
     */
    @PostMapping("deploy-upload")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "上传部署流程文件", notes = "传入文件")
    public R deployUpload(@RequestParam List<MultipartFile> files,
                          @RequestParam String category,
                          @RequestParam(required = false, defaultValue = "") String tenantIds) {
        return R.status(flowEngineService.deployUpload(files, category, Func.toStrList(tenantIds)));
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowModelController.java
New file
@@ -0,0 +1,118 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.Func;
import cn.gistack.flow.engine.entity.FlowModel;
import cn.gistack.flow.engine.service.FlowEngineService;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
/**
 * 流程模型控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@RequestMapping("model")
@AllArgsConstructor
@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
public class FlowModelController {
    private final FlowEngineService flowEngineService;
    /**
     * 分页
     */
    @GetMapping("/list")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "modelKey", value = "模型标识", paramType = "query", dataType = "string"),
        @ApiImplicitParam(name = "name", value = "模型名称", paramType = "query", dataType = "string")
    })
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "分页", notes = "传入notice")
    public R<IPage<FlowModel>> list(@ApiIgnore @RequestParam Map<String, Object> flow, Query query) {
        IPage<FlowModel> pages = flowEngineService.page(Condition.getPage(query), Condition.getQueryWrapper(flow, FlowModel.class)
            .select("id,model_key modelKey,name,description,version,created,last_updated lastUpdated")
            .orderByDesc("last_updated"));
        return R.data(pages);
    }
    /**
     * 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "删除", notes = "传入主键集合")
    public R remove(@ApiParam(value = "主键集合") @RequestParam String ids) {
        boolean temp = flowEngineService.removeByIds(Func.toStrList(ids));
        return R.status(temp);
    }
    /**
     * 部署
     */
    @PostMapping("/deploy")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "部署", notes = "传入模型id和分类")
    public R deploy(@ApiParam(value = "模型id") @RequestParam String modelId,
                    @ApiParam(value = "工作流分类") @RequestParam String category,
                    @ApiParam(value = "租户ID") @RequestParam(required = false, defaultValue = "") String tenantIds) {
        boolean temp = flowEngineService.deployModel(modelId, category, Func.toStrList(tenantIds));
        return R.status(temp);
    }
    @PostMapping("submit")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "保存/编辑")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "模型id"),
        @ApiImplicitParam(name = "name", value = "模型名称", required = true),
        @ApiImplicitParam(name = "modelKey", value = "模型key", required = true),
        @ApiImplicitParam(name = "description", value = "模型描述"),
        @ApiImplicitParam(name = "xml", value = "模型xml", required = true),
    })
    public R<FlowModel> submit(@RequestBody @ApiIgnore FlowModel model) {
        return R.data(flowEngineService.submitModel(model));
    }
    @GetMapping("detail")
    @ApiOperation(value = "详情")
    @ApiOperationSupport(order = 5)
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "模型id", required = true),
    })
    public R<FlowModel> detail(String id) {
        return R.data(flowEngineService.getById(id));
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/controller/FlowProcessController.java
New file
@@ -0,0 +1,96 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.controller;
import cn.gistack.flow.core.entity.BladeFlow;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import cn.gistack.flow.engine.service.FlowEngineService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * 流程通用控制器
 *
 * @author Chill
 */
@NonDS
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("process")
public class FlowProcessController {
    private static final String IMAGE_NAME = "image";
    private final FlowEngineService flowEngineService;
    /**
     * 获取流转历史列表
     *
     * @param processInstanceId 流程实例id
     * @param startActivityId   开始节点id
     * @param endActivityId     结束节点id
     */
    @GetMapping(value = "history-flow-list")
    public R<List<BladeFlow>> historyFlowList(@RequestParam String processInstanceId, String startActivityId, String endActivityId) {
        return R.data(flowEngineService.historyFlowList(processInstanceId, startActivityId, endActivityId));
    }
    /**
     * 流程节点进程图
     *
     * @param processDefinitionId 流程id
     * @param processInstanceId   流程实例id
     */
    @GetMapping(value = "model-view")
    public R modelView(String processDefinitionId, String processInstanceId) {
        return R.data(flowEngineService.modelView(processDefinitionId, processInstanceId));
    }
    /**
     * 流程节点进程图
     *
     * @param processInstanceId   流程实例id
     * @param httpServletResponse http响应
     */
    @GetMapping(value = "diagram-view")
    public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) {
        flowEngineService.diagramView(processInstanceId, httpServletResponse);
    }
    /**
     * 流程图展示
     *
     * @param processDefinitionId 流程id
     * @param processInstanceId   实例id
     * @param resourceType        资源类型
     * @param response            响应
     */
    @GetMapping("resource-view")
    public void resourceView(@RequestParam String processDefinitionId, String processInstanceId, @RequestParam(defaultValue = IMAGE_NAME) String resourceType, HttpServletResponse response) {
        flowEngineService.resourceView(processDefinitionId, processInstanceId, resourceType, response);
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/entity/FlowExecution.java
New file
@@ -0,0 +1,50 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * 运行实体类
 *
 * @author Chill
 */
@Data
public class FlowExecution implements Serializable {
    private static final long serialVersionUID = 1L;
    private String id;
    private String name;
    private String startUserId;
    private String startUser;
    private Date startTime;
    private String taskDefinitionId;
    private String taskDefinitionKey;
    private String category;
    private String categoryName;
    private String processInstanceId;
    private String processDefinitionId;
    private String processDefinitionKey;
    private String activityId;
    private int suspensionState;
    private String executionId;
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/entity/FlowModel.java
New file
@@ -0,0 +1,58 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * 流程模型
 *
 * @author Chill
 */
@Data
@TableName("ACT_DE_MODEL")
public class FlowModel implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final int MODEL_TYPE_BPMN = 0;
    public static final int MODEL_TYPE_FORM = 2;
    public static final int MODEL_TYPE_APP = 3;
    public static final int MODEL_TYPE_DECISION_TABLE = 4;
    public static final int MODEL_TYPE_CMMN = 5;
    private String id;
    private String name;
    private String modelKey;
    private String description;
    private Date created;
    private Date lastUpdated;
    private String createdBy;
    private String lastUpdatedBy;
    private Integer version;
    private String modelEditorJson;
    private String modelComment;
    private Integer modelType;
    private String tenantId;
    private byte[] thumbnail;
    private String modelEditorXml;
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/entity/FlowProcess.java
New file
@@ -0,0 +1,65 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.entity;
import cn.gistack.flow.engine.utils.FlowCache;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
import java.io.Serializable;
import java.util.Date;
/**
 * FlowProcess
 *
 * @author Chill
 */
@Data
@NoArgsConstructor
public class FlowProcess implements Serializable {
    private String id;
    private String tenantId;
    private String name;
    private String key;
    private String category;
    private String categoryName;
    private Integer version;
    private String deploymentId;
    private String resourceName;
    private String diagramResourceName;
    private Integer suspensionState;
    private Date deploymentTime;
    public FlowProcess(ProcessDefinitionEntityImpl entity) {
        if (entity != null) {
            this.id = entity.getId();
            this.tenantId = entity.getTenantId();
            this.name = entity.getName();
            this.key = entity.getKey();
            this.category = entity.getCategory();
            this.categoryName = FlowCache.getCategoryName(entity.getCategory());
            this.version = entity.getVersion();
            this.deploymentId = entity.getDeploymentId();
            this.resourceName = entity.getResourceName();
            this.diagramResourceName = entity.getDiagramResourceName();
            this.suspensionState = entity.getSuspensionState();
        }
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/mapper/FlowMapper.java
New file
@@ -0,0 +1,46 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.mapper;
import cn.gistack.flow.engine.entity.FlowModel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
 * FlowMapper.
 *
 * @author Chill
 */
public interface FlowMapper extends BaseMapper<FlowModel> {
    /**
     * 自定义分页
     * @param page
     * @param flowModel
     * @return
     */
    List<FlowModel> selectFlowPage(IPage page, FlowModel flowModel);
    /**
     * 获取模型
     * @param parentModelId
     * @return
     */
    List<FlowModel> findByParentModelId(String parentModelId);
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/mapper/FlowMapper.xml
New file
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.flow.engine.mapper.FlowMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="flowModelResultMap" type="cn.gistack.flow.engine.entity.FlowModel">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="model_key" property="modelKey"/>
        <result column="description" property="description"/>
        <result column="model_comment" property="modelComment"/>
        <result column="created" property="created"/>
        <result column="created_by" property="createdBy"/>
        <result column="last_updated" property="lastUpdated"/>
        <result column="last_updated_by" property="lastUpdatedBy"/>
        <result column="version" property="version"/>
        <result column="model_editor_json" property="modelEditorJson"/>
        <result column="thumbnail" property="thumbnail"/>
        <result column="model_type" property="modelType"/>
        <result column="tenant_id" property="tenantId"/>
    </resultMap>
    <select id="selectFlowPage" resultMap="flowModelResultMap">
        SELECT
            a.id,
            a.name,
            a.model_key,
            a.description,
            a.model_comment,
            a.created,
            a.created_by,
            a.last_updated,
            a.last_updated_by,
            a.version,
            a.model_editor_json,
            a.thumbnail,
            a.model_type,
            a.tenant_id
        FROM
            ACT_DE_MODEL a
        WHERE
            1 = 1
        ORDER BY
            a.created DESC
    </select>
    <select id="findByParentModelId" parameterType="string" resultMap="flowModelResultMap">
        select model.* from ACT_DE_MODEL_RELATION modelrelation
                                inner join ACT_DE_MODEL model on modelrelation.model_id = model.id
        where modelrelation.parent_model_id = #{_parameter}
    </select>
</mapper>
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/service/FlowEngineService.java
New file
@@ -0,0 +1,166 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.service;
import cn.gistack.flow.core.entity.BladeFlow;
import cn.gistack.flow.engine.entity.FlowExecution;
import cn.gistack.flow.engine.entity.FlowModel;
import cn.gistack.flow.engine.entity.FlowProcess;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
 * FlowService
 *
 * @author Chill
 */
public interface FlowEngineService extends IService<FlowModel> {
    /**
     * 自定义分页
     *
     * @param page      分页工具
     * @param flowModel 流程模型
     * @return
     */
    IPage<FlowModel> selectFlowPage(IPage<FlowModel> page, FlowModel flowModel);
    /**
     * 流程管理列表
     *
     * @param page     分页工具
     * @param category 分类
     * @param mode     形态
     * @return
     */
    IPage<FlowProcess> selectProcessPage(IPage<FlowProcess> page, String category, Integer mode);
    /**
     * 流程管理列表
     *
     * @param page                 分页工具
     * @param processInstanceId    流程实例id
     * @param processDefinitionKey 流程key
     * @return
     */
    IPage<FlowExecution> selectFollowPage(IPage<FlowExecution> page, String processInstanceId, String processDefinitionKey);
    /**
     * 获取流转历史列表
     *
     * @param processInstanceId 流程实例id
     * @param startActivityId   开始节点id
     * @param endActivityId     结束节点id
     * @return
     */
    List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId);
    /**
     * 变更流程状态
     *
     * @param state     状态
     * @param processId 流程ID
     * @return
     */
    String changeState(String state, String processId);
    /**
     * 删除部署流程
     *
     * @param deploymentIds 部署流程id集合
     * @return
     */
    boolean deleteDeployment(String deploymentIds);
    /**
     * 上传部署流程
     *
     * @param files        流程配置文件
     * @param category     流程分类
     * @param tenantIdList 租户id集合
     * @return
     */
    boolean deployUpload(List<MultipartFile> files, String category, List<String> tenantIdList);
    /**
     * 部署流程
     *
     * @param modelId      模型id
     * @param category     分类
     * @param tenantIdList 租户id集合
     * @return
     */
    boolean deployModel(String modelId, String category, List<String> tenantIdList);
    /**
     * 删除流程实例
     *
     * @param processInstanceId 流程实例id
     * @param deleteReason      删除原因
     * @return
     */
    boolean deleteProcessInstance(String processInstanceId, String deleteReason);
    /**
     * 保存/更新模型
     *
     * @param model 模型
     * @return 模型
     */
    FlowModel submitModel(FlowModel model);
    /**
     * 流程节点进程图
     *
     * @param processDefinitionId
     * @param processInstanceId
     * @return
     */
    Map<String, Object> modelView(String processDefinitionId, String processInstanceId);
    /**
     * 流程节点进程图
     *
     * @param processInstanceId
     * @param httpServletResponse
     */
    void diagramView(String processInstanceId, HttpServletResponse httpServletResponse);
    /**
     * 流程图展示
     *
     * @param processDefinitionId
     * @param processInstanceId
     * @param resourceType
     * @param response
     */
    void resourceView(String processDefinitionId, String processInstanceId, String resourceType, HttpServletResponse response);
    /**
     * 获取XML
     *
     * @param model
     * @return
     */
    byte[] getModelEditorXML(FlowModel model);
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/service/impl/FlowEngineServiceImpl.java
New file
@@ -0,0 +1,559 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.service.impl;
import cn.gistack.flow.core.entity.BladeFlow;
import cn.gistack.flow.core.enums.FlowModeEnum;
import cn.gistack.flow.core.utils.TaskUtil;
import cn.gistack.flow.engine.utils.FlowCache;
import cn.gistack.system.user.cache.UserCache;
import cn.gistack.system.user.entity.User;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.util.IoUtil;
import org.flowable.common.engine.impl.util.io.StringStreamSource;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.engine.task.Comment;
import org.flowable.image.ProcessDiagramGenerator;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.FileUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import cn.gistack.flow.engine.constant.FlowEngineConstant;
import cn.gistack.flow.engine.entity.FlowExecution;
import cn.gistack.flow.engine.entity.FlowModel;
import cn.gistack.flow.engine.entity.FlowProcess;
import cn.gistack.flow.engine.mapper.FlowMapper;
import cn.gistack.flow.engine.service.FlowEngineService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
/**
 * 工作流服务实现类
 *
 * @author Chill
 */
@Slf4j
@Service
@AllArgsConstructor
public class FlowEngineServiceImpl extends ServiceImpl<FlowMapper, FlowModel> implements FlowEngineService {
    private static final String ALREADY_IN_STATE = "already in state";
    private static final String USR_TASK = "userTask";
    private static final String IMAGE_NAME = "image";
    private static final String XML_NAME = "xml";
    private static final Integer INT_1024 = 1024;
    private static final BpmnJsonConverter BPMN_JSON_CONVERTER = new BpmnJsonConverter();
    private static final BpmnXMLConverter BPMN_XML_CONVERTER = new BpmnXMLConverter();
    private final ObjectMapper objectMapper;
    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final TaskService taskService;
    private final ProcessEngine processEngine;
    @Override
    public IPage<FlowModel> selectFlowPage(IPage<FlowModel> page, FlowModel flowModel) {
        return page.setRecords(baseMapper.selectFlowPage(page, flowModel));
    }
    @Override
    public IPage<FlowProcess> selectProcessPage(IPage<FlowProcess> page, String category, Integer mode) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().latestVersion().orderByProcessDefinitionKey().asc();
        // 通用流程
        if (mode == FlowModeEnum.COMMON.getMode()) {
            processDefinitionQuery.processDefinitionWithoutTenantId();
        }
        // 定制流程
        else if (!AuthUtil.isAdministrator()) {
            processDefinitionQuery.processDefinitionTenantId(AuthUtil.getTenantId());
        }
        if (StringUtils.isNotEmpty(category)) {
            processDefinitionQuery.processDefinitionCategory(category);
        }
        List<ProcessDefinition> processDefinitionList = processDefinitionQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
        List<FlowProcess> flowProcessList = new ArrayList<>();
        processDefinitionList.forEach(processDefinition -> {
            String deploymentId = processDefinition.getDeploymentId();
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
            FlowProcess flowProcess = new FlowProcess((ProcessDefinitionEntityImpl) processDefinition);
            flowProcess.setDeploymentTime(deployment.getDeploymentTime());
            flowProcessList.add(flowProcess);
        });
        page.setTotal(processDefinitionQuery.count());
        page.setRecords(flowProcessList);
        return page;
    }
    @Override
    public IPage<FlowExecution> selectFollowPage(IPage<FlowExecution> page, String processInstanceId, String processDefinitionKey) {
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        if (StringUtil.isNotBlank(processInstanceId)) {
            processInstanceQuery.processInstanceId(processInstanceId);
        }
        if (StringUtil.isNotBlank(processDefinitionKey)) {
            processInstanceQuery.processDefinitionKey(processDefinitionKey);
        }
        List<FlowExecution> flowList = new ArrayList<>();
        List<ProcessInstance> procInsList = processInstanceQuery.listPage(Func.toInt((page.getCurrent() - 1) * page.getSize()), Func.toInt(page.getSize()));
        procInsList.forEach(processInstance -> {
            ExecutionEntityImpl execution = (ExecutionEntityImpl) processInstance;
            FlowExecution flowExecution = new FlowExecution();
            flowExecution.setId(execution.getId());
            flowExecution.setName(execution.getName());
            flowExecution.setStartUserId(execution.getStartUserId());
            User taskUser = UserCache.getUserByTaskUser(execution.getStartUserId());
            if (taskUser != null) {
                flowExecution.setStartUser(taskUser.getName());
            }
            flowExecution.setStartTime(execution.getStartTime());
            flowExecution.setExecutionId(execution.getId());
            flowExecution.setProcessInstanceId(execution.getProcessInstanceId());
            flowExecution.setProcessDefinitionId(execution.getProcessDefinitionId());
            flowExecution.setProcessDefinitionKey(execution.getProcessDefinitionKey());
            flowExecution.setSuspensionState(execution.getSuspensionState());
            FlowProcess processDefinition = FlowCache.getProcessDefinition(execution.getProcessDefinitionId());
            flowExecution.setCategory(processDefinition.getCategory());
            flowExecution.setCategoryName(FlowCache.getCategoryName(processDefinition.getCategory()));
            flowList.add(flowExecution);
        });
        page.setTotal(processInstanceQuery.count());
        page.setRecords(flowList);
        return page;
    }
    @Override
    public List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId) {
        List<BladeFlow> flowList = new LinkedList<>();
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list();
        boolean start = false;
        Map<String, Integer> activityMap = new HashMap<>(16);
        for (int i = 0; i < historicActivityInstanceList.size(); i++) {
            HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i);
            // 过滤开始节点前的节点
            if (StringUtil.isNotBlank(startActivityId) && startActivityId.equals(historicActivityInstance.getActivityId())) {
                start = true;
            }
            if (StringUtil.isNotBlank(startActivityId) && !start) {
                continue;
            }
            // 显示开始节点和结束节点,并且执行人不为空的任务
            if (StringUtils.equals(USR_TASK, historicActivityInstance.getActivityType())
                || FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())
                || FlowEngineConstant.END_EVENT.equals(historicActivityInstance.getActivityType())) {
                // 给节点增加序号
                Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
                if (activityNum == null) {
                    activityMap.put(historicActivityInstance.getActivityId(), activityMap.size());
                }
                BladeFlow flow = new BladeFlow();
                flow.setHistoryActivityId(historicActivityInstance.getActivityId());
                flow.setHistoryActivityName(historicActivityInstance.getActivityName());
                flow.setCreateTime(historicActivityInstance.getStartTime());
                flow.setEndTime(historicActivityInstance.getEndTime());
                String durationTime = DateUtil.secondToTime(Func.toLong(historicActivityInstance.getDurationInMillis(), 0L) / 1000);
                flow.setHistoryActivityDurationTime(durationTime);
                // 获取流程发起人名称
                if (FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())) {
                    List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).orderByProcessInstanceStartTime().asc().list();
                    if (processInstanceList.size() > 0) {
                        if (StringUtil.isNotBlank(processInstanceList.get(0).getStartUserId())) {
                            String taskUser = processInstanceList.get(0).getStartUserId();
                            User user = UserCache.getUser(TaskUtil.getUserId(taskUser));
                            if (user != null) {
                                flow.setAssignee(historicActivityInstance.getAssignee());
                                flow.setAssigneeName(user.getName());
                            }
                        }
                    }
                }
                // 获取任务执行人名称
                if (StringUtil.isNotBlank(historicActivityInstance.getAssignee())) {
                    User user = UserCache.getUser(TaskUtil.getUserId(historicActivityInstance.getAssignee()));
                    if (user != null) {
                        flow.setAssignee(historicActivityInstance.getAssignee());
                        flow.setAssigneeName(user.getName());
                    }
                }
                // 获取意见评论内容
                if (StringUtil.isNotBlank(historicActivityInstance.getTaskId())) {
                    List<Comment> commentList = taskService.getTaskComments(historicActivityInstance.getTaskId());
                    if (commentList.size() > 0) {
                        flow.setComment(commentList.get(0).getFullMessage());
                    }
                }
                flowList.add(flow);
            }
            // 过滤结束节点后的节点
            if (StringUtils.isNotBlank(endActivityId) && endActivityId.equals(historicActivityInstance.getActivityId())) {
                boolean temp = false;
                Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
                // 该活动节点,后续节点是否在结束节点之前,在后续节点中是否存在
                for (int j = i + 1; j < historicActivityInstanceList.size(); j++) {
                    HistoricActivityInstance hi = historicActivityInstanceList.get(j);
                    Integer activityNumA = activityMap.get(hi.getActivityId());
                    boolean numberTemp = activityNumA != null && activityNumA < activityNum;
                    boolean equalsTemp = StringUtils.equals(hi.getActivityId(), historicActivityInstance.getActivityId());
                    if (numberTemp || equalsTemp) {
                        temp = true;
                    }
                }
                if (!temp) {
                    break;
                }
            }
        }
        return flowList;
    }
    @Override
    public String changeState(String state, String processId) {
        try {
            if (state.equals(FlowEngineConstant.ACTIVE)) {
                repositoryService.activateProcessDefinitionById(processId, true, null);
                return StringUtil.format("激活ID为 [{}] 的流程成功", processId);
            } else if (state.equals(FlowEngineConstant.SUSPEND)) {
                repositoryService.suspendProcessDefinitionById(processId, true, null);
                return StringUtil.format("挂起ID为 [{}] 的流程成功", processId);
            } else {
                return "暂无流程变更";
            }
        } catch (Exception e) {
            if (e.getMessage().contains(ALREADY_IN_STATE)) {
                return StringUtil.format("ID为 [{}] 的流程已是此状态,无需操作", processId);
            }
            return e.getMessage();
        }
    }
    @Override
    public boolean deleteDeployment(String deploymentIds) {
        Func.toStrList(deploymentIds).forEach(deploymentId -> repositoryService.deleteDeployment(deploymentId, true));
        return true;
    }
    @Override
    public boolean deployUpload(List<MultipartFile> files, String category, List<String> tenantIdList) {
        files.forEach(file -> {
            try {
                String fileName = file.getOriginalFilename();
                InputStream fileInputStream = file.getInputStream();
                byte[] bytes = FileUtil.copyToByteArray(fileInputStream);
                if (Func.isNotEmpty(tenantIdList)) {
                    tenantIdList.forEach(tenantId -> {
                        Deployment deployment = repositoryService.createDeployment().addBytes(fileName, bytes).tenantId(tenantId).deploy();
                        deploy(deployment, category);
                    });
                } else {
                    Deployment deployment = repositoryService.createDeployment().addBytes(fileName, bytes).deploy();
                    deploy(deployment, category);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return true;
    }
    @Override
    public boolean deployModel(String modelId, String category, List<String> tenantIdList) {
        FlowModel model = this.getById(modelId);
        if (model == null) {
            throw new ServiceException("未找到模型 id: " + modelId);
        }
        byte[] bytes = getBpmnXML(model);
        String processName = model.getName();
        if (!StringUtil.endsWithIgnoreCase(processName, FlowEngineConstant.SUFFIX)) {
            processName += FlowEngineConstant.SUFFIX;
        }
        String finalProcessName = processName;
        if (Func.isNotEmpty(tenantIdList)) {
            tenantIdList.forEach(tenantId -> {
                Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).tenantId(tenantId).deploy();
                deploy(deployment, category);
            });
        } else {
            Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).deploy();
            deploy(deployment, category);
        }
        return true;
    }
    @Override
    public boolean deleteProcessInstance(String processInstanceId, String deleteReason) {
        runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
        return true;
    }
    private void deploy(Deployment deployment, String category) {
        log.debug("流程部署--------deploy:  " + deployment + "  分类---------->" + category);
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
        StringBuilder logBuilder = new StringBuilder(500);
        List<Object> logArgs = new ArrayList<>();
        // 设置流程分类
        for (ProcessDefinition processDefinition : list) {
            if (StringUtil.isNotBlank(category)) {
                repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);
            }
            logBuilder.append("部署成功,流程ID={} \n");
            logArgs.add(processDefinition.getId());
        }
        if (list.size() == 0) {
            throw new ServiceException("部署失败,未找到流程");
        } else {
            log.info(logBuilder.toString(), logArgs.toArray());
        }
    }
    @Override
    public FlowModel submitModel(FlowModel model) {
        FlowModel flowModel = new FlowModel();
        flowModel.setId(model.getId());
        flowModel.setVersion(Func.toInt(model.getVersion(), 0) + 1);
        flowModel.setName(model.getName());
        flowModel.setModelKey(model.getModelKey());
        flowModel.setModelType(FlowModel.MODEL_TYPE_BPMN);
        flowModel.setCreatedBy(TaskUtil.getTaskUser());
        flowModel.setDescription(model.getDescription());
        flowModel.setLastUpdated(Calendar.getInstance().getTime());
        flowModel.setLastUpdatedBy(TaskUtil.getTaskUser());
        flowModel.setTenantId(AuthUtil.getTenantId());
        flowModel.setModelEditorXml(model.getModelEditorXml());
        if (StringUtil.isBlank(model.getId())) {
            flowModel.setCreated(Calendar.getInstance().getTime());
        }
        if (StringUtil.isNotBlank(model.getModelEditorXml())) {
            flowModel.setModelEditorJson(getBpmnJson(model.getModelEditorXml()));
        }
        this.saveOrUpdate(flowModel);
        return flowModel;
    }
    @Override
    public Map<String, Object> modelView(String processDefinitionId, String processInstanceId) {
        Map<String, Object> result = new HashMap<>();
        // 节点标记
        if (StringUtil.isNotBlank(processInstanceId)) {
            result.put("flow", this.historyFlowList(processInstanceId, null, null));
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
            processDefinitionId = processInstance.getProcessDefinitionId();
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        // 流程图展示
        result.put("xml", new String(new BpmnXMLConverter().convertToXML(bpmnModel)));
        return result;
    }
    @Override
    public void diagramView(String processInstanceId, HttpServletResponse httpServletResponse) {
        // 获得当前活动的节点
        String processDefinitionId;
        // 如果流程已经结束,则得到结束节点
        if (this.isFinished(processInstanceId)) {
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {
            // 如果流程没有结束,则取当前活动节点
            // 根据流程实例ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }
        List<String> highLightedActivities = new ArrayList<>();
        // 获得活动的节点
        List<HistoricActivityInstance> highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        for (HistoricActivityInstance tempActivity : highLightedActivityList) {
            String activityId = tempActivity.getActivityId();
            highLightedActivities.add(activityId);
        }
        List<String> flows = new ArrayList<>();
        // 获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivities, flows, engConf.getActivityFontName(),
            engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);
        OutputStream out = null;
        byte[] buf = new byte[1024];
        int length;
        try {
            out = httpServletResponse.getOutputStream();
            while ((length = in.read(buf)) != -1) {
                out.write(buf, 0, length);
            }
        } catch (IOException e) {
            log.error("操作异常", e);
        } finally {
            IoUtil.closeSilently(out);
            IoUtil.closeSilently(in);
        }
    }
    @Override
    public void resourceView(String processDefinitionId, String processInstanceId, String resourceType, HttpServletResponse response) {
        if (StringUtil.isAllBlank(processDefinitionId, processInstanceId)) {
            return;
        }
        if (StringUtil.isBlank(processDefinitionId)) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = processInstance.getProcessDefinitionId();
        }
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
        String resourceName = "";
        if (resourceType.equals(IMAGE_NAME)) {
            resourceName = processDefinition.getDiagramResourceName();
        } else if (resourceType.equals(XML_NAME)) {
            resourceName = processDefinition.getResourceName();
        }
        try {
            InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
            byte[] b = new byte[1024];
            int len;
            while ((len = resourceAsStream.read(b, 0, INT_1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
    @Override
    public byte[] getModelEditorXML(FlowModel model) {
        return getBpmnXML(model);
    }
    /**
     * 是否已完结
     *
     * @param processInstanceId 流程实例id
     * @return bool
     */
    private boolean isFinished(String processInstanceId) {
        return historyService.createHistoricProcessInstanceQuery().finished()
            .processInstanceId(processInstanceId).count() > 0;
    }
    /**
     * xml转bpmn json
     *
     * @param xml xml
     * @return json
     */
    private String getBpmnJson(String xml) {
        return BPMN_JSON_CONVERTER.convertToJson(getBpmnModel(xml)).toString();
    }
    /**
     * xml转bpmnModel
     *
     * @param xml xml
     * @return bpmnModel
     */
    private BpmnModel getBpmnModel(String xml) {
        return BPMN_XML_CONVERTER.convertToBpmnModel(new StringStreamSource(xml), false, false);
    }
    private byte[] getBpmnXML(FlowModel model) {
        BpmnModel bpmnModel = getBpmnModel(model);
        return getBpmnXML(bpmnModel);
    }
    private byte[] getBpmnXML(BpmnModel bpmnModel) {
        for (Process process : bpmnModel.getProcesses()) {
            if (StringUtils.isNotEmpty(process.getId())) {
                char firstCharacter = process.getId().charAt(0);
                if (Character.isDigit(firstCharacter)) {
                    process.setId("a" + process.getId());
                }
            }
        }
        return BPMN_XML_CONVERTER.convertToXML(bpmnModel);
    }
    private BpmnModel getBpmnModel(FlowModel model) {
        BpmnModel bpmnModel;
        try {
            Map<String, FlowModel> formMap = new HashMap<>(16);
            Map<String, FlowModel> decisionTableMap = new HashMap<>(16);
            List<FlowModel> referencedModels = baseMapper.findByParentModelId(model.getId());
            for (FlowModel childModel : referencedModels) {
                if (FlowModel.MODEL_TYPE_FORM == childModel.getModelType()) {
                    formMap.put(childModel.getId(), childModel);
                } else if (FlowModel.MODEL_TYPE_DECISION_TABLE == childModel.getModelType()) {
                    decisionTableMap.put(childModel.getId(), childModel);
                }
            }
            bpmnModel = getBpmnModel(model, formMap, decisionTableMap);
        } catch (Exception e) {
            log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
            throw new ServiceException("Could not generate BPMN 2.0 model");
        }
        return bpmnModel;
    }
    private BpmnModel getBpmnModel(FlowModel model, Map<String, FlowModel> formMap, Map<String, FlowModel> decisionTableMap) {
        try {
            ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
            Map<String, String> formKeyMap = new HashMap<>(16);
            for (FlowModel formModel : formMap.values()) {
                formKeyMap.put(formModel.getId(), formModel.getModelKey());
            }
            Map<String, String> decisionTableKeyMap = new HashMap<>(16);
            for (FlowModel decisionTableModel : decisionTableMap.values()) {
                decisionTableKeyMap.put(decisionTableModel.getId(), decisionTableModel.getModelKey());
            }
            return BPMN_JSON_CONVERTER.convertToBpmnModel(editorJsonNode, formKeyMap, decisionTableKeyMap);
        } catch (Exception e) {
            log.error("Could not generate BPMN 2.0 model for {}", model.getId(), e);
            throw new ServiceException("Could not generate BPMN 2.0 model");
        }
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/java/cn/gistack/flow/engine/utils/FlowCache.java
New file
@@ -0,0 +1,78 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.engine.utils;
import cn.gistack.system.cache.DictCache;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntityImpl;
import org.flowable.engine.repository.ProcessDefinition;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringPool;
import cn.gistack.flow.engine.entity.FlowProcess;
import static org.springblade.core.cache.constant.CacheConstant.FLOW_CACHE;
/**
 * 流程缓存
 *
 * @author Chill
 */
public class FlowCache {
    private static final String FLOW_DEFINITION_ID = "definition:id:";
    private static RepositoryService repositoryService;
    private static RepositoryService getRepositoryService() {
        if (repositoryService == null) {
            repositoryService = SpringUtil.getBean(RepositoryService.class);
        }
        return repositoryService;
    }
    /**
     * 获得流程定义对象
     *
     * @param processDefinitionId 流程对象id
     * @return
     */
    public static FlowProcess getProcessDefinition(String processDefinitionId) {
        return CacheUtil.get(FLOW_CACHE, FLOW_DEFINITION_ID, processDefinitionId, () -> {
            ProcessDefinition processDefinition = getRepositoryService().createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
            ProcessDefinitionEntityImpl processDefinitionEntity = BeanUtil.copy(processDefinition, ProcessDefinitionEntityImpl.class);
            return new FlowProcess(processDefinitionEntity);
        });
    }
    /**
     * 获取流程类型名
     *
     * @param category 流程类型
     * @return
     */
    public static String getCategoryName(String category) {
        String[] categoryArr = category.split(StringPool.UNDERSCORE);
        if (categoryArr.length <= 1) {
            return StringPool.EMPTY;
        } else {
            return DictCache.getValue(category.split(StringPool.UNDERSCORE)[0], Func.toInt(category.split(StringPool.UNDERSCORE)[1]));
        }
    }
}
skjcmanager-ops/skjcmanager-flow/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.flow.dev.url}
    username: ${blade.datasource.flow.dev.username}
    password: ${blade.datasource.flow.dev.password}
skjcmanager-ops/skjcmanager-flow/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.flow.prod.url}
    username: ${blade.datasource.flow.prod.username}
    password: ${blade.datasource.flow.prod.password}
skjcmanager-ops/skjcmanager-flow/src/main/resources/application-test.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.flow.test.url}
    username: ${blade.datasource.flow.test.username}
    password: ${blade.datasource.flow.test.password}
skjcmanager-ops/skjcmanager-flow/src/main/resources/application.yml
New file
@@ -0,0 +1,13 @@
#服务器端口
server:
  port: 8008
#flowable配置
flowable:
  activity-font-name: \u5B8B\u4F53
  label-font-name: \u5B8B\u4F53
  annotation-font-name: \u5B8B\u4F53
  check-process-definitions: false
  database-schema-update: false
  async-executor-activate: false
  async-history-executor-activate: false
skjcmanager-ops/skjcmanager-flow/src/main/resources/processes/LeaveProcess.bpmn20.xml
New file
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
    <process id="Leave" name="请假流程" isExecutable="true">
        <documentation>请假流程</documentation>
        <startEvent id="start" name="开始" flowable:initiator="applyUser"></startEvent>
        <userTask id="hrTask" name="人事审批" flowable:assignee="${taskUser}">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <exclusiveGateway id="judgeTask"></exclusiveGateway>
        <userTask id="managerTak" name="经理审批" flowable:candidateGroups="manager"></userTask>
        <userTask id="bossTask" name="老板审批" flowable:candidateGroups="boss"></userTask>
        <endEvent id="end" name="结束"></endEvent>
        <sequenceFlow id="flow1" sourceRef="start" targetRef="hrTask"></sequenceFlow>
        <sequenceFlow id="managerPassFlow" name="通过" sourceRef="managerTak" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
        </sequenceFlow>
        <userTask id="userTask" name="调整申请" flowable:assignee="${applyUser}">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="judgeMore" name="大于3天" sourceRef="judgeTask" targetRef="bossTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days > 3}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="managerNotPassFlow" name="驳回" sourceRef="managerTak" targetRef="userTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="userTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="hrPassFlow" name="同意" sourceRef="hrTask" targetRef="judgeTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="hrNotPassFlow" name="驳回" sourceRef="hrTask" targetRef="userTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="judgeLess" name="小于3天" sourceRef="judgeTask" targetRef="managerTak">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days <= 3}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="userPassFlow" name="重新申请" sourceRef="userTask" targetRef="hrTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="userNotPassFlow" name="关闭申请" sourceRef="userTask" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!pass}]]></conditionExpression>
        </sequenceFlow>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_Leave">
        <bpmndi:BPMNPlane bpmnElement="Leave" id="BPMNPlane_Leave">
            <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
                <omgdc:Bounds height="30.0" width="30.0" x="300.0" y="135.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="hrTask" id="BPMNShape_hrTask">
                <omgdc:Bounds height="80.0" width="100.0" x="360.0" y="165.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
                <omgdc:Bounds height="40.0" width="40.0" x="255.0" y="300.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="managerTak" id="BPMNShape_managerTak">
                <omgdc:Bounds height="80.0" width="100.0" x="555.0" y="75.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
                <omgdc:Bounds height="80.0" width="100.0" x="450.0" y="420.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
                <omgdc:Bounds height="28.0" width="28.0" x="705.0" y="390.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="userTask" id="BPMNShape_userTask">
                <omgdc:Bounds height="80.0" width="100.0" x="510.0" y="270.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
                <omgdi:waypoint x="327.9390183144677" y="157.4917313275668"></omgdi:waypoint>
                <omgdi:waypoint x="360.0" y="176.05263157894737"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="hrPassFlow" id="BPMNEdge_hrPassFlow">
                <omgdi:waypoint x="363.04347826086956" y="244.95000000000002"></omgdi:waypoint>
                <omgdi:waypoint x="285.77299999999997" y="310.79999999999995"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="hrNotPassFlow" id="BPMNEdge_hrNotPassFlow">
                <omgdi:waypoint x="459.95" y="236.21875000000006"></omgdi:waypoint>
                <omgdi:waypoint x="513.9794844818516" y="270.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
                <omgdi:waypoint x="274.3359375" y="300.66397214564284"></omgdi:waypoint>
                <omgdi:waypoint x="274.3359375" y="115.0"></omgdi:waypoint>
                <omgdi:waypoint x="554.9999999999982" y="115.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="userPassFlow" id="BPMNEdge_userPassFlow">
                <omgdi:waypoint x="510.0" y="310.0"></omgdi:waypoint>
                <omgdi:waypoint x="411.0" y="310.0"></omgdi:waypoint>
                <omgdi:waypoint x="411.0" y="244.95000000000002"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
                <omgdi:waypoint x="549.9499999999998" y="447.2146118721461"></omgdi:waypoint>
                <omgdi:waypoint x="705.4331577666419" y="407.4567570622598"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
                <omgdi:waypoint x="287.29730895645025" y="327.65205479452055"></omgdi:waypoint>
                <omgdi:waypoint x="450.0" y="428.8888888888889"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="managerPassFlow" id="BPMNEdge_managerPassFlow">
                <omgdi:waypoint x="620.7588235294118" y="154.95"></omgdi:waypoint>
                <omgdi:waypoint x="713.8613704477151" y="390.96328050279476"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="userNotPassFlow" id="BPMNEdge_userNotPassFlow">
                <omgdi:waypoint x="609.95" y="339.5301886792453"></omgdi:waypoint>
                <omgdi:waypoint x="706.9383699359797" y="396.87411962686997"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
                <omgdi:waypoint x="515.98" y="420.0"></omgdi:waypoint>
                <omgdi:waypoint x="544.0" y="349.95000000000005"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="managerNotPassFlow" id="BPMNEdge_managerNotPassFlow">
                <omgdi:waypoint x="595.438344721373" y="154.95"></omgdi:waypoint>
                <omgdi:waypoint x="567.9366337262223" y="270.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>
skjcmanager-ops/skjcmanager-flow/src/test/java/cn/gistack/flow/test/BladeTest.java
New file
@@ -0,0 +1,46 @@
package cn.gistack.flow.test;
import cn.gistack.flow.engine.entity.FlowModel;
import cn.gistack.flow.engine.service.FlowEngineService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springblade.core.test.BladeBootTest;
import org.springblade.core.test.BladeSpringExtension;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
 * Blade单元测试
 *
 * @author Chill
 */
@ExtendWith(BladeSpringExtension.class)
@BladeBootTest(appName = "blade-flow", enableLoader = true)
public class BladeTest {
    @Autowired
    private FlowEngineService service;
    @Test
    public void contextLoads() {
        System.out.println("=====数据迁移启动=====");
        // 获取 ACT_DE_MODEL 表需要转换的数据
        List<FlowModel> list = service.list();
        // 循环转换
        list.forEach(flowModel -> {
            if (StringUtil.isBlank(flowModel.getModelEditorXml())) {
                service.update(Wrappers.<FlowModel>lambdaUpdate()
                    .set(FlowModel::getModelEditorXml, new String(service.getModelEditorXML(flowModel)))
                    .ge(FlowModel::getId, flowModel.getId())
                );
            }
        });
        System.out.println("=====数据迁移完毕=====");
    }
}
skjcmanager-ops/skjcmanager-flow/src/test/java/cn/gistack/flow/test/launch/LauncherTestServiceImpl.java
New file
@@ -0,0 +1,43 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.flow.test.launch;
import cn.gistack.common.constant.LauncherConstant;
import org.springblade.core.auto.service.AutoService;
import org.springblade.core.launch.service.LauncherService;
import org.springblade.core.launch.utils.PropsUtil;
import org.springframework.boot.builder.SpringApplicationBuilder;
import java.util.Properties;
/**
 * 启动参数拓展
 *
 * @author smallchil
 */
@AutoService(LauncherService.class)
public class LauncherTestServiceImpl implements LauncherService {
    @Override
    public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
        Properties props = System.getProperties();
        PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.server-addr", LauncherConstant.nacosAddr(profile));
        PropsUtil.setProperty(props, "spring.cloud.nacos.config.server-addr", LauncherConstant.nacosAddr(profile));
        PropsUtil.setProperty(props, "spring.datasource.dynamic.enabled", "false");
    }
}
skjcmanager-ops/skjcmanager-flow/src/test/resources/application-dev.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.flow.dev.url}
    username: ${blade.datasource.flow.dev.username}
    password: ${blade.datasource.flow.dev.password}
skjcmanager-ops/skjcmanager-flow/src/test/resources/application-prod.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.flow.prod.url}
    username: ${blade.datasource.flow.prod.username}
    password: ${blade.datasource.flow.prod.password}
skjcmanager-ops/skjcmanager-flow/src/test/resources/application-test.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.flow.test.url}
    username: ${blade.datasource.flow.test.username}
    password: ${blade.datasource.flow.test.password}
skjcmanager-ops/skjcmanager-flow/src/test/resources/application.yml
New file
@@ -0,0 +1,13 @@
#服务器端口
server:
  port: 8008
#flowable配置
flowable:
  activity-font-name: \u5B8B\u4F53
  label-font-name: \u5B8B\u4F53
  annotation-font-name: \u5B8B\u4F53
  check-process-definitions: false
  database-schema-update: false
  async-executor-activate: false
  async-history-executor-activate: false
skjcmanager-ops/skjcmanager-log/Dockerfile
New file
@@ -0,0 +1,15 @@
FROM bladex/alpine-java:openjdk8-openj9_cn_slim
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/log
WORKDIR /blade/log
EXPOSE 8103
ADD ./target/blade-log.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]
skjcmanager-ops/skjcmanager-log/pom.xml
New file
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-log</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <!--Blade-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-boot</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-tenant</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/LogApplication.java
New file
@@ -0,0 +1,35 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
/**
 * 日志服务
 *
 * @author Chill
 */
@BladeCloudApplication
public class LogApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_LOG_NAME, LogApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/controller/LogApiController.java
New file
@@ -0,0 +1,66 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.controller;
import cn.gistack.core.log.service.ILogApiService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.springblade.core.log.model.LogApi;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
/**
 * 控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/api")
public class LogApiController {
    private final ILogApiService logService;
    /**
     * 查询单条
     */
    @GetMapping("/detail")
    public R<LogApi> detail(LogApi log) {
        return R.data(logService.getOne(Condition.getQueryWrapper(log)));
    }
    /**
     * 查询多条(分页)
     */
    @GetMapping("/list")
    public R<IPage<LogApi>> list(@ApiIgnore @RequestParam Map<String, Object> log, Query query) {
        IPage<LogApi> pages = logService.page(Condition.getPage(query.setDescs("create_time")), Condition.getQueryWrapper(log, LogApi.class));
        return R.data(pages);
    }
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/controller/LogErrorController.java
New file
@@ -0,0 +1,66 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.controller;
import cn.gistack.core.log.service.ILogErrorService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.springblade.core.log.model.LogError;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
/**
 * 控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/error")
public class LogErrorController {
    private final ILogErrorService errorLogService;
    /**
     * 查询单条
     */
    @GetMapping("/detail")
    public R<LogError> detail(LogError logError) {
        return R.data(errorLogService.getOne(Condition.getQueryWrapper(logError)));
    }
    /**
     * 查询多条(分页)
     */
    @GetMapping("/list")
    public R<IPage<LogError>> list(@ApiIgnore @RequestParam Map<String, Object> logError, Query query) {
        IPage<LogError> pages = errorLogService.page(Condition.getPage(query.setDescs("create_time")), Condition.getQueryWrapper(logError, LogError.class));
        return R.data(pages);
    }
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/controller/LogUsualController.java
New file
@@ -0,0 +1,66 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.controller;
import cn.gistack.core.log.service.ILogUsualService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.springblade.core.log.model.LogUsual;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
/**
 * 控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/usual")
public class LogUsualController {
    private final ILogUsualService logService;
    /**
     * 查询单条
     */
    @GetMapping("/detail")
    public R<LogUsual> detail(LogUsual log) {
        return R.data(logService.getOne(Condition.getQueryWrapper(log)));
    }
    /**
     * 查询多条(分页)
     */
    @GetMapping("/list")
    public R<IPage<LogUsual>> list(@ApiIgnore @RequestParam Map<String, Object> log, Query query) {
        IPage<LogUsual> pages = logService.page(Condition.getPage(query), Condition.getQueryWrapper(log, LogUsual.class));
        return R.data(pages);
    }
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/feign/LogClient.java
New file
@@ -0,0 +1,69 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.feign;
import lombok.AllArgsConstructor;
import org.springblade.core.log.feign.ILogClient;
import org.springblade.core.log.model.LogApi;
import org.springblade.core.log.model.LogError;
import org.springblade.core.log.model.LogUsual;
import cn.gistack.core.log.service.ILogApiService;
import cn.gistack.core.log.service.ILogErrorService;
import cn.gistack.core.log.service.ILogUsualService;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
 * 日志服务Feign实现类
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
public class LogClient implements ILogClient {
    private final ILogUsualService usualLogService;
    private final ILogApiService apiLogService;
    private final ILogErrorService errorLogService;
    @Override
    @PostMapping(API_PREFIX + "/saveUsualLog")
    public R<Boolean> saveUsualLog(@RequestBody LogUsual log) {
        log.setParams(log.getParams().replace("&amp;", "&"));
        return R.data(usualLogService.save(log));
    }
    @Override
    @PostMapping(API_PREFIX + "/saveApiLog")
    public R<Boolean> saveApiLog(@RequestBody LogApi log) {
        log.setParams(log.getParams().replace("&amp;", "&"));
        return R.data(apiLogService.save(log));
    }
    @Override
    @PostMapping(API_PREFIX + "/saveErrorLog")
    public R<Boolean> saveErrorLog(@RequestBody LogError log) {
        log.setParams(log.getParams().replace("&amp;", "&"));
        return R.data(errorLogService.save(log));
    }
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogApiMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.core.log.model.LogApi;
/**
 * Mapper 接口
 *
 * @author Chill
 */
public interface LogApiMapper extends BaseMapper<LogApi> {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogApiMapper.xml
New file
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.core.log.mapper.LogApiMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="logResultMap" type="org.springblade.core.log.model.LogApi">
        <result column="id" property="id"/>
        <result column="create_time" property="createTime"/>
        <result column="service_id" property="serviceId"/>
        <result column="server_host" property="serverHost"/>
        <result column="server_ip" property="serverIp"/>
        <result column="env" property="env"/>
        <result column="type" property="type"/>
        <result column="title" property="title"/>
        <result column="method" property="method"/>
        <result column="request_uri" property="requestUri"/>
        <result column="user_agent" property="userAgent"/>
        <result column="remote_ip" property="remoteIp"/>
        <result column="method_class" property="methodClass"/>
        <result column="method_name" property="methodName"/>
        <result column="params" property="params"/>
        <result column="time" property="time"/>
        <result column="create_by" property="createBy"/>
    </resultMap>
</mapper>
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogErrorMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.core.log.model.LogError;
/**
 * Mapper 接口
 *
 * @author Chill
 */
public interface LogErrorMapper extends BaseMapper<LogError> {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogErrorMapper.xml
New file
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.core.log.mapper.LogErrorMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="errorLogResultMap" type="org.springblade.core.log.model.LogError">
        <result column="id" property="id"/>
        <result column="create_time" property="createTime"/>
        <result column="service_id" property="serviceId"/>
        <result column="server_host" property="serverHost"/>
        <result column="server_ip" property="serverIp"/>
        <result column="env" property="env"/>
        <result column="method" property="method"/>
        <result column="request_uri" property="requestUri"/>
        <result column="user_agent" property="userAgent"/>
        <result column="stack_trace" property="stackTrace"/>
        <result column="exception_name" property="exceptionName"/>
        <result column="message" property="message"/>
        <result column="line_number" property="lineNumber"/>
        <result column="method_class" property="methodClass"/>
        <result column="file_name" property="fileName"/>
        <result column="method_name" property="methodName"/>
        <result column="params" property="params"/>
        <result column="create_by" property="createBy"/>
    </resultMap>
</mapper>
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogUsualMapper.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.core.log.model.LogUsual;
/**
 * Mapper 接口
 *
 * @author Chill
 */
public interface LogUsualMapper extends BaseMapper<LogUsual> {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/mapper/LogUsualMapper.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.core.log.mapper.LogUsualMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="logResultMap" type="org.springblade.core.log.model.LogUsual">
        <result column="id" property="id"/>
        <result column="create_time" property="createTime"/>
        <result column="service_id" property="serviceId"/>
        <result column="server_host" property="serverHost"/>
        <result column="server_ip" property="serverIp"/>
        <result column="env" property="env"/>
        <result column="log_level" property="logLevel"/>
        <result column="log_data" property="logData"/>
        <result column="method" property="method"/>
        <result column="request_uri" property="requestUri"/>
        <result column="user_agent" property="userAgent"/>
        <result column="params" property="params"/>
        <result column="create_by" property="createBy"/>
    </resultMap>
</mapper>
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/ILogApiService.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.core.log.model.LogApi;
/**
 * 服务类
 *
 * @author Chill
 */
public interface ILogApiService extends IService<LogApi> {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/ILogErrorService.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.core.log.model.LogError;
/**
 * 服务类
 *
 * @author Chill
 */
public interface ILogErrorService extends IService<LogError> {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/ILogUsualService.java
New file
@@ -0,0 +1,29 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.core.log.model.LogUsual;
/**
 * 服务类
 *
 * @author Chill
 */
public interface ILogUsualService extends IService<LogUsual> {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/impl/LogApiServiceImpl.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.service.impl;
import cn.gistack.core.log.mapper.LogApiMapper;
import cn.gistack.core.log.service.ILogApiService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.log.model.LogApi;
import org.springframework.stereotype.Service;
/**
 * 服务实现类
 *
 * @author Chill
 */
@Service
public class LogApiServiceImpl extends ServiceImpl<LogApiMapper, LogApi> implements ILogApiService {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/impl/LogErrorServiceImpl.java
New file
@@ -0,0 +1,33 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.service.impl;
import cn.gistack.core.log.mapper.LogErrorMapper;
import cn.gistack.core.log.service.ILogErrorService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.log.model.LogError;
import org.springframework.stereotype.Service;
/**
 * 服务实现类
 *
 * @author Chill
 */
@Service
public class LogErrorServiceImpl extends ServiceImpl<LogErrorMapper, LogError> implements ILogErrorService {
}
skjcmanager-ops/skjcmanager-log/src/main/java/cn/gistack/core/log/service/impl/LogUsualServiceImpl.java
New file
@@ -0,0 +1,33 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.core.log.service.impl;
import cn.gistack.core.log.mapper.LogUsualMapper;
import cn.gistack.core.log.service.ILogUsualService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.log.model.LogUsual;
import org.springframework.stereotype.Service;
/**
 * 服务实现类
 *
 * @author Chill
 */
@Service
public class LogUsualServiceImpl extends ServiceImpl<LogUsualMapper, LogUsual> implements ILogUsualService {
}
skjcmanager-ops/skjcmanager-log/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,10 @@
#服务器端口
server:
  port: 8103
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.dev.url}
    username: ${blade.datasource.dev.username}
    password: ${blade.datasource.dev.password}
skjcmanager-ops/skjcmanager-log/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,10 @@
#数据源配置
server:
  port: 8103
spring:
  datasource:
    url: ${blade.datasource.prod.url}
    username: ${blade.datasource.prod.username}
    password: ${blade.datasource.prod.password}
skjcmanager-ops/skjcmanager-log/src/main/resources/application-test.yml
New file
@@ -0,0 +1,9 @@
#数据源配置
server:
  port: 8103
spring:
  datasource:
    url: ${blade.datasource.test.url}
    username: ${blade.datasource.test.username}
    password: ${blade.datasource.test.password}
skjcmanager-ops/skjcmanager-report/Dockerfile
New file
@@ -0,0 +1,15 @@
FROM bladex/alpine-java:openjdk8-openj9_cn_slim
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/report
WORKDIR /blade/report
EXPOSE 8108
ADD ./target/blade-report.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]
skjcmanager-ops/skjcmanager-report/pom.xml
New file
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-report</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <!--Blade-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-boot</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-tenant</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-report</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-report/src/main/java/cn/gistack/report/ReportApplication.java
New file
@@ -0,0 +1,35 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.report;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
/**
 * UReport启动器
 *
 * @author Chill
 */
@BladeCloudApplication
public class ReportApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_REPORT_NAME, ReportApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-report/src/main/java/cn/gistack/report/config/BladeReportConfiguration.java
New file
@@ -0,0 +1,43 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.report.config;
import org.springblade.core.report.datasource.ReportDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
 * 报表配置类
 *
 * @author Chill
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "report.enabled", havingValue = "true", matchIfMissing = true)
public class BladeReportConfiguration {
    /**
     * 自定义报表可选数据源
     */
    @Bean
    public ReportDataSource reportDataSource(DataSource dataSource) {
        return new ReportDataSource(dataSource);
    }
}
skjcmanager-ops/skjcmanager-report/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.dev.url}
    username: ${blade.datasource.dev.username}
    password: ${blade.datasource.dev.password}
skjcmanager-ops/skjcmanager-report/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.prod.url}
    username: ${blade.datasource.prod.username}
    password: ${blade.datasource.prod.password}
skjcmanager-ops/skjcmanager-report/src/main/resources/application-test.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.test.url}
    username: ${blade.datasource.test.username}
    password: ${blade.datasource.test.password}
skjcmanager-ops/skjcmanager-report/src/main/resources/application.yml
New file
@@ -0,0 +1,10 @@
#服务器端口
server:
  port: 8108
#报表配置
report:
  enabled: true
  database:
    provider:
      prefix: blade-
skjcmanager-ops/skjcmanager-resource/Dockerfile
New file
@@ -0,0 +1,15 @@
FROM bladex/alpine-java:openjdk8-openj9_cn_slim
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/resource
WORKDIR /blade/resource
EXPOSE 8010
ADD ./target/blade-resource.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]
skjcmanager-ops/skjcmanager-resource/pom.xml
New file
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-resource</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-boot</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-swagger</artifactId>
        </dependency>
        <!--Oss-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-oss</artifactId>
        </dependency>
        <!--Sms-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-starter-sms</artifactId>
        </dependency>
        <!--MinIO-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
        </dependency>
        <!--Alioss-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>
        <!--AliSms-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
        </dependency>
        <!--腾讯COS-->
        <dependency>
            <groupId>com.qcloud</groupId>
            <artifactId>cos_api</artifactId>
        </dependency>
        <!--腾讯SMS-->
        <dependency>
            <groupId>com.github.qcloudsms</groupId>
            <artifactId>qcloudsms</artifactId>
        </dependency>
        <!--QiNiu-->
        <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
        </dependency>
        <!--YunPian-->
        <dependency>
            <groupId>com.yunpian.sdk</groupId>
            <artifactId>yunpian-java-sdk</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-resource-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-dict-api</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/ResourceApplication.java
New file
@@ -0,0 +1,36 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
/**
 * 资源启动器
 *
 * @author Chill
 */
@BladeCloudApplication
public class ResourceApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_RESOURCE_NAME, ResourceApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/AliOssBuilder.java
New file
@@ -0,0 +1,63 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.oss;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import lombok.SneakyThrows;
import org.springblade.core.oss.OssTemplate;
import org.springblade.core.oss.AliossTemplate;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import cn.gistack.resource.entity.Oss;
/**
 * 阿里云存储构建类
 *
 * @author Chill
 */
public class AliOssBuilder {
    @SneakyThrows
    public static OssTemplate template(Oss oss, OssRule ossRule) {
        // 创建ClientConfiguration。ClientConfiguration是OSSClient的配置类,可配置代理、连接超时、最大连接数等参数。
        ClientConfiguration conf = new ClientConfiguration();
        // 设置OSSClient允许打开的最大HTTP连接数,默认为1024个。
        conf.setMaxConnections(1024);
        // 设置Socket层传输数据的超时时间,默认为50000毫秒。
        conf.setSocketTimeout(50000);
        // 设置建立连接的超时时间,默认为50000毫秒。
        conf.setConnectionTimeout(50000);
        // 设置从连接池中获取连接的超时时间(单位:毫秒),默认不超时。
        conf.setConnectionRequestTimeout(1000);
        // 设置连接空闲超时时间。超时则关闭连接,默认为60000毫秒。
        conf.setIdleConnectionTime(60000);
        // 设置失败请求重试次数,默认为3次。
        conf.setMaxErrorRetry(5);
        OssProperties ossProperties = new OssProperties();
        ossProperties.setEndpoint(oss.getEndpoint());
        ossProperties.setAccessKey(oss.getAccessKey());
        ossProperties.setSecretKey(oss.getSecretKey());
        ossProperties.setBucketName(oss.getBucketName());
        CredentialsProvider credentialsProvider = new DefaultCredentialProvider(ossProperties.getAccessKey(), ossProperties.getSecretKey());
        OSSClient ossClient = new OSSClient(ossProperties.getEndpoint(), credentialsProvider, conf);
        return new AliossTemplate(ossClient, ossProperties, ossRule);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/MinioOssBuilder.java
New file
@@ -0,0 +1,48 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.oss;
import io.minio.MinioClient;
import lombok.SneakyThrows;
import org.springblade.core.oss.OssTemplate;
import org.springblade.core.oss.MinioTemplate;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import cn.gistack.resource.entity.Oss;
/**
 * Minio云存储构建类
 *
 * @author Chill
 */
public class MinioOssBuilder {
    @SneakyThrows
    public static OssTemplate template(Oss oss, OssRule ossRule) {
        MinioClient minioClient = MinioClient.builder()
            .endpoint(oss.getEndpoint())
            .credentials(oss.getAccessKey(), oss.getSecretKey())
            .build();
        OssProperties ossProperties = new OssProperties();
        ossProperties.setEndpoint(oss.getEndpoint());
        ossProperties.setAccessKey(oss.getAccessKey());
        ossProperties.setSecretKey(oss.getSecretKey());
        ossProperties.setBucketName(oss.getBucketName());
        return new MinioTemplate(minioClient, ossRule, ossProperties);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/OssBuilder.java
New file
@@ -0,0 +1,160 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.oss;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.oss.OssTemplate;
import org.springblade.core.oss.enums.OssEnum;
import org.springblade.core.oss.enums.OssStatusEnum;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.BladeOssRule;
import org.springblade.core.oss.rule.OssRule;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import cn.gistack.resource.entity.Oss;
import cn.gistack.resource.service.IOssService;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.springblade.core.cache.constant.CacheConstant.RESOURCE_CACHE;
/**
 * Oss云存储统一构建类
 *
 * @author Chill
 */
public class OssBuilder {
    public static final String OSS_CODE = "oss:code:";
    public static final String OSS_PARAM_KEY = "code";
    private final OssProperties ossProperties;
    private final IOssService ossService;
    public OssBuilder(OssProperties ossProperties, IOssService ossService) {
        this.ossProperties = ossProperties;
        this.ossService = ossService;
    }
    /**
     * OssTemplate配置缓存池
     */
    private final Map<String, OssTemplate> templatePool = new ConcurrentHashMap<>();
    /**
     * oss配置缓存池
     */
    private final Map<String, Oss> ossPool = new ConcurrentHashMap<>();
    /**
     * 获取template
     *
     * @return OssTemplate
     */
    public OssTemplate template() {
        return template(StringPool.EMPTY);
    }
    /**
     * 获取template
     *
     * @param code 资源编号
     * @return OssTemplate
     */
    public OssTemplate template(String code) {
        String tenantId = AuthUtil.getTenantId();
        Oss oss = getOss(tenantId, code);
        Oss ossCached = ossPool.get(tenantId);
        OssTemplate template = templatePool.get(tenantId);
        // 若为空或者不一致,则重新加载
        if (Func.hasEmpty(template, ossCached) || !oss.getEndpoint().equals(ossCached.getEndpoint()) || !oss.getAccessKey().equals(ossCached.getAccessKey())) {
            synchronized (OssBuilder.class) {
                template = templatePool.get(tenantId);
                if (Func.hasEmpty(template, ossCached) || !oss.getEndpoint().equals(ossCached.getEndpoint()) || !oss.getAccessKey().equals(ossCached.getAccessKey())) {
                    OssRule ossRule;
                    // 若采用默认设置则开启多租户模式, 若是用户自定义oss则不开启
                    if (oss.getEndpoint().equals(ossProperties.getEndpoint()) && oss.getAccessKey().equals(ossProperties.getAccessKey()) && ossProperties.getTenantMode()) {
                        ossRule = new BladeOssRule(Boolean.TRUE);
                    } else {
                        ossRule = new BladeOssRule(Boolean.FALSE);
                    }
                    if (oss.getCategory() == OssEnum.MINIO.getCategory()) {
                        template = MinioOssBuilder.template(oss, ossRule);
                    } else if (oss.getCategory() == OssEnum.QINIU.getCategory()) {
                        template = QiniuOssBuilder.template(oss, ossRule);
                    } else if (oss.getCategory() == OssEnum.ALI.getCategory()) {
                        template = AliOssBuilder.template(oss, ossRule);
                    } else if (oss.getCategory() == OssEnum.TENCENT.getCategory()) {
                        template = TencentOssBuilder.template(oss, ossRule);
                    }
                    templatePool.put(tenantId, template);
                    ossPool.put(tenantId, oss);
                }
            }
        }
        return template;
    }
    /**
     * 获取对象存储实体
     *
     * @param tenantId 租户ID
     * @return Oss
     */
    public Oss getOss(String tenantId, String code) {
        String key = tenantId;
        LambdaQueryWrapper<Oss> lqw = Wrappers.<Oss>query().lambda().eq(Oss::getTenantId, tenantId);
        // 获取传参的资源编号并查询,若有则返回,若没有则调启用的配置
        String ossCode = StringUtil.isBlank(code) ? WebUtil.getParameter(OSS_PARAM_KEY) : code;
        if (StringUtil.isNotBlank(ossCode)) {
            key = key.concat(StringPool.DASH).concat(ossCode);
            lqw.eq(Oss::getOssCode, ossCode);
        } else {
            lqw.eq(Oss::getStatus, OssStatusEnum.ENABLE.getNum());
        }
        Oss oss = CacheUtil.get(RESOURCE_CACHE, OSS_CODE, key, () -> {
            Oss o = ossService.getOne(lqw);
            // 若为空则调用默认配置
            if ((Func.isEmpty(o))) {
                Oss defaultOss = new Oss();
                defaultOss.setId(0L);
                defaultOss.setCategory(OssEnum.of(ossProperties.getName()).getCategory());
                defaultOss.setEndpoint(ossProperties.getEndpoint());
                defaultOss.setBucketName(ossProperties.getBucketName());
                defaultOss.setAccessKey(ossProperties.getAccessKey());
                defaultOss.setSecretKey(ossProperties.getSecretKey());
                return defaultOss;
            } else {
                return o;
            }
        });
        if (oss == null || oss.getId() == null) {
            throw new ServiceException("未获取到对应的对象存储配置");
        } else {
            return oss;
        }
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/QiniuOssBuilder.java
New file
@@ -0,0 +1,52 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.oss;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import lombok.SneakyThrows;
import org.springblade.core.oss.OssTemplate;
import org.springblade.core.oss.QiniuTemplate;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import cn.gistack.resource.entity.Oss;
/**
 * 七牛云存储构建类
 *
 * @author Chill
 */
public class QiniuOssBuilder {
    @SneakyThrows
    public static OssTemplate template(Oss oss, OssRule ossRule) {
        Configuration cfg = new Configuration(Region.autoRegion());
        Auth auth = Auth.create(oss.getAccessKey(), oss.getSecretKey());
        UploadManager uploadManager = new UploadManager(cfg);
        BucketManager bucketManager = new BucketManager(auth, cfg);
        OssProperties ossProperties = new OssProperties();
        ossProperties.setEndpoint(oss.getEndpoint());
        ossProperties.setAccessKey(oss.getAccessKey());
        ossProperties.setSecretKey(oss.getSecretKey());
        ossProperties.setBucketName(oss.getBucketName());
        return new QiniuTemplate(auth, uploadManager, bucketManager, ossProperties, ossRule);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/oss/TencentOssBuilder.java
New file
@@ -0,0 +1,66 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.oss;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.region.Region;
import lombok.SneakyThrows;
import org.springblade.core.oss.OssTemplate;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import org.springblade.core.oss.TencentCosTemplate;
import cn.gistack.resource.entity.Oss;
/**
 * 腾讯云存储构建类
 *
 * @author Chill
 */
public class TencentOssBuilder {
    @SneakyThrows
    public static OssTemplate template(Oss oss, OssRule ossRule) {
        // 创建配置类
        OssProperties ossProperties = new OssProperties();
        ossProperties.setEndpoint(oss.getEndpoint());
        ossProperties.setAccessKey(oss.getAccessKey());
        ossProperties.setSecretKey(oss.getSecretKey());
        ossProperties.setBucketName(oss.getBucketName());
        ossProperties.setAppId(oss.getAppId());
        ossProperties.setRegion(oss.getRegion());
        // 初始化用户身份信息(secretId, secretKey)
        COSCredentials credentials = new BasicCOSCredentials(ossProperties.getAccessKey(), ossProperties.getSecretKey());
        // 设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
        Region region = new Region(ossProperties.getRegion());
        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
        ClientConfig clientConfig = new ClientConfig(region);
        // 设置OSSClient允许打开的最大HTTP连接数,默认为1024个。
        clientConfig.setMaxConnectionsCount(1024);
        // 设置Socket层传输数据的超时时间,默认为50000毫秒。
        clientConfig.setSocketTimeout(50000);
        // 设置建立连接的超时时间,默认为50000毫秒。
        clientConfig.setConnectionTimeout(50000);
        // 设置从连接池中获取连接的超时时间(单位:毫秒),默认不超时。
        clientConfig.setConnectionRequestTimeout(1000);
        COSClient cosClient = new COSClient(credentials, clientConfig);
        return new TencentCosTemplate(cosClient, ossProperties, ossRule);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/AliSmsBuilder.java
New file
@@ -0,0 +1,50 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.SneakyThrows;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.sms.SmsTemplate;
import org.springblade.core.sms.AliSmsTemplate;
import org.springblade.core.sms.props.SmsProperties;
import cn.gistack.resource.entity.Sms;
/**
 * 阿里云短信构建类
 *
 * @author Chill
 */
public class AliSmsBuilder {
    @SneakyThrows
    public static SmsTemplate template(Sms sms, BladeRedis bladeRedis) {
        SmsProperties smsProperties = new SmsProperties();
        smsProperties.setTemplateId(sms.getTemplateId());
        smsProperties.setAccessKey(sms.getAccessKey());
        smsProperties.setSecretKey(sms.getSecretKey());
        smsProperties.setRegionId(sms.getRegionId());
        smsProperties.setSignName(sms.getSignName());
        IClientProfile profile = DefaultProfile.getProfile(smsProperties.getRegionId(), smsProperties.getAccessKey(), smsProperties.getSecretKey());
        IAcsClient acsClient = new DefaultAcsClient(profile);
        return new AliSmsTemplate(smsProperties, acsClient, bladeRedis);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/QiniuSmsBuilder.java
New file
@@ -0,0 +1,47 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.sms;
import com.qiniu.sms.SmsManager;
import com.qiniu.util.Auth;
import lombok.SneakyThrows;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.sms.SmsTemplate;
import org.springblade.core.sms.props.SmsProperties;
import org.springblade.core.sms.QiniuSmsTemplate;
import cn.gistack.resource.entity.Sms;
/**
 * 七牛云短信构建类
 *
 * @author Chill
 */
public class QiniuSmsBuilder {
    @SneakyThrows
    public static SmsTemplate template(Sms sms, BladeRedis bladeRedis) {
        SmsProperties smsProperties = new SmsProperties();
        smsProperties.setTemplateId(sms.getTemplateId());
        smsProperties.setAccessKey(sms.getAccessKey());
        smsProperties.setSecretKey(sms.getSecretKey());
        smsProperties.setSignName(sms.getSignName());
        Auth auth = Auth.create(smsProperties.getAccessKey(), smsProperties.getSecretKey());
        SmsManager smsManager = new SmsManager(auth);
        return new QiniuSmsTemplate(smsProperties, smsManager, bladeRedis);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/SmsBuilder.java
New file
@@ -0,0 +1,157 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.sms;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.sms.SmsTemplate;
import org.springblade.core.sms.enums.SmsEnum;
import org.springblade.core.sms.enums.SmsStatusEnum;
import org.springblade.core.sms.props.SmsProperties;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import cn.gistack.resource.entity.Sms;
import cn.gistack.resource.service.ISmsService;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.springblade.core.cache.constant.CacheConstant.RESOURCE_CACHE;
/**
 * Sms短信服务统一构建类
 *
 * @author Chill
 */
public class SmsBuilder {
    public static final String SMS_CODE = "sms:code:";
    public static final String SMS_PARAM_KEY = "code";
    private final SmsProperties smsProperties;
    private final ISmsService smsService;
    private final BladeRedis bladeRedis;
    public SmsBuilder(SmsProperties smsProperties, ISmsService smsService, BladeRedis bladeRedis) {
        this.smsProperties = smsProperties;
        this.smsService = smsService;
        this.bladeRedis = bladeRedis;
    }
    /**
     * SmsTemplate配置缓存池
     */
    private final Map<String, SmsTemplate> templatePool = new ConcurrentHashMap<>();
    /**
     * Sms配置缓存池
     */
    private final Map<String, Sms> smsPool = new ConcurrentHashMap<>();
    /**
     * 获取template
     *
     * @return SmsTemplate
     */
    public SmsTemplate template() {
        return template(StringPool.EMPTY);
    }
    /**
     * 获取template
     *
     * @param code 资源编号
     * @return SmsTemplate
     */
    public SmsTemplate template(String code) {
        String tenantId = AuthUtil.getTenantId();
        Sms sms = getSms(tenantId, code);
        Sms smsCached = smsPool.get(tenantId);
        SmsTemplate template = templatePool.get(tenantId);
        // 若为空或者不一致,则重新加载
        if (Func.hasEmpty(template, smsCached) || !sms.getTemplateId().equals(smsCached.getTemplateId()) || !sms.getAccessKey().equals(smsCached.getAccessKey())) {
            synchronized (SmsBuilder.class) {
                template = templatePool.get(tenantId);
                if (Func.hasEmpty(template, smsCached) || !sms.getTemplateId().equals(smsCached.getTemplateId()) || !sms.getAccessKey().equals(smsCached.getAccessKey())) {
                    if (sms.getCategory() == SmsEnum.YUNPIAN.getCategory()) {
                        template = YunpianSmsBuilder.template(sms, bladeRedis);
                    } else if (sms.getCategory() == SmsEnum.QINIU.getCategory()) {
                        template = QiniuSmsBuilder.template(sms, bladeRedis);
                    } else if (sms.getCategory() == SmsEnum.ALI.getCategory()) {
                        template = AliSmsBuilder.template(sms, bladeRedis);
                    } else if (sms.getCategory() == SmsEnum.TENCENT.getCategory()) {
                        template = TencentSmsBuilder.template(sms, bladeRedis);
                    }
                    templatePool.put(tenantId, template);
                    smsPool.put(tenantId, sms);
                }
            }
        }
        return template;
    }
    /**
     * 获取短信实体
     *
     * @param tenantId 租户ID
     * @return Sms
     */
    public Sms getSms(String tenantId, String code) {
        String key = tenantId;
        LambdaQueryWrapper<Sms> lqw = Wrappers.<Sms>query().lambda().eq(Sms::getTenantId, tenantId);
        // 获取传参的资源编号并查询,若有则返回,若没有则调启用的配置
        String smsCode = StringUtil.isBlank(code) ? WebUtil.getParameter(SMS_PARAM_KEY) : code;
        if (StringUtil.isNotBlank(smsCode)) {
            key = key.concat(StringPool.DASH).concat(smsCode);
            lqw.eq(Sms::getSmsCode, smsCode);
        } else {
            lqw.eq(Sms::getStatus, SmsStatusEnum.ENABLE.getNum());
        }
        Sms sms = CacheUtil.get(RESOURCE_CACHE, SMS_CODE, key, () -> {
            Sms s = smsService.getOne(lqw);
            // 若为空则调用默认配置
            if ((Func.isEmpty(s))) {
                Sms defaultSms = new Sms();
                defaultSms.setId(0L);
                defaultSms.setTemplateId(smsProperties.getTemplateId());
                defaultSms.setRegionId(smsProperties.getRegionId());
                defaultSms.setCategory(SmsEnum.of(smsProperties.getName()).getCategory());
                defaultSms.setAccessKey(smsProperties.getAccessKey());
                defaultSms.setSecretKey(smsProperties.getSecretKey());
                defaultSms.setSignName(smsProperties.getSignName());
                return defaultSms;
            } else {
                return s;
            }
        });
        if (sms == null || sms.getId() == null) {
            throw new ServiceException("未获取到对应的短信配置");
        } else {
            return sms;
        }
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/TencentSmsBuilder.java
New file
@@ -0,0 +1,46 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.sms;
import com.github.qcloudsms.SmsMultiSender;
import lombok.SneakyThrows;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.sms.SmsTemplate;
import org.springblade.core.sms.props.SmsProperties;
import org.springblade.core.sms.TencentSmsTemplate;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Sms;
/**
 * 腾讯云短信构建类
 *
 * @author Chill
 */
public class TencentSmsBuilder {
    @SneakyThrows
    public static SmsTemplate template(Sms sms, BladeRedis bladeRedis) {
        SmsProperties smsProperties = new SmsProperties();
        smsProperties.setTemplateId(sms.getTemplateId());
        smsProperties.setAccessKey(sms.getAccessKey());
        smsProperties.setSecretKey(sms.getSecretKey());
        smsProperties.setSignName(sms.getSignName());
        SmsMultiSender smsSender = new SmsMultiSender(Func.toInt(smsProperties.getAccessKey()), sms.getSecretKey());
        return new TencentSmsTemplate(smsProperties, smsSender, bladeRedis);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/builder/sms/YunpianSmsBuilder.java
New file
@@ -0,0 +1,44 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.builder.sms;
import com.yunpian.sdk.YunpianClient;
import lombok.SneakyThrows;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.sms.SmsTemplate;
import org.springblade.core.sms.props.SmsProperties;
import org.springblade.core.sms.YunpianSmsTemplate;
import cn.gistack.resource.entity.Sms;
/**
 * 云片短信构建类
 *
 * @author Chill
 */
public class YunpianSmsBuilder {
    @SneakyThrows
    public static SmsTemplate template(Sms sms, BladeRedis bladeRedis) {
        SmsProperties smsProperties = new SmsProperties();
        smsProperties.setTemplateId(sms.getTemplateId());
        smsProperties.setAccessKey(sms.getAccessKey());
        smsProperties.setSignName(sms.getSignName());
        YunpianClient client = new YunpianClient(smsProperties.getAccessKey()).init();
        return new YunpianSmsTemplate(smsProperties, client, bladeRedis);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/config/BladeOssConfiguration.java
New file
@@ -0,0 +1,44 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.config;
import lombok.AllArgsConstructor;
import org.springblade.core.oss.props.OssProperties;
import cn.gistack.resource.builder.oss.OssBuilder;
import cn.gistack.resource.service.IOssService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Oss配置类
 *
 * @author Chill
 */
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
public class BladeOssConfiguration {
    private final OssProperties ossProperties;
    private final IOssService ossService;
    @Bean
    public OssBuilder ossBuilder() {
        return new OssBuilder(ossProperties, ossService);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/config/BladeSmsConfiguration.java
New file
@@ -0,0 +1,47 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.config;
import lombok.AllArgsConstructor;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.sms.props.SmsProperties;
import cn.gistack.resource.builder.sms.SmsBuilder;
import cn.gistack.resource.service.ISmsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Sms配置类
 *
 * @author Chill
 */
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
public class BladeSmsConfiguration {
    private final SmsProperties smsProperties;
    private final ISmsService smsService;
    private final BladeRedis bladeRedis;
    @Bean
    public SmsBuilder smsBuilder() {
        return new SmsBuilder(smsProperties, smsService, bladeRedis);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/controller/AttachController.java
New file
@@ -0,0 +1,127 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.controller;
import cn.gistack.resource.service.IAttachService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Attach;
import cn.gistack.resource.vo.AttachVO;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
 * 附件表 控制器
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/attach")
@Api(value = "附件", tags = "附件")
public class AttachController extends BladeController {
    private final IAttachService attachService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入attach")
    public R<Attach> detail(Attach attach) {
        Attach detail = attachService.getOne(Condition.getQueryWrapper(attach));
        return R.data(detail);
    }
    /**
     * 分页 附件表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入attach")
    public R<IPage<Attach>> list(Attach attach, Query query) {
        IPage<Attach> pages = attachService.page(Condition.getPage(query), Condition.getQueryWrapper(attach));
        return R.data(pages);
    }
    /**
     * 自定义分页 附件表
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入attach")
    public R<IPage<AttachVO>> page(AttachVO attach, Query query) {
        IPage<AttachVO> pages = attachService.selectAttachPage(Condition.getPage(query), attach);
        return R.data(pages);
    }
    /**
     * 新增 附件表
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入attach")
    public R save(@Valid @RequestBody Attach attach) {
        return R.status(attachService.save(attach));
    }
    /**
     * 修改 附件表
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入attach")
    public R update(@Valid @RequestBody Attach attach) {
        return R.status(attachService.updateById(attach));
    }
    /**
     * 新增或修改 附件表
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入attach")
    public R submit(@Valid @RequestBody Attach attach) {
        return R.status(attachService.saveOrUpdate(attach));
    }
    /**
     * 删除 附件表
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(attachService.deleteLogic(Func.toLongList(ids)));
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/controller/OssController.java
New file
@@ -0,0 +1,151 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.controller;
import cn.gistack.resource.service.IOssService;
import cn.gistack.resource.wrapper.OssWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Oss;
import cn.gistack.resource.vo.OssVO;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.validation.Valid;
import static org.springblade.core.cache.constant.CacheConstant.RESOURCE_CACHE;
/**
 * 控制器
 *
 * @author BladeX
 */
@NonDS
@ApiIgnore
@RestController
@AllArgsConstructor
@RequestMapping("/oss")
@PreAuth(RoleConstant.HAS_ROLE_ADMIN)
@Api(value = "对象存储接口", tags = "对象存储接口")
public class OssController extends BladeController {
    private final IOssService ossService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入oss")
    public R<OssVO> detail(Oss oss) {
        Oss detail = ossService.getOne(Condition.getQueryWrapper(oss));
        return R.data(OssWrapper.build().entityVO(detail));
    }
    /**
     * 分页
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入oss")
    public R<IPage<OssVO>> list(Oss oss, Query query) {
        IPage<Oss> pages = ossService.page(Condition.getPage(query), Condition.getQueryWrapper(oss));
        return R.data(OssWrapper.build().pageVO(pages));
    }
    /**
     * 自定义分页
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入oss")
    public R<IPage<OssVO>> page(OssVO oss, Query query) {
        IPage<OssVO> pages = ossService.selectOssPage(Condition.getPage(query), oss);
        return R.data(pages);
    }
    /**
     * 新增
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入oss")
    public R save(@Valid @RequestBody Oss oss) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(ossService.save(oss));
    }
    /**
     * 修改
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入oss")
    public R update(@Valid @RequestBody Oss oss) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(ossService.updateById(oss));
    }
    /**
     * 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入oss")
    public R submit(@Valid @RequestBody Oss oss) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(ossService.submit(oss));
    }
    /**
     * 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(ossService.deleteLogic(Func.toLongList(ids)));
    }
    /**
     * 启用
     */
    @PostMapping("/enable")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "配置启用", notes = "传入id")
    public R enable(@ApiParam(value = "主键", required = true) @RequestParam Long id) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(ossService.enable(id));
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/controller/SmsController.java
New file
@@ -0,0 +1,152 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.controller;
import cn.gistack.resource.service.ISmsService;
import cn.gistack.resource.wrapper.SmsWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Sms;
import cn.gistack.resource.vo.SmsVO;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.validation.Valid;
import static org.springblade.core.cache.constant.CacheConstant.RESOURCE_CACHE;
/**
 * 短信配置表 控制器
 *
 * @author BladeX
 */
@NonDS
@ApiIgnore
@RestController
@AllArgsConstructor
@RequestMapping("/sms")
@PreAuth(RoleConstant.HAS_ROLE_ADMIN)
@Api(value = "短信配置表", tags = "短信配置表接口")
public class SmsController extends BladeController {
    private final ISmsService smsService;
    /**
     * 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入sms")
    public R<SmsVO> detail(Sms sms) {
        Sms detail = smsService.getOne(Condition.getQueryWrapper(sms));
        return R.data(SmsWrapper.build().entityVO(detail));
    }
    /**
     * 分页 短信配置表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入sms")
    public R<IPage<SmsVO>> list(Sms sms, Query query) {
        IPage<Sms> pages = smsService.page(Condition.getPage(query), Condition.getQueryWrapper(sms));
        return R.data(SmsWrapper.build().pageVO(pages));
    }
    /**
     * 自定义分页 短信配置表
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入sms")
    public R<IPage<SmsVO>> page(SmsVO sms, Query query) {
        IPage<SmsVO> pages = smsService.selectSmsPage(Condition.getPage(query), sms);
        return R.data(pages);
    }
    /**
     * 新增 短信配置表
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入sms")
    public R save(@Valid @RequestBody Sms sms) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(smsService.save(sms));
    }
    /**
     * 修改 短信配置表
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入sms")
    public R update(@Valid @RequestBody Sms sms) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(smsService.updateById(sms));
    }
    /**
     * 新增或修改 短信配置表
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入sms")
    public R submit(@Valid @RequestBody Sms sms) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(smsService.submit(sms));
    }
    /**
     * 删除 短信配置表
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(smsService.deleteLogic(Func.toLongList(ids)));
    }
    /**
     * 启用
     */
    @PostMapping("/enable")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "配置启用", notes = "传入id")
    public R enable(@ApiParam(value = "主键", required = true) @RequestParam Long id) {
        CacheUtil.clear(RESOURCE_CACHE);
        return R.status(smsService.enable(id));
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/endpoint/OssEndpoint.java
New file
@@ -0,0 +1,246 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.endpoint;
import cn.gistack.resource.builder.oss.OssBuilder;
import cn.gistack.resource.service.IAttachService;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.oss.model.BladeFile;
import org.springblade.core.oss.model.OssFile;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.FileUtil;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Attach;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
 * 对象存储端点
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/oss/endpoint")
@Api(value = "对象存储端点", tags = "对象存储端点")
public class OssEndpoint {
    /**
     * 对象存储构建类
     */
    private final OssBuilder ossBuilder;
    /**
     * 附件表服务
     */
    private final IAttachService attachService;
    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     * @return Bucket
     */
    @SneakyThrows
    @PostMapping("/make-bucket")
    @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
    public R makeBucket(@RequestParam String bucketName) {
        ossBuilder.template().makeBucket(bucketName);
        return R.success("创建成功");
    }
    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     * @return R
     */
    @SneakyThrows
    @PostMapping("/remove-bucket")
    @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
    public R removeBucket(@RequestParam String bucketName) {
        ossBuilder.template().removeBucket(bucketName);
        return R.success("删除成功");
    }
    /**
     * 拷贝文件
     *
     * @param fileName       存储桶对象名称
     * @param destBucketName 目标存储桶名称
     * @param destFileName   目标存储桶对象名称
     * @return R
     */
    @SneakyThrows
    @PostMapping("/copy-file")
    public R copyFile(@RequestParam String fileName, @RequestParam String destBucketName, String destFileName) {
        ossBuilder.template().copyFile(fileName, destBucketName, destFileName);
        return R.success("操作成功");
    }
    /**
     * 获取文件信息
     *
     * @param fileName 存储桶对象名称
     * @return InputStream
     */
    @SneakyThrows
    @GetMapping("/stat-file")
    public R<OssFile> statFile(@RequestParam String fileName) {
        return R.data(ossBuilder.template().statFile(fileName));
    }
    /**
     * 获取文件相对路径
     *
     * @param fileName 存储桶对象名称
     * @return String
     */
    @SneakyThrows
    @GetMapping("/file-path")
    public R<String> filePath(@RequestParam String fileName) {
        return R.data(ossBuilder.template().filePath(fileName));
    }
    /**
     * 获取文件外链
     *
     * @param fileName 存储桶对象名称
     * @return String
     */
    @SneakyThrows
    @GetMapping("/file-link")
    public R<String> fileLink(@RequestParam String fileName) {
        return R.data(ossBuilder.template().fileLink(fileName));
    }
    /**
     * 上传文件
     *
     * @param file 文件
     * @return ObjectStat
     */
    @SneakyThrows
    @PostMapping("/put-file")
    public R<BladeFile> putFile(@RequestParam MultipartFile file) {
        BladeFile bladeFile = ossBuilder.template().putFile(file.getOriginalFilename(), file.getInputStream());
        return R.data(bladeFile);
    }
    /**
     * 上传文件
     *
     * @param fileName 存储桶对象名称
     * @param file     文件
     * @return ObjectStat
     */
    @SneakyThrows
    @PostMapping("/put-file-by-name")
    public R<BladeFile> putFile(@RequestParam String fileName, @RequestParam MultipartFile file) {
        BladeFile bladeFile = ossBuilder.template().putFile(fileName, file.getInputStream());
        return R.data(bladeFile);
    }
    /**
     * 上传文件并保存至附件表
     *
     * @param file 文件
     * @return ObjectStat
     */
    @SneakyThrows
    @PostMapping("/put-file-attach")
    public R<BladeFile> putFileAttach(@RequestParam MultipartFile file) {
        String fileName = file.getOriginalFilename();
        BladeFile bladeFile = ossBuilder.template().putFile(fileName, file.getInputStream());
        Long attachId = buildAttach(fileName, file.getSize(), bladeFile);
        bladeFile.setAttachId(attachId);
        return R.data(bladeFile);
    }
    /**
     * 上传文件并保存至附件表
     *
     * @param fileName 存储桶对象名称
     * @param file     文件
     * @return ObjectStat
     */
    @SneakyThrows
    @PostMapping("/put-file-attach-by-name")
    public R<BladeFile> putFileAttach(@RequestParam String fileName, @RequestParam MultipartFile file) {
        BladeFile bladeFile = ossBuilder.template().putFile(fileName, file.getInputStream());
        Long attachId = buildAttach(fileName, file.getSize(), bladeFile);
        bladeFile.setAttachId(attachId);
        return R.data(bladeFile);
    }
    /**
     * 构建附件表
     *
     * @param fileName  文件名
     * @param fileSize  文件大小
     * @param bladeFile 对象存储文件
     * @return attachId
     */
    private Long buildAttach(String fileName, Long fileSize, BladeFile bladeFile) {
        String fileExtension = FileUtil.getFileExtension(fileName);
        Attach attach = new Attach();
        attach.setDomainUrl(bladeFile.getDomain());
        attach.setLink(bladeFile.getLink());
        attach.setName(bladeFile.getName());
        attach.setOriginalName(bladeFile.getOriginalName());
        attach.setAttachSize(fileSize);
        attach.setExtension(fileExtension);
        attachService.save(attach);
        return attach.getId();
    }
    /**
     * 删除文件
     *
     * @param fileName 存储桶对象名称
     * @return R
     */
    @SneakyThrows
    @PostMapping("/remove-file")
    @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
    public R removeFile(@RequestParam String fileName) {
        ossBuilder.template().removeFile(fileName);
        return R.success("操作成功");
    }
    /**
     * 批量删除文件
     *
     * @param fileNames 存储桶对象名称集合
     * @return R
     */
    @SneakyThrows
    @PostMapping("/remove-files")
    @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
    public R removeFiles(@RequestParam String fileNames) {
        ossBuilder.template().removeFiles(Func.toStrList(fileNames));
        return R.success("操作成功");
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/endpoint/SmsEndpoint.java
New file
@@ -0,0 +1,176 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.endpoint;
import cn.gistack.resource.builder.sms.SmsBuilder;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.sms.model.SmsCode;
import org.springblade.core.sms.model.SmsData;
import org.springblade.core.sms.model.SmsResponse;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.Func;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import static cn.gistack.resource.utils.SmsUtil.*;
/**
 * 短信服务端点
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
@RequestMapping("/sms/endpoint")
@Api(value = "短信服务端点", tags = "短信服务端点")
public class SmsEndpoint {
    /**
     * 短信服务构建类
     */
    private final SmsBuilder smsBuilder;
    //================================= 短信服务校验 =================================
    /**
     * 短信验证码发送
     *
     * @param phone 手机号
     */
    @SneakyThrows
    @PostMapping("/send-validate")
    public R sendValidate(@RequestParam String phone) {
        Map<String, String> params = getValidateParams();
        SmsCode smsCode = smsBuilder.template().sendValidate(new SmsData(params).setKey(PARAM_KEY), phone);
        return smsCode.isSuccess() ? R.data(smsCode, SEND_SUCCESS) : R.fail(SEND_FAIL);
    }
    /**
     * 校验短信
     *
     * @param smsCode 短信校验信息
     */
    @SneakyThrows
    @PostMapping("/validate-message")
    public R validateMessage(SmsCode smsCode) {
        boolean validate = smsBuilder.template().validateMessage(smsCode);
        return validate ? R.success(VALIDATE_SUCCESS) : R.fail(VALIDATE_FAIL);
    }
    //========== 通用短信自定义发送(支持自定义params参数传递, 推荐用于测试, 不推荐用于生产环境) ==========
    /**
     * 发送信息
     *
     * @param params 自定义短信参数
     * @param phones 手机号集合
     */
    @SneakyThrows
    @PostMapping("/send-message")
    public R sendMessage(@RequestParam String code, @RequestParam String params, @RequestParam String phones) {
        SmsData smsData = new SmsData(JsonUtil.readMap(params, String.class, String.class));
        return send(code, smsData, phones);
    }
    //========== 指定短信服务发送(可根据各种场景自定拓展定制, 损失灵活性增加安全性, 推荐用于生产环境) ==========
    /**
     * 短信通知
     *
     * @param phones 手机号集合
     */
    @SneakyThrows
    @PostMapping("/send-notice")
    public R sendNotice(@RequestParam String phones) {
        Map<String, String> params = new HashMap<>(3);
        params.put("title", "通知标题");
        params.put("content", "通知内容");
        params.put("date", "通知时间");
        SmsData smsData = new SmsData(params);
        return send(smsData, phones);
    }
    /**
     * 订单通知
     *
     * @param phones 手机号集合
     */
    @SneakyThrows
    @PostMapping("/send-order")
    public R sendOrder(@RequestParam String phones) {
        Map<String, String> params = new HashMap<>(3);
        params.put("orderNo", "订单编号");
        params.put("packageNo", "快递单号");
        params.put("user", "收件人");
        SmsData smsData = new SmsData(params);
        return send(smsData, phones);
    }
    /**
     * 会议通知
     *
     * @param phones 手机号集合
     */
    @SneakyThrows
    @PostMapping("/send-meeting")
    public R sendMeeting(@RequestParam String phones) {
        Map<String, String> params = new HashMap<>(2);
        params.put("roomId", "会议室");
        params.put("topic", "会议主题");
        params.put("date", "会议时间");
        SmsData smsData = new SmsData(params);
        return send(smsData, phones);
    }
    //================================= 通用短信发送接口 =================================
    /**
     * 通用短信发送接口
     *
     * @param smsData 短信内容
     * @param phones  手机号列表
     * @return 是否发送成功
     */
    private R send(SmsData smsData, String phones) {
        SmsResponse response = smsBuilder.template().sendMessage(smsData, Func.toStrList(phones));
        return response.isSuccess() ? R.success(SEND_SUCCESS) : R.fail(SEND_FAIL);
    }
    /**
     * 通用短信发送接口
     *
     * @param code    资源编号
     * @param smsData 短信内容
     * @param phones  手机号列表
     * @return 是否发送成功
     */
    private R send(String code, SmsData smsData, String phones) {
        SmsResponse response = smsBuilder.template(code).sendMessage(smsData, Func.toStrList(phones));
        return response.isSuccess() ? R.success(SEND_SUCCESS) : R.fail(SEND_FAIL);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/feign/SmsClient.java
New file
@@ -0,0 +1,70 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.feign;
import lombok.AllArgsConstructor;
import org.springblade.core.sms.model.SmsCode;
import org.springblade.core.sms.model.SmsData;
import org.springblade.core.sms.model.SmsResponse;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.builder.sms.SmsBuilder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import static cn.gistack.resource.utils.SmsUtil.*;
/**
 * 短信远程调用服务
 *
 * @author Chill
 */
@NonDS
@RestController
@AllArgsConstructor
public class SmsClient implements ISmsClient {
    private final SmsBuilder smsBuilder;
    @Override
    @PostMapping(SEND_MESSAGE)
    public R<SmsResponse> sendMessage(String code, String params, String phones) {
        SmsData smsData = new SmsData(JsonUtil.readMap(params, String.class, String.class));
        SmsResponse response = smsBuilder.template(code).sendMessage(smsData, Func.toStrList(phones));
        return R.data(response);
    }
    @Override
    @PostMapping(SEND_VALIDATE)
    public R sendValidate(String code, String phone) {
        Map<String, String> params = getValidateParams();
        SmsCode smsCode = smsBuilder.template(code).sendValidate(new SmsData(params).setKey(PARAM_KEY), phone);
        return smsCode.isSuccess() ? R.data(smsCode, SEND_SUCCESS) : R.fail(SEND_FAIL);
    }
    @Override
    @PostMapping(VALIDATE_MESSAGE)
    public R validateMessage(String code, String id, String value, String phone) {
        SmsCode smsCode = new SmsCode().setId(id).setValue(value).setPhone(phone);
        boolean validate = smsBuilder.template(code).validateMessage(smsCode);
        return validate ? R.success(VALIDATE_SUCCESS) : R.fail(VALIDATE_FAIL);
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/AttachMapper.java
New file
@@ -0,0 +1,42 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.gistack.resource.entity.Attach;
import cn.gistack.resource.vo.AttachVO;
import java.util.List;
/**
 * 附件表 Mapper 接口
 *
 * @author Chill
 */
public interface AttachMapper extends BaseMapper<Attach> {
    /**
     * 自定义分页
     *
     * @param page
     * @param attach
     * @return
     */
    List<AttachVO> selectAttachPage(IPage page, AttachVO attach);
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/AttachMapper.xml
New file
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.resource.mapper.AttachMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="attachResultMap" type="cn.gistack.resource.entity.Attach">
        <result column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_dept" property="createDept"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="link" property="link"/>
        <result column="domain_url" property="domainUrl"/>
        <result column="name" property="name"/>
        <result column="original_name" property="originalName"/>
        <result column="extension" property="extension"/>
        <result column="attach_size" property="attachSize"/>
    </resultMap>
    <select id="selectAttachPage" resultMap="attachResultMap">
        select * from blade_attach where is_deleted = 0
    </select>
</mapper>
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/OssMapper.java
New file
@@ -0,0 +1,42 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.gistack.resource.entity.Oss;
import cn.gistack.resource.vo.OssVO;
import java.util.List;
/**
 *  Mapper 接口
 *
 * @author BladeX
 */
public interface OssMapper extends BaseMapper<Oss> {
    /**
     * 自定义分页
     *
     * @param page
     * @param oss
     * @return
     */
    List<OssVO> selectOssPage(IPage page, OssVO oss);
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/OssMapper.xml
New file
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.resource.mapper.OssMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="ossResultMap" type="cn.gistack.resource.entity.Oss">
        <result column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="oss_code" property="ossCode"/>
        <result column="category" property="category"/>
        <result column="endpoint" property="endpoint"/>
        <result column="access_key" property="accessKey"/>
        <result column="secret_key" property="secretKey"/>
        <result column="bucket_name" property="bucketName"/>
        <result column="app_id" property="appId"/>
        <result column="region" property="region"/>
        <result column="remark" property="remark"/>
    </resultMap>
    <select id="selectOssPage" resultMap="ossResultMap">
        select * from blade_oss where is_deleted = 0
    </select>
</mapper>
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/SmsMapper.java
New file
@@ -0,0 +1,41 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.mapper;
import cn.gistack.resource.entity.Sms;
import cn.gistack.resource.vo.SmsVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
 * 短信配置表 Mapper 接口
 *
 * @author BladeX
 */
public interface SmsMapper extends BaseMapper<Sms> {
    /**
     * 自定义分页
     *
     * @param page
     * @param sms
     * @return
     */
    List<SmsVO> selectSmsPage(IPage page, SmsVO sms);
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/mapper/SmsMapper.xml
New file
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.resource.mapper.SmsMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="smsResultMap" type="cn.gistack.resource.entity.Sms">
        <result column="id" property="id"/>
        <result column="create_user" property="createUser"/>
        <result column="create_dept" property="createDept"/>
        <result column="create_time" property="createTime"/>
        <result column="update_user" property="updateUser"/>
        <result column="update_time" property="updateTime"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="sms_code" property="smsCode"/>
        <result column="template_id" property="templateId"/>
        <result column="category" property="category"/>
        <result column="access_key" property="accessKey"/>
        <result column="secret_key" property="secretKey"/>
        <result column="region_id" property="regionId"/>
        <result column="sign_name" property="signName"/>
        <result column="remark" property="remark"/>
    </resultMap>
    <select id="selectSmsPage" resultMap="smsResultMap">
        select * from blade_sms where is_deleted = 0
    </select>
</mapper>
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/IAttachService.java
New file
@@ -0,0 +1,40 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import cn.gistack.resource.entity.Attach;
import cn.gistack.resource.vo.AttachVO;
/**
 * 附件表 服务类
 *
 * @author Chill
 */
public interface IAttachService extends BaseService<Attach> {
    /**
     * 自定义分页
     *
     * @param page
     * @param attach
     * @return
     */
    IPage<AttachVO> selectAttachPage(IPage<AttachVO> page, AttachVO attach);
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/IOssService.java
New file
@@ -0,0 +1,56 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import cn.gistack.resource.entity.Oss;
import cn.gistack.resource.vo.OssVO;
/**
 * 服务类
 *
 * @author BladeX
 */
public interface IOssService extends BaseService<Oss> {
    /**
     * 自定义分页
     *
     * @param page
     * @param oss
     * @return
     */
    IPage<OssVO> selectOssPage(IPage<OssVO> page, OssVO oss);
    /**
     * 提交oss信息
     *
     * @param oss
     * @return
     */
    boolean submit(Oss oss);
    /**
     * 启动配置
     *
     * @param id
     * @return
     */
    boolean enable(Long id);
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/ISmsService.java
New file
@@ -0,0 +1,56 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import cn.gistack.resource.entity.Sms;
import cn.gistack.resource.vo.SmsVO;
/**
 * 短信配置表 服务类
 *
 * @author BladeX
 */
public interface ISmsService extends BaseService<Sms> {
    /**
     * 自定义分页
     *
     * @param page
     * @param sms
     * @return
     */
    IPage<SmsVO> selectSmsPage(IPage<SmsVO> page, SmsVO sms);
    /**
     * 提交oss信息
     *
     * @param oss
     * @return
     */
    boolean submit(Sms oss);
    /**
     * 启动配置
     *
     * @param id
     * @return
     */
    boolean enable(Long id);
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/impl/AttachServiceImpl.java
New file
@@ -0,0 +1,40 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.service.impl;
import cn.gistack.resource.mapper.AttachMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseServiceImpl;
import cn.gistack.resource.entity.Attach;
import cn.gistack.resource.service.IAttachService;
import cn.gistack.resource.vo.AttachVO;
import org.springframework.stereotype.Service;
/**
 * 附件表 服务实现类
 *
 * @author Chill
 */
@Service
public class AttachServiceImpl extends BaseServiceImpl<AttachMapper, Attach> implements IAttachService {
    @Override
    public IPage<AttachVO> selectAttachPage(IPage<AttachVO> page, AttachVO attach) {
        return page.setRecords(baseMapper.selectAttachPage(page, attach));
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/impl/OssServiceImpl.java
New file
@@ -0,0 +1,67 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.service.impl;
import cn.gistack.resource.mapper.OssMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Oss;
import cn.gistack.resource.vo.OssVO;
import cn.gistack.resource.service.IOssService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * 服务实现类
 *
 * @author BladeX
 */
@Service
public class OssServiceImpl extends BaseServiceImpl<OssMapper, Oss> implements IOssService {
    @Override
    public IPage<OssVO> selectOssPage(IPage<OssVO> page, OssVO oss) {
        return page.setRecords(baseMapper.selectOssPage(page, oss));
    }
    @Override
    public boolean submit(Oss oss) {
        LambdaQueryWrapper<Oss> lqw = Wrappers.<Oss>query().lambda()
            .eq(Oss::getOssCode, oss.getOssCode()).eq(Oss::getTenantId, AuthUtil.getTenantId());
        Long cnt = baseMapper.selectCount(Func.isEmpty(oss.getId()) ? lqw : lqw.notIn(Oss::getId, oss.getId()));
        if (cnt > 0L) {
            throw new ServiceException("当前资源编号已存在!");
        }
        return this.saveOrUpdate(oss);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean enable(Long id) {
        // 先禁用
        boolean temp1 = this.update(Wrappers.<Oss>update().lambda().set(Oss::getStatus, 1));
        // 在启用
        boolean temp2 = this.update(Wrappers.<Oss>update().lambda().set(Oss::getStatus, 2).eq(Oss::getId, id));
        return temp1 && temp2;
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/service/impl/SmsServiceImpl.java
New file
@@ -0,0 +1,67 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.service.impl;
import cn.gistack.resource.mapper.SmsMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import cn.gistack.resource.entity.Sms;
import cn.gistack.resource.service.ISmsService;
import cn.gistack.resource.vo.SmsVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * 短信配置表 服务实现类
 *
 * @author BladeX
 */
@Service
public class SmsServiceImpl extends BaseServiceImpl<SmsMapper, Sms> implements ISmsService {
    @Override
    public IPage<SmsVO> selectSmsPage(IPage<SmsVO> page, SmsVO sms) {
        return page.setRecords(baseMapper.selectSmsPage(page, sms));
    }
    @Override
    public boolean submit(Sms sms) {
        LambdaQueryWrapper<Sms> lqw = Wrappers.<Sms>query().lambda()
            .eq(Sms::getSmsCode, sms.getSmsCode()).eq(Sms::getTenantId, AuthUtil.getTenantId());
        Long cnt = baseMapper.selectCount(Func.isEmpty(sms.getId()) ? lqw : lqw.notIn(Sms::getId, sms.getId()));
        if (cnt > 0L) {
            throw new ServiceException("当前资源编号已存在!");
        }
        return this.saveOrUpdate(sms);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean enable(Long id) {
        // 先禁用
        boolean temp1 = this.update(Wrappers.<Sms>update().lambda().set(Sms::getStatus, 1));
        // 在启用
        boolean temp2 = this.update(Wrappers.<Sms>update().lambda().set(Sms::getStatus, 2).eq(Sms::getId, id));
        return temp1 && temp2;
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/wrapper/OssWrapper.java
New file
@@ -0,0 +1,49 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.wrapper;
import cn.gistack.system.cache.DictCache;
import cn.gistack.system.enums.DictEnum;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import cn.gistack.resource.entity.Oss;
import cn.gistack.resource.vo.OssVO;
import java.util.Objects;
/**
 * 包装类,返回视图层所需的字段
 *
 * @author BladeX
 */
public class OssWrapper extends BaseEntityWrapper<Oss, OssVO> {
    public static OssWrapper build() {
        return new OssWrapper();
    }
    @Override
    public OssVO entityVO(Oss oss) {
        OssVO ossVO = Objects.requireNonNull(BeanUtil.copy(oss, OssVO.class));
        String categoryName = DictCache.getValue(DictEnum.OSS, oss.getCategory());
        String statusName = DictCache.getValue(DictEnum.YES_NO, oss.getStatus());
        ossVO.setCategoryName(categoryName);
        ossVO.setStatusName(statusName);
        return ossVO;
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/java/cn/gistack/resource/wrapper/SmsWrapper.java
New file
@@ -0,0 +1,49 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.resource.wrapper;
import cn.gistack.system.cache.DictCache;
import cn.gistack.system.enums.DictEnum;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import cn.gistack.resource.entity.Sms;
import cn.gistack.resource.vo.SmsVO;
import java.util.Objects;
/**
 * 短信配置表包装类,返回视图层所需的字段
 *
 * @author BladeX
 */
public class SmsWrapper extends BaseEntityWrapper<Sms, SmsVO> {
    public static SmsWrapper build() {
        return new SmsWrapper();
    }
    @Override
    public SmsVO entityVO(Sms sms) {
        SmsVO smsVO = Objects.requireNonNull(BeanUtil.copy(sms, SmsVO.class));
        String categoryName = DictCache.getValue(DictEnum.SMS, sms.getCategory());
        String statusName = DictCache.getValue(DictEnum.YES_NO, sms.getStatus());
        smsVO.setCategoryName(categoryName);
        smsVO.setStatusName(statusName);
        return smsVO;
    }
}
skjcmanager-ops/skjcmanager-resource/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,7 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.dev.url}
    username: ${blade.datasource.dev.username}
    password: ${blade.datasource.dev.password}
skjcmanager-ops/skjcmanager-resource/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,7 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.prod.url}
    username: ${blade.datasource.prod.username}
    password: ${blade.datasource.prod.password}
skjcmanager-ops/skjcmanager-resource/src/main/resources/application-test.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.test.url}
    username: ${blade.datasource.test.username}
    password: ${blade.datasource.test.password}
skjcmanager-ops/skjcmanager-resource/src/main/resources/application.yml
New file
@@ -0,0 +1,13 @@
#服务器端口
server:
  port: 8010
#默认对象存储配置
oss:
  enabled: true
  name: minio
  tenant-mode: true
  endpoint: http://127.0.0.1:9000
  access-key: D99KGE6ZTQXSATTJWU24
  secret-key: QyVqGnhIQQE734UYSUFlGOZViE6+ZlDEfUG3NjhJ
  bucket-name: bladex
skjcmanager-ops/skjcmanager-swagger/Dockerfile
New file
@@ -0,0 +1,15 @@
FROM bladex/alpine-java:openjdk8-openj9_cn_slim
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/swagger
WORKDIR /blade/swagger
EXPOSE 18000
ADD ./target/blade-swagger.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]
skjcmanager-ops/skjcmanager-swagger/pom.xml
New file
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-swagger</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-cloud</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-aggregation-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-swagger/src/main/java/cn/gistack/swagger/SwaggerApplication.java
New file
@@ -0,0 +1,36 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package cn.gistack.swagger;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
/**
 * swagger聚合启动器
 *
 * @author Chill
 */
@BladeCloudApplication
public class SwaggerApplication {
    public static void main(String[] args) {
        BladeApplication.run(AppConstant.APPLICATION_SWAGGER_NAME, SwaggerApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,12 @@
knife4j:
  cloud:
    routes:
      - name: 授权模块
        uri: 127.0.0.1
        location: /blade-auth/v2/api-docs
      - name: 工作台模块
        uri: 127.0.0.1
        location: /blade-desk/v2/api-docs
      - name: 系统模块
        uri: 127.0.0.1
        location: /blade-system/v2/api-docs
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,12 @@
knife4j:
  cloud:
    routes:
      - name: 授权模块
        uri: 192.168.0.157:88
        location: /blade-auth/v2/api-docs
      - name: 工作台模块
        uri: 192.168.0.157:88
        location: /blade-desk/v2/api-docs
      - name: 系统模块
        uri: 192.168.0.157:88
        location: /blade-system/v2/api-docs
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application-test.yml
New file
@@ -0,0 +1,12 @@
knife4j:
  cloud:
    routes:
      - name: 授权模块
        uri: 192.168.0.157:88
        location: /blade-auth/v2/api-docs
      - name: 工作台模块
        uri: 192.168.0.157:88
        location: /blade-desk/v2/api-docs
      - name: 系统模块
        uri: 192.168.0.157:88
        location: /blade-system/v2/api-docs
skjcmanager-ops/skjcmanager-swagger/src/main/resources/application.yml
New file
@@ -0,0 +1,6 @@
server:
  port: 18000
knife4j:
  enableAggregation: true
  cloud:
    enable: true
skjcmanager-ops/skjcmanager-swagger/src/main/resources/banner.txt
New file
@@ -0,0 +1,8 @@
${AnsiColor.BLUE}                   ______  _             _       ___   ___
${AnsiColor.BLUE}                   | ___ \| |           | |      \  \ /  /
${AnsiColor.BLUE}                   | |_/ /| |  __ _   __| |  ___  \  V  /
${AnsiColor.BLUE}                   | ___ \| | / _` | / _` | / _ \   > <
${AnsiColor.BLUE}                   | |_/ /| || (_| || (_| ||  __/ /  .  \
${AnsiColor.BLUE}                   \____/ |_| \__,_| \__,_| \___|/__/ \__\
${AnsiColor.BLUE}:: BladeX ${blade.service.version} :: ${spring.application.name}:${AnsiColor.RED}${blade.env}${AnsiColor.BLUE} :: Running SpringBoot ${spring-boot.version} :: ${AnsiColor.BRIGHT_BLACK}
skjcmanager-ops/skjcmanager-xxljob-admin/Dockerfile
New file
@@ -0,0 +1,13 @@
FROM bladex/alpine-java:openjdk8-openj9_cn_slim
MAINTAINER bladejava@qq.com
RUN mkdir -p /blade/xxljob-admin
WORKDIR /blade/xxljob-admin
EXPOSE 7009
ADD ./target/blade-xxljob-admin.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
skjcmanager-ops/skjcmanager-xxljob-admin/doc/XXL-JOB官方文档.md
New file
@@ -0,0 +1,1740 @@
## 《分布式任务调度平台XXL-JOB》
[![Actions Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/)
[![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases)
[![GitHub stars](https://img.shields.io/github/stars/xuxueli/xxl-job)](https://github.com/xuxueli/xxl-job/)
[![Docker Status](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html)
[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat)](https://www.xuxueli.com/page/donate.html)
[TOCM]
[TOC]
## 一、简介
### 1.1 概述
XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
### 1.2 社区交流
- [社区交流](https://www.xuxueli.com/page/community.html)
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
- 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
- 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
- 9、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
- 10、任务超时控制:支持自定义任务超时时间,任务运行超时将会主动中断任务;
- 11、任务失败重试:支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;其中分片任务支持分片粒度的失败重试;
- 12、任务失败告警;默认提供邮件方式失败告警,同时预留扩展接口,可方便的扩展短信、钉钉等告警方式;
- 13、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 14、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 15、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
- 16、任务进度监控:支持实时监控任务进度;
- 17、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
- 18、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
- 19、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS、PHP、PowerShell等类型脚本;
- 20、命令行任务:原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可;
- 21、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 22、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 23、自定义任务参数:支持在线配置调度任务入参,即时生效;
- 24、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
- 25、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
- 26、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
- 27、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 28、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
- 29、全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行;
- 30、跨平台:原生提供通用HTTP任务Handler(Bean任务,"HttpJobHandler");业务方只需要提供HTTP链接即可,不限制语言、平台;
- 31、国际化:调度中心支持国际化设置,提供中文、英文两种可选语言,默认为中文;
- 32、容器化:提供官方docker镜像,并实时更新推送dockerhub,进一步实现产品开箱即用;
- 33、线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性;
- 34、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
- 35、权限控制:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
### 1.4 发展
于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计……
于2015-11月,XXL-JOB终于RELEASE了第一个大版本V1.0, 随后我将之发布到OSCHINA,XXL-JOB在OSCHINA上获得了@红薯的热门推荐,同期分别达到了OSCHINA的“热门动弹”排行第一和git.oschina的开源软件月热度排行第一,在此特别感谢红薯,感谢大家的关注和支持。
于2015-12月,我将XXL-JOB发表到我司内部知识库,并且得到内部同事认可。
于2016-01月,我司展开XXL-JOB的内部接入和定制工作,在此感谢袁某和尹某两位同事的贡献,同时也感谢内部其他给与关注与支持的同事。
于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。
于2017-10-22,又拍云 Open Talk 联合 Spring Cloud 中国社区举办的 "[进击的微服务实战派上海站](https://opentalk.upyun.com/303.html)",我登台对XXL-JOB做了演讲,现场观众反响热烈并在会后与XXL-JOB用户热烈讨论交流。
于2017-12-11,XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
于2017-12-18,XXL-JOB参与"[2017年度最受欢迎中国开源软件](http://www.oschina.net/project/top_cn_2017?sort=1)"评比,在当时已录入的约九千个国产开源项目中角逐,最终进入了前30强。
于2018-01-15,XXL-JOB参与"[2017码云最火开源项目](https://www.oschina.net/news/92438/2017-mayun-top-50)"评比,在当时已录入的约六千五百个码云项目中角逐,最终进去了前20强。
于2018-04-14,iTechPlus在上海举办的 "[2018互联网开发者大会](http://www.itdks.com/eventlist/detail/2065)",我登台对XXL-JOB做了演讲,现场观众反响热烈并在会后与XXL-JOB用户热烈讨论交流。
于2018-05-27,在上海举办的 "[第75期开源中国源创会](https://www.oschina.net/event/2278742)" 的 "架构" 主题专场,我登台进行“基础架构与中间件图谱”主题演讲,台下上千位在场观众反响热烈([图文回顾](https://www.oschina.net/question/3802184_2280606) )。
于2018-12-05,XXL-JOB参与"[2018年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2018?sort=1)"评比,在当时已录入的一万多个开源项目中角逐,最终排名第19名。
于2019-12-10,XXL-JOB参与"[2019年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2019)"评比,在当时已录入的一万多个开源项目中角逐,最终排名"开发框架和基础组件类"第9名。
> 我司大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。
据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数十个版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。
至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止最新统计时间为止,XXL-JOB已接入的公司包括不限于:
    - 1、大众点评【美团点评】
    - 2、山东学而网络科技有限公司;
    - 3、安徽慧通互联科技有限公司;
    - 4、人人聚财金服;
    - 5、上海棠棣信息科技股份有限公司
    - 6、运满满【运满满】
    - 7、米其林 (中国区)【米其林】
    - 8、妈妈联盟
    - 9、九樱天下(北京)信息技术有限公司
    - 10、万普拉斯科技有限公司【一加手机】
    - 11、上海亿保健康管理有限公司
    - 12、海尔馨厨【海尔】
    - 13、河南大红包电子商务有限公司
    - 14、成都顺点科技有限公司
    - 15、深圳市怡亚通
    - 16、深圳麦亚信科技股份有限公司
    - 17、上海博莹科技信息技术有限公司
    - 18、中国平安科技有限公司【中国平安】
    - 19、杭州知时信息科技有限公司
    - 20、博莹科技(上海)有限公司
    - 21、成都依能股份有限责任公司
    - 22、湖南高阳通联信息技术有限公司
    - 23、深圳市邦德文化发展有限公司
    - 24、福建阿思可网络教育有限公司
    - 25、优信二手车【优信】
    - 26、上海悠游堂投资发展股份有限公司【悠游堂】
    - 27、北京粉笔蓝天科技有限公司
    - 28、中秀科技(无锡)有限公司
    - 29、武汉空心科技有限公司
    - 30、北京蚂蚁风暴科技有限公司
    - 31、四川互宜达科技有限公司
    - 32、钱包行云(北京)科技有限公司
    - 33、重庆欣才集团
    - 34、咪咕互动娱乐有限公司【中国移动】
    - 35、北京诺亦腾科技有限公司
    - 36、增长引擎(北京)信息技术有限公司
    - 37、北京英贝思科技有限公司
    - 38、刚泰集团
    - 39、深圳泰久信息系统股份有限公司
    - 40、随行付支付有限公司
    - 41、广州瀚农网络科技有限公司
    - 42、享点科技有限公司
    - 43、杭州比智科技有限公司
    - 44、圳临界线网络科技有限公司
    - 45、广州知识圈网络科技有限公司
    - 46、国誉商业上海有限公司
    - 47、海尔消费金融有限公司,嗨付、够花【海尔】
    - 48、广州巴图鲁信息科技有限公司
    - 49、深圳市鹏海运电子数据交换有限公司
    - 50、深圳市亚飞电子商务有限公司
    - 51、上海趣医网络有限公司
    - 52、聚金资本
    - 53、北京父母邦网络科技有限公司
    - 54、中山元赫软件科技有限公司
    - 55、中商惠民(北京)电子商务有限公司
    - 56、凯京集团
    - 57、华夏票联(北京)科技有限公司
    - 58、拍拍贷【拍拍贷】
    - 59、北京尚德机构在线教育有限公司
    - 60、任子行股份有限公司
    - 61、北京时态电子商务有限公司
    - 62、深圳卷皮网络科技有限公司
    - 63、北京安博通科技股份有限公司
    - 64、未来无线网
    - 65、厦门瓷禧网络有限公司
    - 66、北京递蓝科软件股份有限公司
    - 67、郑州创海软件科技公司
    - 68、北京国槐信息科技有限公司
    - 69、浪潮软件集团
    - 70、多立恒(北京)信息技术有限公司
    - 71、广州极迅客信息科技有限公司
    - 72、赫基(中国)集团股份有限公司
    - 73、海投汇
    - 74、上海润益创业孵化器管理股份有限公司
    - 75、汉纳森(厦门)数据股份有限公司
    - 76、安信信托
    - 77、岚儒财富
    - 78、捷道软件
    - 79、湖北享七网络科技有限公司
    - 80、湖南创发科技责任有限公司
    - 81、深圳小安时代互联网金融服务有限公司
    - 82、湖北享七网络科技有限公司
    - 83、钱包行云(北京)科技有限公司
    - 84、360金融【360】
    - 85、易企秀
    - 86、摩贝(上海)生物科技有限公司
    - 87、广东芯智慧科技有限公司
    - 88、联想集团【联想】
    - 89、怪兽充电
    - 90、行圆汽车
    - 91、深圳店店通科技邮箱公司
    - 92、京东【京东】
    - 93、米庄理财
    - 94、咖啡易融
    - 95、梧桐诚选
    - 96、恒大地产【恒大】
    - 97、昆明龙慧
    - 98、上海涩瑶软件
    - 99、易信【网易】
    - 100、铜板街
    - 101、杭州云若网络科技有限公司
    - 102、特百惠(中国)有限公司
    - 103、常山众卡运力供应链管理有限公司
    - 104、深圳立创电子商务有限公司
    - 105、杭州智诺科技股份有限公司
    - 106、北京云漾信息科技有限公司
    - 107、深圳市多银科技有限公司
    - 108、亲宝宝
    - 109、上海博卡软件科技有限公司
    - 110、智慧树在线教育平台
    - 111、米族金融
    - 112、北京辰森世纪
    - 113、云南滇医通
    - 114、广州市分领网络科技有限责任公司
    - 115、浙江微能科技有限公司
    - 116、上海馨飞电子商务有限公司
    - 117、上海宝尊电子商务有限公司
    - 118、直客通科技技术有限公司
    - 119、科度科技有限公司
    - 120、上海数慧系统技术有限公司
    - 121、我的医药网
    - 122、多粉平台
    - 123、铁甲二手机
    - 124、上海海新得数据技术有限公司
    - 125、深圳市珍爱网信息技术有限公司【珍爱网】
    - 126、小蜜蜂
    - 127、吉荣数科技
    - 128、上海恺域信息科技有限公司
    - 129、广州荔支网络有限公司【荔枝FM】
    - 130、杭州闪宝科技有限公司
    - 131、北京互联新网科技发展有限公司
    - 132、誉道科技
    - 133、山西兆盛房地产开发有限公司
    - 134、北京蓝睿通达科技有限公司
    - 135、月亮小屋(中国)有限公司【蓝月亮】
    - 136、青岛国瑞信息技术有限公司
    - 137、博雅云计算(北京)有限公司
    - 138、华泰证券香港子公司
    - 139、杭州东方通信软件技术有限公司
    - 140、武汉博晟安全技术股份有限公司
    - 141、深圳市六度人和科技有限公司
    - 142、杭州趣维科技有限公司(小影)
    - 143、宁波单车侠之家科技有限公司【单车侠】
    - 144、丁丁云康信息科技(北京)有限公司
    - 145、云钱袋
    - 146、南京中兴力维
    - 147、上海矽昌通信技术有限公司
    - 148、深圳萨科科技
    - 149、中通服创立科技有限责任公司
    - 150、深圳市对庄科技有限公司
    - 151、上证所信息网络有限公司
    - 152、杭州火烧云科技有限公司【婚礼纪】
    - 153、天津青芒果科技有限公司【芒果头条】
    - 154、长飞光纤光缆股份有限公司
    - 155、世纪凯歌(北京)医疗科技有限公司
    - 156、浙江霖梓控股有限公司
    - 157、江西腾飞网络技术有限公司
    - 158、安迅物流有限公司
    - 159、肉联网
    - 160、北京北广梯影广告传媒有限公司
    - 161、上海数慧系统技术有限公司
    - 162、大志天成
    - 163、上海云鹊医
    - 164、上海云鹊医
    - 165、墨迹天气【墨迹天气】
    - 166、上海逸橙信息科技有限公司
    - 167、沅朋物联
    - 168、杭州恒生云融网络科技有限公司
    - 169、绿米联创
    - 170、重庆易宠科技有限公司
    - 171、安徽引航科技有限公司(乐职网)
    - 172、上海数联医信企业发展有限公司
    - 173、良彬建材
    - 174、杭州求是同创网络科技有限公司
    - 175、荷马国际
    - 176、点雇网
    - 177、深圳市华星光电技术有限公司
    - 178、厦门神州鹰软件科技有限公司
    - 179、深圳市招商信诺人寿保险有限公司
    - 180、上海好屋网信息技术有限公司
    - 181、海信集团【海信】
    - 182、信凌可信息科技(上海)有限公司
    - 183、长春天成科技发展有限公司
    - 184、用友金融信息技术股份有限公司【用友】
    - 185、北京咖啡易融有限公司
    - 186、国投瑞银基金管理有限公司
    - 187、晋松(上海)网络信息技术有限公司
    - 188、深圳市随手科技有限公司【随手记】
    - 189、深圳水务科技有限公司
    - 190、易企秀【易企秀】
    - 191、北京磁云科技
    - 192、南京蜂泰互联网科技有限公司
    - 193、章鱼直播
    - 194、奖多多科技
    - 195、天津市神州商龙科技股份有限公司
    - 196、岩心科技
    - 197、车码科技(北京)有限公司
    - 198、贵阳市投资控股集团
    - 199、康旗股份
    - 200、龙腾出行
    - 201、杭州华量软件
    - 202、合肥顶岭医疗科技有限公司
    - 203、重庆表达式科技有限公司
    - 204、上海米道信息科技有限公司
    - 205、北京益友会科技有限公司
    - 206、北京融贯电子商务有限公司
    - 207、中国外汇交易中心
    - 208、中国外运股份有限公司
    - 209、中国上海晓圈教育科技有限公司
    - 210、普联软件股份有限公司
    - 211、北京科蓝软件股份有限公司
    - 212、江苏斯诺物联科技有限公司
    - 213、北京搜狐-狐友【搜狐】
    - 214、新大陆网商金融
    - 215、山东神码中税信息科技有限公司
    - 216、河南汇顺网络科技有限公司
    - 217、北京华夏思源科技发展有限公司
    - 218、上海东普信息科技有限公司
    - 219、上海鸣勃网络科技有限公司
    - 220、广东学苑教育发展有限公司
    - 221、深圳强时科技有限公司
    - 222、上海云砺信息科技有限公司
    - 223、重庆愉客行网络有限公司
    - 224、数云
    - 225、国家电网运检部
    - 226、杭州找趣
    - 227、浩鲸云计算科技股份有限公司
    - 228、科大讯飞【科大讯飞】
    - 229、杭州行装网络科技有限公司
    - 230、即有分期金融
    - 231、深圳法司德信息科技有限公司
    - 232、上海博复信息科技有限公司
    - 233、杭州云嘉云计算有限公司
    - 234、有家民宿(有家美宿)
    - 235、北京赢销通软件技术有限公司
    - 236、浙江聚有财金融服务外包有限公司
    - 237、易族智汇(北京)科技有限公司
    - 238、合肥顶岭医疗科技开发有限公司
    - 239、车船宝(深圳)旭珩科技有限公司)
    - 240、广州富力地产有限公司
    - 241、氢课(上海)教育科技有限公司
    - 242、武汉氪细胞网络技术有限公司
    - 243、杭州有云科技有限公司
    - 244、上海仙豆智能机器人有限公司
    - 245、拉卡拉支付股份有限公司【拉卡拉】
    - 246、虎彩印艺股份有限公司
    - 247、北京数微科技有限公司
    - 248、广东智瑞科技有限公司
    - 249、找钢网
    - 250、九机网
    - 251、杭州跑跑网络科技有限公司
    - 252、深圳未来云集
    - 253、杭州每日给力科技有限公司
    - 254、上海齐犇信息科技有限公司
    - 255、滴滴出行【滴滴】
    - 256、合肥云诊信息科技有限公司
    - 257、云知声智能科技股份有限公司
    - 258、南京坦道科技有限公司
    - 259、爱乐优(二手平台)
    - 260、猫眼电影(私有化部署)【猫眼电影】
    - 261、美团大象(私有化部署)【美团大象】
    - 262、作业帮教育科技(北京)有限公司【作业帮】
    - 263、北京小年糕互联网技术有限公司
    - 264、山东矩阵软件工程股份有限公司
    - 265、陕西国驿软件科技有限公司
    - 266、君开信息科技
    - 267、村鸟网络科技有限责任公司
    - 268、云南国际信托有限公司
    - 269、金智教育
    - 270、珠海市筑巢科技有限公司
    - 271、上海百胜软件股份有限公司
    - 272、深圳市科盾科技有限公司
    - 273、哈啰出行【哈啰】
    - 274、途虎养车【途虎】
    - 275、卡思优派人力资源集团
    - 276、南京观为智慧软件科技有限公司
    - 277、杭州城市大脑科技有限公司
    - 278、猿辅导【猿辅导】
    - 279、洛阳健创网络科技有限公司
    - 280、魔力耳朵
    - 281、亿阳信通
    - 282、上海招鲤科技有限公司
    - 283、四川商旅无忧科技服务有限公司
    - 284、UU跑腿
    - 285、北京老虎证券【老虎证券】
    - 286、悠活省吧(北京)网络科技有限公司
    - 287、F5未来商店
    - 288、深圳环阳通信息技术有限公司
    - 289、遠傳電信
    - ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
欢迎大家的关注和使用,XXL-JOB也将拥抱变化,持续发展。
### 1.5 下载
#### 文档地址
- [中文文档](https://www.xuxueli.com/xxl-job/)
- [English Documentation](https://www.xuxueli.com/xxl-job/en/)
#### 源码仓库地址
源码仓库地址 | Release Download
--- | ---
[https://github.com/xuxueli/xxl-job](https://github.com/xuxueli/xxl-job) | [Download](https://github.com/xuxueli/xxl-job/releases)
[http://gitee.com/xuxueli0323/xxl-job](http://gitee.com/xuxueli0323/xxl-job) | [Download](http://gitee.com/xuxueli0323/xxl-job/releases)
#### 中央仓库地址
```
<!-- http://repo1.maven.org/maven2/com/xuxueli/xxl-job-core/ -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${最新稳定版本}</version>
</dependency>
```
### 1.6 环境
- Maven3+
- Jdk1.7+
- Mysql5.7+
## 二、快速入门
### 2.1 初始化“调度数据库”
请下载项目源码并解压,获取 "调度数据库初始化SQL脚本" 并执行即可。
"调度数据库初始化SQL脚本" 位置为:
    /xxl-job/doc/db/tables_xxl_job.sql
调度中心支持集群部署,集群情况下各节点务必连接同一个mysql实例;
如果mysql做主从,调度中心集群节点务必强制走主库;
### 2.2 编译源码
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下:
    xxl-job-admin:调度中心
    xxl-job-core:公共依赖
    xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
        :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
        :xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用;
        :xxl-job-executor-sample-frameless:无框架版本;
        :xxl-job-executor-sample-jfinal:JFinal版本,通过JFinal管理执行器;
        :xxl-job-executor-sample-nutz:Nutz版本,通过Nutz管理执行器;
        :xxl-job-executor-sample-jboot:jboot版本,通过jboot管理执行器;
### 2.3 配置部署“调度中心”
    调度中心项目:xxl-job-admin
    作用:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。
#### 步骤一:调度中心配置:
调度中心配置文件地址:
    /xxl-job/xxl-job-admin/src/main/resources/application.properties
调度中心配置内容说明:
    ### 调度中心JDBC链接:链接地址请保持和 2.1章节 所创建的调度数据库的地址一致
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?Unicode=true&characterEncoding=UTF-8
    spring.datasource.username=root
    spring.datasource.password=root_pwd
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    ### 报警邮箱
    spring.mail.host=smtp.qq.com
    spring.mail.port=25
    spring.mail.username=xxx@qq.com
    spring.mail.password=xxx
    spring.mail.properties.mail.smtp.auth=true
    spring.mail.properties.mail.smtp.starttls.enable=true
    spring.mail.properties.mail.smtp.starttls.required=true
    spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
    ### 调度中心通讯TOKEN [选填]:非空时启用;
    xxl.job.accessToken=
    ### 调度中心国际化配置 [选填]: 默认为空,表示中文; "en" 表示英文;
    xxl.job.i18n=
    ## 调度线程池最大线程配置【必填】
    xxl.job.triggerpool.fast.max=200
    xxl.job.triggerpool.slow.max=100
    ### 调度中心日志表数据保存天数 [必填]:过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能;
    xxl.job.logretentiondays=30
#### 步骤二:部署项目:
如果已经正确进行上述配置,可将项目编译打包部署。
调度中心访问地址:http://localhost:8080/xxl-job-admin (该地址执行器将会使用到,作为回调地址)
默认登录账号 "admin/123456", 登录后运行界面如下图所示。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_6yC0.png "在这里输入图片标题")
至此“调度中心”项目已经部署成功。
#### 步骤三:调度中心集群(可选):
调度中心支持集群部署,提升调度系统容灾和可用性。
调度中心集群部署时,几点要求和建议:
- DB配置保持一致;
- 集群机器时钟保持一致(单机集群忽视);
- 建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
#### 其他:Docker 镜像方式搭建调度中心:
- 下载镜像
```
// Docker地址:https://hub.docker.com/r/xuxueli/xxl-job-admin/     (建议指定版本号)
docker pull xuxueli/xxl-job-admin
```
- 创建容器并运行
```
docker run -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d xuxueli/xxl-job-admin
/**
* 如需自定义 mysql 等配置,可通过 "PARAMS" 指定,参数格式 RAMS="--key=value  --key2=value2" ;
* 配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties
*/
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?Unicode=true&characterEncoding=UTF-8" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d xuxueli/xxl-job-admin
```
### 2.4 配置部署“执行器项目”
    “执行器”项目:xxl-job-executor-sample-springboot (提供多种版本执行器供选择,现以 springboot 版本为例,可直接使用,也可以参考其并将现有项目改造成执行器)
    作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。
#### 步骤一:maven依赖
确认pom文件中引入了 "xxl-job-core" 的maven依赖;
#### 步骤二:执行器配置
执行器配置,配置文件地址:
    /xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties
执行器配置,配置内容说明:
    ### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
    xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
    ### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
    xxl.job.executor.appname=xxl-job-executor-sample
    ### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
    xxl.job.executor.ip=
    ### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
    xxl.job.executor.port=9999
    ### 执行器通讯TOKEN [选填]:非空时启用;
    xxl.job.accessToken=
    ### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
    xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
    ### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
    xxl.job.executor.logretentiondays=30
#### 步骤三:执行器组件配置
执行器组件,配置文件地址:
    /xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java
执行器组件,配置内容说明:
```
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
    logger.info(">>>>>>>>>>> xxl-job config init.");
    XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
    xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
    xxlJobSpringExecutor.setAppName(appName);
    xxlJobSpringExecutor.setIp(ip);
    xxlJobSpringExecutor.setPort(port);
    xxlJobSpringExecutor.setAccessToken(accessToken);
    xxlJobSpringExecutor.setLogPath(logPath);
    xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
    return xxlJobSpringExecutor;
}
```
#### 步骤四:部署执行器项目:
如果已经正确进行上述配置,可将执行器项目编译打部署,系统提供多种执行器Sample示例项目,选择其中一个即可,各自的部署方式如下。
    xxl-job-executor-sample-springboot:项目编译打包成springboot类型的可执行JAR包,命令启动即可;
    xxl-job-executor-sample-spring:项目编译打包成WAR包,并部署到tomcat中。
    xxl-job-executor-sample-jfinal:同上
    xxl-job-executor-sample-nutz:同上
    xxl-job-executor-sample-jboot:同上
至此“执行器”项目已经部署结束。
#### 步骤五:执行器集群(可选):
执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
执行器集群部署时,几点要求和建议:
- 执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。
- 同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。
### 2.5 开发第一个任务“Hello World”
本示例以新建一个 “GLUE模式(Java)” 运行模式的任务为例。更多有关任务的详细配置,请查看“章节三:任务详解”。
( “GLUE模式(Java)”的执行代码托管到调度中心在线维护,相比“Bean模式任务”需要在执行器项目开发部署上线,更加简便轻量)
> 前提:请确认“调度中心”和“执行器”项目已经成功部署并启动;
#### 步骤一:新建任务:
登录调度中心,点击下图所示“新建任务”按钮,新建示例任务。然后,参考下面截图中任务的参数配置,点击保存。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_o8HQ.png "在这里输入图片标题")
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAsz.png "在这里输入图片标题")
#### 步骤二:“GLUE模式(Java)” 任务开发:
请点击任务右侧 “GLUE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。
( “GLUE模式(Java)” 运行模式的任务实际上是一段继承自IJobHandler的Java类代码,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务,详细介绍请查看第三章节)
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Fgql.png "在这里输入图片标题")
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_dNUJ.png "在这里输入图片标题")
#### 步骤三:触发执行:
请点击任务右侧 “执行” 按钮,可手动触发一次任务执行(通常情况下,通过配置Cron表达式进行任务调度出发)。
#### 步骤四:查看日志:
请点击任务右侧 “日志” 按钮,可前往任务日志界面查看任务日志。
在任务日志界面中,可查看该任务的历史调度记录以及每一次调度的任务调度信息、执行参数和执行信息。运行中的任务点击右侧的“执行日志”按钮,可进入日志控制台查看实时执行日志。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_inc8.png "在这里输入图片标题")
在日志控制台,可以Rolling方式实时查看任务在执行器一侧运行输出的日志信息,实时监控任务进度;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_eYrv.png "在这里输入图片标题")
## 三、任务详解
### 配置属性详细说明:
    - 执行器:任务的绑定的执行器,任务触发调度时将会自动发现注册成功的执行器, 实现任务自动发现功能; 另一方面也可以方便的进行任务分组。每个任务必须绑定一个执行器, 可在 "执行器管理" 进行设置;
    - 任务描述:任务的描述信息,便于任务管理;
    - 路由策略:当执行器集群部署时,提供丰富的路由策略,包括;
        FIRST(第一个):固定选择第一个机器;
        LAST(最后一个):固定选择最后一个机器;
        ROUND(轮询):;
        RANDOM(随机):随机选择在线的机器;
        CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
        LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
        LEAST_RECENTLY_USED(最近最久未使用):最久为使用的机器优先被选举;
        FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
        BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
        SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
    - Cron:触发任务执行的Cron表达式;
    - 运行模式:
        BEAN模式:任务以JobHandler方式维护在执行器端;需要结合 "JobHandler" 属性匹配执行器中任务;
        GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务;
        GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本;
        GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
        GLUE模式(PHP):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "php" 脚本;
        GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
        GLUE模式(PowerShell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "PowerShell" 脚本;
    - JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值;
    - 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
        单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
        丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
        覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
    - 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
    - 任务超时时间:支持自定义任务超时时间,任务运行超时将会主动中断任务;
    - 失败重试次数;支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;
    - 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔;
    - 负责人:任务的负责人;
    - 执行参数:任务执行所需的参数;
### 3.1 BEAN模式(类形式)
基于类的Bean模式开发方式,这是比较原始的一种开发方式。
- 优点:兼容性好、不限制项目环境,即使是无框架项目,如main方法直接启动的项目也可以提供支持,可以参考示例项目 "xxl-job-executor-sample-frameless";
- 缺点:每个任务需要占用一个Java类,比较浪费资源;而且,不支持自动扫描任务注入到执行器容器,需要手动注入。
#### 步骤一:执行器项目中,开发Job类:
    - 1、开发一个继承自"com.xxl.job.core.handler.IJobHandler"的JobHandler类。
    - 2、手动通过如下方式注入到执行器容器。
    ```
    XxlJobExecutor.registJobHandler("demoJobHandler", new DemoJobHandler());
    ```
#### 步骤二:调度中心,新建调度任务
后续步骤和 "3.2 BEAN模式(方法形式)"一致,可以前往参考。
### 3.2 BEAN模式(方法形式)
基于方法的Bean模式开发方式,这是比较推荐的开发方式。
- 优点:每个任务只需要开发一个方法,添加"@XxlJob"注解即可。更加方便、快速。会自动扫描任务注入到执行器容器。
- 缺点:要求Spring容器环境;
>基于方法开发的任务,底层会生成JobHandler代理,和基于类的方式一样,任务也会以JobHandler的形式存在于执行器任务容器中。
#### 步骤一:执行器项目中,开发Job方法:
    - 1、在Spring Bean实例中,开发Job方法,方式格式要求为 "public ReturnT<String> execute(String param)"
    - 2、为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
    - 3、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
```
// 可参考Sample示例执行器中的 "com.xxl.job.executor.service.jobhandler.SampleXxlJob" ,如下:
@XxlJob("demoJobHandler")
public ReturnT<String> execute(String param) {
    XxlJobLogger.log("hello world.");
    return ReturnT.SUCCESS;
}
```
#### 步骤二:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式",JobHandler属性填写任务注解“@XxlJob”中定义的值;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAsz.png "在这里输入图片标题")
#### 原生内置Bean模式任务
为方便用户参考与快速实用,示例执行器内原生提供多个Bean模式任务Handler,可以直接配置实用,如下:
- demoJobHandler:简单示例任务,任务内部模拟耗时任务逻辑,用户可在线体验Rolling Log等功能;
- shardingJobHandler:分片示例任务,任务内部模拟处理分片参数,可参考熟悉分片任务;
- httpJobHandler:通用HTTP任务Handler;业务方只需要提供HTTP链接即可,不限制语言、平台;
- commandJobHandler:通用命令行任务Handler;业务方只需要提供命令行即可;如 “pwd”命令;
### 3.3 GLUE模式(Java)
任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。开发流程如下:
#### 步骤一:调度中心,新建调度任务:
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Java)";
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_tJOq.png "在这里输入图片标题")
#### 步骤二:开发任务代码:
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
版本回溯功能(支持30个版本的版本回溯):在GLUE任务的Web IDE界面,选择右上角下拉框“版本回溯”,会列出该GLUE的更新历史,选择相应版本即可显示该版本代码,保存后GLUE代码即回退到对应的历史版本;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_dNUJ.png "在这里输入图片标题")
### 3.4 GLUE模式(Shell)
#### 步骤一:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Shell)";
#### 步骤二:开发任务代码:
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
该模式的任务实际上是一段 "shell" 脚本;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_iUw0.png "在这里输入图片标题")
### 3.4 GLUE模式(Python)
#### 步骤一:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Python)";
#### 步骤二:开发任务代码:
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
该模式的任务实际上是一段 "python" 脚本;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_BPLG.png "在这里输入图片标题")
### 3.5 GLUE模式(NodeJS)
#### 步骤一:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(NodeJS)";
#### 步骤二:开发任务代码:
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
该模式的任务实际上是一段 "nodeJS" 脚本;
### 3.6 GLUE模式(PHP)
同上
### 3.7 GLUE模式(PowerShell)
同上
## 四、操作指南
### 4.1 配置执行器
点击进入"执行器管理"界面, 如下图:
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Hr2T.png "在这里输入图片标题")
    1、"调度中心OnLine:"右侧显示在线的"调度中心"列表, 任务执行结束后, 将会以failover的模式进行回调调度中心通知执行结果, 避免回调的单点风险;
    2、"执行器列表" 中显示在线的执行器列表, 可通过"OnLine 机器"查看对应执行器的集群机器。
点击按钮 "+新增执行器" 弹框如下图, 可新增执行器配置:
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_V3vF.png "在这里输入图片标题")
执行器属性说明
    AppName: 是每个执行器集群的唯一标示AppName, 执行器会周期性以AppName为对象进行自动注册。可通过该配置自动发现注册成功的执行器, 供任务调度时使用;
    名称: 执行器的名称, 因为AppName限制字母数字等组成,可读性不强, 名称为了提高执行器的可读性;
    排序: 执行器的排序, 系统中需要执行器的地方,如任务新增, 将会按照该排序读取可用的执行器列表;
    注册方式:调度中心获取执行器地址的方式;
        自动注册:执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址;
        手动录入:人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用;
    机器地址:"注册方式"为"手动录入"时有效,支持人工维护执行器的地址信息;
### 4.2 新建任务
进入任务管理界面,点击“新增任务”按钮,在弹出的“新增任务”界面配置任务属性后保存即可。详情页参考章节 "三、任务详解"。
### 4.3 编辑任务
进入任务管理界面,选中指定任务。点击该任务右侧“编辑”按钮,在弹出的“编辑任务”界面更新任务属性后保存即可,可以修改设置的任务属性信息:
### 4.4 编辑GLUE代码
该操作仅针对GLUE任务。
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发。可参考章节 "3.3 GLUE模式(Java)"。
### 4.5 启动/停止任务
可对任务进行“启动”和“停止”操作。
需要注意的是,此处的启动/停止仅针对任务的后续调度触发行为,不会影响到已经触发的调度任务,如需终止已经触发的调度任务,可查看“4.9 终止运行中的任务”
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题")
### 4.6 手动触发一次调度
点击“执行”按钮,可手动触发一次任务调度,不影响原有调度规则。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题")
### 4.7 查看调度日志
点击“日志”按钮,可以查看任务历史调度日志。在历史调入日志界面可查看每次任务调度的调度结果、执行结果等,点击“执行日志”按钮可查看执行器完整日志。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题")
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_UDSo.png "在这里输入图片标题")
    调度时间:"调度中心"触发本次调度并向"执行器"发送任务执行信号的时间;
    调度结果:"调度中心"触发本次调度的结果,200表示成功,500或其他表示失败;
    调度备注:"调度中心"触发本次调度的日志信息;
    执行器地址:本次任务执行的机器地址
    运行模式:触发调度时任务的运行模式,运行模式可参考章节 "三、任务详解";
    任务参数:本地任务执行的入参
    执行时间:"执行器"中本次任务执行结束后回调的时间;
    执行结果:"执行器"中本次任务执行的结果,200表示成功,500或其他表示失败;
    执行备注:"执行器"中本次任务执行的日志信息;
    操作:
        "执行日志"按钮:点击可查看本地任务执行的详细日志信息;详见“4.8 查看执行日志”;
        "终止任务"按钮:点击可终止本地调度对应执行器上本任务的执行线程,包括未执行的阻塞任务一并被终止;
### 4.8 查看执行日志
点击执行日志右侧的 “执行日志” 按钮,可跳转至执行日志界面,可以查看业务代码中打印的完整日志,如下图;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_tvGI.png "在这里输入图片标题")
### 4.9 终止运行中的任务
仅针对执行中的任务。
在任务日志界面,点击右侧的“终止任务”按钮,将会向本次任务对应的执行器发送任务终止请求,将会终止掉本次任务,同时会清空掉整个任务执行队列。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_hIci.png "在这里输入图片标题")
任务终止时通过 "interrupt" 执行线程的方式实现, 将会触发 "InterruptedException" 异常。因此如果JobHandler内部catch到了该异常并消化掉的话, 任务终止功能将不可用。
因此, 如果遇到上述任务终止不可用的情况, 需要在JobHandler中应该针对 "InterruptedException" 异常进行特殊处理 (向上抛出) , 正确逻辑如下:
```
try{
    // do something
} catch (Exception e) {
    if (e instanceof InterruptedException) {
        throw e;
    }
    logger.warn("{}", e);
}
```
而且,在JobHandler中开启子线程时,子线程也不可catch处理"InterruptedException",应该主动向上抛出。
任务终止时会执行对应JobHandler的"destroy()"方法,可以借助该方法处理一些资源回收的逻辑。
### 4.10 删除执行日志
在任务日志界面,选中执行器和任务之后,点击右侧的"删除"按钮将会出现"日志清理"弹框,弹框中支持选择不同类型的日志清理策略,选中后点击"确定"按钮即可进行日志清理操作;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Ypik.png "在这里输入图片标题")
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_EB65.png "在这里输入图片标题")
### 4.11 删除任务
点击删除按钮,可以删除对应任务。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Z9Qr.png "在这里输入图片标题")
### 4.12 用户管理
进入 "用户管理" 界面,可查看和管理用户信息;
目前用户分为两种角色:
- 管理员:拥有全量权限,支持在线管理用户信息,为用户分配权限,权限分配粒度为执行器;
- 普通用户:仅拥有被分配权限的执行器,及相关任务的操作权限;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_1001.png "在这里输入图片标题")
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_1002.png "在这里输入图片标题")
## 五、总体设计
### 5.1 源码目录介绍
    - /doc :文档资料
    - /db :“调度数据库”建表脚本
    - /xxl-job-admin :调度中心,项目源码
    - /xxl-job-core :公共Jar依赖
    - /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
    - xxl_job_lock:任务调度锁表;
    - xxl_job_group:执行器信息表,维护任务执行器信息;
    - xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
    - xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
    - xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;
    - xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
    - xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
    - xxl_job_user:系统用户表;
### 5.3 架构设计
#### 5.3.1 设计思想
将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;
#### 5.3.2 系统组成
- **调度模块(调度中心)**:
    负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;
    支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。
- **执行模块(执行器)**:
    负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;
    接收“调度中心”的执行请求、终止请求和日志请求等。
#### 5.3.3 架构图
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Qohm.png "在这里输入图片标题")
### 5.4 调度模块剖析
#### 5.4.1 quartz的不足
Quartz作为开源作业调度中的佼佼者,是作业调度的首选。但是集群环境中Quartz采用API的方式对任务进行管理,从而可以避免上述问题,但是同样存在以下问题:
- 问题一:调用API的的方式操作任务,不人性化;
- 问题二:需要持久化业务QuartzJobBean到底层数据表中,系统侵入性相当严重。
- 问题三:调度逻辑和QuartzJobBean耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;
- 问题四:quartz底层以“抢占式”获取DB锁并由抢占成功节点负责运行任务,会导致节点负载悬殊非常大;而XXL-JOB通过执行器实现“协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。
XXL-JOB弥补了quartz的上述不足之处。
#### 5.4.2 自研调度模块
XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
#### 5.4.5 并行调度
XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
#### 5.4.6 过期处理策略
任务调度错过触发时间时的处理策略:
- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
- 处理策略:
    - 过期超5s:本次忽略,当前时间开始计算下次触发时间
    - 过期5s内:立即触发一次,当前时间开始计算下次触发时间
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
调度中心提供的"日志回调服务API服务"代码位置如下:
```
xxl-job-admin#JobApiController.callback
```
“执行器”在接收到任务执行请求后,执行任务,在执行结束之后会将执行结果回调通知“调度中心”:
#### 5.4.8 任务HA(Failover)
执行器如若集群部署,调度中心将会感知到在线的所有执行器,如“127.0.0.1:9997, 127.0.0.1:9998, 127.0.0.1:9999”。
当任务"路由策略"选择"故障转移(FAILOVER)"时,当调度中心每次发起调度请求时,会按照顺序对执行器发出心跳检测请求,第一个检测为存活状态的执行器将会被选定并发送调度请求。
调度成功后,可在日志监控界面查看“调度备注”,如下;
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_jrdI.png "在这里输入图片标题")
“调度备注”可以看出本地调度运行轨迹,执行器的"注册方式"、"地址列表"和任务的"路由策略"。"故障转移(FAILOVER)"路由策略下,调度中心首先对第一个地址进行心跳检测,心跳失败因此自动跳过,第二个依然心跳检测失败……
直至心跳检测第三个地址“127.0.0.1:9999”成功,选定为“目标执行器”;然后对“目标执行器”发送调度请求,调度流程结束,等待执行器回调执行结果。
#### 5.4.9 调度日志
调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括以下三部分内容:
- 任务信息:包括“执行器地址”、“JobHandler”和“执行参数”等属性,点击任务ID按钮可查看,根据这些参数,可以精确的定位任务执行的具体机器和任务代码;
- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。
- 执行信息:包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况;
调度日志,针对单次调度,属性说明如下:
- 执行器地址:任务执行的机器地址;
- JobHandler:Bean模式表示任务执行的JobHandler名称;
- 任务参数:任务执行的入参;
- 调度时间:调度中心,发起调度的时间;
- 调度结果:调度中心,发起调度的结果,SUCCESS或FAIL;
- 调度备注:调度中心,发起调度的备注信息,如地址心跳检测日志等;
- 执行时间:执行器,任务执行结束后回调的时间;
- 执行结果:执行器,任务执行的结果,SUCCESS或FAIL;
- 执行备注:执行器,任务执行的备注信息,如异常日志等;
- 执行日志:任务执行过程中,业务代码中打印的完整执行日志,见“4.8 查看执行日志”;
#### 5.4.10 任务依赖
原理:XXL-JOB中每个任务都对应有一个任务ID,同时,每个任务支持设置属性“子任务ID”,因此,通过“任务ID”可以匹配任务依赖关系。
当父任务执行结束并且执行成功时,将会根据“子任务ID”匹配子任务依赖,如果匹配到子任务,将会主动触发一次子任务的执行。
在任务日志界面,点击任务的“执行备注”的“查看”按钮,可以看到匹配子任务以及触发子任务执行的日志信息,如无信息则表示未触发子任务执行,可参考下图。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Wb2o.png "在这里输入图片标题")
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_jOAU.png "在这里输入图片标题")
#### 5.4.11  全异步化 & 轻量级
- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
    - 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
    - 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
得益于上述两点优化,理论上默认配置下的调度中心,单机能够支撑 5000 任务并发运行稳定运行;
实际场景中,由于调度中心与执行器网络ping延迟不同、DB读写耗时不同、任务调度密集程度不同,会导致任务量上限会上下波动。
如若需要支撑更多的任务量,可以通过 "调大调度线程数" 、"降低调度中心与执行器ping延迟" 和 "提升机器配置" 几种方式优化。
#### 5.4.12 均衡调度
调度中心在集群部署时会自动进行任务平均分配,触发组件每次获取与线程池数量(调度中心支持自定义调度线程池大小)相关数量的任务,避免大量任务集中在单个调度中心集群节点;
### 5.5 任务 "运行模式" 剖析
#### 5.5.1 "Bean模式" 任务
开发步骤:可参考 "章节三" ;
原理:每个Bean模式任务都是一个Spring的Bean类实例,它被维护在“执行器”项目的Spring容器中。任务类需要加“@JobHandler(value="名称")”注解,因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”,任务逻辑在execute方法中开发,因为“执行器”在接收到调度中心的调度请求时,将会调用“IJobHandler”的execute方法,执行任务逻辑。
#### 5.5.2 "GLUE模式(Java)" 任务
开发步骤:可参考 "章节三" ;
原理:每个 "GLUE模式(Java)" 任务的代码,实际上是“一个继承自“IJobHandler”的实现类的类代码”,“执行器”接收到“调度中心”的调度请求时,会通过Groovy类加载器加载此代码,实例化成Java对象,同时注入此代码中声明的Spring服务(请确保Glue代码中的服务和类引用在“执行器”项目中存在),然后调用该对象的execute方法,执行任务逻辑。
#### 5.5.3 GLUE模式(Shell) + GLUE模式(Python) + GLUE模式(NodeJS)
开发步骤:可参考 "章节三" ;
原理:脚本任务的源码托管在调度中心,脚本逻辑在执行器运行。当触发脚本任务时,执行器会加载脚本源码在执行器机器上生成一份脚本文件,然后通过Java代码调用该脚本;并且实时将脚本输出日志写到任务日志文件中,从而在调度中心可以实时监控脚本运行情况;
目前支持的脚本类型如下:
    - shell脚本:任务运行模式选择为 "GLUE模式(Shell)"时支持 "shell" 脚本任务;
    - python脚本:任务运行模式选择为 "GLUE模式(Python)"时支持 "python" 脚本任务;
    - nodejs脚本:务运行模式选择为 "GLUE模式(NodeJS)"时支持 "nodejs" 脚本任务;
脚本任务通过 Exit Code 判断任务执行结果,状态码可参考章节 "5.15 任务执行结果说明";
#### 5.5.4 执行器
执行器实际上是一个内嵌的Server,默认端口9999(配置项:xxl.job.executor.port)。
在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。
“执行器”接收到“调度中心”的调度请求时,如果任务类型为“Bean模式”,将会匹配Spring容器中的“Bean模式任务”,然后调用其execute方法,执行任务逻辑。如果任务类型为“GLUE模式”,将会加载GLue代码,实例化Java对象,注入依赖的Spring服务(注意:Glue代码中注入的Spring服务,必须存在与该“执行器”项目的Spring容器中),然后调用execute方法,执行任务逻辑。
#### 5.5.5 任务日志
XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 "XxlJobLogger.log" 打印执行日志,“调度中心”查看执行日志时将会加载对应的日志文件。
(历史版本通过重写LOG4J的Appender实现,存在依赖限制,该方式在新版本已经被抛弃)
日志文件存放的位置可在“执行器”配置文件进行自定义,默认目录格式为:/data/applogs/xxl-job/jobhandler/“格式化日期”/“数据库调度日志记录的主键ID.log”。
在JobHandler中开启子线程时,子线程将会将会把日志打印在父线程即JobHandler的执行日志中,方便日志追踪。
### 5.6 通讯模块剖析
#### 5.6.1 一次完整的任务调度通讯流程
    - 1、“调度中心”向“执行器”发送http调度请求: “执行器”中接收请求的服务,实际上是一台内嵌Server,默认端口9999;
    - 2、“执行器”执行任务逻辑;
    - 3、“执行器”http回调“调度中心”调度结果: “调度中心”中接收回调的服务,是针对执行器开放一套API服务;
#### 5.6.2 通讯数据加密
调度中心向执行器发送的调度请求时使用RequestModel和ResponseModel两个对象封装调度请求参数和响应数据, 在进行通讯之前底层会将上述两个对象对象序列化,并进行数据协议以及时间戳检验,从而达到数据加密的功能;
### 5.7 任务注册, 任务自动发现
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
    AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
    注册表: 见"xxl_job_registry"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
    执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间为三倍Beat;
    执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
为保证系统"轻量级"并且降低学习部署成本,没有采用Zookeeper作为注册中心,采用DB方式进行任务注册发现;
### 5.8 任务执行结果
自v1.6.2之后,任务执行结果通过 "IJobHandler" 的返回值 "ReturnT" 进行判断;
当返回值符合 "ReturnT.code == ReturnT.SUCCESS_CODE" 时表示任务执行成功,否则表示任务执行失败,而且可以通过 "ReturnT.msg" 回调错误信息给调度中心;
从而,在任务逻辑中可以方便的控制任务执行结果;
### 5.9 分片广播 & 动态分片
执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
"分片广播" 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
"分片广播" 和普通任务开发流程一致,不同之处在于可以可以获取分片参数,获取分片参数进行分片业务处理。
- Java语言任务获取分片参数方式:BEAN、GLUE模式(Java)
```
// 可参考Sample示例执行器中的示例任务"ShardingJobHandler"了解试用
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
```
- 脚本语言任务获取分片参数方式:GLUE模式(Shell)、GLUE模式(Python)、GLUE模式(Nodejs)
```
// 脚本任务入参固定为三个,依次为:任务传参、分片序号、分片总数。以Shell模式任务为例,获取分片参数代码如下
echo "分片序号 index = $2"
echo "分片总数 total = $3"
```
分片参数属性说明:
    index:当前分片序号(从0开始),执行器集群列表中当前执行器的序号;
    total:总分片数,执行器集群的总机器数量;
该特性适用场景如:
- 1、分片任务场景:10个执行器的集群来处理10w条数据,每台机器只需要处理1w条数据,耗时降低10倍;
- 2、广播任务场景:广播执行器机器运行shell脚本、广播集群节点进行缓存更新等
### 5.10 访问令牌(AccessToken)
为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;
调度中心和执行器,可通过配置项 "xxl.job.accessToken" 进行AccessToken的设置。
调度中心和执行器,如果需要正常通讯,只有两种设置;
- 设置一:调度中心和执行器,均不设置AccessToken;关闭安全性校验;
- 设置二:调度中心和执行器,设置了相同的AccessToken;
### 5.11 调度中心API服务
调度中心提供了API服务,主要分为两种类型:
#### 5.11.1 提供给执行器的API服务:
    1、任务结果回调服务;
    2、执行器注册服务;
    3、执行器注册摘除服务;
    4、触发任务单次执行服务,支持任务根据业务事件触发;
API服务位置:com.xxl.job.core.biz.AdminBiz.java
API服务请求参考代码:com.xxl.job.adminbiz.AdminBizTest.java
#### 5.11.2 提供给业务的API服务:
    1、任务列表查询;
    2、任务新增;
    3、任务更新;
    4、任务删除;
    5、任务启动;
    6、任务停止;
    7、任务触发;
API服务位置:JobInfoController.java
API服务请求参考代码:可参考任务界面操作的ajax请求。任何ajax接口均可配置成为API服务,只需在待启用的API服务上添加 “@PermissionLimit(limit = false)” 注解取消登陆态拦截即可;
### 5.12 执行器API服务
执行器提供了API服务,供调度中心选择使用,目前提供的API服务有:
    1、心跳检测:调度中心使用
    2、忙碌检测:调度中心使用
    3、触发任务执行:调度中心使用;本地进行任务开发时,可使用该API服务模拟触发任务;
    4、获取Rolling Log:调度中心使用
    5、终止任务:调度中心使用
API服务位置:com.xxl.job.core.biz.ExecutorBiz
API服务请求参考代码:com.xxl.job.executor.ExecutorBizTest
### 5.13 故障转移 & 失败重试
一次完整任务流程包括"调度(调度中心) + 执行(执行器)"两个阶段。
- "故障转移"发生在调度阶段,在执行器集群部署时,如果某一台执行器发生故障,该策略支持自动进行Failover切换到一台正常的执行器机器并且完成调度请求流程。
- "失败重试"发生在"调度 + 执行"两个阶段,支持通过自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;
### 5.14 执行器灰度上线
调度中心与业务解耦,只需部署一次后常年不需要维护。但是,执行器中托管运行着业务作业,作业上线和变更需要重启执行器,尤其是Bean模式任务。
执行器重启可能会中断运行中的任务。但是,XXL-JOB得益于自建执行器与自建注册中心,可以通过灰度上线的方式,避免因重启导致的任务中断的问题。
步骤如下:
- 1、执行器改为手动注册,下线一半机器列表(A组),线上运行另一半机器列表(B组);
- 2、等待A组机器任务运行结束并编译上线;执行器注册地址替换为A组;
- 3、等待B组机器任务运行结束并编译上线;执行器注册地址替换为A组+B组;
操作结束;
### 5.15 任务执行结果说明
系统根据以下标准判断任务执行结果,可参考之。
-- | Bean/Glue(Java) | Glue(Shell) 等脚本任务
--- | --- | ---
成功 | IJobHandler.SUCCESS | 0
失败 | IJobHandler.FAIL | -1(非0状态码)
### 5.16 任务超时控制
支持设置任务超时时间,任务运行超时的情况下,将会主动中断任务;
需要注意的是,任务超时中断时与任务终止机制(可查看“4.9 终止运行中的任务”)类似,也是通过 "interrupt" 中断任务,因此业务代码需要将 "InterruptedException" 外抛,否则功能不可用。
### 5.17 跨平台 & 跨语言
跨平台、跨语言主要体现在以下两个方面:
- 1、提供Java、Python、PHP……等十来种任务模式,可参考章节 “5.5 任务 "运行模式" ”;理论上可扩展任意语言任务模式;
- 2、提供基于HTTP的任务Handler(Bean任务,JobHandler="HttpJobHandler");业务方只需要提供HTTP链接即可,不限制语言、平台;
### 5.18 任务失败告警
默认提供邮件失败告警,可扩展短信、钉钉等方式,扩展代码位置为 "JobFailMonitorHelper.failAlarm";
### 5.19 调度中心Docker镜像构建
可以通过以下命令快速构建调度中心,并启动运行;
```
mvn clean package
docker build -t xuxueli/xxl-job-admin ./xxl-job-admin
docker run --name xxl-job-admin -p 8080:8080 -d xuxueli/xxl-job-admin
```
### 5.20 避免任务重复执行
调度密集或者耗时任务可能会导致任务阻塞,集群情况下调度组件小概率情况下会重复触发;
针对上述情况,可以通过结合 "单机路由策略(如:第一台、一致性哈希)" + "阻塞策略(如:单机串行、丢弃后续调度)" 来规避,最终避免任务重复执行。
### 5.21 命令行任务
原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可;
如任务参数 "pwd" 将会执行命令并输出数据;
### 5.22  日志自动清理
XXL-JOB日志主要包含如下两部分,均支持日志自动清理,说明如下:
- 调度中心日志表数据:可借助配置项 "xxl.job.logretentiondays" 设置日志表数据保存天数,过期日志自动清理;详情可查看上文配置说明;
- 执行器日志文件数据:可借助配置项 "xxl.job.executor.logretentiondays" 设置日志文件数据保存天数,过期日志自动清理;详情可查看上文配置说明;
## 六、版本更新日志
### 6.1 版本 V1.1.x,新特性[2015-12-05]
**【于V1.1.x版本,XXL-JOB正式应用于我司,内部定制别名为 “Ferrari”,新接入应用推荐使用最新版本】**
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态,动态暂停/恢复任务,即时生效;
- 3、服务HA:任务信息持久化到mysql中,Job服务天然支持集群,保证服务HA;
- 4、任务HA:某台Job服务挂掉,任务会平滑分配给其他的某一台存活服务,即使所有服务挂掉,重启时或补偿执行丢失任务;
- 5、一个任务只会在其中一台服务器上执行;
- 6、任务串行执行;
- 7、支持自定义参数;
- 8、支持远程任务执行终止;
### 6.2 版本 V1.2.x,新特性[2016-01-17]
- 1、支持任务分组;
- 2、支持“本地任务”、“远程任务”;
- 3、底层通讯支持两种方式,Servlet方式 + JETTY方式;
- 4、支持“任务日志”;
- 5、支持“串行执行”,并行执行;
    说明:V1.2版本将系统架构按功能拆分为:
        - 调度模块(调度中心):负责管理调度信息,按照调度配置发出调度请求;
        - 执行模块(执行器):负责接收调度请求并执行任务逻辑;
        - 通讯模块:负责调度模块和任务模块之间的信息通讯;
    优点:
        - 解耦:任务模块提供任务接口,调度模块维护调度信息,业务相互独立;
        - 高扩展性;
        - 稳定性;
### 6.3 版本 V1.3.0,新特性[2016-05-19]
- 1、遗弃“本地任务”模式,推荐使用“远程任务”,易于系统解耦,任务对应的JobHandler统称为“执行器”;
- 2、遗弃“servlet”方式底层系统通讯,推荐使用JETTY方式,调度+回调双向通讯,重构通讯逻辑;
- 3、UI交互优化:左侧菜单展开状态优化,菜单项选中状态优化,任务列表打开表格有压缩优化;
- 4、【重要】“执行器”细分为:BEAN、GLUE两种开发模式,简介见下文:
    “执行器” 模式简介:
        - BEAN模式执行器:每个执行器都是Spring的一个Bean实例,XXL-JOB通过注解@JobHandler识别和调度执行器;
         -GLUE模式执行器:每个执行器对应一段代码,在线Web编辑和维护,动态编译生效,执行器负责加载GLUE代码和执行;
### 6.4 版本 V1.3.1,新特性[2016-05-23]
- 1、更新项目目录结构:
    - /xxl-job-admin -------------------- 【调度中心】:负责管理调度信息,按照调度配置发出调度请求;
    - /xxl-job-core ----------------------- 公共依赖
    - /xxl-job-executor-example ------ 【执行器】:负责接收调度请求并执行任务逻辑;
    - /db ---------------------------------- 建表脚本
    - /doc --------------------------------- 用户手册
- 2、在新的目录结构上,升级了用户手册;
- 3、优化了一些交互和UI;
### 6.5 版本 V1.3.2,新特性[2016-05-28]
- 1、调度逻辑进行事务包裹;
- 2、执行器异步回调执行日志;
- 3、【重要】在 “调度中心” 支持HA的基础上,扩展执行器的Failover支持,支持配置多执行期地址;
### 6.6 版本 V1.4.0 新特性[2016-07-24]
- 1、任务依赖: 通过事件触发方式实现, 任务执行成功并回调时会主动触发一次子任务的调度, 多个子任务用逗号分隔;
- 2、执行器底层实现代码进行重度重构, 优化底层建表脚本;
- 3、执行器中任务线程分组逻辑优化: 之前根据执行器JobHandler进行线程分组,当多个任务复用Jobhanlder会导致相互阻塞。现改为根据调度中心任务进行任务线程分组,任务与任务执行相互隔离;
- 4、执行器调度通讯方案优化, 通过Hex + HC实现建议RPC通讯协议, 优化了通讯参数的维护和解析流程;
- 5、调度中心, 新建/编辑任务, 界面属性调整:
    - 5.1、任务新增/编辑界面中去除 "任务名JobName"属性 ,该属性改为系统自动生成: 该字段之前主要用于在 "调度中心" 唯一标示一个任务, 现实意义不大, 因此计划淡化掉该字段,改为系统生成UUID,从而简化任务新建的操作;
    - 5.2、任务新增/编辑界面中去除 "GLUE模式" 复选框位置调整, 改为贴近"JobHandler"输入框右侧;
    - 5.3、任务新增/编辑界面中去除 "报警阈值" 属性;
    - 5.4、任务新增/编辑界面中去除 "子任务Key" 属性, 每个任务全局任务Key可以从任务列表获取, 当本任务执行结束且成功后, 将会根据子任务Key匹配子任务并主动触发一次子任务执行;
- 6、问题修复:
    - 6.1、执行器jetty关闭优化,解决一处可能导致jetty无法关闭的问题;
    - 6.2、执行器任务终止时,执行队列回调优化,解决一处导致任务无法回调的问题;
    - 6.3、调度中心中列表分页参数优化,解决一处因服务器限制post长度而引起的问题;
    - 6.4、执行器Jobhandler注解优化,解决一处因事务代理导致的容器无法加载JobHandler的问题;
    - 6.5、远程调度优化,禁用retry策略,解决一处可能导致重复调用的问题;
Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段, 地址见分支 [V1.3](https://github.com/xuxueli/xxl-job/tree/v1.3) 。新特性将会在master分支持续更新。
### 6.7 版本 V1.4.1 新特性[2016-09-06]
- 1、项目成功推送maven中央仓库, 中央仓库地址以及依赖如下:
    ```
    <!-- http://repo1.maven.org/maven2/com/xuxueli/xxl-job-core/ -->
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>${最新稳定版}</version>
    </dependency>
    ```
- 2、为适配中央仓库规则, 项目groupId从com.xxl改为com.xuxueli。
- 3、系统版本不在维护在项目跟pom中,各个子模块单独配置版本配置,解决子模块无法单独编译的问题;
- 4、底层RPC通讯,传输数据的字节长度统计规则优化,可节省50%数据传输量;
- 5、IJobHandler取消任务返回值,原通过返回值判断执行状态,逻辑改为:默认任务执行成功,仅在捕获异常时认定任务执行失败。
- 6、系统公共弹框功能,插件化;
- 7、底层表结构,表明统一大写;
- 8、调度中心,异常处理器JSON响应的ContentType修改,修复浏览器不识别的问题;
### 6.8 版本 V1.4.2 新特性[2016-09-29]
- 1、推送新版本 V1.4.2 至中央仓库, 大版本 V1.4 进入维护阶段;
- 2、任务新增时,任务列表偏移问题修复;
- 3、修复一处因bootstrap不支持模态框重叠而导致的样式错乱的问题, 在任务编辑时会出现该问题;
- 4、调度超时和Handler匹配不到时,调度状态优化;
- 5、因catch异常,导致任务不可终止的问题,给出解决方案, 见文档;
### 6.9 版本 V1.5.0 特性[2016-11-13]
- 1、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。
- 2、"执行器" 新增参数 "AppName" : 是每个执行器集群的唯一标示AppName, 并周期性以AppName为对象进行自动注册。
- 3、调度中心新增栏目 "执行器管理" : 管理在线的执行器, 通过属性AppName自动发现注册的执行器。只有被管理的执行器才允许被使用;
- 4、"任务组"属性改为"执行器": 每个任务需要绑定指定的执行器, 调度地址通过绑定的执行器获取;
- 5、抛弃"任务机器"属性: 通过任务绑定的执行器, 自动发现注册的远程执行器地址并触发调度请求。
- 6、"公共依赖"中新增DBGlueLoader,基于原生jdbc实现GLUE源码的加载器,减少第三方依赖(mybatis,spring-orm等);精简和优化执行器测配置(针对GLUE任务),降低上手难度;
- 7、表结构调整,底层重构优化;
- 8、"调度中心"自动注册和发现,failover: 调度中心周期性自动注册, 任务回调时可以感知在线的所有调度中心地址, 通过failover的方式进行任务回调,避免回调单点风险。
### 6.10 版本 V1.5.1 特性[2016-11-13]
- 1、底层代码重构和逻辑优化,POM清理以及CleanCode;
- 2、Servlet/JSP Spec设定为3.0/2.2
- 3、Spring升级至3.2.17.RELEASE版本;
- 4、Jetty升级版本至8.2.0.v20160908;
- 5、已推送V1.5.0和V1.5.1至Maven中央仓库;
### 6.10 版本 V1.5.2 特性[2017-02-28]
- 1、IP工具类获取IP逻辑优化,IP静态缓存;
- 2、执行器、调度中心,均支持自定义注册IP地址;解决机器多网卡时错误网卡注册的情况;
- 3、任务跨天执行时生成多份日志文件的问题修复;
- 4、底层日志底层日志调整,非敏感日志level调整为debug;
- 5、升级数据库连接池c3p0版本;
- 6、执行器log4j配置优化,去除无效属性;
- 7、底层代码重构和逻辑优化以及CleanCode;
- 8、GLUE依赖注入逻辑优化,支持别名注入;
### 6.11 版本 V1.6.0 特性[2017-03-13]
- 1、通讯方案升级,原基于HEX的通讯模型调整为基于HTTP的B-RPC的通讯模型;
- 2、执行器支持手动设置执行地址列表,提供开关切换使用注册地址还是手动设置的地址;
- 3、执行器路由规则:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移;
- 4、规范线程模型统一,统一线程销毁方案(通过listener或stop方法,容器销毁时销毁线程;Daemon方式有时不太理想);
- 5、规范系统配置数据,通过配置文件统一管理;
- 6、CleanCode,清理无效的历史参数;
- 7、底层扩展数据结构以及相关表结构调整;
- 8、新建任务默认为非运行状态;
- 9、GLUE模式任务实例更新逻辑优化,原根据超时时间更新改为根据版本号更新,源码变动版本号加一;
### 6.12 版本 V1.6.1 特性[2017-03-25]
- 1、Rolling日志;
- 2、WebIDE交互重构;
- 3、通讯增强校验,有效过滤非正常请求;
- 4、权限增强校验,采用动态登录TOKEN(推荐接入内部SSO);
- 5、数据库配置优化,解决乱码问题;
### 6.13 版本 V1.6.2 特性[2017-04-25]
- 1、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
- 2、JobHandler支持设置任务返回值,在任务逻辑中可以方便的控制任务执行结果;
- 3、资源路径包含空格或中文时资源文件无法加载时,无法准确查看异常信息的问题处理。
- 4、路由策越优化:循环和LFU路由策略计数器自增无上限问题和首次路由压力集中在首台机器的问题修复;
### 6.14 版本 V1.7.0 特性[2017-05-02]
- 1、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python和Groovy等类型脚本;
- 2、新增spring-boot类型执行器example项目;
- 3、升级jetty版本至9.2;
- 4、任务运行日志移除log4j组件依赖,改为底层自主实现,从而取消了对日志组件的依赖限制;
- 5、执行器移除GlueLoader依赖,改为推送方式实现,从而GLUE源码加载不再依赖JDBC;
- 6、登录拦截Redirect时获取项目名,解决非根据目录发布时跳转404问题;
### 6.15 版本 V1.7.1 特性[2017-05-08]
- 1、运行日志读写编码统一为UTF-8,解决windows环境下日志乱码问题;
- 2、通讯超时时间限定为10s,避免异常情况下调度线程占用;
- 3、执行器,server启动、销毁和注册逻辑调整;
- 4、JettyServer关闭逻辑优化,修复执行器无法正常关闭导致端口占用和频繁打印c3p0日志的问题;
- 5、JobHandler中开启子线程时,支持子线程输出执行日志并通过Rolling查看。
- 6、任务日志清理功能;
- 7、弹框组件统一替换为layer;
- 8、升级quartz版本至2.3.0;
### 6.16 版本 V1.7.2 特性[2017-05-17]
- 1、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
- 2、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
- 3、通讯时间戳超时时间调整为180s;
- 4、执行器与数据库彻底解耦,但是执行器需要配置调度中心集群地址。调度中心提供API供执行器回调和心跳注册服务,取消调度中心内部jetty,心跳周期调整为30s,心跳失效为三倍心跳;
- 5、执行参数编辑时丢失问题修复;
- 6、新增任务测试Demo,方便在开发时进行任务逻辑测试;
### 6.17 版本 V1.8.0 特性[2017-07-17]
- 1、任务Cron更新逻辑优化,改为rescheduleJob,同时防止cron重复设置;
- 2、API回调服务失败状态码优化,方便问题排查;
- 3、XxlJobLogger的日志多参数支持;
- 4、路由策略新增 "忙碌转移" 模式:按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
- 5、路由策略代码重构;
- 6、执行器重复注册问题修复;
- 7、任务线程轮空30次后自动销毁,降低低频任务的无效线程消耗。
- 8、执行器任务执行结果批量回调,降低回调频率提升执行器性能;
- 9、springboot版本执行器,取消XML配置,改为类配置方式;
- 10、执行日志,支持根据运行 "状态" 筛选日志;
- 11、调度中心任务注册检测逻辑优化;
### 6.18 版本 V1.8.1 特性[2017-07-30]
- 1、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数处理分片任务;
- 2、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 3、执行器JobHandler禁止命名冲突;
- 4、执行器集群地址列表进行自然排序;
- 5、调度中心,DAO层代码精简优化并且新增测试用例覆盖;
- 6、调度中心API服务改为自研RPC形式,统一底层通讯模型;
- 7、新增调度中心API服务测试Demo,方便在调度中心API扩展和测试;
- 8、任务列表页交互优化,更换执行器分组时自动刷新任务列表,新建任务时默认定位在当前执行器位置;
- 9、访问令牌(accessToken):为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;
- 10、springboot版本执行器,升级至1.5.6.RELEASE版本;
- 11、统一maven依赖版本管理;
### 6.19 版本 V1.8.2 特性[2017-09-04]
- 1、项目主页搭建:提供中英文文档:https://www.xuxueli.com/xxl-job
- 2、JFinal执行器Sample示例项目;
- 3、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
- 4、执行器摘除:执行器销毁时,主动通知调度中心并摘除对应执行器节点,提高执行器状态感知的时效性。
- 5、执行器手动设置IP时将会绑定Host;
- 6、规范项目目录,方便扩展多执行器;
- 7、解决执行器回调URL不支持配置HTTPS时问题;
- 8、执行器回调线程销毁前, 批量回调队列中数据,防止任务结果丢失;
- 9、调度中心任务监控线程销毁时,批量对失败任务告警,防止告警信息丢失;
- 10、任务日志文件路径时间戳格式化时SimpleDateFormat并发问题解决;
### 6.20 版本 V1.9.0 特性[2017-12-29]
- 1、新增Nutz执行器Sample示例项目;
- 2、新增任务运行模式 "GLUE模式(NodeJS) ",支持NodeJS脚本任务;
- 3、脚本任务Shell、Python和Nodejs等支持获取分片参数;
- 4、失败重试,完整支持:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态(新增失败重试状态返回值)时,也将会自动重试一次;
- 5、失败告警策略扩展:默认提供邮件失败告警,可扩展短信等,扩展代码位置为 "JobFailMonitorHelper.failAlarm";
- 6、执行器端口支持自动生成(小于等于0时),避免端口定义冲突;
- 7、调度报表优化,支持时间区间筛选;
- 8、Log组件支持输出异常栈信息,底层实现优化;
- 9、告警邮件样式优化,调整为表格形式,邮件组件调整为commons-email简化邮件操作;
- 10、项目依赖全量升级至较新稳定版本,如spring、jackson等等;
- 11、任务日志,记录发起调度的机器信息;
- 12、交互优化,如登陆注销;
- 13、任务Cron长度扩展支持至128位,支持负责类型Cron设置;
- 14、执行器地址录入交互优化,地址长度扩展支持至512位,支持大规模执行器集群配置;
- 15、任务参数“IJobHandler.execute”入参改为“String params”,增强入参通用性。
- 16、IJobHandler提供init/destroy方法,支持在相应任务线程初始化和销毁时进行附加操作;
- 17、任务注解调整为 “@JobHandler”,与任务抽象接口统一;
- 18、修复任务监控线程被耗时任务阻塞的问题;
- 19、修复任务监控线程无法监控任务触发和执行状态均未0的问题;
- 20、执行器动态代理对象,拦截非业务方法的执行;
- 21、修复JobThread捕获Error错误不更新JobLog的问题;
- 22、修复任务列表界面左侧菜单合并时样式错乱问题;
- 23、调度中心项目日志配置改为xml文件格式;
- 24、Log地址格式兼容,支持非"/"结尾路径配置;
- 25、底层系统日志级别规范调整,清理遗留代码;
- 26、建表SQL优化,支持同步创建制定编码的库和表;
- 27、系统安全性优化,登陆Token写Cookie时进行MD5加密,同时Cookie启用HttpOnly;
- 28、新增"任务ID"属性,移除"JobKey"属性,前者承担所有功能,方便后续增强任务依赖功能。
- 29、任务循环依赖问题修复,避免子任务与父任务重复导致的调度死循环;
- 30、任务列表新增筛选条件 "任务描述",快速检索任务;
- 31、执行器Log文件定期清理功能:执行器新增配置项("xxl.job.executor.logretentiondays")日志保存天数,日志文件过期自动删除。
### 6.21 版本 V1.9.1 特性[2018-02-22]
- 1、国际化:调度中心实现国际化,支持中文、英文两种语言,默认为中文。
- 2、调度报表新增"运行中"中状态项;
- 3、调度报表优化,报表SQL调优并且新增LocalCache缓存(缓存时间60s),提高大数据量下报表加载速度;
- 4、修复打包部署时资源文件乱码问题;
- 5、修复新版本chrome滚动到顶部失效问题;
- 6、调度中心配置加载优化,取消对配置文件名的强依赖,支持加载磁盘配置;
- 7、修复脚本任务Log文件未正常close的问题;
- 8、项目依赖全量升级至较新稳定版本,如spring、jackson等等;
### 6.22 版本 V1.9.2 特性[2018-10-05]
- 1、任务超时控制:新增任务属性 "任务超时时间",并支持自定义,任务运行超时将会主动中断任务;
- 2、任务失败重试次数:新增任务属性 "失败重试次数",并支持自定义,当任务失败时将会按照预设的失败重试次数主动进行重试;同时收敛废弃其他失败重试策略,如调度失败、执行失败、状态码失败等;
- 3、新增任务运行模式 "GLUE模式(PHP) ",支持php脚本任务;
- 4、新增任务运行模式 "GLUE模式(PowerShell) ",支持PowerShell脚本任务;
- 5、调度全异步处理:任务触发之后,推送到调度队列,多线程并发处理调度请求,提高任务调度速率的同时,避免因网络问题导致quartz调度线程阻塞的问题;
- 6、执行器任务结果落盘优化:执行器回调失败时将任务结果写磁盘,待重启或网络恢复时重试回调任务结果,防止任务执行结果丢失;
- 7、任务日志查询速度大幅提升:百万级别数据量搜索速度提升1000倍;
- 8、调度中心提供API服务,支持通过API服务对任务进行查询、新增、更新、启停等操作;
- 9、底层自研Log组件参数占位符改为"{}",并修复打印有参日志时参数不匹配导致报错的问题;
- 10、任务回调结果优化,支持展示在Rolling log中,方便问题排查;
- 11、底层LocalCache组件兼容性优化,支持jdk9、jdk10及以上版本编译部署;
- 12、告警邮件固定使用 UTF-8 编码格式,修复由机器编码导致的邮件乱码问题;
- 13、告警邮件中展示失败告警信息;
- 14、告警邮箱支持SSL配置;
- 15、Window机器下File.separator不兼容问题修复;
- 16、脚本任务异常Log输出优化;
- 17、任务线程停止变量修饰符优化;
- 18、脚本任务Log文件流关闭优化;
- 19、任务报表成功、失败和进行中统计问题修复;
- 20、核心依赖Core内部国际化处理;
- 21、默认Quartz线程数调整为50;
- 22、新增左侧菜单"运行报表";
- 23、执行器手动设置IP时取消绑定Host的操作,该IP仅供执行器注册使用;修复指定外网IP时无法绑定执行器Host的问题;
- 24、取消父子任务不可重复的限制,支持循环任务触发等特殊场景;
- 25、任务调度备注中标注任务触发类型,如Cron触发、父任务触发、API触发等等,方便排查调度日志;
- 26、底层日志组件SimpleDateFormat线程安全问题修复;
- 27、执行器通讯线程优化,corePoolSize从256降低至32;
- 28、任务日志表状态字段类型优化;
- 29、GLUE脚本文件自动清理功能,及时清理过期脚本文件;
- 30、执行器注册方式切换优化,切换自动注册时主动同步在线机器,避免执行器为空的问题;
- 31、跨平台:除了提供Java、Python、PHP等十来种任务模式之外,新增提供基于HTTP的任务模式;
- 32、底层RPC序列化协议调整为hessian2;
- 33、修复表字段 “t.order”与数据库关键字冲突查询失败的问题,
- 34、任务属性枚举 "任务模式、阻塞策略" 国际化优化;
- 35、分片任务失败重试优化,仅重试当前失败的分片;
- 36、任务触发时支持动态传参,调度中心与API服务均提供提供动态参数功能;
- 37、任务执行日志、调度日志字段类型调整,改为text类型并取消字数限制;
- 38、GLUE任务脚本字段类型调整,改为mediumtext类型,提高GLUE长度上限;
- 39、任务监控线程Log输出优化,运行中任务的监控Log改为debug级别,减少非核心日志量;
- 40、项目依赖全量升级至较新稳定版本,如spring、Jackson、groovy等等;
- 41、docker支持:调度中心提供 Dockerfile 方便快速构建docker镜像;
### 6.23 版本 V2.0.0 Release Notes[2018-11-04]
- 1、调度中心迁移到 springboot;
- 2、底层通讯组件迁移至 xxl-rpc;
- 3、容器化:提供官方docker镜像,并实时更新推送dockerhub(docker pull xuxueli/xxl-job-admin),进一步实现产品开箱即用;
- 4、新增无框架执行器Sample示例项目 "xxl-job-executor-sample-frameless"。不依赖第三方框架,只需main方法即可启动运行执行器;
- 5、命令行任务:原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可;
- 6、任务状态优化,仅运行状态"NORMAL"任务关联至quartz,降低quartz底层数据存储与调度压力;
- 7、任务状态规范:新增任务默认停止状态,任务更新时保持任务状态不变;
- 8、IP获取逻辑优化,优先遍历网卡来获取可用IP;
- 9、任务新增的API服务接口返回任务ID,方便调用方实用;
- 10、组件化优化,移除对 spring 的依赖:非spring应用选用 "XxlJobExecutor" 、spring应用选用 "XxlJobSpringExecutor" 作为执行器组件;
- 11、任务RollingLog展示逻辑优化,修复超时任务无法查看的问题;
- 12、多项UI组件升级到最新版本,如:CodeMirror、Echarts、Jquery 等;
- 13、项目依赖升级 groovy 至较新稳定版本;pom清理;
- 14、子任务失败重试重试逻辑优化,子任务失败时将会按照其预设的失败重试次数主动进行重试
### 6.23 版本 v2.0.1 Release Notes[2018-11-09]
- 1、左侧菜单折叠动画问题修复;
- 2、调度报表日期分布图默认值统一;
- 3、freemarker对数字默认加千分位问题修复,解决日志ID被分隔导致查看日志失败问题;
- 4、底层通讯组件升级,修复通讯异常时无效等待的问题;
- 5、执行器启动之后jetty停止的问题修复;
### 6.24 版本 v2.0.2 Release Notes[2019-04-20]
- 1、底层通讯方案优化:升级较新版本xxl-rpc,由"JETTY"方案调整为"NETTY_HTTP"方案,执行器内嵌netty-http-server提供服务,调度中心复用容器端口提供服务;
- 2、任务告警逻辑调整,改为通过扫描失败日志方式触发。一方面精确扫描失败任务,降低扫描范围;另一方面取消内存队列,降低线程内存消耗;
- 3、Quartz触发线程池废弃并替换为 "XxlJobThreadPool",降低线程切换、内存占用带来的消耗,提高调度性能;
- 4、调度线程池隔离,拆分为"Fast"和"Slow"两个线程池,1分钟窗口期内任务耗时达500ms超过10次,该窗口期内判定为慢任务,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性;
- 5、执行器热部署时JobHandler重新初始化,修复由此导致的 "jobhandler naming conflicts." 问题;
- 6、新增Class的加载缓存,解决频繁加载Class会使jvm的方法区空间不足导致OOM的问题;
- 7、任务支持更换绑定执行器,方便任务分组转移和管理;
- 8、调度中心告警邮件发送组件改为 “spring-boot-starter-mail”;
- 9、记住密码功能优化,选中时永久记住;非选中时关闭浏览器即登出;
- 10、项目依赖升级至较新稳定版本,如quartz、spring、jackson、groovy、xxl-rpc等等;
- 11、精简项目,取消第三方依赖,如 commons-collections4、commons-lang3 ;
- 12、执行器回调日志落盘方案复用RPC序列化方案,并移除Jackson依赖;
- 13、底层Log调优,应用正常终止取消异常栈信息打印;
- 14、交互优化,尽量避免新开页面窗口;仅WebIDE支持新开页,并提供窗口快速关闭按钮;任务启、停、删除、触发等轻操作提示改为toast方式,
- 15、任务暂停、删除优化,避免quartz delete不完整导致任务脏数据;
- 16、任务回调、心跳注册成功日志优化,非核心常规日志调整为debug级别,降低冗余日志输出;
- 17、调整首页报表默认区间为本周,避免日志量太大查询缓慢;
- 18、LRU路由更新不及时问题修复;
- 19、任务失败告警邮件发送逻辑优化;
- 20、调度日志排序逻辑调整为按照调度时间倒序,兼容TIDB等主键不连续日志存储组件;
- 21、执行器优雅停机优化;
- 22、连接池配置优化,增强连接有效性验证;
- 23、JobHandler#msg长度限制,修复异常情况下日志超长导致内存溢出的问题;
- 24、升级xxl-rpc至较新版本,修复springboot 2.x版本兼容性问题;
### 6.25 版本 v2.1.0 Release Notes[2019-07-07]
- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
    - 触发:单节点周期性触发,运行事件如delayqueue;
    - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
- 3、任务日志主键调整为long数据类型,防止海量日志情况下数据溢出;
- 4、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
- 5、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
- 6、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
- 7、调度线程池参数调优;
- 8、注册表索引优化,缓解锁表问题;
- 9、新增Jboot执行器Sample示例项目;
- 10、任务列表优化,支持根据 "任务状态"、"负责人" 属性筛选任务;
- 11、任务日志列表交互优化,操作按钮合并为分割按钮;
- 12、项目依赖升级至较新稳定版本,如spring、springboot、groovy、xxl-rpc等等;并清理冗余POM;
- 13、升级xxl-rpc至较新版本,修复代理服务初始化时远程服务不可用导致长连冗余创建的问题;
- 14、首页调度报表的日期排序在TIDB下乱序问题修复;
- 15、调度中心与执行器双向通讯超时时间调整为3s;
- 16、调度组件销毁流程优化,先停止调度线程,然后等待时间轮内存量任务处理完成,最终销毁时间轮线程;
- 17、执行器回调线程优化,回调地址为空时销毁问题修复;
- 18、HttpJobHandler优化,响应数据指定UTF-8格式,避免中文乱码;
- 19、代码优化,ConcurrentHashMap变量类型改为ConcurrentMap,避免因不同版本实现不同导致的兼容性问题;
### 6.26 版本 v2.1.1 Release Notes[2019-11-24]
- 1、 调度中心日志自动清理功能(至此,调度中心/执行器均支持日志自动清理,过期天数均默认设置为30天):调度中心新增配置项("xxl.job.logretentiondays")日志保存天数,过期日志自动清理;解决海量日志情况下日志表慢SQL问题;限制大于等于7时生效,否则关闭清理功能,默认为30;
- 2、 调度报表优化:新增日志报表的存储表,三天内的任务日志会以每分钟一次的频率异步同步至报表中;任务报表仅读取报表数据,极大提升加载速度;
- 3、 Cron在线生成工具:任务新增、编辑框通过组件在线生成Cron表达式;
- 4、 Cron下次执行时间查询:支持通过界面在线查看后续连续5次执行时间;
- 5、 调度中心新增应用健康检查功能,借助“spring-boot-starter-actuator”,相对地址 “/actuator/health”;
- 6、 DB脚本默认编码改为utf8mb4,修复字符乱码问题(建议Mysql版本5.7+);
- 7、 调度中心任务平均分配,触发组件每次获取与线程池数量相关数量的任务,避免大量任务集中在单个调度中心集群节点;
- 8、 任务触发组件优化,预加载频率正常1s一次,当预加载轮空时主动休眠一个加载周期,动态降低加载频率从而降低DB压力;
- 9、 调度组件优化:针对永远不会触发的Cron禁止配置和启动;任务Cron最后一次触发后再也不会触发时,比如一次性任务,主动停止相关任务;
- 10、DB重连优化,修复DB宕机重连后任务调度停止的问题,重连后自动加入调度集群触发任务调度;
- 11、注册监控线程优化,降低死锁几率;
- 12、调度中心日志删除优化,改为分页获取ID并根据ID删除的方式,避免批量删除海量日志导致死锁问题;
- 13、任务重试时参数丢失的问题修复;
- 14、调度中心移除SQL中的 "now()" 函数;集群部署时不再依赖DB时钟,仅需要保证调度中心应用节点时钟一致即可;
- 15、任务触发组件加载顺序调整,避免小概率情况下组件随机加载顺序导致的I18N的NPE问题;
- 16、JobThread自销毁优化,避免并发触发导致triggerQueue中任务丢失问题;
- 17、调度中心密码限制18位,修复修改密码超过18位无法登陆的问题;
- 18、任务告警组件分页参数无效问题修复;
- 19、升级xxl-rpc版本:服务端线程优化,降低线程内存开销;IpUtil优化:增加连通性校,过滤明确非法的网卡;
- 20、调度中心回调API服务改为restful方式;
- 21、UI优化,任务列表和日志列表数据表格宽度比例调整,避免数据换行提升体验;
- 22、登录界面取消默认填写的登录账号密码;
- 23、执行器表属性调整,"顺序" 属性调整为整型,解决执行器数据较多时无法正确排序的问题;
- 24、任务列表交互优化,支持查看任务所属执行器的注册节点;
- 25、项目依赖升级至较新稳定版本,如spring、spring-boot、mybatis、slf4j、groovy等等;
### 6.27 版本 v2.1.2 Release Notes[2019-12-12]
- 1、方法任务支持:由原来基于JobHandler类任务开发方式,优化为支持基于方法的任务开发方式;因此,可以支持单个类中开发多个任务方法,进行类复用
```
@XxlJob("demoJobHandler")
public ReturnT<String> execute(String param) {
    XxlJobLogger.log("hello world");
    return ReturnT.SUCCESS;
}
```
- 2、移除commons-exec,采用原生方式实现,降低第三方依赖;
- 3、执行器回调乱码问题修复;
- 4、调度中心dispatcher servlet加载顺序优化;
- 5、执行器回调地址https兼容支持;
- 6、多个项目依赖升级至较新稳定版本;
- 注意:最新版本 "XxlJobSpringExecutor" 逻辑有调整,历史项目中该组件的配置方式请参考Sample示例项目进行调整,尤其注意需要移除组件的init和destroy方法;
### TODO LIST
- 1、任务分片路由:分片采用一致性Hash算法计算出尽量稳定的分片顺序,即使注册机器存在波动也不会引起分批分片顺序大的波动;目前采用IP自然排序,可以满足需求,待定;
- 2、任务单机多线程:提升任务单机并行处理能力;
- 3、调度任务优先级;
- 4、多数据库支持,在重写并移除Quartz的基础上,DAO层通过JPA实现,不限制数据库类型;
- 5、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件;
- 6、任务自动注册:Bean模式任务,JobHandler自动从执行器中查询展示为下拉框,选择后自动填充任务名称等属性;待考虑,因为任务自动注册将会导致任务难以管理控制;
- 7、API事件触发类型任务(更类似MQ消息)支持"动态传参、延时消费";该类型任务不走调度组件,单独建立MQ消息表,调度中心竞争触发;待定,该功能与 XXL-MQ 冲突,该场景建议用后者;
- 8、调度线程池改为协程方式实现,大幅降低系统内存消耗;
- 9、任务、执行器数据全量本地缓存;新增消息表广播通知;
- 10、忙碌转移优化,全部机器忙碌时不再直接失败;
- 11、失败重试间隔;
- 12、SimpleTrigger:除Cron外,支持设置固定时间间隔触发;
- 13、调度日志列表加上执行时长列,并支持排序;
- 14、DAG流程任务:替换子任务,支持参数传递:配置并列的"a-b、b-c"路径列表,构成串行、并行、dag任务流程,"dagre-d3"绘图;任务依赖,流程图,子任务+会签任务,各节点日志;支持根据成功、失败选择分支;
- 15、日期过滤:支持多个时间段排除;
- 16、告警邮件内容,支持自定义模板配置;
- 17、暂停状态,支持Cron 为空;
- 18、新增任务运行模式 "GLUE模式(GO) ",支持GO任务;
- 19、注册中心优化,实时性注册发现:心跳注册间隔10s,refresh失败则首次注册并立即更新注册信息,心跳类似;30s过期销毁;
- 20、提供执行器Docker镜像;
- 21、脚本任务,支持数据参数,新版本仅支持单参数不支持需要兼容;
- 22、GLUE 模式 Web Ide 版本对比功能;
- 23、批量调度:调度请求入queue,调度线程批量获取调度请求并发起远程调度;提高线程效率;
- 24、多语言执行器:约定跨语言通讯方案,以及通讯接口;
- 25、移除commons-exec,采用原生实现;
- 26、调度中心JDK版本调整为JDK8,从而升级至最新版本SpringBoot;
- 27、执行器服务端口与注册端口分离,支持docker动态随机端口;
- 28、执行器端口复用,复用容器端口提供通讯服务;
- 29、自定义失败重试时间间隔;
- 30、分片任务全部成功后触发子任务;
- 31、任务复制功能;点击复制是弹出新建任务弹框,并初始化被复制任务信息;
- 32、AccessToken按照执行器维度设置;控制调度、回调;
- 33、任务执行一次的时候指定IP;
- 34、通讯调整;双向HTTP,回调和其他API自定义AccessToken,Restful,执行器复用容器端口;
- 35、父子任务参数传递;流程任务等,透传动态参数;
- 36、新增执行器描述、任务描述属性;
## 七、其他
### 7.1 项目贡献
欢迎参与项目贡献!比如提交PR修复一个bug,或者新建 [Issue](https://github.com/xuxueli/xxl-job/issues/) 讨论新特性或者变更。
### 7.2 用户接入登记
更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
### 7.3 开源协议和版权
产品开源免费,并且将持续提供免费的社区技术支持。个人或企业内部可自由的接入和使用。
- Licensed under the GNU General Public License (GPL) v3.
- Copyright (c) 2015-present, xuxueli.
---
### 捐赠
无论捐赠金额多少都足够表达您这份心意,非常感谢 :)      [前往捐赠](https://www.xuxueli.com/page/donate.html )
skjcmanager-ops/skjcmanager-xxljob-admin/doc/XXL-JOB架构图.pptx
Binary files differ
skjcmanager-ops/skjcmanager-xxljob-admin/doc/db/tables_xxl_job.sql
New file
@@ -0,0 +1,119 @@
#
# XXL-JOB v2.1.2
# Copyright (c) 2015-present, xuxueli.
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
use `xxl_job`;
CREATE TABLE `xxl_job_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `job_group` int(11) NOT NULL COMMENT '执行器主键ID',
  `job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
  `job_desc` varchar(255) NOT NULL,
  `add_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `author` varchar(64) DEFAULT NULL COMMENT '作者',
  `alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
  `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
  `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
  `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
  `executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
  `executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
  `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
  `glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
  `glue_source` mediumtext COMMENT 'GLUE源代码',
  `glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
  `glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
  `child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
  `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
  `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
  `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `job_group` int(11) NOT NULL COMMENT '执行器主键ID',
  `job_id` int(11) NOT NULL COMMENT '任务,主键ID',
  `executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
  `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
  `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
  `executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
  `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
  `trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
  `trigger_code` int(11) NOT NULL COMMENT '调度-结果',
  `trigger_msg` text COMMENT '调度-日志',
  `handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
  `handle_code` int(11) NOT NULL COMMENT '执行-状态',
  `handle_msg` text COMMENT '执行-日志',
  `alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
  PRIMARY KEY (`id`),
  KEY `I_trigger_time` (`trigger_time`),
  KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_log_report` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
  `running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
  `suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
  `fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_logglue` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `job_id` int(11) NOT NULL COMMENT '任务,主键ID',
  `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
  `glue_source` mediumtext COMMENT 'GLUE源代码',
  `glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
  `add_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_registry` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `registry_group` varchar(50) NOT NULL,
  `registry_key` varchar(255) NOT NULL,
  `registry_value` varchar(255) NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_group` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
  `title` varchar(12) NOT NULL COMMENT '执行器名称',
  `order` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
  `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
  `address_list` varchar(512) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '账号',
  `password` varchar(50) NOT NULL COMMENT '密码',
  `role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
  `permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_lock` (
  `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
  PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'blade-xxljob', '示例执行器', 1, 0, NULL);
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
skjcmanager-ops/skjcmanager-xxljob-admin/doc/nacos/blade-xxljob-admin-dev.yaml
New file
@@ -0,0 +1,16 @@
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      validation-query: select 1
#项目模块集中配置
blade:
  #工作流模块开发生产环境数据库地址
  datasource:
    job:
      dev:
        # MySql
        url: jdbc:mysql://127.0.0.1:3306/xxl_job?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8
        username: root
        password: root
skjcmanager-ops/skjcmanager-xxljob-admin/pom.xml
New file
@@ -0,0 +1,117 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>skjcmanager-ops</artifactId>
        <groupId>org.springblade</groupId>
        <version>3.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>skjcmanager-xxljob-admin</artifactId>
    <name>${project.artifactId}</name>
    <version>${bladex.project.version}</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.test.skip>true</maven.test.skip>
        <xxl-rpc.version>1.5.0</xxl-rpc.version>
        <spring.version>4.3.25.RELEASE</spring.version>
        <spring-boot.version>1.5.22.RELEASE</spring-boot.version>
        <mybatis-spring-boot-starter.version>1.3.5</mybatis-spring-boot-starter.version>
        <mysql-connector-java.version>5.1.48</mysql-connector-java.version>
        <slf4j-api.version>1.7.29</slf4j-api.version>
        <junit.version>4.12</junit.version>
        <groovy.version>2.5.8</groovy.version>
        <maven-source-plugin.version>3.2.0</maven-source-plugin.version>
        <maven-javadoc-plugin.version>3.1.1</maven-javadoc-plugin.version>
        <maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
        <maven-war-plugin.version>3.2.3</maven-war-plugin.version>
    </properties>
    <dependencies>
        <!--Blade-->
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>skjcmanager-common</artifactId>
            <version>${bladex.project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-cloud</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springblade</groupId>
            <artifactId>blade-core-launch</artifactId>
        </dependency>
        <!-- freemarker-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <!-- mail-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <!-- starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- mybatis-starter:mybatis + mybatis-spring + tomcat-jdbc(default) -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot-starter.version}</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- xxl-job-core -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <configuration>
                    <username>${docker.username}</username>
                    <password>${docker.password}</password>
                    <repository>${docker.registry.url}/${docker.namespace}/${project.artifactId}</repository>
                    <tag>${project.version}</tag>
                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                    <skip>false</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/JobAdminApplication.java
New file
@@ -0,0 +1,17 @@
package cn.gistack.job.admin;
import cn.gistack.common.constant.LauncherConstant;
import org.springblade.core.launch.BladeApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author xuxueli 2018-10-28 00:38:13
 */
@SpringBootApplication
public class JobAdminApplication {
    public static void main(String[] args) {
        BladeApplication.run(LauncherConstant.APPLICATION_XXLJOB_ADMIN_NAME, JobAdminApplication.class, args);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/IndexController.java
New file
@@ -0,0 +1,93 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.service.LoginService;
import cn.gistack.job.admin.service.XxlJobService;
import cn.gistack.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
 * index controller
 * @author xuxueli 2015-12-19 16:13:16
 */
@Controller
public class IndexController {
    @Resource
    private XxlJobService xxlJobService;
    @Resource
    private LoginService loginService;
    @RequestMapping("/")
    public String index(Model model) {
        Map<String, Object> dashboardMap = xxlJobService.dashboardInfo();
        model.addAllAttributes(dashboardMap);
        return "index";
    }
    @RequestMapping("/chartInfo")
    @ResponseBody
    public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
        ReturnT<Map<String, Object>> chartInfo = xxlJobService.chartInfo(startDate, endDate);
        return chartInfo;
    }
    @RequestMapping("/toLogin")
    @PermissionLimit(limit=false)
    public String toLogin(HttpServletRequest request, HttpServletResponse response) {
        if (loginService.ifLogin(request, response) != null) {
            return "redirect:/";
        }
        return "login";
    }
    @RequestMapping(value="login", method=RequestMethod.POST)
    @ResponseBody
    @PermissionLimit(limit=false)
    public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
        boolean ifRem = (ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember))?true:false;
        return loginService.login(request, response, userName, password, ifRem);
    }
    @RequestMapping(value="logout", method=RequestMethod.POST)
    @ResponseBody
    @PermissionLimit(limit=false)
    public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){
        return loginService.logout(request, response);
    }
    @RequestMapping("/help")
    public String help() {
        /*if (!PermissionInterceptor.ifLogin(request)) {
            return "redirect:/toLogin";
        }*/
        return "help";
    }
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobApiController.java
New file
@@ -0,0 +1,129 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import cn.gistack.job.admin.core.exception.XxlJobException;
import cn.gistack.job.admin.core.util.JacksonUtil;
import cn.gistack.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.XxlJobRemotingUtil;
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 javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
 * Created by xuxueli on 17/5/10.
 */
@Controller
@RequestMapping("/api")
public class JobApiController {
    @Resource
    private AdminBiz adminBiz;
    // ---------------------- base ----------------------
    /**
     * valid access token
     */
    private void validAccessToken(HttpServletRequest request){
        if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null
                && XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0
                && !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_RPC_ACCESS_TOKEN))) {
            throw new XxlJobException("The access token is wrong.");
        }
    }
    /**
     * parse Param
     */
    private Object parseParam(String data, Class<?> parametrized, Class<?>... parameterClasses){
        Object param = null;
        try {
            if (parameterClasses != null) {
                param = JacksonUtil.readValue(data, parametrized, parameterClasses);
            } else {
                param = JacksonUtil.readValue(data, parametrized);
            }
        } catch (Exception e) { }
        if (param==null) {
            throw new XxlJobException("The request data invalid.");
        }
        return param;
    }
    // ---------------------- admin biz ----------------------
    /**
     * callback
     *
     * @param data
     * @return
     */
    @RequestMapping("/callback")
    @ResponseBody
    @PermissionLimit(limit=false)
    public ReturnT<String> callback(HttpServletRequest request, @RequestBody(required = false) String data) {
        // valid
        validAccessToken(request);
        // param
        List<HandleCallbackParam> callbackParamList = (List<HandleCallbackParam>) parseParam(data, List.class, HandleCallbackParam.class);
        // invoke
        return adminBiz.callback(callbackParamList);
    }
    /**
     * registry
     *
     * @param data
     * @return
     */
    @RequestMapping("/registry")
    @ResponseBody
    @PermissionLimit(limit=false)
    public ReturnT<String> registry(HttpServletRequest request, @RequestBody(required = false) String data) {
        // valid
        validAccessToken(request);
        // param
        RegistryParam registryParam = (RegistryParam) parseParam(data, RegistryParam.class);
        // invoke
        return adminBiz.registry(registryParam);
    }
    /**
     * registry remove
     *
     * @param data
     * @return
     */
    @RequestMapping("/registryRemove")
    @ResponseBody
    @PermissionLimit(limit=false)
    public ReturnT<String> registryRemove(HttpServletRequest request, @RequestBody(required = false) String data) {
        // valid
        validAccessToken(request);
        // param
        RegistryParam registryParam = (RegistryParam) parseParam(data, RegistryParam.class);
        // invoke
        return adminBiz.registryRemove(registryParam);
    }
    // ---------------------- job biz ----------------------
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobCodeController.java
New file
@@ -0,0 +1,96 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.model.XxlJobLogGlue;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.dao.XxlJobInfoDao;
import cn.gistack.job.admin.dao.XxlJobLogGlueDao;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.glue.GlueTypeEnum;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
 * job code controller
 * @author xuxueli 2015-12-19 16:13:16
 */
@Controller
@RequestMapping("/jobcode")
public class JobCodeController {
    @Resource
    private XxlJobInfoDao xxlJobInfoDao;
    @Resource
    private XxlJobLogGlueDao xxlJobLogGlueDao;
    @RequestMapping
    public String index(HttpServletRequest request, Model model, int jobId) {
        XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
        List<XxlJobLogGlue> jobLogGlues = xxlJobLogGlueDao.findByJobId(jobId);
        if (jobInfo == null) {
            throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
        }
        if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) {
            throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_unvalid"));
        }
        // valid permission
        JobInfoController.validPermission(request, jobInfo.getJobGroup());
        // Glue类型-字典
        model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
        model.addAttribute("jobInfo", jobInfo);
        model.addAttribute("jobLogGlues", jobLogGlues);
        return "jobcode/jobcode.index";
    }
    @RequestMapping("/save")
    @ResponseBody
    public ReturnT<String> save(Model model, int id, String glueSource, String glueRemark) {
        // valid
        if (glueRemark==null) {
            return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) );
        }
        if (glueRemark.length()<4 || glueRemark.length()>100) {
            return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_remark_limit"));
        }
        XxlJobInfo exists_jobInfo = xxlJobInfoDao.loadById(id);
        if (exists_jobInfo == null) {
            return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
        }
        // update new code
        exists_jobInfo.setGlueSource(glueSource);
        exists_jobInfo.setGlueRemark(glueRemark);
        exists_jobInfo.setGlueUpdatetime(new Date());
        exists_jobInfo.setUpdateTime(new Date());
        xxlJobInfoDao.update(exists_jobInfo);
        // log old code
        XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
        xxlJobLogGlue.setJobId(exists_jobInfo.getId());
        xxlJobLogGlue.setGlueType(exists_jobInfo.getGlueType());
        xxlJobLogGlue.setGlueSource(glueSource);
        xxlJobLogGlue.setGlueRemark(glueRemark);
        xxlJobLogGlue.setAddTime(new Date());
        xxlJobLogGlue.setUpdateTime(new Date());
        xxlJobLogGlueDao.save(xxlJobLogGlue);
        // remove code backup more than 30
        xxlJobLogGlueDao.removeOld(exists_jobInfo.getId(), 30);
        return ReturnT.SUCCESS;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobGroupController.java
New file
@@ -0,0 +1,165 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.dao.XxlJobGroupDao;
import cn.gistack.job.admin.dao.XxlJobInfoDao;
import cn.gistack.job.admin.dao.XxlJobRegistryDao;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.RegistryConfig;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.*;
/**
 * job group controller
 * @author xuxueli 2016-10-02 20:52:56
 */
@Controller
@RequestMapping("/jobgroup")
public class JobGroupController {
    @Resource
    public XxlJobInfoDao xxlJobInfoDao;
    @Resource
    public XxlJobGroupDao xxlJobGroupDao;
    @Resource
    private XxlJobRegistryDao xxlJobRegistryDao;
    @RequestMapping
    public String index(Model model) {
        // job group (executor)
        List<XxlJobGroup> list = xxlJobGroupDao.findAll();
        model.addAttribute("list", list);
        return "jobgroup/jobgroup.index";
    }
    @RequestMapping("/save")
    @ResponseBody
    public ReturnT<String> save(XxlJobGroup xxlJobGroup){
        // valid
        if (xxlJobGroup.getAppName()==null || xxlJobGroup.getAppName().trim().length()==0) {
            return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
        }
        if (xxlJobGroup.getAppName().length()<4 || xxlJobGroup.getAppName().length()>64) {
            return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appName_length") );
        }
        if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
            return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
        }
        if (xxlJobGroup.getAddressType()!=0) {
            if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
                return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
            }
            String[] addresss = xxlJobGroup.getAddressList().split(",");
            for (String item: addresss) {
                if (item==null || item.trim().length()==0) {
                    return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
                }
            }
        }
        int ret = xxlJobGroupDao.save(xxlJobGroup);
        return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
    }
    @RequestMapping("/update")
    @ResponseBody
    public ReturnT<String> update(XxlJobGroup xxlJobGroup){
        // valid
        if (xxlJobGroup.getAppName()==null || xxlJobGroup.getAppName().trim().length()==0) {
            return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
        }
        if (xxlJobGroup.getAppName().length()<4 || xxlJobGroup.getAppName().length()>64) {
            return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appName_length") );
        }
        if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
            return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
        }
        if (xxlJobGroup.getAddressType() == 0) {
            // 0=自动注册
            List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppName());
            String addressListStr = null;
            if (registryList!=null && !registryList.isEmpty()) {
                Collections.sort(registryList);
                addressListStr = "";
                for (String item:registryList) {
                    addressListStr += item + ",";
                }
                addressListStr = addressListStr.substring(0, addressListStr.length()-1);
            }
            xxlJobGroup.setAddressList(addressListStr);
        } else {
            // 1=手动录入
            if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
                return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
            }
            String[] addresss = xxlJobGroup.getAddressList().split(",");
            for (String item: addresss) {
                if (item==null || item.trim().length()==0) {
                    return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
                }
            }
        }
        int ret = xxlJobGroupDao.update(xxlJobGroup);
        return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
    }
    private List<String> findRegistryByAppName(String appNameParam){
        HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
        List<XxlJobRegistry> list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
        if (list != null) {
            for (XxlJobRegistry item: list) {
                if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
                    String appName = item.getRegistryKey();
                    List<String> registryList = appAddressMap.get(appName);
                    if (registryList == null) {
                        registryList = new ArrayList<String>();
                    }
                    if (!registryList.contains(item.getRegistryValue())) {
                        registryList.add(item.getRegistryValue());
                    }
                    appAddressMap.put(appName, registryList);
                }
            }
        }
        return appAddressMap.get(appNameParam);
    }
    @RequestMapping("/remove")
    @ResponseBody
    public ReturnT<String> remove(int id){
        // valid
        int count = xxlJobInfoDao.pageListCount(0, 10, id, -1,  null, null, null);
        if (count > 0) {
            return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_0") );
        }
        List<XxlJobGroup> allList = xxlJobGroupDao.findAll();
        if (allList.size() == 1) {
            return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_1") );
        }
        int ret = xxlJobGroupDao.remove(id);
        return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
    }
    @RequestMapping("/loadById")
    @ResponseBody
    public ReturnT<XxlJobGroup> loadById(int id){
        XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
        return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobInfoController.java
New file
@@ -0,0 +1,166 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.core.cron.CronExpression;
import cn.gistack.job.admin.core.exception.XxlJobException;
import cn.gistack.job.admin.core.route.ExecutorRouteStrategyEnum;
import cn.gistack.job.admin.core.thread.JobTriggerPoolHelper;
import cn.gistack.job.admin.core.trigger.TriggerTypeEnum;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.dao.XxlJobGroupDao;
import cn.gistack.job.admin.service.LoginService;
import cn.gistack.job.admin.service.XxlJobService;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.model.XxlJobUser;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.util.DateUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.util.*;
/**
 * index controller
 * @author xuxueli 2015-12-19 16:13:16
 */
@Controller
@RequestMapping("/jobinfo")
public class JobInfoController {
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @Resource
    private XxlJobService xxlJobService;
    @RequestMapping
    public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) {
        // 枚举-字典
        model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values());        // 路由策略-列表
        model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());                                // Glue类型-字典
        model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values());        // 阻塞处理策略-字典
        // 执行器列表
        List<XxlJobGroup> jobGroupList_all =  xxlJobGroupDao.findAll();
        // filter group
        List<XxlJobGroup> jobGroupList = filterJobGroupByRole(request, jobGroupList_all);
        if (jobGroupList==null || jobGroupList.size()==0) {
            throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
        }
        model.addAttribute("JobGroupList", jobGroupList);
        model.addAttribute("jobGroup", jobGroup);
        return "jobinfo/jobinfo.index";
    }
    public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){
        List<XxlJobGroup> jobGroupList = new ArrayList<>();
        if (jobGroupList_all!=null && jobGroupList_all.size()>0) {
            XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
            if (loginUser.getRole() == 1) {
                jobGroupList = jobGroupList_all;
            } else {
                List<String> groupIdStrs = new ArrayList<>();
                if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
                    groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
                }
                for (XxlJobGroup groupItem:jobGroupList_all) {
                    if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) {
                        jobGroupList.add(groupItem);
                    }
                }
            }
        }
        return jobGroupList;
    }
    public static void validPermission(HttpServletRequest request, int jobGroup) {
        XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
        if (!loginUser.validPermission(jobGroup)) {
            throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
        }
    }
    @RequestMapping("/pageList")
    @ResponseBody
    public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
            @RequestParam(required = false, defaultValue = "10") int length,
            int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
        return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
    }
    @RequestMapping("/add")
    @ResponseBody
    public ReturnT<String> add(XxlJobInfo jobInfo) {
        return xxlJobService.add(jobInfo);
    }
    @RequestMapping("/update")
    @ResponseBody
    public ReturnT<String> update(XxlJobInfo jobInfo) {
        return xxlJobService.update(jobInfo);
    }
    @RequestMapping("/remove")
    @ResponseBody
    public ReturnT<String> remove(int id) {
        return xxlJobService.remove(id);
    }
    @RequestMapping("/stop")
    @ResponseBody
    public ReturnT<String> pause(int id) {
        return xxlJobService.stop(id);
    }
    @RequestMapping("/start")
    @ResponseBody
    public ReturnT<String> start(int id) {
        return xxlJobService.start(id);
    }
    @RequestMapping("/trigger")
    @ResponseBody
    //@PermissionLimit(limit = false)
    public ReturnT<String> triggerJob(int id, String executorParam) {
        // force cover job param
        if (executorParam == null) {
            executorParam = "";
        }
        JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam);
        return ReturnT.SUCCESS;
    }
    @RequestMapping("/nextTriggerTime")
    @ResponseBody
    public ReturnT<List<String>> nextTriggerTime(String cron) {
        List<String> result = new ArrayList<>();
        try {
            CronExpression cronExpression = new CronExpression(cron);
            Date lastTime = new Date();
            for (int i = 0; i < 5; i++) {
                lastTime = cronExpression.getNextValidTimeAfter(lastTime);
                if (lastTime != null) {
                    result.add(DateUtil.formatDateTime(lastTime));
                } else {
                    break;
                }
            }
        } catch (ParseException e) {
            return new ReturnT<List<String>>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid"));
        }
        return new ReturnT<List<String>>(result);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/JobLogController.java
New file
@@ -0,0 +1,230 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.core.exception.XxlJobException;
import cn.gistack.job.admin.core.scheduler.XxlJobScheduler;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.dao.XxlJobGroupDao;
import cn.gistack.job.admin.dao.XxlJobInfoDao;
import cn.gistack.job.admin.dao.XxlJobLogDao;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.model.XxlJobLog;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.LogResult;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * index controller
 * @author xuxueli 2015-12-19 16:13:16
 */
@Controller
@RequestMapping("/joblog")
public class JobLogController {
    private static Logger logger = LoggerFactory.getLogger(JobLogController.class);
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @Resource
    public XxlJobInfoDao xxlJobInfoDao;
    @Resource
    public XxlJobLogDao xxlJobLogDao;
    @RequestMapping
    public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "0") Integer jobId) {
        // 执行器列表
        List<XxlJobGroup> jobGroupList_all =  xxlJobGroupDao.findAll();
        // filter group
        List<XxlJobGroup> jobGroupList = JobInfoController.filterJobGroupByRole(request, jobGroupList_all);
        if (jobGroupList==null || jobGroupList.size()==0) {
            throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
        }
        model.addAttribute("JobGroupList", jobGroupList);
        // 任务
        if (jobId > 0) {
            XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
            if (jobInfo == null) {
                throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid"));
            }
            model.addAttribute("jobInfo", jobInfo);
            // valid permission
            JobInfoController.validPermission(request, jobInfo.getJobGroup());
        }
        return "joblog/joblog.index";
    }
    @RequestMapping("/getJobsByGroup")
    @ResponseBody
    public ReturnT<List<XxlJobInfo>> getJobsByGroup(int jobGroup){
        List<XxlJobInfo> list = xxlJobInfoDao.getJobsByGroup(jobGroup);
        return new ReturnT<List<XxlJobInfo>>(list);
    }
    @RequestMapping("/pageList")
    @ResponseBody
    public Map<String, Object> pageList(HttpServletRequest request,
                                        @RequestParam(required = false, defaultValue = "0") int start,
                                        @RequestParam(required = false, defaultValue = "10") int length,
                                        int jobGroup, int jobId, int logStatus, String filterTime) {
        // valid permission
        JobInfoController.validPermission(request, jobGroup);    // 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup
        // parse param
        Date triggerTimeStart = null;
        Date triggerTimeEnd = null;
        if (filterTime!=null && filterTime.trim().length()>0) {
            String[] temp = filterTime.split(" - ");
            if (temp.length == 2) {
                triggerTimeStart = DateUtil.parseDateTime(temp[0]);
                triggerTimeEnd = DateUtil.parseDateTime(temp[1]);
            }
        }
        // page query
        List<XxlJobLog> list = xxlJobLogDao.pageList(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
        int list_count = xxlJobLogDao.pageListCount(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
        // package result
        Map<String, Object> maps = new HashMap<String, Object>();
        maps.put("recordsTotal", list_count);        // 总记录数
        maps.put("recordsFiltered", list_count);    // 过滤后的总记录数
        maps.put("data", list);                      // 分页列表
        return maps;
    }
    @RequestMapping("/logDetailPage")
    public String logDetailPage(int id, Model model){
        // base check
        ReturnT<String> logStatue = ReturnT.SUCCESS;
        XxlJobLog jobLog = xxlJobLogDao.load(id);
        if (jobLog == null) {
            throw new RuntimeException(I18nUtil.getString("joblog_logid_unvalid"));
        }
        model.addAttribute("triggerCode", jobLog.getTriggerCode());
        model.addAttribute("handleCode", jobLog.getHandleCode());
        model.addAttribute("executorAddress", jobLog.getExecutorAddress());
        model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
        model.addAttribute("logId", jobLog.getId());
        return "joblog/joblog.detail";
    }
    @RequestMapping("/logDetailCat")
    @ResponseBody
    public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum){
        try {
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
            ReturnT<LogResult> logResult = executorBiz.log(triggerTime, logId, fromLineNum);
            // is end
            if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
                XxlJobLog jobLog = xxlJobLogDao.load(logId);
                if (jobLog.getHandleCode() > 0) {
                    logResult.getContent().setEnd(true);
                }
            }
            return logResult;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ReturnT<LogResult>(ReturnT.FAIL_CODE, e.getMessage());
        }
    }
    @RequestMapping("/logKill")
    @ResponseBody
    public ReturnT<String> logKill(int id){
        // base check
        XxlJobLog log = xxlJobLogDao.load(id);
        XxlJobInfo jobInfo = xxlJobInfoDao.loadById(log.getJobId());
        if (jobInfo==null) {
            return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
        }
        if (ReturnT.SUCCESS_CODE != log.getTriggerCode()) {
            return new ReturnT<String>(500, I18nUtil.getString("joblog_kill_log_limit"));
        }
        // request of kill
        ReturnT<String> runResult = null;
        try {
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
            runResult = executorBiz.kill(jobInfo.getId());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            runResult = new ReturnT<String>(500, e.getMessage());
        }
        if (ReturnT.SUCCESS_CODE == runResult.getCode()) {
            log.setHandleCode(ReturnT.FAIL_CODE);
            log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
            log.setHandleTime(new Date());
            xxlJobLogDao.updateHandleInfo(log);
            return new ReturnT<String>(runResult.getMsg());
        } else {
            return new ReturnT<String>(500, runResult.getMsg());
        }
    }
    @RequestMapping("/clearLog")
    @ResponseBody
    public ReturnT<String> clearLog(int jobGroup, int jobId, int type){
        Date clearBeforeTime = null;
        int clearBeforeNum = 0;
        if (type == 1) {
            clearBeforeTime = DateUtil.addMonths(new Date(), -1);    // 清理一个月之前日志数据
        } else if (type == 2) {
            clearBeforeTime = DateUtil.addMonths(new Date(), -3);    // 清理三个月之前日志数据
        } else if (type == 3) {
            clearBeforeTime = DateUtil.addMonths(new Date(), -6);    // 清理六个月之前日志数据
        } else if (type == 4) {
            clearBeforeTime = DateUtil.addYears(new Date(), -1);    // 清理一年之前日志数据
        } else if (type == 5) {
            clearBeforeNum = 1000;        // 清理一千条以前日志数据
        } else if (type == 6) {
            clearBeforeNum = 10000;        // 清理一万条以前日志数据
        } else if (type == 7) {
            clearBeforeNum = 30000;        // 清理三万条以前日志数据
        } else if (type == 8) {
            clearBeforeNum = 100000;    // 清理十万条以前日志数据
        } else if (type == 9) {
            clearBeforeNum = 0;            // 清理所有日志数据
        } else {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_clean_type_unvalid"));
        }
        List<Long> logIds = null;
        do {
            logIds = xxlJobLogDao.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000);
            if (logIds!=null && logIds.size()>0) {
                xxlJobLogDao.clearLog(logIds);
            }
        } while (logIds!=null && logIds.size()>0);
        return ReturnT.SUCCESS;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/UserController.java
New file
@@ -0,0 +1,172 @@
package cn.gistack.job.admin.controller;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.dao.XxlJobGroupDao;
import cn.gistack.job.admin.dao.XxlJobUserDao;
import cn.gistack.job.admin.service.LoginService;
import cn.gistack.job.admin.controller.annotation.PermissionLimit;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobUser;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author xuxueli 2019-05-04 16:39:50
 */
@Controller
@RequestMapping("/user")
public class UserController {
    @Resource
    private XxlJobUserDao xxlJobUserDao;
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @RequestMapping
    @PermissionLimit(adminuser = true)
    public String index(Model model) {
        // 执行器列表
        List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
        model.addAttribute("groupList", groupList);
        return "user/user.index";
    }
    @RequestMapping("/pageList")
    @ResponseBody
    @PermissionLimit(adminuser = true)
    public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
                                        @RequestParam(required = false, defaultValue = "10") int length,
                                        String username, int role) {
        // page list
        List<XxlJobUser> list = xxlJobUserDao.pageList(start, length, username, role);
        int list_count = xxlJobUserDao.pageListCount(start, length, username, role);
        // package result
        Map<String, Object> maps = new HashMap<String, Object>();
        maps.put("recordsTotal", list_count);        // 总记录数
        maps.put("recordsFiltered", list_count);    // 过滤后的总记录数
        maps.put("data", list);                      // 分页列表
        return maps;
    }
    @RequestMapping("/add")
    @ResponseBody
    @PermissionLimit(adminuser = true)
    public ReturnT<String> add(XxlJobUser xxlJobUser) {
        // valid username
        if (!StringUtils.hasText(xxlJobUser.getUsername())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") );
        }
        xxlJobUser.setUsername(xxlJobUser.getUsername().trim());
        if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
        }
        // valid password
        if (!StringUtils.hasText(xxlJobUser.getPassword())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") );
        }
        xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
        if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
        }
        // md5 password
        xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
        // check repeat
        XxlJobUser existUser = xxlJobUserDao.loadByUserName(xxlJobUser.getUsername());
        if (existUser != null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("user_username_repeat") );
        }
        // write
        xxlJobUserDao.save(xxlJobUser);
        return ReturnT.SUCCESS;
    }
    @RequestMapping("/update")
    @ResponseBody
    @PermissionLimit(adminuser = true)
    public ReturnT<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) {
        // avoid opt login seft
        XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
        if (loginUser.getUsername().equals(xxlJobUser.getUsername())) {
            return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
        }
        // valid password
        if (StringUtils.hasText(xxlJobUser.getPassword())) {
            xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
            if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
                return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
            }
            // md5 password
            xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
        } else {
            xxlJobUser.setPassword(null);
        }
        // write
        xxlJobUserDao.update(xxlJobUser);
        return ReturnT.SUCCESS;
    }
    @RequestMapping("/remove")
    @ResponseBody
    @PermissionLimit(adminuser = true)
    public ReturnT<String> remove(HttpServletRequest request, int id) {
        // avoid opt login seft
        XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
        if (loginUser.getId() == id) {
            return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
        }
        xxlJobUserDao.delete(id);
        return ReturnT.SUCCESS;
    }
    @RequestMapping("/updatePwd")
    @ResponseBody
    public ReturnT<String> updatePwd(HttpServletRequest request, String password){
        // valid password
        if (password==null || password.trim().length()==0){
            return new ReturnT<String>(ReturnT.FAIL.getCode(), "密码不可为空");
        }
        password = password.trim();
        if (!(password.length()>=4 && password.length()<=20)) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
        }
        // md5 password
        String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
        // update pwd
        XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
        // do write
        XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername());
        existUser.setPassword(md5Password);
        xxlJobUserDao.update(existUser);
        return ReturnT.SUCCESS;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/annotation/PermissionLimit.java
New file
@@ -0,0 +1,29 @@
package cn.gistack.job.admin.controller.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 权限限制
 * @author xuxueli 2015-12-12 18:29:02
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {
    /**
     * 登录拦截 (默认拦截)
     */
    boolean limit() default true;
    /**
     * 要求管理员权限
     *
     * @return
     */
    boolean adminuser() default false;
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/interceptor/CookieInterceptor.java
New file
@@ -0,0 +1,43 @@
package cn.gistack.job.admin.controller.interceptor;
import cn.gistack.job.admin.core.util.FtlUtil;
import cn.gistack.job.admin.core.util.I18nUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
 * push cookies to model as cookieMap
 *
 * @author xuxueli 2015-12-12 18:09:04
 */
@Component
public class CookieInterceptor extends HandlerInterceptorAdapter {
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // cookie
        if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) {
            HashMap<String, Cookie> cookieMap = new HashMap<String, Cookie>();
            for (Cookie ck : request.getCookies()) {
                cookieMap.put(ck.getName(), ck);
            }
            modelAndView.addObject("cookieMap", cookieMap);
        }
        // static method
        if (modelAndView != null) {
            modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
        }
        super.postHandle(request, response, handler, modelAndView);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/interceptor/PermissionInterceptor.java
New file
@@ -0,0 +1,59 @@
package cn.gistack.job.admin.controller.interceptor;
import cn.gistack.job.admin.controller.annotation.PermissionLimit;
import cn.gistack.job.admin.core.model.XxlJobUser;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.service.LoginService;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 权限拦截
 *
 * @author xuxueli 2015-12-12 18:09:04
 */
@Component
public class PermissionInterceptor extends HandlerInterceptorAdapter {
    @Resource
    private LoginService loginService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return super.preHandle(request, response, handler);
        }
        // if need login
        boolean needLogin = true;
        boolean needAdminuser = false;
        HandlerMethod method = (HandlerMethod)handler;
        PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
        if (permission!=null) {
            needLogin = permission.limit();
            needAdminuser = permission.adminuser();
        }
        if (needLogin) {
            XxlJobUser loginUser = loginService.ifLogin(request, response);
            if (loginUser == null) {
                response.sendRedirect(request.getContextPath() + "/toLogin");
                //request.getRequestDispatcher("/toLogin").forward(request, response);
                return false;
            }
            if (needAdminuser && loginUser.getRole()!=1) {
                throw new RuntimeException(I18nUtil.getString("system_permission_limit"));
            }
            request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
        }
        return super.preHandle(request, response, handler);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/interceptor/WebMvcConfig.java
New file
@@ -0,0 +1,29 @@
package cn.gistack.job.admin.controller.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Resource;
/**
 * web mvc config
 *
 * @author xuxueli 2018-04-02 20:48:20
 */
@Configuration(proxyBeanMethods = false)
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Resource
    private PermissionInterceptor permissionInterceptor;
    @Resource
    private CookieInterceptor cookieInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
        registry.addInterceptor(cookieInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/controller/resolver/WebExceptionResolver.java
New file
@@ -0,0 +1,64 @@
package cn.gistack.job.admin.controller.resolver;
import cn.gistack.job.admin.core.exception.XxlJobException;
import cn.gistack.job.admin.core.util.JacksonUtil;
import com.xxl.job.core.biz.model.ReturnT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * common exception resolver
 *
 * @author xuxueli 2016-1-6 19:22:18
 */
@Component
public class WebExceptionResolver implements HandlerExceptionResolver {
    private static transient Logger logger = LoggerFactory.getLogger(WebExceptionResolver.class);
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex) {
        if (!(ex instanceof XxlJobException)) {
            logger.error("WebExceptionResolver:{}", ex);
        }
        // if json
        boolean isJson = false;
        HandlerMethod method = (HandlerMethod)handler;
        ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
        if (responseBody != null) {
            isJson = true;
        }
        // error result
        ReturnT<String> errorResult = new ReturnT<String>(ReturnT.FAIL_CODE, ex.toString().replaceAll("\n", "<br/>"));
        // response
        ModelAndView mv = new ModelAndView();
        if (isJson) {
            try {
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().print(JacksonUtil.writeValueAsString(errorResult));
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
            return mv;
        } else {
            mv.addObject("exceptionMsg", errorResult.getMsg());
            mv.setViewName("/common/common.exception");
            return mv;
        }
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/conf/XxlJobAdminConfig.java
New file
@@ -0,0 +1,148 @@
package cn.gistack.job.admin.core.conf;
import cn.gistack.job.admin.core.scheduler.XxlJobScheduler;
import cn.gistack.job.admin.dao.*;
import com.xxl.job.admin.dao.*;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
 * xxl-job config
 *
 * @author xuxueli 2017-04-28
 */
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
    private static XxlJobAdminConfig adminConfig = null;
    public static XxlJobAdminConfig getAdminConfig() {
        return adminConfig;
    }
    // ---------------------- XxlJobScheduler ----------------------
    private XxlJobScheduler xxlJobScheduler;
    @Override
    public void afterPropertiesSet() throws Exception {
        adminConfig = this;
        xxlJobScheduler = new XxlJobScheduler();
        xxlJobScheduler.init();
    }
    @Override
    public void destroy() throws Exception {
        xxlJobScheduler.destroy();
    }
    // ---------------------- XxlJobScheduler ----------------------
    // conf
    @Value("${xxl.job.i18n}")
    private String i18n;
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    @Value("${spring.mail.username}")
    private String emailUserName;
    @Value("${xxl.job.triggerpool.fast.max}")
    private int triggerPoolFastMax;
    @Value("${xxl.job.triggerpool.slow.max}")
    private int triggerPoolSlowMax;
    @Value("${xxl.job.logretentiondays}")
    private int logretentiondays;
    // dao, service
    @Resource
    private XxlJobLogDao xxlJobLogDao;
    @Resource
    private XxlJobInfoDao xxlJobInfoDao;
    @Resource
    private XxlJobRegistryDao xxlJobRegistryDao;
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @Resource
    private XxlJobLogReportDao xxlJobLogReportDao;
    @Resource
    private JavaMailSender mailSender;
    @Resource
    private DataSource dataSource;
    public String getI18n() {
        return i18n;
    }
    public String getAccessToken() {
        return accessToken;
    }
    public String getEmailUserName() {
        return emailUserName;
    }
    public int getTriggerPoolFastMax() {
        if (triggerPoolFastMax < 200) {
            return 200;
        }
        return triggerPoolFastMax;
    }
    public int getTriggerPoolSlowMax() {
        if (triggerPoolSlowMax < 100) {
            return 100;
        }
        return triggerPoolSlowMax;
    }
    public int getLogretentiondays() {
        if (logretentiondays < 7) {
            return -1;  // Limit greater than or equal to 7, otherwise close
        }
        return logretentiondays;
    }
    public XxlJobLogDao getXxlJobLogDao() {
        return xxlJobLogDao;
    }
    public XxlJobInfoDao getXxlJobInfoDao() {
        return xxlJobInfoDao;
    }
    public XxlJobRegistryDao getXxlJobRegistryDao() {
        return xxlJobRegistryDao;
    }
    public XxlJobGroupDao getXxlJobGroupDao() {
        return xxlJobGroupDao;
    }
    public XxlJobLogReportDao getXxlJobLogReportDao() {
        return xxlJobLogReportDao;
    }
    public JavaMailSender getMailSender() {
        return mailSender;
    }
    public DataSource getDataSource() {
        return dataSource;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/cron/CronExpression.java
New file
@@ -0,0 +1,1666 @@
/*
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
 *
 * 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
 *
 *   http://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.
 *
 */
package cn.gistack.job.admin.core.cron;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.TreeSet;
/**
 * Provides a parser and evaluator for unix-like cron expressions. Cron
 * expressions provide the ability to specify complex time combinations such as
 * &quot;At 8:00am every Monday through Friday&quot; or &quot;At 1:30am every
 * last Friday of the month&quot;.
 * <P>
 * Cron expressions are comprised of 6 required fields and one optional field
 * separated by white space. The fields respectively are described as follows:
 *
 * <table cellspacing="8">
 * <tr>
 * <th align="left">Field Name</th>
 * <th align="left">&nbsp;</th>
 * <th align="left">Allowed Values</th>
 * <th align="left">&nbsp;</th>
 * <th align="left">Allowed Special Characters</th>
 * </tr>
 * <tr>
 * <td align="left"><code>Seconds</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>0-59</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * /</code></td>
 * </tr>
 * <tr>
 * <td align="left"><code>Minutes</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>0-59</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * /</code></td>
 * </tr>
 * <tr>
 * <td align="left"><code>Hours</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>0-23</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * /</code></td>
 * </tr>
 * <tr>
 * <td align="left"><code>Day-of-month</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>1-31</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * ? / L W</code></td>
 * </tr>
 * <tr>
 * <td align="left"><code>Month</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>0-11 or JAN-DEC</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * /</code></td>
 * </tr>
 * <tr>
 * <td align="left"><code>Day-of-Week</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>1-7 or SUN-SAT</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * ? / L #</code></td>
 * </tr>
 * <tr>
 * <td align="left"><code>Year (Optional)</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>empty, 1970-2199</code></td>
 * <td align="left">&nbsp;</th>
 * <td align="left"><code>, - * /</code></td>
 * </tr>
 * </table>
 * <P>
 * The '*' character is used to specify all values. For example, &quot;*&quot;
 * in the minute field means &quot;every minute&quot;.
 * <P>
 * The '?' character is allowed for the day-of-month and day-of-week fields. It
 * is used to specify 'no specific value'. This is useful when you need to
 * specify something in one of the two fields, but not the other.
 * <P>
 * The '-' character is used to specify ranges For example &quot;10-12&quot; in
 * the hour field means &quot;the hours 10, 11 and 12&quot;.
 * <P>
 * The ',' character is used to specify additional values. For example
 * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
 * Wednesday, and Friday&quot;.
 * <P>
 * The '/' character is used to specify increments. For example &quot;0/15&quot;
 * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And
 * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
 * 50&quot;.  Specifying '*' before the  '/' is equivalent to specifying 0 is
 * the value to start with. Essentially, for each field in the expression, there
 * is a set of numbers that can be turned on or off. For seconds and minutes,
 * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
 * 31, and for months 0 to 11 (JAN to DEC). The &quot;/&quot; character simply helps you turn
 * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
 * month field only turns on month &quot;7&quot;, it does NOT mean every 6th
 * month, please note that subtlety.
 * <P>
 * The 'L' character is allowed for the day-of-month and day-of-week fields.
 * This character is short-hand for &quot;last&quot;, but it has different
 * meaning in each of the two fields. For example, the value &quot;L&quot; in
 * the day-of-month field means &quot;the last day of the month&quot; - day 31
 * for January, day 28 for February on non-leap years. If used in the
 * day-of-week field by itself, it simply means &quot;7&quot; or
 * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
 * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
 * means &quot;the last friday of the month&quot;. You can also specify an offset
 * from the last day of the month, such as "L-3" which would mean the third-to-last
 * day of the calendar month. <i>When using the 'L' option, it is important not to
 * specify lists, or ranges of values, as you'll get confusing/unexpected results.</i>
 * <P>
 * The 'W' character is allowed for the day-of-month field.  This character
 * is used to specify the weekday (Monday-Friday) nearest the given day.  As an
 * example, if you were to specify &quot;15W&quot; as the value for the
 * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
 * the month&quot;. So if the 15th is a Saturday, the trigger will fire on
 * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
 * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
 * However if you specify &quot;1W&quot; as the value for day-of-month, and the
 * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
 * 'jump' over the boundary of a month's days.  The 'W' character can only be
 * specified when the day-of-month is a single day, not a range or list of days.
 * <P>
 * The 'L' and 'W' characters can also be combined for the day-of-month
 * expression to yield 'LW', which translates to &quot;last weekday of the
 * month&quot;.
 * <P>
 * The '#' character is allowed for the day-of-week field. This character is
 * used to specify &quot;the nth&quot; XXX day of the month. For example, the
 * value of &quot;6#3&quot; in the day-of-week field means the third Friday of
 * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month).
 * Other examples: &quot;2#1&quot; = the first Monday of the month and
 * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
 * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
 * no firing will occur that month.  If the '#' character is used, there can
 * only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is
 * not valid, since there are two expressions).
 * <P>
 * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
 * This character is short-hand for "calendar". This means values are
 * calculated against the associated calendar, if any. If no calendar is
 * associated, then it is equivalent to having an all-inclusive calendar. A
 * value of "5C" in the day-of-month field means "the first day included by the
 * calendar on or after the 5th". A value of "1C" in the day-of-week field
 * means "the first day included by the calendar on or after Sunday".-->
 * <P>
 * The legal characters and the names of months and days of the week are not
 * case sensitive.
 *
 * <p>
 * <b>NOTES:</b>
 * <ul>
 * <li>Support for specifying both a day-of-week and a day-of-month value is
 * not complete (you'll need to use the '?' character in one of these fields).
 * </li>
 * <li>Overflowing ranges is supported - that is, having a larger number on
 * the left hand side than the right. You might do 22-2 to catch 10 o'clock
 * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
 * very important to note that overuse of overflowing ranges creates ranges
 * that don't make sense and no effort has been made to determine which
 * interpretation CronExpression chooses. An example would be
 * "0 0 14-6 ? * FRI-MON". </li>
 * </ul>
 * </p>
 *
 *
 * @author Sharada Jambula, James House
 * @author Contributions from Mads Henderson
 * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
 *
 * Borrowed from quartz v2.3.1
 *
 */
public final class CronExpression implements Serializable, Cloneable {
    private static final long serialVersionUID = 12423409423L;
    protected static final int SECOND = 0;
    protected static final int MINUTE = 1;
    protected static final int HOUR = 2;
    protected static final int DAY_OF_MONTH = 3;
    protected static final int MONTH = 4;
    protected static final int DAY_OF_WEEK = 5;
    protected static final int YEAR = 6;
    protected static final int ALL_SPEC_INT = 99; // '*'
    protected static final int NO_SPEC_INT = 98; // '?'
    protected static final Integer ALL_SPEC = ALL_SPEC_INT;
    protected static final Integer NO_SPEC = NO_SPEC_INT;
    protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);
    protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);
    static {
        monthMap.put("JAN", 0);
        monthMap.put("FEB", 1);
        monthMap.put("MAR", 2);
        monthMap.put("APR", 3);
        monthMap.put("MAY", 4);
        monthMap.put("JUN", 5);
        monthMap.put("JUL", 6);
        monthMap.put("AUG", 7);
        monthMap.put("SEP", 8);
        monthMap.put("OCT", 9);
        monthMap.put("NOV", 10);
        monthMap.put("DEC", 11);
        dayMap.put("SUN", 1);
        dayMap.put("MON", 2);
        dayMap.put("TUE", 3);
        dayMap.put("WED", 4);
        dayMap.put("THU", 5);
        dayMap.put("FRI", 6);
        dayMap.put("SAT", 7);
    }
    private final String cronExpression;
    private TimeZone timeZone = null;
    protected transient TreeSet<Integer> seconds;
    protected transient TreeSet<Integer> minutes;
    protected transient TreeSet<Integer> hours;
    protected transient TreeSet<Integer> daysOfMonth;
    protected transient TreeSet<Integer> months;
    protected transient TreeSet<Integer> daysOfWeek;
    protected transient TreeSet<Integer> years;
    protected transient boolean lastdayOfWeek = false;
    protected transient int nthdayOfWeek = 0;
    protected transient boolean lastdayOfMonth = false;
    protected transient boolean nearestWeekday = false;
    protected transient int lastdayOffset = 0;
    protected transient boolean expressionParsed = false;
    public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
    /**
     * Constructs a new <CODE>CronExpression</CODE> based on the specified
     * parameter.
     *
     * @param cronExpression String representation of the cron expression the
     *                       new object should represent
     * @throws java.text.ParseException
     *         if the string expression cannot be parsed into a valid
     *         <CODE>CronExpression</CODE>
     */
    public CronExpression(String cronExpression) throws ParseException {
        if (cronExpression == null) {
            throw new IllegalArgumentException("cronExpression cannot be null");
        }
        this.cronExpression = cronExpression.toUpperCase(Locale.US);
        buildExpression(this.cronExpression);
    }
    /**
     * Constructs a new {@code CronExpression} as a copy of an existing
     * instance.
     *
     * @param expression
     *            The existing cron expression to be copied
     */
    public CronExpression(CronExpression expression) {
        /*
         * We don't call the other constructor here since we need to swallow the
         * ParseException. We also elide some of the sanity checking as it is
         * not logically trippable.
         */
        this.cronExpression = expression.getCronExpression();
        try {
            buildExpression(cronExpression);
        } catch (ParseException ex) {
            throw new AssertionError();
        }
        if (expression.getTimeZone() != null) {
            setTimeZone((TimeZone) expression.getTimeZone().clone());
        }
    }
    /**
     * Indicates whether the given date satisfies the cron expression. Note that
     * milliseconds are ignored, so two Dates falling on different milliseconds
     * of the same second will always have the same result here.
     *
     * @param date the date to evaluate
     * @return a boolean indicating whether the given date satisfies the cron
     *         expression
     */
    public boolean isSatisfiedBy(Date date) {
        Calendar testDateCal = Calendar.getInstance(getTimeZone());
        testDateCal.setTime(date);
        testDateCal.set(Calendar.MILLISECOND, 0);
        Date originalDate = testDateCal.getTime();
        testDateCal.add(Calendar.SECOND, -1);
        Date timeAfter = getTimeAfter(testDateCal.getTime());
        return ((timeAfter != null) && (timeAfter.equals(originalDate)));
    }
    /**
     * Returns the next date/time <I>after</I> the given date/time which
     * satisfies the cron expression.
     *
     * @param date the date/time at which to begin the search for the next valid
     *             date/time
     * @return the next valid date/time
     */
    public Date getNextValidTimeAfter(Date date) {
        return getTimeAfter(date);
    }
    /**
     * Returns the next date/time <I>after</I> the given date/time which does
     * <I>not</I> satisfy the expression
     *
     * @param date the date/time at which to begin the search for the next
     *             invalid date/time
     * @return the next valid date/time
     */
    public Date getNextInvalidTimeAfter(Date date) {
        long difference = 1000;
        //move back to the nearest second so differences will be accurate
        Calendar adjustCal = Calendar.getInstance(getTimeZone());
        adjustCal.setTime(date);
        adjustCal.set(Calendar.MILLISECOND, 0);
        Date lastDate = adjustCal.getTime();
        Date newDate;
        //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
        //keep getting the next included time until it's farther than one second
        // apart. At that point, lastDate is the last valid fire time. We return
        // the second immediately following it.
        while (difference == 1000) {
            newDate = getTimeAfter(lastDate);
            if(newDate == null)
                break;
            difference = newDate.getTime() - lastDate.getTime();
            if (difference == 1000) {
                lastDate = newDate;
            }
        }
        return new Date(lastDate.getTime() + 1000);
    }
    /**
     * Returns the time zone for which this <code>CronExpression</code>
     * will be resolved.
     */
    public TimeZone getTimeZone() {
        if (timeZone == null) {
            timeZone = TimeZone.getDefault();
        }
        return timeZone;
    }
    /**
     * Sets the time zone for which  this <code>CronExpression</code>
     * will be resolved.
     */
    public void setTimeZone(TimeZone timeZone) {
        this.timeZone = timeZone;
    }
    /**
     * Returns the string representation of the <CODE>CronExpression</CODE>
     *
     * @return a string representation of the <CODE>CronExpression</CODE>
     */
    @Override
    public String toString() {
        return cronExpression;
    }
    /**
     * Indicates whether the specified cron expression can be parsed into a
     * valid cron expression
     *
     * @param cronExpression the expression to evaluate
     * @return a boolean indicating whether the given expression is a valid cron
     *         expression
     */
    public static boolean isValidExpression(String cronExpression) {
        try {
            new CronExpression(cronExpression);
        } catch (ParseException pe) {
            return false;
        }
        return true;
    }
    public static void validateExpression(String cronExpression) throws ParseException {
        new CronExpression(cronExpression);
    }
    ////////////////////////////////////////////////////////////////////////////
    //
    // Expression Parsing Functions
    //
    ////////////////////////////////////////////////////////////////////////////
    protected void buildExpression(String expression) throws ParseException {
        expressionParsed = true;
        try {
            if (seconds == null) {
                seconds = new TreeSet<Integer>();
            }
            if (minutes == null) {
                minutes = new TreeSet<Integer>();
            }
            if (hours == null) {
                hours = new TreeSet<Integer>();
            }
            if (daysOfMonth == null) {
                daysOfMonth = new TreeSet<Integer>();
            }
            if (months == null) {
                months = new TreeSet<Integer>();
            }
            if (daysOfWeek == null) {
                daysOfWeek = new TreeSet<Integer>();
            }
            if (years == null) {
                years = new TreeSet<Integer>();
            }
            int exprOn = SECOND;
            StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
                    false);
            while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
                String expr = exprsTok.nextToken().trim();
                // throw an exception if L is used with other days of the month
                if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
                    throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
                }
                // throw an exception if L is used with other days of the week
                if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1  && expr.contains(",")) {
                    throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
                }
                if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
                    throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
                }
                StringTokenizer vTok = new StringTokenizer(expr, ",");
                while (vTok.hasMoreTokens()) {
                    String v = vTok.nextToken();
                    storeExpressionVals(0, v, exprOn);
                }
                exprOn++;
            }
            if (exprOn <= DAY_OF_WEEK) {
                throw new ParseException("Unexpected end of expression.",
                            expression.length());
            }
            if (exprOn <= YEAR) {
                storeExpressionVals(0, "*", YEAR);
            }
            TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
            TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
            // Copying the logic from the UnsupportedOperationException below
            boolean dayOfMSpec = !dom.contains(NO_SPEC);
            boolean dayOfWSpec = !dow.contains(NO_SPEC);
            if (!dayOfMSpec || dayOfWSpec) {
                if (!dayOfWSpec || dayOfMSpec) {
                    throw new ParseException(
                            "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
                }
            }
        } catch (ParseException pe) {
            throw pe;
        } catch (Exception e) {
            throw new ParseException("Illegal cron expression format ("
                    + e.toString() + ")", 0);
        }
    }
    protected int storeExpressionVals(int pos, String s, int type)
        throws ParseException {
        int incr = 0;
        int i = skipWhiteSpace(pos, s);
        if (i >= s.length()) {
            return i;
        }
        char c = s.charAt(i);
        if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
            String sub = s.substring(i, i + 3);
            int sval = -1;
            int eval = -1;
            if (type == MONTH) {
                sval = getMonthNumber(sub) + 1;
                if (sval <= 0) {
                    throw new ParseException("Invalid Month value: '" + sub + "'", i);
                }
                if (s.length() > i + 3) {
                    c = s.charAt(i + 3);
                    if (c == '-') {
                        i += 4;
                        sub = s.substring(i, i + 3);
                        eval = getMonthNumber(sub) + 1;
                        if (eval <= 0) {
                            throw new ParseException("Invalid Month value: '" + sub + "'", i);
                        }
                    }
                }
            } else if (type == DAY_OF_WEEK) {
                sval = getDayOfWeekNumber(sub);
                if (sval < 0) {
                    throw new ParseException("Invalid Day-of-Week value: '"
                                + sub + "'", i);
                }
                if (s.length() > i + 3) {
                    c = s.charAt(i + 3);
                    if (c == '-') {
                        i += 4;
                        sub = s.substring(i, i + 3);
                        eval = getDayOfWeekNumber(sub);
                        if (eval < 0) {
                            throw new ParseException(
                                    "Invalid Day-of-Week value: '" + sub
                                        + "'", i);
                        }
                    } else if (c == '#') {
                        try {
                            i += 4;
                            nthdayOfWeek = Integer.parseInt(s.substring(i));
                            if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
                                throw new Exception();
                            }
                        } catch (Exception e) {
                            throw new ParseException(
                                    "A numeric value between 1 and 5 must follow the '#' option",
                                    i);
                        }
                    } else if (c == 'L') {
                        lastdayOfWeek = true;
                        i++;
                    }
                }
            } else {
                throw new ParseException(
                        "Illegal characters for this position: '" + sub + "'",
                        i);
            }
            if (eval != -1) {
                incr = 1;
            }
            addToSet(sval, eval, incr, type);
            return (i + 3);
        }
        if (c == '?') {
            i++;
            if ((i + 1) < s.length()
                    && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
                throw new ParseException("Illegal character after '?': "
                            + s.charAt(i), i);
            }
            if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
                throw new ParseException(
                            "'?' can only be specified for Day-of-Month or Day-of-Week.",
                            i);
            }
            if (type == DAY_OF_WEEK && !lastdayOfMonth) {
                int val = daysOfMonth.last();
                if (val == NO_SPEC_INT) {
                    throw new ParseException(
                                "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
                                i);
                }
            }
            addToSet(NO_SPEC_INT, -1, 0, type);
            return i;
        }
        if (c == '*' || c == '/') {
            if (c == '*' && (i + 1) >= s.length()) {
                addToSet(ALL_SPEC_INT, -1, incr, type);
                return i + 1;
            } else if (c == '/'
                    && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
                            .charAt(i + 1) == '\t')) {
                throw new ParseException("'/' must be followed by an integer.", i);
            } else if (c == '*') {
                i++;
            }
            c = s.charAt(i);
            if (c == '/') { // is an increment specified?
                i++;
                if (i >= s.length()) {
                    throw new ParseException("Unexpected end of string.", i);
                }
                incr = getNumericValue(s, i);
                i++;
                if (incr > 10) {
                    i++;
                }
                checkIncrementRange(incr, type, i);
            } else {
                incr = 1;
            }
            addToSet(ALL_SPEC_INT, -1, incr, type);
            return i;
        } else if (c == 'L') {
            i++;
            if (type == DAY_OF_MONTH) {
                lastdayOfMonth = true;
            }
            if (type == DAY_OF_WEEK) {
                addToSet(7, 7, 0, type);
            }
            if(type == DAY_OF_MONTH && s.length() > i) {
                c = s.charAt(i);
                if(c == '-') {
                    ValueSet vs = getValue(0, s, i+1);
                    lastdayOffset = vs.value;
                    if(lastdayOffset > 30)
                        throw new ParseException("Offset from last day must be <= 30", i+1);
                    i = vs.pos;
                }
                if(s.length() > i) {
                    c = s.charAt(i);
                    if(c == 'W') {
                        nearestWeekday = true;
                        i++;
                    }
                }
            }
            return i;
        } else if (c >= '0' && c <= '9') {
            int val = Integer.parseInt(String.valueOf(c));
            i++;
            if (i >= s.length()) {
                addToSet(val, -1, -1, type);
            } else {
                c = s.charAt(i);
                if (c >= '0' && c <= '9') {
                    ValueSet vs = getValue(val, s, i);
                    val = vs.value;
                    i = vs.pos;
                }
                i = checkNext(i, s, val, type);
                return i;
            }
        } else {
            throw new ParseException("Unexpected character: " + c, i);
        }
        return i;
    }
    private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
        if (incr > 59 && (type == SECOND || type == MINUTE)) {
            throw new ParseException("Increment > 60 : " + incr, idxPos);
        } else if (incr > 23 && (type == HOUR)) {
            throw new ParseException("Increment > 24 : " + incr, idxPos);
        } else if (incr > 31 && (type == DAY_OF_MONTH)) {
            throw new ParseException("Increment > 31 : " + incr, idxPos);
        } else if (incr > 7 && (type == DAY_OF_WEEK)) {
            throw new ParseException("Increment > 7 : " + incr, idxPos);
        } else if (incr > 12 && (type == MONTH)) {
            throw new ParseException("Increment > 12 : " + incr, idxPos);
        }
    }
    protected int checkNext(int pos, String s, int val, int type)
        throws ParseException {
        int end = -1;
        int i = pos;
        if (i >= s.length()) {
            addToSet(val, end, -1, type);
            return i;
        }
        char c = s.charAt(pos);
        if (c == 'L') {
            if (type == DAY_OF_WEEK) {
                if(val < 1 || val > 7)
                    throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
                lastdayOfWeek = true;
            } else {
                throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
            }
            TreeSet<Integer> set = getSet(type);
            set.add(val);
            i++;
            return i;
        }
        if (c == 'W') {
            if (type == DAY_OF_MONTH) {
                nearestWeekday = true;
            } else {
                throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
            }
            if(val > 31)
                throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
            TreeSet<Integer> set = getSet(type);
            set.add(val);
            i++;
            return i;
        }
        if (c == '#') {
            if (type != DAY_OF_WEEK) {
                throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
            }
            i++;
            try {
                nthdayOfWeek = Integer.parseInt(s.substring(i));
                if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
                    throw new Exception();
                }
            } catch (Exception e) {
                throw new ParseException(
                        "A numeric value between 1 and 5 must follow the '#' option",
                        i);
            }
            TreeSet<Integer> set = getSet(type);
            set.add(val);
            i++;
            return i;
        }
        if (c == '-') {
            i++;
            c = s.charAt(i);
            int v = Integer.parseInt(String.valueOf(c));
            end = v;
            i++;
            if (i >= s.length()) {
                addToSet(val, end, 1, type);
                return i;
            }
            c = s.charAt(i);
            if (c >= '0' && c <= '9') {
                ValueSet vs = getValue(v, s, i);
                end = vs.value;
                i = vs.pos;
            }
            if (i < s.length() && ((c = s.charAt(i)) == '/')) {
                i++;
                c = s.charAt(i);
                int v2 = Integer.parseInt(String.valueOf(c));
                i++;
                if (i >= s.length()) {
                    addToSet(val, end, v2, type);
                    return i;
                }
                c = s.charAt(i);
                if (c >= '0' && c <= '9') {
                    ValueSet vs = getValue(v2, s, i);
                    int v3 = vs.value;
                    addToSet(val, end, v3, type);
                    i = vs.pos;
                    return i;
                } else {
                    addToSet(val, end, v2, type);
                    return i;
                }
            } else {
                addToSet(val, end, 1, type);
                return i;
            }
        }
        if (c == '/') {
            if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
                throw new ParseException("'/' must be followed by an integer.", i);
            }
            i++;
            c = s.charAt(i);
            int v2 = Integer.parseInt(String.valueOf(c));
            i++;
            if (i >= s.length()) {
                checkIncrementRange(v2, type, i);
                addToSet(val, end, v2, type);
                return i;
            }
            c = s.charAt(i);
            if (c >= '0' && c <= '9') {
                ValueSet vs = getValue(v2, s, i);
                int v3 = vs.value;
                checkIncrementRange(v3, type, i);
                addToSet(val, end, v3, type);
                i = vs.pos;
                return i;
            } else {
                throw new ParseException("Unexpected character '" + c + "' after '/'", i);
            }
        }
        addToSet(val, end, 0, type);
        i++;
        return i;
    }
    public String getCronExpression() {
        return cronExpression;
    }
    public String getExpressionSummary() {
        StringBuilder buf = new StringBuilder();
        buf.append("seconds: ");
        buf.append(getExpressionSetSummary(seconds));
        buf.append("\n");
        buf.append("minutes: ");
        buf.append(getExpressionSetSummary(minutes));
        buf.append("\n");
        buf.append("hours: ");
        buf.append(getExpressionSetSummary(hours));
        buf.append("\n");
        buf.append("daysOfMonth: ");
        buf.append(getExpressionSetSummary(daysOfMonth));
        buf.append("\n");
        buf.append("months: ");
        buf.append(getExpressionSetSummary(months));
        buf.append("\n");
        buf.append("daysOfWeek: ");
        buf.append(getExpressionSetSummary(daysOfWeek));
        buf.append("\n");
        buf.append("lastdayOfWeek: ");
        buf.append(lastdayOfWeek);
        buf.append("\n");
        buf.append("nearestWeekday: ");
        buf.append(nearestWeekday);
        buf.append("\n");
        buf.append("NthDayOfWeek: ");
        buf.append(nthdayOfWeek);
        buf.append("\n");
        buf.append("lastdayOfMonth: ");
        buf.append(lastdayOfMonth);
        buf.append("\n");
        buf.append("years: ");
        buf.append(getExpressionSetSummary(years));
        buf.append("\n");
        return buf.toString();
    }
    protected String getExpressionSetSummary(java.util.Set<Integer> set) {
        if (set.contains(NO_SPEC)) {
            return "?";
        }
        if (set.contains(ALL_SPEC)) {
            return "*";
        }
        StringBuilder buf = new StringBuilder();
        Iterator<Integer> itr = set.iterator();
        boolean first = true;
        while (itr.hasNext()) {
            Integer iVal = itr.next();
            String val = iVal.toString();
            if (!first) {
                buf.append(",");
            }
            buf.append(val);
            first = false;
        }
        return buf.toString();
    }
    protected String getExpressionSetSummary(java.util.ArrayList<Integer> list) {
        if (list.contains(NO_SPEC)) {
            return "?";
        }
        if (list.contains(ALL_SPEC)) {
            return "*";
        }
        StringBuilder buf = new StringBuilder();
        Iterator<Integer> itr = list.iterator();
        boolean first = true;
        while (itr.hasNext()) {
            Integer iVal = itr.next();
            String val = iVal.toString();
            if (!first) {
                buf.append(",");
            }
            buf.append(val);
            first = false;
        }
        return buf.toString();
    }
    protected int skipWhiteSpace(int i, String s) {
        for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
        }
        return i;
    }
    protected int findNextWhiteSpace(int i, String s) {
        for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
        }
        return i;
    }
    protected void addToSet(int val, int end, int incr, int type)
        throws ParseException {
        TreeSet<Integer> set = getSet(type);
        if (type == SECOND || type == MINUTE) {
            if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
                throw new ParseException(
                        "Minute and Second values must be between 0 and 59",
                        -1);
            }
        } else if (type == HOUR) {
            if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
                throw new ParseException(
                        "Hour values must be between 0 and 23", -1);
            }
        } else if (type == DAY_OF_MONTH) {
            if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
                    && (val != NO_SPEC_INT)) {
                throw new ParseException(
                        "Day of month values must be between 1 and 31", -1);
            }
        } else if (type == MONTH) {
            if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
                throw new ParseException(
                        "Month values must be between 1 and 12", -1);
            }
        } else if (type == DAY_OF_WEEK) {
            if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
                    && (val != NO_SPEC_INT)) {
                throw new ParseException(
                        "Day-of-Week values must be between 1 and 7", -1);
            }
        }
        if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
            if (val != -1) {
                set.add(val);
            } else {
                set.add(NO_SPEC);
            }
            return;
        }
        int startAt = val;
        int stopAt = end;
        if (val == ALL_SPEC_INT && incr <= 0) {
            incr = 1;
            set.add(ALL_SPEC); // put in a marker, but also fill values
        }
        if (type == SECOND || type == MINUTE) {
            if (stopAt == -1) {
                stopAt = 59;
            }
            if (startAt == -1 || startAt == ALL_SPEC_INT) {
                startAt = 0;
            }
        } else if (type == HOUR) {
            if (stopAt == -1) {
                stopAt = 23;
            }
            if (startAt == -1 || startAt == ALL_SPEC_INT) {
                startAt = 0;
            }
        } else if (type == DAY_OF_MONTH) {
            if (stopAt == -1) {
                stopAt = 31;
            }
            if (startAt == -1 || startAt == ALL_SPEC_INT) {
                startAt = 1;
            }
        } else if (type == MONTH) {
            if (stopAt == -1) {
                stopAt = 12;
            }
            if (startAt == -1 || startAt == ALL_SPEC_INT) {
                startAt = 1;
            }
        } else if (type == DAY_OF_WEEK) {
            if (stopAt == -1) {
                stopAt = 7;
            }
            if (startAt == -1 || startAt == ALL_SPEC_INT) {
                startAt = 1;
            }
        } else if (type == YEAR) {
            if (stopAt == -1) {
                stopAt = MAX_YEAR;
            }
            if (startAt == -1 || startAt == ALL_SPEC_INT) {
                startAt = 1970;
            }
        }
        // if the end of the range is before the start, then we need to overflow into
        // the next day, month etc. This is done by adding the maximum amount for that
        // type, and using modulus max to determine the value being added.
        int max = -1;
        if (stopAt < startAt) {
            switch (type) {
              case       SECOND : max = 60; break;
              case       MINUTE : max = 60; break;
              case         HOUR : max = 24; break;
              case        MONTH : max = 12; break;
              case  DAY_OF_WEEK : max = 7;  break;
              case DAY_OF_MONTH : max = 31; break;
              case         YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
              default           : throw new IllegalArgumentException("Unexpected type encountered");
            }
            stopAt += max;
        }
        for (int i = startAt; i <= stopAt; i += incr) {
            if (max == -1) {
                // ie: there's no max to overflow over
                set.add(i);
            } else {
                // take the modulus to get the real value
                int i2 = i % max;
                // 1-indexed ranges should not include 0, and should include their max
                if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
                    i2 = max;
                }
                set.add(i2);
            }
        }
    }
    TreeSet<Integer> getSet(int type) {
        switch (type) {
            case SECOND:
                return seconds;
            case MINUTE:
                return minutes;
            case HOUR:
                return hours;
            case DAY_OF_MONTH:
                return daysOfMonth;
            case MONTH:
                return months;
            case DAY_OF_WEEK:
                return daysOfWeek;
            case YEAR:
                return years;
            default:
                return null;
        }
    }
    protected ValueSet getValue(int v, String s, int i) {
        char c = s.charAt(i);
        StringBuilder s1 = new StringBuilder(String.valueOf(v));
        while (c >= '0' && c <= '9') {
            s1.append(c);
            i++;
            if (i >= s.length()) {
                break;
            }
            c = s.charAt(i);
        }
        ValueSet val = new ValueSet();
        val.pos = (i < s.length()) ? i : i + 1;
        val.value = Integer.parseInt(s1.toString());
        return val;
    }
    protected int getNumericValue(String s, int i) {
        int endOfVal = findNextWhiteSpace(i, s);
        String val = s.substring(i, endOfVal);
        return Integer.parseInt(val);
    }
    protected int getMonthNumber(String s) {
        Integer integer = monthMap.get(s);
        if (integer == null) {
            return -1;
        }
        return integer;
    }
    protected int getDayOfWeekNumber(String s) {
        Integer integer = dayMap.get(s);
        if (integer == null) {
            return -1;
        }
        return integer;
    }
    ////////////////////////////////////////////////////////////////////////////
    //
    // Computation Functions
    //
    ////////////////////////////////////////////////////////////////////////////
    public Date getTimeAfter(Date afterTime) {
        // Computation is based on Gregorian year only.
        Calendar cl = new java.util.GregorianCalendar(getTimeZone());
        // move ahead one second, since we're computing the time *after* the
        // given time
        afterTime = new Date(afterTime.getTime() + 1000);
        // CronTrigger does not deal with milliseconds
        cl.setTime(afterTime);
        cl.set(Calendar.MILLISECOND, 0);
        boolean gotOne = false;
        // loop until we've computed the next time, or we've past the endTime
        while (!gotOne) {
            //if (endTime != null && cl.getTime().after(endTime)) return null;
            if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
                return null;
            }
            SortedSet<Integer> st = null;
            int t = 0;
            int sec = cl.get(Calendar.SECOND);
            int min = cl.get(Calendar.MINUTE);
            // get second.................................................
            st = seconds.tailSet(sec);
            if (st != null && st.size() != 0) {
                sec = st.first();
            } else {
                sec = seconds.first();
                min++;
                cl.set(Calendar.MINUTE, min);
            }
            cl.set(Calendar.SECOND, sec);
            min = cl.get(Calendar.MINUTE);
            int hr = cl.get(Calendar.HOUR_OF_DAY);
            t = -1;
            // get minute.................................................
            st = minutes.tailSet(min);
            if (st != null && st.size() != 0) {
                t = min;
                min = st.first();
            } else {
                min = minutes.first();
                hr++;
            }
            if (min != t) {
                cl.set(Calendar.SECOND, 0);
                cl.set(Calendar.MINUTE, min);
                setCalendarHour(cl, hr);
                continue;
            }
            cl.set(Calendar.MINUTE, min);
            hr = cl.get(Calendar.HOUR_OF_DAY);
            int day = cl.get(Calendar.DAY_OF_MONTH);
            t = -1;
            // get hour...................................................
            st = hours.tailSet(hr);
            if (st != null && st.size() != 0) {
                t = hr;
                hr = st.first();
            } else {
                hr = hours.first();
                day++;
            }
            if (hr != t) {
                cl.set(Calendar.SECOND, 0);
                cl.set(Calendar.MINUTE, 0);
                cl.set(Calendar.DAY_OF_MONTH, day);
                setCalendarHour(cl, hr);
                continue;
            }
            cl.set(Calendar.HOUR_OF_DAY, hr);
            day = cl.get(Calendar.DAY_OF_MONTH);
            int mon = cl.get(Calendar.MONTH) + 1;
            // '+ 1' because calendar is 0-based for this field, and we are
            // 1-based
            t = -1;
            int tmon = mon;
            // get day...................................................
            boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
            boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
            if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
                st = daysOfMonth.tailSet(day);
                if (lastdayOfMonth) {
                    if(!nearestWeekday) {
                        t = day;
                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                        day -= lastdayOffset;
                        if(t > day) {
                            mon++;
                            if(mon > 12) {
                                mon = 1;
                                tmon = 3333; // ensure test of mon != tmon further below fails
                                cl.add(Calendar.YEAR, 1);
                            }
                            day = 1;
                        }
                    } else {
                        t = day;
                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                        day -= lastdayOffset;
                        java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
                        tcal.set(Calendar.SECOND, 0);
                        tcal.set(Calendar.MINUTE, 0);
                        tcal.set(Calendar.HOUR_OF_DAY, 0);
                        tcal.set(Calendar.DAY_OF_MONTH, day);
                        tcal.set(Calendar.MONTH, mon - 1);
                        tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
                        int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                        int dow = tcal.get(Calendar.DAY_OF_WEEK);
                        if(dow == Calendar.SATURDAY && day == 1) {
                            day += 2;
                        } else if(dow == Calendar.SATURDAY) {
                            day -= 1;
                        } else if(dow == Calendar.SUNDAY && day == ldom) {
                            day -= 2;
                        } else if(dow == Calendar.SUNDAY) {
                            day += 1;
                        }
                        tcal.set(Calendar.SECOND, sec);
                        tcal.set(Calendar.MINUTE, min);
                        tcal.set(Calendar.HOUR_OF_DAY, hr);
                        tcal.set(Calendar.DAY_OF_MONTH, day);
                        tcal.set(Calendar.MONTH, mon - 1);
                        Date nTime = tcal.getTime();
                        if(nTime.before(afterTime)) {
                            day = 1;
                            mon++;
                        }
                    }
                } else if(nearestWeekday) {
                    t = day;
                    day = daysOfMonth.first();
                    java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
                    tcal.set(Calendar.SECOND, 0);
                    tcal.set(Calendar.MINUTE, 0);
                    tcal.set(Calendar.HOUR_OF_DAY, 0);
                    tcal.set(Calendar.DAY_OF_MONTH, day);
                    tcal.set(Calendar.MONTH, mon - 1);
                    tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
                    int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                    int dow = tcal.get(Calendar.DAY_OF_WEEK);
                    if(dow == Calendar.SATURDAY && day == 1) {
                        day += 2;
                    } else if(dow == Calendar.SATURDAY) {
                        day -= 1;
                    } else if(dow == Calendar.SUNDAY && day == ldom) {
                        day -= 2;
                    } else if(dow == Calendar.SUNDAY) {
                        day += 1;
                    }
                    tcal.set(Calendar.SECOND, sec);
                    tcal.set(Calendar.MINUTE, min);
                    tcal.set(Calendar.HOUR_OF_DAY, hr);
                    tcal.set(Calendar.DAY_OF_MONTH, day);
                    tcal.set(Calendar.MONTH, mon - 1);
                    Date nTime = tcal.getTime();
                    if(nTime.before(afterTime)) {
                        day = daysOfMonth.first();
                        mon++;
                    }
                } else if (st != null && st.size() != 0) {
                    t = day;
                    day = st.first();
                    // make sure we don't over-run a short month, such as february
                    int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                    if (day > lastDay) {
                        day = daysOfMonth.first();
                        mon++;
                    }
                } else {
                    day = daysOfMonth.first();
                    mon++;
                }
                if (day != t || mon != tmon) {
                    cl.set(Calendar.SECOND, 0);
                    cl.set(Calendar.MINUTE, 0);
                    cl.set(Calendar.HOUR_OF_DAY, 0);
                    cl.set(Calendar.DAY_OF_MONTH, day);
                    cl.set(Calendar.MONTH, mon - 1);
                    // '- 1' because calendar is 0-based for this field, and we
                    // are 1-based
                    continue;
                }
            } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
                if (lastdayOfWeek) { // are we looking for the last XXX day of
                    // the month?
                    int dow = daysOfWeek.first(); // desired
                    // d-o-w
                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
                    int daysToAdd = 0;
                    if (cDow < dow) {
                        daysToAdd = dow - cDow;
                    }
                    if (cDow > dow) {
                        daysToAdd = dow + (7 - cDow);
                    }
                    int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                    if (day + daysToAdd > lDay) { // did we already miss the
                        // last one?
                        cl.set(Calendar.SECOND, 0);
                        cl.set(Calendar.MINUTE, 0);
                        cl.set(Calendar.HOUR_OF_DAY, 0);
                        cl.set(Calendar.DAY_OF_MONTH, 1);
                        cl.set(Calendar.MONTH, mon);
                        // no '- 1' here because we are promoting the month
                        continue;
                    }
                    // find date of last occurrence of this day in this month...
                    while ((day + daysToAdd + 7) <= lDay) {
                        daysToAdd += 7;
                    }
                    day += daysToAdd;
                    if (daysToAdd > 0) {
                        cl.set(Calendar.SECOND, 0);
                        cl.set(Calendar.MINUTE, 0);
                        cl.set(Calendar.HOUR_OF_DAY, 0);
                        cl.set(Calendar.DAY_OF_MONTH, day);
                        cl.set(Calendar.MONTH, mon - 1);
                        // '- 1' here because we are not promoting the month
                        continue;
                    }
                } else if (nthdayOfWeek != 0) {
                    // are we looking for the Nth XXX day in the month?
                    int dow = daysOfWeek.first(); // desired
                    // d-o-w
                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
                    int daysToAdd = 0;
                    if (cDow < dow) {
                        daysToAdd = dow - cDow;
                    } else if (cDow > dow) {
                        daysToAdd = dow + (7 - cDow);
                    }
                    boolean dayShifted = false;
                    if (daysToAdd > 0) {
                        dayShifted = true;
                    }
                    day += daysToAdd;
                    int weekOfMonth = day / 7;
                    if (day % 7 > 0) {
                        weekOfMonth++;
                    }
                    daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
                    day += daysToAdd;
                    if (daysToAdd < 0
                            || day > getLastDayOfMonth(mon, cl
                                    .get(Calendar.YEAR))) {
                        cl.set(Calendar.SECOND, 0);
                        cl.set(Calendar.MINUTE, 0);
                        cl.set(Calendar.HOUR_OF_DAY, 0);
                        cl.set(Calendar.DAY_OF_MONTH, 1);
                        cl.set(Calendar.MONTH, mon);
                        // no '- 1' here because we are promoting the month
                        continue;
                    } else if (daysToAdd > 0 || dayShifted) {
                        cl.set(Calendar.SECOND, 0);
                        cl.set(Calendar.MINUTE, 0);
                        cl.set(Calendar.HOUR_OF_DAY, 0);
                        cl.set(Calendar.DAY_OF_MONTH, day);
                        cl.set(Calendar.MONTH, mon - 1);
                        // '- 1' here because we are NOT promoting the month
                        continue;
                    }
                } else {
                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
                    int dow = daysOfWeek.first(); // desired
                    // d-o-w
                    st = daysOfWeek.tailSet(cDow);
                    if (st != null && st.size() > 0) {
                        dow = st.first();
                    }
                    int daysToAdd = 0;
                    if (cDow < dow) {
                        daysToAdd = dow - cDow;
                    }
                    if (cDow > dow) {
                        daysToAdd = dow + (7 - cDow);
                    }
                    int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                    if (day + daysToAdd > lDay) { // will we pass the end of
                        // the month?
                        cl.set(Calendar.SECOND, 0);
                        cl.set(Calendar.MINUTE, 0);
                        cl.set(Calendar.HOUR_OF_DAY, 0);
                        cl.set(Calendar.DAY_OF_MONTH, 1);
                        cl.set(Calendar.MONTH, mon);
                        // no '- 1' here because we are promoting the month
                        continue;
                    } else if (daysToAdd > 0) { // are we swithing days?
                        cl.set(Calendar.SECOND, 0);
                        cl.set(Calendar.MINUTE, 0);
                        cl.set(Calendar.HOUR_OF_DAY, 0);
                        cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
                        cl.set(Calendar.MONTH, mon - 1);
                        // '- 1' because calendar is 0-based for this field,
                        // and we are 1-based
                        continue;
                    }
                }
            } else { // dayOfWSpec && !dayOfMSpec
                throw new UnsupportedOperationException(
                        "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
            }
            cl.set(Calendar.DAY_OF_MONTH, day);
            mon = cl.get(Calendar.MONTH) + 1;
            // '+ 1' because calendar is 0-based for this field, and we are
            // 1-based
            int year = cl.get(Calendar.YEAR);
            t = -1;
            // test for expressions that never generate a valid fire date,
            // but keep looping...
            if (year > MAX_YEAR) {
                return null;
            }
            // get month...................................................
            st = months.tailSet(mon);
            if (st != null && st.size() != 0) {
                t = mon;
                mon = st.first();
            } else {
                mon = months.first();
                year++;
            }
            if (mon != t) {
                cl.set(Calendar.SECOND, 0);
                cl.set(Calendar.MINUTE, 0);
                cl.set(Calendar.HOUR_OF_DAY, 0);
                cl.set(Calendar.DAY_OF_MONTH, 1);
                cl.set(Calendar.MONTH, mon - 1);
                // '- 1' because calendar is 0-based for this field, and we are
                // 1-based
                cl.set(Calendar.YEAR, year);
                continue;
            }
            cl.set(Calendar.MONTH, mon - 1);
            // '- 1' because calendar is 0-based for this field, and we are
            // 1-based
            year = cl.get(Calendar.YEAR);
            t = -1;
            // get year...................................................
            st = years.tailSet(year);
            if (st != null && st.size() != 0) {
                t = year;
                year = st.first();
            } else {
                return null; // ran out of years...
            }
            if (year != t) {
                cl.set(Calendar.SECOND, 0);
                cl.set(Calendar.MINUTE, 0);
                cl.set(Calendar.HOUR_OF_DAY, 0);
                cl.set(Calendar.DAY_OF_MONTH, 1);
                cl.set(Calendar.MONTH, 0);
                // '- 1' because calendar is 0-based for this field, and we are
                // 1-based
                cl.set(Calendar.YEAR, year);
                continue;
            }
            cl.set(Calendar.YEAR, year);
            gotOne = true;
        } // while( !done )
        return cl.getTime();
    }
    /**
     * Advance the calendar to the particular hour paying particular attention
     * to daylight saving problems.
     *
     * @param cal the calendar to operate on
     * @param hour the hour to set
     */
    protected void setCalendarHour(Calendar cal, int hour) {
        cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
        if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
            cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
        }
    }
    /**
     * NOT YET IMPLEMENTED: Returns the time before the given time
     * that the <code>CronExpression</code> matches.
     */
    public Date getTimeBefore(Date endTime) {
        // FUTURE_TODO: implement QUARTZ-423
        return null;
    }
    /**
     * NOT YET IMPLEMENTED: Returns the final time that the
     * <code>CronExpression</code> will match.
     */
    public Date getFinalFireTime() {
        // FUTURE_TODO: implement QUARTZ-423
        return null;
    }
    protected boolean isLeapYear(int year) {
        return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
    }
    protected int getLastDayOfMonth(int monthNum, int year) {
        switch (monthNum) {
            case 1:
                return 31;
            case 2:
                return (isLeapYear(year)) ? 29 : 28;
            case 3:
                return 31;
            case 4:
                return 30;
            case 5:
                return 31;
            case 6:
                return 30;
            case 7:
                return 31;
            case 8:
                return 31;
            case 9:
                return 30;
            case 10:
                return 31;
            case 11:
                return 30;
            case 12:
                return 31;
            default:
                throw new IllegalArgumentException("Illegal month number: "
                        + monthNum);
        }
    }
    private void readObject(java.io.ObjectInputStream stream)
        throws java.io.IOException, ClassNotFoundException {
        stream.defaultReadObject();
        try {
            buildExpression(cronExpression);
        } catch (Exception ignore) {
        } // never happens
    }
    @Override
    @Deprecated
    public Object clone() {
        return new CronExpression(this);
    }
}
class ValueSet {
    public int value;
    public int pos;
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/exception/XxlJobException.java
New file
@@ -0,0 +1,14 @@
package cn.gistack.job.admin.core.exception;
/**
 * @author xuxueli 2019-05-04 23:19:29
 */
public class XxlJobException extends RuntimeException {
    public XxlJobException() {
    }
    public XxlJobException(String message) {
        super(message);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobGroup.java
New file
@@ -0,0 +1,76 @@
package cn.gistack.job.admin.core.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * Created by xuxueli on 16/9/30.
 */
public class XxlJobGroup {
    private int id;
    private String appName;
    private String title;
    private int order;
    private int addressType;        // 执行器地址类型:0=自动注册、1=手动录入
    private String addressList;     // 执行器地址列表,多地址逗号分隔(手动录入)
    // registry list
    private List<String> registryList;  // 执行器地址列表(系统注册)
    public List<String> getRegistryList() {
        if (addressList!=null && addressList.trim().length()>0) {
            registryList = new ArrayList<String>(Arrays.asList(addressList.split(",")));
        }
        return registryList;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getAppName() {
        return appName;
    }
    public void setAppName(String appName) {
        this.appName = appName;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public int getOrder() {
        return order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    public int getAddressType() {
        return addressType;
    }
    public void setAddressType(int addressType) {
        this.addressType = addressType;
    }
    public String getAddressList() {
        return addressList;
    }
    public void setAddressList(String addressList) {
        this.addressList = addressList;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobInfo.java
New file
@@ -0,0 +1,218 @@
package cn.gistack.job.admin.core.model;
import java.util.Date;
/**
 * xxl-job info
 *
 * @author xuxueli  2016-1-12 18:25:49
 */
public class XxlJobInfo {
    private int id;                // 主键ID
    private int jobGroup;        // 执行器主键ID
    private String jobCron;        // 任务执行CRON表达式
    private String jobDesc;
    private Date addTime;
    private Date updateTime;
    private String author;        // 负责人
    private String alarmEmail;    // 报警邮件
    private String executorRouteStrategy;    // 执行器路由策略
    private String executorHandler;            // 执行器,任务Handler名称
    private String executorParam;            // 执行器,任务参数
    private String executorBlockStrategy;    // 阻塞处理策略
    private int executorTimeout;             // 任务执行超时时间,单位秒
    private int executorFailRetryCount;        // 失败重试次数
    private String glueType;        // GLUE类型    #com.xxl.job.core.glue.GlueTypeEnum
    private String glueSource;        // GLUE源代码
    private String glueRemark;        // GLUE备注
    private Date glueUpdatetime;    // GLUE更新时间
    private String childJobId;        // 子任务ID,多个逗号分隔
    private int triggerStatus;        // 调度状态:0-停止,1-运行
    private long triggerLastTime;    // 上次调度时间
    private long triggerNextTime;    // 下次调度时间
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getJobGroup() {
        return jobGroup;
    }
    public void setJobGroup(int jobGroup) {
        this.jobGroup = jobGroup;
    }
    public String getJobCron() {
        return jobCron;
    }
    public void setJobCron(String jobCron) {
        this.jobCron = jobCron;
    }
    public String getJobDesc() {
        return jobDesc;
    }
    public void setJobDesc(String jobDesc) {
        this.jobDesc = jobDesc;
    }
    public Date getAddTime() {
        return addTime;
    }
    public void setAddTime(Date addTime) {
        this.addTime = addTime;
    }
    public Date getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public String getAlarmEmail() {
        return alarmEmail;
    }
    public void setAlarmEmail(String alarmEmail) {
        this.alarmEmail = alarmEmail;
    }
    public String getExecutorRouteStrategy() {
        return executorRouteStrategy;
    }
    public void setExecutorRouteStrategy(String executorRouteStrategy) {
        this.executorRouteStrategy = executorRouteStrategy;
    }
    public String getExecutorHandler() {
        return executorHandler;
    }
    public void setExecutorHandler(String executorHandler) {
        this.executorHandler = executorHandler;
    }
    public String getExecutorParam() {
        return executorParam;
    }
    public void setExecutorParam(String executorParam) {
        this.executorParam = executorParam;
    }
    public String getExecutorBlockStrategy() {
        return executorBlockStrategy;
    }
    public void setExecutorBlockStrategy(String executorBlockStrategy) {
        this.executorBlockStrategy = executorBlockStrategy;
    }
    public int getExecutorTimeout() {
        return executorTimeout;
    }
    public void setExecutorTimeout(int executorTimeout) {
        this.executorTimeout = executorTimeout;
    }
    public int getExecutorFailRetryCount() {
        return executorFailRetryCount;
    }
    public void setExecutorFailRetryCount(int executorFailRetryCount) {
        this.executorFailRetryCount = executorFailRetryCount;
    }
    public String getGlueType() {
        return glueType;
    }
    public void setGlueType(String glueType) {
        this.glueType = glueType;
    }
    public String getGlueSource() {
        return glueSource;
    }
    public void setGlueSource(String glueSource) {
        this.glueSource = glueSource;
    }
    public String getGlueRemark() {
        return glueRemark;
    }
    public void setGlueRemark(String glueRemark) {
        this.glueRemark = glueRemark;
    }
    public Date getGlueUpdatetime() {
        return glueUpdatetime;
    }
    public void setGlueUpdatetime(Date glueUpdatetime) {
        this.glueUpdatetime = glueUpdatetime;
    }
    public String getChildJobId() {
        return childJobId;
    }
    public void setChildJobId(String childJobId) {
        this.childJobId = childJobId;
    }
    public int getTriggerStatus() {
        return triggerStatus;
    }
    public void setTriggerStatus(int triggerStatus) {
        this.triggerStatus = triggerStatus;
    }
    public long getTriggerLastTime() {
        return triggerLastTime;
    }
    public void setTriggerLastTime(long triggerLastTime) {
        this.triggerLastTime = triggerLastTime;
    }
    public long getTriggerNextTime() {
        return triggerNextTime;
    }
    public void setTriggerNextTime(long triggerNextTime) {
        this.triggerNextTime = triggerNextTime;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobLog.java
New file
@@ -0,0 +1,157 @@
package cn.gistack.job.admin.core.model;
import java.util.Date;
/**
 * xxl-job log, used to track trigger process
 * @author xuxueli  2015-12-19 23:19:09
 */
public class XxlJobLog {
    private long id;
    // job info
    private int jobGroup;
    private int jobId;
    // execute info
    private String executorAddress;
    private String executorHandler;
    private String executorParam;
    private String executorShardingParam;
    private int executorFailRetryCount;
    // trigger info
    private Date triggerTime;
    private int triggerCode;
    private String triggerMsg;
    // handle info
    private Date handleTime;
    private int handleCode;
    private String handleMsg;
    // alarm info
    private int alarmStatus;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public int getJobGroup() {
        return jobGroup;
    }
    public void setJobGroup(int jobGroup) {
        this.jobGroup = jobGroup;
    }
    public int getJobId() {
        return jobId;
    }
    public void setJobId(int jobId) {
        this.jobId = jobId;
    }
    public String getExecutorAddress() {
        return executorAddress;
    }
    public void setExecutorAddress(String executorAddress) {
        this.executorAddress = executorAddress;
    }
    public String getExecutorHandler() {
        return executorHandler;
    }
    public void setExecutorHandler(String executorHandler) {
        this.executorHandler = executorHandler;
    }
    public String getExecutorParam() {
        return executorParam;
    }
    public void setExecutorParam(String executorParam) {
        this.executorParam = executorParam;
    }
    public String getExecutorShardingParam() {
        return executorShardingParam;
    }
    public void setExecutorShardingParam(String executorShardingParam) {
        this.executorShardingParam = executorShardingParam;
    }
    public int getExecutorFailRetryCount() {
        return executorFailRetryCount;
    }
    public void setExecutorFailRetryCount(int executorFailRetryCount) {
        this.executorFailRetryCount = executorFailRetryCount;
    }
    public Date getTriggerTime() {
        return triggerTime;
    }
    public void setTriggerTime(Date triggerTime) {
        this.triggerTime = triggerTime;
    }
    public int getTriggerCode() {
        return triggerCode;
    }
    public void setTriggerCode(int triggerCode) {
        this.triggerCode = triggerCode;
    }
    public String getTriggerMsg() {
        return triggerMsg;
    }
    public void setTriggerMsg(String triggerMsg) {
        this.triggerMsg = triggerMsg;
    }
    public Date getHandleTime() {
        return handleTime;
    }
    public void setHandleTime(Date handleTime) {
        this.handleTime = handleTime;
    }
    public int getHandleCode() {
        return handleCode;
    }
    public void setHandleCode(int handleCode) {
        this.handleCode = handleCode;
    }
    public String getHandleMsg() {
        return handleMsg;
    }
    public void setHandleMsg(String handleMsg) {
        this.handleMsg = handleMsg;
    }
    public int getAlarmStatus() {
        return alarmStatus;
    }
    public void setAlarmStatus(int alarmStatus) {
        this.alarmStatus = alarmStatus;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobLogGlue.java
New file
@@ -0,0 +1,75 @@
package cn.gistack.job.admin.core.model;
import java.util.Date;
/**
 * xxl-job log for glue, used to track job code process
 * @author xuxueli 2016-5-19 17:57:46
 */
public class XxlJobLogGlue {
    private int id;
    private int jobId;                // 任务主键ID
    private String glueType;        // GLUE类型    #com.xxl.job.core.glue.GlueTypeEnum
    private String glueSource;
    private String glueRemark;
    private Date addTime;
    private Date updateTime;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getJobId() {
        return jobId;
    }
    public void setJobId(int jobId) {
        this.jobId = jobId;
    }
    public String getGlueType() {
        return glueType;
    }
    public void setGlueType(String glueType) {
        this.glueType = glueType;
    }
    public String getGlueSource() {
        return glueSource;
    }
    public void setGlueSource(String glueSource) {
        this.glueSource = glueSource;
    }
    public String getGlueRemark() {
        return glueRemark;
    }
    public void setGlueRemark(String glueRemark) {
        this.glueRemark = glueRemark;
    }
    public Date getAddTime() {
        return addTime;
    }
    public void setAddTime(Date addTime) {
        this.addTime = addTime;
    }
    public Date getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobLogReport.java
New file
@@ -0,0 +1,54 @@
package cn.gistack.job.admin.core.model;
import java.util.Date;
public class XxlJobLogReport {
    private int id;
    private Date triggerDay;
    private int runningCount;
    private int sucCount;
    private int failCount;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public Date getTriggerDay() {
        return triggerDay;
    }
    public void setTriggerDay(Date triggerDay) {
        this.triggerDay = triggerDay;
    }
    public int getRunningCount() {
        return runningCount;
    }
    public void setRunningCount(int runningCount) {
        this.runningCount = runningCount;
    }
    public int getSucCount() {
        return sucCount;
    }
    public void setSucCount(int sucCount) {
        this.sucCount = sucCount;
    }
    public int getFailCount() {
        return failCount;
    }
    public void setFailCount(int failCount) {
        this.failCount = failCount;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobRegistry.java
New file
@@ -0,0 +1,55 @@
package cn.gistack.job.admin.core.model;
import java.util.Date;
/**
 * Created by xuxueli on 16/9/30.
 */
public class XxlJobRegistry {
    private int id;
    private String registryGroup;
    private String registryKey;
    private String registryValue;
    private Date updateTime;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getRegistryGroup() {
        return registryGroup;
    }
    public void setRegistryGroup(String registryGroup) {
        this.registryGroup = registryGroup;
    }
    public String getRegistryKey() {
        return registryKey;
    }
    public void setRegistryKey(String registryKey) {
        this.registryKey = registryKey;
    }
    public String getRegistryValue() {
        return registryValue;
    }
    public void setRegistryValue(String registryValue) {
        this.registryValue = registryValue;
    }
    public Date getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/model/XxlJobUser.java
New file
@@ -0,0 +1,73 @@
package cn.gistack.job.admin.core.model;
import org.springframework.util.StringUtils;
/**
 * @author xuxueli 2019-05-04 16:43:12
 */
public class XxlJobUser {
    private int id;
    private String username;        // 账号
    private String password;        // 密码
    private int role;                // 角色:0-普通用户、1-管理员
    private String permission;    // 权限:执行器ID列表,多个逗号分割
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getRole() {
        return role;
    }
    public void setRole(int role) {
        this.role = role;
    }
    public String getPermission() {
        return permission;
    }
    public void setPermission(String permission) {
        this.permission = permission;
    }
    // plugin
    public boolean validPermission(int jobGroup){
        if (this.role == 1) {
            return true;
        } else {
            if (StringUtils.hasText(this.permission)) {
                for (String permissionItem : this.permission.split(",")) {
                    if (String.valueOf(jobGroup).equals(permissionItem)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/old/RemoteHttpJobBean.java
New file
@@ -0,0 +1,32 @@
//package com.xxl.job.admin.core.jobbean;
//
//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
//import org.quartz.JobExecutionContext;
//import org.quartz.JobExecutionException;
//import org.quartz.JobKey;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.scheduling.quartz.QuartzJobBean;
//
///**
// * http job bean
// * “@DisallowConcurrentExecution” disable concurrent, thread size can not be only one, better given more
// * @author xuxueli 2015-12-17 18:20:34
// */
////@DisallowConcurrentExecution
//public class RemoteHttpJobBean extends QuartzJobBean {
//    private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
//
//    @Override
//    protected void executeInternal(JobExecutionContext context)
//            throws JobExecutionException {
//
//        // load jobId
//        JobKey jobKey = context.getTrigger().getJobKey();
//        Integer jobId = Integer.valueOf(jobKey.getName());
//
//
//    }
//
//}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/old/XxlJobDynamicScheduler.java
New file
@@ -0,0 +1,413 @@
//package com.xxl.job.admin.core.schedule;
//
//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
//import com.xxl.job.admin.core.model.XxlJobInfo;
//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
//import com.xxl.job.admin.core.util.I18nUtil;
//import com.xxl.job.core.biz.AdminBiz;
//import com.xxl.job.core.biz.ExecutorBiz;
//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
//import com.xxl.rpc.remoting.invoker.call.CallType;
//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
//import com.xxl.rpc.remoting.net.NetEnum;
//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
//import com.xxl.rpc.serialize.Serializer;
//import org.quartz.*;
//import org.quartz.Trigger.TriggerState;
//import org.quartz.impl.triggers.CronTriggerImpl;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.util.Assert;
//
//import javax.servlet.ServletException;
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.io.IOException;
//import java.util.Date;
//import java.util.concurrent.ConcurrentHashMap;
//
///**
// * base quartz scheduler util
// * @author xuxueli 2015-12-19 16:13:53
// */
//public final class XxlJobDynamicScheduler {
//    private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
//
//    // ---------------------- param ----------------------
//
//    // scheduler
//    private static Scheduler scheduler;
//    public void setScheduler(Scheduler scheduler) {
//        XxlJobDynamicScheduler_old.scheduler = scheduler;
//    }
//
//
//    // ---------------------- init + destroy ----------------------
//    public void start() throws Exception {
//        // valid
//        Assert.notNull(scheduler, "quartz scheduler is null");
//
//        // init i18n
//        initI18n();
//
//        // admin registry monitor run
//        JobRegistryMonitorHelper.getInstance().start();
//
//        // admin monitor run
//        JobFailMonitorHelper.getInstance().start();
//
//        // admin-server
//        initRpcProvider();
//
//        logger.info(">>>>>>>>> init xxl-job admin success.");
//    }
//
//
//    public void destroy() throws Exception {
//        // admin trigger pool stop
//        JobTriggerPoolHelper.toStop();
//
//        // admin registry stop
//        JobRegistryMonitorHelper.getInstance().toStop();
//
//        // admin monitor stop
//        JobFailMonitorHelper.getInstance().toStop();
//
//        // admin-server
//        stopRpcProvider();
//    }
//
//
//    // ---------------------- I18n ----------------------
//
//    private void initI18n(){
//        for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
//            item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
//        }
//    }
//
//
//    // ---------------------- admin rpc provider (no server version) ----------------------
//    private static ServletServerHandler servletServerHandler;
//    private void initRpcProvider(){
//        // init
//        XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
//        xxlRpcProviderFactory.initConfig(
//                NetEnum.NETTY_HTTP,
//                Serializer.SerializeEnum.HESSIAN.getSerializer(),
//                null,
//                0,
//                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
//                null,
//                null);
//
//        // add services
//        xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
//
//        // servlet handler
//        servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
//    }
//    private void stopRpcProvider() throws Exception {
//        XxlRpcInvokerFactory.getInstance().stop();
//    }
//    public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//        servletServerHandler.handle(null, request, response);
//    }
//
//
//    // ---------------------- executor-client ----------------------
//    private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
//    public static ExecutorBiz getExecutorBiz(String address) throws Exception {
//        // valid
//        if (address==null || address.trim().length()==0) {
//            return null;
//        }
//
//        // load-cache
//        address = address.trim();
//        ExecutorBiz executorBiz = executorBizRepository.get(address);
//        if (executorBiz != null) {
//            return executorBiz;
//        }
//
//        // set-cache
//        executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
//                NetEnum.NETTY_HTTP,
//                Serializer.SerializeEnum.HESSIAN.getSerializer(),
//                CallType.SYNC,
//                LoadBalance.ROUND,
//                ExecutorBiz.class,
//                null,
//                5000,
//                address,
//                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
//                null,
//                null).getObject();
//
//        executorBizRepository.put(address, executorBiz);
//        return executorBiz;
//    }
//
//
//    // ---------------------- schedule util ----------------------
//
//    /**
//     * fill job info
//     *
//     * @param jobInfo
//     */
//    public static void fillJobInfo(XxlJobInfo jobInfo) {
//
//        String name = String.valueOf(jobInfo.getId());
//
//        // trigger key
//        TriggerKey triggerKey = TriggerKey.triggerKey(name);
//        try {
//
//            // trigger cron
//            Trigger trigger = scheduler.getTrigger(triggerKey);
//            if (trigger!=null && trigger instanceof CronTriggerImpl) {
//                String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
//                jobInfo.setJobCron(cronExpression);
//            }
//
//            // trigger state
//            TriggerState triggerState = scheduler.getTriggerState(triggerKey);
//            if (triggerState!=null) {
//                jobInfo.setJobStatus(triggerState.name());
//            }
//
//            //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
//            //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//            //String jobClass = jobDetail.getJobClass().getName();
//
//        } catch (SchedulerException e) {
//            logger.error(e.getMessage(), e);
//        }
//    }
//
//
//    /**
//     * add trigger + job
//     *
//     * @param jobName
//     * @param cronExpression
//     * @return
//     * @throws SchedulerException
//     */
//    public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
//        // 1、job key
//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//        JobKey jobKey = new JobKey(jobName);
//
//        // 2、valid
//        if (scheduler.checkExists(triggerKey)) {
//            return true;    // PASS
//        }
//
//        // 3、corn trigger
//        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();   // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
//        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
//
//        // 4、job detail
//        Class<? extends Job> jobClass_ = RemoteHttpJobBean.class;   // Class.forName(jobInfo.getJobClass());
//        JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
//
//        /*if (jobInfo.getJobData()!=null) {
//            JobDataMap jobDataMap = jobDetail.getJobDataMap();
//            jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
//            // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
//        }*/
//
//        // 5、schedule job
//        Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
//
//        logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
//        return true;
//    }
//
//
//    /**
//     * remove trigger + job
//     *
//     * @param jobName
//     * @return
//     * @throws SchedulerException
//     */
//    public static boolean removeJob(String jobName) throws SchedulerException {
//
//        JobKey jobKey = new JobKey(jobName);
//        scheduler.deleteJob(jobKey);
//
//        /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//        if (scheduler.checkExists(triggerKey)) {
//            scheduler.unscheduleJob(triggerKey);    // trigger + job
//        }*/
//
//        logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
//        return true;
//    }
//
//
//    /**
//     * updateJobCron
//     *
//     * @param jobName
//     * @param cronExpression
//     * @return
//     * @throws SchedulerException
//     */
//    public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
//
//        // 1、job key
//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
//        // 2、valid
//        if (!scheduler.checkExists(triggerKey)) {
//            return true;    // PASS
//        }
//
//        CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//
//        // 3、avoid repeat cron
//        String oldCron = oldTrigger.getCronExpression();
//        if (oldCron.equals(cronExpression)){
//            return true;    // PASS
//        }
//
//        // 4、new cron trigger
//        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
//        oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
//
//        // 5、rescheduleJob
//        scheduler.rescheduleJob(triggerKey, oldTrigger);
//
//        /*
//        JobKey jobKey = new JobKey(jobName);
//
//        // old job detail
//        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//
//        // new trigger
//        HashSet<Trigger> triggerSet = new HashSet<Trigger>();
//        triggerSet.add(cronTrigger);
//        // cover trigger of job detail
//        scheduler.scheduleJob(jobDetail, triggerSet, true);*/
//
//        logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
//        return true;
//    }
//
//
//    /**
//     * pause
//     *
//     * @param jobName
//     * @return
//     * @throws SchedulerException
//     */
//    /*public static boolean pauseJob(String jobName) throws SchedulerException {
//
//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
//        boolean result = false;
//        if (scheduler.checkExists(triggerKey)) {
//            scheduler.pauseTrigger(triggerKey);
//            result =  true;
//        }
//
//        logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
//        return result;
//    }*/
//
//
//    /**
//     * resume
//     *
//     * @param jobName
//     * @return
//     * @throws SchedulerException
//     */
//    /*public static boolean resumeJob(String jobName) throws SchedulerException {
//
//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
//        boolean result = false;
//        if (scheduler.checkExists(triggerKey)) {
//            scheduler.resumeTrigger(triggerKey);
//            result = true;
//        }
//
//        logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
//        return result;
//    }*/
//
//
//    /**
//     * run
//     *
//     * @param jobName
//     * @return
//     * @throws SchedulerException
//     */
//    /*public static boolean triggerJob(String jobName) throws SchedulerException {
//        // TriggerKey : name + group
//        JobKey jobKey = new JobKey(jobName);
//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
//        boolean result = false;
//        if (scheduler.checkExists(triggerKey)) {
//            scheduler.triggerJob(jobKey);
//            result = true;
//            logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
//        } else {
//            logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
//        }
//        return result;
//    }*/
//
//
//    /**
//     * finaAllJobList
//     *
//     * @return
//     *//*
//    @Deprecated
//    public static List<Map<String, Object>> finaAllJobList(){
//        List<Map<String, Object>> jobList = new ArrayList<Map<String,Object>>();
//
//        try {
//            if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
//                return null;
//            }
//            String groupName = scheduler.getJobGroupNames().get(0);
//            Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
//            if (jobKeys!=null && jobKeys.size()>0) {
//                for (JobKey jobKey : jobKeys) {
//                    TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
//                    Trigger trigger = scheduler.getTrigger(triggerKey);
//                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//                    TriggerState triggerState = scheduler.getTriggerState(triggerKey);
//                    Map<String, Object> jobMap = new HashMap<String, Object>();
//                    jobMap.put("TriggerKey", triggerKey);
//                    jobMap.put("Trigger", trigger);
//                    jobMap.put("JobDetail", jobDetail);
//                    jobMap.put("TriggerState", triggerState);
//                    jobList.add(jobMap);
//                }
//            }
//
//        } catch (SchedulerException e) {
//            logger.error(e.getMessage(), e);
//            return null;
//        }
//        return jobList;
//    }*/
//
//}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/old/XxlJobThreadPool.java
New file
@@ -0,0 +1,58 @@
//package com.xxl.job.admin.core.quartz;
//
//import org.quartz.SchedulerConfigException;
//import org.quartz.spi.ThreadPool;
//
///**
// * single thread pool, for async trigger
// *
// * @author xuxueli 2019-03-06
// */
//public class XxlJobThreadPool implements ThreadPool {
//
//    @Override
//    public boolean runInThread(Runnable runnable) {
//
//        // async run
//        runnable.run();
//        return true;
//
//        //return false;
//    }
//
//    @Override
//    public int blockForAvailableThreads() {
//        return 1;
//    }
//
//    @Override
//    public void initialize() throws SchedulerConfigException {
//
//    }
//
//    @Override
//    public void shutdown(boolean waitForJobsToComplete) {
//
//    }
//
//    @Override
//    public int getPoolSize() {
//        return 1;
//    }
//
//    @Override
//    public void setInstanceId(String schedInstId) {
//
//    }
//
//    @Override
//    public void setInstanceName(String schedName) {
//
//    }
//
//    // support
//    public void setThreadCount(int count) {
//        //
//    }
//
//}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/ExecutorRouteStrategyEnum.java
New file
@@ -0,0 +1,49 @@
package cn.gistack.job.admin.core.route;
import cn.gistack.job.admin.core.route.strategy.*;
import com.xxl.job.admin.core.route.strategy.*;
import cn.gistack.job.admin.core.util.I18nUtil;
/**
 * Created by xuxueli on 17/3/10.
 */
public enum ExecutorRouteStrategyEnum {
    FIRST(I18nUtil.getString("jobconf_route_first"), new ExecutorRouteFirst()),
    LAST(I18nUtil.getString("jobconf_route_last"), new ExecutorRouteLast()),
    ROUND(I18nUtil.getString("jobconf_route_round"), new ExecutorRouteRound()),
    RANDOM(I18nUtil.getString("jobconf_route_random"), new ExecutorRouteRandom()),
    CONSISTENT_HASH(I18nUtil.getString("jobconf_route_consistenthash"), new ExecutorRouteConsistentHash()),
    LEAST_FREQUENTLY_USED(I18nUtil.getString("jobconf_route_lfu"), new ExecutorRouteLFU()),
    LEAST_RECENTLY_USED(I18nUtil.getString("jobconf_route_lru"), new ExecutorRouteLRU()),
    FAILOVER(I18nUtil.getString("jobconf_route_failover"), new ExecutorRouteFailover()),
    BUSYOVER(I18nUtil.getString("jobconf_route_busyover"), new ExecutorRouteBusyover()),
    SHARDING_BROADCAST(I18nUtil.getString("jobconf_route_shard"), null);
    ExecutorRouteStrategyEnum(String title, ExecutorRouter router) {
        this.title = title;
        this.router = router;
    }
    private String title;
    private ExecutorRouter router;
    public String getTitle() {
        return title;
    }
    public ExecutorRouter getRouter() {
        return router;
    }
    public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){
        if (name != null) {
            for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) {
                if (item.name().equals(name)) {
                    return item;
                }
            }
        }
        return defaultItem;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/ExecutorRouter.java
New file
@@ -0,0 +1,24 @@
package cn.gistack.job.admin.core.route;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
 * Created by xuxueli on 17/3/10.
 */
public abstract class ExecutorRouter {
    protected static Logger logger = LoggerFactory.getLogger(ExecutorRouter.class);
    /**
     * route address
     *
     * @param addressList
     * @return  ReturnT.content=address
     */
    public abstract ReturnT<String> route(TriggerParam triggerParam, List<String> addressList);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteBusyover.java
New file
@@ -0,0 +1,47 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import cn.gistack.job.admin.core.scheduler.XxlJobScheduler;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteBusyover extends ExecutorRouter {
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        StringBuffer idleBeatResultSB = new StringBuffer();
        for (String address : addressList) {
            // beat
            ReturnT<String> idleBeatResult = null;
            try {
                ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
                idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                idleBeatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
            }
            idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"<br><br>":"")
                    .append(I18nUtil.getString("jobconf_idleBeat") + ":")
                    .append("<br>address:").append(address)
                    .append("<br>code:").append(idleBeatResult.getCode())
                    .append("<br>msg:").append(idleBeatResult.getMsg());
            // beat success
            if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
                idleBeatResult.setMsg(idleBeatResultSB.toString());
                idleBeatResult.setContent(address);
                return idleBeatResult;
            }
        }
        return new ReturnT<String>(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
New file
@@ -0,0 +1,85 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
 * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器;
 *      a、virtual node:解决不均衡问题
 *      b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteConsistentHash extends ExecutorRouter {
    private static int VIRTUAL_NODE_NUM = 5;
    /**
     * get hash code on 2^32 ring (md5散列的方式计算hash值)
     * @param key
     * @return
     */
    private static long hash(String key) {
        // md5 byte
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 not supported", e);
        }
        md5.reset();
        byte[] keyBytes = null;
        try {
            keyBytes = key.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unknown string :" + key, e);
        }
        md5.update(keyBytes);
        byte[] digest = md5.digest();
        // hash code, Truncate to 32-bits
        long hashCode = ((long) (digest[3] & 0xFF) << 24)
                | ((long) (digest[2] & 0xFF) << 16)
                | ((long) (digest[1] & 0xFF) << 8)
                | (digest[0] & 0xFF);
        long truncateHashCode = hashCode & 0xffffffffL;
        return truncateHashCode;
    }
    public String hashJob(int jobId, List<String> addressList) {
        // ------A1------A2-------A3------
        // -----------J1------------------
        TreeMap<Long, String> addressRing = new TreeMap<Long, String>();
        for (String address: addressList) {
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                long addressHash = hash("SHARD-" + address + "-NODE-" + i);
                addressRing.put(addressHash, address);
            }
        }
        long jobHash = hash(String.valueOf(jobId));
        SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
        if (!lastRing.isEmpty()) {
            return lastRing.get(lastRing.firstKey());
        }
        return addressRing.firstEntry().getValue();
    }
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = hashJob(triggerParam.getJobId(), addressList);
        return new ReturnT<String>(address);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteFailover.java
New file
@@ -0,0 +1,48 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import cn.gistack.job.admin.core.scheduler.XxlJobScheduler;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteFailover extends ExecutorRouter {
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        StringBuffer beatResultSB = new StringBuffer();
        for (String address : addressList) {
            // beat
            ReturnT<String> beatResult = null;
            try {
                ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
                beatResult = executorBiz.beat();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                beatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
            }
            beatResultSB.append( (beatResultSB.length()>0)?"<br><br>":"")
                    .append(I18nUtil.getString("jobconf_beat") + ":")
                    .append("<br>address:").append(address)
                    .append("<br>code:").append(beatResult.getCode())
                    .append("<br>msg:").append(beatResult.getMsg());
            // beat success
            if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
                beatResult.setMsg(beatResultSB.toString());
                beatResult.setContent(address);
                return beatResult;
            }
        }
        return new ReturnT<String>(ReturnT.FAIL_CODE, beatResultSB.toString());
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteFirst.java
New file
@@ -0,0 +1,19 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteFirst extends ExecutorRouter {
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList){
        return new ReturnT<String>(addressList.get(0));
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteLFU.java
New file
@@ -0,0 +1,79 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * 单个JOB对应的每个执行器,使用频率最低的优先被选举
 *      a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数
 *      b、LRU(Least Recently Used):最近最久未使用,时间
 *
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteLFU extends ExecutorRouter {
    private static ConcurrentMap<Integer, HashMap<String, Integer>> jobLfuMap = new ConcurrentHashMap<Integer, HashMap<String, Integer>>();
    private static long CACHE_VALID_TIME = 0;
    public String route(int jobId, List<String> addressList) {
        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLfuMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }
        // lfu item init
        HashMap<String, Integer> lfuItemMap = jobLfuMap.get(jobId);     // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList;
        if (lfuItemMap == null) {
            lfuItemMap = new HashMap<String, Integer>();
            jobLfuMap.putIfAbsent(jobId, lfuItemMap);   // 避免重复覆盖
        }
        // put new
        for (String address: addressList) {
            if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {
                lfuItemMap.put(address, new Random().nextInt(addressList.size()));  // 初始化时主动Random一次,缓解首次压力
            }
        }
        // remove old
        List<String> delKeys = new ArrayList<>();
        for (String existKey: lfuItemMap.keySet()) {
            if (!addressList.contains(existKey)) {
                delKeys.add(existKey);
            }
        }
        if (delKeys.size() > 0) {
            for (String delKey: delKeys) {
                lfuItemMap.remove(delKey);
            }
        }
        // load least userd count address
        List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<Map.Entry<String, Integer>>(lfuItemMap.entrySet());
        Collections.sort(lfuItemList, new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });
        Map.Entry<String, Integer> addressItem = lfuItemList.get(0);
        String minAddress = addressItem.getKey();
        addressItem.setValue(addressItem.getValue() + 1);
        return addressItem.getKey();
    }
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = route(triggerParam.getJobId(), addressList);
        return new ReturnT<String>(address);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteLRU.java
New file
@@ -0,0 +1,76 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * 单个JOB对应的每个执行器,最久为使用的优先被选举
 *      a、LFU(Least Frequently Used):最不经常使用,频率/次数
 *      b(*)、LRU(Least Recently Used):最近最久未使用,时间
 *
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteLRU extends ExecutorRouter {
    private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<Integer, LinkedHashMap<String, String>>();
    private static long CACHE_VALID_TIME = 0;
    public String route(int jobId, List<String> addressList) {
        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLRUMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }
        // init lru
        LinkedHashMap<String, String> lruItem = jobLRUMap.get(jobId);
        if (lruItem == null) {
            /**
             * LinkedHashMap
             *      a、accessOrder:true=访问顺序排序(get/put时排序);false=插入顺序排期;
             *      b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法;
             */
            lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);
            jobLRUMap.putIfAbsent(jobId, lruItem);
        }
        // put new
        for (String address: addressList) {
            if (!lruItem.containsKey(address)) {
                lruItem.put(address, address);
            }
        }
        // remove old
        List<String> delKeys = new ArrayList<>();
        for (String existKey: lruItem.keySet()) {
            if (!addressList.contains(existKey)) {
                delKeys.add(existKey);
            }
        }
        if (delKeys.size() > 0) {
            for (String delKey: delKeys) {
                lruItem.remove(delKey);
            }
        }
        // load
        String eldestKey = lruItem.entrySet().iterator().next().getKey();
        String eldestValue = lruItem.get(eldestKey);
        return eldestValue;
    }
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = route(triggerParam.getJobId(), addressList);
        return new ReturnT<String>(address);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteLast.java
New file
@@ -0,0 +1,19 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteLast extends ExecutorRouter {
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        return new ReturnT<String>(addressList.get(addressList.size()-1));
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteRandom.java
New file
@@ -0,0 +1,23 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
import java.util.Random;
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteRandom extends ExecutorRouter {
    private static Random localRandom = new Random();
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = addressList.get(localRandom.nextInt(addressList.size()));
        return new ReturnT<String>(address);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/route/strategy/ExecutorRouteRound.java
New file
@@ -0,0 +1,39 @@
package cn.gistack.job.admin.core.route.strategy;
import cn.gistack.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteRound extends ExecutorRouter {
    private static ConcurrentMap<Integer, Integer> routeCountEachJob = new ConcurrentHashMap<Integer, Integer>();
    private static long CACHE_VALID_TIME = 0;
    private static int count(int jobId) {
        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            routeCountEachJob.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }
        // count++
        Integer count = routeCountEachJob.get(jobId);
        count = (count==null || count>1000000)?(new Random().nextInt(100)):++count;  // 初始化时主动Random一次,缓解首次压力
        routeCountEachJob.put(jobId, count);
        return count;
    }
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = addressList.get(count(triggerParam.getJobId())%addressList.size());
        return new ReturnT<String>(address);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/scheduler/XxlJobScheduler.java
New file
@@ -0,0 +1,113 @@
package cn.gistack.job.admin.core.scheduler;
import cn.gistack.job.admin.core.thread.*;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.thread.*;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.rpc.remoting.invoker.call.CallType;
import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
import com.xxl.rpc.remoting.invoker.route.LoadBalance;
import com.xxl.rpc.remoting.net.impl.netty_http.client.NettyHttpClient;
import com.xxl.rpc.serialize.impl.HessianSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * @author xuxueli 2018-10-28 00:18:17
 */
public class XxlJobScheduler  {
    private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
    public void init() throws Exception {
        // init i18n
        initI18n();
        // admin registry monitor run
        JobRegistryMonitorHelper.getInstance().start();
        // admin monitor run
        JobFailMonitorHelper.getInstance().start();
        // admin trigger pool start
        JobTriggerPoolHelper.toStart();
        // admin log report start
        JobLogReportHelper.getInstance().start();
        // start-schedule
        JobScheduleHelper.getInstance().start();
        logger.info(">>>>>>>>> init xxl-job admin success.");
    }
    public void destroy() throws Exception {
        // stop-schedule
        JobScheduleHelper.getInstance().toStop();
        // admin log report stop
        JobLogReportHelper.getInstance().toStop();
        // admin trigger pool stop
        JobTriggerPoolHelper.toStop();
        // admin monitor stop
        JobFailMonitorHelper.getInstance().toStop();
        // admin registry stop
        JobRegistryMonitorHelper.getInstance().toStop();
    }
    // ---------------------- I18n ----------------------
    private void initI18n(){
        for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
            item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
        }
    }
    // ---------------------- executor-client ----------------------
    private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
    public static ExecutorBiz getExecutorBiz(String address) throws Exception {
        // valid
        if (address==null || address.trim().length()==0) {
            return null;
        }
        // load-cache
        address = address.trim();
        ExecutorBiz executorBiz = executorBizRepository.get(address);
        if (executorBiz != null) {
            return executorBiz;
        }
        // set-cache
        XxlRpcReferenceBean referenceBean = new XxlRpcReferenceBean();
        referenceBean.setClient(NettyHttpClient.class);
        referenceBean.setSerializer(HessianSerializer.class);
        referenceBean.setCallType(CallType.SYNC);
        referenceBean.setLoadBalance(LoadBalance.ROUND);
        referenceBean.setIface(ExecutorBiz.class);
        referenceBean.setVersion(null);
        referenceBean.setTimeout(3000);
        referenceBean.setAddress(address);
        referenceBean.setAccessToken(XxlJobAdminConfig.getAdminConfig().getAccessToken());
        referenceBean.setInvokeCallback(null);
        referenceBean.setInvokerFactory(null);
        executorBiz = (ExecutorBiz) referenceBean.getObject();
        executorBizRepository.put(address, executorBiz);
        return executorBiz;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobFailMonitorHelper.java
New file
@@ -0,0 +1,209 @@
package cn.gistack.job.admin.core.thread;
import cn.gistack.job.admin.core.trigger.TriggerTypeEnum;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.model.XxlJobLog;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.internet.MimeMessage;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
 * job monitor instance
 *
 * @author xuxueli 2015-9-1 18:05:56
 */
public class JobFailMonitorHelper {
    private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class);
    private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
    public static JobFailMonitorHelper getInstance(){
        return instance;
    }
    // ---------------------- monitor ----------------------
    private Thread monitorThread;
    private volatile boolean toStop = false;
    public void start(){
        monitorThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // monitor
                while (!toStop) {
                    try {
                        List<Long> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
                        if (failLogIds!=null && !failLogIds.isEmpty()) {
                            for (long failLogId: failLogIds) {
                                // lock log
                                int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
                                if (lockRet < 1) {
                                    continue;
                                }
                                XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
                                XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
                                // 1、fail retry monitor
                                if (log.getExecutorFailRetryCount() > 0) {
                                    JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam());
                                    String retryMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
                                    log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
                                    XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
                                }
                                // 2、fail alarm monitor
                                int newAlarmStatus = 0;        // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
                                if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
                                    boolean alarmResult = true;
                                    try {
                                        alarmResult = failAlarm(info, log);
                                    } catch (Exception e) {
                                        alarmResult = false;
                                        logger.error(e.getMessage(), e);
                                    }
                                    newAlarmStatus = alarmResult?2:3;
                                } else {
                                    newAlarmStatus = 1;
                                }
                                XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
                            }
                        }
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");
            }
        });
        monitorThread.setDaemon(true);
        monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
        monitorThread.start();
    }
    public void toStop(){
        toStop = true;
        // interrupt and wait
        monitorThread.interrupt();
        try {
            monitorThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }
    // ---------------------- alarm ----------------------
    // email alarm template
    private static final String mailBodyTemplate = "<h5>" + I18nUtil.getString("jobconf_monitor_detail") + ":</span>" +
            "<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" +
            "   <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" +
            "      <tr>\n" +
            "         <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobgroup") +"</td>\n" +
            "         <td width=\"10%\" >"+ I18nUtil.getString("jobinfo_field_id") +"</td>\n" +
            "         <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobdesc") +"</td>\n" +
            "         <td width=\"10%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_title") +"</td>\n" +
            "         <td width=\"40%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_content") +"</td>\n" +
            "      </tr>\n" +
            "   </thead>\n" +
            "   <tbody>\n" +
            "      <tr>\n" +
            "         <td>{0}</td>\n" +
            "         <td>{1}</td>\n" +
            "         <td>{2}</td>\n" +
            "         <td>"+ I18nUtil.getString("jobconf_monitor_alarm_type") +"</td>\n" +
            "         <td>{3}</td>\n" +
            "      </tr>\n" +
            "   </tbody>\n" +
            "</table>";
    /**
     * fail alarm
     *
     * @param jobLog
     */
    private boolean failAlarm(XxlJobInfo info, XxlJobLog jobLog){
        boolean alarmResult = true;
        // send monitor email
        if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
            // alarmContent
            String alarmContent = "Alarm Job LogId=" + jobLog.getId();
            if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) {
                alarmContent += "<br>TriggerMsg=<br>" + jobLog.getTriggerMsg();
            }
            if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) {
                alarmContent += "<br>HandleCode=" + jobLog.getHandleMsg();
            }
            // email info
            XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(Integer.valueOf(info.getJobGroup()));
            String personal = I18nUtil.getString("admin_name_full");
            String title = I18nUtil.getString("jobconf_monitor");
            String content = MessageFormat.format(mailBodyTemplate,
                    group!=null?group.getTitle():"null",
                    info.getId(),
                    info.getJobDesc(),
                    alarmContent);
            Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(",")));
            for (String email: emailSet) {
                // make mail
                try {
                    MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage();
                    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
                    helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailUserName(), personal);
                    helper.setTo(email);
                    helper.setSubject(title);
                    helper.setText(content, true);
                    XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage);
                } catch (Exception e) {
                    logger.error(">>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:{}", jobLog.getId(), e);
                    alarmResult = false;
                }
            }
        }
        // do something, custom alarm strategy, such as sms
        return alarmResult;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobLogReportHelper.java
New file
@@ -0,0 +1,152 @@
package cn.gistack.job.admin.core.thread;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import cn.gistack.job.admin.core.model.XxlJobLogReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * job log report helper
 *
 * @author xuxueli 2019-11-22
 */
public class JobLogReportHelper {
    private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
    private static JobLogReportHelper instance = new JobLogReportHelper();
    public static JobLogReportHelper getInstance(){
        return instance;
    }
    private Thread logrThread;
    private volatile boolean toStop = false;
    public void start(){
        logrThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // last clean log time
                long lastCleanLogTime = 0;
                while (!toStop) {
                    // 1、log-report refresh: refresh log report in 3 days
                    try {
                        for (int i = 0; i < 3; i++) {
                            // today
                            Calendar itemDay = Calendar.getInstance();
                            itemDay.add(Calendar.DAY_OF_MONTH, -i);
                            itemDay.set(Calendar.HOUR_OF_DAY, 0);
                            itemDay.set(Calendar.MINUTE, 0);
                            itemDay.set(Calendar.SECOND, 0);
                            itemDay.set(Calendar.MILLISECOND, 0);
                            Date todayFrom = itemDay.getTime();
                            itemDay.set(Calendar.HOUR_OF_DAY, 23);
                            itemDay.set(Calendar.MINUTE, 59);
                            itemDay.set(Calendar.SECOND, 59);
                            itemDay.set(Calendar.MILLISECOND, 999);
                            Date todayTo = itemDay.getTime();
                            // refresh log-report every minute
                            XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
                            xxlJobLogReport.setTriggerDay(todayFrom);
                            xxlJobLogReport.setRunningCount(0);
                            xxlJobLogReport.setSucCount(0);
                            xxlJobLogReport.setFailCount(0);
                            Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo);
                            if (triggerCountMap!=null && triggerCountMap.size()>0) {
                                int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))):0;
                                int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0;
                                int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0;
                                int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
                                xxlJobLogReport.setRunningCount(triggerDayCountRunning);
                                xxlJobLogReport.setSucCount(triggerDayCountSuc);
                                xxlJobLogReport.setFailCount(triggerDayCountFail);
                            }
                            // do refresh
                            int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().update(xxlJobLogReport);
                            if (ret < 1) {
                                XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport);
                            }
                        }
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(">>>>>>>>>>> xxl-job, job log report thread error:{}", e);
                        }
                    }
                    // 2、log-clean: switch open & once each day
                    if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0
                            && System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) {
                        // expire-time
                        Calendar expiredDay = Calendar.getInstance();
                        expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays());
                        expiredDay.set(Calendar.HOUR_OF_DAY, 0);
                        expiredDay.set(Calendar.MINUTE, 0);
                        expiredDay.set(Calendar.SECOND, 0);
                        expiredDay.set(Calendar.MILLISECOND, 0);
                        Date clearBeforeTime = expiredDay.getTime();
                        // clean expired log
                        List<Long> logIds = null;
                        do {
                            logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
                            if (logIds!=null && logIds.size()>0) {
                                XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
                            }
                        } while (logIds!=null && logIds.size()>0);
                        // update clean time
                        lastCleanLogTime = System.currentTimeMillis();
                    }
                    try {
                        TimeUnit.MINUTES.sleep(1);
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, job log report thread stop");
            }
        });
        logrThread.setDaemon(true);
        logrThread.setName("xxl-job, admin JobLogReportHelper");
        logrThread.start();
    }
    public void toStop(){
        toStop = true;
        // interrupt and wait
        logrThread.interrupt();
        try {
            logrThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobRegistryMonitorHelper.java
New file
@@ -0,0 +1,111 @@
package cn.gistack.job.admin.core.thread;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.core.enums.RegistryConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * job registry instance
 * @author xuxueli 2016-10-02 19:10:24
 */
public class JobRegistryMonitorHelper {
    private static Logger logger = LoggerFactory.getLogger(JobRegistryMonitorHelper.class);
    private static JobRegistryMonitorHelper instance = new JobRegistryMonitorHelper();
    public static JobRegistryMonitorHelper getInstance(){
        return instance;
    }
    private Thread registryThread;
    private volatile boolean toStop = false;
    public void start(){
        registryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!toStop) {
                    try {
                        // auto registry group
                        List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
                        if (groupList!=null && !groupList.isEmpty()) {
                            // remove dead address (admin/executor)
                            List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
                            if (ids!=null && ids.size()>0) {
                                XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
                            }
                            // fresh online address (admin/executor)
                            HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
                            List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
                            if (list != null) {
                                for (XxlJobRegistry item: list) {
                                    if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
                                        String appName = item.getRegistryKey();
                                        List<String> registryList = appAddressMap.get(appName);
                                        if (registryList == null) {
                                            registryList = new ArrayList<String>();
                                        }
                                        if (!registryList.contains(item.getRegistryValue())) {
                                            registryList.add(item.getRegistryValue());
                                        }
                                        appAddressMap.put(appName, registryList);
                                    }
                                }
                            }
                            // fresh group address
                            for (XxlJobGroup group: groupList) {
                                List<String> registryList = appAddressMap.get(group.getAppName());
                                String addressListStr = null;
                                if (registryList!=null && !registryList.isEmpty()) {
                                    Collections.sort(registryList);
                                    addressListStr = "";
                                    for (String item:registryList) {
                                        addressListStr += item + ",";
                                    }
                                    addressListStr = addressListStr.substring(0, addressListStr.length()-1);
                                }
                                group.setAddressList(addressListStr);
                                XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
                            }
                        }
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                    } catch (InterruptedException e) {
                        if (!toStop) {
                            logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
            }
        });
        registryThread.setDaemon(true);
        registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
        registryThread.start();
    }
    public void toStop(){
        toStop = true;
        // interrupt and wait
        registryThread.interrupt();
        try {
            registryThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobScheduleHelper.java
New file
@@ -0,0 +1,354 @@
package cn.gistack.job.admin.core.thread;
import cn.gistack.job.admin.core.trigger.TriggerTypeEnum;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import cn.gistack.job.admin.core.cron.CronExpression;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
 * @author xuxueli 2019-05-21
 */
public class JobScheduleHelper {
    private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
    private static JobScheduleHelper instance = new JobScheduleHelper();
    public static JobScheduleHelper getInstance(){
        return instance;
    }
    public static final long PRE_READ_MS = 5000;    // pre read
    private Thread scheduleThread;
    private Thread ringThread;
    private volatile boolean scheduleThreadToStop = false;
    private volatile boolean ringThreadToStop = false;
    private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
    public void start(){
        // schedule thread
        scheduleThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
                } catch (InterruptedException e) {
                    if (!scheduleThreadToStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
                logger.info(">>>>>>>>> init xxl-job admin scheduler success.");
                // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
                int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
                while (!scheduleThreadToStop) {
                    // Scan Job
                    long start = System.currentTimeMillis();
                    Connection conn = null;
                    Boolean connAutoCommit = null;
                    PreparedStatement preparedStatement = null;
                    boolean preReadSuc = true;
                    try {
                        conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
                        connAutoCommit = conn.getAutoCommit();
                        conn.setAutoCommit(false);
                        preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
                        preparedStatement.execute();
                        // tx start
                        // 1、pre read
                        long nowTime = System.currentTimeMillis();
                        List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
                        if (scheduleList!=null && scheduleList.size()>0) {
                            // 2、push time-ring
                            for (XxlJobInfo jobInfo: scheduleList) {
                                // time-ring jump
                                if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
                                    // 2.1、trigger-expire > 5s:pass && make next-trigger-time
                                    logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
                                    // fresh next
                                    refreshNextValidTime(jobInfo, new Date());
                                } else if (nowTime > jobInfo.getTriggerNextTime()) {
                                    // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time
                                    // 1、trigger
                                    JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null);
                                    logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
                                    // 2、fresh next
                                    refreshNextValidTime(jobInfo, new Date());
                                    // next-trigger-time in 5s, pre-read again
                                    if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
                                        // 1、make ring second
                                        int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
                                        // 2、push time ring
                                        pushTimeRing(ringSecond, jobInfo.getId());
                                        // 3、fresh next
                                        refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
                                    }
                                } else {
                                    // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
                                    // 1、make ring second
                                    int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
                                    // 2、push time ring
                                    pushTimeRing(ringSecond, jobInfo.getId());
                                    // 3、fresh next
                                    refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
                                }
                            }
                            // 3、update trigger info
                            for (XxlJobInfo jobInfo: scheduleList) {
                                XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
                            }
                        } else {
                            preReadSuc = false;
                        }
                        // tx stop
                    } catch (Exception e) {
                        if (!scheduleThreadToStop) {
                            logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
                        }
                    } finally {
                        // commit
                        if (conn != null) {
                            try {
                                conn.commit();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                            try {
                                conn.setAutoCommit(connAutoCommit);
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                            try {
                                conn.close();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                        }
                        // close PreparedStatement
                        if (null != preparedStatement) {
                            try {
                                preparedStatement.close();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                        }
                    }
                    long cost = System.currentTimeMillis()-start;
                    // Wait seconds, align second
                    if (cost < 1000) {  // scan-overtime, not wait
                        try {
                            // pre-read period: success > scan each second; fail > skip this period;
                            TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
                        } catch (InterruptedException e) {
                            if (!scheduleThreadToStop) {
                                logger.error(e.getMessage(), e);
                            }
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
            }
        });
        scheduleThread.setDaemon(true);
        scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
        scheduleThread.start();
        // ring thread
        ringThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // align second
                try {
                    TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000 );
                } catch (InterruptedException e) {
                    if (!ringThreadToStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
                while (!ringThreadToStop) {
                    try {
                        // second data
                        List<Integer> ringItemData = new ArrayList<>();
                        int nowSecond = Calendar.getInstance().get(Calendar.SECOND);   // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
                        for (int i = 0; i < 2; i++) {
                            List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
                            if (tmpData != null) {
                                ringItemData.addAll(tmpData);
                            }
                        }
                        // ring trigger
                        logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
                        if (ringItemData.size() > 0) {
                            // do trigger
                            for (int jobId: ringItemData) {
                                // do trigger
                                JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
                            }
                            // clear
                            ringItemData.clear();
                        }
                    } catch (Exception e) {
                        if (!ringThreadToStop) {
                            logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
                        }
                    }
                    // next second, align second
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000);
                    } catch (InterruptedException e) {
                        if (!ringThreadToStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
            }
        });
        ringThread.setDaemon(true);
        ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
        ringThread.start();
    }
    private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws ParseException {
        Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(fromTime);
        if (nextValidTime != null) {
            jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
            jobInfo.setTriggerNextTime(nextValidTime.getTime());
        } else {
            jobInfo.setTriggerStatus(0);
            jobInfo.setTriggerLastTime(0);
            jobInfo.setTriggerNextTime(0);
        }
    }
    private void pushTimeRing(int ringSecond, int jobId){
        // push async ring
        List<Integer> ringItemData = ringData.get(ringSecond);
        if (ringItemData == null) {
            ringItemData = new ArrayList<Integer>();
            ringData.put(ringSecond, ringItemData);
        }
        ringItemData.add(jobId);
        logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) );
    }
    public void toStop(){
        // 1、stop schedule
        scheduleThreadToStop = true;
        try {
            TimeUnit.SECONDS.sleep(1);  // wait
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
        if (scheduleThread.getState() != Thread.State.TERMINATED){
            // interrupt and wait
            scheduleThread.interrupt();
            try {
                scheduleThread.join();
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
        }
        // if has ring data
        boolean hasRingData = false;
        if (!ringData.isEmpty()) {
            for (int second : ringData.keySet()) {
                List<Integer> tmpData = ringData.get(second);
                if (tmpData!=null && tmpData.size()>0) {
                    hasRingData = true;
                    break;
                }
            }
        }
        if (hasRingData) {
            try {
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
        }
        // stop ring (wait job-in-memory stop)
        ringThreadToStop = true;
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
        if (ringThread.getState() != Thread.State.TERMINATED){
            // interrupt and wait
            ringThread.interrupt();
            try {
                ringThread.join();
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
        }
        logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/thread/JobTriggerPoolHelper.java
New file
@@ -0,0 +1,145 @@
package cn.gistack.job.admin.core.thread;
import cn.gistack.job.admin.core.trigger.TriggerTypeEnum;
import cn.gistack.job.admin.core.trigger.XxlJobTrigger;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * job trigger thread pool helper
 *
 * @author xuxueli 2018-07-03 21:08:07
 */
public class JobTriggerPoolHelper {
    private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
    // ---------------------- trigger pool ----------------------
    // fast/slow thread pool
    private ThreadPoolExecutor fastTriggerPool = null;
    private ThreadPoolExecutor slowTriggerPool = null;
    public void start(){
        fastTriggerPool = new ThreadPoolExecutor(
                10,
                XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(1000),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
                    }
                });
        slowTriggerPool = new ThreadPoolExecutor(
                10,
                XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(2000),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
                    }
                });
    }
    public void stop() {
        //triggerPool.shutdown();
        fastTriggerPool.shutdownNow();
        slowTriggerPool.shutdownNow();
        logger.info(">>>>>>>>> xxl-job trigger thread pool shutdown success.");
    }
    // job timeout count
    private volatile long minTim = System.currentTimeMillis()/60000;     // ms > min
    private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>();
    /**
     * add trigger
     */
    public void addTrigger(final int jobId, final TriggerTypeEnum triggerType, final int failRetryCount, final String executorShardingParam, final String executorParam) {
        // choose thread pool
        ThreadPoolExecutor triggerPool_ = fastTriggerPool;
        AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
        if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) {      // job-timeout 10 times in 1 min
            triggerPool_ = slowTriggerPool;
        }
        // trigger
        triggerPool_.execute(new Runnable() {
            @Override
            public void run() {
                long start = System.currentTimeMillis();
                try {
                    // do trigger
                    XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam);
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                } finally {
                    // check timeout-count-map
                    long minTim_now = System.currentTimeMillis()/60000;
                    if (minTim != minTim_now) {
                        minTim = minTim_now;
                        jobTimeoutCountMap.clear();
                    }
                    // incr timeout-count-map
                    long cost = System.currentTimeMillis()-start;
                    if (cost > 500) {       // ob-timeout threshold 500ms
                        AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));
                        if (timeoutCount != null) {
                            timeoutCount.incrementAndGet();
                        }
                    }
                }
            }
        });
    }
    // ---------------------- helper ----------------------
    private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();
    public static void toStart() {
        helper.start();
    }
    public static void toStop() {
        helper.stop();
    }
    /**
     * @param jobId
     * @param triggerType
     * @param failRetryCount
     *             >=0: use this param
     *             <0: use param from job info config
     * @param executorShardingParam
     * @param executorParam
     *          null: use job param
     *          not null: cover job param
     */
    public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam) {
        helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/trigger/TriggerTypeEnum.java
New file
@@ -0,0 +1,26 @@
package cn.gistack.job.admin.core.trigger;
import cn.gistack.job.admin.core.util.I18nUtil;
/**
 * trigger type enum
 *
 * @author xuxueli 2018-09-16 04:56:41
 */
public enum TriggerTypeEnum {
    MANUAL(I18nUtil.getString("jobconf_trigger_type_manual")),
    CRON(I18nUtil.getString("jobconf_trigger_type_cron")),
    RETRY(I18nUtil.getString("jobconf_trigger_type_retry")),
    PARENT(I18nUtil.getString("jobconf_trigger_type_parent")),
    API(I18nUtil.getString("jobconf_trigger_type_api"));
    private TriggerTypeEnum(String title){
        this.title = title;
    }
    private String title;
    public String getTitle() {
        return title;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/trigger/XxlJobTrigger.java
New file
@@ -0,0 +1,211 @@
package cn.gistack.job.admin.core.trigger;
import cn.gistack.job.admin.core.route.ExecutorRouteStrategyEnum;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import cn.gistack.job.admin.core.scheduler.XxlJobScheduler;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.model.XxlJobLog;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.rpc.util.IpUtil;
import com.xxl.rpc.util.ThrowableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
/**
 * xxl-job trigger
 * Created by xuxueli on 17/7/13.
 */
public class XxlJobTrigger {
    private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class);
    /**
     * trigger job
     *
     * @param jobId
     * @param triggerType
     * @param failRetryCount
     *             >=0: use this param
     *             <0: use param from job info config
     * @param executorShardingParam
     * @param executorParam
     *          null: use job param
     *          not null: cover job param
     */
    public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam) {
        // load data
        XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
        if (jobInfo == null) {
            logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
            return;
        }
        if (executorParam != null) {
            jobInfo.setExecutorParam(executorParam);
        }
        int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount();
        XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
        // sharding param
        int[] shardingParam = null;
        if (executorShardingParam!=null){
            String[] shardingArr = executorShardingParam.split("/");
            if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
                shardingParam = new int[2];
                shardingParam[0] = Integer.valueOf(shardingArr[0]);
                shardingParam[1] = Integer.valueOf(shardingArr[1]);
            }
        }
        if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
                && group.getRegistryList()!=null && !group.getRegistryList().isEmpty()
                && shardingParam==null) {
            for (int i = 0; i < group.getRegistryList().size(); i++) {
                processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
            }
        } else {
            if (shardingParam == null) {
                shardingParam = new int[]{0, 1};
            }
            processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
        }
    }
    private static boolean isNumeric(String str){
        try {
            int result = Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    /**
     * @param group                     job group, registry list may be empty
     * @param jobInfo
     * @param finalFailRetryCount
     * @param triggerType
     * @param index                     sharding index
     * @param total                     sharding index
     */
    private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){
        // param
        ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION);  // block strategy
        ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);    // route strategy
        String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;
        // 1、save log-id
        XxlJobLog jobLog = new XxlJobLog();
        jobLog.setJobGroup(jobInfo.getJobGroup());
        jobLog.setJobId(jobInfo.getId());
        jobLog.setTriggerTime(new Date());
        XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
        logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
        // 2、init trigger-param
        TriggerParam triggerParam = new TriggerParam();
        triggerParam.setJobId(jobInfo.getId());
        triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
        triggerParam.setExecutorParams(jobInfo.getExecutorParam());
        triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
        triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
        triggerParam.setLogId(jobLog.getId());
        triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
        triggerParam.setGlueType(jobInfo.getGlueType());
        triggerParam.setGlueSource(jobInfo.getGlueSource());
        triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
        triggerParam.setBroadcastIndex(index);
        triggerParam.setBroadcastTotal(total);
        // 3、init address
        String address = null;
        ReturnT<String> routeAddressResult = null;
        if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
            if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
                if (index < group.getRegistryList().size()) {
                    address = group.getRegistryList().get(index);
                } else {
                    address = group.getRegistryList().get(0);
                }
            } else {
                routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
                if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
                    address = routeAddressResult.getContent();
                }
            }
        } else {
            routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
        }
        // 4、trigger remote executor
        ReturnT<String> triggerResult = null;
        if (address != null) {
            triggerResult = runExecutor(triggerParam, address);
        } else {
            triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
        }
        // 5、collection trigger info
        StringBuffer triggerMsgSb = new StringBuffer();
        triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":").append(IpUtil.getIp());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append(":")
                .append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") );
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":").append(group.getRegistryList());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":").append(executorRouteStrategyEnum.getTitle());
        if (shardingParam != null) {
            triggerMsgSb.append("("+shardingParam+")");
        }
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":").append(blockStrategy.getTitle());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append(":").append(jobInfo.getExecutorTimeout());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(finalFailRetryCount);
        triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<< </span><br>")
                .append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"<br><br>":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():"");
        // 6、save log trigger-info
        jobLog.setExecutorAddress(address);
        jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
        jobLog.setExecutorParam(jobInfo.getExecutorParam());
        jobLog.setExecutorShardingParam(shardingParam);
        jobLog.setExecutorFailRetryCount(finalFailRetryCount);
        //jobLog.setTriggerTime();
        jobLog.setTriggerCode(triggerResult.getCode());
        jobLog.setTriggerMsg(triggerMsgSb.toString());
        XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);
        logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
    }
    /**
     * run executor
     * @param triggerParam
     * @param address
     * @return
     */
    public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
        ReturnT<String> runResult = null;
        try {
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
            runResult = executorBiz.run(triggerParam);
        } catch (Exception e) {
            logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
            runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
        }
        StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
        runResultSB.append("<br>address:").append(address);
        runResultSB.append("<br>code:").append(runResult.getCode());
        runResultSB.append("<br>msg:").append(runResult.getMsg());
        runResult.setMsg(runResultSB.toString());
        return runResult;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/CookieUtil.java
New file
@@ -0,0 +1,98 @@
package cn.gistack.job.admin.core.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Cookie.Util
 *
 * @author xuxueli 2015-12-12 18:01:06
 */
public class CookieUtil {
    // 默认缓存时间,单位/秒, 2H
    private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE;
    // 保存路径,根路径
    private static final String COOKIE_PATH = "/";
    /**
     * 保存
     *
     * @param response
     * @param key
     * @param value
     * @param ifRemember
     */
    public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
        int age = ifRemember?COOKIE_MAX_AGE:-1;
        set(response, key, value, null, COOKIE_PATH, age, true);
    }
    /**
     * 保存
     *
     * @param response
     * @param key
     * @param value
     * @param maxAge
     */
    private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
        Cookie cookie = new Cookie(key, value);
        if (domain != null) {
            cookie.setDomain(domain);
        }
        cookie.setPath(path);
        cookie.setMaxAge(maxAge);
        cookie.setHttpOnly(isHttpOnly);
        response.addCookie(cookie);
    }
    /**
     * 查询value
     *
     * @param request
     * @param key
     * @return
     */
    public static String getValue(HttpServletRequest request, String key) {
        Cookie cookie = get(request, key);
        if (cookie != null) {
            return cookie.getValue();
        }
        return null;
    }
    /**
     * 查询Cookie
     *
     * @param request
     * @param key
     */
    private static Cookie get(HttpServletRequest request, String key) {
        Cookie[] arr_cookie = request.getCookies();
        if (arr_cookie != null && arr_cookie.length > 0) {
            for (Cookie cookie : arr_cookie) {
                if (cookie.getName().equals(key)) {
                    return cookie;
                }
            }
        }
        return null;
    }
    /**
     * 删除Cookie
     *
     * @param request
     * @param response
     * @param key
     */
    public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
        Cookie cookie = get(request, key);
        if (cookie != null) {
            set(response, key, "", null, COOKIE_PATH, 0, true);
        }
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/FtlUtil.java
New file
@@ -0,0 +1,31 @@
package cn.gistack.job.admin.core.util;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.TemplateHashModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * ftl util
 *
 * @author xuxueli 2018-01-17 20:37:48
 */
public class FtlUtil {
    private static Logger logger = LoggerFactory.getLogger(FtlUtil.class);
    private static BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build();     //BeansWrapper.getDefaultInstance();
    public static TemplateHashModel generateStaticModel(String packageName) {
        try {
            TemplateHashModel staticModels = wrapper.getStaticModels();
            TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName);
            return fileStatics;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/I18nUtil.java
New file
@@ -0,0 +1,80 @@
package cn.gistack.job.admin.core.util;
import cn.gistack.job.admin.core.conf.XxlJobAdminConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
 * i18n util
 *
 * @author xuxueli 2018-01-17 20:39:06
 */
public class I18nUtil {
    private static Logger logger = LoggerFactory.getLogger(I18nUtil.class);
    private static Properties prop = null;
    public static Properties loadI18nProp(){
        if (prop != null) {
            return prop;
        }
        try {
            // build i18n prop
            String i18n = XxlJobAdminConfig.getAdminConfig().getI18n();
            i18n = (i18n!=null && i18n.trim().length()>0)?("_"+i18n):i18n;
            String i18nFile = MessageFormat.format("i18n/message{0}.properties", i18n);
            // load prop
            Resource resource = new ClassPathResource(i18nFile);
            EncodedResource encodedResource = new EncodedResource(resource,"UTF-8");
            prop = PropertiesLoaderUtils.loadProperties(encodedResource);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return prop;
    }
    /**
     * get val of i18n key
     *
     * @param key
     * @return
     */
    public static String getString(String key) {
        return loadI18nProp().getProperty(key);
    }
    /**
     * get mult val of i18n mult key, as json
     *
     * @param keys
     * @return
     */
    public static String getMultString(String... keys) {
        Map<String, String> map = new HashMap<String, String>();
        Properties prop = loadI18nProp();
        if (keys!=null && keys.length>0) {
            for (String key: keys) {
                map.put(key, prop.getProperty(key));
            }
        } else {
            for (String key: prop.stringPropertyNames()) {
                map.put(key, prop.getProperty(key));
            }
        }
        String json = JacksonUtil.writeValueAsString(map);
        return json;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/JacksonUtil.java
New file
@@ -0,0 +1,92 @@
package cn.gistack.job.admin.core.util;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
 * Jackson util
 *
 * 1、obj need private and set/get;
 * 2、do not support inner class;
 *
 * @author xuxueli 2015-9-25 18:02:56
 */
public class JacksonUtil {
    private static Logger logger = LoggerFactory.getLogger(JacksonUtil.class);
    private final static ObjectMapper objectMapper = new ObjectMapper();
    public static ObjectMapper getInstance() {
        return objectMapper;
    }
    /**
     * bean、array、List、Map --> json
     *
     * @param obj
     * @return json string
     * @throws Exception
     */
    public static String writeValueAsString(Object obj) {
        try {
            return getInstance().writeValueAsString(obj);
        } catch (JsonGenerationException e) {
            logger.error(e.getMessage(), e);
        } catch (JsonMappingException e) {
            logger.error(e.getMessage(), e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
    /**
     * string --> bean、Map、List(array)
     *
     * @param jsonStr
     * @param clazz
     * @return obj
     * @throws Exception
     */
    public static <T> T readValue(String jsonStr, Class<T> clazz) {
        try {
            return getInstance().readValue(jsonStr, clazz);
        } catch (JsonParseException e) {
            logger.error(e.getMessage(), e);
        } catch (JsonMappingException e) {
            logger.error(e.getMessage(), e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
    /**
     * string --> List<Bean>...
     *
     * @param jsonStr
     * @param parametrized
     * @param parameterClasses
     * @param <T>
     * @return
     */
    public static <T> T readValue(String jsonStr, Class<?> parametrized, Class<?>... parameterClasses) {
        try {
            JavaType javaType = getInstance().getTypeFactory().constructParametricType(parametrized, parameterClasses);
            return getInstance().readValue(jsonStr, javaType);
        } catch (JsonParseException e) {
            logger.error(e.getMessage(), e);
        } catch (JsonMappingException e) {
            logger.error(e.getMessage(), e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/core/util/LocalCacheUtil.java
New file
@@ -0,0 +1,133 @@
package cn.gistack.job.admin.core.util;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * local cache tool
 *
 * @author xuxueli 2018-01-22 21:37:34
 */
public class LocalCacheUtil {
    private static ConcurrentMap<String, LocalCacheData> cacheRepository = new ConcurrentHashMap<String, LocalCacheData>();   // 类型建议用抽象父类,兼容性更好;
    private static class LocalCacheData{
        private String key;
        private Object val;
        private long timeoutTime;
        public LocalCacheData() {
        }
        public LocalCacheData(String key, Object val, long timeoutTime) {
            this.key = key;
            this.val = val;
            this.timeoutTime = timeoutTime;
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public Object getVal() {
            return val;
        }
        public void setVal(Object val) {
            this.val = val;
        }
        public long getTimeoutTime() {
            return timeoutTime;
        }
        public void setTimeoutTime(long timeoutTime) {
            this.timeoutTime = timeoutTime;
        }
    }
    /**
     * set cache
     *
     * @param key
     * @param val
     * @param cacheTime
     * @return
     */
    public static boolean set(String key, Object val, long cacheTime){
        // clean timeout cache, before set new cache (avoid cache too much)
        cleanTimeoutCache();
        // set new cache
        if (key==null || key.trim().length()==0) {
            return false;
        }
        if (val == null) {
            remove(key);
        }
        if (cacheTime <= 0) {
            remove(key);
        }
        long timeoutTime = System.currentTimeMillis() + cacheTime;
        LocalCacheData localCacheData = new LocalCacheData(key, val, timeoutTime);
        cacheRepository.put(localCacheData.getKey(), localCacheData);
        return true;
    }
    /**
     * remove cache
     *
     * @param key
     * @return
     */
    public static boolean remove(String key){
        if (key==null || key.trim().length()==0) {
            return false;
        }
        cacheRepository.remove(key);
        return true;
    }
    /**
     * get cache
     *
     * @param key
     * @return
     */
    public static Object get(String key){
        if (key==null || key.trim().length()==0) {
            return null;
        }
        LocalCacheData localCacheData = cacheRepository.get(key);
        if (localCacheData!=null && System.currentTimeMillis()<localCacheData.getTimeoutTime()) {
            return localCacheData.getVal();
        } else {
            remove(key);
            return null;
        }
    }
    /**
     * clean timeout cache
     *
     * @return
     */
    public static boolean cleanTimeoutCache(){
        if (!cacheRepository.keySet().isEmpty()) {
            for (String key: cacheRepository.keySet()) {
                LocalCacheData localCacheData = cacheRepository.get(key);
                if (localCacheData!=null && System.currentTimeMillis()>=localCacheData.getTimeoutTime()) {
                    cacheRepository.remove(key);
                }
            }
        }
        return true;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobGroupDao.java
New file
@@ -0,0 +1,26 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * Created by xuxueli on 16/9/30.
 */
@Mapper
public interface XxlJobGroupDao {
    public List<XxlJobGroup> findAll();
    public List<XxlJobGroup> findByAddressType(@Param("addressType") int addressType);
    public int save(XxlJobGroup xxlJobGroup);
    public int update(XxlJobGroup xxlJobGroup);
    public int remove(@Param("id") int id);
    public XxlJobGroup load(@Param("id") int id);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobInfoDao.java
New file
@@ -0,0 +1,49 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * job info
 * @author xuxueli 2016-1-12 18:03:45
 */
@Mapper
public interface XxlJobInfoDao {
    public List<XxlJobInfo> pageList(@Param("offset") int offset,
                                     @Param("pagesize") int pagesize,
                                     @Param("jobGroup") int jobGroup,
                                     @Param("triggerStatus") int triggerStatus,
                                     @Param("jobDesc") String jobDesc,
                                     @Param("executorHandler") String executorHandler,
                                     @Param("author") String author);
    public int pageListCount(@Param("offset") int offset,
                             @Param("pagesize") int pagesize,
                             @Param("jobGroup") int jobGroup,
                             @Param("triggerStatus") int triggerStatus,
                             @Param("jobDesc") String jobDesc,
                             @Param("executorHandler") String executorHandler,
                             @Param("author") String author);
    public int save(XxlJobInfo info);
    public XxlJobInfo loadById(@Param("id") int id);
    public int update(XxlJobInfo xxlJobInfo);
    public int delete(@Param("id") long id);
    public List<XxlJobInfo> getJobsByGroup(@Param("jobGroup") int jobGroup);
    public int findAllCount();
    public List<XxlJobInfo> scheduleJobQuery(@Param("maxNextTime") long maxNextTime, @Param("pagesize") int pagesize );
    public int scheduleUpdate(XxlJobInfo xxlJobInfo);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobLogDao.java
New file
@@ -0,0 +1,60 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * job log
 * @author xuxueli 2016-1-12 18:03:06
 */
@Mapper
public interface XxlJobLogDao {
    // exist jobId not use jobGroup, not exist use jobGroup
    public List<XxlJobLog> pageList(@Param("offset") int offset,
                                    @Param("pagesize") int pagesize,
                                    @Param("jobGroup") int jobGroup,
                                    @Param("jobId") int jobId,
                                    @Param("triggerTimeStart") Date triggerTimeStart,
                                    @Param("triggerTimeEnd") Date triggerTimeEnd,
                                    @Param("logStatus") int logStatus);
    public int pageListCount(@Param("offset") int offset,
                             @Param("pagesize") int pagesize,
                             @Param("jobGroup") int jobGroup,
                             @Param("jobId") int jobId,
                             @Param("triggerTimeStart") Date triggerTimeStart,
                             @Param("triggerTimeEnd") Date triggerTimeEnd,
                             @Param("logStatus") int logStatus);
    public XxlJobLog load(@Param("id") long id);
    public long save(XxlJobLog xxlJobLog);
    public int updateTriggerInfo(XxlJobLog xxlJobLog);
    public int updateHandleInfo(XxlJobLog xxlJobLog);
    public int delete(@Param("jobId") int jobId);
    public Map<String, Object> findLogReport(@Param("from") Date from,
                                             @Param("to") Date to);
    public List<Long> findClearLogIds(@Param("jobGroup") int jobGroup,
                                      @Param("jobId") int jobId,
                                      @Param("clearBeforeTime") Date clearBeforeTime,
                                      @Param("clearBeforeNum") int clearBeforeNum,
                                      @Param("pagesize") int pagesize);
    public int clearLog(@Param("logIds") List<Long> logIds);
    public List<Long> findFailJobLogIds(@Param("pagesize") int pagesize);
    public int updateAlarmStatus(@Param("logId") long logId,
                                 @Param("oldAlarmStatus") int oldAlarmStatus,
                                 @Param("newAlarmStatus") int newAlarmStatus);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobLogGlueDao.java
New file
@@ -0,0 +1,24 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobLogGlue;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * job log for glue
 * @author xuxueli 2016-5-19 18:04:56
 */
@Mapper
public interface XxlJobLogGlueDao {
    public int save(XxlJobLogGlue xxlJobLogGlue);
    public List<XxlJobLogGlue> findByJobId(@Param("jobId") int jobId);
    public int removeOld(@Param("jobId") int jobId, @Param("limit") int limit);
    public int deleteByJobId(@Param("jobId") int jobId);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobLogReportDao.java
New file
@@ -0,0 +1,26 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobLogReport;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
 * job log
 * @author xuxueli 2019-11-22
 */
@Mapper
public interface XxlJobLogReportDao {
    public int save(XxlJobLogReport xxlJobLogReport);
    public int update(XxlJobLogReport xxlJobLogReport);
    public List<XxlJobLogReport> queryLogReport(@Param("triggerDayFrom") Date triggerDayFrom,
                                                @Param("triggerDayTo") Date triggerDayTo);
    public XxlJobLogReport queryLogReportTotal();
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobRegistryDao.java
New file
@@ -0,0 +1,38 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobRegistry;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
 * Created by xuxueli on 16/9/30.
 */
@Mapper
public interface XxlJobRegistryDao {
    public List<Integer> findDead(@Param("timeout") int timeout,
                                  @Param("nowTime") Date nowTime);
    public int removeDead(@Param("ids") List<Integer> ids);
    public List<XxlJobRegistry> findAll(@Param("timeout") int timeout,
                                        @Param("nowTime") Date nowTime);
    public int registryUpdate(@Param("registryGroup") String registryGroup,
                              @Param("registryKey") String registryKey,
                              @Param("registryValue") String registryValue,
                              @Param("updateTime") Date updateTime);
    public int registrySave(@Param("registryGroup") String registryGroup,
                            @Param("registryKey") String registryKey,
                            @Param("registryValue") String registryValue,
                            @Param("updateTime") Date updateTime);
    public int registryDelete(@Param("registryGroup") String registryGroup,
                          @Param("registryKey") String registryKey,
                          @Param("registryValue") String registryValue);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/dao/XxlJobUserDao.java
New file
@@ -0,0 +1,31 @@
package cn.gistack.job.admin.dao;
import cn.gistack.job.admin.core.model.XxlJobUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * @author xuxueli 2019-05-04 16:44:59
 */
@Mapper
public interface XxlJobUserDao {
    public List<XxlJobUser> pageList(@Param("offset") int offset,
                                     @Param("pagesize") int pagesize,
                                     @Param("username") String username,
                                     @Param("role") int role);
    public int pageListCount(@Param("offset") int offset,
                             @Param("pagesize") int pagesize,
                             @Param("username") String username,
                             @Param("role") int role);
    public XxlJobUser loadByUserName(@Param("username") String username);
    public int save(XxlJobUser xxlJobUser);
    public int update(XxlJobUser xxlJobUser);
    public int delete(@Param("id") int id);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/LoginService.java
New file
@@ -0,0 +1,107 @@
package cn.gistack.job.admin.service;
import cn.gistack.job.admin.dao.XxlJobUserDao;
import cn.gistack.job.admin.core.model.XxlJobUser;
import cn.gistack.job.admin.core.util.CookieUtil;
import cn.gistack.job.admin.core.util.I18nUtil;
import cn.gistack.job.admin.core.util.JacksonUtil;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigInteger;
/**
 * @author xuxueli 2019-05-04 22:13:264
 */
@Configuration(proxyBeanMethods = false)
public class LoginService {
    public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY";
    @Resource
    private XxlJobUserDao xxlJobUserDao;
    private String makeToken(XxlJobUser xxlJobUser){
        String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser);
        String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16);
        return tokenHex;
    }
    private XxlJobUser parseToken(String tokenHex){
        XxlJobUser xxlJobUser = null;
        if (tokenHex != null) {
            String tokenJson = new String(new BigInteger(tokenHex, 16).toByteArray());      // username_password(md5)
            xxlJobUser = JacksonUtil.readValue(tokenJson, XxlJobUser.class);
        }
        return xxlJobUser;
    }
    public ReturnT<String> login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean ifRemember){
        // param
        if (username==null || username.trim().length()==0 || password==null || password.trim().length()==0){
            return new ReturnT<String>(500, I18nUtil.getString("login_param_empty"));
        }
        // valid passowrd
        XxlJobUser xxlJobUser = xxlJobUserDao.loadByUserName(username);
        if (xxlJobUser == null) {
            return new ReturnT<String>(500, I18nUtil.getString("login_param_unvalid"));
        }
        String passwordMd5 = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!passwordMd5.equals(xxlJobUser.getPassword())) {
            return new ReturnT<String>(500, I18nUtil.getString("login_param_unvalid"));
        }
        String loginToken = makeToken(xxlJobUser);
        // do login
        CookieUtil.set(response, LOGIN_IDENTITY_KEY, loginToken, ifRemember);
        return ReturnT.SUCCESS;
    }
    /**
     * logout
     *
     * @param request
     * @param response
     */
    public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){
        CookieUtil.remove(request, response, LOGIN_IDENTITY_KEY);
        return ReturnT.SUCCESS;
    }
    /**
     * logout
     *
     * @param request
     * @return
     */
    public XxlJobUser ifLogin(HttpServletRequest request, HttpServletResponse response){
        String cookieToken = CookieUtil.getValue(request, LOGIN_IDENTITY_KEY);
        if (cookieToken != null) {
            XxlJobUser cookieUser = null;
            try {
                cookieUser = parseToken(cookieToken);
            } catch (Exception e) {
                logout(request, response);
            }
            if (cookieUser != null) {
                XxlJobUser dbUser = xxlJobUserDao.loadByUserName(cookieUser.getUsername());
                if (dbUser != null) {
                    if (cookieUser.getPassword().equals(dbUser.getPassword())) {
                        return dbUser;
                    }
                }
            }
        }
        return null;
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/XxlJobService.java
New file
@@ -0,0 +1,86 @@
package cn.gistack.job.admin.service;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import com.xxl.job.core.biz.model.ReturnT;
import java.util.Date;
import java.util.Map;
/**
 * core job action for xxl-job
 *
 * @author xuxueli 2016-5-28 15:30:33
 */
public interface XxlJobService {
    /**
     * page list
     *
     * @param start
     * @param length
     * @param jobGroup
     * @param jobDesc
     * @param executorHandler
     * @param author
     * @return
     */
    public Map<String, Object> pageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author);
    /**
     * add job
     *
     * @param jobInfo
     * @return
     */
    public ReturnT<String> add(XxlJobInfo jobInfo);
    /**
     * update job
     *
     * @param jobInfo
     * @return
     */
    public ReturnT<String> update(XxlJobInfo jobInfo);
    /**
     * remove job
     *      *
     * @param id
     * @return
     */
    public ReturnT<String> remove(int id);
    /**
     * start job
     *
     * @param id
     * @return
     */
    public ReturnT<String> start(int id);
    /**
     * stop job
     *
     * @param id
     * @return
     */
    public ReturnT<String> stop(int id);
    /**
     * dashboard info
     *
     * @return
     */
    public Map<String,Object> dashboardInfo();
    /**
     * chart info
     *
     * @param startDate
     * @param endDate
     * @return
     */
    public ReturnT<Map<String,Object>> chartInfo(Date startDate, Date endDate);
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/impl/AdminBizImpl.java
New file
@@ -0,0 +1,171 @@
package cn.gistack.job.admin.service.impl;
import cn.gistack.job.admin.core.thread.JobTriggerPoolHelper;
import cn.gistack.job.admin.core.trigger.TriggerTypeEnum;
import cn.gistack.job.admin.dao.XxlJobGroupDao;
import cn.gistack.job.admin.dao.XxlJobInfoDao;
import cn.gistack.job.admin.dao.XxlJobLogDao;
import cn.gistack.job.admin.dao.XxlJobRegistryDao;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.model.XxlJobLog;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.Date;
import java.util.List;
/**
 * @author xuxueli 2017-07-27 21:54:20
 */
@Service
public class AdminBizImpl implements AdminBiz {
    private static Logger logger = LoggerFactory.getLogger(AdminBizImpl.class);
    @Resource
    public XxlJobLogDao xxlJobLogDao;
    @Resource
    private XxlJobInfoDao xxlJobInfoDao;
    @Resource
    private XxlJobRegistryDao xxlJobRegistryDao;
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @Override
    public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
        for (HandleCallbackParam handleCallbackParam: callbackParamList) {
            ReturnT<String> callbackResult = callback(handleCallbackParam);
            logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
                    (callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult);
        }
        return ReturnT.SUCCESS;
    }
    private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
        // valid log item
        XxlJobLog log = xxlJobLogDao.load(handleCallbackParam.getLogId());
        if (log == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
        }
        if (log.getHandleCode() > 0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback.");     // avoid repeat callback, trigger child job etc
        }
        // trigger success, to trigger child job
        String callbackMsg = null;
        if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
            XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
            if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
                callbackMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<< </span><br>";
                String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
                for (int i = 0; i < childJobIds.length; i++) {
                    int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
                    if (childJobId > 0) {
                        JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null);
                        ReturnT<String> triggerChildResult = ReturnT.SUCCESS;
                        // add msg
                        callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
                                (i+1),
                                childJobIds.length,
                                childJobIds[i],
                                (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
                                triggerChildResult.getMsg());
                    } else {
                        callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
                                (i+1),
                                childJobIds.length,
                                childJobIds[i]);
                    }
                }
            }
        }
        // handle msg
        StringBuffer handleMsg = new StringBuffer();
        if (log.getHandleMsg()!=null) {
            handleMsg.append(log.getHandleMsg()).append("<br>");
        }
        if (handleCallbackParam.getExecuteResult().getMsg() != null) {
            handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
        }
        if (callbackMsg != null) {
            handleMsg.append(callbackMsg);
        }
        // success, save log
        log.setHandleTime(new Date());
        log.setHandleCode(handleCallbackParam.getExecuteResult().getCode());
        log.setHandleMsg(handleMsg.toString());
        xxlJobLogDao.updateHandleInfo(log);
        return ReturnT.SUCCESS;
    }
    private boolean isNumeric(String str){
        try {
            int result = Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    @Override
    public ReturnT<String> registry(RegistryParam registryParam) {
        // valid
        if (!StringUtils.hasText(registryParam.getRegistryGroup())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
        }
        int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
        if (ret < 1) {
            xxlJobRegistryDao.registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
            // fresh
            freshGroupRegistryInfo(registryParam);
        }
        return ReturnT.SUCCESS;
    }
    @Override
    public ReturnT<String> registryRemove(RegistryParam registryParam) {
        // valid
        if (!StringUtils.hasText(registryParam.getRegistryGroup())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
        }
        int ret = xxlJobRegistryDao.registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
        if (ret > 0) {
            // fresh
            freshGroupRegistryInfo(registryParam);
        }
        return ReturnT.SUCCESS;
    }
    private void freshGroupRegistryInfo(RegistryParam registryParam){
        // Under consideration, prevent affecting core tables
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/java/cn/gistack/job/admin/service/impl/XxlJobServiceImpl.java
New file
@@ -0,0 +1,373 @@
package cn.gistack.job.admin.service.impl;
import cn.gistack.job.admin.core.route.ExecutorRouteStrategyEnum;
import cn.gistack.job.admin.core.thread.JobScheduleHelper;
import cn.gistack.job.admin.dao.*;
import cn.gistack.job.admin.core.model.XxlJobGroup;
import cn.gistack.job.admin.core.model.XxlJobInfo;
import cn.gistack.job.admin.core.cron.CronExpression;
import cn.gistack.job.admin.core.model.XxlJobLogReport;
import cn.gistack.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.*;
import cn.gistack.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.*;
/**
 * core job action for xxl-job
 * @author xuxueli 2016-5-28 15:30:33
 */
@Service
public class XxlJobServiceImpl implements XxlJobService {
    private static Logger logger = LoggerFactory.getLogger(XxlJobServiceImpl.class);
    @Resource
    private XxlJobGroupDao xxlJobGroupDao;
    @Resource
    private XxlJobInfoDao xxlJobInfoDao;
    @Resource
    public XxlJobLogDao xxlJobLogDao;
    @Resource
    private XxlJobLogGlueDao xxlJobLogGlueDao;
    @Resource
    private XxlJobLogReportDao xxlJobLogReportDao;
    @Override
    public Map<String, Object> pageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
        // page list
        List<XxlJobInfo> list = xxlJobInfoDao.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
        int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
        // package result
        Map<String, Object> maps = new HashMap<String, Object>();
        maps.put("recordsTotal", list_count);        // 总记录数
        maps.put("recordsFiltered", list_count);    // 过滤后的总记录数
        maps.put("data", list);                      // 分页列表
        return maps;
    }
    @Override
    public ReturnT<String> add(XxlJobInfo jobInfo) {
        // valid
        XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup());
        if (group == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")) );
        }
        if (!CronExpression.isValidExpression(jobInfo.getJobCron())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid") );
        }
        if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
        }
        if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
        }
        if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
        }
        if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
        }
        if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) );
        }
        if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+"JobHandler") );
        }
        // fix "\r" in shell
        if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource()!=null) {
            jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
        }
        // ChildJobId valid
        if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
            String[] childJobIds = jobInfo.getChildJobId().split(",");
            for (String childJobIdItem: childJobIds) {
                if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
                    XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
                    if (childJobInfo==null) {
                        return new ReturnT<String>(ReturnT.FAIL_CODE,
                                MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
                    }
                } else {
                    return new ReturnT<String>(ReturnT.FAIL_CODE,
                            MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem));
                }
            }
            // join , avoid "xxx,,"
            String temp = "";
            for (String item:childJobIds) {
                temp += item + ",";
            }
            temp = temp.substring(0, temp.length()-1);
            jobInfo.setChildJobId(temp);
        }
        // add in db
        jobInfo.setAddTime(new Date());
        jobInfo.setUpdateTime(new Date());
        jobInfo.setGlueUpdatetime(new Date());
        xxlJobInfoDao.save(jobInfo);
        if (jobInfo.getId() < 1) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) );
        }
        return new ReturnT<String>(String.valueOf(jobInfo.getId()));
    }
    private boolean isNumeric(String str){
        try {
            int result = Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    @Override
    public ReturnT<String> update(XxlJobInfo jobInfo) {
        // valid
        if (!CronExpression.isValidExpression(jobInfo.getJobCron())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid") );
        }
        if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
        }
        if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
        }
        if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
        }
        if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
        }
        // ChildJobId valid
        if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
            String[] childJobIds = jobInfo.getChildJobId().split(",");
            for (String childJobIdItem: childJobIds) {
                if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
                    XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
                    if (childJobInfo==null) {
                        return new ReturnT<String>(ReturnT.FAIL_CODE,
                                MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
                    }
                } else {
                    return new ReturnT<String>(ReturnT.FAIL_CODE,
                            MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem));
                }
            }
            // join , avoid "xxx,,"
            String temp = "";
            for (String item:childJobIds) {
                temp += item + ",";
            }
            temp = temp.substring(0, temp.length()-1);
            jobInfo.setChildJobId(temp);
        }
        // group valid
        XxlJobGroup jobGroup = xxlJobGroupDao.load(jobInfo.getJobGroup());
        if (jobGroup == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_jobgroup")+I18nUtil.getString("system_unvalid")) );
        }
        // stage job info
        XxlJobInfo exists_jobInfo = xxlJobInfoDao.loadById(jobInfo.getId());
        if (exists_jobInfo == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) );
        }
        // next trigger time (5s后生效,避开预读周期)
        long nextTriggerTime = exists_jobInfo.getTriggerNextTime();
        if (exists_jobInfo.getTriggerStatus() == 1 && !jobInfo.getJobCron().equals(exists_jobInfo.getJobCron()) ) {
            try {
                Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
                if (nextValidTime == null) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_never_fire"));
                }
                nextTriggerTime = nextValidTime.getTime();
            } catch (ParseException e) {
                logger.error(e.getMessage(), e);
                return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
            }
        }
        exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
        exists_jobInfo.setJobCron(jobInfo.getJobCron());
        exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
        exists_jobInfo.setAuthor(jobInfo.getAuthor());
        exists_jobInfo.setAlarmEmail(jobInfo.getAlarmEmail());
        exists_jobInfo.setExecutorRouteStrategy(jobInfo.getExecutorRouteStrategy());
        exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler());
        exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam());
        exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
        exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
        exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
        exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
        exists_jobInfo.setTriggerNextTime(nextTriggerTime);
        exists_jobInfo.setUpdateTime(new Date());
        xxlJobInfoDao.update(exists_jobInfo);
        return ReturnT.SUCCESS;
    }
    @Override
    public ReturnT<String> remove(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
        if (xxlJobInfo == null) {
            return ReturnT.SUCCESS;
        }
        xxlJobInfoDao.delete(id);
        xxlJobLogDao.delete(id);
        xxlJobLogGlueDao.deleteByJobId(id);
        return ReturnT.SUCCESS;
    }
    @Override
    public ReturnT<String> start(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
        // next trigger time (5s后生效,避开预读周期)
        long nextTriggerTime = 0;
        try {
            Date nextValidTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
            if (nextValidTime == null) {
                return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_never_fire"));
            }
            nextTriggerTime = nextValidTime.getTime();
        } catch (ParseException e) {
            logger.error(e.getMessage(), e);
            return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
        }
        xxlJobInfo.setTriggerStatus(1);
        xxlJobInfo.setTriggerLastTime(0);
        xxlJobInfo.setTriggerNextTime(nextTriggerTime);
        xxlJobInfo.setUpdateTime(new Date());
        xxlJobInfoDao.update(xxlJobInfo);
        return ReturnT.SUCCESS;
    }
    @Override
    public ReturnT<String> stop(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
        xxlJobInfo.setTriggerStatus(0);
        xxlJobInfo.setTriggerLastTime(0);
        xxlJobInfo.setTriggerNextTime(0);
        xxlJobInfo.setUpdateTime(new Date());
        xxlJobInfoDao.update(xxlJobInfo);
        return ReturnT.SUCCESS;
    }
    @Override
    public Map<String, Object> dashboardInfo() {
        int jobInfoCount = xxlJobInfoDao.findAllCount();
        int jobLogCount = 0;
        int jobLogSuccessCount = 0;
        XxlJobLogReport xxlJobLogReport = xxlJobLogReportDao.queryLogReportTotal();
        if (xxlJobLogReport != null) {
            jobLogCount = xxlJobLogReport.getRunningCount() + xxlJobLogReport.getSucCount() + xxlJobLogReport.getFailCount();
            jobLogSuccessCount = xxlJobLogReport.getSucCount();
        }
        // executor count
        Set<String> executorAddressSet = new HashSet<String>();
        List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
        if (groupList!=null && !groupList.isEmpty()) {
            for (XxlJobGroup group: groupList) {
                if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
                    executorAddressSet.addAll(group.getRegistryList());
                }
            }
        }
        int executorCount = executorAddressSet.size();
        Map<String, Object> dashboardMap = new HashMap<String, Object>();
        dashboardMap.put("jobInfoCount", jobInfoCount);
        dashboardMap.put("jobLogCount", jobLogCount);
        dashboardMap.put("jobLogSuccessCount", jobLogSuccessCount);
        dashboardMap.put("executorCount", executorCount);
        return dashboardMap;
    }
    @Override
    public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
        // process
        List<String> triggerDayList = new ArrayList<String>();
        List<Integer> triggerDayCountRunningList = new ArrayList<Integer>();
        List<Integer> triggerDayCountSucList = new ArrayList<Integer>();
        List<Integer> triggerDayCountFailList = new ArrayList<Integer>();
        int triggerCountRunningTotal = 0;
        int triggerCountSucTotal = 0;
        int triggerCountFailTotal = 0;
        List<XxlJobLogReport> logReportList = xxlJobLogReportDao.queryLogReport(startDate, endDate);
        if (logReportList!=null && logReportList.size()>0) {
            for (XxlJobLogReport item: logReportList) {
                String day = DateUtil.formatDate(item.getTriggerDay());
                int triggerDayCountRunning = item.getRunningCount();
                int triggerDayCountSuc = item.getSucCount();
                int triggerDayCountFail = item.getFailCount();
                triggerDayList.add(day);
                triggerDayCountRunningList.add(triggerDayCountRunning);
                triggerDayCountSucList.add(triggerDayCountSuc);
                triggerDayCountFailList.add(triggerDayCountFail);
                triggerCountRunningTotal += triggerDayCountRunning;
                triggerCountSucTotal += triggerDayCountSuc;
                triggerCountFailTotal += triggerDayCountFail;
            }
        } else {
            for (int i = -6; i <= 0; i++) {
                triggerDayList.add(DateUtil.formatDate(DateUtil.addDays(new Date(), i)));
                triggerDayCountRunningList.add(0);
                triggerDayCountSucList.add(0);
                triggerDayCountFailList.add(0);
            }
        }
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("triggerDayList", triggerDayList);
        result.put("triggerDayCountRunningList", triggerDayCountRunningList);
        result.put("triggerDayCountSucList", triggerDayCountSucList);
        result.put("triggerDayCountFailList", triggerDayCountFailList);
        result.put("triggerCountRunningTotal", triggerCountRunningTotal);
        result.put("triggerCountSucTotal", triggerCountSucTotal);
        result.put("triggerCountFailTotal", triggerCountFailTotal);
        return new ReturnT<Map<String, Object>>(result);
    }
}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application-dev.yml
New file
@@ -0,0 +1,7 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.job.dev.url}
    username: ${blade.datasource.job.dev.username}
    password: ${blade.datasource.job.dev.password}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,7 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.job.prod.url}
    username: ${blade.datasource.job.prod.username}
    password: ${blade.datasource.job.prod.password}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application-test.yml
New file
@@ -0,0 +1,6 @@
#数据源配置
spring:
  datasource:
    url: ${blade.datasource.job.test.url}
    username: ${blade.datasource.job.test.username}
    password: ${blade.datasource.job.test.password}
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/application.yml
New file
@@ -0,0 +1,61 @@
server:
  port: 7009
  servlet:
    context-path: /xxl-job-admin
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator
  freemarker:
    charset: UTF-8
    request-context-attribute: request
    settings:
      number_format: 0.##########
    suffix: .ftl
    templateLoaderPath: classpath:/templates/
  mail:
    host: smtp.qq.com
    password: xxx
    port: 25
    properties:
      mail:
        smtp:
          auth: true
          socketFactory:
            class: javax.net.ssl.SSLSocketFactory
          starttls:
            enable: true
            required: true
    username: xxx@qq.com
  mvc:
    servlet:
      load-on-startup: 0
    static-path-pattern: /static/**
  resources:
    static-locations: classpath:/static/
management:
  health:
    mail:
      enabled: false
  server:
    servlet:
      context-path: /actuator
mybatis:
  mapper-locations: classpath:/mybatis-mapper/*Mapper.xml
xxl:
  job:
    accessToken: ''
    i18n: ''
    logretentiondays: 30
    triggerpool:
      fast:
        max: 200
      slow:
        max: 100
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/i18n/message.properties
New file
@@ -0,0 +1,262 @@
admin_name=任务调度中心
admin_name_full=分布式任务调度平台XXL-JOB
admin_version=2.1.2
admin_i18n=
## system
system_tips=系统提示
system_ok=确定
system_close=关闭
system_save=保存
system_cancel=取消
system_search=搜索
system_status=状态
system_opt=操作
system_please_input=请输入
system_please_choose=请选择
system_success=成功
system_fail=失败
system_add_suc=新增成功
system_add_fail=新增失败
system_update_suc=更新成功
system_update_fail=更新失败
system_all=全部
system_api_error=接口异常
system_show=查看
system_empty=无
system_opt_suc=操作成功
system_opt_fail=操作失败
system_opt_edit=编辑
system_opt_del=删除
system_unvalid=非法
system_not_found=不存在
system_nav=导航
system_digits=整数
system_lengh_limit=长度限制
system_permission_limit=权限拦截
system_welcome=欢迎
## daterangepicker
daterangepicker_ranges_recent_hour=最近一小时
daterangepicker_ranges_today=今日
daterangepicker_ranges_yesterday=昨日
daterangepicker_ranges_this_month=本月
daterangepicker_ranges_last_month=上个月
daterangepicker_ranges_recent_week=最近一周
daterangepicker_ranges_recent_month=最近一月
daterangepicker_custom_name=自定义
daterangepicker_custom_starttime=起始时间
daterangepicker_custom_endtime=结束时间
daterangepicker_custom_daysofweek=日,一,二,三,四,五,六
daterangepicker_custom_monthnames=一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月
## dataTable
dataTable_sProcessing=处理中...
dataTable_sLengthMenu=每页 _MENU_ 条记录
dataTable_sZeroRecords=没有匹配结果
dataTable_sInfo=第 _PAGE_ 页 ( 总共 _PAGES_ 页,_TOTAL_ 条记录 )
dataTable_sInfoEmpty=无记录
dataTable_sInfoFiltered=(由 _MAX_ 项结果过滤)
dataTable_sSearch=搜索
dataTable_sEmptyTable=表中数据为空
dataTable_sLoadingRecords=载入中...
dataTable_sFirst=首页
dataTable_sPrevious=上页
dataTable_sNext=下页
dataTable_sLast=末页
dataTable_sSortAscending=: 以升序排列此列
dataTable_sSortDescending=: 以降序排列此列
## login
login_btn=登录
login_remember_me=记住密码
login_username_placeholder=请输入登录账号
login_password_placeholder=请输入登录密码
login_username_empty=请输入登录账号
login_username_lt_4=登录账号不应低于4位
login_password_empty=请输入登录密码
login_password_lt_4=登录密码不应低于4位
login_success=登录成功
login_fail=登录失败
login_param_empty=账号或密码为空
login_param_unvalid=账号或密码错误
## logout
logout_btn=注销
logout_confirm=确认注销登录?
logout_success=注销成功
logout_fail=注销失败
## change pwd
change_pwd=修改密码
change_pwd_suc_to_logout=修改密码成功,即将注销登陆
change_pwd_field_newpwd=新密码
## dashboard
job_dashboard_name=运行报表
job_dashboard_job_num=任务数量
job_dashboard_job_num_tip=调度中心运行的任务数量
job_dashboard_trigger_num=调度次数
job_dashboard_trigger_num_tip=调度中心触发的调度次数
job_dashboard_jobgroup_num=执行器数量
job_dashboard_jobgroup_num_tip=调度中心在线的执行器机器数量
job_dashboard_report=调度报表
job_dashboard_report_loaddata_fail=调度报表数据加载异常
job_dashboard_date_report=日期分布图
job_dashboard_rate_report=成功比例图
## job info
jobinfo_name=任务管理
jobinfo_job=任务
jobinfo_field_add=新增
jobinfo_field_update=更新任务
jobinfo_field_id=任务ID
jobinfo_field_jobgroup=执行器
jobinfo_field_jobdesc=任务描述
jobinfo_field_gluetype=运行模式
jobinfo_field_executorparam=任务参数
jobinfo_field_cron_unvalid=Cron格式非法
jobinfo_field_cron_never_fire=Cron非法,永远不会触发
jobinfo_field_author=负责人
jobinfo_field_timeout=任务超时时间
jobinfo_field_alarmemail=报警邮件
jobinfo_field_alarmemail_placeholder=请输入报警邮件,多个邮件地址则逗号分隔
jobinfo_field_executorRouteStrategy=路由策略
jobinfo_field_childJobId=子任务ID
jobinfo_field_childJobId_placeholder=请输入子任务的任务ID,如存在多个则逗号分隔
jobinfo_field_executorBlockStrategy=阻塞处理策略
jobinfo_field_executorFailRetryCount=失败重试次数
jobinfo_field_executorFailRetryCount_placeholder=失败重试次数,大于零时生效
jobinfo_script_location=脚本位置
jobinfo_shard_index=分片序号
jobinfo_shard_total=分片总数
jobinfo_opt_stop=停止
jobinfo_opt_start=启动
jobinfo_opt_log=查询日志
jobinfo_opt_run=执行一次
jobinfo_opt_registryinfo=注册节点
jobinfo_opt_next_time=下次执行时间
jobinfo_glue_remark=源码备注
jobinfo_glue_remark_limit=源码备注长度限制为4~100
jobinfo_glue_rollback=版本回溯
jobinfo_glue_jobid_unvalid=任务ID非法
jobinfo_glue_gluetype_unvalid=该任务非GLUE模式
jobinfo_field_executorTimeout_placeholder=任务超时时间,单位秒,大于零时生效
## job log
joblog_name=调度日志
joblog_status=状态
joblog_status_all=全部
joblog_status_suc=成功
joblog_status_fail=失败
joblog_status_running=进行中
joblog_field_triggerTime=调度时间
joblog_field_triggerCode=调度结果
joblog_field_triggerMsg=调度备注
joblog_field_handleTime=执行时间
joblog_field_handleCode=执行结果
joblog_field_handleMsg=执行备注
joblog_field_executorAddress=执行器地址
joblog_clean=清理
joblog_clean_log=日志清理
joblog_clean_type=清理方式
joblog_clean_type_1=清理一个月之前日志数据
joblog_clean_type_2=清理三个月之前日志数据
joblog_clean_type_3=清理六个月之前日志数据
joblog_clean_type_4=清理一年之前日志数据
joblog_clean_type_5=清理一千条以前日志数据
joblog_clean_type_6=清理一万条以前日志数据
joblog_clean_type_7=清理三万条以前日志数据
joblog_clean_type_8=清理十万条以前日志数据
joblog_clean_type_9=清理所有日志数据
joblog_clean_type_unvalid=清理类型参数异常
joblog_handleCode_200=成功
joblog_handleCode_500=失败
joblog_handleCode_502=失败(超时)
joblog_kill_log=终止任务
joblog_kill_log_limit=调度失败,无法终止日志
joblog_kill_log_byman=人为操作主动终止
joblog_rolling_log=执行日志
joblog_rolling_log_refresh=刷新
joblog_rolling_log_triggerfail=任务发起调度失败,无法查看执行日志
joblog_rolling_log_failoften=终止请求Rolling日志,请求失败次数超上限,可刷新页面重新加载日志
joblog_logid_unvalid=日志ID非法
## job group
jobgroup_name=执行器管理
jobgroup_list=执行器列表
jobgroup_add=新增执行器
jobgroup_edit=编辑执行器
jobgroup_del=删除执行器
jobgroup_field_order=排序
jobgroup_field_title=名称
jobgroup_field_addressType=注册方式
jobgroup_field_addressType_0=自动注册
jobgroup_field_addressType_1=手动录入
jobgroup_field_addressType_limit=手动录入注册方式,机器地址不可为空
jobgroup_field_registryList=机器地址
jobgroup_field_registryList_unvalid=机器地址格式非法
jobgroup_field_registryList_placeholder=请输入执行器地址列表,多地址逗号分隔
jobgroup_field_appName_limit=限制以小写字母开头,由小写字母、数字和中划线组成
jobgroup_field_appName_length=AppName长度限制为4~64
jobgroup_field_title_length=名称长度限制为4~12
jobgroup_field_order_digits=请输入整数
jobgroup_field_orderrange=取值范围为1~1000
jobgroup_del_limit_0=拒绝删除,该执行器使用中
jobgroup_del_limit_1=拒绝删除, 系统至少保留一个执行器
jobgroup_empty=不存在有效执行器,请联系管理员
## job conf
jobconf_block_SERIAL_EXECUTION=单机串行
jobconf_block_DISCARD_LATER=丢弃后续调度
jobconf_block_COVER_EARLY=覆盖之前调度
jobconf_route_first=第一个
jobconf_route_last=最后一个
jobconf_route_round=轮询
jobconf_route_random=随机
jobconf_route_consistenthash=一致性HASH
jobconf_route_lfu=最不经常使用
jobconf_route_lru=最近最久未使用
jobconf_route_failover=故障转移
jobconf_route_busyover=忙碌转移
jobconf_route_shard=分片广播
jobconf_idleBeat=空闲检测
jobconf_beat=心跳检测
jobconf_monitor=任务调度中心监控报警
jobconf_monitor_detail=监控告警明细
jobconf_monitor_alarm_title=告警类型
jobconf_monitor_alarm_type=调度失败
jobconf_monitor_alarm_content=告警内容
jobconf_trigger_admin_adress=调度机器
jobconf_trigger_exe_regtype=执行器-注册方式
jobconf_trigger_exe_regaddress=执行器-地址列表
jobconf_trigger_address_empty=调度失败:执行器地址为空
jobconf_trigger_run=触发调度
jobconf_trigger_child_run=触发子任务
jobconf_callback_child_msg1={0}/{1} [任务ID={2}], 触发{3}, 触发备注: {4} <br>
jobconf_callback_child_msg2={0}/{1} [任务ID={2}], 触发失败, 触发备注: 任务ID格式错误 <br>
jobconf_trigger_type=任务触发类型
jobconf_trigger_type_cron=Cron触发
jobconf_trigger_type_manual=手动触发
jobconf_trigger_type_parent=父任务触发
jobconf_trigger_type_api=API触发
jobconf_trigger_type_retry=失败重试触发
## user
user_manage=用户管理
user_username=账号
user_password=密码
user_role=角色
user_role_admin=管理员
user_role_normal=普通用户
user_permission=权限
user_add=新增用户
user_update=更新用户
user_username_repeat=账号重复
user_username_valid=限制以小写字母开头,由小写字母、数字组成
user_password_update_placeholder=请输入新密码,为空则不更新密码
user_update_loginuser_limit=禁止操作当前登录账号
## help
job_help=使用教程
job_help_document=官方文档
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/i18n/message_en.properties
New file
@@ -0,0 +1,262 @@
admin_name=Scheduling Center
admin_name_full=Distributed Task Scheduling Platform XXL-JOB
admin_version=2.1.2
admin_i18n=en
## system
system_tips=System message
system_ok=Confirm
system_close=Close
system_save=Save
system_cancel=Cancel
system_search=Search
system_status=Status
system_opt=Operate
system_please_input=please input
system_please_choose=please choose
system_success=success
system_fail=fail
system_add_suc=add success
system_add_fail=add fail
system_update_suc=update success
system_update_fail=update fail
system_all=All
system_api_error=net error
system_show=Show
system_empty=Empty
system_opt_suc=operate success
system_opt_fail=operate fail
system_opt_edit=Edit
system_opt_del=Delete
system_unvalid=illegal
system_not_found=not exist
system_nav=Navigation
system_digits=digits
system_lengh_limit=Length limit
system_permission_limit=Permission limit
system_welcome=Welcome
## daterangepicker
daterangepicker_ranges_recent_hour=recent one hour
daterangepicker_ranges_today=today
daterangepicker_ranges_yesterday=yesterday
daterangepicker_ranges_this_month=this month
daterangepicker_ranges_last_month=last month
daterangepicker_ranges_recent_week=recent one week
daterangepicker_ranges_recent_month=recent one month
daterangepicker_custom_name=custom
daterangepicker_custom_starttime=start time
daterangepicker_custom_endtime=end time
daterangepicker_custom_daysofweek=Sun,Mon,Tue,Wed,Thu,Fri,Sat
daterangepicker_custom_monthnames=Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
## dataTable
dataTable_sProcessing=processing...
dataTable_sLengthMenu= _MENU_ records per page
dataTable_sZeroRecords=No matching results
dataTable_sInfo=page _PAGE_  ( Total _PAGES_ pages,_TOTAL_ records )
dataTable_sInfoEmpty=No Record
dataTable_sInfoFiltered=(Filtered by _MAX_ results)
dataTable_sSearch=Search
dataTable_sEmptyTable=Table data is empty
dataTable_sLoadingRecords=Loading...
dataTable_sFirst=FIRST PAGE
dataTable_sPrevious=Previous Page
dataTable_sNext=Next Page
dataTable_sLast=LAST PAGE
dataTable_sSortAscending=: Rank this column in ascending order
dataTable_sSortDescending=: Rank this column in descending order
## login
login_btn=Login
login_remember_me=Remember Me
login_username_placeholder=Please enter username
login_password_placeholder=Please enter password
login_username_empty=Please enter username
login_username_lt_4=Username length should not be less than 4
login_password_empty=Please enter password
login_password_lt_4=Password length should not be less than 4
login_success=Login success
login_fail=Login fail
login_param_empty=Username or password is empty
login_param_unvalid=Username or password error
## logout
logout_btn=Logout
logout_confirm=Confirm logout?
logout_success=Logout success
logout_fail=Logout fail
## change pwd
change_pwd=Change password
change_pwd_suc_to_logout=Change password successful, about to log out login
change_pwd_field_newpwd=new password
## dashboard
job_dashboard_name=Run report
job_dashboard_job_num=Job number
job_dashboard_job_num_tip=The number of tasks running in the scheduling center
job_dashboard_trigger_num=trigger number
job_dashboard_trigger_num_tip=The number of trigger record scheduled by the scheduling center
job_dashboard_jobgroup_num=Executor number
job_dashboard_jobgroup_num_tip=The number of online executor machines perceived by the scheduling center
job_dashboard_report=Scheduling report
job_dashboard_report_loaddata_fail=Scheduling report load data error
job_dashboard_date_report=Date distribution
job_dashboard_rate_report=Percentage distribution
## job info
jobinfo_name=Job Manage
jobinfo_job=Job
jobinfo_field_add=Add Job
jobinfo_field_update=Edit Job
jobinfo_field_id=Job ID
jobinfo_field_jobgroup=Executor
jobinfo_field_jobdesc=Job description
jobinfo_field_timeout=Job timeout period
jobinfo_field_gluetype=GLUE Type
jobinfo_field_executorparam=Param
jobinfo_field_cron_unvalid=The Cron is illegal
jobinfo_field_cron_never_fire=The Cron will never fire
jobinfo_field_author=Author
jobinfo_field_alarmemail=Alarm email
jobinfo_field_alarmemail_placeholder=Please enter alarm mail, if there are more than one comma separated
jobinfo_field_executorRouteStrategy=Route Strategy
jobinfo_field_childJobId=Child Job ID
jobinfo_field_childJobId_placeholder=Please enter the Child job ID, if there are more than one comma separated
jobinfo_field_executorBlockStrategy=Block Strategy
jobinfo_field_executorFailRetryCount=Fail Retry Count
jobinfo_field_executorFailRetryCount_placeholder=Fail Retry Count. effect if greater than zero
jobinfo_script_location=Script location
jobinfo_shard_index=Shard index
jobinfo_shard_total=Shard total
jobinfo_opt_stop=Stop
jobinfo_opt_start=Start
jobinfo_opt_log=Query Log
jobinfo_opt_run=Run Once
jobinfo_opt_registryinfo=Registry Info
jobinfo_opt_next_time=Next trigger time
jobinfo_glue_remark=Resource Remark
jobinfo_glue_remark_limit=Resource Remark length is limited to 4~100
jobinfo_glue_rollback=Version Backtrack
jobinfo_glue_jobid_unvalid=Job ID is illegal
jobinfo_glue_gluetype_unvalid=The job is not GLUE Type
jobinfo_field_executorTimeout_placeholder=Job Timeout period,in seconds. effect if greater than zero
## job log
joblog_name=Trigger Log
joblog_status=Status
joblog_status_all=All
joblog_status_suc=Success
joblog_status_fail=Fail
joblog_status_running=Running
joblog_field_triggerTime=Trigger Time
joblog_field_triggerCode=Trigger Result
joblog_field_triggerMsg=Trigger Msg
joblog_field_handleTime=Handle Time
joblog_field_handleCode=Handle Result
joblog_field_handleMsg=Trigger Msg
joblog_field_executorAddress=Executor Address
joblog_clean=Clean
joblog_clean_log=Clean Log
joblog_clean_type=Clean Type
joblog_clean_type_1=Clean up log data a month ago
joblog_clean_type_2=Clean up log data three month ago
joblog_clean_type_3=Clean up log data six month ago
joblog_clean_type_4=Clean up log data a year ago
joblog_clean_type_5=Clean up log data a thousand record ago
joblog_clean_type_6=Clean up log data ten thousand record ago
joblog_clean_type_7=Clean up log data thirty thousand record ago
joblog_clean_type_8=Clean up log data hundred thousand record ago
joblog_clean_type_9=Clean up all log data
joblog_clean_type_unvalid=Clean type is illegal
joblog_handleCode_200=Success
joblog_handleCode_500=Fail
joblog_handleCode_502=Timeout
joblog_kill_log=Kill Job
joblog_kill_log_limit=Trigger Fail, can not kill job
joblog_kill_log_byman=Manual operation to active kill job
joblog_rolling_log=Rolling log
joblog_rolling_log_refresh=Refresh
joblog_rolling_log_triggerfail=The job trigger fail, can not view the rolling log
joblog_rolling_log_failoften=The request for the Rolling log is terminated, the number of failed requests exceeds the limit, Reload the log on the refresh page
joblog_logid_unvalid=Log ID is illegal
## job group
jobgroup_name=Executor Manage
jobgroup_list=Executor List
jobgroup_add=Add Executor
jobgroup_edit=Edit Executor
jobgroup_del=Delete Executor
jobgroup_field_order=Order
jobgroup_field_title=Title
jobgroup_field_addressType=Registry Type
jobgroup_field_addressType_0=Automatic registration
jobgroup_field_addressType_1=Manual registration
jobgroup_field_addressType_limit=Manually registration type, the machine address must not be empty
jobgroup_field_registryList=machine address
jobgroup_field_registryList_unvalid=registry machine address is illegal
jobgroup_field_registryList_placeholder=Please enter the machine address, if there are more than one comma separated
jobgroup_field_appName_limit=Limit the beginning of a lowercase letter, consists of lowercase letters、number and hyphen.
jobgroup_field_appName_length=AppName length is limited to 4~64
jobgroup_field_title_length=Title length is limited to 4~12
jobgroup_field_order_digits=Please enter a positive integer
jobgroup_field_orderrange=Order is limited to 1~1000
jobgroup_del_limit_0=Refuse to delete, the executor is being used
jobgroup_del_limit_1=Refuses to delete, the system retains at least one executor
jobgroup_empty=There is no valid executor. Please contact the administrator
## job conf
jobconf_block_SERIAL_EXECUTION=Serial execution
jobconf_block_DISCARD_LATER=Discard Later
jobconf_block_COVER_EARLY=Cover Early
jobconf_route_first=First
jobconf_route_last=Last
jobconf_route_round=Round
jobconf_route_random=Random
jobconf_route_consistenthash=Consistent Hash
jobconf_route_lfu=Least Frequently Used
jobconf_route_lru=Least Recently Used
jobconf_route_failover=Failover
jobconf_route_busyover=Busyover
jobconf_route_shard=Sharding Broadcast
jobconf_idleBeat=Idle check
jobconf_beat=Heartbeats
jobconf_monitor=Task Scheduling Center monitor alarm
jobconf_monitor_detail=monitor alarm details
jobconf_monitor_alarm_title=Alarm Type
jobconf_monitor_alarm_type=Trigger Fail
jobconf_monitor_alarm_content=Alarm Content
jobconf_trigger_admin_adress=Trigger machine address
jobconf_trigger_exe_regtype=Execotor-Registry Type
jobconf_trigger_exe_regaddress=Execotor-Registry Address
jobconf_trigger_address_empty=Trigger Fail:registry address is empty
jobconf_trigger_run=Trigger Job
jobconf_trigger_child_run=Trigger child job
jobconf_callback_child_msg1={0}/{1} [Job ID={2}], Trigger {3}, Trigger msg: {4} <br>
jobconf_callback_child_msg2={0}/{1} [Job ID={2}], Trigger Fail, Trigger msg: Job ID is illegal <br>
jobconf_trigger_type=Job trigger type
jobconf_trigger_type_cron=Cron trigger
jobconf_trigger_type_manual=Manual trigger
jobconf_trigger_type_parent=Parent job trigger
jobconf_trigger_type_api=Api trigger
jobconf_trigger_type_retry=Fail retry trigger
## user
user_manage=User Manage
user_username=Username
user_password=Password
user_role=Role
user_role_admin=Admin User
user_role_normal=Normal User
user_permission=Permission
user_add=Add User
user_update=Edit User
user_username_repeat=Username Repeat
user_username_valid=Restrictions start with a lowercase letter and consist of lowercase letters and Numbers
user_password_update_placeholder=Please input password, empty means not update
user_update_loginuser_limit=Operation of current login account is not allowed
## help
job_help=Tutorial
job_help_document=Official Document
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/logback.xml
New file
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="../data/applogs/xxl-job/xxl-job-admin.log"/>
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </root>
</configuration>
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
New file
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.job.admin.dao.XxlJobGroupDao">
    <resultMap id="XxlJobGroup" type="cn.gistack.job.admin.core.model.XxlJobGroup" >
        <result column="id" property="id" />
        <result column="app_name" property="appName" />
        <result column="title" property="title" />
        <result column="order" property="order" />
        <result column="address_type" property="addressType" />
        <result column="address_list" property="addressList" />
    </resultMap>
    <sql id="Base_Column_List">
        t.id,
        t.app_name,
        t.title,
        t.`order`,
        t.address_type,
        t.address_list
    </sql>
    <select id="findAll" resultMap="XxlJobGroup">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_group AS t
        ORDER BY t.order ASC
    </select>
    <select id="findByAddressType" parameterType="java.lang.Integer" resultMap="XxlJobGroup">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_group AS t
        WHERE t.address_type = #{addressType}
        ORDER BY t.order ASC
    </select>
    <insert id="save" parameterType="cn.gistack.job.admin.core.model.XxlJobGroup" useGeneratedKeys="true" keyProperty="id" >
        INSERT INTO xxl_job_group ( `app_name`, `title`, `order`, `address_type`, `address_list`)
        values ( #{appName}, #{title}, #{order}, #{addressType}, #{addressList});
    </insert>
    <update id="update" parameterType="cn.gistack.job.admin.core.model.XxlJobGroup" >
        UPDATE xxl_job_group
        SET `app_name` = #{appName},
            `title` = #{title},
            `order` = #{order},
            `address_type` = #{addressType},
            `address_list` = #{addressList}
        WHERE id = #{id}
    </update>
    <delete id="remove" parameterType="java.lang.Integer" >
        DELETE FROM xxl_job_group
        WHERE id = #{id}
    </delete>
    <select id="load" parameterType="java.lang.Integer" resultMap="XxlJobGroup">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_group AS t
        WHERE t.id = #{id}
    </select>
</mapper>
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
New file
@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.job.admin.dao.XxlJobInfoDao">
    <resultMap id="XxlJobInfo" type="cn.gistack.job.admin.core.model.XxlJobInfo" >
        <result column="id" property="id" />
        <result column="job_group" property="jobGroup" />
        <result column="job_cron" property="jobCron" />
        <result column="job_desc" property="jobDesc" />
        <result column="add_time" property="addTime" />
        <result column="update_time" property="updateTime" />
        <result column="author" property="author" />
        <result column="alarm_email" property="alarmEmail" />
        <result column="executor_route_strategy" property="executorRouteStrategy" />
        <result column="executor_handler" property="executorHandler" />
        <result column="executor_param" property="executorParam" />
        <result column="executor_block_strategy" property="executorBlockStrategy" />
        <result column="executor_timeout" property="executorTimeout" />
        <result column="executor_fail_retry_count" property="executorFailRetryCount" />
        <result column="glue_type" property="glueType" />
        <result column="glue_source" property="glueSource" />
        <result column="glue_remark" property="glueRemark" />
        <result column="glue_updatetime" property="glueUpdatetime" />
        <result column="child_jobid" property="childJobId" />
        <result column="trigger_status" property="triggerStatus" />
        <result column="trigger_last_time" property="triggerLastTime" />
        <result column="trigger_next_time" property="triggerNextTime" />
    </resultMap>
    <sql id="Base_Column_List">
        t.id,
        t.job_group,
        t.job_cron,
        t.job_desc,
        t.add_time,
        t.update_time,
        t.author,
        t.alarm_email,
        t.executor_route_strategy,
        t.executor_handler,
        t.executor_param,
        t.executor_block_strategy,
        t.executor_timeout,
        t.executor_fail_retry_count,
        t.glue_type,
        t.glue_source,
        t.glue_remark,
        t.glue_updatetime,
        t.child_jobid,
        t.trigger_status,
        t.trigger_last_time,
        t.trigger_next_time
    </sql>
    <select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_info AS t
        <trim prefix="WHERE" prefixOverrides="AND | OR" >
            <if test="jobGroup gt 0">
                AND t.job_group = #{jobGroup}
            </if>
            <if test="triggerStatus gte 0">
                AND t.trigger_status = #{triggerStatus}
            </if>
            <if test="jobDesc != null and jobDesc != ''">
                AND t.job_desc like CONCAT(CONCAT('%', #{jobDesc}), '%')
            </if>
            <if test="executorHandler != null and executorHandler != ''">
                AND t.executor_handler like CONCAT(CONCAT('%', #{executorHandler}), '%')
            </if>
            <if test="author != null and author != ''">
                AND t.author like CONCAT(CONCAT('%', #{author}), '%')
            </if>
        </trim>
        ORDER BY id DESC
        LIMIT #{offset}, #{pagesize}
    </select>
    <select id="pageListCount" parameterType="java.util.HashMap" resultType="int">
        SELECT count(1)
        FROM xxl_job_info AS t
        <trim prefix="WHERE" prefixOverrides="AND | OR" >
            <if test="jobGroup gt 0">
                AND t.job_group = #{jobGroup}
            </if>
            <if test="triggerStatus gte 0">
                AND t.trigger_status = #{triggerStatus}
            </if>
            <if test="jobDesc != null and jobDesc != ''">
                AND t.job_desc like CONCAT(CONCAT('%', #{jobDesc}), '%')
            </if>
            <if test="executorHandler != null and executorHandler != ''">
                AND t.executor_handler like CONCAT(CONCAT('%', #{executorHandler}), '%')
            </if>
            <if test="author != null and author != ''">
                AND t.author like CONCAT(CONCAT('%', #{author}), '%')
            </if>
        </trim>
    </select>
    <insert id="save" parameterType="cn.gistack.job.admin.core.model.XxlJobInfo" useGeneratedKeys="true" keyProperty="id" >
        INSERT INTO xxl_job_info (
            job_group,
            job_cron,
            job_desc,
            add_time,
            update_time,
            author,
            alarm_email,
            executor_route_strategy,
            executor_handler,
            executor_param,
            executor_block_strategy,
            executor_timeout,
            executor_fail_retry_count,
            glue_type,
            glue_source,
            glue_remark,
            glue_updatetime,
            child_jobid,
            trigger_status,
            trigger_last_time,
            trigger_next_time
        ) VALUES (
            #{jobGroup},
            #{jobCron},
            #{jobDesc},
            #{addTime},
            #{updateTime},
            #{author},
            #{alarmEmail},
            #{executorRouteStrategy},
            #{executorHandler},
            #{executorParam},
            #{executorBlockStrategy},
            #{executorTimeout},
            #{executorFailRetryCount},
            #{glueType},
            #{glueSource},
            #{glueRemark},
            #{glueUpdatetime},
            #{childJobId},
            #{triggerStatus},
            #{triggerLastTime},
            #{triggerNextTime}
        );
        <!--<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
            SELECT LAST_INSERT_ID()
            /*SELECT @@IDENTITY AS id*/
        </selectKey>-->
    </insert>
    <select id="loadById" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_info AS t
        WHERE t.id = #{id}
    </select>
    <update id="update" parameterType="cn.gistack.job.admin.core.model.XxlJobInfo" >
        UPDATE xxl_job_info
        SET
            job_group = #{jobGroup},
            job_cron = #{jobCron},
            job_desc = #{jobDesc},
            update_time = #{updateTime},
            author = #{author},
            alarm_email = #{alarmEmail},
            executor_route_strategy = #{executorRouteStrategy},
            executor_handler = #{executorHandler},
            executor_param = #{executorParam},
            executor_block_strategy = #{executorBlockStrategy},
            executor_timeout = ${executorTimeout},
            executor_fail_retry_count = ${executorFailRetryCount},
            glue_type = #{glueType},
            glue_source = #{glueSource},
            glue_remark = #{glueRemark},
            glue_updatetime = #{glueUpdatetime},
            child_jobid = #{childJobId},
            trigger_status = #{triggerStatus},
            trigger_last_time = #{triggerLastTime},
            trigger_next_time = #{triggerNextTime}
        WHERE id = #{id}
    </update>
    <delete id="delete" parameterType="java.util.HashMap">
        DELETE
        FROM xxl_job_info
        WHERE id = #{id}
    </delete>
    <select id="getJobsByGroup" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_info AS t
        WHERE t.job_group = #{jobGroup}
    </select>
    <select id="findAllCount" resultType="int">
        SELECT count(1)
        FROM xxl_job_info
    </select>
    <select id="scheduleJobQuery" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_info AS t
        WHERE t.trigger_status = 1
            and t.trigger_next_time <![CDATA[ <= ]]> #{maxNextTime}
        ORDER BY id ASC
        LIMIT #{pagesize}
    </select>
    <update id="scheduleUpdate" parameterType="cn.gistack.job.admin.core.model.XxlJobInfo"  >
        UPDATE xxl_job_info
        SET
            trigger_last_time = #{triggerLastTime},
            trigger_next_time = #{triggerNextTime},
            trigger_status = #{triggerStatus}
        WHERE id = #{id}
    </update>
</mapper>
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml
New file
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.job.admin.dao.XxlJobLogGlueDao">
    <resultMap id="XxlJobLogGlue" type="cn.gistack.job.admin.core.model.XxlJobLogGlue" >
        <result column="id" property="id" />
        <result column="job_id" property="jobId" />
        <result column="glue_type" property="glueType" />
        <result column="glue_source" property="glueSource" />
        <result column="glue_remark" property="glueRemark" />
        <result column="add_time" property="addTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>
    <sql id="Base_Column_List">
        t.id,
        t.job_id,
        t.glue_type,
        t.glue_source,
        t.glue_remark,
        t.add_time,
        t.update_time
    </sql>
    <insert id="save" parameterType="cn.gistack.job.admin.core.model.XxlJobLogGlue" useGeneratedKeys="true" keyProperty="id" >
        INSERT INTO xxl_job_logglue (
            `job_id`,
            `glue_type`,
            `glue_source`,
            `glue_remark`,
            `add_time`,
            `update_time`
        ) VALUES (
            #{jobId},
            #{glueType},
            #{glueSource},
            #{glueRemark},
            #{addTime},
            #{updateTime}
        );
        <!--<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
            SELECT LAST_INSERT_ID()
        </selectKey>-->
    </insert>
    <select id="findByJobId" parameterType="java.lang.Integer" resultMap="XxlJobLogGlue">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_logglue AS t
        WHERE t.job_id = #{jobId}
        ORDER BY id DESC
    </select>
    <delete id="removeOld" >
        DELETE FROM xxl_job_logglue
        WHERE id NOT in(
            SELECT id FROM(
                SELECT id FROM xxl_job_logglue
                WHERE `job_id` = #{jobId}
                ORDER BY update_time desc
                LIMIT 0, #{limit}
            ) t1
        ) AND `job_id` = #{jobId}
    </delete>
    <delete id="deleteByJobId" parameterType="java.lang.Integer" >
        DELETE FROM xxl_job_logglue
        WHERE `job_id` = #{jobId}
    </delete>
</mapper>
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
New file
@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.gistack.job.admin.dao.XxlJobLogDao">
    <resultMap id="XxlJobLog" type="cn.gistack.job.admin.core.model.XxlJobLog" >
        <result column="id" property="id" />
        <result column="job_group" property="jobGroup" />
        <result column="job_id" property="jobId" />
        <result column="executor_address" property="executorAddress" />
        <result column="executor_handler" property="executorHandler" />
        <result column="executor_param" property="executorParam" />
        <result column="executor_sharding_param" property="executorShardingParam" />
        <result column="executor_fail_retry_count" property="executorFailRetryCount" />
        <result column="trigger_time" property="triggerTime" />
        <result column="trigger_code" property="triggerCode" />
        <result column="trigger_msg" property="triggerMsg" />
        <result column="handle_time" property="handleTime" />
        <result column="handle_code" property="handleCode" />
        <result column="handle_msg" property="handleMsg" />
        <result column="alarm_status" property="alarmStatus" />
    </resultMap>
    <sql id="Base_Column_List">
        t.id,
        t.job_group,
        t.job_id,
        t.executor_address,
        t.executor_handler,
        t.executor_param,
        t.executor_sharding_param,
        t.executor_fail_retry_count,
        t.trigger_time,
        t.trigger_code,
        t.trigger_msg,
        t.handle_time,
        t.handle_code,
        t.handle_msg,
        t.alarm_status
    </sql>
    <select id="pageList" resultMap="XxlJobLog">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_log AS t
        <trim prefix="WHERE" prefixOverrides="AND | OR" >
            <if test="jobId==0 and jobGroup gt 0">
                AND t.job_group = #{jobGroup}
            </if>
            <if test="jobId gt 0">
                AND t.job_id = #{jobId}
            </if>
            <if test="triggerTimeStart != null">
                AND t.trigger_time <![CDATA[ >= ]]> #{triggerTimeStart}
            </if>
            <if test="triggerTimeEnd != null">
                AND t.trigger_time <![CDATA[ <= ]]> #{triggerTimeEnd}
            </if>
            <if test="logStatus == 1" >
                AND t.handle_code = 200
            </if>
            <if test="logStatus == 2" >
                AND (
                    t.trigger_code NOT IN (0, 200) OR
                    t.handle_code NOT IN (0, 200)
                )
            </if>
            <if test="logStatus == 3" >
                AND t.trigger_code = 200
                AND t.handle_code = 0
            </if>
        </trim>
        ORDER BY t.trigger_time DESC
        LIMIT #{offset}, #{pagesize}
    </select>
    <select id="pageListCount" resultType="int">
        SELECT count(1)
        FROM xxl_job_log AS t
        <trim prefix="WHERE" prefixOverrides="AND | OR" >
            <if test="jobId==0 and jobGroup gt 0">
                AND t.job_group = #{jobGroup}
            </if>
            <if test="jobId gt 0">
                AND t.job_id = #{jobId}
            </if>
            <if test="triggerTimeStart != null">
                AND t.trigger_time <![CDATA[ >= ]]> #{triggerTimeStart}
            </if>
            <if test="triggerTimeEnd != null">
                AND t.trigger_time <![CDATA[ <= ]]> #{triggerTimeEnd}
            </if>
            <if test="logStatus == 1" >
                AND t.handle_code = 200
            </if>
            <if test="logStatus == 2" >
                AND (
                    t.trigger_code NOT IN (0, 200) OR
                    t.handle_code NOT IN (0, 200)
                )
            </if>
            <if test="logStatus == 3" >
                AND t.trigger_code = 200
                AND t.handle_code = 0
            </if>
        </trim>
    </select>
    <select id="load" parameterType="java.lang.Long" resultMap="XxlJobLog">
        SELECT <include refid="Base_Column_List" />
        FROM xxl_job_log AS t
        WHERE t.id = #{id}
    </select>
    <insert id="save" parameterType="cn.gistack.job.admin.core.model.XxlJobLog" useGeneratedKeys="true" keyProperty="id" >
        INSERT INTO xxl_job_log (
            `job_group`,
            `job_id`,
            `trigger_time`,
            `trigger_code`,
            `handle_code`
        ) VALUES (
            #{jobGroup},
            #{jobId},
            #{triggerTime},
            #{triggerCode},
            #{handleCode}
        );
        <!--<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
            SELECT LAST_INSERT_ID()
        </selectKey>-->
    </insert>
    <update id="updateTriggerInfo" >
        UPDATE xxl_job_log
        SET
            `trigger_time`= #{triggerTime},
            `trigger_code`= #{triggerCode},
            `trigger_msg`= #{triggerMsg},
            `executor_address`= #{executorAddress},
            `executor_handler`=#{executorHandler},
            `executor_param`= #{executorParam},
            `executor_sharding_param`= #{executorShardingParam},
            `executor_fail_retry_count`= #{executorFailRetryCount}
        WHERE `id`= #{id}
    </update>
    <update id="updateHandleInfo">
        UPDATE xxl_job_log
        SET
            `handle_time`= #{handleTime},
            `handle_code`= #{handleCode},
            `handle_msg`= #{handleMsg}
        WHERE `id`= #{id}
    </update>
    <delete id="delete" >
        delete from xxl_job_log
        WHERE job_id = #{jobId}
    </delete>
    <!--<select id="triggerCountByDay" resultType="java.util.Map" >
        SELECT
            DATE_FORMAT(trigger_time,'%Y-%m-%d') triggerDay,
            COUNT(handle_code) triggerDayCount,
            SUM(CASE WHEN (trigger_code in (0, 200) and handle_code = 0) then 1 else 0 end) as triggerDayCountRunning,
            SUM(CASE WHEN handle_code = 200 then 1 else 0 end) as triggerDayCountSuc
        FROM xxl_job_log
        WHERE trigger_time BETWEEN #{from} and #{to}
        GROUP BY triggerDay
        ORDER BY triggerDay
    </select>-->
    <select id="findLogReport" resultType="java.util.Map" >
        SELECT
            COUNT(handle_code) triggerDayCount,
            SUM(CASE WHEN (trigger_code in (0, 200) and handle_code = 0) then 1 else 0 end) as triggerDayCountRunning,
            SUM(CASE WHEN handle_code = 200 then 1 else 0 end) as triggerDayCountSuc
        FROM xxl_job_log
        WHERE trigger_time BETWEEN #{from} and #{to}
    </select>
    <select id="findClearLogIds" resultType="long" >
        SELECT id FROM xxl_job_log
        <trim prefix="WHERE" prefixOverrides="AND | OR" >
            <if test="jobGroup gt 0">
                AND job_group = #{jobGroup}
            </if>
            <if test="jobId gt 0">
                AND job_id = #{jobId}
            </if>
            <if test="clearBeforeTime != null">
                AND trigger_time <![CDATA[ <= ]]> #{clearBeforeTime}
            </if>
            <if test="clearBeforeNum gt 0">
                AND id NOT in(
                SELECT id FROM(
                SELECT id FROM xxl_job_log AS t
                <trim prefix="WHERE" prefixOverrides="AND | OR" >
                    <if test="jobGroup gt 0">
                        AND t.job_group = #{jobGroup}
                    </if>
                    <if test="jobId gt 0">
                        AND t.job_id = #{jobId}
                    </if>
                </trim>
                ORDER BY t.trigger_time desc
                LIMIT 0, #{clearBeforeNum}
                ) t1
                )
            </if>
        </trim>
        order by id asc
        LIMIT #{pagesize}
    </select>
    <delete id="clearLog" >
        delete from xxl_job_log
        WHERE id in
        <foreach collection="logIds" item="item" open="(" close=")" separator="," >
            #{item}
        </foreach>
    </delete>
    <select id="findFailJobLogIds" resultType="long" >
        SELECT id FROM `xxl_job_log`
        WHERE !(
            (trigger_code in (0, 200) and handle_code = 0)
            OR
            (handle_code = 200)
        )
        AND `alarm_status` = 0
        ORDER BY id ASC
        LIMIT #{pagesize}
    </select>
    <update id="updateAlarmStatus" >
        UPDATE xxl_job_log
        SET
            `alarm_status` = #{newAlarmStatus}
        WHERE `id`= #{logId} AND `alarm_status` = #{oldAlarmStatus}
    </update>
</mapper>
Diff truncated after the above file
skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.eot skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.svg skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.ttf skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.woff skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.css.map skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2 skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/fastclick/fastclick.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.css.map skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.min.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/FontAwesome.otf skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.eot skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.svg skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.ttf skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff2 skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/jquery-slimscroll/jquery.slimscroll.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/jquery/jquery.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/bower_components/moment/moment.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/dist/css/AdminLTE.min.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/dist/css/skins/_all-skins.min.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/dist/js/adminlte.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/icheck.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.png skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue@2x.png skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/favicon.ico skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/common.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/index.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/jobcode.index.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/jobgroup.index.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/jobinfo.index.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/joblog.detail.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/joblog.index.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/login.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/js/user.index.1.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/addon/hint/anyword-hint.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/clike/clike.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/javascript/javascript.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/php/php.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/powershell/powershell.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/python/python.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/codemirror/mode/shell/shell.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/cronGen/cronGen.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/echarts/echarts.common.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/jquery/jquery.validate.min.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/layer.js skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/icon-ext.png skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/icon.png skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/layer.css skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/loading-0.gif skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/loading-1.gif skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/static/plugins/layer/theme/default/loading-2.gif skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/common/common.exception.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/common/common.macro.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/help.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/index.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/jobcode/jobcode.index.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/joblog/joblog.detail.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/joblog/joblog.index.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/login.ftl skjcmanager-ops/skjcmanager-xxljob-admin/src/main/resources/templates/user/user.index.ftl skjcmanager-ops/skjcmanager-xxljob/Dockerfile skjcmanager-ops/skjcmanager-xxljob/pom.xml skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/JobApplication.java skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/config/XxlJobConfig.java skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/controller/TestController.java skjcmanager-ops/skjcmanager-xxljob/src/main/java/cn/gistack/job/executor/jobhandler/SampleXxlJob.java skjcmanager-ops/skjcmanager-xxljob/src/main/resources/application.yml skjcmanager-ops/skjcmanager-xxljob/src/main/resources/logback.xml skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/controller/LeaveController.java skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/controller/NoticeController.java skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/mapper/LeaveMapper.xml skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/mapper/NoticeMapper.java skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/service/INoticeService.java skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/service/impl/LeaveServiceImpl.java skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/service/impl/NoticeServiceImpl.java skjcmanager-service/skjcmanager-desk/src/main/java/cn/gistack/desk/wrapper/NoticeWrapper.java skjcmanager-service/skjcmanager-desk/src/main/resources/application-dev.yml skjcmanager-service/skjcmanager-desk/src/main/resources/application-prod.yml skjcmanager-service/skjcmanager-desk/src/main/resources/application-test.yml skjcmanager-service/skjcmanager-system/Dockerfile skjcmanager-service/skjcmanager-system/pom.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/SystemApplication.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/AuthClientController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DataScopeController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DeptController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DictBizController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/DictController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/PostController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/SearchController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/TenantController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/TenantPackageController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/controller/TopMenuController.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/excel/RegionExcel.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/ApiScopeClient.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/DataScopeClient.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/DictBizClient.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/DictClient.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/feign/SysClient.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/ApiScopeMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DataScopeMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DictBizMapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DictBizMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/DictMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/MenuMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/ParamMapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/ParamMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/PostMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/RegionMapper.xml skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/RoleMenuMapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/TenantMapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/mapper/TopMenuMapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IApiScopeService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IDataScopeService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IDeptService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IParamService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IRoleScopeService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/IRoleService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/ITenantPackageService.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/DeptServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/DictBizServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/MenuServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/RegionServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/TenantServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/TopMenuServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/service/impl/TopMenuSettingServiceImpl.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/DeptWrapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/DictWrapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/PostWrapper.java skjcmanager-service/skjcmanager-system/src/main/java/cn/gistack/system/wrapper/RoleWrapper.java skjcmanager-service/skjcmanager-system/src/main/resources/application-dev.yml skjcmanager-service/skjcmanager-user/Dockerfile skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/UserApplication.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/excel/UserExcel.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/excel/UserImporter.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/feign/UserClient.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserDeptMapper.xml skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserOauthMapper.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserOtherMapper.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserWebMapper.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/mapper/UserWebMapper.xml skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/IUserDeptService.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/IUserOauthService.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/IUserSearchService.java skjcmanager-service/skjcmanager-user/src/main/java/cn/gistack/system/user/service/impl/UserServiceImpl.java skjcmanager-service/skjcmanager-user/src/main/resources/application-dev.yml skjcmanager-service/skjcmanager-user/src/main/resources/application-prod.yml skjcmanager-service/skjcmanager-user/src/main/resources/application-test.yml